diff --git a/apps/web/content/docs/dropdown-menu/index.mdx b/apps/web/content/docs/dropdown-menu/index.mdx index 4201afd2..dfe737e5 100644 --- a/apps/web/content/docs/dropdown-menu/index.mdx +++ b/apps/web/content/docs/dropdown-menu/index.mdx @@ -205,11 +205,17 @@ Then, compose with the same parts for the root menu to render the popup. -### Subpage navigation +### Subpage -Use subpages to model deeper menu flows without relying on deep-search/data-first APIs. +Use subpages as a natural replacement to modals, great for workflows such as item creation. - +Use the `` part, give it a `pageId`, and wrap it around a new `` with the subpage content. + +Render a `` and wire it up using the `targetPageId` prop. + + + + ### Virtualized diff --git a/apps/web/registry/__index__.tsx b/apps/web/registry/__index__.tsx index db54a5ba..a1ec655b 100644 --- a/apps/web/registry/__index__.tsx +++ b/apps/web/registry/__index__.tsx @@ -114,6 +114,27 @@ export const examples: RegistryIndex = { 'registry/examples/dropdown-menu/subpage-linear/icons.tsx', ], }, + 'dropdown-menu-subpage': { + name: 'dropdown-menu-subpage', + type: 'registry:example', + component: React.lazy( + () => import('@/registry/examples/dropdown-menu/subpage'), + ), + files: ['registry/examples/dropdown-menu/subpage/index.tsx'], + }, + 'dropdown-menu-linear-subpage-label-creation': { + name: 'dropdown-menu-linear-subpage-label-creation', + type: 'registry:example', + component: React.lazy( + () => + import( + '@/registry/examples/dropdown-menu/linear-subpage-label-creation' + ), + ), + files: [ + 'registry/examples/dropdown-menu/linear-subpage-label-creation/index.tsx', + ], + }, 'dropdown-menu-search': { name: 'dropdown-menu-search', type: 'registry:example', diff --git a/apps/web/registry/examples/dropdown-menu/deep-search-linear/index.tsx b/apps/web/registry/examples/dropdown-menu/deep-search-linear/index.tsx index 08579e37..b9802fd7 100644 --- a/apps/web/registry/examples/dropdown-menu/deep-search-linear/index.tsx +++ b/apps/web/registry/examples/dropdown-menu/deep-search-linear/index.tsx @@ -275,11 +275,16 @@ function buildMenuContent(): NodeDef[] { ) return [ + aiFilterSubpage, + { + kind: 'separator', + id: 'ai-filter-separator', + render: ({ props }) => , + }, statusMenu, assigneeMenu, priorityMenu, labelsMenu, - aiFilterSubpage, projectPropertiesMenu, ] } diff --git a/apps/web/registry/examples/dropdown-menu/deep-search-subpages-linear/index.tsx b/apps/web/registry/examples/dropdown-menu/deep-search-subpages-linear/index.tsx index c5578019..804745c8 100644 --- a/apps/web/registry/examples/dropdown-menu/deep-search-subpages-linear/index.tsx +++ b/apps/web/registry/examples/dropdown-menu/deep-search-subpages-linear/index.tsx @@ -277,7 +277,7 @@ function createLabelsSubmenu(params: { - + ( <> @@ -810,9 +810,9 @@ export default function DropdownMenuDeepSearchSubpagesLinear() { debug={{ showSafeTriangleArea: { enabled: true, - showMissState: true, + showMissState: false, missColor: '#ff4d4f', - missFreezeDuration: 180, + missFreezeDuration: 2000, }, }} > diff --git a/apps/web/registry/examples/dropdown-menu/subpage-linear/index.tsx b/apps/web/registry/examples/dropdown-menu/subpage-linear/index.tsx index 4b3dfdb4..2f40c223 100644 --- a/apps/web/registry/examples/dropdown-menu/subpage-linear/index.tsx +++ b/apps/web/registry/examples/dropdown-menu/subpage-linear/index.tsx @@ -221,7 +221,7 @@ function SubmenuSurface({ export default function DropdownMenuSubpageLinear() { return ( - }> + }> Filter @@ -242,13 +242,6 @@ export default function DropdownMenuSubpageLinear() { - - - AI Filter - - - - } label="Status" /> @@ -326,10 +319,7 @@ export default function DropdownMenuSubpageLinear() { label="Project properties" /> - + (labelsData) + const [search, setSearch] = React.useState('') + + const selectedLabels = React.useMemo( + () => labels.filter((label) => label.checked), + [labels], + ) + + const hasSelectedLabels = React.useMemo( + () => selectedLabels.length > 0, + [selectedLabels.length], + ) + + const normalizedSearch = React.useMemo( + () => search.trim().toLowerCase(), + [search], + ) + + const foundExactMatch = React.useMemo( + () => labels.find((label) => normalizedSearch === label.name.toLowerCase()), + [labels, normalizedSearch], + ) + + function createLabel(value: LabelRecord) { + setLabels((prev) => [...prev, { ...value, checked: true }]) + toast(`Created label: ${value.name}`) + } + + function handleLabelCheckedChange(id: string, checked: boolean) { + setLabels((prev) => + prev.map((label) => (label.id === id ? { ...label, checked } : label)), + ) + } + + return ( +
+
+ Select labels to add them to the list. + + Then, search for a label that doesn't exist (e.g. "Bug") and create + it. + +
+ +
+ {selectedLabels.map((label) => ( +
+
+ {label.name} +
+ ))} + + } + > + + {selectedLabels.length === 0 && 'Add label'} + +
+ + + + + + + {labels.map((label) => ( + + handleLabelCheckedChange(label.id, checked) + } + > + + +
+ + {label.name} + + ))} + {normalizedSearch.length > 0 && !foundExactMatch && ( + + } /> + + Create new label:{' '} + + "{search}" + + + + )} + + + + + + + + {CREATABLE_LABEL_COLORS.map((color) => ( + + createLabel({ + id: search, + name: search, + color: color.id, + }) + } + > + +
+ + + {color.label} + + ))} + + + + + + + +
+ ) +} diff --git a/apps/web/registry/ui/dropdown-menu/index.tsx b/apps/web/registry/ui/dropdown-menu/index.tsx index 8408bf6e..601ffc8f 100644 --- a/apps/web/registry/ui/dropdown-menu/index.tsx +++ b/apps/web/registry/ui/dropdown-menu/index.tsx @@ -160,15 +160,7 @@ function Root({ [onOpenChange], ) - return ( - { - console.log('highlight changed to', id, 'at index', index) - }} - {...props} - /> - ) + return } const Trigger = Primitive.Trigger @@ -892,13 +884,6 @@ function Submenu({ > >[1], ) => { - console.log('[DropdownMenu.Submenu] onOpenChange:', { - open, - reason: eventDetails.reason, - hasEvent: !!eventDetails.event, - hasCancel: typeof eventDetails.cancel === 'function', - }) - // Prevent closing when clicking on feedback toolbar elements if ( !open && @@ -910,13 +895,7 @@ function Submenu({ const feedbackToolbar = target?.closest( '[data-feedback-toolbar="true"]', ) - console.log('[DropdownMenu.Submenu] outside-press check:', { - target: target?.tagName, - targetClasses: target?.className, - feedbackToolbar: !!feedbackToolbar, - }) if (feedbackToolbar) { - console.log('[DropdownMenu.Submenu] Cancelling close!') eventDetails.cancel() return }