"use client" import * as React from "react" import { type RegistryItem } from "shadcn/schema" import useSWR from "swr" import { useDesignSystemSearchParams } from "@/app/(app)/(create)/lib/search-params" import { groupItemsByType } from "@/app/(app)/(create)/lib/utils" const ACTION_MENU_OPEN_KEY = "create:action-menu-open" type ActionMenuItem = { id: string type: string label: string registryName: string } type ActionMenuGroup = { type: string title: string items: ActionMenuItem[] } type ActionMenuSourceItem = Pick const SEARCH_KEYWORDS: Record = { "registry:block": "block blocks component components", "registry:item": "item items component components", } function sortRegistryGroups(groups: ReturnType) { return [...groups].sort((a, b) => { if (a.type === b.type) { return a.title.localeCompare(b.title) } if (a.type === "registry:block") { return -1 } if (b.type === "registry:block") { return 1 } return a.title.localeCompare(b.title) }) } export function useActionMenu( itemsByBase: Record ) { const [params, setParams] = useDesignSystemSearchParams() const { data: open = false, mutate: setOpenData } = useSWR( ACTION_MENU_OPEN_KEY, { fallbackData: false, revalidateOnFocus: false, revalidateIfStale: false, revalidateOnReconnect: false, } ) const groups = React.useMemo(() => { const currentBaseItems = itemsByBase?.[params.base] ?? [] const sortedRegistryGroups = sortRegistryGroups( groupItemsByType(currentBaseItems) ) return sortedRegistryGroups.map((group) => ({ type: group.type, title: group.title, items: group.items.map((item) => ({ id: `${group.type}:${item.name}`, type: group.type, label: item.title ?? item.name, registryName: item.name, })), })) }, [itemsByBase, params.base]) const activeRegistryName = params.item const handleSelect = React.useCallback( (registryName: string) => { setParams({ item: registryName }) void setOpenData(false, { revalidate: false }) }, [setOpenData, setParams] ) const handleOpenChange = React.useCallback( (nextOpen: boolean) => { void setOpenData(nextOpen, { revalidate: false }) }, [setOpenData] ) const getCommandValue = React.useCallback((item: ActionMenuItem) => { const keywords = SEARCH_KEYWORDS[item.type] ?? item.type.replace(":", " ") return `${item.label ?? ""} ${keywords}`.trim() }, []) React.useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "p" && (e.metaKey || e.ctrlKey)) { e.preventDefault() void setOpenData((currentOpen = false) => !currentOpen, { revalidate: false, }) } } document.addEventListener("keydown", down) return () => { document.removeEventListener("keydown", down) } }, [setOpenData]) return { activeRegistryName, getCommandValue, groups, handleSelect, open, setOpen: handleOpenChange, } } export function useActionMenuTrigger() { const { mutate: setOpenData } = useSWR(ACTION_MENU_OPEN_KEY, { fallbackData: false, revalidateOnFocus: false, revalidateIfStale: false, revalidateOnReconnect: false, }) const openActionMenu = React.useCallback(() => { void setOpenData(true, { revalidate: false }) }, [setOpenData]) return { openActionMenu, } }