astryx-dracula 0.1.5 → 0.2.1

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 (90) hide show
  1. package/README.md +8 -0
  2. package/USAGE.md +13 -0
  3. package/astryx.integration.mjs +3 -0
  4. package/package.json +4 -2
  5. package/templates/ai-chat-landing.template.mjs +6 -0
  6. package/templates/ai-chat-landing.tsx +478 -0
  7. package/templates/ai-chat.template.mjs +6 -0
  8. package/templates/ai-chat.tsx +766 -0
  9. package/templates/blank.template.mjs +6 -0
  10. package/templates/blank.tsx +18 -0
  11. package/templates/centered-hero.template.mjs +6 -0
  12. package/templates/centered-hero.tsx +190 -0
  13. package/templates/classic-gallery.template.mjs +6 -0
  14. package/templates/classic-gallery.tsx +183 -0
  15. package/templates/contact-form.template.mjs +6 -0
  16. package/templates/contact-form.tsx +294 -0
  17. package/templates/dashboard-portfolio.template.mjs +6 -0
  18. package/templates/dashboard-portfolio.tsx +802 -0
  19. package/templates/dashboard.template.mjs +6 -0
  20. package/templates/dashboard.tsx +776 -0
  21. package/templates/detail-page.template.mjs +6 -0
  22. package/templates/detail-page.tsx +697 -0
  23. package/templates/documentation-design.template.mjs +6 -0
  24. package/templates/documentation-design.tsx +753 -0
  25. package/templates/documentation-technical.template.mjs +6 -0
  26. package/templates/documentation-technical.tsx +294 -0
  27. package/templates/documentation.template.mjs +6 -0
  28. package/templates/documentation.tsx +349 -0
  29. package/templates/editor.template.mjs +6 -0
  30. package/templates/editor.tsx +961 -0
  31. package/templates/file-explorer.template.mjs +6 -0
  32. package/templates/file-explorer.tsx +579 -0
  33. package/templates/form-two-column.template.mjs +6 -0
  34. package/templates/form-two-column.tsx +316 -0
  35. package/templates/gallery-hero.template.mjs +6 -0
  36. package/templates/gallery-hero.tsx +289 -0
  37. package/templates/ide.template.mjs +6 -0
  38. package/templates/ide.tsx +537 -0
  39. package/templates/incident-console.template.mjs +6 -0
  40. package/templates/incident-console.tsx +586 -0
  41. package/templates/kanban-board.template.mjs +6 -0
  42. package/templates/kanban-board.tsx +748 -0
  43. package/templates/library.template.mjs +6 -0
  44. package/templates/library.tsx +491 -0
  45. package/templates/login-card.template.mjs +6 -0
  46. package/templates/login-card.tsx +202 -0
  47. package/templates/login-split.template.mjs +6 -0
  48. package/templates/login-split.tsx +316 -0
  49. package/templates/login-sso.template.mjs +6 -0
  50. package/templates/login-sso.tsx +275 -0
  51. package/templates/login.template.mjs +6 -0
  52. package/templates/login.tsx +100 -0
  53. package/templates/messaging-shell.template.mjs +6 -0
  54. package/templates/messaging-shell.tsx +677 -0
  55. package/templates/mixed-gallery.template.mjs +6 -0
  56. package/templates/mixed-gallery.tsx +192 -0
  57. package/templates/payment-form.template.mjs +6 -0
  58. package/templates/payment-form.tsx +992 -0
  59. package/templates/product-detail.template.mjs +6 -0
  60. package/templates/product-detail.tsx +368 -0
  61. package/templates/product-gallery.template.mjs +6 -0
  62. package/templates/product-gallery.tsx +175 -0
  63. package/templates/settings-dialog.template.mjs +6 -0
  64. package/templates/settings-dialog.tsx +926 -0
  65. package/templates/settings-sidebar.template.mjs +6 -0
  66. package/templates/settings-sidebar.tsx +954 -0
  67. package/templates/settings.template.mjs +6 -0
  68. package/templates/settings.tsx +237 -0
  69. package/templates/shell-nav.template.mjs +6 -0
  70. package/templates/shell-nav.tsx +308 -0
  71. package/templates/shell-side-nav.template.mjs +6 -0
  72. package/templates/shell-side-nav.tsx +240 -0
  73. package/templates/shell-top-nav.template.mjs +6 -0
  74. package/templates/shell-top-nav.tsx +245 -0
  75. package/templates/side-gallery.template.mjs +6 -0
  76. package/templates/side-gallery.tsx +181 -0
  77. package/templates/table-grouped.template.mjs +6 -0
  78. package/templates/table-grouped.tsx +1176 -0
  79. package/templates/table-page-chart.template.mjs +6 -0
  80. package/templates/table-page-chart.tsx +699 -0
  81. package/templates/table-page-heatmap-status.template.mjs +6 -0
  82. package/templates/table-page-heatmap-status.tsx +549 -0
  83. package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
  84. package/templates/table-page-shoe-store-heatmap.tsx +1044 -0
  85. package/templates/table-page.template.mjs +6 -0
  86. package/templates/table-page.tsx +447 -0
  87. package/templates/table.template.mjs +6 -0
  88. package/templates/table.tsx +86 -0
  89. package/templates/theme-showcase.template.mjs +6 -0
  90. package/templates/theme-showcase.tsx +1284 -0
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'Orders with Chart',
4
+ description: 'Revenue area chart over filterable orders',
5
+ category: 'data',
6
+ };
@@ -0,0 +1,699 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LH[divider] > H[a=center g=2] > (SI[size=fill] > Hd"Matcha Bar"[level=1]) + IB"Filter"[variant=ghost] + IB"Export"[variant=ghost] + B.primary"New order") + (LC[p=3] > V[g=4] > C[p=3] + (T[hover] > (TR > THC"Order" + THC"Product" + THC"Amount" + THC"Customer" + THC"Email" + THC"Status" + THC"Date") + (TR > TC"ORD-1001" + TC"Ceremonial Matcha Latte" + TC"$6" + TC"Sarah Chen" + TC"sarah.chen@acme.co" + (TC > Bd.green"Completed") + TC"2025-01-15")*6))
4
+
5
+ import type {CSSProperties} from 'react';
6
+ import {
7
+ VStack,
8
+ HStack,
9
+ StackItem,
10
+ Layout,
11
+ LayoutContent,
12
+ LayoutHeader,
13
+ } from '@astryxdesign/core/Layout';
14
+ import {Text, Heading} from '@astryxdesign/core/Text';
15
+ import {Button} from '@astryxdesign/core/Button';
16
+ import {IconButton} from '@astryxdesign/core/IconButton';
17
+ import {Icon} from '@astryxdesign/core/Icon';
18
+ import {Badge} from '@astryxdesign/core/Badge';
19
+ import {Card} from '@astryxdesign/core/Card';
20
+ import {Link} from '@astryxdesign/core/Link';
21
+ import {Table, proportional, pixel} from '@astryxdesign/core/Table';
22
+ import type {TableColumn} from '@astryxdesign/core/Table';
23
+ import {Filter, Download, Plus, Square} from 'lucide-react';
24
+
25
+ // ============= ICONS (verified lucide-react exports) =============
26
+ // Filter ← FunnelIcon, Download ← ArrowDownTrayIcon, Plus ← PlusIcon.
27
+ // Square marks the chart legend swatch.
28
+
29
+ // Per-product Dracula accent for the order thumbnail swatches.
30
+ const PRODUCT_ACCENTS = [
31
+ 'var(--dracula-green)',
32
+ 'var(--dracula-orange)',
33
+ 'var(--dracula-cyan)',
34
+ 'var(--dracula-pink)',
35
+ 'var(--dracula-purple)',
36
+ 'var(--dracula-yellow)',
37
+ ] as const;
38
+
39
+ const swatchStyle: CSSProperties = {flexShrink: 0};
40
+
41
+ // Rounded product swatch: Dracula surface with a per-product accent glyph.
42
+ function ProductSwatch({accent, label}: {accent: string; label: string}) {
43
+ return (
44
+ <svg
45
+ viewBox="0 0 36 36"
46
+ width={36}
47
+ height={36}
48
+ style={swatchStyle}
49
+ role="img"
50
+ aria-label={`${label} swatch`}>
51
+ <rect width={36} height={36} rx={5} fill="var(--dracula-bg-light)" />
52
+ <rect
53
+ x={1}
54
+ y={1}
55
+ width={34}
56
+ height={34}
57
+ rx={4}
58
+ fill="none"
59
+ stroke="var(--color-widget-content-border)"
60
+ />
61
+ <circle cx={18} cy={18} r={7} fill="none" stroke={accent} strokeWidth={3} />
62
+ </svg>
63
+ );
64
+ }
65
+
66
+ // ============= DATA =============
67
+
68
+ type ProductCategory = 'Matcha' | 'Coffee' | 'Tea' | 'Smoothie' | 'Specialty';
69
+
70
+ interface OrderRow extends Record<string, unknown> {
71
+ id: string;
72
+ customer: string;
73
+ email: string;
74
+ product: string;
75
+ category: ProductCategory;
76
+ imageIndex: number;
77
+ amount: number;
78
+ status: 'completed' | 'processing' | 'shipped' | 'refunded';
79
+ date: string;
80
+ }
81
+
82
+ const PRODUCTS = [
83
+ {
84
+ name: 'Ceremonial Matcha Latte',
85
+ category: 'Matcha' as ProductCategory,
86
+ price: 6,
87
+ },
88
+ {
89
+ name: 'Oat Milk Cappuccino',
90
+ category: 'Coffee' as ProductCategory,
91
+ price: 5,
92
+ },
93
+ {
94
+ name: 'Jasmine Green Tea',
95
+ category: 'Tea' as ProductCategory,
96
+ price: 4,
97
+ },
98
+ {
99
+ name: 'Mango Matcha Smoothie',
100
+ category: 'Smoothie' as ProductCategory,
101
+ price: 8,
102
+ },
103
+ {
104
+ name: 'Hojicha Latte',
105
+ category: 'Specialty' as ProductCategory,
106
+ price: 7,
107
+ },
108
+ {
109
+ name: 'Iced Yuzu Matcha',
110
+ category: 'Matcha' as ProductCategory,
111
+ price: 7,
112
+ },
113
+ ];
114
+
115
+ const orders: OrderRow[] = [
116
+ {
117
+ id: 'ORD-1001',
118
+ customer: 'Sarah Chen',
119
+ email: 'sarah.chen@acme.co',
120
+ product: PRODUCTS[0].name,
121
+ category: PRODUCTS[0].category,
122
+ imageIndex: 0,
123
+ amount: 6,
124
+ status: 'completed',
125
+ date: '2025-01-15',
126
+ },
127
+ {
128
+ id: 'ORD-1002',
129
+ customer: 'Marcus Rivera',
130
+ email: 'mrivera@globex.com',
131
+ product: PRODUCTS[1].name,
132
+ category: PRODUCTS[1].category,
133
+ imageIndex: 1,
134
+ amount: 5,
135
+ status: 'completed',
136
+ date: '2025-01-15',
137
+ },
138
+ {
139
+ id: 'ORD-1003',
140
+ customer: 'Aisha Patel',
141
+ email: 'aisha.p@initech.io',
142
+ product: PRODUCTS[2].name,
143
+ category: PRODUCTS[2].category,
144
+ imageIndex: 2,
145
+ amount: 4,
146
+ status: 'shipped',
147
+ date: '2025-01-14',
148
+ },
149
+ {
150
+ id: 'ORD-1004',
151
+ customer: "James O'Brien",
152
+ email: 'jobrien@umbrella.net',
153
+ product: PRODUCTS[3].name,
154
+ category: PRODUCTS[3].category,
155
+ imageIndex: 3,
156
+ amount: 8,
157
+ status: 'processing',
158
+ date: '2025-01-14',
159
+ },
160
+ {
161
+ id: 'ORD-1005',
162
+ customer: 'Yuki Tanaka',
163
+ email: 'yuki@soylent.jp',
164
+ product: PRODUCTS[4].name,
165
+ category: PRODUCTS[4].category,
166
+ imageIndex: 4,
167
+ amount: 7,
168
+ status: 'completed',
169
+ date: '2025-01-13',
170
+ },
171
+ {
172
+ id: 'ORD-1006',
173
+ customer: 'Elena Volkov',
174
+ email: 'elena.v@wayne.com',
175
+ product: PRODUCTS[5].name,
176
+ category: PRODUCTS[5].category,
177
+ imageIndex: 5,
178
+ amount: 7,
179
+ status: 'refunded',
180
+ date: '2025-01-13',
181
+ },
182
+ {
183
+ id: 'ORD-1007',
184
+ customer: 'David Kim',
185
+ email: 'dkim@stark.io',
186
+ product: PRODUCTS[0].name,
187
+ category: PRODUCTS[0].category,
188
+ imageIndex: 0,
189
+ amount: 6,
190
+ status: 'completed',
191
+ date: '2025-01-12',
192
+ },
193
+ {
194
+ id: 'ORD-1008',
195
+ customer: 'Fatima Al-Rashid',
196
+ email: 'fatima@oscorp.ae',
197
+ product: PRODUCTS[1].name,
198
+ category: PRODUCTS[1].category,
199
+ imageIndex: 1,
200
+ amount: 5,
201
+ status: 'shipped',
202
+ date: '2025-01-12',
203
+ },
204
+ {
205
+ id: 'ORD-1009',
206
+ customer: 'Lucas Andersson',
207
+ email: 'lucas.a@cyberdyne.se',
208
+ product: PRODUCTS[2].name,
209
+ category: PRODUCTS[2].category,
210
+ imageIndex: 2,
211
+ amount: 4,
212
+ status: 'completed',
213
+ date: '2025-01-11',
214
+ },
215
+ {
216
+ id: 'ORD-1010',
217
+ customer: 'Priya Sharma',
218
+ email: 'priya@aperture.in',
219
+ product: PRODUCTS[3].name,
220
+ category: PRODUCTS[3].category,
221
+ imageIndex: 3,
222
+ amount: 8,
223
+ status: 'processing',
224
+ date: '2025-01-11',
225
+ },
226
+ {
227
+ id: 'ORD-1011',
228
+ customer: 'Noah Williams',
229
+ email: 'noah.w@massive.com',
230
+ product: PRODUCTS[4].name,
231
+ category: PRODUCTS[4].category,
232
+ imageIndex: 4,
233
+ amount: 7,
234
+ status: 'completed',
235
+ date: '2025-01-10',
236
+ },
237
+ {
238
+ id: 'ORD-1012',
239
+ customer: 'Sofia Garcia',
240
+ email: 'sgarcia@tyrell.mx',
241
+ product: PRODUCTS[5].name,
242
+ category: PRODUCTS[5].category,
243
+ imageIndex: 5,
244
+ amount: 7,
245
+ status: 'completed',
246
+ date: '2025-01-10',
247
+ },
248
+ {
249
+ id: 'ORD-1013',
250
+ customer: 'Oliver Brown',
251
+ email: 'oliver.b@weyland.uk',
252
+ product: PRODUCTS[0].name,
253
+ category: PRODUCTS[0].category,
254
+ imageIndex: 0,
255
+ amount: 6,
256
+ status: 'shipped',
257
+ date: '2025-01-09',
258
+ },
259
+ {
260
+ id: 'ORD-1014',
261
+ customer: 'Mei Lin',
262
+ email: 'mei.lin@choam.cn',
263
+ product: PRODUCTS[1].name,
264
+ category: PRODUCTS[1].category,
265
+ imageIndex: 1,
266
+ amount: 5,
267
+ status: 'completed',
268
+ date: '2025-01-09',
269
+ },
270
+ {
271
+ id: 'ORD-1015',
272
+ customer: 'Hassan Ahmed',
273
+ email: 'hahmed@abstergo.eg',
274
+ product: PRODUCTS[2].name,
275
+ category: PRODUCTS[2].category,
276
+ imageIndex: 2,
277
+ amount: 4,
278
+ status: 'refunded',
279
+ date: '2025-01-08',
280
+ },
281
+ {
282
+ id: 'ORD-1016',
283
+ customer: 'Isabella Rossi',
284
+ email: 'irossi@genom.it',
285
+ product: PRODUCTS[3].name,
286
+ category: PRODUCTS[3].category,
287
+ imageIndex: 3,
288
+ amount: 8,
289
+ status: 'completed',
290
+ date: '2025-01-08',
291
+ },
292
+ {
293
+ id: 'ORD-1017',
294
+ customer: 'Liam Murphy',
295
+ email: 'liam.m@mishima.ie',
296
+ product: PRODUCTS[4].name,
297
+ category: PRODUCTS[4].category,
298
+ imageIndex: 4,
299
+ amount: 7,
300
+ status: 'processing',
301
+ date: '2025-01-07',
302
+ },
303
+ {
304
+ id: 'ORD-1018',
305
+ customer: 'Chloe Dubois',
306
+ email: 'cdubois@armacham.fr',
307
+ product: PRODUCTS[5].name,
308
+ category: PRODUCTS[5].category,
309
+ imageIndex: 5,
310
+ amount: 7,
311
+ status: 'completed',
312
+ date: '2025-01-07',
313
+ },
314
+ {
315
+ id: 'ORD-1019',
316
+ customer: 'Andre Santos',
317
+ email: 'asantos@shinra.br',
318
+ product: PRODUCTS[0].name,
319
+ category: PRODUCTS[0].category,
320
+ imageIndex: 0,
321
+ amount: 6,
322
+ status: 'shipped',
323
+ date: '2025-01-06',
324
+ },
325
+ {
326
+ id: 'ORD-1020',
327
+ customer: 'Nina Johansson',
328
+ email: 'nina.j@lexcorp.se',
329
+ product: PRODUCTS[1].name,
330
+ category: PRODUCTS[1].category,
331
+ imageIndex: 1,
332
+ amount: 5,
333
+ status: 'completed',
334
+ date: '2025-01-06',
335
+ },
336
+ {
337
+ id: 'ORD-1021',
338
+ customer: 'Raj Kapoor',
339
+ email: 'raj.k@vaultec.in',
340
+ product: PRODUCTS[2].name,
341
+ category: PRODUCTS[2].category,
342
+ imageIndex: 2,
343
+ amount: 4,
344
+ status: 'completed',
345
+ date: '2025-01-05',
346
+ },
347
+ {
348
+ id: 'ORD-1022',
349
+ customer: 'Emma Thompson',
350
+ email: 'ethompson@monarch.uk',
351
+ product: PRODUCTS[3].name,
352
+ category: PRODUCTS[3].category,
353
+ imageIndex: 3,
354
+ amount: 8,
355
+ status: 'completed',
356
+ date: '2025-01-05',
357
+ },
358
+ {
359
+ id: 'ORD-1023',
360
+ customer: 'Carlos Mendez',
361
+ email: 'cmendez@sirius.ar',
362
+ product: PRODUCTS[4].name,
363
+ category: PRODUCTS[4].category,
364
+ imageIndex: 4,
365
+ amount: 7,
366
+ status: 'shipped',
367
+ date: '2025-01-04',
368
+ },
369
+ {
370
+ id: 'ORD-1024',
371
+ customer: 'Zoe Mitchell',
372
+ email: 'zoe.m@hyperion.au',
373
+ product: PRODUCTS[5].name,
374
+ category: PRODUCTS[5].category,
375
+ imageIndex: 5,
376
+ amount: 7,
377
+ status: 'processing',
378
+ date: '2025-01-04',
379
+ },
380
+ {
381
+ id: 'ORD-1025',
382
+ customer: 'Henrik Larsson',
383
+ email: 'hlarsson@volvo.se',
384
+ product: PRODUCTS[0].name,
385
+ category: PRODUCTS[0].category,
386
+ imageIndex: 0,
387
+ amount: 6,
388
+ status: 'completed',
389
+ date: '2025-01-03',
390
+ },
391
+ {
392
+ id: 'ORD-1026',
393
+ customer: 'Amara Okafor',
394
+ email: 'aokafor@wakanda.ng',
395
+ product: PRODUCTS[1].name,
396
+ category: PRODUCTS[1].category,
397
+ imageIndex: 1,
398
+ amount: 5,
399
+ status: 'completed',
400
+ date: '2025-01-03',
401
+ },
402
+ {
403
+ id: 'ORD-1027',
404
+ customer: 'Leo Fischer',
405
+ email: 'lfischer@kruger.de',
406
+ product: PRODUCTS[2].name,
407
+ category: PRODUCTS[2].category,
408
+ imageIndex: 2,
409
+ amount: 4,
410
+ status: 'completed',
411
+ date: '2025-01-02',
412
+ },
413
+ {
414
+ id: 'ORD-1028',
415
+ customer: 'Maya Petrov',
416
+ email: 'mpetrov@kaiba.ru',
417
+ product: PRODUCTS[3].name,
418
+ category: PRODUCTS[3].category,
419
+ imageIndex: 3,
420
+ amount: 8,
421
+ status: 'shipped',
422
+ date: '2025-01-02',
423
+ },
424
+ {
425
+ id: 'ORD-1029',
426
+ customer: 'Tomás Herrera',
427
+ email: 'therrera@nexus.co',
428
+ product: PRODUCTS[4].name,
429
+ category: PRODUCTS[4].category,
430
+ imageIndex: 4,
431
+ amount: 7,
432
+ status: 'completed',
433
+ date: '2025-01-01',
434
+ },
435
+ {
436
+ id: 'ORD-1030',
437
+ customer: 'Grace Nakamura',
438
+ email: 'gnakamura@capsule.jp',
439
+ product: PRODUCTS[5].name,
440
+ category: PRODUCTS[5].category,
441
+ imageIndex: 5,
442
+ amount: 7,
443
+ status: 'completed',
444
+ date: '2025-01-01',
445
+ },
446
+ ];
447
+
448
+ const revenueData = [
449
+ {date: 'Jan 1', revenue: 84},
450
+ {date: 'Jan 2', revenue: 72},
451
+ {date: 'Jan 3', revenue: 91},
452
+ {date: 'Jan 4', revenue: 68},
453
+ {date: 'Jan 5', revenue: 105},
454
+ {date: 'Jan 6', revenue: 118},
455
+ {date: 'Jan 7', revenue: 96},
456
+ {date: 'Jan 8', revenue: 132},
457
+ {date: 'Jan 9', revenue: 110},
458
+ {date: 'Jan 10', revenue: 98},
459
+ {date: 'Jan 11', revenue: 125},
460
+ {date: 'Jan 12', revenue: 142},
461
+ {date: 'Jan 13', revenue: 108},
462
+ {date: 'Jan 14', revenue: 156},
463
+ {date: 'Jan 15', revenue: 138},
464
+ ];
465
+
466
+ const statusColor: Record<string, 'green' | 'blue' | 'orange' | 'red'> = {
467
+ completed: 'green',
468
+ shipped: 'blue',
469
+ processing: 'orange',
470
+ refunded: 'red',
471
+ };
472
+
473
+ const columns: TableColumn<OrderRow>[] = [
474
+ {
475
+ key: 'id',
476
+ header: 'Order',
477
+ width: pixel(110),
478
+ renderCell: (item: OrderRow) => (
479
+ <Link href="#/templates/table-page-chart" isStandalone>
480
+ {item.id}
481
+ </Link>
482
+ ),
483
+ },
484
+ {
485
+ key: 'product',
486
+ header: 'Product',
487
+ width: proportional(3),
488
+ renderCell: (item: OrderRow) => (
489
+ <HStack gap={3} vAlign="center">
490
+ <ProductSwatch
491
+ accent={PRODUCT_ACCENTS[item.imageIndex]}
492
+ label={item.product}
493
+ />
494
+ <VStack gap={0}>
495
+ <Text type="body">{item.product}</Text>
496
+ <Text type="supporting" color="secondary">
497
+ {item.category}
498
+ </Text>
499
+ </VStack>
500
+ </HStack>
501
+ ),
502
+ },
503
+ {
504
+ key: 'amount',
505
+ header: 'Amount',
506
+ width: pixel(90),
507
+ renderCell: (item: OrderRow) => (
508
+ <Text type="body" hasTabularNumbers>
509
+ ${item.amount}
510
+ </Text>
511
+ ),
512
+ },
513
+ {
514
+ key: 'customer',
515
+ header: 'Customer',
516
+ width: proportional(2),
517
+ renderCell: (item: OrderRow) => <Text type="body">{item.customer}</Text>,
518
+ },
519
+ {
520
+ key: 'email',
521
+ header: 'Email',
522
+ width: proportional(2),
523
+ renderCell: (item: OrderRow) => <Text type="body">{item.email}</Text>,
524
+ },
525
+ {
526
+ key: 'status',
527
+ header: 'Status',
528
+ width: pixel(120),
529
+ renderCell: (item: OrderRow) => (
530
+ <Badge
531
+ label={item.status.charAt(0).toUpperCase() + item.status.slice(1)}
532
+ variant={statusColor[item.status]}
533
+ />
534
+ ),
535
+ },
536
+ {
537
+ key: 'date',
538
+ header: 'Date',
539
+ width: pixel(110),
540
+ renderCell: (item: OrderRow) => (
541
+ <Text type="body" hasTabularNumbers>
542
+ {item.date}
543
+ </Text>
544
+ ),
545
+ },
546
+ ];
547
+
548
+ // ============= REVENUE CHART (hand SVG, Dracula ramp) =============
549
+
550
+ const revenueLine = 'var(--dracula-purple)';
551
+
552
+ function RevenueChart() {
553
+ const W = 540;
554
+ const H = 200;
555
+ const padLeft = 40;
556
+ const padRight = 12;
557
+ const padTop = 12;
558
+ const baseline = 164;
559
+ const max = 160;
560
+ const plotW = W - padLeft - padRight;
561
+ const plotH = baseline - padTop;
562
+ const points = revenueData.map((d, i) => ({
563
+ ...d,
564
+ x: padLeft + (i / (revenueData.length - 1)) * plotW,
565
+ y: baseline - (d.revenue / max) * plotH,
566
+ }));
567
+ const linePath = points
568
+ .map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(1)},${p.y.toFixed(1)}`)
569
+ .join(' ');
570
+ const areaPath = `${linePath} L${points[points.length - 1].x.toFixed(1)},${baseline} L${points[0].x.toFixed(1)},${baseline} Z`;
571
+ const gridTicks = [0, 50, 100, 150];
572
+ return (
573
+ <VStack gap={3}>
574
+ <Card
575
+ padding={3}
576
+ style={{
577
+ backgroundColor: 'var(--color-background)',
578
+ border: 'var(--border-width) solid var(--color-separator)',
579
+ }}>
580
+ <svg
581
+ viewBox={`0 0 ${W} ${H}`}
582
+ width="100%"
583
+ role="img"
584
+ aria-label="Daily revenue, January 1 to 15">
585
+ {gridTicks.map(tick => {
586
+ const y = baseline - (tick / max) * plotH;
587
+ return (
588
+ <g key={tick}>
589
+ <line
590
+ x1={padLeft}
591
+ y1={y}
592
+ x2={W - padRight}
593
+ y2={y}
594
+ stroke="var(--color-separator)"
595
+ strokeDasharray={tick === 0 ? undefined : '3 3'}
596
+ opacity={tick === 0 ? 1 : 0.5}
597
+ />
598
+ <text
599
+ x={padLeft - 6}
600
+ y={y + 3}
601
+ textAnchor="end"
602
+ fontSize={9}
603
+ fill="var(--color-text-paragraph)"
604
+ fontFamily="var(--font-family-mono)">
605
+ ${tick}
606
+ </text>
607
+ </g>
608
+ );
609
+ })}
610
+ <path d={areaPath} fill={revenueLine} opacity={0.25} />
611
+ <path
612
+ d={linePath}
613
+ fill="none"
614
+ stroke={revenueLine}
615
+ strokeWidth={2}
616
+ strokeLinejoin="round"
617
+ strokeLinecap="round"
618
+ />
619
+ {points.map(
620
+ (p, i) =>
621
+ (i % 3 === 0 || i === points.length - 1) && (
622
+ <text
623
+ key={p.date}
624
+ x={i === points.length - 1 ? W - padRight - 2 : p.x}
625
+ y={H - 8}
626
+ textAnchor={i === points.length - 1 ? 'end' : 'middle'}
627
+ fontSize={9}
628
+ fill="var(--color-text-paragraph)"
629
+ fontFamily="var(--font-family-mono)">
630
+ {p.date}
631
+ </text>
632
+ ),
633
+ )}
634
+ </svg>
635
+ </Card>
636
+ <Text type="supporting" color="secondary">
637
+ Daily revenue · Jan 1–15
638
+ </Text>
639
+ <HStack gap={2} vAlign="center">
640
+ <Icon icon={Square} size="xsm" style={{color: revenueLine}} />
641
+ <Text type="supporting" color="secondary">
642
+ Revenue
643
+ </Text>
644
+ </HStack>
645
+ </VStack>
646
+ );
647
+ }
648
+
649
+ // ============= PAGE =============
650
+
651
+ export default function ChartTable() {
652
+ return (
653
+ <Layout
654
+ height="fill"
655
+ header={
656
+ <LayoutHeader hasDivider>
657
+ <HStack gap={2} vAlign="center">
658
+ <StackItem size="fill">
659
+ <Heading level={1}>Matcha Bar</Heading>
660
+ </StackItem>
661
+ <IconButton
662
+ label="Filter"
663
+ icon={<Icon icon={Filter} size="sm" />}
664
+ variant="ghost"
665
+ tooltip="Filter"
666
+ />
667
+ <IconButton
668
+ label="Export"
669
+ icon={<Icon icon={Download} size="sm" />}
670
+ variant="ghost"
671
+ tooltip="Export"
672
+ />
673
+ <Button
674
+ label="New order"
675
+ variant="primary"
676
+ icon={<Icon icon={Plus} size="sm" />}
677
+ />
678
+ </HStack>
679
+ </LayoutHeader>
680
+ }
681
+ content={
682
+ <LayoutContent padding={3}>
683
+ <VStack gap={4}>
684
+ <RevenueChart />
685
+
686
+ <Table<OrderRow>
687
+ data={orders}
688
+ columns={columns}
689
+ idKey="id"
690
+ density="balanced"
691
+ dividers="rows"
692
+ hasHover
693
+ />
694
+ </VStack>
695
+ </LayoutContent>
696
+ }
697
+ />
698
+ );
699
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'Incident Heatmap',
4
+ description: 'Outage heatmap with status table',
5
+ category: 'data',
6
+ };