mirror of
https://github.com/cosscom/coss.git
synced 2026-09-14 20:06:50 +08:00
e7a3a00c5f
* feat: 1:1 font replacement * feat: add examples app * style: update font sizes and improve component styles for consistency * update font * style: restore default font weights * refactor: move local fonts to packages * feat: update font weights and refactor font imports in examples app * fix CommandDialog not closing on ESC * refactor: command component improvements * chore: remove console log from command demo * refactor: update Command and Menu components to use <kbd> for shortcuts and improve styling * style: fix formatting in globals.css by removing unnecessary whitespace * fix: refactor group and toggle-group to prevent flickering menu trigger at the end of a group * refactor: toggle group and group improvements * refactor: update toggle component styles for consistency and clarity * refactor: major styling improvements * fix: dialogs * refactor: prefer not-dark: * fix: scrollarea overscroll * refactor: integrate ScrollArea component throughout * style: various improvements * style: mc * style: update button sizes and improve mobile footer layout * style: adjust code component font size and refactor particle card container className * style: improve mobile footer * refactor: improve carious components * style: mc * chore: implement Cal.ai badge * feat: implement new event type dialog * refactor: clean up event types and improve page layout * feat: implement ai-mode in the cmdk component * chore: format * fix: remove outline from CommandDialogPopup * fix: update separator className to use bg-input instead of bg-border * fix: cubic issues * style: update font size in code components for consistency * style: improve shadows * fix: update badge and toggle components to use bg-background * chore: update color references * chore: fix registry * chore: sync package component + build registry * fix(accordion): issue 596 * fix: issue 609 * docs: updates * chore: improve sync script * feat: add AI-powered command particle * feat: improve switch and add menu checkbox switch variant * style: toggle improvements * fix: docs container * style: remove css vars from examples * chore: format * feat: add more example pages
244 lines
7.4 KiB
TypeScript
244 lines
7.4 KiB
TypeScript
"use client";
|
|
|
|
import { CommandPanel } from "@coss/ui/components/command";
|
|
import {
|
|
ArrowTurnBackwardIcon,
|
|
Atom01Icon,
|
|
BookOpen02Icon,
|
|
Search01Icon,
|
|
} from "@hugeicons/core-free-icons";
|
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
import Link from "next/link";
|
|
import type { ComponentProps } from "react";
|
|
import * as React from "react";
|
|
import { useConfig } from "@/hooks/use-config";
|
|
import { useIsMac } from "@/hooks/use-is-mac";
|
|
import type { source } from "@/lib/source";
|
|
import { useCopyToClipboard } from "@/registry/default/hooks/use-copy-to-clipboard";
|
|
import { Button } from "@/registry/default/ui/button";
|
|
import {
|
|
Command,
|
|
CommandCollection,
|
|
CommandDialog,
|
|
CommandDialogPopup,
|
|
CommandDialogTrigger,
|
|
CommandEmpty,
|
|
CommandFooter,
|
|
CommandGroup,
|
|
CommandGroupLabel,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
} from "@/registry/default/ui/command";
|
|
import { Kbd, KbdGroup } from "@/registry/default/ui/kbd";
|
|
|
|
interface PageItem {
|
|
value: string;
|
|
label: string;
|
|
url: string;
|
|
isComponent: boolean;
|
|
keywords?: string[];
|
|
}
|
|
|
|
interface PageGroup {
|
|
value: string;
|
|
items: PageItem[];
|
|
}
|
|
|
|
export function CommandMenu({
|
|
tree,
|
|
navItems,
|
|
...props
|
|
}: ComponentProps<typeof CommandDialog> & {
|
|
tree: typeof source.pageTree;
|
|
navItems?: { href: string; label: string }[];
|
|
}) {
|
|
const isMac = useIsMac();
|
|
const [config] = useConfig();
|
|
const { copyToClipboard } = useCopyToClipboard();
|
|
const [open, setOpen] = React.useState(false);
|
|
const [selectedType, setSelectedType] = React.useState<
|
|
"page" | "component" | null
|
|
>(null);
|
|
const [copyPayload, setCopyPayload] = React.useState("");
|
|
const packageManager = config.packageManager || "pnpm";
|
|
|
|
// Convert tree structure to grouped items
|
|
const groupedItems = React.useMemo<PageGroup[]>(() => {
|
|
const groups: PageGroup[] = [];
|
|
|
|
// Add nav items group
|
|
if (navItems && navItems.length > 0) {
|
|
groups.push({
|
|
items: navItems.map((item) => ({
|
|
isComponent: false,
|
|
keywords: ["nav", "navigation", item.label.toLowerCase()],
|
|
label: item.label,
|
|
url: item.href,
|
|
value: `Navigation ${item.label}`,
|
|
})),
|
|
value: "Pages",
|
|
});
|
|
}
|
|
|
|
// Add tree groups
|
|
tree.children.forEach((group) => {
|
|
if (group.type === "folder") {
|
|
const items: PageItem[] = [];
|
|
group.children.forEach((item) => {
|
|
if (item.type === "page") {
|
|
const isComponent = item.url.includes("/components/");
|
|
const itemName = item.name?.toString() || "";
|
|
items.push({
|
|
isComponent,
|
|
keywords: isComponent ? ["component"] : undefined,
|
|
label: itemName,
|
|
url: item.url,
|
|
value: itemName ? `${group.name} ${itemName}` : "",
|
|
});
|
|
}
|
|
});
|
|
if (items.length > 0) {
|
|
groups.push({
|
|
items,
|
|
value:
|
|
typeof group.name === "string" ? group.name : String(group.name),
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return groups;
|
|
}, [tree, navItems]);
|
|
|
|
const handlePageHighlight = React.useCallback(
|
|
(item: PageItem) => {
|
|
if (item.isComponent) {
|
|
const componentName = item.url.split("/").pop();
|
|
setSelectedType("component");
|
|
const registryItem = `@coss/${componentName}`;
|
|
let cmd: string;
|
|
switch (packageManager) {
|
|
case "pnpm":
|
|
cmd = `pnpm dlx shadcn@latest add ${registryItem}`;
|
|
break;
|
|
case "bun":
|
|
cmd = `bunx --bun shadcn@latest add ${registryItem}`;
|
|
break;
|
|
case "yarn":
|
|
cmd = `yarn dlx shadcn@latest add ${registryItem}`;
|
|
break;
|
|
default:
|
|
cmd = `npx shadcn@latest add ${registryItem}`;
|
|
}
|
|
|
|
setCopyPayload(cmd);
|
|
} else {
|
|
setSelectedType("page");
|
|
setCopyPayload("");
|
|
}
|
|
},
|
|
[packageManager],
|
|
);
|
|
|
|
React.useEffect(() => {
|
|
const down = (e: KeyboardEvent) => {
|
|
if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || e.key === "/") {
|
|
if (
|
|
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
|
|
e.target instanceof HTMLInputElement ||
|
|
e.target instanceof HTMLTextAreaElement ||
|
|
e.target instanceof HTMLSelectElement
|
|
) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
setOpen((open) => !open);
|
|
}
|
|
|
|
if (e.key === "c" && (e.metaKey || e.ctrlKey)) {
|
|
if (selectedType === "page" || selectedType === "component") {
|
|
copyToClipboard(copyPayload);
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener("keydown", down);
|
|
return () => document.removeEventListener("keydown", down);
|
|
}, [copyPayload, selectedType, copyToClipboard]);
|
|
|
|
return (
|
|
<CommandDialog onOpenChange={setOpen} open={open} {...props}>
|
|
<CommandDialogTrigger render={<Button variant="outline" />}>
|
|
<HugeiconsIcon icon={Search01Icon} strokeWidth={2} />
|
|
<KbdGroup className="gap-1">
|
|
<Kbd>{isMac ? "⌘" : "Ctrl"}</Kbd>
|
|
<Kbd className="aspect-square">K</Kbd>
|
|
</KbdGroup>
|
|
</CommandDialogTrigger>
|
|
<CommandDialogPopup>
|
|
<Command
|
|
items={groupedItems}
|
|
onItemHighlighted={(highlightedValue) => {
|
|
const item = highlightedValue as PageItem | null;
|
|
if (item) {
|
|
handlePageHighlight(item);
|
|
}
|
|
}}
|
|
>
|
|
<CommandInput placeholder="Search documentation…" />
|
|
<CommandPanel>
|
|
<CommandEmpty>No results found.</CommandEmpty>
|
|
<CommandList>
|
|
{(group: PageGroup, _index: number) => (
|
|
<CommandGroup items={group.items} key={group.value}>
|
|
<CommandGroupLabel>{group.value}</CommandGroupLabel>
|
|
<CommandCollection>
|
|
{(item: PageItem) => (
|
|
<CommandItem
|
|
className="flex w-full items-center"
|
|
key={item.value}
|
|
render={
|
|
<Link
|
|
href={item.url}
|
|
onNavigate={() => setOpen(false)}
|
|
/>
|
|
}
|
|
>
|
|
<HugeiconsIcon
|
|
className="mr-2 h-4 w-4 opacity-80"
|
|
icon={item.isComponent ? Atom01Icon : BookOpen02Icon}
|
|
strokeWidth={2}
|
|
/>
|
|
<span className="flex-1">{item.label}</span>
|
|
</CommandItem>
|
|
)}
|
|
</CommandCollection>
|
|
</CommandGroup>
|
|
)}
|
|
</CommandList>
|
|
</CommandPanel>
|
|
<CommandFooter>
|
|
<div className="flex items-center gap-2">
|
|
<span className="whitespace-nowrap">Go to Page</span>
|
|
<Kbd>
|
|
<HugeiconsIcon icon={ArrowTurnBackwardIcon} strokeWidth={2} />
|
|
</Kbd>
|
|
</div>
|
|
{copyPayload && (
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<span className="truncate font-mono">{copyPayload}</span>
|
|
<KbdGroup>
|
|
<Kbd>{isMac ? "⌘" : "Ctrl"}</Kbd>
|
|
<Kbd>C</Kbd>
|
|
</KbdGroup>
|
|
</div>
|
|
)}
|
|
</CommandFooter>
|
|
</Command>
|
|
</CommandDialogPopup>
|
|
</CommandDialog>
|
|
);
|
|
}
|