122 lines
4.8 KiB
TypeScript
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 /> }
|