import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters" import type { Meta, StoryObj } from "@storybook/react" import { useState } from "react" import { StoryCell, StoryGrid, StoryRow, StorySection, } from "../board.storyHelpers.tsx" import { Card } from "../Card/Card.tsx" import { SegmentedControl } from "../SegmentedControl/SegmentedControl.tsx" import { AdaptiveGrid } from "./AdaptiveGrid.tsx" import { getColumnChoices } from "./chooseColumns.ts" import type { BlockSizeResolver } from "./useAdaptiveColumns.ts" /** * A fixed block size, so a story shows the rule rather than the * height of whichever frame Storybook happened to render it in. * * This is also the seam that makes the DOM tests deterministic: the * source app forced the viewport with * `Object.defineProperty(window, "innerHeight", …)`, because in a * real chromium `innerHeight` is a read-only accessor and a plain * assignment is a silent no-op. Injecting a resolver retires that * trick entirely. */ const fixedBlockSize = ( blockSize: number, ): BlockSizeResolver => ({ get: () => blockSize, subscribe: () => () => {}, }) /** * `getColumnChoices({ maxColumns: 4 })`, as the picker's items — * `"auto"` first, because a picker that only offers numbers once a * number has been chosen is a one-way door. */ const COLUMN_ITEMS = getColumnChoices({ maxColumns: 4, }).map((choice) => ({ label: String(choice), value: String(choice), })) const NINE_ITEMS = Array.from( { length: 9 }, (_, index) => `Bay ${index + 1}`, ) const Item = ({ label }: { label: string }) => (

{label}

{/* * An unbroken string with no wrap opportunity — the thing a * grid item's `min-content` minimum turns into a horizontal * scrollbar. `truncate` clips it; `[&>*]:min-w-0` inside * `AdaptiveGrid` is what lets the track get small enough for * the clipping to matter. */}

/mnt/Bunnies/Family/Media/very/long/unbroken/path/that/would/push/a/track/wide

) const meta = { title: "Components/Layout/AdaptiveGrid", component: AdaptiveGrid, parameters: { layout: "padded" }, args: { children: NINE_ITEMS.map((label) => ( )), chromeBlockSize: 260, itemBlockSize: 150, }, } satisfies Meta export default meta type Story = StoryObj /** * Nine items in a 1080px-tall window. Five stack, so two columns * carry nine — and the content cap widens from 56rem to 72rem to * hold them. */ export const Playground: Story = { parameters: playgroundParameters, args: { blockSizeResolver: fixedBlockSize(1080) }, } /** * The same nine items, the same width, three window heights. * * This is the whole rule in one board, and the middle panel is the * part that reads backwards: a **taller** window takes **fewer** * columns, because height is what a column is bought with. */ export const AllVariants: Story = { render: (gridProps) => ( {[ { blockSize: 1440, label: "1440px tall — 2 columns", }, { blockSize: 900, label: "900px tall — 3 columns" }, { blockSize: 600, label: "600px tall — 3 columns" }, ].map((panel) => (
{panel.label} {NINE_ITEMS.map((label) => ( ))}
))}
), } /** * One item is one column at every size — the case that would * otherwise stretch a lone card across an ultrawide. */ export const AllStates: Story = { args: { blockSizeResolver: fixedBlockSize(900) }, render: (gridProps) => ( {NINE_ITEMS.map((label) => ( ))} ), } /** * Three container widths, with the column floor dropped to 160px so * the panels differ inside a docs page. * * The honest demonstration that the inline size is read from the * **container** and not the window: the browser never moves, and * the cap still falls from three columns to one. */ export const Responsive: Story = { args: { blockSizeResolver: fixedBlockSize(600), minColumnInlineSize: 160, }, render: (gridProps) => (
{[ { inlineSize: "15rem", label: "15rem container" }, { inlineSize: "24rem", label: "24rem container" }, { inlineSize: "34rem", label: "34rem container" }, ].map((panel) => (
{panel.label} {NINE_ITEMS.slice(0, 6).map((label) => ( ))}
))}
), } /** * The manual override beside the automatic answer. * * A picker that only offered numbers once a number had been chosen * would strand a person on a layout they tried once, so `"auto"` is * always in the list. */ export const Interactive: Story = { args: { blockSizeResolver: fixedBlockSize(1080) }, render: (gridProps) => { const [choice, setChoice] = useState("auto") return ( { setChoice(value ?? "auto") }} selectedValue={choice} /> {NINE_ITEMS.map((label) => ( ))} ) }, } /** A small fleet and a gallery opt in to spending width first. */ export const Fill: Story = { args: { layout: "fill", minColumnInlineSize: 300, maxColumns: 6, blockSizeResolver: fixedBlockSize(1920), }, render: (gridProps) => ( {NINE_ITEMS.slice(0, 6).map((label) => ( ))} ), }