Files
cadence-ui/apps/docs/src/components/segmented-control.stories.tsx
T

176 lines
6.9 KiB
TypeScript

import { useState } from "react";
import { SegmentedControl, SegmentedControlItem } from "@ai-ui/ui";
import type { Meta, StoryObj } from "@storybook/react";
type RevenueLens = "revenue" | "support" | "forecast";
type RevenueLensControlProps = {
includeDisabled?: boolean;
orientation?: "horizontal" | "vertical";
};
const lensContent: Record<
RevenueLens,
{
body: string;
eyebrow: string;
title: string;
}
> = {
revenue: {
body:
"Lead scoring, sequence timing, and route suggestions are lifting close quality without making the top-line motion feel synthetic.",
eyebrow: "Revenue lens",
title: "Commercial coverage stays compact while the active segment changes the read."
},
support: {
body:
"Classification, summaries, and next-step hints are reducing queue drag while still keeping team-lead review visible at the handoff edge.",
eyebrow: "Support lens",
title: "Use the segmented switch when the layout already owns the content panel."
},
forecast: {
body:
"A lighter control works well for near-peer views, especially when the panel chrome and surrounding card structure already exist outside the switcher.",
eyebrow: "Forecast lens",
title: "Choose Tabs instead when the control needs to own a tabpanel relationship."
}
};
function RevenueLensControl({
includeDisabled = false,
orientation = "horizontal"
}: RevenueLensControlProps) {
const [value, setValue] = useState<RevenueLens>("revenue");
const activeContent = lensContent[value];
return (
<div className="grid w-[720px] gap-5">
<div className={orientation === "vertical" ? "flex items-start" : undefined}>
<SegmentedControl
aria-label="Revenue dashboard lens"
orientation={orientation}
onValueChange={(nextValue) => setValue(nextValue as RevenueLens)}
value={value}
>
<SegmentedControlItem value="revenue">Revenue</SegmentedControlItem>
<SegmentedControlItem value="support">Support</SegmentedControlItem>
<SegmentedControlItem value="forecast">Forecast</SegmentedControlItem>
{includeDisabled ? (
<SegmentedControlItem disabled value="handoff">
Handoff
</SegmentedControlItem>
) : null}
</SegmentedControl>
</div>
<div className="rounded-[1.75rem] border border-[var(--color-border)] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-card)_88%,white_12%),color-mix(in_oklch,var(--color-primary-container)_18%,var(--color-card)))] p-5 shadow-[var(--shadow-sm)]">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
{activeContent.eyebrow}
</p>
<h3 className="mt-3 text-xl font-semibold tracking-[var(--tracking-tight)] text-[var(--color-foreground)]">
{activeContent.title}
</h3>
<p className="mt-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
{activeContent.body}
</p>
</div>
</div>
);
}
const meta = {
title: "Components/SegmentedControl",
component: SegmentedControl,
parameters: {
docs: {
description: {
component:
"SegmentedControl is the compact single-select switch for lightweight dashboard lenses, filters, and mode changes when the surrounding layout already owns the content surface. Use Tabs instead when the control needs to own tab panels and the full in-place content relationship. The checked state now rides on a shared pill so the control reads as one moving slab rather than separate button fills."
}
},
layout: "centered"
},
tags: ["autodocs"]
} satisfies Meta<typeof SegmentedControl>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Playground: Story = {
render: () => <RevenueLensControl />
};
export const States: Story = {
render: () => <RevenueLensControl includeDisabled />
};
export const Anatomy: Story = {
render: () => (
<div className="w-[760px] rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 text-[var(--color-foreground)] shadow-[var(--shadow-sm)]">
<div className="space-y-3">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
SegmentedControl anatomy
</p>
<RevenueLensControl includeDisabled />
<div className="grid gap-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
<p>
<code className="text-[var(--color-foreground)]">data-slot="root"</code> carries{" "}
<code className="text-[var(--color-foreground)]">data-orientation</code> so the
segment stack can switch between horizontal and vertical layouts without changing the
item API.
</p>
<p>
<code className="text-[var(--color-foreground)]">data-slot="control"</code> exposes
each segment, while <code className="text-[var(--color-foreground)]">data-state="checked"</code>{" "}
and <code className="text-[var(--color-foreground)]">data-disabled</code> stay stable
for styling and tests.
</p>
</div>
</div>
</div>
)
};
export const Motion: Story = {
parameters: {
docs: {
description: {
story:
"The checked segment now carries a shared indicator that glides between items with the same calm timing as the rest of the system. The control should feel tactile and cohesive, not snappy or game-like."
}
}
},
render: () => <RevenueLensControl includeDisabled />
};
export const Accessibility: Story = {
parameters: {
docs: {
description: {
story:
"Keep labels brief, use segmented controls for single-select peer views, and avoid using them as page navigation. If the control needs a persistent tabpanel relationship, choose Tabs instead."
}
}
},
render: () => (
<div className="grid w-[760px] gap-4 lg:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)]">
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
<h3 className="text-lg font-semibold tracking-[var(--tracking-tight)]">
Accessibility notes
</h3>
<div className="mt-4 grid gap-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
<p>Use it for one active choice, not for multi-select filters.</p>
<p>Keep labels short so the active segment remains easy to scan at a glance.</p>
<p>Do not use it as a substitute for top-level page navigation.</p>
</div>
</article>
<div className="flex items-center justify-center rounded-[var(--radius-lg)] border border-dashed border-[var(--color-border-strong)] bg-[var(--color-background)] p-6">
<RevenueLensControl orientation="vertical" />
</div>
</div>
)
};