import type { ColorSchemeMode } from "@charcuterie/logic" import { type ReactNode, useState } from "react" import { Badge } from "./Badge/Badge.tsx" import { Button } from "./Button/Button.tsx" import { Card } from "./Card/Card.tsx" import { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.tsx" import { MonitorIcon, MoonIcon, SunIcon, } from "./icons.storyHelpers.tsx" /** * The furniture the four shell stories share: a rail's worth of * links, a page's worth of cards, the scheme control that lives in * the header, and the three things that make a page scroll * sideways. * * `.storyHelpers.tsx` rather than `.tsx` because `react/no-multi-comp` * is switched off for that suffix, and it is excluded from the * package build so none of this ships. */ /** * A scheme control with its own state — `ColorSchemeToggle` rather * than `ColorSchemeSwitcher`, deliberately. * * The connected switcher writes `data-scheme` onto `` and a * mode into `localStorage`. In a shell story that is a component * reaching outside its own canvas to re-theme Storybook and leave * a value behind for the next test, which is exactly the kind of * cross-test coupling `mountStory` exists to prevent. The * presentational half proves the same thing about `Header` — that * the actions slot holds the fleet's scheme control — and touches * nothing. */ export const HeaderSchemeToggle = (): ReactNode => { const [mode, setMode] = useState("system") return ( , light: , system: , }} mode={mode} onCycle={setMode} size="sm" /> ) } /** What a start rail holds: the app's sections. */ export const RailNavigation = (): ReactNode => ( <> {["Queue", "Library", "Transfers", "Settings"].map( (section) => ( {section} ), )} ) /** What an end rail holds: the detail of whatever is selected. */ export const RailDetail = (): ReactNode => ( <> Selected job Disc 3 of 9 · reading at 6.0x Healthy ) /** An ordinary page, so a template shows something. */ export const PageContent = (): ReactNode => ( <>

Nine discs, three bays. The column stays readable at any window width because the cap is a token rather than a number somebody typed.

Start} heading="Bay 3" >

Idle since 21:04.

) /** * Enough page to scroll, with **no landmarks in it**. * * `` twice would be the obvious way to make a tall * page and it is an accessibility violation: `Card` takes a * `region` landmark from its heading, so two copies are two pairs * of identically-named regions — axe's `landmark-unique`, and the * same "which one did the agent mean" ambiguity in a test. A * sticky-header demo needs height, not more landmarks. */ export const ScrollFiller = (): ReactNode => ( <> {[ "Bays are polled every two seconds.", "A stalled bay is one that has not moved a byte in forty.", "Verification runs after the last title, never during.", "The tower's USB hub is the single point of failure.", "Discs are ejected only once the hash matches.", "A retry lands in a different bay when one is free.", "Nothing here is a landmark, which is the point.", "Scroll to watch the header stay where it is.", ].map((line) => (

{line}

))} ) /** * A closed drawer parked off the inline edge by a transform — the * third shape that makes a page scroll sideways, and the one that * broke plex-channels. * * It is still laid out, still painted, and still part of the * scrollable overflow region of whatever it resolves against: a * transform does not remove a box from that region, and neither * does `visibility: hidden`. So the page scrolls sideways to reach * a panel nobody can see, and every `min-width: 0` in the tree is * irrelevant. Only a clip on the box's containing block answers * it. * * **Which element that is depends on where the drawer is * rendered, and the two stories place it differently on purpose.** * Beside `Main` it is `Shell`'s to clip, which is where an app's * parked chrome belongs. Inside `Main` it is `Main`'s, because * `Main` is `position: relative` — and that is why `Main` carries * an `overflow-x: hidden` of its own rather than leaning on the * frame's. */ export const ParkedDrawer = (): ReactNode => (