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.
103 lines
4.4 KiB
TypeScript
103 lines
4.4 KiB
TypeScript
import { useState, type ReactNode } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import {
|
|
Pagination,
|
|
PaginationContent,
|
|
PaginationEllipsis,
|
|
PaginationItem,
|
|
PaginationLink,
|
|
PaginationNext,
|
|
PaginationPrevious,
|
|
} from "@hm-design/ui"
|
|
import { StateBoard } from "@/components/story-board"
|
|
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// Pagination is presentational: the consumer owns the page and the hrefs. The Story keeps the page in local state and uses
|
|
// `#page-N` hrefs with preventDefault. The representative Story shows each case on one page; the same specimens also exist as
|
|
// Stories tagged `verification` (hidden from the sidebar).
|
|
|
|
// Page list with ellipses: 1 … (p-1 p p+1) … total.
|
|
function pageRange(page: number, total: number): (number | "start" | "end")[] {
|
|
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1)
|
|
const out: (number | "start" | "end")[] = [1]
|
|
const from = Math.max(2, Math.min(page - 1, total - 4))
|
|
const to = Math.min(total - 1, Math.max(page + 1, 5))
|
|
if (from > 2) out.push("start")
|
|
for (let p = from; p <= to; p++) out.push(p)
|
|
if (to < total - 1) out.push("end")
|
|
out.push(total)
|
|
return out
|
|
}
|
|
|
|
// `showValue={false}` hides the value readout on the representative page; the verification Stories keep it.
|
|
function Specimen({ total, initial, showValue = true }: { total: number; initial: number; showValue?: boolean }) {
|
|
const [page, setPage] = useState(initial)
|
|
const go = (p: number) => (event: React.MouseEvent) => {
|
|
event.preventDefault()
|
|
setPage(p)
|
|
}
|
|
return (
|
|
<div className="grid gap-2">
|
|
<Pagination>
|
|
<PaginationContent>
|
|
<PaginationItem>
|
|
<PaginationPrevious href={`#page-${page - 1}`} aria-disabled={page === 1} onClick={go(page - 1)} />
|
|
</PaginationItem>
|
|
{pageRange(page, total).map((p) =>
|
|
typeof p === "number" ? (
|
|
<PaginationItem key={p}>
|
|
<PaginationLink href={`#page-${p}`} isActive={p === page} onClick={go(p)}>
|
|
{p}
|
|
</PaginationLink>
|
|
</PaginationItem>
|
|
) : (
|
|
<PaginationItem key={p}>
|
|
<PaginationEllipsis />
|
|
</PaginationItem>
|
|
)
|
|
)}
|
|
<PaginationItem>
|
|
<PaginationNext href={`#page-${page + 1}`} aria-disabled={page === total} onClick={go(page + 1)} />
|
|
</PaginationItem>
|
|
</PaginationContent>
|
|
</Pagination>
|
|
{showValue ? <p className="text-center text-sm text-muted-foreground" data-testid="page-state">{`현재 ${page} / ${total}`}</p> : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const wrap = (node: ReactNode) => <div className="w-full max-w-xl">{node}</div>
|
|
|
|
const sections = [
|
|
{ id: "first", label: "First page", node: <Specimen total={5} initial={1} showValue={false} /> },
|
|
{ id: "middle", label: "Middle page", node: <Specimen total={5} initial={3} showValue={false} /> },
|
|
{ id: "last", label: "Last page", node: <Specimen total={5} initial={5} showValue={false} /> },
|
|
{ id: "ellipsis", label: "Long range", node: <Specimen total={20} initial={10} showValue={false} /> },
|
|
]
|
|
|
|
const meta = { title: "Components/Navigation/Pagination", component: Pagination, parameters: { layout: "padded" } } satisfies Meta<typeof Pagination>
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
const board = (...ids: string[]) => (
|
|
<StateBoard min={420} states={sections.filter((s) => ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, children: wrap(s.node) }))} />
|
|
)
|
|
|
|
// Representative Story: the common Story page; the sections group the same specimens.
|
|
export const All: Story = {
|
|
name: "Pagination",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader title="Pagination" />
|
|
<StorySection title="Overview">{board("middle")}</StorySection>
|
|
<StorySection title="States">{board("first", "last")}</StorySection>
|
|
<StorySection title="Usage">{board("ellipsis")}</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
export const FirstPage: Story = { tags: ["verification"], name: "First Page", render: () => wrap(<Specimen total={5} initial={1} />) }
|
|
export const LastPage: Story = { tags: ["verification"], name: "Last Page", render: () => wrap(<Specimen total={5} initial={5} />) }
|
|
export const LongRange: Story = { tags: ["verification"], name: "Long Range", render: () => wrap(<Specimen total={20} initial={10} />) }
|