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 `