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