import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters" import type { Meta, StoryObj } from "@storybook/react" import { controlSizeArgType } from "../argTypes.storyHelpers.ts" import { Badge } from "../Badge/Badge.tsx" import { ContainerBoard, StoryCell, StoryGrid, StorySection, } from "../board.storyHelpers.tsx" import type { SegmentedItem } from "./SegmentedControl.tsx" import { SegmentedControl } from "./SegmentedControl.tsx" /** * rip-deck's column picker, which is where this component came * from. `auto` sits first and always, because a control that only * offers numbers once a number has been picked is a one-way door. */ const COLUMN_ITEMS: SegmentedItem[] = [ { label: "auto · 3", value: "auto" }, { label: "1", value: "1" }, { label: "2", value: "2" }, { label: "3", value: "3" }, { label: "4", value: "4" }, ] const meta = { title: "Components/Controls/SegmentedControl", component: SegmentedControl, parameters: { layout: "padded" }, argTypes: { size: controlSizeArgType }, // The component's own defaults, restated — Storybook does not seed // `args` from docgen, so an unstated default shows in the props // table with nothing selected in its control. args: { size: "md" }, } satisfies Meta export default meta type Story = StoryObj export const Playground: Story = { parameters: playgroundParameters, args: { items: COLUMN_ITEMS, label: "Columns" }, } export const AllVariants: Story = { args: { items: COLUMN_ITEMS, label: "Columns" }, render: (controlProps) => ( {(["sm", "md", "lg"] as const).map((size) => ( ))} ), } const STATE_ITEMS: SegmentedItem[] = [ { label: "auto · 3", value: "auto" }, { label: "1", value: "1" }, { isDisabled: true, label: "2", value: "2" }, { label: "3", value: "3" }, ] /** * A disabled option is out of the *focus* group and still one of the * *options* — it can even be the one a consumer names as the initial * `selectedValue`, which is what a saved preference pointing at a * layout the window no longer fits looks like. */ export const AllStates: Story = { args: { items: STATE_ITEMS, label: "Columns" }, render: (controlProps) => ( ), } /** * It is `inline-flex`, so it takes the width of its options and no * more — a segmented control that stretched to its container would * put four numbers across a whole dashboard header. * * `ContainerBoard` is a width harness here rather than a * container-query claim: `SegmentedControl` declares no container, * because nothing about it needs to *know* its width. */ export const Responsive: Story = { args: { items: COLUMN_ITEMS, label: "Columns" }, render: (controlProps) => ( {(width) => ( )} ), } /** * A board's lane selector is a route selector, not a compact * preference. It reaches the board's edges and each route gets the * same hit area. At a cramped width, two targets share the first * row and the final target takes the full second row, so no label * wraps or clips. */ export const FullWidth: Story = { args: { isFullWidth: true, items: [ { label: ( Todo ), value: "todo", }, { label: ( In Progress ), value: "in-progress", }, { label: ( Needs Review ), value: "needs-review", }, ], label: "Board lanes", }, render: (controlProps) => ( {(width) => ( )} ), } /** * The complete keyboard path. Tab enters the group **once** — the * roving-tabindex rule — then the arrow keys move and check * together, which is what a radio group does and what `Tabs`' * `manual` mode deliberately does not. */ export const Interactive: Story = { args: { items: COLUMN_ITEMS, label: "Columns" }, } /** Inline rich labels must be centered as boxes, not aligned to an anonymous text baseline. */ export const RichLabels: Story = { args: { isFullWidth: true, label: "Message type", items: [ { value: "text", label: ( ), }, { value: "voice", label: ( ), }, ], }, } export const NarrowRichLabels: Story = { args: RichLabels.args, render: (controlProps) => ( {(width) => ( )} ), } export const FragmentLabels: Story = { args: { isFullWidth: true, label: "Message format", items: [ { value: "text", label: ( <>