Files
upstream-shadcn-ui/apps/v4/app/(app)/(create)/hooks/use-action-menu.ts
upstream-mirror 1416db152c
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped
snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
2026-09-15 22:44:43 +05:00

143 lines
3.5 KiB
TypeScript

"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<RegistryItem, "name" | "title" | "type">
const SEARCH_KEYWORDS: Record<string, string> = {
"registry:block": "block blocks component components",
"registry:item": "item items component components",
}
function sortRegistryGroups(groups: ReturnType<typeof groupItemsByType>) {
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<string, ActionMenuSourceItem[]>
) {
const [params, setParams] = useDesignSystemSearchParams()
const { data: open = false, mutate: setOpenData } = useSWR<boolean>(
ACTION_MENU_OPEN_KEY,
{
fallbackData: false,
revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: false,
}
)
const groups = React.useMemo<ActionMenuGroup[]>(() => {
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<boolean>(ACTION_MENU_OPEN_KEY, {
fallbackData: false,
revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: false,
})
const openActionMenu = React.useCallback(() => {
void setOpenData(true, { revalidate: false })
}, [setOpenData])
return {
openActionMenu,
}
}