Skip to main content

ui_storybook/stories/
primitives.rs

1//! Primitive stories — `Button`, `Card`, `Surface`, `Badge`,
2//! `Divider`, `Kbd`, `IconTile`. UI-01 expanded this list with the
3//! design-token + base surface primitives.
4
5use leptos::prelude::*;
6
7use crate::components::primitives::{
8    Badge, BadgeKind, Button, ButtonSize, ButtonVariant, Card, CardBody, CardHeader, Divider,
9    DividerOrientation, IconTile, IconTileKind, Kbd, Surface, SurfaceKind,
10};
11
12use super::{Story, StoryViewport, render};
13
14/// All primitive-surface stories, in display order.
15#[must_use]
16pub fn stories() -> Vec<Story> {
17    vec![
18        Story {
19            id: "button-variants",
20            category: "Primitives",
21            title: "Button — variants",
22            viewport: StoryViewport::Auto,
23            render: render_button_variants,
24        },
25        Story {
26            id: "button-sizes",
27            category: "Primitives",
28            title: "Button — sizes",
29            viewport: StoryViewport::Auto,
30            render: render_button_sizes,
31        },
32        Story {
33            id: "card-basic",
34            category: "Primitives",
35            title: "Card — header + body",
36            viewport: StoryViewport::Auto,
37            render: render_card_basic,
38        },
39        Story {
40            id: "tokens-dark-zinc",
41            category: "Primitives",
42            title: "Tokens — semantic surface + text",
43            viewport: StoryViewport::Auto,
44            render: render_tokens_dark_zinc,
45        },
46        Story {
47            id: "surface-stack",
48            category: "Primitives",
49            title: "Surface — five kinds stacked",
50            viewport: StoryViewport::Auto,
51            render: render_surface_stack,
52        },
53        Story {
54            id: "badge-variants",
55            category: "Primitives",
56            title: "Badge — six kinds",
57            viewport: StoryViewport::Auto,
58            render: render_badge_variants,
59        },
60        Story {
61            id: "kbd-shortcuts",
62            category: "Primitives",
63            title: "Kbd — keyboard shortcut chips",
64            viewport: StoryViewport::Auto,
65            render: render_kbd_shortcuts,
66        },
67        Story {
68            id: "icon-tile-variants",
69            category: "Primitives",
70            title: "IconTile — five kinds",
71            viewport: StoryViewport::Auto,
72            render: render_icon_tile_variants,
73        },
74    ]
75}
76
77fn render_button_variants() -> String {
78    render(view! {
79        <div class="story-row">
80            <Button variant=ButtonVariant::Default>"Default"</Button>
81            <Button variant=ButtonVariant::Outline>"Outline"</Button>
82            <Button variant=ButtonVariant::Ghost>"Ghost"</Button>
83            <Button variant=ButtonVariant::Destructive>"Destructive"</Button>
84            <Button variant=ButtonVariant::Secondary>"Secondary"</Button>
85        </div>
86    })
87}
88
89fn render_button_sizes() -> String {
90    render(view! {
91        <div class="story-row">
92            <Button size=ButtonSize::Sm>"Small"</Button>
93            <Button size=ButtonSize::Md>"Medium"</Button>
94            <Button size=ButtonSize::Lg>"Large"</Button>
95            <Button disabled=true>"Disabled"</Button>
96        </div>
97    })
98}
99
100fn render_card_basic() -> String {
101    render(view! {
102        <Card>
103            <CardHeader title="Recording 01" subtitle="Captured 2026-05-08 · 1m 24s" />
104            <CardBody>
105                <p>"Drag the file onto the player or hit "<kbd>"⌘O"</kbd>" to import."</p>
106                <div class="story-row">
107                    <Button variant=ButtonVariant::Default>"Open editor"</Button>
108                    <Button variant=ButtonVariant::Ghost>"Reveal in Finder"</Button>
109                </div>
110            </CardBody>
111        </Card>
112    })
113}
114
115fn render_tokens_dark_zinc() -> String {
116    // Inline swatch styles use `style=` because the tokens are the
117    // demo content — the only place raw colors are allowed outside
118    // `style.css`.
119    render(view! {
120        <div class="token-grid">
121            <div class="token-name">"--surface-base"</div>
122            <div class="token-swatch" style="background:var(--surface-base)"></div>
123            <div class="token-value">"App background"</div>
124
125            <div class="token-name">"--surface-elevated"</div>
126            <div class="token-swatch" style="background:var(--surface-elevated)"></div>
127            <div class="token-value">"Panels + cards"</div>
128
129            <div class="token-name">"--surface-popover"</div>
130            <div class="token-swatch" style="background:var(--surface-popover)"></div>
131            <div class="token-value">"Menus + popovers"</div>
132
133            <div class="token-name">"--surface-selected"</div>
134            <div class="token-swatch" style="background:var(--surface-selected)"></div>
135            <div class="token-value">"Selected row inside a menu"</div>
136
137            <div class="token-name">"--text-primary"</div>
138            <div class="token-swatch" style="background:var(--text-primary)"></div>
139            <div class="token-value">"Default text colour"</div>
140
141            <div class="token-name">"--text-secondary"</div>
142            <div class="token-swatch" style="background:var(--text-secondary)"></div>
143            <div class="token-value">"Muted labels"</div>
144
145            <div class="token-name">"--text-tertiary"</div>
146            <div class="token-swatch" style="background:var(--text-tertiary)"></div>
147            <div class="token-value">"Footnotes + axis labels"</div>
148
149            <div class="token-name">"--action-record"</div>
150            <div class="token-swatch" style="background:var(--action-record)"></div>
151            <div class="token-value">"Record / destructive action"</div>
152        </div>
153    })
154}
155
156fn render_surface_stack() -> String {
157    render(view! {
158        <div class="surface-stack">
159            <Surface kind=SurfaceKind::Base>
160                <div class="surface-demo">"surface-base — the app canvas"</div>
161            </Surface>
162            <Surface kind=SurfaceKind::Elevated>
163                <div class="surface-demo">"surface-elevated — panels + cards"</div>
164            </Surface>
165            <Surface kind=SurfaceKind::Popover>
166                <div class="surface-demo">"surface-popover — menus + tray popovers"</div>
167            </Surface>
168            <Surface kind=SurfaceKind::Selected>
169                <div class="surface-demo">"surface-selected — highlighted row inside a menu"</div>
170            </Surface>
171            <Surface kind=SurfaceKind::Glass>
172                <div class="surface-demo">"surface-glass — overlay over recording"</div>
173            </Surface>
174        </div>
175    })
176}
177
178fn render_badge_variants() -> String {
179    render(view! {
180        <div class="badge-row">
181            <Badge kind=BadgeKind::Neutral>"Beta"</Badge>
182            <Badge kind=BadgeKind::Accent>"New"</Badge>
183            <Badge kind=BadgeKind::Danger>"Failed"</Badge>
184            <Badge kind=BadgeKind::Live>"Live"</Badge>
185            <Badge kind=BadgeKind::Plan>"Pro"</Badge>
186            <Badge kind=BadgeKind::Count>"12"</Badge>
187            <Divider orientation=DividerOrientation::Vertical />
188            <span style="font-size:11px;color:var(--text-tertiary)">"divider shown inline (vertical)"</span>
189        </div>
190    })
191}
192
193fn render_kbd_shortcuts() -> String {
194    render(view! {
195        <div class="story-row" style="padding:14px;flex-direction:column;align-items:flex-start;gap:10px">
196            <div class="story-row">
197                <span style="font-size:12px;color:var(--text-secondary)">"Start recording"</span>
198                <Kbd keys=vec!["⌘", "⇧", "R"] />
199            </div>
200            <div class="story-row">
201                <span style="font-size:12px;color:var(--text-secondary)">"Stop recording"</span>
202                <Kbd keys=vec!["⌘", "⇧", "S"] />
203            </div>
204            <div class="story-row">
205                <span style="font-size:12px;color:var(--text-secondary)">"Pause / resume"</span>
206                <Kbd keys=vec!["Space"] />
207            </div>
208            <div class="story-row">
209                <span style="font-size:12px;color:var(--text-secondary)">"Open editor"</span>
210                <Kbd keys=vec!["⌘", "O"] />
211            </div>
212        </div>
213    })
214}
215
216fn render_icon_tile_variants() -> String {
217    render(view! {
218        <div class="story-row" style="padding:14px;gap:14px">
219            <div class="story-row" style="flex-direction:column;gap:4px">
220                <IconTile kind=IconTileKind::Workspace>"PE"</IconTile>
221                <span style="font-size:11px;color:var(--text-tertiary)">"Workspace"</span>
222            </div>
223            <div class="story-row" style="flex-direction:column;gap:4px">
224                <IconTile kind=IconTileKind::Device>"🎙"</IconTile>
225                <span style="font-size:11px;color:var(--text-tertiary)">"Device"</span>
226            </div>
227            <div class="story-row" style="flex-direction:column;gap:4px">
228                <IconTile kind=IconTileKind::App>"S"</IconTile>
229                <span style="font-size:11px;color:var(--text-tertiary)">"App"</span>
230            </div>
231            <div class="story-row" style="flex-direction:column;gap:4px">
232                <IconTile kind=IconTileKind::Action>"+"</IconTile>
233                <span style="font-size:11px;color:var(--text-tertiary)">"Action"</span>
234            </div>
235            <div class="story-row" style="flex-direction:column;gap:4px">
236                <IconTile kind=IconTileKind::User>"M"</IconTile>
237                <span style="font-size:11px;color:var(--text-tertiary)">"User"</span>
238            </div>
239        </div>
240    })
241}