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,547 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {
4
+ VStack,
5
+ HStack,
6
+ StackItem,
7
+ Layout,
8
+ LayoutContent,
9
+ LayoutHeader,
10
+ } from '@astryxdesign/core/Layout';
11
+ import {Text, Heading} from '@astryxdesign/core/Text';
12
+ import {Button} from '@astryxdesign/core/Button';
13
+ import {IconButton} from '@astryxdesign/core/IconButton';
14
+ import {Icon} from '@astryxdesign/core/Icon';
15
+ import {Avatar} from '@astryxdesign/core/Avatar';
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, RotateCw} from 'lucide-react';
22
+
23
+ // ============= ICONS (verified lucide-react exports) =============
24
+ // Filter ← FunnelIcon, Download ← ArrowDownTrayIcon, RotateCw ← ArrowPathIcon.
25
+
26
+ // ============= DATA =============
27
+
28
+ type Severity = 'critical' | 'major' | 'minor' | 'resolved';
29
+ type ProductName =
30
+ | 'Ads Manager'
31
+ | 'Business Suite'
32
+ | 'Commerce Manager'
33
+ | 'Pages'
34
+ | 'Messenger API'
35
+ | 'Graph API'
36
+ | 'WhatsApp Business'
37
+ | 'Instagram API';
38
+
39
+ interface IncidentRow extends Record<string, unknown> {
40
+ id: string;
41
+ product: ProductName;
42
+ title: string;
43
+ severity: Severity;
44
+ status: 'ongoing' | 'identified' | 'monitoring' | 'resolved';
45
+ oncall: string;
46
+ startTime: string;
47
+ duration: string;
48
+ date: string;
49
+ dayOfWeek: number;
50
+ hour: number;
51
+ }
52
+
53
+ const incidents: IncidentRow[] = [
54
+ // Active — ongoing
55
+ {
56
+ id: 'INC-4008',
57
+ product: 'Business Suite',
58
+ title:
59
+ 'Notification delivery delays affecting mobile push and email channels — queue saturation under investigation',
60
+ severity: 'major',
61
+ oncall: 'Carlos Mendez',
62
+ status: 'ongoing',
63
+ startTime: '09:15',
64
+ duration: 'ongoing',
65
+ date: '2025-01-15',
66
+ dayOfWeek: 3,
67
+ hour: 9,
68
+ },
69
+ // Tue (2) 3pm — the only major outage cluster (3 incidents)
70
+ {
71
+ id: 'INC-4001',
72
+ product: 'Ads Manager',
73
+ title:
74
+ 'Campaign creation timing out for ~12% of advertisers in EMEA region — root caused to elevated DB write latency',
75
+ severity: 'critical',
76
+ oncall: 'Sarah Chen',
77
+ status: 'resolved',
78
+ startTime: '15:10',
79
+ duration: '2h 15m',
80
+ date: '2025-01-14',
81
+ dayOfWeek: 2,
82
+ hour: 15,
83
+ },
84
+ {
85
+ id: 'INC-4002',
86
+ product: 'Graph API',
87
+ title:
88
+ 'Elevated 5xx error rates on /me and /me/accounts endpoints affecting third-party integrations globally',
89
+ severity: 'critical',
90
+ oncall: 'David Kim',
91
+ status: 'resolved',
92
+ startTime: '15:30',
93
+ duration: '3h 20m',
94
+ date: '2025-01-14',
95
+ dayOfWeek: 2,
96
+ hour: 15,
97
+ },
98
+ {
99
+ id: 'INC-4003',
100
+ product: 'Instagram API',
101
+ title:
102
+ 'Media upload failures for image and video content via /media endpoint affecting ~8% of publishers',
103
+ severity: 'major',
104
+ oncall: 'Priya Sharma',
105
+ status: 'resolved',
106
+ startTime: '15:05',
107
+ duration: '1h 50m',
108
+ date: '2025-01-14',
109
+ dayOfWeek: 2,
110
+ hour: 15,
111
+ },
112
+ // Tue (2) 4pm — spillover (2 incidents)
113
+ {
114
+ id: 'INC-4004',
115
+ product: 'Ads Manager',
116
+ title:
117
+ 'Audience insights tab returning empty results due to cache invalidation cascade from earlier outage',
118
+ severity: 'minor',
119
+ oncall: 'Marcus Rivera',
120
+ status: 'resolved',
121
+ startTime: '16:00',
122
+ duration: '45m',
123
+ date: '2025-01-14',
124
+ dayOfWeek: 2,
125
+ hour: 16,
126
+ },
127
+ {
128
+ id: 'INC-4005',
129
+ product: 'WhatsApp Business',
130
+ title:
131
+ 'Outbound message delivery delays of 2-5 minutes for high-volume senders due to queue backpressure',
132
+ severity: 'major',
133
+ oncall: 'Elena Volkov',
134
+ status: 'resolved',
135
+ startTime: '16:20',
136
+ duration: '1h 15m',
137
+ date: '2025-01-14',
138
+ dayOfWeek: 2,
139
+ hour: 16,
140
+ },
141
+ // Thu (4) 10am — single incident
142
+ {
143
+ id: 'INC-4006',
144
+ product: 'Commerce Manager',
145
+ title:
146
+ 'Product catalog sync failures from Shopify and BigCommerce integrations after schema migration deployed',
147
+ severity: 'major',
148
+ oncall: 'Noah Williams',
149
+ status: 'resolved',
150
+ startTime: '10:15',
151
+ duration: '2h 40m',
152
+ date: '2025-01-16',
153
+ dayOfWeek: 4,
154
+ hour: 10,
155
+ },
156
+ // Fri (5) 4pm — single incident
157
+ {
158
+ id: 'INC-4007',
159
+ product: 'Messenger API',
160
+ title:
161
+ 'Webhook delivery failures for subscribed page events — retries succeeded but signaled false alerts',
162
+ severity: 'minor',
163
+ oncall: 'Mei Lin',
164
+ status: 'resolved',
165
+ startTime: '16:30',
166
+ duration: '45m',
167
+ date: '2025-01-17',
168
+ dayOfWeek: 5,
169
+ hour: 16,
170
+ },
171
+ // Active incident
172
+ // Scattered minor blips — light pink cells
173
+ {
174
+ id: 'INC-4009',
175
+ product: 'Pages',
176
+ title:
177
+ 'Image upload timeouts for posts larger than 4MB — auto-recovered after CDN region failover',
178
+ severity: 'minor',
179
+ oncall: 'Fatima Al-Rashid',
180
+ status: 'resolved',
181
+ startTime: '11:00',
182
+ duration: '20m',
183
+ date: '2025-01-12',
184
+ dayOfWeek: 0,
185
+ hour: 11,
186
+ },
187
+ {
188
+ id: 'INC-4010',
189
+ product: 'Graph API',
190
+ title:
191
+ 'Intermittent 503 errors on a single load balancer in us-east-1 — auto-healed by health check rotation',
192
+ severity: 'minor',
193
+ oncall: 'Lucas Andersson',
194
+ status: 'resolved',
195
+ startTime: '14:30',
196
+ duration: '15m',
197
+ date: '2025-01-13',
198
+ dayOfWeek: 1,
199
+ hour: 14,
200
+ },
201
+ {
202
+ id: 'INC-4011',
203
+ product: 'WhatsApp Business',
204
+ title:
205
+ 'Template message rejections due to stale category cache for newly approved templates',
206
+ severity: 'minor',
207
+ oncall: 'Sofia Garcia',
208
+ status: 'resolved',
209
+ startTime: '13:00',
210
+ duration: '30m',
211
+ date: '2025-01-16',
212
+ dayOfWeek: 4,
213
+ hour: 13,
214
+ },
215
+ {
216
+ id: 'INC-4012',
217
+ product: 'Commerce Manager',
218
+ title:
219
+ 'Inventory count mismatch between checkout and catalog services for ~200 SKUs in test region',
220
+ severity: 'minor',
221
+ oncall: 'Raj Kapoor',
222
+ status: 'resolved',
223
+ startTime: '12:00',
224
+ duration: '1h 00m',
225
+ date: '2025-01-17',
226
+ dayOfWeek: 5,
227
+ hour: 12,
228
+ },
229
+ {
230
+ id: 'INC-4013',
231
+ product: 'Instagram API',
232
+ title:
233
+ 'Story insights data delayed by 30-60 minutes for accounts with >100K followers',
234
+ severity: 'minor',
235
+ oncall: 'Emma Thompson',
236
+ status: 'resolved',
237
+ startTime: '10:00',
238
+ duration: '45m',
239
+ date: '2025-01-18',
240
+ dayOfWeek: 6,
241
+ hour: 10,
242
+ },
243
+ {
244
+ id: 'INC-4014',
245
+ product: 'Business Suite',
246
+ title:
247
+ 'Slow inbox loading times (>3s p95) for accounts with large message histories — index rebuild in progress',
248
+ severity: 'minor',
249
+ oncall: 'Andre Santos',
250
+ status: 'resolved',
251
+ startTime: '15:00',
252
+ duration: '25m',
253
+ date: '2025-01-15',
254
+ dayOfWeek: 3,
255
+ hour: 15,
256
+ },
257
+ ];
258
+
259
+ const DAYS = [
260
+ 'Jan 12',
261
+ 'Jan 13',
262
+ 'Jan 14',
263
+ 'Jan 15',
264
+ 'Jan 16',
265
+ 'Jan 17',
266
+ 'Jan 18',
267
+ ];
268
+ const HOURS = [
269
+ '9am',
270
+ '10am',
271
+ '11am',
272
+ '12pm',
273
+ '1pm',
274
+ '2pm',
275
+ '3pm',
276
+ '4pm',
277
+ '5pm',
278
+ ];
279
+
280
+ function buildHeatmapData(data: IncidentRow[]) {
281
+ return HOURS.flatMap((hour, hi) =>
282
+ DAYS.map((day, di) => {
283
+ const hourValue = 9 + hi;
284
+ const count = data.filter(
285
+ o => o.dayOfWeek === di && o.hour === hourValue,
286
+ ).length;
287
+ return {day, hour, incidents: count};
288
+ }),
289
+ );
290
+ }
291
+
292
+ const statusVariant: Record<string, 'error' | 'warning' | 'info' | 'success'> =
293
+ {
294
+ ongoing: 'error',
295
+ identified: 'warning',
296
+ monitoring: 'info',
297
+ resolved: 'success',
298
+ };
299
+
300
+ const columns: TableColumn<IncidentRow>[] = [
301
+ {
302
+ key: 'id',
303
+ header: 'Incident',
304
+ width: pixel(110),
305
+ renderCell: (item: IncidentRow) => (
306
+ <Link href="#/templates/table-page-heatmap-status" isStandalone>
307
+ {item.id}
308
+ </Link>
309
+ ),
310
+ },
311
+ {
312
+ key: 'product',
313
+ header: 'Product',
314
+ width: proportional(2),
315
+ renderCell: (item: IncidentRow) => <Text type="body">{item.product}</Text>,
316
+ },
317
+ {
318
+ key: 'title',
319
+ header: 'Description',
320
+ width: proportional(5),
321
+ renderCell: (item: IncidentRow) => <Text type="body">{item.title}</Text>,
322
+ },
323
+ {
324
+ key: 'startTime',
325
+ header: 'Started',
326
+ width: pixel(96),
327
+ renderCell: (item: IncidentRow) => (
328
+ <Text type="body" hasTabularNumbers>
329
+ {item.startTime}
330
+ </Text>
331
+ ),
332
+ },
333
+ {
334
+ key: 'duration',
335
+ header: 'Duration',
336
+ width: pixel(100),
337
+ renderCell: (item: IncidentRow) => (
338
+ <Text type="body" hasTabularNumbers>
339
+ {item.duration}
340
+ </Text>
341
+ ),
342
+ },
343
+ {
344
+ key: 'oncall',
345
+ header: 'On-call',
346
+ width: proportional(2),
347
+ renderCell: (item: IncidentRow) => (
348
+ <HStack gap={2} vAlign="center">
349
+ <Avatar name={item.oncall} size="sm" />
350
+ <Text type="body">{item.oncall}</Text>
351
+ </HStack>
352
+ ),
353
+ },
354
+ {
355
+ key: 'status',
356
+ header: 'Status',
357
+ width: pixel(120),
358
+ renderCell: (item: IncidentRow) => (
359
+ <Badge
360
+ label={item.status.charAt(0).toUpperCase() + item.status.slice(1)}
361
+ variant={statusVariant[item.status]}
362
+ />
363
+ ),
364
+ },
365
+ {
366
+ key: 'date',
367
+ header: 'Date',
368
+ width: pixel(110),
369
+ renderCell: (item: IncidentRow) => (
370
+ <Text type="body" hasTabularNumbers>
371
+ {item.date}
372
+ </Text>
373
+ ),
374
+ },
375
+ ];
376
+
377
+ // ============= HEATMAP (hand SVG, Dracula ramp) =============
378
+
379
+ // Incident-count ramp: quiet days rest on the Dracula surface, busier cells
380
+ // climb green → orange → red.
381
+ function heatFill(count: number): string {
382
+ if (count >= 3) {
383
+ return 'var(--dracula-red)';
384
+ }
385
+ if (count === 2) {
386
+ return 'var(--dracula-orange)';
387
+ }
388
+ if (count === 1) {
389
+ return 'var(--dracula-green)';
390
+ }
391
+ return 'var(--dracula-bg-light)';
392
+ }
393
+
394
+ const HEAT_LEGEND = [
395
+ {label: '0', fill: 'var(--dracula-bg-light)'},
396
+ {label: '1', fill: 'var(--dracula-green)'},
397
+ {label: '2', fill: 'var(--dracula-orange)'},
398
+ {label: '3+', fill: 'var(--dracula-red)'},
399
+ ];
400
+
401
+ function OutageHeatmap() {
402
+ const heatmapData = buildHeatmapData(incidents);
403
+ const cellW = 56;
404
+ const cellH = 24;
405
+ const gap = 4;
406
+ const labelW = 44;
407
+ const labelH = 22;
408
+ const W = labelW + DAYS.length * (cellW + gap);
409
+ const H = labelH + HOURS.length * (cellH + gap);
410
+ const countFor = (day: string, hour: string) =>
411
+ heatmapData.find(d => d.day === day && d.hour === hour)?.incidents ?? 0;
412
+ return (
413
+ <VStack gap={3}>
414
+ <Card
415
+ padding={3}
416
+ style={{
417
+ backgroundColor: 'var(--color-background)',
418
+ border: 'var(--border-width) solid var(--color-separator)',
419
+ }}>
420
+ <svg
421
+ viewBox={`0 0 ${W} ${H}`}
422
+ width="100%"
423
+ role="img"
424
+ aria-label="Incidents by day and hour, January 12 to 18">
425
+ {DAYS.map((day, di) => (
426
+ <text
427
+ key={day}
428
+ x={labelW + di * (cellW + gap) + cellW / 2}
429
+ y={14}
430
+ textAnchor="middle"
431
+ fontSize={9}
432
+ fill="var(--color-text-paragraph)"
433
+ fontFamily="var(--font-family-mono)">
434
+ {day}
435
+ </text>
436
+ ))}
437
+ {HOURS.map((hour, hi) => (
438
+ <g key={hour}>
439
+ <text
440
+ x={labelW - 6}
441
+ y={labelH + hi * (cellH + gap) + cellH / 2 + 3}
442
+ textAnchor="end"
443
+ fontSize={9}
444
+ fill="var(--color-text-paragraph)"
445
+ fontFamily="var(--font-family-mono)">
446
+ {hour}
447
+ </text>
448
+ {DAYS.map((day, di) => {
449
+ const count = countFor(day, hour);
450
+ return (
451
+ <g key={day}>
452
+ <rect
453
+ x={labelW + di * (cellW + gap)}
454
+ y={labelH + hi * (cellH + gap)}
455
+ width={cellW}
456
+ height={cellH}
457
+ rx={4}
458
+ fill={heatFill(count)}
459
+ />
460
+ {count > 0 && (
461
+ <text
462
+ x={labelW + di * (cellW + gap) + cellW / 2}
463
+ y={labelH + hi * (cellH + gap) + cellH / 2 + 3}
464
+ textAnchor="middle"
465
+ fontSize={9}
466
+ fill="var(--dracula-bg-dark)"
467
+ fontFamily="var(--font-family-mono)">
468
+ {count}
469
+ </text>
470
+ )}
471
+ </g>
472
+ );
473
+ })}
474
+ </g>
475
+ ))}
476
+ </svg>
477
+ </Card>
478
+ <Text type="supporting" color="secondary">
479
+ Incidents by day and hour · Jan 12–18
480
+ </Text>
481
+ <HStack gap={4} vAlign="center">
482
+ {HEAT_LEGEND.map(entry => (
483
+ <HStack key={entry.label} gap={2} vAlign="center">
484
+ <svg
485
+ width={12}
486
+ height={12}
487
+ role="img"
488
+ aria-label={`${entry.label} incidents`}>
489
+ <rect width={12} height={12} rx={4} fill={entry.fill} />
490
+ </svg>
491
+ <Text type="supporting" color="secondary" hasTabularNumbers>
492
+ {entry.label}
493
+ </Text>
494
+ </HStack>
495
+ ))}
496
+ </HStack>
497
+ </VStack>
498
+ );
499
+ }
500
+
501
+ // ============= PAGE =============
502
+
503
+ export default function HeatmapTable() {
504
+ return (
505
+ <Layout
506
+ height="fill"
507
+ header={
508
+ <LayoutHeader hasDivider>
509
+ <HStack gap={2} vAlign="center">
510
+ <StackItem size="fill">
511
+ <Heading level={1}>Status</Heading>
512
+ </StackItem>
513
+ <IconButton
514
+ label="Filter"
515
+ icon={<Icon icon={Filter} size="sm" />}
516
+ variant="ghost"
517
+ tooltip="Filter"
518
+ />
519
+ <IconButton
520
+ label="Export"
521
+ icon={<Icon icon={Download} size="sm" />}
522
+ variant="ghost"
523
+ tooltip="Export"
524
+ />
525
+ <Button label="Refresh" icon={<Icon icon={RotateCw} size="sm" />} />
526
+ </HStack>
527
+ </LayoutHeader>
528
+ }
529
+ content={
530
+ <LayoutContent padding={3}>
531
+ <VStack gap={4}>
532
+ <OutageHeatmap />
533
+
534
+ <Table<IncidentRow>
535
+ data={incidents}
536
+ columns={columns}
537
+ idKey="id"
538
+ density="balanced"
539
+ dividers="rows"
540
+ hasHover
541
+ />
542
+ </VStack>
543
+ </LayoutContent>
544
+ }
545
+ />
546
+ );
547
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'Shoe Store Sales',
4
+ description: 'Revenue chart over shoe order history',
5
+ category: 'data',
6
+ };