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 (
{pageRange(page, total).map((p) =>
typeof p === "number" ? (
{p}
) : (
)
)}
{showValue ?
{`현재 ${page} / ${total}`}
: null}
)
}
const wrap = (node: ReactNode) => {node}
const sections = [
{ id: "first", label: "First page", node: },
{ id: "middle", label: "Middle page", node: },
{ id: "last", label: "Last page", node: },
{ id: "ellipsis", label: "Long range", node: },
]
const meta = { title: "Components/Navigation/Pagination", component: Pagination, parameters: { layout: "padded" } } satisfies Meta
export default meta
type Story = StoryObj
// Representative-page layout (Storybook presentation only): one Variants section, 2 equal columns x 2 rows on PC (iframe width
// 1030px and up), 1 column below that. Each specimen sits in its own single-column StateBoard (min larger than any
// column), so every title and divider has the width of its column. The Pagination root is `mx-auto flex w-full justify-center`;
// `justify-start` on the cell left-aligns the button group in its column without stretching it.
const cellAlignLeft = "w-full [&_[data-slot=pagination]]:justify-start"
// Representative Story: the common Story page. Overview / States / Usage are merged into Variants
// (First page, Middle page, Last page, Long range).
export const All: Story = {
name: "Pagination",
parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => (
{/* Long range is 447px wide (20 pages, 7 items), so a column must be at least that: 16px column gap and the 2-column switch at
iframe width 1030px give 449px columns at 1280px; below that it is one column (460px minimum) instead of a wrapped "Next". */}
{["first", "middle", "last", "ellipsis"].map((id) => {
const spec = sections.find((section) => section.id === id)!
return {spec.node}
}]} />
})}
),
}
export const FirstPage: Story = { tags: ["verification"], name: "First Page", render: () => wrap() }
export const LastPage: Story = { tags: ["verification"], name: "Last Page", render: () => wrap() }
export const LongRange: Story = { tags: ["verification"], name: "Long Range", render: () => wrap() }