@syscore/ui-library 1.25.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/client/components/ui/app-bar.tsx +299 -0
  2. package/client/components/ui/banner.tsx +108 -0
  3. package/client/components/ui/bottom-navigation.tsx +223 -0
  4. package/client/components/ui/card.tsx +108 -26
  5. package/client/components/ui/date-picker.tsx +188 -0
  6. package/client/components/ui/empty-state.tsx +197 -0
  7. package/client/components/ui/file-upload.tsx +214 -0
  8. package/client/components/ui/footer.tsx +179 -0
  9. package/client/components/ui/layout.tsx +381 -0
  10. package/client/components/ui/sidebar.tsx +11 -5
  11. package/client/components/ui/stepper.tsx +148 -0
  12. package/client/components/ui/system-bar.tsx +119 -0
  13. package/client/components/ui/timeline.tsx +181 -0
  14. package/client/global.css +2304 -41
  15. package/client/ui/AppBar/app-bar.stories.tsx +280 -0
  16. package/client/ui/Banner/banner.stories.tsx +238 -0
  17. package/client/ui/BottomNavigation/bottom-navigation.stories.tsx +285 -0
  18. package/client/ui/Card.stories.tsx +285 -168
  19. package/client/ui/DatePicker/DatePicker.stories.tsx +293 -0
  20. package/client/ui/EmptyState/empty-state.stories.tsx +251 -0
  21. package/client/ui/FileUpload/FileUpload.stories.tsx +218 -0
  22. package/client/ui/Footer/footer.stories.tsx +194 -0
  23. package/client/ui/Layout.stories.tsx +1481 -0
  24. package/client/ui/Stepper/Stepper.stories.tsx +344 -0
  25. package/client/ui/SystemBar/system-bar.stories.tsx +179 -0
  26. package/client/ui/Timeline/timeline.stories.tsx +404 -0
  27. package/dist/index.cjs.js +1 -1
  28. package/dist/index.d.ts +219 -0
  29. package/dist/index.es.js +1504 -99
  30. package/package.json +1 -1
