Files
HMDesign/src/components/command.stories.tsx
T

122 lines
4.8 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>
)
}
// Same 3-column track as the Form pages (1 column on narrow screens). Each cell is a one-specimen StateBoard so subtitles, dividers and
// command boxes line up; sections with fewer specimens leave the remaining tracks empty.
const Grid = ({ children }: { children: ReactNode }) => (
<div className="grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr))] min-[1000px]:grid-cols-3">{children}</div>
)
const Cell = ({ id, label, children }: { id: string; label: string; children: ReactNode }) => (
<div className="min-w-0">
<StateBoard min={10000} states={[{ id, label, children }]} />
</div>
)
const meta = { title: "Components/Search & Date/Command", component: Command, parameters: { layout: "padded" } } satisfies Meta<typeof Command>
export default meta
type Story = StoryObj<typeof meta>
// 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="Variants">
<Grid>
<Cell id="basic" label="Search"><Basic showValue={false} /></Cell>
<Cell id="grouped" label="Groups"><Grouped /></Cell>
</Grid>
</StorySection>
<StorySection title="Usage">
<Grid>
<Cell id="long" label="Long list"><Long /></Cell>
</Grid>
</StorySection>
</StoryPage>
),
}
export const Search: Story = { tags: ["verification"], render: () => <Basic /> }
export const GroupedList: Story = { tags: ["verification"], name: "Grouped", render: () => <Grouped /> }