@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
@@ -1,5 +1,4 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
- import { useState } from "react";
3
2
 
4
3
  import {
5
4
  Card,
@@ -8,6 +7,10 @@ import {
8
7
  CardTitle,
9
8
  CardDescription,
10
9
  CardContent,
10
+ CardMedia,
11
+ CardBadge,
12
+ CardActions,
13
+ CardMetric,
11
14
  CardWithIcon,
12
15
  } from "../components/ui/card";
13
16
  import { Button } from "../components/ui/button";
@@ -36,29 +39,63 @@ import {
36
39
  import { UtilityReset } from "../components/icons/UtilityReset";
37
40
 
38
41
  const meta = {
39
- title: "Review/Card",
42
+ title: "UI/Card",
40
43
  component: Card,
41
44
  tags: ["autodocs"],
42
45
  parameters: {
43
46
  layout: "padded",
44
47
  docs: {
45
- source: {
46
- type: "code",
48
+ description: {
49
+ component: [
50
+ "Flexible card component with variants and composable sub-parts.",
51
+ "",
52
+ "**Import:**",
53
+ "```tsx",
54
+ `import {`,
55
+ ` Card, CardHeader, CardTitle, CardDescription,`,
56
+ ` CardContent, CardFooter,`,
57
+ ` CardMedia, CardBadge, CardActions, CardMetric,`,
58
+ ` CardWithIcon,`,
59
+ `} from "@syscore/ui-library"`,
60
+ "```",
61
+ "",
62
+ "**Variants:** `default` · `outlined` · `elevated` · `filled` · `interactive`",
63
+ ].join("\n"),
47
64
  },
65
+ source: { type: "code" },
48
66
  },
49
67
  },
50
68
  } satisfies Meta<typeof Card>;
51
69
 
52
70
  export default meta;
53
-
54
71
  type Story = StoryObj<typeof meta>;
55
72
 
73
+ // ---------------------------------------------------------------------------
74
+ // Variants
75
+ // ---------------------------------------------------------------------------
76
+
56
77
  export const Default: Story = {
78
+ parameters: {
79
+ docs: {
80
+ source: {
81
+ code: `import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@syscore/ui-library"
82
+
83
+ <Card variant="default">
84
+ <CardHeader>
85
+ <CardTitle>Default Card</CardTitle>
86
+ <CardDescription>Cyan-50 background with a subtle border.</CardDescription>
87
+ </CardHeader>
88
+ <CardContent>Main content area.</CardContent>
89
+ <CardFooter>Footer information</CardFooter>
90
+ </Card>`,
91
+ },
92
+ },
93
+ },
57
94
  render: () => (
58
- <Card className="w-full max-w-sm">
95
+ <Card variant="default" className="w-full max-w-sm">
59
96
  <CardHeader>
60
- <CardTitle>Card Title</CardTitle>
61
- <CardDescription>Card description goes here</CardDescription>
97
+ <CardTitle>Default Card</CardTitle>
98
+ <CardDescription>Cyan-50 background with a subtle border.</CardDescription>
62
99
  </CardHeader>
63
100
  <CardContent>This is the main content area of the card.</CardContent>
64
101
  <CardFooter>
@@ -68,6 +105,223 @@ export const Default: Story = {
68
105
  ),
69
106
  };
70
107
 
108
+ export const Outlined: Story = {
109
+ render: () => (
110
+ <Card variant="outlined" className="w-full max-w-sm">
111
+ <CardHeader>
112
+ <CardTitle>Outlined Card</CardTitle>
113
+ <CardDescription>White background with a visible border, no fill.</CardDescription>
114
+ </CardHeader>
115
+ <CardContent>Clean and minimal — great for secondary content areas.</CardContent>
116
+ <CardFooter>
117
+ <Button size="large" variant="secondary-light">Action</Button>
118
+ </CardFooter>
119
+ </Card>
120
+ ),
121
+ };
122
+
123
+ export const Elevated: Story = {
124
+ render: () => (
125
+ <Card variant="elevated" className="w-full max-w-sm">
126
+ <CardHeader>
127
+ <CardTitle>Elevated Card</CardTitle>
128
+ <CardDescription>White background with a drop shadow, no border.</CardDescription>
129
+ </CardHeader>
130
+ <CardContent>Use this to make content float above the page surface.</CardContent>
131
+ <CardFooter>
132
+ <Button size="large" variant="primary-dark">Get started</Button>
133
+ </CardFooter>
134
+ </Card>
135
+ ),
136
+ };
137
+
138
+ export const Filled: Story = {
139
+ render: () => (
140
+ <Card variant="filled" className="w-full max-w-sm">
141
+ <CardHeader>
142
+ <CardTitle>Filled Card</CardTitle>
143
+ <CardDescription>Dark background for high-contrast sections.</CardDescription>
144
+ </CardHeader>
145
+ <CardContent>Use for featured content, CTAs, or dark-themed sections.</CardContent>
146
+ <CardFooter>
147
+ <Button size="large" variant="tertiary-light">Learn more</Button>
148
+ </CardFooter>
149
+ </Card>
150
+ ),
151
+ };
152
+
153
+ export const Interactive: Story = {
154
+ render: () => (
155
+ <div className="grid grid-cols-1 gap-4 max-w-2xl">
156
+ {["Card One", "Card Two", "Card Three"].map((title) => (
157
+ <Card
158
+ key={title}
159
+ variant="interactive"
160
+ className="w-full"
161
+ onClick={() => alert(`Clicked: ${title}`)}
162
+ >
163
+ <CardHeader>
164
+ <CardTitle>{title}</CardTitle>
165
+ <CardDescription>Click me — I'm interactive.</CardDescription>
166
+ </CardHeader>
167
+ <CardContent>Hover to see the scale effect, click to interact.</CardContent>
168
+ </Card>
169
+ ))}
170
+ </div>
171
+ ),
172
+ };
173
+
174
+ export const AllVariants: Story = {
175
+ render: () => (
176
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 max-w-5xl">
177
+ {(["default", "outlined", "elevated", "filled", "interactive"] as const).map((variant) => (
178
+ <Card key={variant} variant={variant}>
179
+ <CardHeader>
180
+ <CardTitle>{variant.charAt(0).toUpperCase() + variant.slice(1)}</CardTitle>
181
+ <CardDescription>variant="{variant}"</CardDescription>
182
+ </CardHeader>
183
+ <CardContent>Card content goes here.</CardContent>
184
+ </Card>
185
+ ))}
186
+ </div>
187
+ ),
188
+ };
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // Sub-components
192
+ // ---------------------------------------------------------------------------
193
+
194
+ export const WithMedia: Story = {
195
+ parameters: {
196
+ docs: {
197
+ source: {
198
+ code: `import { Card, CardMedia, CardHeader, CardTitle, CardDescription, CardActions } from "@syscore/ui-library"
199
+ import { Button } from "@syscore/ui-library"
200
+
201
+ <Card variant="outlined" className="p-0">
202
+ <CardMedia src="/your-image.jpg" alt="Description" height={200} />
203
+ <div className="p-6 flex flex-col gap-6">
204
+ <CardHeader>
205
+ <CardTitle>Media Card</CardTitle>
206
+ <CardDescription>Full-bleed image above the content.</CardDescription>
207
+ </CardHeader>
208
+ <CardActions>
209
+ <Button variant="primary-dark" size="large">View details</Button>
210
+ <Button variant="secondary-light" size="large">Save</Button>
211
+ </CardActions>
212
+ </div>
213
+ </Card>`,
214
+ },
215
+ },
216
+ },
217
+ render: () => (
218
+ <Card variant="outlined" className="w-full max-w-sm p-0">
219
+ <CardMedia
220
+ src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=80"
221
+ alt="Mountain landscape"
222
+ height={200}
223
+ />
224
+ <div className="p-6 flex flex-col gap-6">
225
+ <CardHeader>
226
+ <CardTitle>Media Card</CardTitle>
227
+ <CardDescription>A full-bleed image sits above the card content.</CardDescription>
228
+ </CardHeader>
229
+ <CardActions>
230
+ <Button size="large" variant="primary-dark">View details</Button>
231
+ <Button size="large" variant="secondary-light">Save</Button>
232
+ </CardActions>
233
+ </div>
234
+ </Card>
235
+ ),
236
+ };
237
+
238
+ export const WithBadge: Story = {
239
+ render: () => (
240
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl">
241
+ {(["default", "success", "warning", "error", "info"] as const).map((color) => (
242
+ <Card key={color} variant="outlined" className="w-full">
243
+ <CardBadge color={color}>
244
+ {color.charAt(0).toUpperCase() + color.slice(1)}
245
+ </CardBadge>
246
+ <CardHeader>
247
+ <CardTitle>Badge Card</CardTitle>
248
+ <CardDescription>Status shown via top-right badge.</CardDescription>
249
+ </CardHeader>
250
+ <CardContent>Badge color: {color}</CardContent>
251
+ </Card>
252
+ ))}
253
+ </div>
254
+ ),
255
+ };
256
+
257
+ export const WithActions: Story = {
258
+ render: () => (
259
+ <Card variant="default" className="w-full max-w-sm">
260
+ <CardHeader>
261
+ <CardTitle>Card with Actions</CardTitle>
262
+ <CardDescription>Action buttons sit in a dedicated row with a separator.</CardDescription>
263
+ </CardHeader>
264
+ <CardContent>Review the details below before confirming.</CardContent>
265
+ <CardActions>
266
+ <Button size="large" variant="primary-dark">Confirm</Button>
267
+ <Button size="large" variant="secondary-light">Cancel</Button>
268
+ </CardActions>
269
+ </Card>
270
+ ),
271
+ };
272
+
273
+ export const MetricCard: Story = {
274
+ parameters: {
275
+ docs: {
276
+ source: {
277
+ code: `import { Card, CardMetric } from "@syscore/ui-library"
278
+
279
+ <Card variant="outlined">
280
+ <CardMetric
281
+ value="2,847"
282
+ label="Total projects"
283
+ trend="up"
284
+ trendValue="12% this month"
285
+ />
286
+ </Card>`,
287
+ },
288
+ },
289
+ },
290
+ render: () => (
291
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 max-w-3xl">
292
+ <Card variant="outlined">
293
+ <CardMetric value="2,847" label="Total projects" trend="up" trendValue="12% this month" />
294
+ </Card>
295
+ <Card variant="outlined">
296
+ <CardMetric value="94%" label="Compliance score" trend="up" trendValue="3% vs last quarter" />
297
+ </Card>
298
+ <Card variant="outlined">
299
+ <CardMetric value="18" label="Open issues" trend="down" trendValue="5 resolved today" />
300
+ </Card>
301
+ </div>
302
+ ),
303
+ };
304
+
305
+ export const MetricCardFilled: Story = {
306
+ render: () => (
307
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 max-w-3xl">
308
+ <Card variant="filled">
309
+ <CardMetric value="2,847" label="Total projects" trend="up" trendValue="12% this month" />
310
+ </Card>
311
+ <Card variant="filled">
312
+ <CardMetric value="94%" label="Compliance score" trend="neutral" trendValue="No change" />
313
+ </Card>
314
+ <Card variant="filled">
315
+ <CardMetric value="18" label="Open issues" trend="down" trendValue="5 resolved today" />
316
+ </Card>
317
+ </div>
318
+ ),
319
+ };
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // Existing stories preserved
323
+ // ---------------------------------------------------------------------------
324
+
71
325
  export const WithButton: Story = {
72
326
  render: () => (
73
327
  <Card className="w-full max-w-sm">
@@ -92,24 +346,14 @@ export const WithButton: Story = {
92
346
  export const Multiple: Story = {
93
347
  render: () => (
94
348
  <div className="grid grid-cols-1 gap-4 max-w-2xl">
95
- <Card>
96
- <CardHeader>
97
- <CardTitle className="heading-xsmall">Card One</CardTitle>
98
- </CardHeader>
99
- <CardContent>Content for the first card</CardContent>
100
- </Card>
101
- <Card>
102
- <CardHeader>
103
- <CardTitle className="heading-xsmall">Card Two</CardTitle>
104
- </CardHeader>
105
- <CardContent>Content for the second card</CardContent>
106
- </Card>
107
- <Card>
108
- <CardHeader>
109
- <CardTitle className="heading-xsmall">Card Three</CardTitle>
110
- </CardHeader>
111
- <CardContent>Content for the third card</CardContent>
112
- </Card>
349
+ {["Card One", "Card Two", "Card Three"].map((title) => (
350
+ <Card key={title}>
351
+ <CardHeader>
352
+ <CardTitle className="heading-xsmall">{title}</CardTitle>
353
+ </CardHeader>
354
+ <CardContent>Content for {title.toLowerCase()}</CardContent>
355
+ </Card>
356
+ ))}
113
357
  </div>
114
358
  ),
115
359
  };
@@ -129,7 +373,6 @@ export const FiltersPanel: Story = {
129
373
  </div>
130
374
 
131
375
  <div className="flex flex-col gap-6 w-full">
132
- {/* Segmented Control */}
133
376
  <div className="flex flex-col gap-3">
134
377
  <Label className="text-gray-600">Label</Label>
135
378
  <Toggle
@@ -141,7 +384,6 @@ export const FiltersPanel: Story = {
141
384
  />
142
385
  </div>
143
386
 
144
- {/* Search Selects */}
145
387
  {[1, 2, 3].map((i) => (
146
388
  <div key={i} className="flex flex-col gap-3">
147
389
  <Label className="text-gray-600">Label</Label>
@@ -161,150 +403,26 @@ export const FiltersPanel: Story = {
161
403
  ),
162
404
  };
163
405
 
164
- // export const AttributesPanel: Story = {
165
- // render: () => {
166
- // const [activeTag, setActiveTag] = useState<string>("tag1");
167
- // const tags = [
168
- // { id: "tag1", label: "Tag" },
169
- // { id: "tag2", label: "Tag" },
170
- // { id: "tag3", label: "Tag" },
171
- // ];
172
-
173
- // return (
174
- // <Card className="w-[344px] bg-cyan-50 border-gray-100 rounded-xl p-6 shadow-none flex flex-col gap-6">
175
- // <div className="flex items-center justify-between w-full h-8">
176
- // <Label className="text-gray-800 overline-large">Attributes</Label>
177
- // </div>
178
-
179
- // <div className="flex flex-col gap-6 w-full">
180
- // {/* Segmented Control */}
181
- // <div className="flex flex-col gap-3">
182
- // <Label className="text-gray-600">Label</Label>
183
- // <Toggle
184
- // options={[
185
- // { label: "Active", value: "active" },
186
- // { label: "Inactive", value: "inactive" },
187
- // ]}
188
- // defaultValue="active"
189
- // />
190
- // </div>
191
-
192
- // {/* Tags */}
193
- // <div className="flex flex-col gap-3">
194
- // <Label className="text-gray-600">Field name</Label>
195
- // <div className="flex flex-wrap gap-3 items-start">
196
- // {tags.map((tag) => (
197
- // <Tag
198
- // key={tag.id}
199
- // active={activeTag === tag.id}
200
- // onClick={() => setActiveTag(tag.id)}
201
- // >
202
- // {tag.label}
203
- // </Tag>
204
- // ))}
205
- // </div>
206
- // </div>
207
- // </div>
208
- // </Card>
209
- // );
210
- // },
211
- // };
212
-
213
406
  export const ConceptCard: Story = {
214
407
  parameters: {
215
- docs: {
216
- source: {
217
- type: "code",
218
- },
219
- },
408
+ docs: { source: { type: "code" } },
220
409
  },
221
410
  render: () => {
222
411
  const CONCEPTS_MOCK = [
223
- {
224
- id: "air",
225
- slug: "air",
226
- name: "Air",
227
- title: "AIR",
228
- description: "Reduce or minimize sources of indoor air pollution",
229
- },
230
- {
231
- id: "water",
232
- slug: "water",
233
- name: "Water",
234
- title: "WATER",
235
- description: "Increase hydration and access to high quality water",
236
- },
237
- {
238
- id: "nourishment",
239
- slug: "nourishment",
240
- name: "Nourishment",
241
- title: "NOURISHMENT",
242
- description:
243
- "Make healthy foods the easy decision and encourage better food choices",
244
- },
245
- {
246
- id: "light",
247
- slug: "light",
248
- name: "Light",
249
- title: "LIGHT",
250
- description:
251
- "Design lighting to increase alertness, enhance occupant experience, and promote optimal sleep patterns",
252
- },
253
- {
254
- id: "movement",
255
- slug: "movement",
256
- name: "Movement",
257
- title: "MOVEMENT",
258
- description: "Integrate physical activity and fitness into everyday life",
259
- },
260
- {
261
- id: "thermal-comfort",
262
- slug: "thermal-comfort",
263
- name: "Thermal Comfort",
264
- title: "THERMAL COMFORT",
265
- description: "Provide productive and comfortable indoor environments",
266
- },
267
- {
268
- id: "sound",
269
- slug: "sound",
270
- name: "Sound",
271
- title: "SOUND",
272
- description:
273
- "Support optimal acoustical comfort to reduce distractions and promote focus",
274
- },
275
- {
276
- id: "materials",
277
- slug: "materials",
278
- name: "Materials",
279
- title: "MATERIALS",
280
- description:
281
- "Improve respiratory health through use of safe materials and finishes",
282
- },
283
- {
284
- id: "community",
285
- slug: "community",
286
- name: "Community",
287
- title: "COMMUNITY",
288
- description: "Foster community engagement and social support",
289
- },
290
- {
291
- id: "mind",
292
- slug: "mind",
293
- name: "Mind",
294
- title: "MIND",
295
- description:
296
- "Support cognitive and emotional health through design, technology, and treatment strategies",
297
- },
298
- {
299
- id: "innovation",
300
- slug: "innovation",
301
- name: "Innovation",
302
- title: "INNOVATION",
303
- description: "Develop unique strategies for creating healthy environments",
304
- },
412
+ { id: "air", slug: "air", title: "AIR", description: "Reduce or minimize sources of indoor air pollution" },
413
+ { id: "water", slug: "water", title: "WATER", description: "Increase hydration and access to high quality water" },
414
+ { id: "nourishment", slug: "nourishment", title: "NOURISHMENT", description: "Make healthy foods the easy decision and encourage better food choices" },
415
+ { id: "light", slug: "light", title: "LIGHT", description: "Design lighting to increase alertness, enhance occupant experience, and promote optimal sleep patterns" },
416
+ { id: "movement", slug: "movement", title: "MOVEMENT", description: "Integrate physical activity and fitness into everyday life" },
417
+ { id: "thermal-comfort", slug: "thermal-comfort", title: "THERMAL COMFORT", description: "Provide productive and comfortable indoor environments" },
418
+ { id: "sound", slug: "sound", title: "SOUND", description: "Support optimal acoustical comfort to reduce distractions and promote focus" },
419
+ { id: "materials", slug: "materials", title: "MATERIALS", description: "Improve respiratory health through use of safe materials and finishes" },
420
+ { id: "community", slug: "community", title: "COMMUNITY", description: "Foster community engagement and social support" },
421
+ { id: "mind", slug: "mind", title: "MIND", description: "Support cognitive and emotional health through design, technology, and treatment strategies" },
422
+ { id: "innovation", slug: "innovation", title: "INNOVATION", description: "Develop unique strategies for creating healthy environments" },
305
423
  ];
306
424
 
307
- const CONCEPT_ICONS = {
425
+ const CONCEPT_ICONS: Record<string, React.ComponentType<React.SVGProps<SVGSVGElement>>> = {
308
426
  air: IconConceptAir,
309
427
  water: IconConceptWater,
310
428
  nourishment: IconConceptNourishment,
@@ -319,9 +437,9 @@ export const ConceptCard: Story = {
319
437
  };
320
438
 
321
439
  return (
322
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
440
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
323
441
  {CONCEPTS_MOCK.map((concept) => {
324
- const Icon = CONCEPT_ICONS[concept.slug as keyof typeof CONCEPT_ICONS];
442
+ const Icon = CONCEPT_ICONS[concept.slug];
325
443
  return (
326
444
  <CardWithIcon
327
445
  key={concept.id}
@@ -335,4 +453,3 @@ export const ConceptCard: Story = {
335
453
  );
336
454
  },
337
455
  };
338
-