From 462ba36be8b7d5e8ba8604f7d86513f8eae33f0f Mon Sep 17 00:00:00 2001
From: Philipinho <16838612+Philipinho@users.noreply.github.com>
Date: Thu, 2 Jul 2026 02:30:20 +0100
Subject: [PATCH] feat: rename kanban group by clicking column name
---
.../ee/base/components/kanban/base-kanban.tsx | 3 +-
.../kanban/kanban-column-header.tsx | 10 +-
.../components/kanban/kanban-column-title.tsx | 113 ++++++++++++++++++
.../base/components/kanban/kanban-column.tsx | 5 +-
.../src/ee/base/styles/kanban.module.css | 15 +++
5 files changed, 139 insertions(+), 7 deletions(-)
create mode 100644 apps/client/src/ee/base/components/kanban/kanban-column-title.tsx
diff --git a/apps/client/src/ee/base/components/kanban/base-kanban.tsx b/apps/client/src/ee/base/components/kanban/base-kanban.tsx
index 2ad2df93b..f19f37d6b 100644
--- a/apps/client/src/ee/base/components/kanban/base-kanban.tsx
+++ b/apps/client/src/ee/base/components/kanban/base-kanban.tsx
@@ -30,7 +30,7 @@ type BaseKanbanProps = {
export function BaseKanban({ base, view, pageId, embedded, editable, viewFilter }: BaseKanbanProps) {
const { t } = useTranslation();
- const { groupByPropertyId, columns, hasValidGroupBy } = useKanbanColumns(base, view);
+ const { groupByPropertyId, groupByProperty, columns, hasValidGroupBy } = useKanbanColumns(base, view);
const updateView = useUpdateViewMutation();
const moveCard = useKanbanMoveCardMutation();
const { openRow } = useRowDetailModal(pageId);
@@ -201,6 +201,7 @@ export function BaseKanban({ base, view, pageId, embedded, editable, viewFilter
column={column}
viewFilter={viewFilter}
groupByPropertyId={groupByPropertyId!}
+ groupByProperty={groupByProperty}
canEdit={editable}
onOpenRow={handleOpenRow}
onHide={hideColumn}
diff --git a/apps/client/src/ee/base/components/kanban/kanban-column-header.tsx b/apps/client/src/ee/base/components/kanban/kanban-column-header.tsx
index 3e53080f5..99248e7e9 100644
--- a/apps/client/src/ee/base/components/kanban/kanban-column-header.tsx
+++ b/apps/client/src/ee/base/components/kanban/kanban-column-header.tsx
@@ -3,22 +3,24 @@ import { useTranslation } from "react-i18next";
import { ActionIcon, Menu, Text } from "@mantine/core";
import { IconDots, IconPlus, IconGripVertical } from "@tabler/icons-react";
import clsx from "clsx";
-import { KanbanColumn } from "@/ee/base/types/base.types";
+import { IBaseProperty, KanbanColumn } from "@/ee/base/types/base.types";
import { choiceColor } from "@/ee/base/components/cells/choice-color";
import { useKanbanColumnDnd } from "@/ee/base/hooks/use-kanban-column-dnd";
import { BaseDropEdgeIndicator } from "@/ee/base/components/grid/base-drop-edge-indicator";
+import { KanbanColumnTitle } from "@/ee/base/components/kanban/kanban-column-title";
import classes from "@/ee/base/styles/kanban.module.css";
type KanbanColumnHeaderProps = {
column: KanbanColumn;
pageId: string;
+ property: IBaseProperty | undefined;
count?: string;
canEdit: boolean;
onHide: () => void;
onAddCard: () => void;
};
-export function KanbanColumnHeader({ column, pageId, count, canEdit, onHide, onAddCard }: KanbanColumnHeaderProps) {
+export function KanbanColumnHeader({ column, pageId, property, count, canEdit, onHide, onAddCard }: KanbanColumnHeaderProps) {
const { t } = useTranslation();
const dotColor = column.color
? choiceColor(column.color).color as string
@@ -49,9 +51,7 @@ export function KanbanColumnHeader({ column, pageId, count, canEdit, onHide, onA
background: dotColor,
}}
/>
-
- {column.isNoValue ? t("No value") : column.name}
-
+
{count !== undefined && {count}}
{canEdit && (
<>
diff --git a/apps/client/src/ee/base/components/kanban/kanban-column-title.tsx b/apps/client/src/ee/base/components/kanban/kanban-column-title.tsx
new file mode 100644
index 000000000..fe13996ed
--- /dev/null
+++ b/apps/client/src/ee/base/components/kanban/kanban-column-title.tsx
@@ -0,0 +1,113 @@
+import { useCallback, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Button, Group, Popover, Text, TextInput, UnstyledButton } from "@mantine/core";
+import { IBaseProperty, KanbanColumn, SelectTypeOptions } from "@/ee/base/types/base.types";
+import { useUpdatePropertyMutation } from "@/ee/base/queries/base-property-query";
+import classes from "@/ee/base/styles/kanban.module.css";
+
+type KanbanColumnTitleProps = {
+ column: KanbanColumn;
+ property: IBaseProperty | undefined;
+ pageId: string;
+ canEdit: boolean;
+};
+
+export function KanbanColumnTitle({ column, property, pageId, canEdit }: KanbanColumnTitleProps) {
+ const { t } = useTranslation();
+ const [opened, setOpened] = useState(false);
+ const [draft, setDraft] = useState("");
+ const updateProperty = useUpdatePropertyMutation();
+
+ const commit = useCallback(() => {
+ setOpened(false);
+ const name = draft.trim();
+ const options = property?.typeOptions as SelectTypeOptions | undefined;
+ if (!property || !options || !name || name === column.name) return;
+ if (!options.choices.some((c) => c.id === column.key)) return;
+ updateProperty.mutate({
+ propertyId: property.id,
+ pageId,
+ typeOptions: {
+ ...options,
+ choices: options.choices.map((c) =>
+ c.id === column.key ? { ...c, name } : c,
+ ),
+ },
+ });
+ }, [draft, property, column.name, column.key, pageId, updateProperty]);
+
+ const toggle = useCallback(() => {
+ if (opened) {
+ commit();
+ } else {
+ setDraft(column.name);
+ setOpened(true);
+ }
+ }, [opened, commit, column.name]);
+
+ const cancel = useCallback(() => setOpened(false), []);
+
+ if (!canEdit || column.isNoValue || !property) {
+ return (
+
+ {column.isNoValue ? t("No value") : column.name}
+
+ );
+ }
+
+ return (
+ {
+ if (!next) commit();
+ }}
+ position="bottom-start"
+ shadow="md"
+ width={240}
+ withinPortal
+ trapFocus
+ returnFocus
+ closeOnClickOutside
+ closeOnEscape={false}
+ >
+
+
+
+ {column.name}
+
+
+
+ e.stopPropagation()}
+ onKeyDown={(e) => {
+ if (e.key === "Escape") {
+ e.preventDefault();
+ e.stopPropagation();
+ cancel();
+ }
+ }}
+ >
+
+ e.currentTarget.select()}
+ onChange={(e) => setDraft(e.currentTarget.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ commit();
+ }
+ }}
+ />
+
+
+
+
+ );
+}
diff --git a/apps/client/src/ee/base/components/kanban/kanban-column.tsx b/apps/client/src/ee/base/components/kanban/kanban-column.tsx
index 95c1a1858..9975f4f66 100644
--- a/apps/client/src/ee/base/components/kanban/kanban-column.tsx
+++ b/apps/client/src/ee/base/components/kanban/kanban-column.tsx
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef } from "react";
import { generateJitteredKeyBetween } from "fractional-indexing-jittered";
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
-import { type IBase, type IBaseRow, type IBaseView, type FilterGroup, type KanbanColumn as KanbanColumnType, KANBAN_CARD_DRAG_TYPE } from "@/ee/base/types/base.types";
+import { type IBase, type IBaseProperty, type IBaseRow, type IBaseView, type FilterGroup, type KanbanColumn as KanbanColumnType, KANBAN_CARD_DRAG_TYPE } from "@/ee/base/types/base.types";
import { buildColumnFilter } from "@/ee/base/services/kanban-column-filter";
import { formatKanbanCount } from "@/ee/base/services/format-kanban-count";
import { useKanbanColumnAutoScroll } from "@/ee/base/hooks/use-kanban-autoscroll";
@@ -19,6 +19,7 @@ type KanbanColumnProps = {
column: KanbanColumnType;
viewFilter: FilterGroup | undefined;
groupByPropertyId: string;
+ groupByProperty: IBaseProperty | undefined;
canEdit: boolean;
onOpenRow: (rowId: string) => void;
onHide: (columnKey: string) => void;
@@ -33,6 +34,7 @@ export function KanbanColumn({
column,
viewFilter,
groupByPropertyId,
+ groupByProperty,
canEdit,
onOpenRow,
onHide,
@@ -139,6 +141,7 @@ export function KanbanColumn({
onHide(column.key)}
diff --git a/apps/client/src/ee/base/styles/kanban.module.css b/apps/client/src/ee/base/styles/kanban.module.css
index 915188c73..c6aaa1ddd 100644
--- a/apps/client/src/ee/base/styles/kanban.module.css
+++ b/apps/client/src/ee/base/styles/kanban.module.css
@@ -76,6 +76,21 @@
cursor: grabbing;
}
+.columnTitleButton {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ padding: 2px 6px;
+ margin: -2px -6px;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+.columnTitleButton:hover {
+ background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
+}
+
.cardList {
display: flex;
flex-direction: column;