@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,404 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Timeline, TimelineItem } from "../../components/ui/timeline";
4
+
5
+ // ─── Inline SVG icons ─────────────────────────────────────────────────────────
6
+
7
+ function IconStar() {
8
+ return (
9
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
10
+ <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
11
+ </svg>
12
+ );
13
+ }
14
+ function IconBook() {
15
+ return (
16
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
17
+ <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
18
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
19
+ </svg>
20
+ );
21
+ }
22
+ function IconWind() {
23
+ return (
24
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
25
+ <path d="M9.59 4.59A2 2 0 1 1 11 8H2" />
26
+ <path d="M12.59 19.41A2 2 0 1 0 14 16H2" />
27
+ <path d="M6.91 12.91A2 2 0 1 0 8 16H2" />
28
+ </svg>
29
+ );
30
+ }
31
+ function IconLayers() {
32
+ return (
33
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
34
+ <polygon points="12 2 2 7 12 12 22 7 12 2" />
35
+ <polyline points="2 17 12 22 22 17" />
36
+ <polyline points="2 12 12 17 22 12" />
37
+ </svg>
38
+ );
39
+ }
40
+ function IconInfo() {
41
+ return (
42
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
43
+ <circle cx="12" cy="12" r="10" />
44
+ <line x1="12" y1="8" x2="12" y2="12" />
45
+ <line x1="12" y1="16" x2="12.01" y2="16" />
46
+ </svg>
47
+ );
48
+ }
49
+ function IconAlertCircle() {
50
+ return (
51
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
52
+ <circle cx="12" cy="12" r="10" />
53
+ <line x1="12" y1="8" x2="12" y2="12" />
54
+ <line x1="12" y1="16" x2="12.01" y2="16" />
55
+ </svg>
56
+ );
57
+ }
58
+
59
+ // ─── Color token map ──────────────────────────────────────────────────────────
60
+
61
+ const DOT_COLOR_OPTIONS = ["primary", "secondary", "accent", "destructive", "muted"] as const;
62
+
63
+ const DOT_COLOR_MAP: Record<string, string> = {
64
+ primary: "hsl(var(--primary))",
65
+ secondary: "hsl(var(--secondary-foreground))",
66
+ accent: "hsl(var(--accent))",
67
+ destructive: "hsl(var(--destructive))",
68
+ muted: "hsl(var(--muted-foreground))",
69
+ };
70
+
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+
73
+ type TimelineStoryArgs = React.ComponentPropsWithoutRef<typeof Timeline> & {
74
+ dotColor?: string;
75
+ };
76
+
77
+ const meta: Meta<TimelineStoryArgs> = {
78
+ title: "UI/Timeline",
79
+ component: Timeline,
80
+ tags: ["autodocs"],
81
+ parameters: { layout: "padded" },
82
+ args: {
83
+ dotColor: DOT_COLOR_MAP.primary,
84
+ },
85
+ argTypes: {
86
+ direction: { control: "select", options: ["vertical", "horizontal"] },
87
+ side: { control: "select", options: ["alternate", "start", "end"] },
88
+ align: { control: "select", options: ["center", "start"] },
89
+ density: { control: "select", options: ["default", "compact"] },
90
+ dotColor: {
91
+ control: "select",
92
+ options: DOT_COLOR_OPTIONS,
93
+ mapping: DOT_COLOR_MAP,
94
+ },
95
+ },
96
+ };
97
+
98
+ export default meta;
99
+ type Story = StoryObj<TimelineStoryArgs>;
100
+
101
+ // ─── Default ─────────────────────────────────────────────────────────────────
102
+
103
+ export const Default: Story = {
104
+ render: ({ dotColor }) => (
105
+ <Timeline>
106
+ {["First event", "Second event", "Third event"].map((label, i) => (
107
+ <TimelineItem key={i} dotColor={dotColor} size="small">
108
+ <div className="timeline-story-card">
109
+ <strong>{label}</strong>
110
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
111
+ </div>
112
+ </TimelineItem>
113
+ ))}
114
+ </Timeline>
115
+ ),
116
+ };
117
+
118
+ // ─── Side: End ───────────────────────────────────────────────────────────────
119
+
120
+ export const SideEnd: Story = {
121
+ render: ({ dotColor }) => (
122
+ <Timeline side="end">
123
+ <TimelineItem dotColor={dotColor} size="small" icon={<IconInfo />} fillDot>
124
+ <div className="timeline-alert timeline-alert--info">
125
+ <strong>Info</strong>
126
+ <p>Lorem ipsum dolor sit amet, no nam oblique veritus.</p>
127
+ </div>
128
+ </TimelineItem>
129
+ <TimelineItem dotColor={dotColor} size="small" icon={<IconAlertCircle />} fillDot>
130
+ <div className="timeline-alert timeline-alert--error">
131
+ <strong>Error</strong>
132
+ <p>Lorem ipsum dolor sit amet, no nam oblique veritus.</p>
133
+ </div>
134
+ </TimelineItem>
135
+ </Timeline>
136
+ ),
137
+ };
138
+
139
+ // ─── Alignment: Start ────────────────────────────────────────────────────────
140
+
141
+ export const AlignStart: Story = {
142
+ render: ({ dotColor }) => (
143
+ <Timeline align="start" side="end">
144
+ {[
145
+ { time: "5pm", title: "New Icon", sub: "Mobile App" },
146
+ { time: "3–4pm", title: "Design Stand Up", sub: "Hangouts" },
147
+ { time: "12pm", title: "Lunch break", sub: "" },
148
+ { time: "9–11am", title: "Finish Home Screen", sub: "Web App" },
149
+ ].map((item, i) => (
150
+ <TimelineItem key={i} dotColor={dotColor} size="small">
151
+ <div style={{ display: "flex", gap: "1rem" }}>
152
+ <strong style={{ flexShrink: 0, width: "4rem" }}>{item.time}</strong>
153
+ <div>
154
+ <strong>{item.title}</strong>
155
+ {item.sub && <div className="timeline-story-sub">{item.sub}</div>}
156
+ </div>
157
+ </div>
158
+ </TimelineItem>
159
+ ))}
160
+ </Timeline>
161
+ ),
162
+ };
163
+
164
+ // ─── Dot Color ───────────────────────────────────────────────────────────────
165
+
166
+ export const DotColor: Story = {
167
+ render: ({ dotColor }) => (
168
+ <Timeline side="end">
169
+ {[
170
+ "Order placed",
171
+ "Processing",
172
+ "Shipped",
173
+ "Delivered",
174
+ ].map((label, i) => (
175
+ <TimelineItem key={i} dotColor={dotColor} size="small">
176
+ <span>{label}</span>
177
+ </TimelineItem>
178
+ ))}
179
+ </Timeline>
180
+ ),
181
+ };
182
+
183
+ // ─── Icon Dots ────────────────────────────────────────────────────────────────
184
+
185
+ export const IconDots: Story = {
186
+ render: ({ dotColor }) => (
187
+ <Timeline align="start">
188
+ {[
189
+ { icon: <IconStar />, title: "Lorem Ipsum Dolor" },
190
+ { icon: <IconBook />, title: "Lorem Ipsum Dolor" },
191
+ { icon: <IconWind />, title: "Lorem Ipsum Dolor" },
192
+ { icon: <IconLayers />, title: "Lorem Ipsum Dolor" },
193
+ ].map((item, i) => (
194
+ <TimelineItem key={i} dotColor={dotColor} icon={item.icon} fillDot size="default">
195
+ <div className="timeline-story-card" style={{ borderTop: `3px solid ${dotColor}` }}>
196
+ <strong>{item.title}</strong>
197
+ <p>Lorem ipsum dolor sit amet, no nam oblique veritus. Commune scaevola imperdiet nec ut.</p>
198
+ <button
199
+ type="button"
200
+ className="timeline-story-btn"
201
+ style={{ borderColor: dotColor, color: dotColor }}
202
+ >
203
+ Button
204
+ </button>
205
+ </div>
206
+ </TimelineItem>
207
+ ))}
208
+ </Timeline>
209
+ ),
210
+ };
211
+
212
+ // ─── Size ─────────────────────────────────────────────────────────────────────
213
+
214
+ export const Size: Story = {
215
+ render: ({ dotColor }) => (
216
+ <Timeline>
217
+ {(["x-small", "small", "default", "large", "x-large"] as const).map((size) => (
218
+ <TimelineItem key={size} dotColor={dotColor} size={size} fillDot>
219
+ <div className="timeline-story-card">
220
+ <strong>size="{size}"</strong>
221
+ <p>Lorem ipsum dolor sit amet.</p>
222
+ </div>
223
+ </TimelineItem>
224
+ ))}
225
+ </Timeline>
226
+ ),
227
+ };
228
+
229
+ // ─── Horizontal ───────────────────────────────────────────────────────────────
230
+
231
+ export const Horizontal: Story = {
232
+ render: ({ dotColor }) => (
233
+ <Timeline direction="horizontal">
234
+ {["Step 1", "Step 2", "Step 3"].map((label, i) => (
235
+ <TimelineItem
236
+ key={i}
237
+ dotColor={dotColor}
238
+ size="small"
239
+ opposite={<span className="timeline-story-opposite">Opposite content</span>}
240
+ >
241
+ <div>
242
+ <strong>{label}</strong>
243
+ <p style={{ fontSize: "0.8125rem" }}>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
244
+ </div>
245
+ </TimelineItem>
246
+ ))}
247
+ </Timeline>
248
+ ),
249
+ };
250
+
251
+ // ─── Slot: Icon ──────────────────────────────────────────────────────────────
252
+
253
+ export const SlotIcon: Story = {
254
+ render: ({ dotColor }) => (
255
+ <Timeline>
256
+ <TimelineItem
257
+ size="large"
258
+ dotColor={dotColor}
259
+ icon={
260
+ <img
261
+ src="https://i.pravatar.cc/64"
262
+ alt="User avatar"
263
+ style={{ width: "100%", height: "100%", borderRadius: "50%", objectFit: "cover" }}
264
+ />
265
+ }
266
+ opposite={<span className="timeline-story-opposite">Tus eu perfecto</span>}
267
+ >
268
+ <div className="timeline-story-card">
269
+ <strong style={{ fontSize: "1.125rem" }}>Lorem ipsum</strong>
270
+ <p>Lorem ipsum dolor sit amet, no nam oblique veritus. Commune scaevola imperdiet nec ut, sed euismod convenire principes at.</p>
271
+ </div>
272
+ </TimelineItem>
273
+ </Timeline>
274
+ ),
275
+ };
276
+
277
+ // ─── Slot: Opposite ──────────────────────────────────────────────────────────
278
+
279
+ export const SlotOpposite: Story = {
280
+ render: ({ dotColor }) => (
281
+ <Timeline align="start">
282
+ {["1960", "1970", "1980", "1990", "2000"].map((year) => (
283
+ <TimelineItem
284
+ key={year}
285
+ dotColor={dotColor}
286
+ size="small"
287
+ opposite={
288
+ <strong style={{ color: dotColor, fontSize: "1.25rem" }}>{year}</strong>
289
+ }
290
+ >
291
+ <div>
292
+ <h3 style={{ color: dotColor, fontWeight: 300, marginBottom: "0.5rem" }}>
293
+ Lorem ipsum
294
+ </h3>
295
+ <p style={{ fontSize: "0.875rem" }}>
296
+ Lorem ipsum dolor sit amet, no nam oblique veritus. Commune
297
+ scaevola imperdiet nec ut, sed euismod convenire principes at.
298
+ </p>
299
+ </div>
300
+ </TimelineItem>
301
+ ))}
302
+ </Timeline>
303
+ ),
304
+ };
305
+
306
+ // ─── Order Activity ───────────────────────────────────────────────────────────
307
+
308
+ export const OrderActivity: Story = {
309
+ render: ({ dotColor }) => {
310
+ const [events, setEvents] = useState<{ id: number; text: string; time: string }[]>([]);
311
+ const [input, setInput] = useState("");
312
+ const [nonce, setNonce] = useState(0);
313
+
314
+ function handleComment() {
315
+ if (!input.trim()) return;
316
+ const now = new Date();
317
+ const time = `${now.getHours()}:${String(now.getMinutes()).padStart(2, "0")} EDT`;
318
+ setEvents((prev) => [...prev, { id: nonce, text: input, time }]);
319
+ setNonce((n) => n + 1);
320
+ setInput("");
321
+ }
322
+
323
+ return (
324
+ <div style={{ maxWidth: 560, margin: "0 auto" }}>
325
+ <Timeline density="compact" side="end">
326
+ <TimelineItem
327
+ dotColor={dotColor}
328
+ size="large"
329
+ fillDot
330
+ icon={<span style={{ fontSize: "0.625rem", fontWeight: 700, color: "#fff" }}>JL</span>}
331
+ >
332
+ <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
333
+ <input
334
+ type="text"
335
+ value={input}
336
+ onChange={(e) => setInput(e.target.value)}
337
+ onKeyDown={(e) => e.key === "Enter" && handleComment()}
338
+ placeholder="Leave a comment..."
339
+ className="timeline-comment-input"
340
+ />
341
+ <button type="button" className="timeline-comment-btn" onClick={handleComment}>
342
+ Post
343
+ </button>
344
+ </div>
345
+ </TimelineItem>
346
+
347
+ {[...events].reverse().map((ev) => (
348
+ <TimelineItem key={ev.id} dotColor={dotColor} size="small">
349
+ <div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
350
+ <span>{ev.text}</span>
351
+ <span style={{ flexShrink: 0, fontSize: "0.75rem", color: "#71747d" }}>{ev.time}</span>
352
+ </div>
353
+ </TimelineItem>
354
+ ))}
355
+
356
+ <TimelineItem hideDot>
357
+ <strong style={{ fontSize: "0.75rem", letterSpacing: "0.05em" }}>TODAY</strong>
358
+ </TimelineItem>
359
+
360
+ <TimelineItem dotColor={dotColor} size="small">
361
+ <div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
362
+ <span>This order was archived.</span>
363
+ <span style={{ flexShrink: 0, fontSize: "0.75rem", color: "#71747d" }}>15:26 EDT</span>
364
+ </div>
365
+ </TimelineItem>
366
+
367
+ <TimelineItem dotColor={dotColor} size="small">
368
+ <div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
369
+ <div>
370
+ <span className="timeline-chip">APP</span> Digital Downloads fulfilled 1 item.
371
+ </div>
372
+ <span style={{ flexShrink: 0, fontSize: "0.75rem", color: "#71747d" }}>15:25 EDT</span>
373
+ </div>
374
+ </TimelineItem>
375
+
376
+ <TimelineItem dotColor={dotColor} size="small">
377
+ <div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
378
+ <span>Order confirmation email was sent to John Leider (john@google.com).</span>
379
+ <span style={{ flexShrink: 0, fontSize: "0.75rem", color: "#71747d" }}>15:25 EDT</span>
380
+ </div>
381
+ </TimelineItem>
382
+
383
+ <TimelineItem hideDot>
384
+ <button type="button" className="timeline-story-btn">Resend Email</button>
385
+ </TimelineItem>
386
+
387
+ <TimelineItem dotColor={dotColor} size="small">
388
+ <div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
389
+ <span>A $15.00 USD payment was processed on PayPal Express Checkout.</span>
390
+ <span style={{ flexShrink: 0, fontSize: "0.75rem", color: "#71747d" }}>15:25 EDT</span>
391
+ </div>
392
+ </TimelineItem>
393
+
394
+ <TimelineItem dotColor={dotColor} size="small">
395
+ <div style={{ display: "flex", justifyContent: "space-between", gap: "1rem" }}>
396
+ <span>John Leider placed this order on Online Store (checkout #1937432132572).</span>
397
+ <span style={{ flexShrink: 0, fontSize: "0.75rem", color: "#71747d" }}>15:25 EDT</span>
398
+ </div>
399
+ </TimelineItem>
400
+ </Timeline>
401
+ </div>
402
+ );
403
+ },
404
+ };