Click on items
to expand &
collapse
command-palette.tsx
"use client";
import { useCallback, useEffect, useState } from "react";
import { Command } from "cmdk";
import { Copy, Check, Zap } from "lucide-react";
import { cn } from "@/lib/utils";
import { SCROLL_TO_EVENT } from "@/components/smooth-scroll";
const COMMAND = "npx shadcn add @ruvix-ui/roll-reveal-title";
export default function CommandPalette() {
const [open, setOpen] = useState(false);
const [copied, setCopied] = useState(false);
useEffect(() => {
function onKey(e: KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
setOpen((v) => !v);
}
}
function onOpen() {
setOpen(true);
}
window.addEventListener("keydown", onKey);
window.addEventListener("ruvix:open-palette", onOpen);
return () => {
window.removeEventListener("keydown", onKey);
window.removeEventListener("ruvix:open-palette", onOpen);
};
}, []);
const copyInstall = useCallback(async () => {
try {
await navigator.clipboard.writeText(COMMAND);
} catch {
const ta = document.createElement("textarea");
ta.value = COMMAND;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
}
setCopied(true);
window.setTimeout(() => setCopied(false), 1600);
}, []);
function quickStart() {
setOpen(false);
// Let the dialog unmount before scrolling; SmoothScroll routes it through Lenis.
window.setTimeout(() => {
window.dispatchEvent(new CustomEvent(SCROLL_TO_EVENT, { detail: "#pricing" }));
}, 60);
}
return (
<Command.Dialog
open={open}
onOpenChange={setOpen}
label="Command palette"
contentClassName="palette-panel fixed left-1/2 top-[18vh] z-50 w-full max-w-lg -translate-x-1/2 overflow-hidden rounded-2xl border border-white/10 bg-chrome/90 text-ink shadow-2xl shadow-black/60 backdrop-blur-xl"
overlayClassName="palette-overlay fixed inset-0 z-40 bg-black/70 backdrop-blur-sm"
>
<div className="flex items-center gap-2 border-b border-white/10 px-4">
<Command.Input
placeholder="Type a command or search…"
className="h-12 w-full bg-transparent text-sm text-ink outline-none placeholder:text-ink/40"
/>
<kbd className="hidden shrink-0 items-center gap-1 rounded-md border border-white/10 bg-white/5 px-1.5 py-0.5 font-mono text-[10px] text-ink/60 sm:flex">
ESC
</kbd>
</div>
<Command.List className="max-h-72 overflow-y-auto p-2">
<Command.Empty className="px-3 py-8 text-center text-sm text-ink/50">
No results found.
</Command.Empty>
<Command.Group
heading="Actions"
className="[&_[cmdk-group-heading]]:px-3 [&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-[11px] [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider [&_[cmdk-group-heading]]:text-ink/40"
>
<Item
icon={copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
title={copied ? "Copied to clipboard" : "Copy install command"}
hint={COMMAND}
onSelect={() => void copyInstall()}
/>
<Item
icon={<Zap className="h-4 w-4" />}
title="Quick Start"
hint="Scroll to install"
onSelect={quickStart}
/>
</Command.Group>
</Command.List>
<div className="flex items-center gap-4 border-t border-white/10 px-4 py-2.5 text-[11px] text-ink/50">
<span className="flex items-center gap-1.5">
<Kbd>↑↓</Kbd> navigate
</span>
<span className="flex items-center gap-1.5">
<Kbd>↵</Kbd> select
</span>
<span className="ml-auto flex items-center gap-1.5">
<Kbd>ctrl K</Kbd> toggle
</span>
</div>
</Command.Dialog>
);
}
function Item({
icon,
title,
hint,
disabled,
onSelect,
}: {
icon: React.ReactNode;
title: string;
hint: string;
disabled?: boolean;
onSelect: () => void;
}) {
return (
<Command.Item
disabled={disabled}
onSelect={onSelect}
className={cn(
"flex cursor-pointer items-center gap-3 rounded-xl px-3 py-2.5 text-sm",
"data-[selected=true]:bg-white/10",
disabled && "cursor-not-allowed opacity-40"
)}
>
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg border border-white/10 bg-white/5 text-ink/80">
{icon}
</span>
<span className="font-medium text-ink">{title}</span>
<span className="ml-auto truncate font-mono text-xs text-ink/40">{hint}</span>
</Command.Item>
);
}
function Kbd({ children }: { children: React.ReactNode }) {
return (
<kbd className="rounded border border-white/10 bg-white/5 px-1 py-px font-mono text-[10px] text-ink/60">
{children}
</kbd>
);
}