Drop page / section descriptions, state notes and repeated captions from the representative Stories of 44 components, the Patterns, Center Graphic and OrgChart pages. Value readouts, spec lists and captions hide only on the representative page; hidden verification Stories keep them. Tabs Default API and the Filter Bar draft / applied counts move to the hidden DefaultApi and DraftApplied Stories; Center Overview is hidden with its direct URL kept. Story IDs are unchanged. Align the component, pattern and OrgChart docs with the pages, point the quality reference check at the DraftApplied Story, and make the P8 styles check expect no section header on the preview-only Expanded page.
147 lines
5.6 KiB
TypeScript
147 lines
5.6 KiB
TypeScript
import { useState, type ReactNode } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import { DatePicker } from "@hm-design/ui"
|
|
import { StateBoard } from "@/components/story-board"
|
|
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// DatePicker is a Popover + Calendar composition packaged as a component (value handling, trigger text, close on select).
|
|
// The representative Story shows real pickers; the same specimens also exist as Stories tagged `verification` (hidden from the
|
|
// sidebar). The Label is Story composition (the way a consumer would); `today` is pinned through Calendar's own prop so the
|
|
// Story looks the same on any day.
|
|
|
|
const today = new Date(2026, 9, 2)
|
|
const calendarProps = { today, startMonth: new Date(2026, 0), endMonth: new Date(2027, 11) }
|
|
|
|
function Field({ label, id, children }: { label: string; id: string; children: ReactNode }) {
|
|
return (
|
|
<div className="grid max-w-xs gap-2">
|
|
<label htmlFor={id} className="justify-self-start text-sm font-medium text-muted-foreground">
|
|
{label}
|
|
</label>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Empty() {
|
|
return (
|
|
<Field label="시작일" id="dp-empty">
|
|
<DatePicker id="dp-empty" calendarProps={calendarProps} />
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
function Selected() {
|
|
return (
|
|
<Field label="시작일" id="dp-selected">
|
|
<DatePicker id="dp-selected" defaultValue={new Date(2026, 9, 6)} calendarProps={calendarProps} />
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
function FormatExamples() {
|
|
return (
|
|
<div className="grid gap-3">
|
|
<Field label="기본 형식 · YYYY.MM.DD" id="dp-padded">
|
|
<DatePicker id="dp-padded" defaultValue={new Date(2026, 0, 9)} calendarProps={calendarProps} />
|
|
</Field>
|
|
<Field label="Custom format override" id="dp-custom">
|
|
<DatePicker id="dp-custom" defaultValue={new Date(2026, 9, 6)} format={(date) => date.toLocaleDateString("ko-KR")} calendarProps={calendarProps} />
|
|
</Field>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// `showValue={false}` hides the value readout on the representative page; the verification Stories keep it.
|
|
function Controlled({ showValue = true }: { showValue?: boolean }) {
|
|
const [date, setDate] = useState<Date | undefined>()
|
|
return (
|
|
<div className="grid gap-2">
|
|
<Field label="마감일 (controlled)" id="dp-controlled">
|
|
<DatePicker id="dp-controlled" controlled value={date} onValueChange={setDate} calendarProps={calendarProps} />
|
|
</Field>
|
|
{showValue ? <span className="text-sm text-muted-foreground" data-testid="controlled">{`value = ${date ? date.toLocaleDateString("ko-KR") : "undefined"}`}</span> : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Medium() {
|
|
return (
|
|
<Field label="Medium" id="dp-medium">
|
|
<DatePicker id="dp-medium" size="medium" defaultValue={new Date(2026, 9, 15)} calendarProps={calendarProps} />
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
function DisabledDates() {
|
|
return (
|
|
<Field label="5일 이전과 일요일은 선택 불가" id="dp-disabled-dates">
|
|
<DatePicker
|
|
id="dp-disabled-dates"
|
|
calendarProps={{ ...calendarProps, disabled: [{ before: new Date(2026, 9, 5) }, { dayOfWeek: [0] }] }}
|
|
/>
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
function Disabled() {
|
|
return (
|
|
<Field label="비활성" id="dp-disabled">
|
|
<DatePicker id="dp-disabled" disabled defaultValue={new Date(2026, 9, 15)} />
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
const sections = [
|
|
{ id: "empty", label: "Empty", node: <Empty /> },
|
|
{ id: "selected", label: "Selected", node: <Selected /> },
|
|
{ id: "format", label: "Format", node: <FormatExamples /> },
|
|
{ id: "controlled", label: "Controlled", node: <Controlled showValue={false} /> },
|
|
{ id: "disabled-dates", label: "Disabled dates", node: <DisabledDates /> },
|
|
{ id: "disabled", label: "Disabled", node: <Disabled /> },
|
|
]
|
|
|
|
const meta = { title: "Components/Search & Date/Date Picker", component: DatePicker, parameters: { layout: "padded" } } satisfies Meta<typeof DatePicker>
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
const board = (...ids: string[]) => (
|
|
<StateBoard min={320} 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: "Date Picker",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader title="Date Picker" />
|
|
<StorySection title="Overview">{board("empty")}</StorySection>
|
|
{/* The field label already names the size, so the Medium specimen has no separate example title. */}
|
|
<StorySection title="Sizes">
|
|
<div className="max-w-sm">
|
|
<Medium />
|
|
</div>
|
|
</StorySection>
|
|
<StorySection title="States">{board("selected", "controlled", "disabled-dates", "disabled")}</StorySection>
|
|
<StorySection title="Usage">{board("format")}</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
export const EmptyPicker: Story = { tags: ["verification"], name: "Empty", render: () => <Empty /> }
|
|
export const InvalidFocus: Story = {
|
|
tags: ["verification"],
|
|
render: () => (
|
|
<div className="grid max-w-xs gap-6">
|
|
<button type="button">Before field</button>
|
|
<Field label="Invalid Date Picker" id="dp-invalid">
|
|
<DatePicker id="dp-invalid" aria-invalid defaultValue={new Date(2026, 9, 6)} calendarProps={calendarProps} />
|
|
</Field>
|
|
<button type="button">After field</button>
|
|
</div>
|
|
),
|
|
}
|
|
export const SelectedPicker: Story = { tags: ["verification"], name: "Selected", render: () => <Selected /> }
|
|
export const ControlledPicker: Story = { tags: ["verification"], name: "Controlled", render: () => <Controlled /> }
|