127 lines
5.2 KiB
TypeScript
127 lines
5.2 KiB
TypeScript
import type { ReactNode } from "react"
|
||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@hm-design/ui"
|
||
import { StateBoard } from "@/components/story-board"
|
||
import { STORY_KICKER, 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 (list min 15%)", node: <Horizontal /> },
|
||
{ id: "vertical", label: "Vertical with grip", node: <Vertical /> },
|
||
{ id: "handle", label: "Horizontal with grip", node: <WithHandle /> },
|
||
{ id: "minmax", label: "Min / max (25%–60%)", 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>
|
||
|
||
const board = (...ids: string[]) => (
|
||
<StateBoard min={580} 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
|
||
kicker={STORY_KICKER.components}
|
||
title="Resizable"
|
||
description="경계선을 끌어 인접한 패널의 크기를 조절하는 레이아웃입니다."
|
||
/>
|
||
<StorySection title="Overview">{board("horizontal")}</StorySection>
|
||
<StorySection title="Variants" description="세로 방향과 grip 표시입니다.">{board("vertical", "handle")}</StorySection>
|
||
<StorySection title="Usage" description="최소·최대 크기와 접을 수 있는 패널입니다.">{board("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 /> }
|