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,1282 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {type CSSProperties, type ReactNode} from 'react';
4
+ import {
5
+ Plus,
6
+ Search,
7
+ Tag,
8
+ Folder,
9
+ MapPin,
10
+ List,
11
+ LayoutGrid,
12
+ ShoppingBag,
13
+ Banknote,
14
+ Mic,
15
+ CreditCard,
16
+ Lock,
17
+ X,
18
+ Download,
19
+ Smartphone,
20
+ Wallet,
21
+ User,
22
+ } from 'lucide-react';
23
+ import {Text, Heading} from '@astryxdesign/core/Text';
24
+ import {VStack, HStack} from '@astryxdesign/core/Layout';
25
+ import {useAppShellMobile} from '@astryxdesign/core/AppShell';
26
+ import {Grid, GridSpan} from '@astryxdesign/core/Grid';
27
+ import {Card} from '@astryxdesign/core/Card';
28
+ import {Button} from '@astryxdesign/core/Button';
29
+ import {Link} from '@astryxdesign/core/Link';
30
+ import {Badge} from '@astryxdesign/core/Badge';
31
+ import {Banner} from '@astryxdesign/core/Banner';
32
+ import {Divider} from '@astryxdesign/core/Divider';
33
+ import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
34
+ import {Item} from '@astryxdesign/core/Item';
35
+ import {Table, proportional, pixel} from '@astryxdesign/core/Table';
36
+ import {TextInput} from '@astryxdesign/core/TextInput';
37
+ import {Selector} from '@astryxdesign/core/Selector';
38
+ import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
39
+ import {SelectableCard} from '@astryxdesign/core/SelectableCard';
40
+ import {MoreMenu} from '@astryxdesign/core/MoreMenu';
41
+ import {Center} from '@astryxdesign/core/Center';
42
+ import {Section} from '@astryxdesign/core/Section';
43
+ import {AspectRatio} from '@astryxdesign/core/AspectRatio';
44
+ import {NumberInput} from '@astryxdesign/core/NumberInput';
45
+ import {OverflowList} from '@astryxdesign/core/OverflowList';
46
+ import {TopNav, TopNavHeading, TopNavItem} from '@astryxdesign/core/TopNav';
47
+ import {
48
+ ChatComposer,
49
+ ChatMessage,
50
+ ChatMessageBubble,
51
+ ChatMessageList,
52
+ ChatSystemMessage,
53
+ } from '@astryxdesign/core/Chat';
54
+
55
+ // Styles passed to Astryx components via their `style` prop. Astryx components
56
+ // forward the DOM `style` prop, so these work with no CSS compiler — in
57
+ // compiled builds and in the live playground preview alike.
58
+ const styles: Record<string, CSSProperties> = {
59
+ card: {
60
+ backgroundColor: 'var(--color-background-body)',
61
+ color: 'var(--color-text-primary)',
62
+ minWidth: 0,
63
+ borderColor: 'transparent',
64
+ },
65
+ checkoutStack: {
66
+ minWidth: 0,
67
+ width: '100%',
68
+ },
69
+ paymentCardContent: {
70
+ minWidth: 0,
71
+ width: '100%',
72
+ textAlign: 'center',
73
+ wordBreak: 'break-word',
74
+ },
75
+ inventoryCard: {
76
+ backgroundColor: 'var(--color-background-surface)',
77
+ color: 'var(--color-text-primary)',
78
+ overflow: 'hidden',
79
+ },
80
+ inventoryHeader: {
81
+ paddingBlock: 'var(--spacing-6)',
82
+ paddingInline: 'var(--spacing-6)',
83
+ },
84
+ inventoryFilterRow: {
85
+ paddingBlock: 'var(--spacing-4)',
86
+ paddingInline: 'var(--spacing-6)',
87
+ width: '100%',
88
+ overflowX: 'auto' as const,
89
+ },
90
+ // Inset the table by --spacing-6 (the card is padding={0}) so its edge lines
91
+ // up with the header/filter row in every theme's spacing scale.
92
+ inventoryTableWrap: {
93
+ paddingInline: 'var(--spacing-6)',
94
+ paddingBlockEnd: 'var(--spacing-2)',
95
+ },
96
+ searchInput: {
97
+ flex: 1,
98
+ minWidth: 0,
99
+ maxWidth: 240,
100
+ },
101
+ activityCard: {
102
+ backgroundColor: 'var(--color-background-surface)',
103
+ color: 'var(--color-text-primary)',
104
+ minWidth: 0,
105
+ height: '100%',
106
+ },
107
+ chatCard: {
108
+ backgroundColor: 'var(--color-background-surface)',
109
+ color: 'var(--color-text-primary)',
110
+ minWidth: 0,
111
+ overflow: 'hidden',
112
+ display: 'flex',
113
+ flexDirection: 'column' as const,
114
+ },
115
+ chatHeader: {
116
+ paddingBlock: 'var(--spacing-4)',
117
+ paddingInline: 'var(--spacing-4)',
118
+ },
119
+ activityCardStack: {
120
+ height: '100%',
121
+ },
122
+ activityListFade: {
123
+ flex: 1,
124
+ minHeight: 0,
125
+ overflow: 'hidden',
126
+ maskImage:
127
+ 'linear-gradient(to bottom, black calc(100% - 48px), transparent)',
128
+ WebkitMaskImage:
129
+ 'linear-gradient(to bottom, black calc(100% - 48px), transparent)',
130
+ marginInline: 'calc(var(--spacing-2) * -1)',
131
+ },
132
+ content: {
133
+ maxWidth: 960,
134
+ marginInline: 'auto',
135
+ minWidth: 0,
136
+ },
137
+ contentFluid: {
138
+ maxWidth: 880,
139
+ },
140
+ heroText: {
141
+ textAlign: 'center' as const,
142
+ maxWidth: 560,
143
+ },
144
+ centerText: {
145
+ textAlign: 'center',
146
+ },
147
+ cardStack: {
148
+ height: '100%',
149
+ },
150
+ cardDescription: {
151
+ flex: 1,
152
+ },
153
+ quantityInput: {
154
+ // minWidth (not a hard width) so the field grows to fit the digit + the
155
+ // theme's input padding. A fixed 40px was too tight on themes with larger
156
+ // padding / bigger type scale (e.g. Matcha, Y2K), clipping the value.
157
+ minWidth: 64,
158
+ flexShrink: 0,
159
+ },
160
+ cartButton: {
161
+ flex: 1,
162
+ },
163
+ };
164
+
165
+ // Styles applied directly to plain DOM elements via the `style` prop.
166
+ // Plain inline styles so they render with no CSS compiler. All are static
167
+ // (no media/pseudo variants), so inline styles reproduce them exactly.
168
+ const inlineStyles: Record<string, CSSProperties> = {
169
+ inventoryBannerWrap: {
170
+ paddingInline: 'var(--spacing-6)',
171
+ paddingBottom: 'var(--spacing-4)',
172
+ },
173
+ chatBody: {
174
+ flex: 1,
175
+ minHeight: 0,
176
+ overflow: 'hidden',
177
+ },
178
+ chatSuggestions: {
179
+ paddingInline: 'var(--spacing-4)',
180
+ paddingBottom: 'var(--spacing-2)',
181
+ },
182
+ chatComposer: {
183
+ paddingInline: 'var(--spacing-4)',
184
+ paddingBottom: 'var(--spacing-4)',
185
+ },
186
+ activityIcon: {
187
+ width: 32,
188
+ height: 32,
189
+ borderRadius: 'var(--radius-full)',
190
+ backgroundColor: 'var(--color-background-muted)',
191
+ color: 'var(--color-text-secondary)',
192
+ display: 'flex',
193
+ alignItems: 'center',
194
+ justifyContent: 'center',
195
+ flexShrink: 0,
196
+ },
197
+ cardBody: {
198
+ padding: 'var(--spacing-4)',
199
+ flex: 1,
200
+ display: 'flex',
201
+ flexDirection: 'column',
202
+ },
203
+ };
204
+
205
+ // Fills the AspectRatio box with a Dracula product scene. No Image primitive
206
+ // in Astryx (#2582), so product tiles are inline SVG on brand tokens.
207
+ const artImage: CSSProperties = {
208
+ width: '100%',
209
+ height: '100%',
210
+ display: 'block',
211
+ };
212
+ // Rounded inventory swatch. No radius prop where the swatch renders, so the
213
+ // SVG carries its own rounded corners (rx=4).
214
+ const thumbSwatch: CSSProperties = {
215
+ flexShrink: 0,
216
+ display: 'block',
217
+ };
218
+
219
+ // One Dracula accent per hero product slot.
220
+ const PRODUCT_HUES = [
221
+ 'var(--dracula-purple)',
222
+ 'var(--dracula-cyan)',
223
+ 'var(--dracula-yellow)',
224
+ ];
225
+
226
+ /** Categorical badge variants usable for showcase product/inventory tags. */
227
+ export type ShowcaseBadgeVariant =
228
+ | 'blue'
229
+ | 'cyan'
230
+ | 'green'
231
+ | 'orange'
232
+ | 'pink'
233
+ | 'purple'
234
+ | 'red'
235
+ | 'teal'
236
+ | 'yellow';
237
+
238
+ export interface ProductSpec {
239
+ name: string;
240
+ description: string;
241
+ badge: string;
242
+ badgeVariant: ShowcaseBadgeVariant;
243
+ }
244
+
245
+ const DEFAULT_PRODUCTS: ProductSpec[] = [
246
+ {
247
+ name: 'Moonphase Watch',
248
+ description: 'Clean lines and lume that carry through the longest night.',
249
+ badge: 'New',
250
+ badgeVariant: 'blue',
251
+ },
252
+ {
253
+ name: 'Night-Owl Headphones',
254
+ description: 'Deep sound and soft cushions for all-night listening.',
255
+ badge: 'Popular',
256
+ badgeVariant: 'green',
257
+ },
258
+ {
259
+ name: 'Coven Canvas Backpack',
260
+ description: 'Waxed canvas with a quiet, moonlit profile.',
261
+ badge: 'Limited',
262
+ badgeVariant: 'yellow',
263
+ },
264
+ ];
265
+
266
+ // Dracula product scene: harvest moon over sleeping hills with a per-product
267
+ // accent glyph. All fills are brand vars, corners rx=4.
268
+ function ProductArt({hue, label}: {hue: string; label: string}) {
269
+ return (
270
+ <svg
271
+ viewBox="0 0 400 300"
272
+ preserveAspectRatio="xMidYMid slice"
273
+ style={artImage}
274
+ role="img"
275
+ aria-label={`${label} artwork`}>
276
+ <rect width="400" height="300" fill="var(--dracula-bg-light)" />
277
+ <circle cx="200" cy="110" r="52" fill={hue} opacity={0.9} />
278
+ <circle
279
+ cx="184"
280
+ cy="100"
281
+ r={44}
282
+ fill="var(--dracula-bg-light)"
283
+ opacity={0.55}
284
+ />
285
+ <path
286
+ d="M0 210 Q120 170 240 200 T400 190 V300 H0 Z"
287
+ fill="var(--dracula-current-line)"
288
+ />
289
+ <path
290
+ d="M0 245 Q140 215 300 240 T400 235 V300 H0 Z"
291
+ fill="var(--dracula-bg)"
292
+ />
293
+ <g
294
+ transform="translate(200 232)"
295
+ fill="none"
296
+ stroke={hue}
297
+ strokeWidth="5"
298
+ strokeLinecap="round"
299
+ strokeLinejoin="round">
300
+ <rect x="-30" y="-30" width="60" height="60" rx={4} />
301
+ <circle cx="13" cy="-13" r="2.5" fill={hue} stroke="none" />
302
+ <path d="M-22 20 L-5 2 L7 12 L14 5 L23 15" />
303
+ </g>
304
+ </svg>
305
+ );
306
+ }
307
+
308
+ // Rounded inventory swatch: Dracula surface with a per-row accent ring.
309
+ function ThumbSwatch({hue, label}: {hue: string; label: string}) {
310
+ return (
311
+ <svg
312
+ viewBox="0 0 40 40"
313
+ width={40}
314
+ height={40}
315
+ style={thumbSwatch}
316
+ role="img"
317
+ aria-label={`${label} swatch`}>
318
+ <rect width={40} height={40} rx={5} fill="var(--dracula-bg-light)" />
319
+ <rect
320
+ x={1}
321
+ y={1}
322
+ width={38}
323
+ height={38}
324
+ rx={4}
325
+ fill="none"
326
+ stroke="var(--color-widget-content-border)"
327
+ />
328
+ <circle cx={20} cy={20} r={8} fill="none" stroke={hue} strokeWidth={3} />
329
+ </svg>
330
+ );
331
+ }
332
+
333
+ export interface ThemeShowcaseProps {
334
+ /** The three hero product cards. Defaults to the nocturne store products. */
335
+ products?: ProductSpec[];
336
+ /** Inventory table rows. Defaults to the nocturne store inventory. */
337
+ inventory?: InventoryRow[];
338
+ }
339
+
340
+ // Default export is the route page (sandbox renders this as a Next.js page, so
341
+ // it must take no props / satisfy PageProps). It renders the store with the
342
+ // nocturne defaults. Consumers that need per-theme content import the named
343
+ // `ThemeShowcaseStore` below and pass products/inventory.
344
+ export default function ThemeShowcase() {
345
+ return <ThemeShowcaseStore />;
346
+ }
347
+
348
+ export function ThemeShowcaseStore({
349
+ products = DEFAULT_PRODUCTS,
350
+ inventory = DEFAULT_INVENTORY,
351
+ }: ThemeShowcaseProps = {}) {
352
+ const {isMobile} = useAppShellMobile();
353
+ return (
354
+ <div
355
+ style={{
356
+ minHeight: '100%',
357
+ backgroundColor: 'var(--color-background-body)',
358
+ }}>
359
+ <StorePreview products={products} isMobile={isMobile} />
360
+ <div
361
+ style={{
362
+ padding: 'var(--spacing-6)',
363
+ backgroundColor: 'var(--color-background-surface)',
364
+ }}>
365
+ <CardShowcase inventory={inventory} isMobile={isMobile} />
366
+ </div>
367
+ </div>
368
+ );
369
+ }
370
+
371
+ function CardShowcase({
372
+ inventory,
373
+ isMobile,
374
+ }: {
375
+ inventory: InventoryRow[];
376
+ isMobile: boolean;
377
+ }) {
378
+ const columns = isMobile ? 1 : ({minWidth: 200, repeat: 'fit'} as const);
379
+
380
+ return (
381
+ <VStack gap={8}>
382
+ <Grid columns={columns} gap={4}>
383
+ <GridSpan columns={1}>
384
+ <CheckoutCard isMobile={isMobile} />
385
+ </GridSpan>
386
+ <GridSpan columns={isMobile ? 1 : 2}>
387
+ <ChatCard />
388
+ </GridSpan>
389
+ </Grid>
390
+ <Grid columns={columns} gap={4}>
391
+ <GridSpan columns={isMobile ? 1 : 3}>
392
+ <InventoryCard inventory={inventory} />
393
+ </GridSpan>
394
+ <GridSpan columns={1}>
395
+ <LatestActivityCard isMobile={isMobile} />
396
+ </GridSpan>
397
+ </Grid>
398
+ </VStack>
399
+ );
400
+ }
401
+
402
+ function StorePreview({
403
+ products,
404
+ isMobile,
405
+ }: {
406
+ products: ProductSpec[];
407
+ isMobile: boolean;
408
+ }) {
409
+ return (
410
+ <div data-theme-preview="true">
411
+ <VStack gap={0}>
412
+ <TopNav
413
+ label="Theme preview navigation"
414
+ heading={<TopNavHeading heading="Nocturne" />}
415
+ centerContent={
416
+ isMobile ? undefined : (
417
+ <>
418
+ <TopNavItem label="Shop" href="#/templates/theme-showcase" isSelected />
419
+ <TopNavItem label="New In" href="#/templates/theme-showcase" />
420
+ <TopNavItem label="Stories" href="#/templates/theme-showcase" />
421
+ <TopNavItem label="Help" href="#/templates/theme-showcase" />
422
+ </>
423
+ )
424
+ }
425
+ endContent={
426
+ <HStack gap={2} vAlign="center">
427
+ <HStack gap={0.5}>
428
+ <Button
429
+ label="Search"
430
+ tooltip="Search"
431
+ variant="ghost"
432
+ isIconOnly
433
+ icon={<Search size={20} />}
434
+ href="#/templates/theme-showcase"
435
+ />
436
+ <Button
437
+ label="Account"
438
+ tooltip="Account"
439
+ variant="ghost"
440
+ isIconOnly
441
+ icon={<User size={20} />}
442
+ href="#/templates/theme-showcase"
443
+ />
444
+ <Button
445
+ label="Cart"
446
+ tooltip="Cart"
447
+ variant="ghost"
448
+ isIconOnly
449
+ icon={<ShoppingBag size={20} />}
450
+ href="#/templates/theme-showcase"
451
+ />
452
+ </HStack>
453
+ <Button label="Sign in" variant="primary" href="#/templates/theme-showcase" />
454
+ </HStack>
455
+ }
456
+ />
457
+
458
+ <Section padding={6} variant="transparent">
459
+ <VStack gap={10} style={{...styles.content, ...styles.contentFluid}}>
460
+ <Center>
461
+ <VStack gap={4} hAlign="center" style={styles.heroText}>
462
+ <Text type="display-2" color="accent">
463
+ Little haunts,
464
+ <br />
465
+ everywhere you roam
466
+ </Text>
467
+ <Text type="body" color="secondary">
468
+ We believe the smallest shadows are the ones that matter most.
469
+ Turn an ordinary evening into something worth remembering.
470
+ </Text>
471
+ </VStack>
472
+ </Center>
473
+
474
+ <Grid columns={isMobile ? 1 : {minWidth: 200, max: 3}} gap={4}>
475
+ {products.map((p, i) => (
476
+ <Card key={p.name} padding={0} height="100%">
477
+ <VStack gap={0} style={styles.cardStack}>
478
+ <AspectRatio ratio={1}>
479
+ <ProductArt
480
+ hue={PRODUCT_HUES[i % PRODUCT_HUES.length]}
481
+ label={p.name}
482
+ />
483
+ </AspectRatio>
484
+ <div style={inlineStyles.cardBody}>
485
+ <VStack gap={2} hAlign="center" style={styles.cardStack}>
486
+ <HStack>
487
+ <Badge label={p.badge} variant={p.badgeVariant} />
488
+ </HStack>
489
+ <Heading level={2} style={styles.centerText}>
490
+ {p.name}
491
+ </Heading>
492
+ <Text
493
+ type="body"
494
+ color="secondary"
495
+ style={{
496
+ ...styles.cardDescription,
497
+ ...styles.centerText,
498
+ }}>
499
+ {p.description}
500
+ </Text>
501
+ <HStack gap={2} vAlign="center" hAlign="center">
502
+ <NumberInput
503
+ label="Quantity"
504
+ isLabelHidden
505
+ value={1}
506
+ onChange={() => {}}
507
+ min={1}
508
+ max={99}
509
+ size="sm"
510
+ style={styles.quantityInput}
511
+ />
512
+ <Button
513
+ label="Add to cart"
514
+ variant="secondary"
515
+ size="sm"
516
+ href="#/templates/theme-showcase"
517
+ style={styles.cartButton}
518
+ />
519
+ </HStack>
520
+ </VStack>
521
+ </div>
522
+ </VStack>
523
+ </Card>
524
+ ))}
525
+ </Grid>
526
+ </VStack>
527
+ </Section>
528
+ </VStack>
529
+ </div>
530
+ );
531
+ }
532
+
533
+ function CheckoutCard({isMobile}: {isMobile: boolean}) {
534
+ return (
535
+ <Card padding={5} style={styles.card}>
536
+ <VStack gap={4} style={styles.checkoutStack}>
537
+ <Heading level={2}>Checkout</Heading>
538
+
539
+ <VStack gap={3} style={styles.checkoutStack}>
540
+ <TextInput
541
+ label="Email"
542
+ placeholder="you@nocturne.shop"
543
+ value=""
544
+ onChange={() => {}}
545
+ size="lg"
546
+ />
547
+
548
+ <RadioList
549
+ label="Shipping method"
550
+ description="Delivery time may vary based on location and availability."
551
+ value="economy"
552
+ onChange={() => {}}>
553
+ <RadioListItem
554
+ value="economy"
555
+ label="Economy Shipping"
556
+ description="Delivered in 5–7 business days"
557
+ endContent={
558
+ <Text type="body" weight="bold" hasTabularNumbers>
559
+ $12.00
560
+ </Text>
561
+ }
562
+ />
563
+ <RadioListItem
564
+ value="standard"
565
+ label="Standard Shipping"
566
+ description="Delivered in 3–5 business days"
567
+ endContent={
568
+ <Text type="body" weight="bold" hasTabularNumbers>
569
+ $16.00
570
+ </Text>
571
+ }
572
+ />
573
+ <RadioListItem
574
+ value="express"
575
+ label="Express Shipping"
576
+ description="Delivered in 1–2 business days"
577
+ endContent={
578
+ <Text type="body" weight="bold" hasTabularNumbers>
579
+ $24.00
580
+ </Text>
581
+ }
582
+ />
583
+ </RadioList>
584
+
585
+ <VStack gap={2} style={styles.checkoutStack}>
586
+ <Text type="label" weight="bold">
587
+ Payment method
588
+ </Text>
589
+ <Grid columns={isMobile ? 1 : {minWidth: 70, max: 3}} gap={2}>
590
+ <SelectableCard
591
+ label="Pay with card"
592
+ isSelected={true}
593
+ onChange={() => {}}
594
+ padding={3}>
595
+ <VStack
596
+ gap={1}
597
+ hAlign="center"
598
+ style={styles.paymentCardContent}>
599
+ <CreditCard size={20} />
600
+ <Text type="supporting" weight="bold">
601
+ Card
602
+ </Text>
603
+ </VStack>
604
+ </SelectableCard>
605
+ <SelectableCard
606
+ label="Pay with Apple Pay"
607
+ isSelected={false}
608
+ onChange={() => {}}
609
+ padding={3}>
610
+ <VStack
611
+ gap={1}
612
+ hAlign="center"
613
+ style={styles.paymentCardContent}>
614
+ <Smartphone size={20} />
615
+ <Text type="supporting" weight="bold">
616
+ Apple Pay
617
+ </Text>
618
+ </VStack>
619
+ </SelectableCard>
620
+ <SelectableCard
621
+ label="Pay with Google Pay"
622
+ isSelected={false}
623
+ onChange={() => {}}
624
+ padding={3}>
625
+ <VStack
626
+ gap={1}
627
+ hAlign="center"
628
+ style={styles.paymentCardContent}>
629
+ <Wallet size={20} />
630
+ <Text type="supporting" weight="bold">
631
+ Google Pay
632
+ </Text>
633
+ </VStack>
634
+ </SelectableCard>
635
+ </Grid>
636
+ </VStack>
637
+
638
+ <TextInput
639
+ label="Card number"
640
+ placeholder="1234 1234 1234 1234"
641
+ value=""
642
+ onChange={() => {}}
643
+ startIcon={<CreditCard size={16} />}
644
+ size="lg"
645
+ />
646
+
647
+ <Grid columns={isMobile ? 1 : {minWidth: 90, max: 2}} gap={2}>
648
+ <TextInput
649
+ label="Expiry"
650
+ placeholder="MM / YY"
651
+ value=""
652
+ onChange={() => {}}
653
+ size="lg"
654
+ />
655
+ <TextInput
656
+ label="CVC"
657
+ placeholder="123"
658
+ value=""
659
+ onChange={() => {}}
660
+ size="lg"
661
+ />
662
+ </Grid>
663
+
664
+ <Selector
665
+ label="Country"
666
+ value="us"
667
+ onChange={() => {}}
668
+ size="lg"
669
+ options={[
670
+ {value: 'us', label: 'United States'},
671
+ {value: 'ca', label: 'Canada'},
672
+ {value: 'uk', label: 'United Kingdom'},
673
+ {value: 'de', label: 'Germany'},
674
+ {value: 'jp', label: 'Japan'},
675
+ {value: 'au', label: 'Australia'},
676
+ ]}
677
+ />
678
+ </VStack>
679
+
680
+ <CheckboxInput
681
+ label="Securely save my information for 1-click checkout"
682
+ description="Pay faster on Nocturne and everywhere Link is accepted."
683
+ value={true}
684
+ onChange={() => {}}
685
+ />
686
+
687
+ <Button
688
+ variant="primary"
689
+ size="lg"
690
+ label="Pay now"
691
+ icon={<Lock size={16} />}
692
+ />
693
+ </VStack>
694
+ </Card>
695
+ );
696
+ }
697
+
698
+ const SUGGESTED_QUESTIONS = [
699
+ 'Reschedule delivery',
700
+ 'Update shipping address',
701
+ 'Start a return',
702
+ ];
703
+
704
+ function ChatCard() {
705
+ return (
706
+ <Card padding={0} style={styles.chatCard}>
707
+ <HStack
708
+ hAlign="between"
709
+ vAlign="center"
710
+ gap={3}
711
+ style={styles.chatHeader}>
712
+ <Heading level={2}>Night Owl AI</Heading>
713
+
714
+ <HStack gap={1} vAlign="center">
715
+ <Button
716
+ variant="ghost"
717
+ size="sm"
718
+ isIconOnly
719
+ label="Export conversation"
720
+ tooltip="Export conversation"
721
+ icon={<Download size={16} />}
722
+ />
723
+ <Button
724
+ variant="ghost"
725
+ size="sm"
726
+ isIconOnly
727
+ label="Close chat"
728
+ tooltip="Close chat"
729
+ icon={<X size={16} />}
730
+ />
731
+ </HStack>
732
+ </HStack>
733
+
734
+ <Divider variant="subtle" />
735
+
736
+ <div style={inlineStyles.chatBody}>
737
+ <ChatMessageList>
738
+ <ChatSystemMessage>Today</ChatSystemMessage>
739
+
740
+ <ChatMessage sender="user">
741
+ <ChatMessageBubble variant="filled">
742
+ Where’s my order?
743
+ </ChatMessageBubble>
744
+ </ChatMessage>
745
+
746
+ <ChatMessage sender="assistant">
747
+ <VStack gap={3}>
748
+ <Text type="body">
749
+ Your order #1043 — the Moonphase Watch and Belfry Throw —
750
+ shipped this morning from the Aisle 3 warehouse and is currently
751
+ in transit with UPS. It’s on track to arrive at your address by
752
+ end of day tomorrow.
753
+ </Text>
754
+ <Text type="body">
755
+ Let me know if you’d like to reschedule the delivery, redirect
756
+ it to a pickup point, or start a return once it arrives.
757
+ </Text>
758
+ </VStack>
759
+ </ChatMessage>
760
+
761
+ <ChatMessage sender="user">
762
+ <ChatMessageBubble variant="filled">
763
+ Can you show me the full details?
764
+ </ChatMessageBubble>
765
+ </ChatMessage>
766
+
767
+ <ChatMessage sender="assistant">
768
+ <VStack gap={3}>
769
+ <Text type="body">Here’s everything I have on order #1043:</Text>
770
+ <Card padding={3}>
771
+ <VStack gap={1}>
772
+ <Item
773
+ label="Items"
774
+ description="Moonphase Watch · Belfry Throw"
775
+ endContent={
776
+ <Text type="body" weight="bold" hasTabularNumbers>
777
+ $248
778
+ </Text>
779
+ }
780
+ />
781
+ <Item
782
+ label="Shipping"
783
+ description="UPS Ground"
784
+ endContent={
785
+ <Text type="body" weight="bold" hasTabularNumbers>
786
+ $12
787
+ </Text>
788
+ }
789
+ />
790
+ <Item
791
+ label="Estimated arrival"
792
+ description="Tomorrow by 8pm"
793
+ endContent={<Badge variant="green" label="On time" />}
794
+ />
795
+ <Item
796
+ label="Tracking"
797
+ description="UPS 1Z 999 AA1 0123 4567 84"
798
+ endContent={<Link href="#/templates/theme-showcase">Track →</Link>}
799
+ />
800
+ </VStack>
801
+ </Card>
802
+ </VStack>
803
+ </ChatMessage>
804
+ </ChatMessageList>
805
+ </div>
806
+
807
+ <div style={inlineStyles.chatSuggestions}>
808
+ <HStack gap={1} hAlign="center" wrap="wrap">
809
+ {SUGGESTED_QUESTIONS.map(question => (
810
+ <Button
811
+ key={question}
812
+ variant="secondary"
813
+ size="sm"
814
+ label={question}
815
+ />
816
+ ))}
817
+ </HStack>
818
+ </div>
819
+
820
+ <div style={inlineStyles.chatComposer}>
821
+ <ChatComposer
822
+ value=""
823
+ onChange={() => {}}
824
+ onSubmit={() => {}}
825
+ placeholder="Ask Night Owl…"
826
+ footerActions={
827
+ <Button
828
+ variant="ghost"
829
+ size="md"
830
+ isIconOnly
831
+ label="Attach"
832
+ tooltip="Attach"
833
+ icon={<Plus size={16} />}
834
+ />
835
+ }
836
+ sendActions={
837
+ <Button
838
+ variant="ghost"
839
+ size="md"
840
+ isIconOnly
841
+ label="Voice input"
842
+ tooltip="Voice input"
843
+ icon={<Mic size={16} />}
844
+ />
845
+ }
846
+ />
847
+ </div>
848
+ </Card>
849
+ );
850
+ }
851
+
852
+ interface ActivityRow {
853
+ id: string;
854
+ icon: ReactNode;
855
+ label: string;
856
+ detail: string;
857
+ time: string;
858
+ amount: number;
859
+ }
860
+
861
+ const ACTIVITY: ActivityRow[] = [
862
+ {
863
+ id: '1',
864
+ icon: <ShoppingBag size={16} />,
865
+ label: 'Order #1043',
866
+ detail: 'Placed · 1:59 pm',
867
+ time: '1:59 pm',
868
+ amount: 248,
869
+ },
870
+ {
871
+ id: '2',
872
+ icon: <Banknote size={16} />,
873
+ label: 'Order #1041',
874
+ detail: 'Refunded · 12:40 pm',
875
+ time: '12:40 pm',
876
+ amount: -89,
877
+ },
878
+ {
879
+ id: '3',
880
+ icon: <ShoppingBag size={16} />,
881
+ label: 'Order #1040',
882
+ detail: 'Placed · 10:30 am',
883
+ time: '10:30 am',
884
+ amount: 156,
885
+ },
886
+ {
887
+ id: '4',
888
+ icon: <ShoppingBag size={16} />,
889
+ label: 'Order #1038',
890
+ detail: 'Placed · 9:11 am',
891
+ time: '9:11 am',
892
+ amount: 412,
893
+ },
894
+ {
895
+ id: '5',
896
+ icon: <ShoppingBag size={16} />,
897
+ label: 'Order #1037',
898
+ detail: 'Placed · 8:42 am',
899
+ time: '8:42 am',
900
+ amount: 95,
901
+ },
902
+ ];
903
+
904
+ function formatAmount(amount: number): string {
905
+ const sign = amount < 0 ? '−' : '+';
906
+ return sign + '$' + Math.abs(amount).toLocaleString();
907
+ }
908
+
909
+ function LatestActivityCard({isMobile}: {isMobile: boolean}) {
910
+ return (
911
+ <Card padding={5} style={styles.activityCard}>
912
+ <VStack gap={4} style={styles.activityCardStack}>
913
+ <Heading level={2}>Revenue</Heading>
914
+
915
+ <Grid columns={isMobile ? 1 : 2} gap={3}>
916
+ <VStack gap={0}>
917
+ <Text type="display-3" weight="bold" hasTabularNumbers>
918
+ 18K
919
+ </Text>
920
+ <Text type="supporting" color="secondary">
921
+ Monthly revenue
922
+ </Text>
923
+ </VStack>
924
+ <VStack gap={0}>
925
+ <Text type="display-3" weight="bold" hasTabularNumbers>
926
+ +12%
927
+ </Text>
928
+ <Text type="supporting" color="secondary">
929
+ Order growth
930
+ </Text>
931
+ </VStack>
932
+ </Grid>
933
+
934
+ <Divider variant="subtle" />
935
+
936
+ <HStack hAlign="between" vAlign="center">
937
+ <Heading level={3}>Activity</Heading>
938
+ <Link href="#/templates/theme-showcase">See all</Link>
939
+ </HStack>
940
+
941
+ <VStack gap={1} style={styles.activityListFade}>
942
+ {ACTIVITY.map(item => (
943
+ <Item
944
+ key={item.id}
945
+ startContent={
946
+ <div style={inlineStyles.activityIcon} aria-hidden="true">
947
+ {item.icon}
948
+ </div>
949
+ }
950
+ label={item.label}
951
+ description={item.detail}
952
+ endContent={
953
+ <Text
954
+ type="body"
955
+ weight="bold"
956
+ hasTabularNumbers
957
+ color={item.amount < 0 ? 'secondary' : 'primary'}>
958
+ {formatAmount(item.amount)}
959
+ </Text>
960
+ }
961
+ href="#/templates/theme-showcase"
962
+ />
963
+ ))}
964
+ </VStack>
965
+ </VStack>
966
+ </Card>
967
+ );
968
+ }
969
+
970
+ type TagSpec = {label: string; variant: ShowcaseBadgeVariant};
971
+
972
+ export interface InventoryRow extends Record<string, unknown> {
973
+ id: string;
974
+ name: string;
975
+ meta: string;
976
+ available: number;
977
+ location: string;
978
+ tags: TagSpec[];
979
+ hue: string;
980
+ selected: boolean;
981
+ }
982
+
983
+ const DEFAULT_INVENTORY: InventoryRow[] = [
984
+ {
985
+ id: 'a',
986
+ name: 'Moonphase Watch',
987
+ meta: 'Steel case, moonphase dial',
988
+ available: 42,
989
+ location: 'Aisle 3',
990
+ tags: [{label: 'New', variant: 'blue'}],
991
+ hue: 'var(--dracula-purple)',
992
+ selected: false,
993
+ },
994
+ {
995
+ id: 'b',
996
+ name: 'Night-Owl Headphones',
997
+ meta: 'ANC, 30hr battery',
998
+ available: 128,
999
+ location: 'Aisle 1',
1000
+ tags: [{label: 'Popular', variant: 'green'}],
1001
+ hue: 'var(--dracula-cyan)',
1002
+ selected: true,
1003
+ },
1004
+ {
1005
+ id: 'c',
1006
+ name: 'Coven Canvas Backpack',
1007
+ meta: 'Waxed canvas, 25L',
1008
+ available: 63,
1009
+ location: 'Aisle 2',
1010
+ tags: [{label: 'Limited', variant: 'yellow'}],
1011
+ hue: 'var(--dracula-yellow)',
1012
+ selected: false,
1013
+ },
1014
+ {
1015
+ id: 'd',
1016
+ name: 'Night Market Wallet',
1017
+ meta: 'Full-grain, RFID blocking',
1018
+ available: 15,
1019
+ location: 'Aisle 4',
1020
+ tags: [{label: 'Leather', variant: 'yellow'}],
1021
+ hue: 'var(--dracula-orange)',
1022
+ selected: true,
1023
+ },
1024
+ {
1025
+ id: 'e',
1026
+ name: 'Midnight Tumbler',
1027
+ meta: 'Vacuum insulated, 16oz',
1028
+ available: 87,
1029
+ location: 'Aisle 5',
1030
+ tags: [{label: 'Drinkware', variant: 'green'}],
1031
+ hue: 'var(--dracula-pink)',
1032
+ selected: false,
1033
+ },
1034
+ {
1035
+ id: 'f',
1036
+ name: 'Belfry Throw',
1037
+ meta: 'Heavyweight, oat',
1038
+ available: 24,
1039
+ location: 'Aisle 6',
1040
+ tags: [{label: 'Home', variant: 'orange'}],
1041
+ hue: 'var(--dracula-green)',
1042
+ selected: true,
1043
+ },
1044
+ ];
1045
+
1046
+ const LOW_STOCK_THRESHOLD = 25;
1047
+
1048
+ function SelectCell({row}: {row: InventoryRow}) {
1049
+ return (
1050
+ <CheckboxInput
1051
+ label={'Select ' + row.name}
1052
+ isLabelHidden
1053
+ value={row.selected}
1054
+ onChange={() => {}}
1055
+ />
1056
+ );
1057
+ }
1058
+
1059
+ function ItemCell({row}: {row: InventoryRow}) {
1060
+ return (
1061
+ <HStack gap={3} vAlign="center">
1062
+ <ThumbSwatch hue={row.hue} label={row.name} />
1063
+ <VStack gap={0} style={{minWidth: 0}}>
1064
+ <Text type="body" weight="bold">
1065
+ {row.name}
1066
+ </Text>
1067
+ <Text type="supporting" color="secondary">
1068
+ {row.meta}
1069
+ </Text>
1070
+ </VStack>
1071
+ </HStack>
1072
+ );
1073
+ }
1074
+
1075
+ function TagsCell({row}: {row: InventoryRow}) {
1076
+ return (
1077
+ <HStack gap={1} wrap="wrap" hAlign="end">
1078
+ {row.tags.map(tag => (
1079
+ <Badge key={tag.label} label={tag.label} variant={tag.variant} />
1080
+ ))}
1081
+ </HStack>
1082
+ );
1083
+ }
1084
+
1085
+ function ActionsCell() {
1086
+ return (
1087
+ <MoreMenu
1088
+ label="Row actions"
1089
+ size="sm"
1090
+ items={[
1091
+ {label: 'Edit'},
1092
+ {label: 'Duplicate'},
1093
+ {label: 'Move to…'},
1094
+ {type: 'divider'},
1095
+ {label: 'Delete'},
1096
+ ]}
1097
+ />
1098
+ );
1099
+ }
1100
+
1101
+ function InventoryCard({inventory}: {inventory: InventoryRow[]}) {
1102
+ const lowStockCount = inventory.filter(
1103
+ row => row.available < LOW_STOCK_THRESHOLD,
1104
+ ).length;
1105
+ return (
1106
+ <Card padding={0} style={styles.inventoryCard}>
1107
+ <HStack hAlign="between" vAlign="center" style={styles.inventoryHeader}>
1108
+ <Heading level={2}>Inventory</Heading>
1109
+ <Button
1110
+ label="Add item"
1111
+ variant="primary"
1112
+ size="sm"
1113
+ icon={<Plus size={16} />}
1114
+ />
1115
+ </HStack>
1116
+
1117
+ <Divider variant="subtle" />
1118
+
1119
+ <HStack
1120
+ gap={3}
1121
+ vAlign="center"
1122
+ hAlign="between"
1123
+ style={styles.inventoryFilterRow}>
1124
+ <HStack gap={2} vAlign="center" style={{flex: 1, minWidth: 0}}>
1125
+ <TextInput
1126
+ label="Search inventory"
1127
+ isLabelHidden
1128
+ placeholder="Type and hit enter…"
1129
+ value=""
1130
+ onChange={() => {}}
1131
+ startIcon={<Search size={16} />}
1132
+ style={styles.searchInput}
1133
+ />
1134
+ <OverflowList
1135
+ gap={2}
1136
+ overflowRenderer={() => (
1137
+ <Button
1138
+ label="Filters"
1139
+ variant="ghost"
1140
+ size="sm"
1141
+ icon={<Tag size={16} />}
1142
+ />
1143
+ )}>
1144
+ <Selector
1145
+ label="Categories"
1146
+ isLabelHidden
1147
+ placeholder="Categories"
1148
+ size="sm"
1149
+ startIcon={<Folder size={16} />}
1150
+ value={undefined}
1151
+ onChange={() => {}}
1152
+ options={['Wearables', 'Audio', 'Bags', 'Drinkware', 'Home']}
1153
+ />
1154
+ <Selector
1155
+ label="Locations"
1156
+ isLabelHidden
1157
+ placeholder="Locations"
1158
+ size="sm"
1159
+ startIcon={<MapPin size={16} />}
1160
+ value={undefined}
1161
+ onChange={() => {}}
1162
+ options={[
1163
+ 'Aisle 1',
1164
+ 'Aisle 2',
1165
+ 'Aisle 3',
1166
+ 'Aisle 4',
1167
+ 'Aisle 5',
1168
+ 'Aisle 6',
1169
+ ]}
1170
+ />
1171
+ <Selector
1172
+ label="Tags"
1173
+ isLabelHidden
1174
+ placeholder="Tags"
1175
+ size="sm"
1176
+ startIcon={<Tag size={16} />}
1177
+ value={undefined}
1178
+ onChange={() => {}}
1179
+ options={[
1180
+ 'New',
1181
+ 'Popular',
1182
+ 'Limited',
1183
+ 'Leather',
1184
+ 'Drinkware',
1185
+ 'Home',
1186
+ ]}
1187
+ />
1188
+ </OverflowList>
1189
+ </HStack>
1190
+ <HStack gap={1} vAlign="center">
1191
+ <Button
1192
+ variant="ghost"
1193
+ size="sm"
1194
+ isIconOnly
1195
+ label="List view"
1196
+ tooltip="List view"
1197
+ icon={<List size={18} />}
1198
+ />
1199
+ <Button
1200
+ variant="ghost"
1201
+ size="sm"
1202
+ isIconOnly
1203
+ label="Grid view"
1204
+ tooltip="Grid view"
1205
+ icon={<LayoutGrid size={18} />}
1206
+ />
1207
+ </HStack>
1208
+ </HStack>
1209
+
1210
+ {lowStockCount > 0 && (
1211
+ <div style={inlineStyles.inventoryBannerWrap}>
1212
+ <Banner
1213
+ status="warning"
1214
+ title={lowStockCount + ' items are running low'}
1215
+ />
1216
+ </div>
1217
+ )}
1218
+
1219
+ <div style={styles.inventoryTableWrap}>
1220
+ <Table<InventoryRow>
1221
+ data={inventory}
1222
+ columns={[
1223
+ {
1224
+ key: 'select',
1225
+ header: '',
1226
+ // Wide enough that the control + the theme's cell padding (up to
1227
+ // --spacing-4 = 16px/side on spacious density) fit inside the cell,
1228
+ // so the control's hover background doesn't overflow toward the
1229
+ // card's clipped (rounded) edge on larger-padding themes.
1230
+ width: pixel(64),
1231
+ renderCell: row => <SelectCell row={row} />,
1232
+ },
1233
+ {
1234
+ key: 'item',
1235
+ header: 'Item',
1236
+ // Lower min-width (default 120) so the table fits its container on
1237
+ // larger-spacing themes instead of overflowing the actions column.
1238
+ width: proportional(3, {minWidth: 80}),
1239
+ renderCell: row => <ItemCell row={row} />,
1240
+ },
1241
+ {
1242
+ key: 'available',
1243
+ header: 'Available',
1244
+ width: pixel(100),
1245
+ renderCell: row => (
1246
+ <Text type="body" hasTabularNumbers>
1247
+ {row.available}
1248
+ </Text>
1249
+ ),
1250
+ },
1251
+ {
1252
+ key: 'location',
1253
+ header: 'Location',
1254
+ width: pixel(100),
1255
+ renderCell: row => <Text type="body">{row.location}</Text>,
1256
+ },
1257
+ {
1258
+ key: 'tags',
1259
+ header: 'Tags',
1260
+ width: proportional(2, {minWidth: 80}),
1261
+ align: 'end',
1262
+ renderCell: row => <TagsCell row={row} />,
1263
+ },
1264
+ {
1265
+ key: 'actions',
1266
+ header: '',
1267
+ // Match the select column: fit the sm more-menu button + cell
1268
+ // padding so its hover background stays clear of the card's
1269
+ // clipped rounded edge across themes.
1270
+ width: pixel(64),
1271
+ align: 'end',
1272
+ renderCell: () => <ActionsCell />,
1273
+ },
1274
+ ]}
1275
+ density="spacious"
1276
+ dividers="rows"
1277
+ hasHover
1278
+ />
1279
+ </div>
1280
+ </Card>
1281
+ );
1282
+ }