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: 'Order Detail',
4
+ description: 'Items, invoice, timeline, and side panel',
5
+ category: 'commerce',
6
+ };
@@ -0,0 +1,695 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {useState} from 'react';
4
+ import {useMediaQuery} from '@astryxdesign/core/hooks';
5
+ import {
6
+ Layout,
7
+ LayoutHeader,
8
+ LayoutContent,
9
+ LayoutPanel,
10
+ VStack,
11
+ HStack,
12
+ StackItem,
13
+ Card,
14
+ Section,
15
+ } from '@astryxdesign/core/Layout';
16
+ import {Text, Heading} from '@astryxdesign/core/Text';
17
+ import {Badge} from '@astryxdesign/core/Badge';
18
+ import {Avatar} from '@astryxdesign/core/Avatar';
19
+ import {Button} from '@astryxdesign/core/Button';
20
+ import {TabList, Tab, TabMenu} from '@astryxdesign/core/TabList';
21
+ import {Divider} from '@astryxdesign/core/Divider';
22
+ import {Link} from '@astryxdesign/core/Link';
23
+ import {List, ListItem} from '@astryxdesign/core/List';
24
+ import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
25
+ import {ProgressBar} from '@astryxdesign/core/ProgressBar';
26
+ import {Collapsible} from '@astryxdesign/core/Collapsible';
27
+ import {Icon} from '@astryxdesign/core/Icon';
28
+ import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
29
+ import {
30
+ Calendar,
31
+ Flag,
32
+ Filter,
33
+ ThumbsUp,
34
+ Heart,
35
+ SquarePen,
36
+ ArrowLeft,
37
+ Columns2,
38
+ } from 'lucide-react';
39
+
40
+ // ─── Styles ─────────────────────────────────────────────────────────────────
41
+ import type {CSSProperties} from 'react';
42
+
43
+ // The only custom CSS in this template is small optical-alignment negative
44
+ // margins: LayoutHeader/TabList have no edge-dock prop (#2622) and List
45
+ // has no "bleed to container edge" prop (#2626). Everything else uses props.
46
+ // Plain inline styles — no StyleX compiler required.
47
+
48
+ // Bleed the tab bar to the header's content edges so the active-tab underline
49
+ // meets the header divider. No edge-dock prop on TabList (#2622).
50
+ const tabsRow: CSSProperties = {
51
+ marginInline: -12,
52
+ marginBottom: -16,
53
+ marginTop: 12,
54
+ };
55
+ // Pull the list items' inner padding back so their content optically aligns
56
+ // with the section heading above (ListItem insets content by ~8px). No
57
+ // edge/inset prop on List (#2626).
58
+ const itemsList: CSSProperties = {
59
+ marginInline: -8,
60
+ };
61
+
62
+ // ─── Product data ───────────────────────────────────────────────────────────
63
+ const PRODUCTS = [
64
+ {
65
+ name: 'Midnight Mug',
66
+ details: 'Glaze: Moonlight\nFinish: Matte',
67
+ price: 89.0,
68
+ qty: 1,
69
+ },
70
+ {
71
+ name: 'Cinder Bowl',
72
+ details: 'Glaze: Sage\nSize: 6 in',
73
+ price: 42.0,
74
+ qty: 2,
75
+ },
76
+ {
77
+ name: 'Crypt Cup',
78
+ details: 'Glaze: Batwing\nSize: 14 in',
79
+ price: 65.0,
80
+ qty: 1,
81
+ },
82
+ {
83
+ name: 'Dusk Plate Set',
84
+ details: 'Glaze: Charcoal\nCapacity: 3 oz',
85
+ price: 34.0,
86
+ qty: 3,
87
+ },
88
+ {
89
+ name: 'Coven Salad Bowl',
90
+ details: 'Glaze: Moonlight\nHeight: 8 in',
91
+ price: 78.0,
92
+ qty: 1,
93
+ },
94
+ ];
95
+
96
+ // Dracula accent per product row.
97
+ const PRODUCT_HUES = [
98
+ 'var(--dracula-purple)',
99
+ 'var(--dracula-cyan)',
100
+ 'var(--dracula-pink)',
101
+ 'var(--dracula-yellow)',
102
+ 'var(--dracula-green)',
103
+ ];
104
+
105
+ const sigilClip: CSSProperties = {
106
+ width: 40,
107
+ height: 40,
108
+ overflow: 'clip',
109
+ flexShrink: 0,
110
+ };
111
+
112
+ function ProductSigil({name, index}: {name: string; index: number}) {
113
+ const hue = PRODUCT_HUES[index % PRODUCT_HUES.length];
114
+ return (
115
+ <Card padding={0} style={sigilClip}>
116
+ <svg
117
+ viewBox="0 0 40 40"
118
+ width={40}
119
+ height={40}
120
+ role="img"
121
+ aria-label={name}>
122
+ <rect width="40" height="40" fill="var(--dracula-bg-light)" />
123
+ <circle cx={28} cy={11} r={7} fill={hue} />
124
+ <circle
125
+ cx={25}
126
+ cy={9}
127
+ r={5.5}
128
+ fill="var(--dracula-bg-light)"
129
+ opacity={0.5}
130
+ />
131
+ <path
132
+ d="M0 28 Q10 22 20 26 T40 24 V40 H0 Z"
133
+ fill="var(--dracula-current-line)"
134
+ />
135
+ </svg>
136
+ </Card>
137
+ );
138
+ }
139
+
140
+ const SUBTOTAL = PRODUCTS.reduce((sum, p) => sum + p.price * p.qty, 0);
141
+ const DISCOUNT = 15.0;
142
+ const SHIPPING = 0;
143
+ const TAX_RATE = 0.0825;
144
+ const TAX = Math.round((SUBTOTAL - DISCOUNT) * TAX_RATE * 100) / 100;
145
+ const TOTAL = SUBTOTAL - DISCOUNT + SHIPPING + TAX;
146
+ const fmt = (n: number) => `$${n.toFixed(2)}`;
147
+
148
+ // ─── Activity data ──────────────────────────────────────────────────────────
149
+ const ACTIVITY = [
150
+ {
151
+ type: 'event' as const,
152
+ user: 'Jane Doe',
153
+ text: 'placed order #1001',
154
+ reactions: 2,
155
+ time: 'Feb 23 at 9:12 AM',
156
+ },
157
+ {
158
+ type: 'comment' as const,
159
+ user: 'Alex Rivera',
160
+ text: "Customer requested gift wrapping for the mug & plate set. I've added a note to the packing slip — warehouse team should wrap in recycled kraft paper.",
161
+ reactions: 3,
162
+ time: 'Feb 23 at 10:45 AM',
163
+ },
164
+ {
165
+ type: 'update' as const,
166
+ user: 'System',
167
+ text: 'has several information changes',
168
+ time: 'Feb 23 at 11:30 AM',
169
+ changes: [
170
+ 'Payment verified via Visa ...7482',
171
+ 'Fraud check passed — low risk',
172
+ ],
173
+ },
174
+ {
175
+ type: 'event' as const,
176
+ user: 'Alex Rivera',
177
+ text: 'marked order as ready for fulfillment',
178
+ reactions: 1,
179
+ time: 'Feb 23 at 2:15 PM',
180
+ },
181
+ ];
182
+
183
+ // ─── Bullet separator ───────────────────────────────────────────────────────
184
+ function Bullet() {
185
+ return (
186
+ <Text type="supporting" color="secondary">
187
+ {'・'}
188
+ </Text>
189
+ );
190
+ }
191
+
192
+ // ─── Page Header ────────────────────────────────────────────────────────────
193
+ function PageHeader({
194
+ activeTab,
195
+ onTabChange,
196
+ isPanelOpen,
197
+ onTogglePanel,
198
+ isNarrow,
199
+ }: {
200
+ activeTab: string;
201
+ onTabChange: (v: string) => void;
202
+ isPanelOpen: boolean;
203
+ onTogglePanel: () => void;
204
+ isNarrow: boolean;
205
+ }) {
206
+ return (
207
+ <LayoutHeader hasDivider padding={4}>
208
+ <VStack gap={3}>
209
+ <HStack gap={4} vAlign="start">
210
+ <StackItem size="fill">
211
+ <VStack gap={0}>
212
+ <Link href="#/templates/detail-page" color="secondary">
213
+ <HStack gap={1} vAlign="center">
214
+ <Icon icon={ArrowLeft} size="sm" color="inherit" />
215
+ All orders
216
+ </HStack>
217
+ </Link>
218
+ <VStack gap={0}>
219
+ <Heading level={1} maxLines={1}>
220
+ #1001
221
+ </Heading>
222
+ {/* Metadata wraps to multiple lines on narrow screens (rather
223
+ than collapsing items behind a "+N more" overflow). */}
224
+ <HStack gap={1} vAlign="center" wrap="wrap">
225
+ <Text type="supporting" color="secondary" hasTabularNumbers>
226
+ {PRODUCTS.length} ordered items
227
+ </Text>
228
+ <HStack gap={1} vAlign="center">
229
+ <Bullet />
230
+ <Avatar name="Jane Doe" size="sm" />
231
+ <Text type="supporting" color="secondary" maxLines={1}>
232
+ Jane Doe
233
+ </Text>
234
+ </HStack>
235
+ <HStack gap={1} vAlign="center">
236
+ <Bullet />
237
+ <Badge variant="warning" label="Unfulfilled" />
238
+ </HStack>
239
+ <HStack gap={1} vAlign="center">
240
+ <Bullet />
241
+ <Icon icon={Calendar} size="sm" color="secondary" />
242
+ <Text
243
+ type="supporting"
244
+ color="secondary"
245
+ maxLines={1}
246
+ hasTabularNumbers>
247
+ 02/23/2026
248
+ </Text>
249
+ </HStack>
250
+ <HStack gap={1} vAlign="center">
251
+ <Bullet />
252
+ <Icon icon={Flag} size="sm" color="secondary" />
253
+ <Text type="supporting" color="secondary" maxLines={1}>
254
+ Needs attention
255
+ </Text>
256
+ </HStack>
257
+ <HStack gap={1} vAlign="center">
258
+ <Bullet />
259
+ <Link href="#/templates/detail-page" color="secondary">
260
+ See all
261
+ </Link>
262
+ </HStack>
263
+ </HStack>
264
+ </VStack>
265
+ </VStack>
266
+ </StackItem>
267
+ {!isNarrow && (
268
+ <HStack gap={2}>
269
+ <Button label="Restock" variant="secondary" />
270
+ <Button label="Edit" variant="secondary" />
271
+ </HStack>
272
+ )}
273
+ </HStack>
274
+
275
+ {/* Mobile: actions drop below the metadata as a full-width row. The
276
+ VStack hAlign="stretch" wrapper is the full-width-button
277
+ workaround — Button has no full-width prop (#2600). */}
278
+ {isNarrow && (
279
+ <HStack gap={2}>
280
+ <StackItem size="fill">
281
+ <VStack hAlign="stretch">
282
+ <Button label="Restock" variant="secondary" />
283
+ </VStack>
284
+ </StackItem>
285
+ <StackItem size="fill">
286
+ <VStack hAlign="stretch">
287
+ <Button label="Edit" variant="secondary" />
288
+ </VStack>
289
+ </StackItem>
290
+ </HStack>
291
+ )}
292
+
293
+ <HStack vAlign="center" style={tabsRow}>
294
+ <StackItem size="fill">
295
+ <TabList value={activeTab} onChange={onTabChange} size="lg">
296
+ <Tab value="details" label="Details" />
297
+ <Tab value="invoices" label="Invoices" />
298
+ <Tab value="timeline" label="Timeline" />
299
+ <TabMenu
300
+ label="More"
301
+ options={[
302
+ {value: 'customer', label: 'Customer'},
303
+ {value: 'analysis', label: 'Analysis'},
304
+ ]}
305
+ />
306
+ </TabList>
307
+ </StackItem>
308
+ <Button
309
+ label={isPanelOpen ? 'Hide panel' : 'Show panel'}
310
+ variant="ghost"
311
+ size="md"
312
+ icon={<Icon icon={Columns2} size="sm" />}
313
+ isIconOnly
314
+ onClick={onTogglePanel}
315
+ />
316
+ </HStack>
317
+ </VStack>
318
+ </LayoutHeader>
319
+ );
320
+ }
321
+
322
+ // ─── Items Card ─────────────────────────────────────────────────────────────
323
+ function ItemsCard() {
324
+ return (
325
+ <Section>
326
+ <VStack gap={4}>
327
+ <HStack vAlign="center" gap={2} wrap="wrap">
328
+ <StackItem size="fill">
329
+ <HStack gap={2} vAlign="center">
330
+ <Heading level={2}>Items</Heading>
331
+ <Badge variant="warning" label="Unfulfilled" />
332
+ </HStack>
333
+ </StackItem>
334
+ <HStack gap={2}>
335
+ <Button label="Fulfill item" variant="ghost" />
336
+ <Button label="Create label" variant="secondary" />
337
+ </HStack>
338
+ </HStack>
339
+
340
+ <List density="spacious" style={itemsList}>
341
+ {PRODUCTS.map((product, i) => (
342
+ <ListItem
343
+ key={i}
344
+ label={product.name}
345
+ description={
346
+ <VStack gap={0}>
347
+ {product.details.split('\n').map((line, j) => (
348
+ <Text
349
+ key={j}
350
+ type="supporting"
351
+ color="secondary"
352
+ hasTabularNumbers>
353
+ {line}
354
+ </Text>
355
+ ))}
356
+ </VStack>
357
+ }
358
+ onClick={() => {}}
359
+ startContent={
360
+ <ProductSigil name={product.name} index={i} />
361
+ }
362
+ endContent={
363
+ <VStack gap={0} hAlign="end">
364
+ <Text
365
+ type="body"
366
+ weight="bold"
367
+ maxLines={1}
368
+ hasTabularNumbers>
369
+ {fmt(product.price * product.qty)}
370
+ </Text>
371
+ <Text
372
+ type="supporting"
373
+ color="secondary"
374
+ maxLines={1}
375
+ hasTabularNumbers>
376
+ {fmt(product.price)} {'×'} {product.qty}
377
+ </Text>
378
+ </VStack>
379
+ }
380
+ />
381
+ ))}
382
+ </List>
383
+ </VStack>
384
+ </Section>
385
+ );
386
+ }
387
+
388
+ // ─── Invoice Card ───────────────────────────────────────────────────────────
389
+ function InvoiceCard() {
390
+ return (
391
+ <Section>
392
+ <VStack gap={4}>
393
+ <HStack vAlign="center" gap={2} wrap="wrap">
394
+ <StackItem size="fill">
395
+ <HStack gap={2} vAlign="center">
396
+ <Heading level={2}>Invoice</Heading>
397
+ <Badge variant="success" label="Paid" />
398
+ </HStack>
399
+ </StackItem>
400
+ <HStack gap={2}>
401
+ <Button label="Refund" variant="ghost" />
402
+ <Button label="Send Invoice" variant="secondary" />
403
+ </HStack>
404
+ </HStack>
405
+
406
+ <MetadataList>
407
+ <MetadataListItem label="Subtotal">
408
+ <HStack>
409
+ <StackItem size="fill">
410
+ <Text type="body" hasTabularNumbers>
411
+ {PRODUCTS.length} items
412
+ </Text>
413
+ </StackItem>
414
+ <Text type="body" hasTabularNumbers>
415
+ {fmt(SUBTOTAL)}
416
+ </Text>
417
+ </HStack>
418
+ </MetadataListItem>
419
+ <MetadataListItem label="Discount">
420
+ <HStack>
421
+ <StackItem size="fill">
422
+ <Text type="body">New customer code: NEW15</Text>
423
+ </StackItem>
424
+ <Text type="body" hasTabularNumbers>
425
+ – {fmt(DISCOUNT)}
426
+ </Text>
427
+ </HStack>
428
+ </MetadataListItem>
429
+ <MetadataListItem label="Shipping">
430
+ <HStack>
431
+ <StackItem size="fill">
432
+ <Text type="body" hasTabularNumbers>
433
+ Free shipping (0.0lbs) USPS
434
+ </Text>
435
+ </StackItem>
436
+ <Text type="body" hasTabularNumbers>
437
+ {fmt(SHIPPING)}
438
+ </Text>
439
+ </HStack>
440
+ </MetadataListItem>
441
+ <MetadataListItem label="Tax">
442
+ <HStack>
443
+ <StackItem size="fill">
444
+ <Text type="body" hasTabularNumbers>
445
+ Sales tax (8.25%)
446
+ </Text>
447
+ </StackItem>
448
+ <Text type="body" hasTabularNumbers>
449
+ {fmt(TAX)}
450
+ </Text>
451
+ </HStack>
452
+ </MetadataListItem>
453
+ <MetadataListItem label="Total">
454
+ <HStack>
455
+ <StackItem size="fill" />
456
+ <Text type="body" weight="bold" hasTabularNumbers>
457
+ {fmt(TOTAL)}
458
+ </Text>
459
+ </HStack>
460
+ </MetadataListItem>
461
+ </MetadataList>
462
+
463
+ <Divider />
464
+
465
+ <MetadataList>
466
+ <MetadataListItem label="Paid by customer">
467
+ <HStack>
468
+ <StackItem size="fill">
469
+ <Text type="body" hasTabularNumbers>
470
+ Visa ...7482
471
+ </Text>
472
+ </StackItem>
473
+ <Text type="body" hasTabularNumbers>
474
+ {fmt(TOTAL)}
475
+ </Text>
476
+ </HStack>
477
+ </MetadataListItem>
478
+ </MetadataList>
479
+ </VStack>
480
+ </Section>
481
+ );
482
+ }
483
+
484
+ // ─── Timeline ───────────────────────────────────────────────────────────────
485
+ function TimelineSection() {
486
+ return (
487
+ <Section>
488
+ <VStack gap={4}>
489
+ <HStack vAlign="center">
490
+ <StackItem size="fill">
491
+ <Heading level={2}>Timeline</Heading>
492
+ </StackItem>
493
+ <Button
494
+ label="Filters"
495
+ variant="ghost"
496
+ icon={<Icon icon={Filter} />}
497
+ isIconOnly
498
+ />
499
+ </HStack>
500
+
501
+ <VStack gap={4}>
502
+ {ACTIVITY.map((item, i) => (
503
+ <VStack key={i} gap={2}>
504
+ <HStack gap={3} vAlign="start">
505
+ <Avatar name={item.user} size="md" />
506
+ <StackItem size="fill">
507
+ <VStack gap={2}>
508
+ <Card variant="muted" padding={3}>
509
+ <VStack gap={1}>
510
+ <Text type="body" weight="bold">
511
+ {item.user}
512
+ </Text>
513
+ <Text type="body">{item.text}</Text>
514
+ {item.changes && (
515
+ <VStack gap={1}>
516
+ {item.changes.map((change, j) => (
517
+ <HStack key={j} gap={2} vAlign="center">
518
+ <Icon
519
+ icon={SquarePen}
520
+ size="sm"
521
+ color="secondary"
522
+ />
523
+ <Text type="supporting" color="secondary">
524
+ {change}
525
+ </Text>
526
+ </HStack>
527
+ ))}
528
+ </VStack>
529
+ )}
530
+ </VStack>
531
+ </Card>
532
+ <HStack gap={3} vAlign="center">
533
+ <HStack gap={1} vAlign="center">
534
+ <Icon
535
+ icon={ThumbsUp}
536
+ size="xsm"
537
+ color="secondary"
538
+ />
539
+ <Icon icon={Heart} size="xsm" color="secondary" />
540
+ <Text
541
+ type="supporting"
542
+ color="secondary"
543
+ hasTabularNumbers>
544
+ {item.reactions}
545
+ </Text>
546
+ </HStack>
547
+ <Text type="supporting" color="secondary">
548
+ Like
549
+ </Text>
550
+ <Bullet />
551
+ <Text type="supporting" color="secondary">
552
+ Reply
553
+ </Text>
554
+ <Bullet />
555
+ <Text
556
+ type="supporting"
557
+ color="secondary"
558
+ hasTabularNumbers>
559
+ {item.time}
560
+ </Text>
561
+ </HStack>
562
+ </VStack>
563
+ </StackItem>
564
+ </HStack>
565
+ </VStack>
566
+ ))}
567
+ </VStack>
568
+ </VStack>
569
+ </Section>
570
+ );
571
+ }
572
+
573
+ // ─── Right Panel ────────────────────────────────────────────────────────────
574
+ // Renders as a fixed-width side panel on desktop, and as a plain stacked section
575
+ // on mobile (so it flows below the content instead of squishing beside it).
576
+ function PanelContent() {
577
+ return (
578
+ <VStack gap={4}>
579
+ <Collapsible trigger={<Heading level={4}>Notes</Heading>}>
580
+ <Text type="body">
581
+ Customer is a repeat buyer — 3rd order this quarter. Prefers
582
+ moonlight and sage glazes. Requested gift wrapping for the mug set.
583
+ Ships to a residential address in CA.{' '}
584
+ <Link href="#/templates/detail-page" color="secondary">
585
+ Show more
586
+ </Link>
587
+ </Text>
588
+ </Collapsible>
589
+ <Collapsible trigger={<Heading level={4}>Customer</Heading>}>
590
+ <MetadataList>
591
+ <MetadataListItem label="Name">Jane Doe</MetadataListItem>
592
+ <MetadataListItem label="Address">
593
+ 321 Smith Road, CA 38238
594
+ </MetadataListItem>
595
+ <MetadataListItem label="Phone">234-555-0134</MetadataListItem>
596
+ <MetadataListItem label="Email">janedoe@email.com</MetadataListItem>
597
+ <MetadataListItem label="Billing Address">
598
+ Same as shipping address
599
+ </MetadataListItem>
600
+ </MetadataList>
601
+ </Collapsible>
602
+ <Collapsible trigger={<Heading level={4}>Fraud Analysis</Heading>}>
603
+ <VStack gap={1}>
604
+ <ProgressBar
605
+ label="Risk level"
606
+ value={15}
607
+ variant="success"
608
+ isLabelHidden
609
+ />
610
+ <Text type="body">Recommendation: Fulfill order</Text>
611
+ <Text type="body">
612
+ There is a low chance that you will receive a chargeback on this
613
+ order.
614
+ </Text>
615
+ </VStack>
616
+ </Collapsible>
617
+ </VStack>
618
+ );
619
+ }
620
+
621
+ // Desktop: fixed-width side panel in the layout's `end` slot.
622
+ function RightPanel() {
623
+ return (
624
+ <LayoutPanel width={320} padding={4} role="complementary">
625
+ <PanelContent />
626
+ </LayoutPanel>
627
+ );
628
+ }
629
+
630
+ // ─── Main Page ──────────────────────────────────────────────────────────────
631
+ export default function DetailPage() {
632
+ const [activeTab, setActiveTab] = useState('details');
633
+ const isNarrow = useMediaQuery('(max-width: 1024px)');
634
+ // Desktop: a fixed-width `end`-slot side panel that can be hidden.
635
+ const [showSidePanel, setShowSidePanel] = useState(true);
636
+ // Mobile: the panel opens as a full-screen dialog (the side slot would squish
637
+ // the main content), driven by the same toolbar button.
638
+ const [isPanelDialogOpen, setPanelDialogOpen] = useState(false);
639
+
640
+ const isPanelShown = isNarrow ? isPanelDialogOpen : showSidePanel;
641
+ const togglePanel = () =>
642
+ isNarrow
643
+ ? setPanelDialogOpen(prev => !prev)
644
+ : setShowSidePanel(prev => !prev);
645
+
646
+ return (
647
+ <>
648
+ <Layout
649
+ height="fill"
650
+ contentWidth={1000}
651
+ defaultHasDividers
652
+ header={
653
+ <PageHeader
654
+ activeTab={activeTab}
655
+ onTabChange={setActiveTab}
656
+ isPanelOpen={isPanelShown}
657
+ onTogglePanel={togglePanel}
658
+ isNarrow={isNarrow}
659
+ />
660
+ }
661
+ content={
662
+ <LayoutContent role="main">
663
+ <VStack gap={4}>
664
+ <ItemsCard />
665
+ <InvoiceCard />
666
+ <TimelineSection />
667
+ </VStack>
668
+ </LayoutContent>
669
+ }
670
+ end={!isNarrow && showSidePanel ? <RightPanel /> : undefined}
671
+ />
672
+ {/* Mobile: the side panel content opens as a full-screen dialog. (A
673
+ side drawer/sheet would be more idiomatic, but Astryx has no Drawer
674
+ component yet — #2575 — so we use the fullscreen Dialog variant.) */}
675
+ <Dialog
676
+ variant="fullscreen"
677
+ isOpen={isNarrow && isPanelDialogOpen}
678
+ onOpenChange={setPanelDialogOpen}>
679
+ <Layout
680
+ header={
681
+ <DialogHeader
682
+ title="Order details"
683
+ onOpenChange={setPanelDialogOpen}
684
+ />
685
+ }
686
+ content={
687
+ <LayoutContent padding={4}>
688
+ <PanelContent />
689
+ </LayoutContent>
690
+ }
691
+ />
692
+ </Dialog>
693
+ </>
694
+ );
695
+ }