Command
可搜索的命令列表和 ⌘K 面板:44 高的缝线搜索行,32 高的行,选中是粉色淡底。
没有找到相关结果
药屋少女的呢喃 第三季12 集
葬送的芙莉莲 第二季9 集
今日放送
喵萌奶茶屋
LoliHouse
发布资源⌘U
我的 RSS
API 密钥
站点设置(仅管理员)
command-demo.tsx
"use client"
import { CalendarDaysIcon, FilmIcon, KeyRoundIcon, RssIcon, SettingsIcon, UploadIcon, UsersIcon } from "lucide-react"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command"
export default function CommandDemo() {
return (
<Command className="anibt-pop w-full max-w-md">
<CommandInput placeholder="搜索番剧、字幕组、设置…" />
<CommandList>
<CommandEmpty>没有找到相关结果</CommandEmpty>
<CommandGroup heading="番剧">
<CommandItem>
<FilmIcon />
药屋少女的呢喃 第三季
<CommandShortcut>12 集</CommandShortcut>
</CommandItem>
<CommandItem>
<FilmIcon />
葬送的芙莉莲 第二季
<CommandShortcut>9 集</CommandShortcut>
</CommandItem>
<CommandItem>
<CalendarDaysIcon />
今日放送
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="字幕组">
<CommandItem>
<UsersIcon />
喵萌奶茶屋
</CommandItem>
<CommandItem>
<UsersIcon />
LoliHouse
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="操作">
<CommandItem>
<UploadIcon />
发布资源
<CommandShortcut>⌘U</CommandShortcut>
</CommandItem>
<CommandItem>
<RssIcon />
我的 RSS
</CommandItem>
<CommandItem>
<KeyRoundIcon />
API 密钥
</CommandItem>
<CommandItem disabled>
<SettingsIcon />
站点设置(仅管理员)
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
)
}安装
npx shadcn@latest add @anibt/command第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。 CLI 会一并装好 @anibt/dialog。
手动安装
- 1. 先装主题组件只读
@anibt/theme的 token 和配方,没有主题就没有样子。npx shadcn@latest add @anibt/theme - 2. 安装依赖
npm install cmdk lucide-react - 3. 它还用到
npx shadcn@latest add @anibt/dialog - 4. 把下面的文件放进项目components/ui/command.tsx
"use client" import * as React from "react" import { Command as CommandPrimitive } from "cmdk" import { SearchIcon } from "lucide-react" import { cn } from "@/lib/utils" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" /** * Command — AniBT 「贴纸手账」 command list and ⌘K palette (cmdk). * * The list is *content*, not a tier of its own: it fills a `DialogContent` * (`CommandDialog`) or a `PopoverContent`, which own the floating edge and * shadow, so it paints the popover paper and no second border. The search row * is 44px over the dashed stitch with a muted glass; anything passed as * `CommandInput`'s children sits at its right end (an `esc` key cap). Rows are * 32px on radius 10; the row under the cursor takes the pink wash, pink ink * and a pink icon. Group heads are 12/600 muted, groups part on the stitch. * * Export names and props are shadcn's, so this file can replace * `components/ui/command.tsx`. */ function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) { return ( <CommandPrimitive data-slot="command" className={cn( "flex h-full w-full min-h-0 flex-col overflow-hidden rounded-[14px] bg-popover text-[13px] text-popover-foreground", className )} {...props} /> ) } function CommandDialog({ title = "命令面板", description = "搜索要执行的命令或要去的页面", children, className, showCloseButton = false, ...props }: Omit<React.ComponentProps<typeof Dialog>, "children"> & { title?: string description?: string className?: string /** The dialog's ✕ square. Off by default: Esc closes a palette. */ showCloseButton?: boolean children?: React.ReactNode }) { return ( <Dialog {...props}> <DialogContent className={cn("top-[12vh] translate-y-0 gap-0 overflow-hidden p-0 sm:top-[18vh] sm:max-w-xl", className)} showCloseButton={showCloseButton} > {/* Radix reads the accessible name from inside the content. */} <DialogHeader className="sr-only"> <DialogTitle>{title}</DialogTitle> <DialogDescription>{description}</DialogDescription> </DialogHeader> <Command className="rounded-none **:data-[slot=command-input-wrapper]:h-12 **:data-[slot=command-list]:max-h-[min(360px,60dvh)] **:data-[slot=command-item]:min-h-10 [&_[cmdk-group]]:px-1.5"> {children} </Command> </DialogContent> </Dialog> ) } function CommandInput({ className, children, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) { return ( <div data-slot="command-input-wrapper" className="anibt-stitch-under flex h-11 shrink-0 items-center gap-2 px-3 text-muted-foreground focus-within:text-accent-foreground" > <SearchIcon className="size-4 shrink-0" aria-hidden="true" /> <CommandPrimitive.Input data-slot="command-input" className={cn( "flex h-full w-full min-w-0 bg-transparent text-[16px] text-foreground outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:text-muted-foreground sm:text-[13px]", className )} {...props} /> {children} </div> ) } function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) { return ( <CommandPrimitive.List data-slot="command-list" className={cn( "max-h-[300px] min-h-0 flex-1 scroll-py-1 overflow-x-hidden overflow-y-auto overscroll-contain py-1", className )} {...props} /> ) } function CommandEmpty({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) { return ( <CommandPrimitive.Empty data-slot="command-empty" className={cn("py-8 text-center text-[13px] text-muted-foreground", className)} {...props} /> ) } function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) { return ( <CommandPrimitive.Group data-slot="command-group" className={cn( "overflow-hidden px-1 py-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pt-1.5 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:text-[12px] [&_[cmdk-group-heading]]:font-semibold [&_[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("anibt-stitch mx-3 my-1", className)} {...props} /> ) } function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) { return ( <CommandPrimitive.Item data-slot="command-item" className={cn( "relative flex min-h-8 cursor-default items-center gap-2 rounded-[10px] px-2 py-1 text-[13px] outline-hidden transition-colors duration-150 select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:text-muted-foreground data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[selected=true]:[&_svg:not([class*='text-'])]:text-anibt-mark", className )} {...props} /> ) } /** A key or a hint at the row's right end, in the round face. */ function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) { return ( <span data-slot="command-shortcut" className={cn("anibt-num ml-auto pl-3 text-[12px] font-medium text-muted-foreground", className)} {...props} /> ) } export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator, }
用法
components/site-search.tsx
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
<Command>
<CommandInput placeholder="搜索番剧、字幕组、设置…" />
<CommandList>
<CommandEmpty>没有找到相关结果</CommandEmpty>
<CommandGroup heading="番剧">
<CommandItem>药屋少女的呢喃 第三季</CommandItem>
</CommandGroup>
</CommandList>
</Command>材质
- 列表是内容,不是一层浮层:它铺满所在的
DialogContent或PopoverContent,边和阴影由外层负责,自己只画浮层纸面,不画第二条边。单独放在页面上时,给它加anibt-pop,像上面的例子。 - 搜索行 44 高,下沿是缝线,放大镜在输入时变粉。
CommandInput的子元素排在搜索行右端,适合放esc键帽。 - 行高 32、圆角 10;键盘或指针所在的行是粉色淡底、粉字、粉色图标。组名 12/600 弱色,组之间是缝线。
- 手机上输入框是 16 号字,iOS 不会自动放大页面。
⌘K 面板
command-dialog.tsx
"use client"
import { CalendarDaysIcon, FilmIcon, MoonStarIcon, SearchIcon, SunIcon, UploadIcon, UsersIcon } from "lucide-react"
import { useEffect, useState } from "react"
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command"
export default function CommandDialogDemo() {
const [open, setOpen] = useState(false)
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key.toLowerCase() === "k" && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
setOpen((current) => !current)
}
}
document.addEventListener("keydown", onKeyDown)
return () => document.removeEventListener("keydown", onKeyDown)
}, [])
const run = () => setOpen(false)
return (
<>
<button
type="button"
onClick={() => setOpen(true)}
className="anibt-field w-full max-w-xs cursor-pointer text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
>
<SearchIcon className="size-4 shrink-0" aria-hidden="true" />
<span className="flex-1">搜索番剧、字幕组、资源</span>
<kbd className="anibt-kbd">⌘K</kbd>
</button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="搜索番剧、字幕组、资源…">
<kbd className="anibt-kbd">esc</kbd>
</CommandInput>
<CommandList>
<CommandEmpty>没有找到相关结果</CommandEmpty>
<CommandGroup heading="番剧">
<CommandItem onSelect={run}>
<FilmIcon />
药屋少女的呢喃 第三季
<CommandShortcut>Kusuriya no Hitorigoto</CommandShortcut>
</CommandItem>
<CommandItem onSelect={run}>
<FilmIcon />
葬送的芙莉莲 第二季
<CommandShortcut>Sousou no Frieren</CommandShortcut>
</CommandItem>
<CommandItem onSelect={run}>
<FilmIcon />
间谍过家家 第三季
<CommandShortcut>SPY×FAMILY</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="去往">
<CommandItem onSelect={run}>
<CalendarDaysIcon />
今日放送
</CommandItem>
<CommandItem onSelect={run}>
<UsersIcon />
字幕组列表
</CommandItem>
<CommandItem onSelect={run}>
<UploadIcon />
发布资源
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="外观">
<CommandItem onSelect={run}>
<SunIcon />
切换到白樱
</CommandItem>
<CommandItem onSelect={run}>
<MoonStarIcon />
切换到夜樱
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</>
)
}CommandDialog 把列表放进 Dialog:贴在视口上方、宽 576,默认不显示关闭按钮(Esc 关闭)。标题和说明只给读屏器。
属性
CommandDialog
titlestring默认 "命令面板"读屏标题
descriptionstring默认 "搜索要执行的命令或要去的页面"读屏说明
showCloseButtonboolean默认 false显示弹窗的关闭方块
open / onOpenChangeboolean / (open) => void默认 —受控打开,配合 ⌘K 快捷键
CommandInput
childrenReactNode默认 —搜索行右端的内容,比如 esc 键帽
其余部件的属性透传给 cmdk。导出名与 shadcn 一致。
