feat: kanban group editing (#2322)

* feat: support opening property menu directly on options panel

* refactor: expose group-by property from useKanbanColumns

* feat: rename kanban group by clicking column name

* feat: edit group-by property from kanban column menu

* fix: license-gate base and kanban inserts

* fix: add missing bases strings to en-US translation file
This commit is contained in:
Philip Okugbe
2026-07-03 11:35:26 +01:00
committed by GitHub
parent 6ff7f69137
commit 8a2c457ab9
15 changed files with 484 additions and 52 deletions
@@ -10,6 +10,7 @@ import {
Paper,
ScrollArea,
Text,
Tooltip,
UnstyledButton,
VisuallyHidden,
} from "@mantine/core";
@@ -18,6 +19,7 @@ import clsx from "clsx";
import { useTranslation } from "react-i18next";
import { useHasFeature } from "@/ee/hooks/use-feature";
import { Feature } from "@/ee/features";
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
const CommandList = ({
items,
@@ -37,11 +39,12 @@ const CommandList = ({
const [selectionAnnouncement, setSelectionAnnouncement] = useState("");
const hasBases = useHasFeature(Feature.BASES);
// Title must match the "Base (Inline)" item in menu-items.ts. Without the
// bases entitlement the item stays visible but disabled; an expired license
// the client can't detect falls through to a handled create failure.
const upgradeLabel = useUpgradeLabel();
// Without the bases entitlement the item stays visible but inert; an
// expired license the client can't detect falls through to a handled
// create failure.
const isItemDisabled = (item: SlashMenuItemType) =>
!hasBases && item.title === "Base (Inline)";
!hasBases && item.requiresBases === true;
const flatItems = useMemo(() => {
return Object.values(items).flat();
@@ -152,18 +155,22 @@ const CommandList = ({
const itemIndex = flatIndex;
const disabled = isItemDisabled(item);
return (
<Tooltip
key={itemIndex}
label={upgradeLabel}
disabled={!disabled}
position="right"
>
<UnstyledButton
data-item-index={itemIndex}
key={itemIndex}
id={`slash-command-option-${itemIndex}`}
role="option"
aria-selected={itemIndex === selectedIndex}
aria-disabled={disabled}
disabled={disabled}
onClick={() => selectItem(itemIndex)}
className={clsx(classes.menuBtn, {
[classes.selectedItem]: itemIndex === selectedIndex,
[classes.disabledItem]: disabled,
[classes.gatedItem]: disabled,
})}
>
<Group wrap="nowrap">
@@ -188,6 +195,7 @@ const CommandList = ({
)}
</Group>
</UnstyledButton>
</Tooltip>
);
})}
</div>
@@ -366,6 +366,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
description: "Insert an inline base on this page",
searchTerms: ["base", "database", "table", "grid", "spreadsheet"],
icon: IconTable,
requiresBases: true,
command: ({ editor, range }: CommandProps) => {
insertBaseEmbedBlock(editor, { range });
},
@@ -375,6 +376,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
description: "Insert a kanban board on this page",
searchTerms: ["kanban", "board", "cards", "status", "task", "database"],
icon: IconLayoutKanban,
requiresBases: true,
command: ({ editor, range }: CommandProps) => {
insertBaseEmbedBlock(editor, { range, template: "kanban" });
},
@@ -26,7 +26,6 @@
}
}
.disabledItem {
opacity: 0.45;
cursor: not-allowed;
.gatedItem {
opacity: 0.7;
}
@@ -21,6 +21,7 @@ export type SlashMenuItemType = {
searchTerms: string[];
command: (props: CommandProps) => void;
disable?: (editor: ReturnType<typeof useEditor>) => boolean;
requiresBases?: true;
};
export type SlashMenuGroupedItemsType = {