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;