import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters" import type { Meta, StoryObj } from "@storybook/react" import type { ReactNode } from "react" import { useState } from "react" import { ContainerBoard, StoryCell, StoryGrid, } from "../board.storyHelpers.tsx" import { EmptyState } from "../EmptyState/EmptyState.tsx" import { LaneTimeline } from "./LaneTimeline.tsx" import type { TimelineItem } from "./LaneTimelineBar.tsx" import type { TimelineLane } from "./LaneTimelineLane.tsx" /** * **Invented, all of it.** The calendar this component was built for * is a real household, and a fixture taken from one would be * unremovable the moment it reached a committed screenshot — a PNG * is opaque to every grep and no text scrub redacts it. * * So this is a fictional recording studio's week. The names are the * same four the `Board` stories use, because a reader moving between * two docs pages should not have to learn a second cast. * * Every date is **April 2026**, and `2026-04-06` is a Monday — so a * seven-day week is `06` to `12`, and the inclusive end is something * a reader can check by counting columns rather than by trusting * prose. */ const WEEK = { end: "2026-04-12", start: "2026-04-06", } const MONTH = { end: "2026-04-30", start: "2026-04-01", } /** * The load asymmetry, which is the whole reason the component exists * rather than a month grid. Wren is carrying most of this week and * Ada is carrying one afternoon of it, and that is meant to be * obvious before anybody reads a word: Wren's lane stacks three bars * deep and is three times the height of Ada's. */ const LANES: TimelineLane[] = [ { items: [ { end: "2026-04-08", key: "foley", start: "2026-04-07", title: "Foley session", }, ], key: "ada", label: "Ada", }, { items: [ { end: "2026-04-12", key: "on-call", start: "2026-04-06", title: "On call for the rig", }, { end: "2026-04-09", key: "field-trip", start: "2026-04-06", title: "Field recording trip", }, { end: "2026-04-06", key: "mic-locker", start: "2026-04-06", title: "Mic locker inventory", }, { end: "2026-04-10", key: "mixing", start: "2026-04-08", title: "Mixing block", }, { end: "2026-04-09", key: "listen-back", start: "2026-04-09", title: "Client listen back", }, { end: "2026-04-12", key: "stems", start: "2026-04-10", title: "Stems delivery", }, ], key: "wren", label: "Wren", }, { items: [ { end: "2026-04-08", key: "score-revisions", start: "2026-04-06", title: "Score revisions", }, { end: "2026-04-10", key: "coast-train", start: "2026-04-10", title: "Train to the coast", }, { end: "2026-04-12", key: "rig-service", start: "2026-04-11", title: "Rig service", }, ], key: "juno", label: "Juno", }, { items: [ { end: "2026-04-11", key: "patch-bay", start: "2026-04-09", title: "Patch bay rewire", }, ], key: "rook", label: "Rook", }, ] /** * The four edge cases, in one lane, because each of them is a place * a naive implementation draws nothing or draws it somewhere else — * and none of the four is visible in a screenshot without a correct * one beside it. */ const EDGE_CASE_ITEMS: TimelineItem[] = [ { // Starts nine days before the week. Clipped to column 1, square // leading edge, and its hidden sentence still says 30 March. end: "2026-04-08", key: "long-hire", start: "2026-03-30", title: "Desk hire, started last month", }, { // Runs to the end of April. Clipped to the last column, square // trailing edge. end: "2026-04-30", key: "residency", start: "2026-04-10", title: "Residency, runs into May", }, { // One day. A bar, not a dot — one vocabulary on the axis. end: "2026-04-09", key: "delivery", start: "2026-04-09", title: "Tape delivery", }, { // `end` before `start`: somebody typed the dates the wrong way // round. Drawn on its start day rather than dropped, because a // dropped row is a data fault nobody can see. end: "2026-04-06", key: "reversed", start: "2026-04-11", title: "Kit return, dates entered backwards", }, { // Entirely outside the week. Not drawn at all, and not counted — // nothing is missing from this picture, the picture is of // another week. end: "2026-04-26", key: "next-month", start: "2026-04-20", title: "Next month's session", }, ] /** * A **fixed** inline size, because the fold is a measurement of the * component's own box. A story that inherited the canvas width would * draw an axis on one machine and the Narrow View on another, and a * screenshot of it would be a screenshot of somebody's window. */ const Frame = ({ children, inlineSize, }: { children: ReactNode inlineSize: string }): ReactNode => (
{children}
) const meta = { title: "Components/Data/LaneTimeline", component: LaneTimeline, parameters: { layout: "padded" }, args: { headingLevel: 3, label: "The week", lanes: LANES, /* * Pinned, and not because anybody prefers it. `Intl` with no * locale uses the runtime's, so the same story renders * "Mon 6 Apr" on one machine and "Mon, Apr 6" on the next — and * the test asserting the bar's name would then be asserting the * test runner's locale. An app passes its own, or omits it and * gets the reader's. */ locale: "en-GB", maxRowCount: 6, minColumnSize: 1.75, range: WEEK, }, } satisfies Meta export default meta type Story = StoryObj /** * Four lanes across one week, at the density a real schedule * reaches. * * Read it for the **shape** rather than the entries: Wren's lane is * three bars deep for most of the week and Ada's is one bar on one * afternoon. That is the question a month grid cannot answer, because * a grid gives every day the same box no matter what is in it. */ export const Playground: Story = { parameters: playgroundParameters, render: (timelineProps) => ( ), } /** * Plain bars, lanes whose headings navigate, and bars that act. * * `href` and `onSelect` are a **union** on the item, not two * optionals: the first renders an `` and the second a `