mirror of
https://github.com/jakejarvis/stanza.git
synced 2026-10-16 06:15:43 -04:00
feat: replace cmdk-backed CommandDialog with a bespoke search dialog and @tanstack/react-hotkeys
- Remove `cmdk` and the generated `src/components/ui/command.tsx` shadcn primitive; replace `CommandDialog` / `CommandInput` / `CommandList` / `CommandItem` in `SiteSearch` with a plain `Dialog` + `<input>` + `<button>` elements so the component owns its own rendering without the cmdk abstraction layer - Add `@tanstack/react-hotkeys` and replace the hand-rolled `keydown` listener with `useHotkey(HOTKEY, …)`; use `formatForDisplay` (deferred to mount to avoid SSR hydration mismatch) so the `<Kbd>` badge shows `⌘K` on macOS and `Ctrl+K` on Windows/Linux - Implement client-side fuzzy filtering via a local `matches` helper (splits query on whitespace, checks every term against a joined haystack of group/id/label/description) and drive the list from derived `filtered` / `groups` / `flat` memos - Add keyboard navigation — `ArrowDown`/`ArrowUp` cycle `activeIndex` through the flat item list, `Enter` selects; `onPointerMove` on each row syncs hover → `activeIndex`; the active row scrolls into view via a `data-index` attribute + `scrollIntoView` - Reset `query` and `activeIndex` when the dialog opens; reset `activeIndex` whenever `query` changes - Pin `fumadocs-ui` to `npm:@fumadocs/base-ui@^16.9.0`
This commit is contained in:
5 files changed
+291
-1266
No files matched your search
@@ -22,6 +22,7 @@
|
|||||||
"@tabler/icons-react": "^3.44.0",
|
"@tabler/icons-react": "^3.44.0",
|
||||||
"@tailwindcss/vite": "^4.3.0",
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
"@tanstack/react-devtools": "^0.10.5",
|
"@tanstack/react-devtools": "^0.10.5",
|
||||||
|
"@tanstack/react-hotkeys": "^0.10.0",
|
||||||
"@tanstack/react-router": "^1.170.7",
|
"@tanstack/react-router": "^1.170.7",
|
||||||
"@tanstack/react-router-devtools": "^1.167.0",
|
"@tanstack/react-router-devtools": "^1.167.0",
|
||||||
"@tanstack/react-router-ssr-query": "^1.167.0",
|
"@tanstack/react-router-ssr-query": "^1.167.0",
|
||||||
@@ -31,10 +32,9 @@
|
|||||||
"@vercel/og": "^0.11.1",
|
"@vercel/og": "^0.11.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
|
||||||
"fumadocs-core": "^16.9.0",
|
"fumadocs-core": "^16.9.0",
|
||||||
"fumadocs-mdx": "^15.0.7",
|
"fumadocs-mdx": "^15.0.7",
|
||||||
"fumadocs-ui": "^16.9.0",
|
"fumadocs-ui": "npm:@fumadocs/base-ui@^16.9.0",
|
||||||
"nitro": "^3.0.260429-beta",
|
"nitro": "^3.0.260429-beta",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
|
|||||||
@@ -1,46 +1,65 @@
|
|||||||
import type { ModuleSummary, RegistryIndex } from "@stanza/registry";
|
import type { ModuleSummary, RegistryIndex } from "@stanza/registry";
|
||||||
import { groupLabel, moduleGroup } from "@stanza/registry";
|
import { groupLabel, moduleGroup } from "@stanza/registry";
|
||||||
import { IconSearch } from "@tabler/icons-react";
|
import { IconSearch } from "@tabler/icons-react";
|
||||||
|
import { formatForDisplay, useHotkey } from "@tanstack/react-hotkeys";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import { ModuleLogo } from "@/components/module-logo";
|
import { ModuleLogo } from "@/components/module-logo";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
CommandDialog,
|
Dialog,
|
||||||
CommandEmpty,
|
DialogContent,
|
||||||
CommandGroup,
|
DialogDescription,
|
||||||
CommandInput,
|
DialogHeader,
|
||||||
CommandItem,
|
DialogTitle,
|
||||||
CommandList,
|
} from "@/components/ui/dialog";
|
||||||
} from "@/components/ui/command";
|
|
||||||
import { Kbd } from "@/components/ui/kbd";
|
import { Kbd } from "@/components/ui/kbd";
|
||||||
import { groupBySlot } from "@/lib/module-search";
|
import { groupBySlot } from "@/lib/module-search";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const HOTKEY = "Mod+K";
|
||||||
|
|
||||||
|
function matches(m: ModuleSummary, query: string): boolean {
|
||||||
|
if (!query) return true;
|
||||||
|
const haystack = `${moduleGroup(m)} ${m.id} ${m.label} ${m.description}`.toLowerCase();
|
||||||
|
return query
|
||||||
|
.toLowerCase()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.every((term) => haystack.includes(term));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global module search. Triggered by a search-input-looking button in the
|
* Global module search. Triggered by a search-input-looking button in the
|
||||||
* header and by `⌘K` / `Ctrl-K`. cmdk filters the registry index client-side,
|
* header and by `⌘K` / `Ctrl-K` (resolved per-platform by `@tanstack/react-hotkeys`).
|
||||||
* groups by slot, and navigates to `/m/$slot/$id` on selection.
|
* Filters the registry index client-side, groups by slot, and navigates to
|
||||||
|
* `/m/$slot/$id` on selection. Arrow keys move the highlight; Enter selects.
|
||||||
*/
|
*/
|
||||||
export function SiteSearch({ index }: { index: RegistryIndex }) {
|
export function SiteSearch({ index }: { index: RegistryIndex }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useHotkey(HOTKEY, () => setOpen((o) => !o));
|
||||||
function onKey(e: KeyboardEvent) {
|
|
||||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
|
|
||||||
e.preventDefault();
|
|
||||||
setOpen((o) => !o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.addEventListener("keydown", onKey);
|
|
||||||
return () => window.removeEventListener("keydown", onKey);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const groups = useMemo(() => groupBySlot(index.modules), [index.modules]);
|
// Resolved client-side: the platform-aware label differs between server and
|
||||||
|
// client, so render nothing until mount to avoid a hydration mismatch.
|
||||||
|
const [hotkeyLabel, setHotkeyLabel] = useState<string | null>(null);
|
||||||
|
useEffect(() => setHotkeyLabel(formatForDisplay(HOTKEY)), []);
|
||||||
|
|
||||||
const openSearch = useCallback(() => setOpen(true), []);
|
const filtered = useMemo(
|
||||||
const go = useCallback(
|
() => index.modules.filter((m) => matches(m, query)),
|
||||||
|
[index.modules, query],
|
||||||
|
);
|
||||||
|
const groups = useMemo(() => groupBySlot(filtered), [filtered]);
|
||||||
|
const flat = useMemo(() => groups.flatMap((g) => g.modules), [groups]);
|
||||||
|
|
||||||
|
useEffect(() => setActiveIndex(0), [query]);
|
||||||
|
|
||||||
|
const selectModule = useCallback(
|
||||||
(summary: ModuleSummary) => {
|
(summary: ModuleSummary) => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
void navigate({ to: "/m/$slot/$id", params: { slot: moduleGroup(summary), id: summary.id } });
|
void navigate({ to: "/m/$slot/$id", params: { slot: moduleGroup(summary), id: summary.id } });
|
||||||
@@ -48,48 +67,132 @@ export function SiteSearch({ index }: { index: RegistryIndex }) {
|
|||||||
[navigate],
|
[navigate],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onOpenChange = useCallback((next: boolean) => {
|
||||||
|
setOpen(next);
|
||||||
|
if (next) {
|
||||||
|
setQuery("");
|
||||||
|
setActiveIndex(0);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault();
|
||||||
|
setActiveIndex((i) => (flat.length ? (i + 1) % flat.length : 0));
|
||||||
|
} else if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault();
|
||||||
|
setActiveIndex((i) => (flat.length ? (i - 1 + flat.length) % flat.length : 0));
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
const m = flat[activeIndex];
|
||||||
|
if (m) selectModule(m);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[flat, activeIndex, selectModule],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
const node = listRef.current?.querySelector<HTMLElement>(`[data-index="${activeIndex}"]`);
|
||||||
|
node?.scrollIntoView({ block: "nearest" });
|
||||||
|
}, [activeIndex, open]);
|
||||||
|
|
||||||
|
let flatIndex = 0;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={openSearch}
|
onClick={() => onOpenChange(true)}
|
||||||
aria-label="Search modules"
|
aria-label="Search modules"
|
||||||
className="gap-2 bg-background px-2 text-muted-foreground hover:bg-muted hover:text-foreground sm:min-w-[180px] sm:px-2.5"
|
className="gap-2 bg-background px-2 text-muted-foreground hover:bg-muted hover:text-foreground sm:min-w-[180px] sm:px-2.5"
|
||||||
>
|
>
|
||||||
<IconSearch data-icon="inline-start" />
|
<IconSearch data-icon="inline-start" />
|
||||||
<span className="hidden flex-1 text-left sm:inline">Search modules…</span>
|
<span className="hidden flex-1 text-left sm:inline">Search modules…</span>
|
||||||
<Kbd className="hidden sm:inline">⌘K</Kbd>
|
{hotkeyLabel && <Kbd className="hidden sm:inline">{hotkeyLabel}</Kbd>}
|
||||||
</Button>
|
</Button>
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<CommandInput placeholder="Search modules…" />
|
<DialogContent
|
||||||
<CommandList>
|
className="top-1/3 translate-y-0 gap-0 overflow-hidden rounded-none p-0"
|
||||||
<CommandEmpty>No matches.</CommandEmpty>
|
showCloseButton={false}
|
||||||
{groups.map(({ group, modules }) => (
|
>
|
||||||
<CommandGroup key={group} heading={groupLabel(group)}>
|
<DialogHeader className="sr-only">
|
||||||
{modules.map((m) => (
|
<DialogTitle>Search modules</DialogTitle>
|
||||||
<SearchResult key={`${moduleGroup(m)}:${m.id}`} summary={m} onGo={go} />
|
<DialogDescription>Search the registry for a module to add.</DialogDescription>
|
||||||
))}
|
</DialogHeader>
|
||||||
</CommandGroup>
|
<div className="flex items-center gap-2 border-b px-3">
|
||||||
))}
|
<IconSearch className="size-4 shrink-0 opacity-50" />
|
||||||
</CommandList>
|
<input
|
||||||
</CommandDialog>
|
autoFocus
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
placeholder="Search modules…"
|
||||||
|
aria-label="Search modules"
|
||||||
|
className="h-10 w-full bg-transparent text-xs outline-hidden placeholder:text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={listRef}
|
||||||
|
className="no-scrollbar max-h-72 overflow-x-hidden overflow-y-auto p-1"
|
||||||
|
>
|
||||||
|
{flat.length === 0 ? (
|
||||||
|
<div className="py-6 text-center text-xs text-muted-foreground">No matches.</div>
|
||||||
|
) : (
|
||||||
|
groups.map(({ group, modules }) => (
|
||||||
|
<div key={group} className="overflow-hidden">
|
||||||
|
<div className="px-2 py-1.5 text-xs text-muted-foreground">
|
||||||
|
{groupLabel(group)}
|
||||||
|
</div>
|
||||||
|
{modules.map((m) => {
|
||||||
|
const i = flatIndex++;
|
||||||
|
return (
|
||||||
|
<SearchResult
|
||||||
|
key={`${moduleGroup(m)}:${m.id}`}
|
||||||
|
summary={m}
|
||||||
|
index={i}
|
||||||
|
active={i === activeIndex}
|
||||||
|
onSelect={selectModule}
|
||||||
|
onActivate={setActiveIndex}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchResult({
|
function SearchResult({
|
||||||
summary,
|
summary,
|
||||||
onGo,
|
index,
|
||||||
|
active,
|
||||||
|
onSelect,
|
||||||
|
onActivate,
|
||||||
}: {
|
}: {
|
||||||
summary: ModuleSummary;
|
summary: ModuleSummary;
|
||||||
onGo: (summary: ModuleSummary) => void;
|
index: number;
|
||||||
|
active: boolean;
|
||||||
|
onSelect: (summary: ModuleSummary) => void;
|
||||||
|
onActivate: (index: number) => void;
|
||||||
}) {
|
}) {
|
||||||
const onSelect = useCallback(() => onGo(summary), [onGo, summary]);
|
const handleSelect = useCallback(() => onSelect(summary), [onSelect, summary]);
|
||||||
|
const handlePointerMove = useCallback(() => onActivate(index), [onActivate, index]);
|
||||||
return (
|
return (
|
||||||
<CommandItem
|
<button
|
||||||
value={`${moduleGroup(summary)} ${summary.id} ${summary.label} ${summary.description}`}
|
type="button"
|
||||||
onSelect={onSelect}
|
data-index={index}
|
||||||
className="cursor-pointer"
|
data-selected={active || undefined}
|
||||||
|
onClick={handleSelect}
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full cursor-pointer items-center gap-2 rounded-none px-2 py-2 text-left text-xs outline-hidden",
|
||||||
|
active && "bg-muted text-foreground",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<ModuleLogo logo={summary.logo} label={summary.label} size="sm" />
|
<ModuleLogo logo={summary.logo} label={summary.label} size="sm" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
@@ -99,6 +202,6 @@ function SearchResult({
|
|||||||
<span className="shrink-0 font-mono text-[10px] text-muted-foreground/60">
|
<span className="shrink-0 font-mono text-[10px] text-muted-foreground/60">
|
||||||
{moduleGroup(summary)}/{summary.id}
|
{moduleGroup(summary)}/{summary.id}
|
||||||
</span>
|
</span>
|
||||||
</CommandItem>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { IconSearch, IconCheck } from "@tabler/icons-react";
|
|
||||||
import { Command as CommandPrimitive } from "cmdk";
|
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive
|
|
||||||
data-slot="command"
|
|
||||||
className={cn(
|
|
||||||
"flex size-full flex-col overflow-hidden rounded-none bg-popover text-popover-foreground",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandDialog({
|
|
||||||
title = "Command Palette",
|
|
||||||
description = "Search for a command to run...",
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
showCloseButton = false,
|
|
||||||
...props
|
|
||||||
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
className?: string;
|
|
||||||
showCloseButton?: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Dialog {...props}>
|
|
||||||
<DialogContent
|
|
||||||
className={cn("top-1/3 translate-y-0 overflow-hidden rounded-none p-0", className)}
|
|
||||||
showCloseButton={showCloseButton}
|
|
||||||
>
|
|
||||||
<DialogHeader className="sr-only">
|
|
||||||
<DialogTitle>{title}</DialogTitle>
|
|
||||||
<DialogDescription>{description}</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<Command>{children}</Command>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandInput({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
||||||
return (
|
|
||||||
<div data-slot="command-input-wrapper" className="border-b pb-0">
|
|
||||||
<InputGroup className="h-8 border-none border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!">
|
|
||||||
<CommandPrimitive.Input
|
|
||||||
data-slot="command-input"
|
|
||||||
className={cn(
|
|
||||||
"w-full text-xs outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
<InputGroupAddon>
|
|
||||||
<IconSearch className="size-4 shrink-0 opacity-50" />
|
|
||||||
</InputGroupAddon>
|
|
||||||
</InputGroup>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.List
|
|
||||||
data-slot="command-list"
|
|
||||||
className={cn(
|
|
||||||
"no-scrollbar max-h-72 scroll-py-0 overflow-x-hidden overflow-y-auto outline-none",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandEmpty({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Empty
|
|
||||||
data-slot="command-empty"
|
|
||||||
className={cn("py-6 text-center text-xs", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandGroup({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Group
|
|
||||||
data-slot="command-group"
|
|
||||||
className={cn(
|
|
||||||
"overflow-hidden text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:text-muted-foreground",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandSeparator({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Separator
|
|
||||||
data-slot="command-separator"
|
|
||||||
className={cn("-mx-1 h-px bg-border", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandItem({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Item
|
|
||||||
data-slot="command-item"
|
|
||||||
className={cn(
|
|
||||||
"group/command-item relative flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none in-data-[slot=dialog-content]:rounded-none! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-foreground",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<IconCheck className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
|
|
||||||
</CommandPrimitive.Item>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
data-slot="command-shortcut"
|
|
||||||
className={cn(
|
|
||||||
"ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Command,
|
|
||||||
CommandDialog,
|
|
||||||
CommandInput,
|
|
||||||
CommandList,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandItem,
|
|
||||||
CommandShortcut,
|
|
||||||
CommandSeparator,
|
|
||||||
};
|
|
||||||
Generated
+131
-1031
File diff suppressed because it is too large.
Load diff
+9
-6
@@ -1,10 +1,13 @@
|
|||||||
packages:
|
packages:
|
||||||
- "apps/*"
|
- apps/*
|
||||||
- "packages/*"
|
- packages/*
|
||||||
- "registry/modules/*"
|
- registry/modules/*
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
ignoredBuiltDependencies:
|
||||||
- "esbuild"
|
- sharp
|
||||||
- "better-sqlite3"
|
|
||||||
|
|
||||||
ignoredOptionalDependencies: []
|
ignoredOptionalDependencies: []
|
||||||
|
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- msw
|
||||||
Reference in new issue
Block a user