93 lines
4.2 KiB
TypeScript
93 lines
4.2 KiB
TypeScript
import { useState, type ReactNode } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import type { DateRange } from "react-day-picker"
|
|
import { Calendar } from "@hm-design/ui"
|
|
import { StateBoard } from "@/components/story-board"
|
|
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// The representative "Calendar" Story shows each mode with real, clickable calendars; the same specimens also exist as Stories
|
|
// tagged `verification` (hidden from the sidebar). `today` is pinned to 2026-10-02 so the Story looks the same on any day; this
|
|
// uses react-day-picker's own `today` prop, not an HM state. Korean is the Calendar default locale.
|
|
|
|
const today = new Date(2026, 9, 2)
|
|
const month = new Date(2026, 9, 1)
|
|
|
|
const box = (node: ReactNode) => <div className="rounded-md border bg-card w-fit">{node}</div>
|
|
|
|
// `showValue={false}` hides the value readout on the representative page; the verification Stories keep it.
|
|
function Single({ showValue = true }: { showValue?: boolean }) {
|
|
const [date, setDate] = useState<Date | undefined>(new Date(2026, 9, 15))
|
|
return (
|
|
<div className="grid gap-2">
|
|
{box(<Calendar mode="single" selected={date} onSelect={setDate} defaultMonth={month} today={today} />)}
|
|
{showValue ? <span className="text-sm text-muted-foreground" data-testid="single">{`선택: ${date ? date.toLocaleDateString("ko-KR") : "없음"}`}</span> : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function DisabledDates() {
|
|
const [date, setDate] = useState<Date | undefined>()
|
|
return box(
|
|
<Calendar
|
|
mode="single"
|
|
selected={date}
|
|
onSelect={setDate}
|
|
defaultMonth={month}
|
|
today={today}
|
|
disabled={[{ before: new Date(2026, 9, 5) }, { dayOfWeek: [0] }]}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function Range() {
|
|
const [range, setRange] = useState<DateRange | undefined>({ from: new Date(2026, 9, 8), to: new Date(2026, 9, 14) })
|
|
return box(<Calendar mode="range" selected={range} onSelect={setRange} defaultMonth={month} today={today} />)
|
|
}
|
|
|
|
function TwoMonths() {
|
|
return box(<Calendar mode="single" numberOfMonths={2} defaultMonth={month} today={today} />)
|
|
}
|
|
|
|
// Same 3-column track as the Form pages (1 column on narrow screens). Each cell is a one-specimen StateBoard so subtitles, dividers and
|
|
// specimen tops line up; the wide Two months specimen takes two tracks (the full row on narrow screens).
|
|
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, wide, children }: { id: string; label: string; wide?: boolean; children: ReactNode }) => (
|
|
<div className={wide ? "col-span-full min-w-0 min-[1000px]:col-span-2" : "min-w-0"}>
|
|
<StateBoard min={10000} states={[{ id, label, children }]} />
|
|
</div>
|
|
)
|
|
|
|
// No `component`: Calendar props are a union of selection modes, so Stories without args would not type-check.
|
|
const meta = { title: "Components/Search & Date/Calendar", parameters: { layout: "padded" } } satisfies Meta
|
|
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: "Calendar",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader title="Calendar" />
|
|
<StorySection title="Variants">
|
|
<Grid>
|
|
<Cell id="single" label="Single"><Single showValue={false} /></Cell>
|
|
<Cell id="range" label="Range"><Range /></Cell>
|
|
<Cell id="months" label="Two months" wide><TwoMonths /></Cell>
|
|
</Grid>
|
|
</StorySection>
|
|
<StorySection title="States">
|
|
<Grid>
|
|
<Cell id="disabled" label="Disabled dates"><DisabledDates /></Cell>
|
|
</Grid>
|
|
</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
export const SingleSelection: Story = { tags: ["verification"], name: "Single", render: () => <Single /> }
|
|
export const DisabledSelection: Story = { tags: ["verification"], name: "Disabled Dates", render: () => <DisabledDates /> }
|
|
export const RangeSelection: Story = { tags: ["verification"], name: "Range", render: () => <Range /> }
|