@@ -0,0 +1,1481 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Stack, Grid, Columns, Container, Section, AppShell, AppShellHeader, AppShellSidebar, AppShellMain, Show, Hide, Positioned, Spacer } from "../components/ui/layout";
3
+ import { Card, CardHeader, CardTitle, CardContent } from "../components/ui/card";
4
+ import { Button } from "../components/ui/button";
5
+ import { Text } from "../components/ui/typography";
6
+ import { Badge } from "../components/ui/badge";
7
+
8
+ const meta = {
9
+ title: "Layout/Primitives",
10
+ component: Stack,
11
+ tags: ["autodocs"],
12
+ parameters: {
13
+ layout: "padded",
14
+ docs: {
15
+ description: {
16
+ component: [
17
+ "Layout primitives that replace raw `<div>` wrappers. No `className` needed — intent is prop-driven.",
18
+ "",
19
+ "---",
20
+ "",
21
+ "## Why layout components?",
22
+ "",
23
+ "Raw divs with Tailwind work fine on day one. Six weeks later, no one knows what this means:",
24
+ "",
25
+ "```tsx",
26
+ "// ❌ What is this? A row? A column? Why flex-wrap?",
27
+ `<div className="flex flex-col gap-4 items-start justify-between flex-wrap">`,
28
+ ` <div className="grid grid-cols-3 gap-6 w-full max-w-5xl mx-auto px-4">`,
29
+ " …",
30
+ " </div>",
31
+ "</div>",
32
+ "```",
33
+ "",
34
+ "With layout components, the code reads like what it **is**:",
35
+ "",
36
+ "```tsx",
37
+ "// ✅ Reads like a layout description",
38
+ "<Stack gap={4} align=\"start\" justify=\"between\" wrap>",
39
+ " <Grid cols={3} gap={6}>",
40
+ " …",
41
+ " </Grid>",
42
+ "</Stack>",
43
+ "```",
44
+ "",
45
+ "---",
46
+ "",
47
+ "## Import",
48
+ "",
49
+ "```tsx",
50
+ `import {`,
51
+ ` Stack, Grid, Columns, Container, Section,`,
52
+ ` AppShell, AppShellHeader, AppShellSidebar, AppShellMain, AppShellFooter,`,
53
+ ` Show, Hide, Positioned, Spacer,`,
54
+ `} from "@syscore/ui-library"`,
55
+ "```",
56
+ "",
57
+ "---",
58
+ "",
59
+ "## Which component do I reach for?",
60
+ "",
61
+ "| Situation | Use |",
62
+ "|-----------|-----|",
63
+ "| Vertical list of items | `Stack` |",
64
+ "| Horizontal row of items | `Stack direction=\"horizontal\"` |",
65
+ "| Equal-width grid of cards | `Grid` |",
66
+ "| Sidebar + content (different widths) | `Columns` |",
67
+ "| Center content with max-width | `Container` |",
68
+ "| Vertical padding between page sections | `Section` |",
69
+ "| Full app frame (header + sidebar + main) | `AppShell` |",
70
+ "| Show or hide at a breakpoint | `Show` / `Hide` |",
71
+ "| Pin a badge or overlay to a parent | `Positioned` |",
72
+ "| Fixed whitespace gap | `Spacer` |",
73
+ "",
74
+ "---",
75
+ "",
76
+ "## Grid vs Columns — the most important decision",
77
+ "",
78
+ "This is where most confusion happens:",
79
+ "",
80
+ "```tsx",
81
+ "// ❌ Wrong: both columns are equal (50 / 50)",
82
+ "<Grid cols={2}>",
83
+ " <Sidebar /> {/* 50% wide — too wide */}",
84
+ " <Main /> {/* 50% wide — too narrow */}",
85
+ "</Grid>",
86
+ "",
87
+ "// ✅ Right: sidebar is fixed, content is fluid",
88
+ `<Columns preset="sidebar"> {/* 280px fixed | 1fr fluid */}`,
89
+ " <Sidebar />",
90
+ " <Main />",
91
+ "</Columns>",
92
+ "```",
93
+ "",
94
+ "> **Rule:** Equal widths → `Grid`. Different widths → `Columns`.",
95
+ ].join("\n"),
96
+ },
97
+ source: { type: "code" },
98
+ },
99
+ },
100
+ } satisfies Meta<typeof Stack>;
101
+
102
+ export default meta;
103
+ type Story = StoryObj<typeof meta>;
104
+
105
+ // Demo box — only used for visual presentation inside Storybook canvas
106
+ const Box = ({ label, tall }: { label?: string; tall?: boolean }) => (
107
+ <div
108
+ style={{ minHeight: tall ? 96 : 56 }}
109
+ className="flex items-center justify-center rounded-lg border border-dashed border-cyan-400 bg-cyan-50 text-cyan-800 text-sm font-medium"
110
+ >
111
+ {label ?? "Item"}
112
+ </div>
113
+ );
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Stack
117
+ // ---------------------------------------------------------------------------
118
+
119
+ export const StackVertical: Story = {
120
+ parameters: {
121
+ docs: {
122
+ description: {
123
+ story: [
124
+ "**Stack** is the workhorse of layout. Use it whenever items need to be arranged in a line — vertical or horizontal — with consistent spacing between them.",
125
+ "",
126
+ "**The problem:**",
127
+ "```tsx",
128
+ "// ❌ Hard to scan at a glance — is this a row or column? What's the gap?",
129
+ `<div className="flex flex-col gap-6 items-center justify-between">`,
130
+ " …",
131
+ "</div>",
132
+ "```",
133
+ "",
134
+ "**The solution:**",
135
+ "```tsx",
136
+ "// ✅ Intent is obvious",
137
+ `<Stack direction="vertical" gap={6} align="center" justify="between">`,
138
+ " …",
139
+ "</Stack>",
140
+ "```",
141
+ "",
142
+ "**Key props:**",
143
+ "",
144
+ "| Prop | Values | Default | Effect |",
145
+ "|------|--------|---------|--------|",
146
+ "| `direction` | `\"vertical\"` \\| `\"horizontal\"` | `\"vertical\"` | Column vs row |",
147
+ "| `gap` | number (Tailwind scale) | `4` (1rem) | Space between items |",
148
+ "| `align` | `\"start\"` \\| `\"center\"` \\| `\"end\"` \\| `\"stretch\"` \\| `\"baseline\"` | — | Cross-axis alignment |",
149
+ "| `justify` | `\"start\"` \\| `\"center\"` \\| `\"end\"` \\| `\"between\"` \\| `\"around\"` \\| `\"evenly\"` | — | Main-axis distribution |",
150
+ "| `wrap` | `boolean` | `false` | Allow items to wrap to the next line |",
151
+ "| `as` | HTML tag | `\"div\"` | Render as `nav`, `ul`, `header`, etc. |",
152
+ "",
153
+ "**Gap scale:** `gap={4}` = 1rem (16px). Follows Tailwind: 1 unit = 4px. So `gap={6}` = 1.5rem, `gap={8}` = 2rem.",
154
+ "",
155
+ "**Common mistake:** Using Stack for sidebar + content layouts. Stack gives equal space to all children. If you need a fixed-width sidebar next to fluid content, use `Columns` instead.",
156
+ ].join("\n"),
157
+ },
158
+ source: {
159
+ code: `import { Stack } from "@syscore/ui-library"
160
+
161
+ <Stack gap={4}>
162
+ <div>Item 1</div>
163
+ <div>Item 2</div>
164
+ <div>Item 3</div>
165
+ </Stack>`,
166
+ },
167
+ },
168
+ },
169
+ render: () => (
170
+ <Stack gap={4} className="max-w-sm w-full">
171
+ <Box label="Item 1" />
172
+ <Box label="Item 2" />
173
+ <Box label="Item 3" />
174
+ </Stack>
175
+ ),
176
+ };
177
+
178
+ export const StackHorizontal: Story = {
179
+ parameters: {
180
+ docs: {
181
+ source: {
182
+ code: `import { Stack } from "@syscore/ui-library"
183
+
184
+ <Stack direction="horizontal" gap={4} align="center">
185
+ <div>Item 1</div>
186
+ <div>Item 2</div>
187
+ <div>Item 3</div>
188
+ </Stack>`,
189
+ },
190
+ },
191
+ },
192
+ render: () => (
193
+ <Stack direction="horizontal" gap={4} align="center">
194
+ <Box label="Item 1" />
195
+ <Box label="Item 2" />
196
+ <Box label="Item 3" />
197
+ </Stack>
198
+ ),
199
+ };
200
+
201
+ export const StackSpaceBetween: Story = {
202
+ parameters: {
203
+ docs: {
204
+ source: {
205
+ code: `import { Stack } from "@syscore/ui-library"
206
+ import { Button, Text } from "@syscore/ui-library"
207
+
208
+ <Stack direction="horizontal" justify="between" align="center">
209
+ <Text variant="heading-xxsmall">Page Title</Text>
210
+ <Stack direction="horizontal" gap={2}>
211
+ <Button variant="secondary-light" size="large">Cancel</Button>
212
+ <Button variant="primary-dark" size="large">Save</Button>
213
+ </Stack>
214
+ </Stack>`,
215
+ },
216
+ },
217
+ },
218
+ render: () => (
219
+ <Stack direction="horizontal" justify="between" align="center" className="w-full max-w-2xl border rounded-xl p-4">
220
+ <Text variant="heading-xxsmall">Page Title</Text>
221
+ <Stack direction="horizontal" gap={2}>
222
+ <Button variant="secondary-light" size="large">Cancel</Button>
223
+ <Button variant="primary-dark" size="large">Save</Button>
224
+ </Stack>
225
+ </Stack>
226
+ ),
227
+ };
228
+
229
+ export const StackWrap: Story = {
230
+ parameters: {
231
+ docs: {
232
+ source: {
233
+ code: `import { Stack } from "@syscore/ui-library"
234
+ import { Badge } from "@syscore/ui-library"
235
+
236
+ <Stack direction="horizontal" gap={3} wrap>
237
+ <Badge>Design</Badge>
238
+ <Badge>Engineering</Badge>
239
+ <Badge>Product</Badge>
240
+ </Stack>`,
241
+ },
242
+ },
243
+ },
244
+ render: () => (
245
+ <Stack direction="horizontal" gap={3} wrap className="max-w-md">
246
+ {["Design", "Engineering", "Product", "Marketing", "Research", "Data"].map((tag) => (
247
+ <Badge key={tag}>{tag}</Badge>
248
+ ))}
249
+ </Stack>
250
+ ),
251
+ };
252
+
253
+ export const StackForm: Story = {
254
+ name: "Stack — form layout",
255
+ parameters: {
256
+ docs: {
257
+ source: {
258
+ code: `import { Stack } from "@syscore/ui-library"
259
+ import { Card, CardHeader, CardTitle, CardContent } from "@syscore/ui-library"
260
+ import { Button, Text, Input, Textarea } from "@syscore/ui-library"
261
+
262
+ <Card>
263
+ <CardHeader>
264
+ <CardTitle>Create project</CardTitle>
265
+ </CardHeader>
266
+ <CardContent>
267
+ <Stack gap={6}>
268
+ <Stack gap={2}>
269
+ <Text variant="overline-small">Project name</Text>
270
+ <Input placeholder="My project" />
271
+ </Stack>
272
+ <Stack gap={2}>
273
+ <Text variant="overline-small">Description</Text>
274
+ <Textarea placeholder="Describe your project..." />
275
+ </Stack>
276
+ <Stack direction="horizontal" gap={3} justify="end">
277
+ <Button variant="secondary-light" size="large">Cancel</Button>
278
+ <Button variant="primary-dark" size="large">Create</Button>
279
+ </Stack>
280
+ </Stack>
281
+ </CardContent>
282
+ </Card>`,
283
+ },
284
+ },
285
+ },
286
+ render: () => (
287
+ <Card className="w-full max-w-md">
288
+ <CardHeader>
289
+ <CardTitle>Create project</CardTitle>
290
+ </CardHeader>
291
+ <CardContent>
292
+ <Stack gap={6}>
293
+ <Stack gap={2}>
294
+ <Text variant="overline-small" className="text-gray-500">Project name</Text>
295
+ <input className="border border-gray-200 rounded-md px-3 py-2 text-sm w-full" placeholder="My project" />
296
+ </Stack>
297
+ <Stack gap={2}>
298
+ <Text variant="overline-small" className="text-gray-500">Description</Text>
299
+ <textarea className="border border-gray-200 rounded-md px-3 py-2 text-sm w-full" rows={3} placeholder="Describe your project..." />
300
+ </Stack>
301
+ <Stack direction="horizontal" gap={3} justify="end">
302
+ <Button variant="secondary-light" size="large">Cancel</Button>
303
+ <Button variant="primary-dark" size="large">Create</Button>
304
+ </Stack>
305
+ </Stack>
306
+ </CardContent>
307
+ </Card>
308
+ ),
309
+ };
310
+
311
+ // ---------------------------------------------------------------------------
312
+ // Grid
313
+ // ---------------------------------------------------------------------------
314
+
315
+ export const GridTwoColumns: Story = {
316
+ parameters: {
317
+ docs: {
318
+ description: {
319
+ story: [
320
+ "**Grid** creates equal-width columns. Use it when all columns should be the same size — metric cards, feature lists, project tiles, gallery items.",
321
+ "",
322
+ "**The problem:**",
323
+ "```tsx",
324
+ "// ❌ Class soup — the responsive intent is buried",
325
+ `<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">`,
326
+ " …",
327
+ "</div>",
328
+ "```",
329
+ "",
330
+ "**The solution:**",
331
+ "```tsx",
332
+ "// ✅ Props read like a layout description",
333
+ "<Grid cols={1} colsSm={2} colsLg={3} gap={6}>",
334
+ " …",
335
+ "</Grid>",
336
+ "```",
337
+ "",
338
+ "**Key props:**",
339
+ "",
340
+ "| Prop | Values | Default | Effect |",
341
+ "|------|--------|---------|--------|",
342
+ "| `cols` | `1 \\| 2 \\| 3 \\| 4 \\| 6 \\| 12` | `1` | Columns at base (mobile) size |",
343
+ "| `colsSm` | same | — | Columns at 640px+ |",
344
+ "| `colsMd` | same | — | Columns at 768px+ |",
345
+ "| `colsLg` | same | — | Columns at 1024px+ |",
346
+ "| `gap` | number | `4` | Gap between all cells |",
347
+ "| `rowGap` | number | — | Gap between rows only |",
348
+ "| `colGap` | number | — | Gap between columns only |",
349
+ "",
350
+ "**Grid vs Columns — the key distinction:**",
351
+ "",
352
+ "`Grid` makes all columns equal width. If you need unequal widths (sidebar + main content), use `Columns`.",
353
+ "",
354
+ "```tsx",
355
+ "// Equal cards → Grid",
356
+ "<Grid cols={3} gap={6}><Card /><Card /><Card /></Grid>",
357
+ "",
358
+ "// Fixed sidebar + fluid content → Columns",
359
+ `<Columns preset="sidebar"><Nav /><Main /></Columns>`,
360
+ "```",
361
+ ].join("\n"),
362
+ },
363
+ source: {
364
+ code: `import { Grid } from "@syscore/ui-library"
365
+
366
+ <Grid cols={1} colsMd={2} gap={4}>
367
+ <div>One</div>
368
+ <div>Two</div>
369
+ <div>Three</div>
370
+ <div>Four</div>
371
+ </Grid>`,
372
+ },
373
+ },
374
+ },
375
+ render: () => (
376
+ <Grid cols={1} colsMd={2} gap={4} className="w-full max-w-2xl">
377
+ {["One", "Two", "Three", "Four"].map((n) => (
378
+ <Box key={n} label={n} tall />
379
+ ))}
380
+ </Grid>
381
+ ),
382
+ };
383
+
384
+ export const GridThreeColumns: Story = {
385
+ parameters: {
386
+ docs: {
387
+ source: {
388
+ code: `import { Grid } from "@syscore/ui-library"
389
+
390
+ // 1 col on mobile → 2 on sm → 3 on lg
391
+ <Grid cols={1} colsSm={2} colsLg={3} gap={6}>
392
+ <div>Alpha</div>
393
+ <div>Beta</div>
394
+ <div>Gamma</div>
395
+ </Grid>`,
396
+ },
397
+ },
398
+ },
399
+ render: () => (
400
+ <Grid cols={1} colsSm={2} colsLg={3} gap={6} className="w-full max-w-4xl">
401
+ {["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta"].map((n) => (
402
+ <Box key={n} label={n} tall />
403
+ ))}
404
+ </Grid>
405
+ ),
406
+ };
407
+
408
+ export const GridFourColumns: Story = {
409
+ parameters: {
410
+ docs: {
411
+ source: {
412
+ code: `import { Grid } from "@syscore/ui-library"
413
+
414
+ <Grid cols={2} colsMd={4} gap={4}>
415
+ <div>Q1</div>
416
+ <div>Q2</div>
417
+ <div>Q3</div>
418
+ <div>Q4</div>
419
+ </Grid>`,
420
+ },
421
+ },
422
+ },
423
+ render: () => (
424
+ <Grid cols={2} colsMd={4} gap={4} className="w-full max-w-4xl">
425
+ {["Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "Q7", "Q8"].map((n) => (
426
+ <Box key={n} label={n} />
427
+ ))}
428
+ </Grid>
429
+ ),
430
+ };
431
+
432
+ export const GridMetrics: Story = {
433
+ name: "Grid — metric cards",
434
+ parameters: {
435
+ docs: {
436
+ source: {
437
+ code: `import { Grid, Stack } from "@syscore/ui-library"
438
+ import { Card, CardMetric } from "@syscore/ui-library"
439
+
440
+ <Grid cols={1} colsSm={2} colsLg={4} gap={4}>
441
+ <Card variant="outlined">
442
+ <CardMetric value="2,847" label="Total projects" trend="up" trendValue="12% this month" />
443
+ </Card>
444
+ <Card variant="outlined">
445
+ <CardMetric value="94%" label="Compliance score" trend="up" trendValue="3% vs last quarter" />
446
+ </Card>
447
+ </Grid>`,
448
+ },
449
+ },
450
+ },
451
+ render: () => (
452
+ <Grid cols={1} colsSm={2} colsLg={4} gap={4} className="w-full max-w-5xl">
453
+ {[
454
+ { label: "Total projects", value: "2,847", trend: "+12%" },
455
+ { label: "Compliance score", value: "94%", trend: "+3%" },
456
+ { label: "Open issues", value: "18", trend: "-5" },
457
+ { label: "Active users", value: "1,204", trend: "+8%" },
458
+ ].map((item) => (
459
+ <Card key={item.label} variant="outlined">
460
+ <Text variant="overline-small" className="text-gray-500">{item.label}</Text>
461
+ <Stack direction="horizontal" align="baseline" gap={2}>
462
+ <Text variant="number-large" className="text-gray-900">{item.value}</Text>
463
+ <Text variant="body-small" className="text-green-600">{item.trend}</Text>
464
+ </Stack>
465
+ </Card>
466
+ ))}
467
+ </Grid>
468
+ ),
469
+ };
470
+
471
+ // ---------------------------------------------------------------------------
472
+ // Container
473
+ // ---------------------------------------------------------------------------
474
+
475
+ export const ContainerBasic: Story = {
476
+ parameters: {
477
+ docs: {
478
+ description: {
479
+ story: [
480
+ "**Container** centers content horizontally and constrains its max-width. Every page should wrap its content in a Container — it prevents text and cards from stretching to full viewport width on large monitors.",
481
+ "",
482
+ "**The problem:**",
483
+ "```tsx",
484
+ "// ❌ On a 4K monitor, content stretches to ~2500px wide — unreadable",
485
+ "<section>",
486
+ " <h1>Page title</h1>",
487
+ "</section>",
488
+ "",
489
+ "// ❌ Custom max-width copy-pasted on every page",
490
+ `<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">`,
491
+ " …",
492
+ "</div>",
493
+ "```",
494
+ "",
495
+ "**The solution:**",
496
+ "```tsx",
497
+ "// ✅ One prop, consistent across the whole app",
498
+ `<Container size="xl">`,
499
+ " <h1>Page title</h1>",
500
+ "</Container>",
501
+ "```",
502
+ "",
503
+ "**Size reference:**",
504
+ "",
505
+ "| Size | Max-width | Use for |",
506
+ "|------|-----------|---------|",
507
+ "| `\"sm\"` | 640px | Auth forms, modal body content |",
508
+ "| `\"md\"` | 768px | Reading content, blog posts |",
509
+ "| `\"lg\"` | 1024px | Standard page layouts |",
510
+ "| `\"xl\"` | 1280px | Wide dashboards (default) |",
511
+ "| `\"2xl\"` | 1536px | Full-width tables, data-heavy pages |",
512
+ "| `\"full\"` | none | Edge-to-edge, no constraint |",
513
+ "",
514
+ "**Always pair Section (vertical rhythm) with Container (horizontal constraint):**",
515
+ "```tsx",
516
+ `<Section padding="lg">`,
517
+ ` <Container size="xl">`,
518
+ " {/* Your content here */}",
519
+ " </Container>",
520
+ "</Section>",
521
+ "```",
522
+ ].join("\n"),
523
+ },
524
+ source: {
525
+ code: `import { Container } from "@syscore/ui-library"
526
+
527
+ // Centers content, adds responsive horizontal padding
528
+ <Container size="xl">
529
+ <h1>Page content</h1>
530
+ </Container>
531
+
532
+ // Sizes: "sm" (640px) · "md" (768px) · "lg" (1024px) · "xl" (1280px) · "2xl" (1536px) · "full"`,
533
+ },
534
+ },
535
+ },
536
+ render: () => (
537
+ <Container size="lg" className="border border-dashed border-cyan-400 bg-cyan-50 py-6 rounded-lg">
538
+ <Text variant="body-small" className="text-cyan-800 font-medium text-center">
539
+ Container size="lg" — max-width 1024px, centered, padded
540
+ </Text>
541
+ </Container>
542
+ ),
543
+ };
544
+
545
+ export const ContainerSizes: Story = {
546
+ parameters: {
547
+ docs: {
548
+ source: {
549
+ code: `import { Container } from "@syscore/ui-library"
550
+
551
+ <Container size="sm">640px max-width</Container>
552
+ <Container size="md">768px max-width</Container>
553
+ <Container size="lg">1024px max-width</Container>
554
+ <Container size="xl">1280px max-width</Container>
555
+ <Container size="2xl">1536px max-width</Container>`,
556
+ },
557
+ },
558
+ },
559
+ render: () => (
560
+ <Stack gap={4}>
561
+ {(["sm", "md", "lg", "xl", "2xl"] as const).map((size) => (
562
+ <Container key={size} size={size} className="border border-dashed border-cyan-400 bg-cyan-50 py-3 rounded-lg">
563
+ <Text variant="body-small" className="text-cyan-800 font-medium text-center">
564
+ size="{size}" — {{ sm: "640px", md: "768px", lg: "1024px", xl: "1280px", "2xl": "1536px" }[size]}
565
+ </Text>
566
+ </Container>
567
+ ))}
568
+ </Stack>
569
+ ),
570
+ };
571
+
572
+ export const ContainerPageLayout: Story = {
573
+ name: "Container — page layout",
574
+ parameters: {
575
+ docs: {
576
+ source: {
577
+ code: `import { Container, Stack, Grid } from "@syscore/ui-library"
578
+ import { Text, Button, Card } from "@syscore/ui-library"
579
+
580
+ <Container size="lg">
581
+ <Stack gap={8}>
582
+ <Stack direction="horizontal" justify="between" align="center">
583
+ <Stack gap={1}>
584
+ <Text variant="heading-small">Dashboard</Text>
585
+ <Text variant="body-base">Welcome back</Text>
586
+ </Stack>
587
+ <Button variant="primary-dark" size="large">New project</Button>
588
+ </Stack>
589
+
590
+ <Grid cols={1} colsSm={3} gap={4}>
591
+ <Card variant="outlined">…</Card>
592
+ <Card variant="outlined">…</Card>
593
+ <Card variant="outlined">…</Card>
594
+ </Grid>
595
+ </Stack>
596
+ </Container>`,
597
+ },
598
+ },
599
+ },
600
+ render: () => (
601
+ <div className="w-full bg-gray-50 rounded-xl overflow-hidden">
602
+ <Container size="lg" padded>
603
+ <Stack gap={8} className="py-8">
604
+ <Stack direction="horizontal" justify="between" align="center">
605
+ <Stack gap={1}>
606
+ <Text variant="heading-small">Dashboard</Text>
607
+ <Text variant="body-base" className="text-gray-500">Welcome back, User</Text>
608
+ </Stack>
609
+ <Button variant="primary-dark" size="large">New project</Button>
610
+ </Stack>
611
+ <Grid cols={1} colsSm={3} gap={4}>
612
+ {["2,847 Projects", "94% Compliance", "18 Issues"].map((item) => (
613
+ <Card key={item} variant="outlined">
614
+ <Text variant="body-base">{item}</Text>
615
+ </Card>
616
+ ))}
617
+ </Grid>
618
+ </Stack>
619
+ </Container>
620
+ </div>
621
+ ),
622
+ };
623
+
624
+ // ---------------------------------------------------------------------------
625
+ // Section
626
+ // ---------------------------------------------------------------------------
627
+
628
+ export const SectionBasic: Story = {
629
+ parameters: {
630
+ docs: {
631
+ description: {
632
+ story: [
633
+ "**Section** adds consistent vertical padding between major page sections. Use it to create breathing room between a hero, a features row, a metrics block, and a footer — without magic numbers.",
634
+ "",
635
+ "**The problem:**",
636
+ "```tsx",
637
+ "// ❌ Different padding on every section — inconsistent rhythm",
638
+ `<div className="py-12 md:py-20">Hero</div>`,
639
+ `<div className="py-8 md:py-16">Features</div>`,
640
+ `<div className="py-6 md:py-12">Metrics</div>`,
641
+ "```",
642
+ "",
643
+ "**The solution:**",
644
+ "```tsx",
645
+ "// ✅ Named scale, responsive built-in",
646
+ `<Section padding="xl">Hero</Section>`,
647
+ `<Section padding="lg">Features</Section>`,
648
+ `<Section padding="md">Metrics</Section>`,
649
+ "```",
650
+ "",
651
+ "**Padding scale:**",
652
+ "",
653
+ "| Value | Mobile | Desktop (768px+) | Use for |",
654
+ "|-------|--------|-----------------|---------|",
655
+ "| `\"sm\"` | 2rem | 3rem | Tight sections, secondary content |",
656
+ "| `\"md\"` | 3rem | 4rem | Standard content sections |",
657
+ "| `\"lg\"` | 4rem | 6rem | Main page sections (default) |",
658
+ "| `\"xl\"` | 6rem | 8rem | Hero sections, major separations |",
659
+ "| `\"none\"` | 0 | 0 | No padding (you control it) |",
660
+ "",
661
+ "**Standard pattern — Section wraps Container:**",
662
+ "```tsx",
663
+ "{/* Section = vertical rhythm. Container = horizontal constraint. */}",
664
+ `<Section padding="lg">`,
665
+ ` <Container size="xl">`,
666
+ " <Grid cols={1} colsSm={3} gap={6}>",
667
+ " <Card>…</Card>",
668
+ " </Grid>",
669
+ " </Container>",
670
+ "</Section>",
671
+ "```",
672
+ "",
673
+ "**Use `as` to render semantic HTML:**",
674
+ "```tsx",
675
+ `<Section as="article" padding="md">…</Section>`,
676
+ `<Section as="footer" padding="sm">…</Section>`,
677
+ "```",
678
+ ].join("\n"),
679
+ },
680
+ source: {
681
+ code: `import { Section, Container } from "@syscore/ui-library"
682
+
683
+ // Always pair Section (vertical padding) with Container (max-width)
684
+ <Section padding="lg">
685
+ <Container size="xl">
686
+ <h1>Section content</h1>
687
+ </Container>
688
+ </Section>
689
+
690
+ // Paddings: "none" · "sm" (2rem) · "md" (3rem) · "lg" (4rem) · "xl" (6rem)`,
691
+ },
692
+ },
693
+ },
694
+ render: () => (
695
+ <Section padding="lg" className="border border-dashed border-cyan-400 bg-cyan-50 rounded-lg w-full max-w-2xl">
696
+ <Text variant="body-small" className="text-cyan-800 font-medium text-center">
697
+ Section padding="lg" — 4rem top/bottom (6rem on md+)
698
+ </Text>
699
+ </Section>
700
+ ),
701
+ };
702
+
703
+ export const SectionPaddings: Story = {
704
+ parameters: {
705
+ docs: {
706
+ source: {
707
+ code: `import { Section } from "@syscore/ui-library"
708
+
709
+ <Section padding="sm">…</Section> {/* 2rem / 3rem on md+ */}
710
+ <Section padding="md">…</Section> {/* 3rem / 4rem on md+ */}
711
+ <Section padding="lg">…</Section> {/* 4rem / 6rem on md+ */}
712
+ <Section padding="xl">…</Section> {/* 6rem / 8rem on md+ */}`,
713
+ },
714
+ },
715
+ },
716
+ render: () => (
717
+ <Stack gap={2} className="w-full max-w-2xl">
718
+ {(["sm", "md", "lg", "xl"] as const).map((padding) => (
719
+ <Section key={padding} padding={padding} className="border border-dashed border-cyan-400 bg-cyan-50 rounded-lg">
720
+ <Text variant="body-small" className="text-cyan-800 font-medium text-center">
721
+ padding="{padding}"
722
+ </Text>
723
+ </Section>
724
+ ))}
725
+ </Stack>
726
+ ),
727
+ };
728
+
729
+ export const SectionLandingPage: Story = {
730
+ name: "Section — landing page",
731
+ parameters: {
732
+ docs: {
733
+ source: {
734
+ code: `import { Section, Container, Stack, Grid } from "@syscore/ui-library"
735
+ import { Text, Button, Card } from "@syscore/ui-library"
736
+
737
+ {/* Dark hero section */}
738
+ <Section padding="lg">
739
+ <Container size="lg">
740
+ <Stack gap={4} align="center">
741
+ <Text variant="heading-medium">One unified standard</Text>
742
+ <Text variant="body-large">The design system that powers every WELL project.</Text>
743
+ <Stack direction="horizontal" gap={3}>
744
+ <Button variant="primary-gradient" size="large">Get started</Button>
745
+ <Button variant="tertiary-light" size="large">Learn more</Button>
746
+ </Stack>
747
+ </Stack>
748
+ </Container>
749
+ </Section>
750
+
751
+ {/* Content section */}
752
+ <Section padding="md">
753
+ <Container size="lg">
754
+ <Grid cols={1} colsSm={2} colsLg={3} gap={6}>
755
+ <Card variant="default">…</Card>
756
+ </Grid>
757
+ </Container>
758
+ </Section>`,
759
+ },
760
+ },
761
+ },
762
+ render: () => (
763
+ <div className="w-full bg-white rounded-xl border border-gray-100 overflow-hidden">
764
+ <Section padding="lg" className="bg-gray-900">
765
+ <Container size="lg" padded>
766
+ <Stack gap={4} align="center" className="text-center">
767
+ <Text variant="heading-medium" className="text-white">One unified standard</Text>
768
+ <Text variant="body-large" className="text-gray-300 max-w-lg">
769
+ The design system that powers every WELL-certified project.
770
+ </Text>
771
+ <Stack direction="horizontal" gap={3}>
772
+ <Button variant="primary-gradient" size="large">Get started</Button>
773
+ <Button variant="tertiary-light" size="large">Learn more</Button>
774
+ </Stack>
775
+ </Stack>
776
+ </Container>
777
+ </Section>
778
+ <Section padding="md">
779
+ <Container size="lg" padded>
780
+ <Grid cols={1} colsSm={2} colsLg={3} gap={6}>
781
+ {["Air", "Water", "Light"].map((concept) => (
782
+ <Card key={concept} variant="default">
783
+ <Text variant="overline-large">{concept.toUpperCase()}</Text>
784
+ <Text variant="body-base" className="text-gray-600">
785
+ Improve wellbeing through {concept.toLowerCase()} quality standards.
786
+ </Text>
787
+ </Card>
788
+ ))}
789
+ </Grid>
790
+ </Container>
791
+ </Section>
792
+ </div>
793
+ ),
794
+ };
795
+
796
+ // ---------------------------------------------------------------------------
797
+ // Spacer
798
+ // ---------------------------------------------------------------------------
799
+
800
+ export const SpacerDemo: Story = {
801
+ parameters: {
802
+ docs: {
803
+ description: {
804
+ story: [
805
+ "**Spacer** adds a fixed-size blank area between elements. Use it when you need one specific gap that doesn't belong to a Stack or Grid — like a visual break between a header and a form, or extra whitespace before a footer.",
806
+ "",
807
+ "```tsx",
808
+ "{/* Vertical spacer — default */}",
809
+ "<Spacer size={8} /> {/* 2rem height */}",
810
+ "<Spacer size={4} /> {/* 1rem height */}",
811
+ "",
812
+ "{/* Horizontal spacer — inside a row */}",
813
+ `<Stack direction="horizontal">`,
814
+ " <Button>Left</Button>",
815
+ ` <Spacer axis="horizontal" size={4} />`,
816
+ " <Button>Right</Button>",
817
+ "</Stack>",
818
+ "```",
819
+ "",
820
+ "**When to use Spacer vs Stack gap:**",
821
+ "",
822
+ "Use `Stack gap` for even spacing between *all* siblings. Use `Spacer` for one *specific* extra gap that is larger or different from the rest.",
823
+ "",
824
+ "```tsx",
825
+ "{/* Stack gap: uniform spacing */}",
826
+ "<Stack gap={4}><A /><B /><C /></Stack>",
827
+ "",
828
+ "{/* Spacer: A and B are close, B and C have extra space */}",
829
+ "<Stack gap={4}>",
830
+ " <A />",
831
+ " <B />",
832
+ " <Spacer size={8} />",
833
+ " <C />",
834
+ "</Stack>",
835
+ "```",
836
+ ].join("\n"),
837
+ },
838
+ source: {
839
+ code: `import { Spacer } from "@syscore/ui-library"
840
+
841
+ {/* Vertical spacer — adds height between elements */}
842
+ <Text variant="heading-small">Above</Text>
843
+ <Spacer size={8} />
844
+ <Text variant="body-base">Below (2rem gap)</Text>
845
+
846
+ {/* Horizontal spacer — adds width inside a row */}
847
+ <Stack direction="horizontal">
848
+ <Button>Left</Button>
849
+ <Spacer axis="horizontal" size={4} />
850
+ <Button>Right</Button>
851
+ </Stack>`,
852
+ },
853
+ },
854
+ },
855
+ render: () => (
856
+ <div className="max-w-sm border border-dashed border-gray-300 rounded-xl p-4">
857
+ <Box label="Above" />
858
+ <Spacer size={8} />
859
+ <Box label="size={8} gap below (2rem)" />
860
+ <Spacer size={4} />
861
+ <Box label="size={4} gap below (1rem)" />
862
+ </div>
863
+ ),
864
+ };
865
+
866
+ // ---------------------------------------------------------------------------
867
+ // Columns
868
+ // ---------------------------------------------------------------------------
869
+
870
+ export const ColumnsSidebar: Story = {
871
+ parameters: {
872
+ docs: {
873
+ description: {
874
+ story: [
875
+ "**Columns** creates asymmetric two-column layouts. Use it when your columns need *different* widths — sidebar + content, form + preview, label + input.",
876
+ "",
877
+ "**Why not Grid?**",
878
+ "",
879
+ "`Grid cols={2}` gives both columns 50% width. `Columns` lets one column be fixed (`280px`) while the other fills the remaining space (`1fr`).",
880
+ "",
881
+ "```tsx",
882
+ "// ❌ Grid splits 50/50 — sidebar is too wide",
883
+ "<Grid cols={2}><Sidebar /><Main /></Grid>",
884
+ "",
885
+ "// ✅ Columns: fixed sidebar, fluid main",
886
+ `<Columns preset="sidebar"><Sidebar /><Main /></Columns>`,
887
+ "```",
888
+ "",
889
+ "**Available presets:**",
890
+ "",
891
+ "| Preset | Template | Use when |",
892
+ "|--------|----------|----------|",
893
+ "| `\"sidebar\"` | `280px 1fr` | Standard left nav |",
894
+ "| `\"sidebar-right\"` | `1fr 280px` | Right panel / inspector |",
895
+ "| `\"wide-sidebar\"` | `360px 1fr` | Wide nav or filter panel |",
896
+ "| `\"narrow-sidebar\"` | `200px 1fr` | Compact navigation |",
897
+ "| `\"split\"` | `1fr 1fr` | Equal halves |",
898
+ "| `\"1-2\"` | `1fr 2fr` | Narrow label + wide content |",
899
+ "| `\"2-1\"` | `2fr 1fr` | Wide content + narrow metadata |",
900
+ "| `\"1-3\"` | `1fr 3fr` | Form label + input area |",
901
+ "| `\"3-1\"` | `3fr 1fr` | Main content + slim sidebar |",
902
+ "",
903
+ "**Custom template (3+ columns):**",
904
+ "```tsx",
905
+ `<Columns template="200px 1fr 320px" gap={4}>`,
906
+ " <LeftNav />",
907
+ " <MainContent />",
908
+ " <RightPanel />",
909
+ "</Columns>",
910
+ "```",
911
+ "",
912
+ "**Responsive collapse:**",
913
+ "```tsx",
914
+ "{/* Stacks to a single column below 768px */}",
915
+ `<Columns preset="sidebar" collapse="md">`,
916
+ " <Sidebar />",
917
+ " <Main />",
918
+ "</Columns>",
919
+ "```",
920
+ ].join("\n"),
921
+ },
922
+ source: {
923
+ code: `import { Columns } from "@syscore/ui-library"
924
+
925
+ {/* Fixed 280px sidebar + fluid content */}
926
+ <Columns preset="sidebar">
927
+ <nav>Sidebar</nav>
928
+ <main>Main content</main>
929
+ </Columns>
930
+
931
+ {/* Sidebar on right */}
932
+ <Columns preset="sidebar-right">
933
+ <main>Main content</main>
934
+ <aside>Sidebar</aside>
935
+ </Columns>
936
+
937
+ {/* Presets: "sidebar" | "sidebar-right" | "wide-sidebar" | "narrow-sidebar"
938
+ | "split" | "1-2" | "2-1" | "1-3" | "3-1" */}`,
939
+ },
940
+ },
941
+ },
942
+ render: () => (
943
+ <Columns preset="sidebar" gap={4} className="w-full max-w-4xl">
944
+ <Box label="Sidebar (280px)" tall />
945
+ <Box label="Main content (1fr)" tall />
946
+ </Columns>
947
+ ),
948
+ };
949
+
950
+ export const ColumnsPresets: Story = {
951
+ parameters: {
952
+ docs: {
953
+ source: {
954
+ code: `import { Columns } from "@syscore/ui-library"
955
+
956
+ <Columns preset="1-2" gap={6}>
957
+ <aside>Narrow (1fr)</aside>
958
+ <main>Wide (2fr)</main>
959
+ </Columns>
960
+
961
+ <Columns preset="2-1" gap={6}>
962
+ <main>Wide (2fr)</main>
963
+ <aside>Narrow (1fr)</aside>
964
+ </Columns>`,
965
+ },
966
+ },
967
+ },
968
+ render: () => (
969
+ <Stack gap={4} className="w-full max-w-4xl">
970
+ {(["sidebar", "wide-sidebar", "split", "1-2", "2-1"] as const).map((preset) => (
971
+ <Stack gap={1} key={preset}>
972
+ <Text variant="overline-small" className="text-gray-400">preset="{preset}"</Text>
973
+ <Columns preset={preset} gap={3}>
974
+ <Box label="Col A" />
975
+ <Box label="Col B" />
976
+ </Columns>
977
+ </Stack>
978
+ ))}
979
+ </Stack>
980
+ ),
981
+ };
982
+
983
+ export const ColumnsCollapse: Story = {
984
+ name: "Columns — collapse on mobile",
985
+ parameters: {
986
+ docs: {
987
+ source: {
988
+ code: `import { Columns } from "@syscore/ui-library"
989
+
990
+ {/* Stacks to single column below md (768px) */}
991
+ <Columns preset="sidebar" collapse="md">
992
+ <nav>Sidebar</nav>
993
+ <main>Content</main>
994
+ </Columns>`,
995
+ },
996
+ },
997
+ },
998
+ render: () => (
999
+ <Columns preset="sidebar" collapse="md" gap={4} className="w-full max-w-4xl">
1000
+ <Box label="Sidebar — stacks below md" tall />
1001
+ <Box label="Main content" tall />
1002
+ </Columns>
1003
+ ),
1004
+ };
1005
+
1006
+ export const ColumnsCustomTemplate: Story = {
1007
+ name: "Columns — custom template",
1008
+ parameters: {
1009
+ docs: {
1010
+ source: {
1011
+ code: `import { Columns } from "@syscore/ui-library"
1012
+
1013
+ {/* Full control via CSS grid-template-columns string */}
1014
+ <Columns template="200px 1fr 300px" gap={4}>
1015
+ <nav>Left nav</nav>
1016
+ <main>Content</main>
1017
+ <aside>Right panel</aside>
1018
+ </Columns>`,
1019
+ },
1020
+ },
1021
+ },
1022
+ render: () => (
1023
+ <Columns template="200px 1fr 300px" gap={4} className="w-full max-w-5xl">
1024
+ <Box label="Left (200px)" tall />
1025
+ <Box label="Content (1fr)" tall />
1026
+ <Box label="Right (300px)" tall />
1027
+ </Columns>
1028
+ ),
1029
+ };
1030
+
1031
+ // ---------------------------------------------------------------------------
1032
+ // AppShell
1033
+ // ---------------------------------------------------------------------------
1034
+
1035
+ export const AppShellBasic: Story = {
1036
+ name: "AppShell — basic",
1037
+ parameters: {
1038
+ docs: {
1039
+ description: {
1040
+ story: [
1041
+ "**AppShell** is the full-page frame for apps with a persistent header and sidebar. It handles sticky header, fixed sidebar, and scrollable main area — using CSS grid with named areas.",
1042
+ "",
1043
+ "**The problem:**",
1044
+ "```tsx",
1045
+ "// ❌ Brittle CSS, magic calc() heights, easy to break scroll behavior",
1046
+ `<div className="min-h-screen flex flex-col">`,
1047
+ ` <header className="h-16 sticky top-0 z-50 bg-white border-b">…</header>`,
1048
+ ` <div className="flex flex-1">`,
1049
+ ` <aside className="w-[280px] sticky top-16 h-[calc(100vh-4rem)] overflow-y-auto">…</aside>`,
1050
+ ` <main className="flex-1 overflow-y-auto">…</main>`,
1051
+ " </div>",
1052
+ "</div>",
1053
+ "```",
1054
+ "",
1055
+ "**The solution:**",
1056
+ "```tsx",
1057
+ "// ✅ Declarative. Dimensions via props, behavior built-in.",
1058
+ "<AppShell>",
1059
+ " <AppShellHeader>…</AppShellHeader>",
1060
+ " <AppShellSidebar>…</AppShellSidebar>",
1061
+ " <AppShellMain>…</AppShellMain>",
1062
+ "</AppShell>",
1063
+ "```",
1064
+ "",
1065
+ "**Sub-components:**",
1066
+ "",
1067
+ "| Component | Renders as | Behavior |",
1068
+ "|-----------|------------|----------|",
1069
+ "| `AppShellHeader` | `<header>` | Sticky, full-width, sits above the sidebar |",
1070
+ "| `AppShellSidebar` | `<aside>` | Fixed width, sticky under the header, scrollable |",
1071
+ "| `AppShellMain` | `<main>` | Fills remaining space, independently scrollable |",
1072
+ "| `AppShellFooter` | `<footer>` | Spans the full width below sidebar + main |",
1073
+ "",
1074
+ "**Customizing dimensions:**",
1075
+ "```tsx",
1076
+ "{/* Numbers → converted to px. Strings → used as-is. */}",
1077
+ "<AppShell sidebarWidth={240} headerHeight={56}>…</AppShell>",
1078
+ `<AppShell sidebarWidth="20rem" headerHeight="3.5rem">…</AppShell>`,
1079
+ "```",
1080
+ "",
1081
+ "**Without a sidebar:**",
1082
+ "```tsx",
1083
+ "{/* Omit AppShellSidebar — AppShellMain fills the full width */}",
1084
+ "<AppShell>",
1085
+ " <AppShellHeader>…</AppShellHeader>",
1086
+ " <AppShellMain>…</AppShellMain>",
1087
+ "</AppShell>",
1088
+ "```",
1089
+ ].join("\n"),
1090
+ },
1091
+ source: {
1092
+ code: `import { AppShell, AppShellHeader, AppShellSidebar, AppShellMain } from "@syscore/ui-library"
1093
+ import { Stack, Text, Button } from "@syscore/ui-library"
1094
+
1095
+ <AppShell>
1096
+ <AppShellHeader>
1097
+ <Stack direction="horizontal" justify="between" align="center">
1098
+ <Text variant="heading-xxsmall">My App</Text>
1099
+ <Button variant="primary-dark" size="large">Sign in</Button>
1100
+ </Stack>
1101
+ </AppShellHeader>
1102
+
1103
+ <AppShellSidebar>
1104
+ <Stack gap={2}>
1105
+ <Button variant="transparent-tertiary" size="large">Dashboard</Button>
1106
+ <Button variant="transparent-tertiary" size="large">Projects</Button>
1107
+ <Button variant="transparent-tertiary" size="large">Settings</Button>
1108
+ </Stack>
1109
+ </AppShellSidebar>
1110
+
1111
+ <AppShellMain>
1112
+ <Container size="xl">
1113
+ <Text variant="heading-small">Page content</Text>
1114
+ </Container>
1115
+ </AppShellMain>
1116
+ </AppShell>`,
1117
+ },
1118
+ },
1119
+ },
1120
+ render: () => (
1121
+ <div className="w-full rounded-xl overflow-hidden border border-gray-200" style={{ height: 480 }}>
1122
+ <AppShell style={{ minHeight: "100%", height: "100%" }}>
1123
+ <AppShellHeader>
1124
+ <Stack direction="horizontal" justify="between" align="center" className="w-full">
1125
+ <Text variant="heading-xxsmall">My App</Text>
1126
+ <Button variant="primary-dark" size="large">Sign in</Button>
1127
+ </Stack>
1128
+ </AppShellHeader>
1129
+ <AppShellSidebar className="p-4">
1130
+ <Stack gap={1}>
1131
+ {["Dashboard", "Projects", "Reports", "Settings"].map((item) => (
1132
+ <div key={item} className="px-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 cursor-pointer">{item}</div>
1133
+ ))}
1134
+ </Stack>
1135
+ </AppShellSidebar>
1136
+ <AppShellMain className="p-6">
1137
+ <Stack gap={4}>
1138
+ <Text variant="heading-small">Dashboard</Text>
1139
+ <Grid cols={1} colsSm={3} gap={4}>
1140
+ {["2,847 Projects", "94% Score", "18 Issues"].map((m) => (
1141
+ <Card key={m} variant="outlined"><Text variant="body-base">{m}</Text></Card>
1142
+ ))}
1143
+ </Grid>
1144
+ </Stack>
1145
+ </AppShellMain>
1146
+ </AppShell>
1147
+ </div>
1148
+ ),
1149
+ };
1150
+
1151
+ export const AppShellCustomWidth: Story = {
1152
+ name: "AppShell — custom sidebar width",
1153
+ parameters: {
1154
+ docs: {
1155
+ source: {
1156
+ code: `import { AppShell, AppShellHeader, AppShellSidebar, AppShellMain } from "@syscore/ui-library"
1157
+
1158
+ {/* Custom sidebar width and header height */}
1159
+ <AppShell sidebarWidth={240} headerHeight={56}>
1160
+ <AppShellHeader>…</AppShellHeader>
1161
+ <AppShellSidebar>…</AppShellSidebar>
1162
+ <AppShellMain>…</AppShellMain>
1163
+ </AppShell>
1164
+
1165
+ {/* String values also work */}
1166
+ <AppShell sidebarWidth="20rem" headerHeight="3.5rem">
1167
+
1168
+ </AppShell>`,
1169
+ },
1170
+ },
1171
+ },
1172
+ render: () => (
1173
+ <div className="w-full rounded-xl overflow-hidden border border-gray-200" style={{ height: 360 }}>
1174
+ <AppShell sidebarWidth={200} headerHeight={52} style={{ minHeight: "100%", height: "100%" }}>
1175
+ <AppShellHeader>
1176
+ <Text variant="heading-xxsmall">Narrow sidebar (200px)</Text>
1177
+ </AppShellHeader>
1178
+ <AppShellSidebar className="p-3">
1179
+ <Stack gap={1}>
1180
+ {["Home", "Docs", "API"].map((item) => (
1181
+ <div key={item} className="px-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 cursor-pointer">{item}</div>
1182
+ ))}
1183
+ </Stack>
1184
+ </AppShellSidebar>
1185
+ <AppShellMain className="p-6">
1186
+ <Text variant="body-base" className="text-gray-500">Main content area</Text>
1187
+ </AppShellMain>
1188
+ </AppShell>
1189
+ </div>
1190
+ ),
1191
+ };
1192
+
1193
+ // ---------------------------------------------------------------------------
1194
+ // Show / Hide
1195
+ // ---------------------------------------------------------------------------
1196
+
1197
+ export const ShowHideDemo: Story = {
1198
+ name: "Show / Hide — resize to see changes",
1199
+ parameters: {
1200
+ docs: {
1201
+ description: {
1202
+ story: [
1203
+ "**Show** and **Hide** control visibility at breakpoints — without writing CSS media queries by hand.",
1204
+ "",
1205
+ "**The problem:**",
1206
+ "```tsx",
1207
+ "// ❌ Tailwind visibility classes duplicated everywhere, easy to mismatch",
1208
+ `<div className="hidden md:block">Desktop only</div>`,
1209
+ `<div className="block md:hidden">Mobile only</div>`,
1210
+ "```",
1211
+ "",
1212
+ "**The solution:**",
1213
+ "```tsx",
1214
+ "// ✅ Readable, consistent, no CSS strings",
1215
+ `<Show above="md">Desktop only</Show>`,
1216
+ `<Show below="md">Mobile only</Show>`,
1217
+ "```",
1218
+ "",
1219
+ "**Props:**",
1220
+ "",
1221
+ "| Prop | Effect |",
1222
+ "|------|--------|",
1223
+ "| `above=\"md\"` | Visible at 768px and wider |",
1224
+ "| `below=\"md\"` | Visible below 768px |",
1225
+ "| `Hide above=\"md\"` | Hidden at 768px and wider |",
1226
+ "| `Hide below=\"md\"` | Hidden below 768px |",
1227
+ "",
1228
+ "**Breakpoints:** `\"sm\"` = 640px · `\"md\"` = 768px · `\"lg\"` = 1024px · `\"xl\"` = 1280px",
1229
+ "",
1230
+ "**Common patterns:**",
1231
+ "```tsx",
1232
+ "{/* Sidebar only on desktop */}",
1233
+ `<Show above="lg">`,
1234
+ " <AppShellSidebar>…</AppShellSidebar>",
1235
+ "</Show>",
1236
+ "",
1237
+ "{/* Hamburger menu only on mobile */}",
1238
+ `<Show below="md">`,
1239
+ " <MobileNavTrigger />",
1240
+ "</Show>",
1241
+ "",
1242
+ "{/* Switch between table (desktop) and cards (mobile) */}",
1243
+ `<Show above="md"><DataTable /></Show>`,
1244
+ `<Show below="md"><CardList /></Show>`,
1245
+ "```",
1246
+ "",
1247
+ "**Use `as` to avoid an extra wrapper div:**",
1248
+ "```tsx",
1249
+ `<Show above="md" as="nav">`,
1250
+ " <DesktopMenu />",
1251
+ "</Show>",
1252
+ "```",
1253
+ "",
1254
+ "> Resize your browser window to see the stories below appear and disappear.",
1255
+ ].join("\n"),
1256
+ },
1257
+ source: {
1258
+ code: `import { Show, Hide } from "@syscore/ui-library"
1259
+
1260
+ {/* Show only on desktop (md and above) */}
1261
+ <Show above="md">
1262
+ <Sidebar />
1263
+ </Show>
1264
+
1265
+ {/* Show only on mobile (below md) */}
1266
+ <Show below="md">
1267
+ <MobileNav />
1268
+ </Show>
1269
+
1270
+ {/* Hide on mobile */}
1271
+ <Hide below="md">
1272
+ <DesktopMenu />
1273
+ </Hide>
1274
+
1275
+ {/* Breakpoints: "sm" (640px) · "md" (768px) · "lg" (1024px) · "xl" (1280px) */}`,
1276
+ },
1277
+ },
1278
+ },
1279
+ render: () => (
1280
+ <Stack gap={3} className="w-full max-w-lg">
1281
+ <Show above="md">
1282
+ <Box label="Show above='md' — visible on 768px+" />
1283
+ </Show>
1284
+ <Show below="md">
1285
+ <Box label="Show below='md' — visible below 768px" />
1286
+ </Show>
1287
+ <Hide above="lg">
1288
+ <Box label="Hide above='lg' — hidden on 1024px+" />
1289
+ </Hide>
1290
+ <Hide below="sm">
1291
+ <Box label="Hide below='sm' — hidden below 640px" />
1292
+ </Hide>
1293
+ </Stack>
1294
+ ),
1295
+ };
1296
+
1297
+ // ---------------------------------------------------------------------------
1298
+ // Positioned
1299
+ // ---------------------------------------------------------------------------
1300
+
1301
+ export const PositionedDemo: Story = {
1302
+ name: "Positioned — absolute overlay",
1303
+ parameters: {
1304
+ docs: {
1305
+ description: {
1306
+ story: [
1307
+ "**Positioned** is a semantic wrapper for `position: absolute/relative/fixed/sticky`. Use it when you need to layer elements — badges on cards, overlays on images, sticky toolbars.",
1308
+ "",
1309
+ "**The problem:**",
1310
+ "```tsx",
1311
+ "// ❌ Position CSS mixed into className, intent unclear",
1312
+ `<div className="relative">`,
1313
+ ` <span className="absolute top-3 right-3 z-10">Badge</span>`,
1314
+ "</div>",
1315
+ "```",
1316
+ "",
1317
+ "**The solution:**",
1318
+ "```tsx",
1319
+ "// ✅ Props express intent",
1320
+ `<Positioned position="relative">`,
1321
+ ` <Positioned position="absolute" top={3} right={3} zIndex={10}>`,
1322
+ " <Badge>New</Badge>",
1323
+ " </Positioned>",
1324
+ "</Positioned>",
1325
+ "```",
1326
+ "",
1327
+ "**Props:**",
1328
+ "",
1329
+ "| Prop | Type | Effect |",
1330
+ "|------|------|--------|",
1331
+ "| `position` | `\"relative\" \\| \"absolute\" \\| \"fixed\" \\| \"sticky\"` | CSS position value |",
1332
+ "| `top / right / bottom / left` | number or string | Offset from that edge |",
1333
+ "| `inset` | number or string | All four sides at once |",
1334
+ "| `fill` | `boolean` | Sets `inset: 0` — covers the entire parent |",
1335
+ "| `zIndex` | number | Stack order |",
1336
+ "",
1337
+ "**Number offsets follow the spacing scale:** `top={4}` = `top: 1rem`, `top={2}` = `top: 0.5rem`",
1338
+ "",
1339
+ "**Full overlay pattern:**",
1340
+ "```tsx",
1341
+ `<Positioned position="relative">`,
1342
+ " <img src={src} alt={alt} />",
1343
+ ` <Positioned position="absolute" fill zIndex={10}>`,
1344
+ ` <Stack align="center" justify="center">`,
1345
+ " Overlay content",
1346
+ " </Stack>",
1347
+ " </Positioned>",
1348
+ "</Positioned>",
1349
+ "```",
1350
+ "",
1351
+ "**Most important rule:** An absolutely-positioned child escapes to the nearest *positioned* ancestor. Always set `position=\"relative\"` on the parent, or the badge/overlay will float to the viewport instead of the card.",
1352
+ ].join("\n"),
1353
+ },
1354
+ source: {
1355
+ code: `import { Positioned } from "@syscore/ui-library"
1356
+ import { Badge } from "@syscore/ui-library"
1357
+
1358
+ {/* Badge pinned to top-right of a card */}
1359
+ <Positioned position="relative">
1360
+ <Card variant="outlined">
1361
+ <Positioned position="absolute" top={3} right={3}>
1362
+ <Badge>New</Badge>
1363
+ </Positioned>
1364
+ <CardContent>Card with badge</CardContent>
1365
+ </Positioned>
1366
+ </Positioned>
1367
+
1368
+ {/* Full overlay — covers the entire parent */}
1369
+ <Positioned position="relative">
1370
+ <img src="..." />
1371
+ <Positioned position="absolute" fill zIndex={10}>
1372
+ <Stack align="center" justify="center">
1373
+ Overlay content
1374
+ </Stack>
1375
+ </Positioned>
1376
+ </Positioned>`,
1377
+ },
1378
+ },
1379
+ },
1380
+ render: () => (
1381
+ <Stack gap={6} className="max-w-md">
1382
+ <Positioned position="relative">
1383
+ <Card variant="outlined" className="w-full">
1384
+ <Positioned position="absolute" top={3} right={3}>
1385
+ <Badge variant="default">New</Badge>
1386
+ </Positioned>
1387
+ <Text variant="body-base" className="pt-4">Card with absolute badge pinned top-right</Text>
1388
+ </Card>
1389
+ </Positioned>
1390
+
1391
+ <Positioned position="relative" className="w-full h-32 rounded-xl overflow-hidden bg-gray-900">
1392
+ <Positioned position="absolute" fill zIndex={10} className="flex items-center justify-center">
1393
+ <Text variant="body-base" className="text-white">fill overlay (inset: 0)</Text>
1394
+ </Positioned>
1395
+ </Positioned>
1396
+ </Stack>
1397
+ ),
1398
+ };
1399
+
1400
+ // ---------------------------------------------------------------------------
1401
+ // All together
1402
+ // ---------------------------------------------------------------------------
1403
+
1404
+ export const FullPageExample: Story = {
1405
+ name: "All together — full page",
1406
+ parameters: {
1407
+ docs: {
1408
+ source: {
1409
+ code: `import { Stack, Grid, Container, Section } from "@syscore/ui-library"
1410
+ import { Text, Button, Card } from "@syscore/ui-library"
1411
+
1412
+ {/* App shell */}
1413
+ <Stack gap={0}>
1414
+
1415
+ {/* Top nav */}
1416
+ <Stack direction="horizontal" justify="between" align="center">
1417
+ <Text variant="heading-xxsmall">WELL Design System</Text>
1418
+ <Stack direction="horizontal" gap={2}>
1419
+ <Button variant="secondary-light" size="large">Docs</Button>
1420
+ <Button variant="primary-dark" size="large">Sign in</Button>
1421
+ </Stack>
1422
+ </Stack>
1423
+
1424
+ {/* Hero */}
1425
+ <Section padding="lg">
1426
+ <Container size="xl">
1427
+ <Stack gap={4} align="center">
1428
+ <Text variant="heading-large">Build better spaces</Text>
1429
+ <Text variant="body-large">Component-driven design for healthier environments.</Text>
1430
+ </Stack>
1431
+ </Container>
1432
+ </Section>
1433
+
1434
+ {/* Metrics */}
1435
+ <Section padding="md">
1436
+ <Container size="xl">
1437
+ <Grid cols={1} colsSm={2} colsLg={4} gap={4}>
1438
+ <Card variant="outlined">…</Card>
1439
+ </Grid>
1440
+ </Container>
1441
+ </Section>
1442
+
1443
+ </Stack>`,
1444
+ },
1445
+ },
1446
+ },
1447
+ render: () => (
1448
+ <div className="w-full bg-gray-50 rounded-xl overflow-hidden border border-gray-100">
1449
+ <div className="bg-white border-b border-gray-100 px-6 py-4">
1450
+ <Stack direction="horizontal" justify="between" align="center">
1451
+ <Text variant="heading-xxsmall">WELL Design System</Text>
1452
+ <Stack direction="horizontal" gap={2}>
1453
+ <Button variant="secondary-light" size="large">Docs</Button>
1454
+ <Button variant="primary-dark" size="large">Sign in</Button>
1455
+ </Stack>
1456
+ </Stack>
1457
+ </div>
1458
+ <Section padding="lg" className="bg-gray-900">
1459
+ <Container size="xl" padded>
1460
+ <Stack gap={4} align="center" className="text-center">
1461
+ <Text variant="heading-large" className="text-white">Build better spaces</Text>
1462
+ <Text variant="body-large" className="text-gray-400">
1463
+ Component-driven design for healthier environments.
1464
+ </Text>
1465
+ </Stack>
1466
+ </Container>
1467
+ </Section>
1468
+ <Section padding="md">
1469
+ <Container size="xl" padded>
1470
+ <Grid cols={1} colsSm={2} colsLg={4} gap={4}>
1471
+ {["2,847 Projects", "94% Score", "18 Issues", "1,204 Users"].map((m) => (
1472
+ <Card key={m} variant="outlined">
1473
+ <Text variant="body-base" className="font-semibold">{m}</Text>
1474
+ </Card>
1475
+ ))}
1476
+ </Grid>
1477
+ </Container>
1478
+ </Section>
1479
+ </div>
1480
+ ),
1481
+ };