113 lines
4.4 KiB
TypeScript
113 lines
4.4 KiB
TypeScript
import { useState, type ReactNode } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import { CalendarIcon, SettingsIcon, UserIcon } from "lucide-react"
|
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from "@hm-design/ui"
|
|
import { StateBoard } from "@/components/story-board"
|
|
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// The representative "Command" Story shows real, typeable lists; the same specimens also exist as Stories tagged `verification`
|
|
// (hidden from the sidebar). Filtering is cmdk's. The bordered box is Story composition (Command draws no border of its own).
|
|
// A selection log shows that Enter / click reach `onSelect`.
|
|
|
|
const box = (node: ReactNode) => <div className="w-full max-w-sm rounded-md border">{node}</div>
|
|
|
|
// `showValue={false}` hides the value readout on the representative page; the verification Stories keep it.
|
|
function Basic({ showValue = true }: { showValue?: boolean }) {
|
|
const [last, setLast] = useState("-")
|
|
return (
|
|
<div className="grid gap-2">
|
|
{box(
|
|
<Command>
|
|
<CommandInput placeholder="검색..." />
|
|
<CommandList>
|
|
<CommandEmpty>결과가 없습니다.</CommandEmpty>
|
|
<CommandItem value="next" keywords={["Next.js"]} onSelect={setLast}>Next.js</CommandItem>
|
|
<CommandItem value="sveltekit" keywords={["SvelteKit"]} onSelect={setLast}>SvelteKit</CommandItem>
|
|
<CommandItem value="remix" keywords={["Remix"]} onSelect={setLast} disabled>
|
|
Remix
|
|
</CommandItem>
|
|
<CommandItem value="astro" keywords={["Astro"]} onSelect={setLast}>Astro</CommandItem>
|
|
</CommandList>
|
|
</Command>
|
|
)}
|
|
{showValue ? <span className="text-sm text-muted-foreground" data-testid="last">{`선택: ${last}`}</span> : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Grouped() {
|
|
return box(
|
|
<Command>
|
|
<CommandInput placeholder="명령 검색..." />
|
|
<CommandList>
|
|
<CommandEmpty>결과가 없습니다.</CommandEmpty>
|
|
<CommandGroup heading="제안">
|
|
<CommandItem>
|
|
<CalendarIcon />
|
|
일정 보기
|
|
</CommandItem>
|
|
<CommandItem>
|
|
<UserIcon />
|
|
프로필
|
|
<CommandShortcut>⌘P</CommandShortcut>
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
<CommandSeparator />
|
|
<CommandGroup heading="설정">
|
|
<CommandItem>
|
|
<SettingsIcon />
|
|
환경설정
|
|
<CommandShortcut>⌘,</CommandShortcut>
|
|
</CommandItem>
|
|
<CommandItem disabled>결제 (사용 불가)</CommandItem>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
)
|
|
}
|
|
|
|
function Long() {
|
|
return box(
|
|
<Command>
|
|
<CommandInput placeholder="항목 검색..." />
|
|
<CommandList>
|
|
<CommandEmpty>결과가 없습니다.</CommandEmpty>
|
|
{Array.from({ length: 30 }, (_, i) => (
|
|
<CommandItem key={i}>{`항목 ${i + 1} — 긴 항목 이름도 줄바꿈 없이 한 줄로 이어질 수 있습니다`}</CommandItem>
|
|
))}
|
|
</CommandList>
|
|
</Command>
|
|
)
|
|
}
|
|
|
|
const sections = [
|
|
{ id: "basic", label: "Search", node: <Basic showValue={false} /> },
|
|
{ id: "grouped", label: "Groups", node: <Grouped /> },
|
|
{ id: "long", label: "Long list", node: <Long /> },
|
|
]
|
|
|
|
const meta = { title: "Components/Search & Date/Command", component: Command, parameters: { layout: "padded" } } satisfies Meta<typeof Command>
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
const board = (...ids: string[]) => (
|
|
<StateBoard min={360} states={sections.filter((s) => ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, children: s.node }))} />
|
|
)
|
|
|
|
// Representative Story: the common Story page; the sections group the same specimens.
|
|
export const All: Story = {
|
|
name: "Command",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader title="Command" />
|
|
<StorySection title="Overview">{board("basic")}</StorySection>
|
|
<StorySection title="Variants">{board("grouped")}</StorySection>
|
|
<StorySection title="Usage">{board("long")}</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
export const Search: Story = { tags: ["verification"], render: () => <Basic /> }
|
|
export const GroupedList: Story = { tags: ["verification"], name: "Grouped", render: () => <Grouped /> }
|