99 lines
4.5 KiB
TypeScript
99 lines
4.5 KiB
TypeScript
import { useState, type ReactNode } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import { ChevronDownIcon } from "lucide-react"
|
|
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger } from "@hm-design/ui"
|
|
import { StateBoard } from "@/components/story-board"
|
|
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// The representative "Collapsible" Story shows one independent disclosure per state with real, clickable triggers. The same
|
|
// sections also exist as Stories tagged `verification` (hidden from the sidebar). Collapsible is a single disclosure: no divider
|
|
// system and no animation.
|
|
|
|
const longText = "긴 텍스트는 줄바꿈으로 전체를 표시합니다. 공백 없는 긴 문자열도 너비를 밀어내지 않고 줄바꿈합니다. ".repeat(6)
|
|
|
|
const narrow = (children: ReactNode) => <div className="w-full max-w-md">{children}</div>
|
|
|
|
function Frame({ label, defaultOpen, disabled, children }: { label: string; defaultOpen?: boolean; disabled?: boolean; children: ReactNode }) {
|
|
return narrow(
|
|
<Collapsible defaultOpen={defaultOpen} disabled={disabled}>
|
|
<CollapsibleTrigger>
|
|
{label}
|
|
<ChevronDownIcon aria-hidden />
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent>{children}</CollapsibleContent>
|
|
</Collapsible>
|
|
)
|
|
}
|
|
|
|
const ClosedSpecimen = () => <Frame label="고급 설정 보기">추가 옵션이 여기에 표시됩니다.</Frame>
|
|
const OpenSpecimen = () => (
|
|
<Frame label="고급 설정 보기" defaultOpen>
|
|
처음부터 열려 있는 영역입니다.
|
|
</Frame>
|
|
)
|
|
const DisabledSpecimen = () => (
|
|
<Frame label="비활성 영역" disabled>
|
|
열 수 없습니다.
|
|
</Frame>
|
|
)
|
|
const LongSpecimen = () => (
|
|
<Frame label="긴 콘텐츠" defaultOpen>
|
|
<p>{longText}</p>
|
|
<p className="mt-2">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p>
|
|
</Frame>
|
|
)
|
|
|
|
// Controlled: the parent owns `open`; an HM Button is the trigger through asChild and keeps its style.
|
|
function ControlledSpecimen() {
|
|
const [open, setOpen] = useState(false)
|
|
return narrow(
|
|
<Collapsible open={open} onOpenChange={setOpen}>
|
|
<CollapsibleTrigger asChild>
|
|
<Button variant="primary" appearance="outline">
|
|
{open ? "접기" : "펼치기"}
|
|
</Button>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent>{`controlled: open = ${String(open)}`}</CollapsibleContent>
|
|
</Collapsible>
|
|
)
|
|
}
|
|
|
|
const sections = [
|
|
{ id: "closed", label: "Closed", node: <ClosedSpecimen /> },
|
|
{ id: "open", label: "Open", node: <OpenSpecimen /> },
|
|
{ id: "disabled", label: "Disabled", node: <DisabledSpecimen /> },
|
|
{ id: "long", label: "Long Content", node: <LongSpecimen /> },
|
|
{ id: "controlled", label: "Controlled + Button trigger", note: "asChild: Button이 trigger이며 HM Button 스타일 유지", node: <ControlledSpecimen /> },
|
|
]
|
|
|
|
const meta = { title: "Components/Disclosure/Collapsible", component: Collapsible, parameters: { layout: "padded" } } satisfies Meta<typeof Collapsible>
|
|
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, note: s.note, children: s.node }))} />
|
|
)
|
|
|
|
// Representative Story: the common Story page; the sections group the same specimens.
|
|
export const All: Story = {
|
|
name: "Collapsible",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader
|
|
kicker={STORY_KICKER.components}
|
|
title="Collapsible"
|
|
description="하나의 영역을 펼치고 접는 단일 disclosure입니다. 구분선 체계와 애니메이션은 없습니다."
|
|
/>
|
|
<StorySection title="Overview">{board("closed")}</StorySection>
|
|
<StorySection title="States">{board("open", "disabled")}</StorySection>
|
|
<StorySection title="Usage" description="긴 콘텐츠와, Button을 trigger로 쓰는 제어형 조합입니다.">{board("long", "controlled")}</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
export const Closed: Story = { tags: ["verification"], render: () => <ClosedSpecimen /> }
|
|
export const Open: Story = { tags: ["verification"], render: () => <OpenSpecimen /> }
|
|
export const Disabled: Story = { tags: ["verification"], render: () => <DisabledSpecimen /> }
|
|
export const LongContent: Story = { tags: ["verification"], name: "Long Content", render: () => <LongSpecimen /> }
|