refactor(ui): use the shadcn Kbd primitive for keyboard hints (#48885)

Replaces the hand-rolled kbd and code key chips across the dashboard with
the shared Kbd component from components/ui/kbd, so every shortcut hint
renders the same way
This commit is contained in:
yuneng-jiang 2026-10-28 12:51:24 -07:00 • committed by GitHub
parent 7c7304c22b
commit 1bd9a0780a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 25 additions and 46 deletions

View file

@ -2,6 +2,7 @@ import React, { useState } from "react";
import { Copy, Info } from "lucide-react";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import { Textarea } from "@/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
@ -165,9 +166,7 @@ export function GuardrailTestPanel({
/>
<div className="mt-1 flex items-center justify-between">
<span className="text-xs text-muted-foreground">
Press <kbd className="rounded-sm border border-border bg-muted px-1 py-0.5 text-xs">Enter</kbd> to
submit • <kbd className="rounded-sm border border-border bg-muted px-1 py-0.5 text-xs">Shift+Enter</kbd>{" "}
for new line
Press <Kbd>Enter</Kbd> to submit • <Kbd>Shift+Enter</Kbd> for new line
</span>
<span className="text-xs text-muted-foreground">Characters: {inputText.length}</span>
</div>

View file

@ -5,6 +5,7 @@ import React, { useId, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Kbd } from "@/components/ui/kbd";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@ -369,8 +370,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
<div className="flex w-full flex-col gap-6">
<StepCard step={1} title="Open Cursor Settings">
<span className="text-muted-foreground">
Use the keyboard shortcut <code className="bg-muted px-2 py-1 rounded-sm">⇧+⌘+J</code> (Mac) or{" "}
<code className="bg-muted px-2 py-1 rounded-sm">Ctrl+Shift+J</code> (Windows/Linux)
Use the keyboard shortcut <Kbd>⇧+⌘+J</Kbd> (Mac) or <Kbd>Ctrl+Shift+J</Kbd> (Windows/Linux)
</span>
</StepCard>
@ -380,9 +380,8 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
<StepCard step={3} title="Add Configuration">
<span className="mb-3 text-muted-foreground">
Copy the JSON configuration below and paste it into Cursor, then save with{" "}
<code className="bg-muted px-2 py-1 rounded-sm">Cmd+S</code> or{" "}
<code className="bg-muted px-2 py-1 rounded-sm">Ctrl+S</code>
Copy the JSON configuration below and paste it into Cursor, then save with <Kbd>Cmd+S</Kbd> or{" "}
<Kbd>Ctrl+S</Kbd>
</span>
<FeatureCard
icon={<Code className="text-purple-600 dark:text-purple-400" size={16} />}

View file

@ -12,6 +12,7 @@ import { getGuardrailsList, testPoliciesAndGuardrails } from "@/components/netwo
import PolicySelector, { getPolicyOptionEntries } from "@/components/policies/PolicySelector";
import { Policy } from "@/components/policies/types";
import { makeOpenAIChatCompletionRequest } from "@/components/llm_calls/chat_completion";
import { Kbd } from "@/components/ui/kbd";
import {
AlertTriangle,
BarChart3,
@ -1306,10 +1307,7 @@ export default function ComplianceUI({
/>
<div className="flex items-center justify-between px-3 pb-2">
<span className="text-[10px] text-muted-foreground">
Press <kbd className="px-1 py-0.5 bg-muted rounded-sm text-[10px] font-mono">Enter</kbd> to
submit ·{" "}
<kbd className="px-1 py-0.5 bg-muted rounded-sm text-[10px] font-mono">Shift+Enter</kbd> for new
line
Press <Kbd>Enter</Kbd> to submit · <Kbd>Shift+Enter</Kbd> for new line
</span>
<span className="text-[10px] text-muted-foreground tabular-nums">{quickTestInput.length}</span>
</div>

View file

@ -5,6 +5,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Kbd } from "@/components/ui/kbd";
import { Textarea } from "@/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
@ -654,8 +655,7 @@ const AiSuggestionModal: React.FC<AiSuggestionModalProps> = ({
/>
<div className="mt-1">
<span className="text-xs text-muted-foreground">
Press <kbd className="rounded-sm border border-border bg-muted px-1 py-0.5 text-xs">Enter</kbd> to
submit
Press <Kbd>Enter</Kbd> to submit
</span>
</div>
</div>

View file

@ -7,6 +7,7 @@ import { usePathname, useRouter } from "next/navigation";
import { useKeys } from "@/app/(dashboard)/hooks/keys/useKeys";
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { Kbd } from "@/components/ui/kbd";
import { useTerminology } from "@/contexts/TerminologyContext";
import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants";
import { keyDetailHref } from "@/utils/entityLinks";
@ -31,9 +32,6 @@ interface PaletteGroup {
isLoading?: boolean;
}
const shortcutChipClass =
"rounded-sm border border-border border-b-2 bg-muted px-[3px] font-mono text-muted-foreground";
export function CommandPalette({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) {
if (!open) return null;
return <CommandPaletteDialog setOpen={setOpen} />;
@ -305,13 +303,13 @@ function CommandPaletteDialog({ setOpen }: { setOpen: (open: boolean) => void })
</div>
<div className="flex items-center gap-3">
<span className="flex items-center gap-1">
<kbd className={shortcutChipClass}>↵</kbd> Open
<Kbd>↵</Kbd> Open
</span>
<span className="flex items-center gap-1">
<kbd className={shortcutChipClass}>↑↓</kbd> Navigate
<Kbd>↑↓</Kbd> Navigate
</span>
<span className="flex items-center gap-1">
<kbd className={shortcutChipClass}>esc</kbd> Close
<Kbd>esc</Kbd> Close
</span>
</div>
</div>

View file

@ -2,6 +2,7 @@
import { Search, X } from "lucide-react";
import { z } from "zod";
import { Kbd } from "@/components/ui/kbd";
import { storageKey } from "@/lib/storage";
import type { PaletteScope } from "./utils";
import { useCommandPalette } from "./CommandPaletteProvider";
@ -26,9 +27,7 @@ export function CommandPaletteHint({ scope, onDismiss }: CommandPaletteHintProps
<button type="button" className="flex items-center gap-1.5 hover:text-foreground" onClick={() => setOpen(true)}>
<Search className="size-3.5" aria-hidden="true" />
<span>Press</span>
<kbd className="rounded-sm border border-border border-b-2 bg-muted px-[3px] font-mono text-xs text-muted-foreground">
{shortcut}
</kbd>
<Kbd>{shortcut}</Kbd>
<span>{scope === "keys" ? "to search keys" : "to search or jump to a page"}</span>
</button>
<button

View file

@ -3,6 +3,7 @@
import { Search } from "lucide-react";
import { usePathname } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import { routeSegmentForPathname } from "@/utils/uiHref";
import { paletteScopeForRoute } from "./utils";
import { useCommandPalette } from "./CommandPaletteProvider";
@ -42,9 +43,7 @@ export function CommandPaletteTrigger({ mobile = false }: { mobile?: boolean })
<Search className="size-4 shrink-0" />
<span className="truncate">{label}</span>
</span>
<kbd className="ml-2 shrink-0 rounded-sm border border-border border-b-2 bg-muted px-[3px] font-mono text-xs text-muted-foreground">
{shortcut}
</kbd>
<Kbd className="ml-2 shrink-0">{shortcut}</Kbd>
</button>
);
}

View file

@ -29,6 +29,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog";
import { FieldError } from "@/components/ui/field";
import { Kbd } from "@/components/ui/kbd";
import { Skeleton } from "@/components/ui/skeleton";
import { useTerminology } from "@/contexts/TerminologyContext";
import { cn } from "@/lib/cva.config";
@ -185,7 +186,7 @@ export default function LiteAdminTrigger() {
>
<AriaAvatar />
<span className="hidden lg:inline">{ARIA_AGENT_LABEL}</span>
<kbd className="hidden rounded-full border border-info/30 px-1.5 font-sans text-xs lg:inline">⌘J</kbd>
<Kbd className="hidden lg:inline-flex">⌘J</Kbd>
</Button>
);
}

View file

@ -1,8 +1,9 @@
"use client";
import { Fragment, type ReactNode } from "react";
import { Fragment } from "react";
import { useHotkeysContext } from "react-hotkeys-hook";
import { Kbd } from "@/components/ui/kbd";
import { cn } from "@/lib/cva.config";
type BoundShortcut = ReturnType<typeof useHotkeysContext>["hotkeys"][number];
@ -59,14 +60,6 @@ export function shortcutHints(shortcuts: readonly BoundShortcut[]): readonly Sho
>((hints, [key, shortcut]) => mergeAdjacent(hints, key, shortcut.description ?? ""), []);
}
function Kbd({ children }: { children: ReactNode }) {
return (
<kbd className="rounded-sm border border-border border-b-2 bg-muted px-[3px] font-mono text-muted-foreground">
{children}
</kbd>
);
}
/** The shortcuts active right now, read from what is bound rather than typed by hand. */
export function ShortcutHints({ className }: { className?: string }) {
const { hotkeys } = useHotkeysContext();

View file

@ -8,9 +8,10 @@ import { Check, Copy, CornerDownLeft, Loader2, type LucideIcon, Search } from "l
import { Schema } from "prosemirror-model";
import { EditorState, Plugin, TextSelection, type Transaction } from "prosemirror-state";
import { Decoration, DecorationSet, type EditorView } from "prosemirror-view";
import { type ComponentProps, createContext, type ReactNode, useContext, useId, useMemo, useState } from "react";
import { type ComponentProps, createContext, useContext, useId, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import { cn } from "@/lib/cva.config";
import { type FilterOp, languageOps, parseQuery, type QueryClause, type QueryLanguage } from "./language";
@ -326,7 +327,7 @@ function Suggestions({ className, children, ...props }: SearchBoxSuggestionsProp
</span>
<span className="flex items-center gap-1">
<Kbd>
<CornerDownLeft className="size-3" />
<CornerDownLeft />
</Kbd>
Select
</span>
@ -392,12 +393,4 @@ function OperatorHints({ ops }: { ops: readonly FilterOp[] }) {
);
}
function Kbd({ children }: { children: ReactNode }) {
return (
<kbd className="inline-flex h-5 min-w-5 items-center justify-center rounded border border-border px-1 font-sans">
{children}
</kbd>
);
}
export const SearchBox = { Root, Input, Suggestions, CopyCommand } as const;