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

137 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@hm-design/ui"
import { StateBoard, type BoardState } from "@/components/story-board"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// The representative Story shows real, draggable layouts; the same specimens also exist as Stories tagged `verification` (hidden
// from the sidebar). react-resizable-panels v4 sizes: a string without a unit ("30") or with "%" is a percentage, a number is
// pixels. The frames (size, border) and panel content are Story layout. These are not Dashboard layouts.
const frame = (node: ReactNode, height = 200) => (
<div className="rounded-md border" style={{ width: "100%", maxWidth: 560, height }}>
{node}
</div>
)
const Pane = ({ children }: { children: ReactNode }) => <div className="grid size-full place-items-center p-3 text-sm">{children}</div>
function Horizontal() {
return frame(
<ResizablePanelGroup orientation="horizontal" aria-label="가로 분할" id="h">
<ResizablePanel defaultSize="30" minSize="15" id="h-list">
<Pane>목록 (min 15%)</Pane>
</ResizablePanel>
<ResizableHandle aria-label="목록 폭 조절" />
<ResizablePanel defaultSize="70" minSize="20" id="h-detail">
<Pane>상세</Pane>
</ResizablePanel>
</ResizablePanelGroup>
)
}
function Vertical() {
return frame(
<ResizablePanelGroup orientation="vertical" aria-label="세로 분할" id="v">
<ResizablePanel defaultSize="40" minSize="20" id="v-top">
<Pane>위</Pane>
</ResizablePanel>
<ResizableHandle withHandle aria-label="높이 조절" />
<ResizablePanel defaultSize="60" minSize="20" id="v-bottom">
<Pane>아래</Pane>
</ResizablePanel>
</ResizablePanelGroup>,
240
)
}
function WithHandle() {
return frame(
<ResizablePanelGroup orientation="horizontal" aria-label="handle 분할" id="g">
<ResizablePanel defaultSize="50" minSize="20" id="g-a">
<Pane>A</Pane>
</ResizablePanel>
<ResizableHandle withHandle aria-label="A와 B 사이 조절" />
<ResizablePanel defaultSize="50" minSize="20" id="g-b">
<Pane>B</Pane>
</ResizablePanel>
</ResizablePanelGroup>
)
}
function MinMax() {
return frame(
<ResizablePanelGroup orientation="horizontal" aria-label="min max 분할" id="m">
<ResizablePanel defaultSize="40" minSize="25" maxSize="60" id="m-a">
<Pane>25% – 60%</Pane>
</ResizablePanel>
<ResizableHandle withHandle aria-label="min max 조절" />
<ResizablePanel defaultSize="60" minSize="20" id="m-b">
<Pane>나머지</Pane>
</ResizablePanel>
</ResizablePanelGroup>
)
}
function Collapsible() {
return frame(
<ResizablePanelGroup orientation="horizontal" aria-label="접힘 분할" id="c">
<ResizablePanel defaultSize="30" minSize="15" collapsible collapsedSize="0" id="c-side">
<Pane>사이드 (끌면 접힘)</Pane>
</ResizablePanel>
<ResizableHandle withHandle aria-label="사이드 폭 조절" />
<ResizablePanel defaultSize="70" minSize="30" id="c-main">
<Pane>본문</Pane>
</ResizablePanel>
</ResizablePanelGroup>
)
}
const sections = [
{ id: "horizontal", label: "Horizontal", node: <Horizontal /> },
{ id: "vertical", label: "Vertical with grip", node: <Vertical /> },
{ id: "handle", label: "Horizontal with grip", node: <WithHandle /> },
{ id: "minmax", label: "Min / max", node: <MinMax /> },
{ id: "collapsible", label: "Collapsible panel", node: <Collapsible /> },
]
const meta = { title: "Components/Disclosure/Resizable", component: ResizablePanelGroup, parameters: { layout: "padded" } } satisfies Meta<typeof ResizablePanelGroup>
export default meta
type Story = StoryObj<typeof meta>
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), one column
// below that. Each specimen sits in its own single-column StateBoard (min larger than any column), so the title, divider and
// specimen start line up and unused columns stay empty. The frames keep their own size (max 560px wide).
function StateGrid({ states, cols }: { states: BoardState[]; cols: 2 | 3 }) {
return (
<div className={`grid grid-cols-1 items-start gap-6 ${cols === 3 ? "min-[1000px]:grid-cols-3" : "min-[1000px]:grid-cols-2"}`}>
{states.map((state) => (
<StateBoard key={state.id} min={10000} states={[state]} />
))}
</div>
)
}
const board = (cols: 2 | 3, ...ids: string[]) => (
<StateGrid cols={cols} 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: "Resizable",
parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader
title="Resizable"
/>
<StorySection title="Variants">{board(3, "horizontal", "handle", "vertical")}</StorySection>
<StorySection title="Usage">{board(3, "minmax", "collapsible")}</StorySection>
</StoryPage>
),
}
export const HorizontalLayout: Story = { tags: ["verification"], name: "Horizontal", render: () => <Horizontal /> }
export const VerticalLayout: Story = { tags: ["verification"], name: "Vertical", render: () => <Vertical /> }
export const MinMaxLayout: Story = { tags: ["verification"], name: "Min Max", render: () => <MinMax /> }