astryx-dracula 0.5.0 → 0.6.2

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 (129) hide show
  1. package/BRAND.md +3 -3
  2. package/README.md +7 -4
  3. package/USAGE.md +49 -49
  4. package/astryx-dracula.d.ts +2 -2
  5. package/astryx-dracula.js +25 -9
  6. package/astryx-dracula.variants.d.ts +2 -2
  7. package/astryx.integration.mjs +1 -1
  8. package/icons.mjs +21 -17
  9. package/icons.tsx +69 -62
  10. package/package.json +125 -120
  11. package/shared/auth-chrome-config.ts +6 -6
  12. package/shared/auth-chrome.tsx +143 -144
  13. package/shared/auth-copy.ts +14 -14
  14. package/shared/chaptered-doc-config.ts +48 -51
  15. package/shared/chaptered-doc.tsx +267 -289
  16. package/shared/chart-hues.ts +25 -10
  17. package/shared/chart-labels.tsx +31 -30
  18. package/shared/chart-legend.tsx +55 -56
  19. package/shared/chart-panel-style.ts +3 -3
  20. package/shared/data-bar.tsx +97 -102
  21. package/shared/format-money.ts +1 -1
  22. package/shared/gallery-image.ts +6 -6
  23. package/shared/login-demo.ts +1 -1
  24. package/shared/metric-card.tsx +101 -105
  25. package/shared/metric-delta.tsx +28 -27
  26. package/shared/order-desk.tsx +205 -216
  27. package/shared/revenue-chart.tsx +145 -165
  28. package/shared/scene-castle.tsx +206 -291
  29. package/shared/scene-frame.tsx +33 -32
  30. package/shared/scene-hues.ts +17 -17
  31. package/shared/scene-tile.tsx +88 -92
  32. package/shared/settings-data.ts +119 -125
  33. package/shared/settings-rows.tsx +122 -159
  34. package/shared/sparkline.tsx +75 -88
  35. package/shared/sso-icons.tsx +10 -22
  36. package/shared/stars.tsx +16 -25
  37. package/templates/ai-chat-landing.template.mjs +5 -4
  38. package/templates/ai-chat-landing.tsx +473 -460
  39. package/templates/ai-chat.template.mjs +5 -4
  40. package/templates/ai-chat.tsx +543 -612
  41. package/templates/blank.template.mjs +5 -4
  42. package/templates/blank.tsx +18 -14
  43. package/templates/centered-hero.template.mjs +5 -4
  44. package/templates/centered-hero.tsx +55 -68
  45. package/templates/classic-gallery.template.mjs +5 -4
  46. package/templates/classic-gallery.tsx +97 -104
  47. package/templates/contact-form.template.mjs +5 -4
  48. package/templates/contact-form.tsx +247 -282
  49. package/templates/dashboard-portfolio.template.mjs +5 -4
  50. package/templates/dashboard-portfolio.tsx +717 -548
  51. package/templates/dashboard.template.mjs +5 -4
  52. package/templates/dashboard.tsx +596 -567
  53. package/templates/detail-page.template.mjs +5 -4
  54. package/templates/detail-page.tsx +546 -603
  55. package/templates/documentation-design.template.mjs +5 -4
  56. package/templates/documentation-design.tsx +231 -243
  57. package/templates/documentation-technical.template.mjs +5 -4
  58. package/templates/documentation-technical.tsx +236 -239
  59. package/templates/documentation.template.mjs +5 -4
  60. package/templates/documentation.tsx +267 -279
  61. package/templates/editor.template.mjs +5 -4
  62. package/templates/editor.tsx +844 -877
  63. package/templates/file-explorer.template.mjs +5 -4
  64. package/templates/file-explorer.tsx +566 -590
  65. package/templates/form-two-column.template.mjs +5 -4
  66. package/templates/form-two-column.tsx +195 -210
  67. package/templates/gallery-hero.template.mjs +5 -4
  68. package/templates/gallery-hero.tsx +93 -114
  69. package/templates/ide.template.mjs +5 -4
  70. package/templates/ide.tsx +407 -453
  71. package/templates/incident-console.template.mjs +5 -4
  72. package/templates/incident-console.tsx +593 -575
  73. package/templates/kanban-board.template.mjs +5 -4
  74. package/templates/kanban-board.tsx +665 -683
  75. package/templates/library.template.mjs +5 -4
  76. package/templates/library.tsx +409 -430
  77. package/templates/login-card.template.mjs +5 -4
  78. package/templates/login-card.tsx +63 -72
  79. package/templates/login-split.template.mjs +5 -4
  80. package/templates/login-split.tsx +192 -193
  81. package/templates/login-sso.template.mjs +5 -4
  82. package/templates/login-sso.tsx +257 -258
  83. package/templates/login.template.mjs +5 -4
  84. package/templates/login.tsx +53 -49
  85. package/templates/messaging-shell.template.mjs +5 -4
  86. package/templates/messaging-shell.tsx +556 -574
  87. package/templates/mixed-gallery.template.mjs +5 -4
  88. package/templates/mixed-gallery.tsx +60 -58
  89. package/templates/payment-form.template.mjs +5 -4
  90. package/templates/payment-form.tsx +882 -991
  91. package/templates/product-detail.template.mjs +5 -4
  92. package/templates/product-detail.tsx +261 -272
  93. package/templates/product-gallery.template.mjs +5 -4
  94. package/templates/product-gallery.tsx +126 -137
  95. package/templates/product-tour.template.mjs +5 -4
  96. package/templates/product-tour.tsx +741 -773
  97. package/templates/settings-dialog.template.mjs +5 -4
  98. package/templates/settings-dialog.tsx +688 -717
  99. package/templates/settings-sidebar.template.mjs +5 -4
  100. package/templates/settings-sidebar.tsx +762 -807
  101. package/templates/settings.template.mjs +5 -4
  102. package/templates/settings.tsx +289 -304
  103. package/templates/shell-nav.template.mjs +5 -4
  104. package/templates/shell-nav.tsx +212 -219
  105. package/templates/shell-side-nav.template.mjs +5 -4
  106. package/templates/shell-side-nav.tsx +135 -157
  107. package/templates/shell-top-nav.template.mjs +5 -4
  108. package/templates/shell-top-nav.tsx +182 -203
  109. package/templates/side-gallery.template.mjs +5 -4
  110. package/templates/side-gallery.tsx +85 -90
  111. package/templates/table-grouped.template.mjs +5 -4
  112. package/templates/table-grouped.tsx +1000 -1053
  113. package/templates/table-page-chart.template.mjs +5 -4
  114. package/templates/table-page-chart.tsx +400 -398
  115. package/templates/table-page-heatmap-status.template.mjs +5 -4
  116. package/templates/table-page-heatmap-status.tsx +483 -512
  117. package/templates/table-page-shoe-store-heatmap.template.mjs +5 -4
  118. package/templates/table-page-shoe-store-heatmap.tsx +748 -746
  119. package/templates/table-page.template.mjs +5 -4
  120. package/templates/table-page.tsx +396 -405
  121. package/templates/table.template.mjs +5 -4
  122. package/templates/table.tsx +92 -93
  123. package/templates/tech-report.template.mjs +5 -4
  124. package/templates/tech-report.tsx +1316 -1304
  125. package/templates/theme-showcase.template.mjs +5 -4
  126. package/templates/theme-showcase.tsx +871 -951
  127. package/theme.css +35 -77
  128. package/theme.css.d.ts +9 -0
  129. package/tokens.css +373 -380
@@ -1,48 +1,49 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
- // XLE (canonical structure, validated with `bunx astryx layout check`):
3
- // L > (LH[divider] > V[g=3] > ((H[g=4 a=start] > (SI > V[g=0] > (Lk"All orders" + (V[g=0] > Hd"#1001"[level=1] + (H[g=1 a=center] > Tx"5 ordered items"[t=supporting] + Av"Vlad Dracul" + SD"Unfulfilled" + Tx"02/23/2026"[t=supporting] + Lk"See all")))) + (H[g=2] > B"Restock" + B"Edit")) + (H[g=3 a=center] > (SI > TL > Tab"Details"! + Tab"Invoices" + Tab"Timeline" + TabMenu"More") + B.ghost))) + (LC > V[g=4] > ((S > V[g=4] > ((H[g=2 a=center] > Hd"Items"[level=2] + SD"Unfulfilled") + UL)) + (S > V[g=4] > ((H[g=2 a=center] > Hd"Invoice"[level=2] + SD"Paid") + ML)) + (S > V[g=4] > ((H[g=2 a=center] > Hd"Timeline"[level=2] + B.ghost) + UL)))) + (LP[w=320 p=4] > V[g=4] > Col"Notes" + Col"Customer" + Col"Fraud analysis")
2
+ // XLE (canonical structure of the tree below; the CLI had no XLE validator
3
+ // after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
4
+ // human drift check — update it with the file, never alone):
5
+ // L[h=fill mw=1000] > (LH[divider p=6] > V[g=3] > ((H[g=4 a=start] > (SI[fill] > V[g=2] > (Lk"All orders" + (V[g=0] > Hd"#1001"[level=1] + (H[g=1 a=center wrap] > Tx"5 ordered items"[t=supporting] + (H[g=1 a=center] > Bu + Av"Vlad Dracul" + Tx"Vlad Dracul"[t=supporting]) + (H[g=1 a=center] > Bu + SD"Unfulfilled" + Tx"Unfulfilled"[t=supporting]) + (H[g=1 a=center] > Bu + Ic + Tx"02/23/2026"[t=supporting]) + (H[g=1 a=center] > Bu + Ic + Tx"Needs attention"[t=supporting]) + (H[g=1 a=center] > Bu + Lk"See all")))) + (H[g=2] > B.secondary"Restock" + B.secondary"Edit")) + (H[g=2] > (SI[fill] > V[a=stretch] > B.secondary"Restock") + (SI[fill] > V[a=stretch] > B.secondary"Edit")) + (H[a=center] > (SI[fill] > TL[size=lg] > (Tab"Details"! + Tab"Invoices" + Tab"Timeline" + TabMenu"More")) + B.ghost))) + (LC > V[g=4] > ((S > V[g=4] > ((H[g=2 a=center wrap] > (SI[fill] > (H[g=2 a=center] > Hd"Items"[level=2] + SD"Unfulfilled" + Tx"Unfulfilled"[t=supporting])) + (H[g=2] > B.ghost"Fulfill item" + B.secondary"Create label")) + (UL[density=spacious edge=outset] > LI*5)) + (S > V[g=4] > ((H[g=2 a=center wrap] > (SI[fill] > (H[g=2 a=center] > Hd"Invoice"[level=2] + SD"Paid" + Tx"Paid"[t=supporting])) + (H[g=2] > B.ghost"Refund" + B.secondary"Send Invoice")) + (ML > MLI*5) + D + (ML > MLI*1))) + (S > V[g=4] > ((H[g=2 a=center] > (SI[fill] > Hd"Timeline"[level=2]) + B.ghost"Filters") + (UL[density=spacious edge=outset] > LI*4)))) + (LP[w=320 p=4] > V[g=4] > Col"Notes" + Col"Customer" + Col"Fraud analysis")) ;; Dlg[variant=fullscreen] > L[h=auto] > (DH"Order details" + (LC[p=4] > V[g=4] > Col"Notes" + Col"Customer" + Col"Fraud analysis"))
4
6
 
5
- import {useState} from 'react';
6
- import {useMediaQuery} from '@astryxdesign/core/hooks';
7
+ import { Avatar } from "@astryxdesign/core/Avatar";
8
+ import { Button } from "@astryxdesign/core/Button";
9
+ import { Collapsible } from "@astryxdesign/core/Collapsible";
10
+ import { Dialog, DialogHeader } from "@astryxdesign/core/Dialog";
11
+ import { Divider } from "@astryxdesign/core/Divider";
12
+ import { useMediaQuery } from "@astryxdesign/core/hooks";
13
+ import { Icon } from "@astryxdesign/core/Icon";
7
14
  import {
8
- Layout,
9
- LayoutHeader,
10
- LayoutContent,
11
- LayoutPanel,
12
- VStack,
13
- HStack,
14
- StackItem,
15
- Section,
16
- } from '@astryxdesign/core/Layout';
17
- import {Text, Heading} from '@astryxdesign/core/Text';
18
- import {StatusDot} from '@astryxdesign/core/StatusDot';
19
- import {Avatar} from '@astryxdesign/core/Avatar';
20
- import {Button} from '@astryxdesign/core/Button';
21
- import {TabList, Tab, TabMenu} from '@astryxdesign/core/TabList';
22
- import {Divider} from '@astryxdesign/core/Divider';
23
- import {Link} from '@astryxdesign/core/Link';
24
- import {List, ListItem} from '@astryxdesign/core/List';
25
- import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
26
- import {ProgressBar} from '@astryxdesign/core/ProgressBar';
27
- import {Collapsible} from '@astryxdesign/core/Collapsible';
28
- import {Icon} from '@astryxdesign/core/Icon';
29
- import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
30
- import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
31
- import {SceneFrame} from 'astryx-dracula/shared/scene-frame';
32
- import {formatMoney} from 'astryx-dracula/shared/format-money';
15
+ HStack,
16
+ Layout,
17
+ LayoutContent,
18
+ LayoutHeader,
19
+ LayoutPanel,
20
+ Section,
21
+ StackItem,
22
+ VStack,
23
+ } from "@astryxdesign/core/Layout";
24
+ import { Link } from "@astryxdesign/core/Link";
25
+ import { List, ListItem } from "@astryxdesign/core/List";
26
+ import { MetadataList, MetadataListItem } from "@astryxdesign/core/MetadataList";
27
+ import { ProgressBar } from "@astryxdesign/core/ProgressBar";
28
+ import { StatusDot } from "@astryxdesign/core/StatusDot";
29
+ import { Tab, TabList, TabMenu } from "@astryxdesign/core/TabList";
30
+ import { Heading, Text } from "@astryxdesign/core/Text";
31
+ import { formatMoney } from "astryx-dracula/shared/format-money";
32
+ import { SceneFrame } from "astryx-dracula/shared/scene-frame";
33
+ import type { SceneHue } from "astryx-dracula/shared/scene-hues";
33
34
  import {
34
- Calendar,
35
- Flag,
36
- Filter,
37
- ThumbsUp,
38
- Heart,
39
- SquarePen,
40
- ArrowLeft,
41
- Columns2,
42
- } from 'lucide-react';
43
-
35
+ ArrowLeft,
36
+ Calendar,
37
+ Columns2,
38
+ Filter,
39
+ Flag,
40
+ Heart,
41
+ SquarePen,
42
+ ThumbsUp,
43
+ } from "lucide-react";
44
44
  // ─── Styles ─────────────────────────────────────────────────────────────────
45
- import type {CSSProperties} from 'react';
45
+ import type { CSSProperties } from "react";
46
+ import { useState } from "react";
46
47
 
47
48
  // The only custom CSS in this template is small optical-alignment negative
48
49
  // margins: LayoutHeader/TabList have no edge-dock prop (#2622) and List
@@ -51,50 +52,48 @@ import type {CSSProperties} from 'react';
51
52
 
52
53
  // Bleed the tab bar to the header's content edges so the active-tab underline
53
54
  // meets the header divider. No edge-dock prop on TabList (#2622).
55
+ //
56
+ // These are written as spacing-token arithmetic, not raw pixels: core's
57
+ // --spacing-* scale is `rem` since 0.7.0, so a literal -12px stops matching
58
+ // the padding it cancels at any root font size other than 16px.
54
59
  const tabsRow: CSSProperties = {
55
- marginInline: -12,
56
- marginBottom: -16,
57
- marginTop: 12,
58
- };
59
- // Pull the list items' inner padding back so their content optically aligns
60
- // with the section heading above (ListItem insets content by ~8px). No
61
- // edge/inset prop on List (#2626).
62
- const itemsList: CSSProperties = {
63
- marginInline: -8,
60
+ marginInline: "calc(var(--spacing-3) * -1)",
61
+ marginBottom: "calc(var(--spacing-4) * -1)",
62
+ marginTop: "var(--spacing-3)",
64
63
  };
65
64
 
66
65
  // ─── Product data ───────────────────────────────────────────────────────────
67
66
  const PRODUCTS = [
68
- {
69
- name: 'Midnight Mug',
70
- details: 'Glaze: Moonlight\nFinish: Matte',
71
- price: 89.0,
72
- qty: 1,
73
- },
74
- {
75
- name: 'Cinder Bowl',
76
- details: 'Glaze: Sage\nSize: 6 in',
77
- price: 42.0,
78
- qty: 2,
79
- },
80
- {
81
- name: 'Crypt Cup',
82
- details: 'Glaze: Batwing\nSize: 14 in',
83
- price: 65.0,
84
- qty: 1,
85
- },
86
- {
87
- name: 'Dusk Plate Set',
88
- details: 'Glaze: Charcoal\nCapacity: 3 oz',
89
- price: 34.0,
90
- qty: 3,
91
- },
92
- {
93
- name: 'Coven Salad Bowl',
94
- details: 'Glaze: Moonlight\nHeight: 8 in',
95
- price: 78.0,
96
- qty: 1,
97
- },
67
+ {
68
+ name: "Midnight Mug",
69
+ details: "Glaze: Moonlight\nFinish: Matte",
70
+ price: 89.0,
71
+ qty: 1,
72
+ },
73
+ {
74
+ name: "Cinder Bowl",
75
+ details: "Glaze: Sage\nSize: 6 in",
76
+ price: 42.0,
77
+ qty: 2,
78
+ },
79
+ {
80
+ name: "Crypt Cup",
81
+ details: "Glaze: Batwing\nSize: 14 in",
82
+ price: 65.0,
83
+ qty: 1,
84
+ },
85
+ {
86
+ name: "Dusk Plate Set",
87
+ details: "Glaze: Charcoal\nCapacity: 3 oz",
88
+ price: 34.0,
89
+ qty: 3,
90
+ },
91
+ {
92
+ name: "Coven Salad Bowl",
93
+ details: "Glaze: Moonlight\nHeight: 8 in",
94
+ price: 78.0,
95
+ qty: 1,
96
+ },
98
97
  ];
99
98
 
100
99
  // Per-product art tint, read positionally out of a never-reordered array so
@@ -105,41 +104,36 @@ const PRODUCTS = [
105
104
  // The art-treatment remit bans purple, so the leading entry changes to cyan on
106
105
  // the same pass.
107
106
  const PRODUCT_HUES: readonly SceneHue[] = [
108
- 'var(--dracula-cyan)',
109
- 'var(--dracula-cyan)',
110
- 'var(--dracula-pink)',
111
- 'var(--dracula-yellow)',
112
- 'var(--dracula-green)',
107
+ "var(--dracula-cyan)",
108
+ "var(--dracula-cyan)",
109
+ "var(--dracula-pink)",
110
+ "var(--dracula-yellow)",
111
+ "var(--dracula-green)",
113
112
  ];
114
113
 
115
114
  const sigilClip: CSSProperties = {
116
- width: 40,
117
- height: 40,
118
- overflow: 'clip',
119
- flexShrink: 0,
115
+ width: 40,
116
+ height: 40,
117
+ overflow: "clip",
118
+ flexShrink: 0,
120
119
  };
121
120
 
122
- function ProductSigil({index}: {name: string; index: number}) {
123
- const hue = PRODUCT_HUES[index % PRODUCT_HUES.length];
124
- return (
125
- <div style={sigilClip}>
126
- <SceneFrame label={`${PRODUCTS[index % PRODUCTS.length].name} sigil`} viewBox="0 0 40 40" hasBackdrop={false}>
127
- <rect width="40" height="40" fill="var(--dracula-bg-light)" />
128
- <circle cx={28} cy={11} r={7} fill={hue} />
129
- <circle
130
- cx={25}
131
- cy={9}
132
- r={5.5}
133
- fill="var(--dracula-bg-light)"
134
- opacity={0.5}
135
- />
136
- <path
137
- d="M0 28 Q10 22 20 26 T40 24 V40 H0 Z"
138
- fill="var(--dracula-current-line)"
139
- />
140
- </SceneFrame>
141
- </div>
142
- );
121
+ function ProductSigil({ index }: { name: string; index: number }) {
122
+ const hue = PRODUCT_HUES[index % PRODUCT_HUES.length];
123
+ return (
124
+ <div style={sigilClip}>
125
+ <SceneFrame
126
+ label={`${PRODUCTS[index % PRODUCTS.length].name} sigil`}
127
+ viewBox="0 0 40 40"
128
+ hasBackdrop={false}
129
+ >
130
+ <rect width="40" height="40" fill="var(--dracula-bg-light)" />
131
+ <circle cx={28} cy={11} r={7} fill={hue} />
132
+ <circle cx={25} cy={9} r={5.5} fill="var(--dracula-bg-light)" opacity={0.5} />
133
+ <path d="M0 28 Q10 22 20 26 T40 24 V40 H0 Z" fill="var(--dracula-current-line)" />
134
+ </SceneFrame>
135
+ </div>
136
+ );
143
137
  }
144
138
 
145
139
  const SUBTOTAL = PRODUCTS.reduce((sum, p) => sum + p.price * p.qty, 0);
@@ -151,554 +145,503 @@ const TOTAL = SUBTOTAL - DISCOUNT + SHIPPING + TAX;
151
145
 
152
146
  // ─── Activity data ──────────────────────────────────────────────────────────
153
147
  const ACTIVITY = [
154
- {
155
- type: 'event' as const,
156
- user: 'Vlad Dracul',
157
- text: 'placed order #1001',
158
- reactions: 2,
159
- time: 'Feb 23 at 9:12 AM',
160
- },
161
- {
162
- type: 'comment' as const,
163
- user: 'Alex Rivera',
164
- 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.",
165
- reactions: 3,
166
- time: 'Feb 23 at 10:45 AM',
167
- },
168
- {
169
- type: 'update' as const,
170
- user: 'System',
171
- text: 'has several information changes',
172
- time: 'Feb 23 at 11:30 AM',
173
- changes: [
174
- 'Payment verified via Visa …7482',
175
- 'Fraud check passed, low risk',
176
- ],
177
- },
178
- {
179
- type: 'event' as const,
180
- user: 'Alex Rivera',
181
- text: 'marked order as ready for fulfillment',
182
- reactions: 1,
183
- time: 'Feb 23 at 2:15 PM',
184
- },
148
+ {
149
+ type: "event" as const,
150
+ user: "Vlad Dracul",
151
+ text: "placed order #1001",
152
+ reactions: 2,
153
+ time: "Feb 23 at 9:12 AM",
154
+ },
155
+ {
156
+ type: "comment" as const,
157
+ user: "Alex Rivera",
158
+ 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.",
159
+ reactions: 3,
160
+ time: "Feb 23 at 10:45 AM",
161
+ },
162
+ {
163
+ type: "update" as const,
164
+ user: "System",
165
+ text: "has several information changes",
166
+ time: "Feb 23 at 11:30 AM",
167
+ changes: ["Payment verified via Visa …7482", "Fraud check passed, low risk"],
168
+ },
169
+ {
170
+ type: "event" as const,
171
+ user: "Alex Rivera",
172
+ text: "marked order as ready for fulfillment",
173
+ reactions: 1,
174
+ time: "Feb 23 at 2:15 PM",
175
+ },
185
176
  ];
186
177
 
187
178
  // ─── Bullet separator ───────────────────────────────────────────────────────
188
179
  function Bullet() {
189
- return (
190
- <Text type="supporting" color="secondary" aria-hidden="true">
191
- {'・'}
192
- </Text>
193
- );
180
+ return (
181
+ <Text type="supporting" color="secondary" aria-hidden="true">
182
+ {"・"}
183
+ </Text>
184
+ );
194
185
  }
195
186
 
196
187
  // ─── Page Header ────────────────────────────────────────────────────────────
197
188
  function PageHeader({
198
- activeTab,
199
- onTabChange,
200
- isPanelOpen,
201
- onTogglePanel,
202
- isNarrow,
189
+ activeTab,
190
+ onTabChange,
191
+ isPanelOpen,
192
+ onTogglePanel,
193
+ isNarrow,
203
194
  }: {
204
- activeTab: string;
205
- onTabChange: (v: string) => void;
206
- isPanelOpen: boolean;
207
- onTogglePanel: () => void;
208
- isNarrow: boolean;
195
+ activeTab: string;
196
+ onTabChange: (v: string) => void;
197
+ isPanelOpen: boolean;
198
+ onTogglePanel: () => void;
199
+ isNarrow: boolean;
209
200
  }) {
210
- return (
211
- <LayoutHeader hasDivider padding={6}>
212
- <VStack gap={3}>
213
- <HStack gap={4} vAlign="start">
214
- <StackItem size="fill">
215
- {/* gap={2}: the breadcrumb is a control, not a text-baseline
201
+ return (
202
+ <LayoutHeader hasDivider padding={6}>
203
+ <VStack gap={3}>
204
+ <HStack gap={4} vAlign="start">
205
+ <StackItem size="fill">
206
+ {/* gap={2}: the breadcrumb is a control, not a text-baseline
216
207
  pairing, so it needs separation from the H1 rather than 0. */}
217
- <VStack gap={2}>
218
- <Link href="#/templates/detail-page">
219
- <HStack gap={1} vAlign="center">
220
- <Icon icon={ArrowLeft} size="sm" color="inherit" />
221
- All orders
222
- </HStack>
223
- </Link>
224
- <VStack gap={0}>
225
- <Heading level={1} maxLines={1}>
226
- #1001
227
- </Heading>
228
- {/* Metadata wraps to multiple lines on narrow screens (rather
208
+ <VStack gap={2}>
209
+ <Link href="#/templates/detail-page">
210
+ <HStack gap={1} vAlign="center">
211
+ <Icon icon={ArrowLeft} size="sm" color="inherit" />
212
+ All orders
213
+ </HStack>
214
+ </Link>
215
+ <VStack gap={0}>
216
+ <Heading level={1} maxLines={1}>
217
+ #1001
218
+ </Heading>
219
+ {/* Metadata wraps to multiple lines on narrow screens (rather
229
220
  than collapsing items behind a "+N more" overflow). */}
230
- <HStack gap={1} vAlign="center" wrap="wrap">
231
- <Text type="supporting" color="secondary" hasTabularNumbers>
232
- {PRODUCTS.length} ordered items
233
- </Text>
234
- <HStack gap={1} vAlign="center">
235
- <Bullet />
236
- <Avatar name="Vlad Dracul" size="sm" />
237
- <Text type="supporting" color="secondary" maxLines={1}>
238
- Vlad Dracul
239
- </Text>
240
- </HStack>
241
- <HStack gap={1} vAlign="center">
242
- <Bullet />
243
- <StatusDot variant="warning" label="Unfulfilled" />
244
- <Text type="supporting" color="secondary">
245
- Unfulfilled
246
- </Text>
247
- </HStack>
248
- <HStack gap={1} vAlign="center">
249
- <Bullet />
250
- <Icon icon={Calendar} size="sm" color="secondary" />
251
- <Text
252
- type="supporting"
253
- color="secondary"
254
- maxLines={1}
255
- hasTabularNumbers>
256
- 02/23/2026
257
- </Text>
258
- </HStack>
259
- <HStack gap={1} vAlign="center">
260
- <Bullet />
261
- <Icon icon={Flag} size="sm" color="secondary" />
262
- <Text type="supporting" color="secondary" maxLines={1}>
263
- Needs attention
264
- </Text>
265
- </HStack>
266
- <HStack gap={1} vAlign="center">
267
- <Bullet />
268
- <Link href="#/templates/detail-page">
269
- See all
270
- </Link>
271
- </HStack>
272
- </HStack>
273
- </VStack>
274
- </VStack>
275
- </StackItem>
276
- {!isNarrow && (
277
- <HStack gap={2}>
278
- <Button label="Restock" variant="secondary" />
279
- <Button label="Edit" variant="secondary" />
280
- </HStack>
281
- )}
282
- </HStack>
221
+ <HStack gap={1} vAlign="center" wrap="wrap">
222
+ <Text type="supporting" color="secondary" hasTabularNumbers>
223
+ {PRODUCTS.length} ordered items
224
+ </Text>
225
+ <HStack gap={1} vAlign="center">
226
+ <Bullet />
227
+ <Avatar name="Vlad Dracul" size="sm" />
228
+ <Text type="supporting" color="secondary" maxLines={1}>
229
+ Vlad Dracul
230
+ </Text>
231
+ </HStack>
232
+ <HStack gap={1} vAlign="center">
233
+ <Bullet />
234
+ <StatusDot variant="warning" label="Unfulfilled" />
235
+ <Text type="supporting" color="secondary">
236
+ Unfulfilled
237
+ </Text>
238
+ </HStack>
239
+ <HStack gap={1} vAlign="center">
240
+ <Bullet />
241
+ <Icon icon={Calendar} size="sm" color="secondary" />
242
+ <Text type="supporting" color="secondary" maxLines={1} hasTabularNumbers>
243
+ 02/23/2026
244
+ </Text>
245
+ </HStack>
246
+ <HStack gap={1} vAlign="center">
247
+ <Bullet />
248
+ <Icon icon={Flag} size="sm" color="secondary" />
249
+ <Text type="supporting" color="secondary" maxLines={1}>
250
+ Needs attention
251
+ </Text>
252
+ </HStack>
253
+ <HStack gap={1} vAlign="center">
254
+ <Bullet />
255
+ <Link href="#/templates/detail-page">See all</Link>
256
+ </HStack>
257
+ </HStack>
258
+ </VStack>
259
+ </VStack>
260
+ </StackItem>
261
+ {!isNarrow && (
262
+ <HStack gap={2}>
263
+ <Button label="Restock" variant="secondary" />
264
+ <Button label="Edit" variant="secondary" />
265
+ </HStack>
266
+ )}
267
+ </HStack>
283
268
 
284
- {/* Mobile: actions drop below the metadata as a full-width row. The
269
+ {/* Mobile: actions drop below the metadata as a full-width row. The
285
270
  VStack hAlign="stretch" wrapper is the full-width-button
286
271
  workaround — Button has no full-width prop (#2600). */}
287
- {isNarrow && (
288
- <HStack gap={2}>
289
- <StackItem size="fill">
290
- <VStack hAlign="stretch">
291
- <Button label="Restock" variant="secondary" />
292
- </VStack>
293
- </StackItem>
294
- <StackItem size="fill">
295
- <VStack hAlign="stretch">
296
- <Button label="Edit" variant="secondary" />
297
- </VStack>
298
- </StackItem>
299
- </HStack>
300
- )}
272
+ {isNarrow && (
273
+ <HStack gap={2}>
274
+ <StackItem size="fill">
275
+ <VStack hAlign="stretch">
276
+ <Button label="Restock" variant="secondary" />
277
+ </VStack>
278
+ </StackItem>
279
+ <StackItem size="fill">
280
+ <VStack hAlign="stretch">
281
+ <Button label="Edit" variant="secondary" />
282
+ </VStack>
283
+ </StackItem>
284
+ </HStack>
285
+ )}
301
286
 
302
- <HStack vAlign="center" style={tabsRow}>
303
- <StackItem size="fill">
304
- <TabList value={activeTab} onChange={onTabChange} size="lg">
305
- <Tab value="details" label="Details" />
306
- <Tab value="invoices" label="Invoices" />
307
- <Tab value="timeline" label="Timeline" />
308
- <TabMenu
309
- label="More"
310
- options={[
311
- {value: 'customer', label: 'Customer'},
312
- {value: 'analysis', label: 'Analysis'},
313
- ]}
314
- />
315
- </TabList>
316
- </StackItem>
317
- <Button
318
- label={isPanelOpen ? 'Hide panel' : 'Show panel'}
319
- variant="ghost"
320
- size="md"
321
- icon={<Icon icon={Columns2} size="sm" />}
322
- isIconOnly
323
- onClick={onTogglePanel}
324
- />
325
- </HStack>
326
- </VStack>
327
- </LayoutHeader>
328
- );
287
+ <HStack vAlign="center" style={tabsRow}>
288
+ <StackItem size="fill">
289
+ <TabList value={activeTab} onChange={onTabChange} size="lg">
290
+ <Tab value="details" label="Details" />
291
+ <Tab value="invoices" label="Invoices" />
292
+ <Tab value="timeline" label="Timeline" />
293
+ <TabMenu
294
+ label="More"
295
+ options={[
296
+ { value: "customer", label: "Customer" },
297
+ { value: "analysis", label: "Analysis" },
298
+ ]}
299
+ />
300
+ </TabList>
301
+ </StackItem>
302
+ <Button
303
+ label={isPanelOpen ? "Hide panel" : "Show panel"}
304
+ variant="ghost"
305
+ size="md"
306
+ icon={<Icon icon={Columns2} size="sm" />}
307
+ isIconOnly
308
+ onClick={onTogglePanel}
309
+ />
310
+ </HStack>
311
+ </VStack>
312
+ </LayoutHeader>
313
+ );
329
314
  }
330
315
 
331
316
  // ─── Items Card ─────────────────────────────────────────────────────────────
332
317
  function ItemsCard() {
333
- return (
334
- <Section>
335
- <VStack gap={4}>
336
- <HStack vAlign="center" gap={2} wrap="wrap">
337
- <StackItem size="fill">
338
- <HStack gap={2} vAlign="center">
339
- <Heading level={2}>Items</Heading>
340
- <StatusDot variant="warning" label="Unfulfilled" />
341
- <Text type="supporting" color="secondary">
342
- Unfulfilled
343
- </Text>
344
- </HStack>
345
- </StackItem>
346
- <HStack gap={2}>
347
- <Button label="Fulfill item" variant="ghost" />
348
- <Button label="Create label" variant="secondary" />
349
- </HStack>
350
- </HStack>
318
+ return (
319
+ <Section>
320
+ <VStack gap={4}>
321
+ <HStack vAlign="center" gap={2} wrap="wrap">
322
+ <StackItem size="fill">
323
+ <HStack gap={2} vAlign="center">
324
+ <Heading level={2}>Items</Heading>
325
+ <StatusDot variant="warning" label="Unfulfilled" />
326
+ <Text type="supporting" color="secondary">
327
+ Unfulfilled
328
+ </Text>
329
+ </HStack>
330
+ </StackItem>
331
+ <HStack gap={2}>
332
+ <Button label="Fulfill item" variant="ghost" />
333
+ <Button label="Create label" variant="secondary" />
334
+ </HStack>
335
+ </HStack>
351
336
 
352
- <List density="spacious" style={itemsList}>
353
- {PRODUCTS.map((product, i) => (
354
- <ListItem
355
- key={product.name}
356
- label={product.name}
357
- description={
358
- <VStack gap={0}>
359
- {product.details.split('\n').map(line => (
360
- <Text
361
- key={line}
362
- type="supporting"
363
- color="secondary"
364
- hasTabularNumbers>
365
- {line}
366
- </Text>
367
- ))}
368
- </VStack>
369
- }
370
- startContent={
371
- <ProductSigil name={product.name} index={i} />
372
- }
373
- endContent={
374
- <VStack gap={0} hAlign="end">
375
- <Text
376
- type="body"
377
- weight="semibold"
378
- maxLines={1}
379
- hasTabularNumbers>
380
- {formatMoney(product.price * product.qty)}
381
- </Text>
382
- <Text
383
- type="supporting"
384
- color="secondary"
385
- maxLines={1}
386
- hasTabularNumbers>
387
- {formatMoney(product.price)} {'×'} {product.qty}
388
- </Text>
389
- </VStack>
390
- }
391
- />
392
- ))}
393
- </List>
394
- </VStack>
395
- </Section>
396
- );
337
+ <List density="spacious" edge="outset">
338
+ {PRODUCTS.map((product, i) => (
339
+ <ListItem
340
+ key={product.name}
341
+ label={product.name}
342
+ description={
343
+ <VStack gap={0}>
344
+ {product.details.split("\n").map((line) => (
345
+ <Text key={line} type="supporting" color="secondary" hasTabularNumbers>
346
+ {line}
347
+ </Text>
348
+ ))}
349
+ </VStack>
350
+ }
351
+ startContent={<ProductSigil name={product.name} index={i} />}
352
+ endContent={
353
+ <VStack gap={0} hAlign="end">
354
+ <Text type="body" weight="semibold" maxLines={1} hasTabularNumbers>
355
+ {formatMoney(product.price * product.qty)}
356
+ </Text>
357
+ <Text type="supporting" color="secondary" maxLines={1} hasTabularNumbers>
358
+ {formatMoney(product.price)} {"×"} {product.qty}
359
+ </Text>
360
+ </VStack>
361
+ }
362
+ />
363
+ ))}
364
+ </List>
365
+ </VStack>
366
+ </Section>
367
+ );
397
368
  }
398
369
 
399
370
  // ─── Invoice Card ───────────────────────────────────────────────────────────
400
371
  function InvoiceCard() {
401
- return (
402
- <Section>
403
- <VStack gap={4}>
404
- <HStack vAlign="center" gap={2} wrap="wrap">
405
- <StackItem size="fill">
406
- <HStack gap={2} vAlign="center">
407
- <Heading level={2}>Invoice</Heading>
408
- <StatusDot variant="success" label="Paid" />
409
- <Text type="supporting" color="secondary">
410
- Paid
411
- </Text>
412
- </HStack>
413
- </StackItem>
414
- <HStack gap={2}>
415
- <Button label="Refund" variant="ghost" />
416
- <Button label="Send Invoice" variant="secondary" />
417
- </HStack>
418
- </HStack>
372
+ return (
373
+ <Section>
374
+ <VStack gap={4}>
375
+ <HStack vAlign="center" gap={2} wrap="wrap">
376
+ <StackItem size="fill">
377
+ <HStack gap={2} vAlign="center">
378
+ <Heading level={2}>Invoice</Heading>
379
+ <StatusDot variant="success" label="Paid" />
380
+ <Text type="supporting" color="secondary">
381
+ Paid
382
+ </Text>
383
+ </HStack>
384
+ </StackItem>
385
+ <HStack gap={2}>
386
+ <Button label="Refund" variant="ghost" />
387
+ <Button label="Send Invoice" variant="secondary" />
388
+ </HStack>
389
+ </HStack>
419
390
 
420
- <MetadataList>
421
- <MetadataListItem label="Subtotal">
422
- <HStack>
423
- <StackItem size="fill">
424
- <Text type="body" hasTabularNumbers>
425
- {PRODUCTS.length} items
426
- </Text>
427
- </StackItem>
428
- <Text type="body" hasTabularNumbers>
429
- {formatMoney(SUBTOTAL)}
430
- </Text>
431
- </HStack>
432
- </MetadataListItem>
433
- <MetadataListItem label="Discount">
434
- <HStack>
435
- <StackItem size="fill">
436
- <Text type="body">New customer code: NEW15</Text>
437
- </StackItem>
438
- <Text type="body" hasTabularNumbers>
439
- – {formatMoney(DISCOUNT)}
440
- </Text>
441
- </HStack>
442
- </MetadataListItem>
443
- <MetadataListItem label="Shipping">
444
- <HStack>
445
- <StackItem size="fill">
446
- <Text type="body" hasTabularNumbers>
447
- Free shipping (0.0lbs) USPS
448
- </Text>
449
- </StackItem>
450
- <Text type="body" hasTabularNumbers>
451
- {formatMoney(SHIPPING)}
452
- </Text>
453
- </HStack>
454
- </MetadataListItem>
455
- <MetadataListItem label="Tax">
456
- <HStack>
457
- <StackItem size="fill">
458
- <Text type="body" hasTabularNumbers>
459
- Sales tax (8.25%)
460
- </Text>
461
- </StackItem>
462
- <Text type="body" hasTabularNumbers>
463
- {formatMoney(TAX)}
464
- </Text>
465
- </HStack>
466
- </MetadataListItem>
467
- <MetadataListItem label="Total">
468
- <HStack>
469
- <StackItem size="fill" />
470
- <Text type="body" weight="semibold" hasTabularNumbers>
471
- {formatMoney(TOTAL)}
472
- </Text>
473
- </HStack>
474
- </MetadataListItem>
475
- </MetadataList>
391
+ <MetadataList>
392
+ <MetadataListItem label="Subtotal">
393
+ <HStack>
394
+ <StackItem size="fill">
395
+ <Text type="body" hasTabularNumbers>
396
+ {PRODUCTS.length} items
397
+ </Text>
398
+ </StackItem>
399
+ <Text type="body" hasTabularNumbers>
400
+ {formatMoney(SUBTOTAL)}
401
+ </Text>
402
+ </HStack>
403
+ </MetadataListItem>
404
+ <MetadataListItem label="Discount">
405
+ <HStack>
406
+ <StackItem size="fill">
407
+ <Text type="body">New customer code: NEW15</Text>
408
+ </StackItem>
409
+ <Text type="body" hasTabularNumbers>
410
+ – {formatMoney(DISCOUNT)}
411
+ </Text>
412
+ </HStack>
413
+ </MetadataListItem>
414
+ <MetadataListItem label="Shipping">
415
+ <HStack>
416
+ <StackItem size="fill">
417
+ <Text type="body" hasTabularNumbers>
418
+ Free shipping (0.0lbs) USPS
419
+ </Text>
420
+ </StackItem>
421
+ <Text type="body" hasTabularNumbers>
422
+ {formatMoney(SHIPPING)}
423
+ </Text>
424
+ </HStack>
425
+ </MetadataListItem>
426
+ <MetadataListItem label="Tax">
427
+ <HStack>
428
+ <StackItem size="fill">
429
+ <Text type="body" hasTabularNumbers>
430
+ Sales tax (8.25%)
431
+ </Text>
432
+ </StackItem>
433
+ <Text type="body" hasTabularNumbers>
434
+ {formatMoney(TAX)}
435
+ </Text>
436
+ </HStack>
437
+ </MetadataListItem>
438
+ <MetadataListItem label="Total">
439
+ <HStack>
440
+ <StackItem size="fill" />
441
+ <Text type="body" weight="semibold" hasTabularNumbers>
442
+ {formatMoney(TOTAL)}
443
+ </Text>
444
+ </HStack>
445
+ </MetadataListItem>
446
+ </MetadataList>
476
447
 
477
- <Divider />
448
+ <Divider />
478
449
 
479
- <MetadataList>
480
- <MetadataListItem label="Paid by customer">
481
- <HStack>
482
- <StackItem size="fill">
483
- <Text type="body" hasTabularNumbers>
484
- Visa …7482
485
- </Text>
486
- </StackItem>
487
- <Text type="body" hasTabularNumbers>
488
- {formatMoney(TOTAL)}
489
- </Text>
490
- </HStack>
491
- </MetadataListItem>
492
- </MetadataList>
493
- </VStack>
494
- </Section>
495
- );
450
+ <MetadataList>
451
+ <MetadataListItem label="Paid by customer">
452
+ <HStack>
453
+ <StackItem size="fill">
454
+ <Text type="body" hasTabularNumbers>
455
+ Visa …7482
456
+ </Text>
457
+ </StackItem>
458
+ <Text type="body" hasTabularNumbers>
459
+ {formatMoney(TOTAL)}
460
+ </Text>
461
+ </HStack>
462
+ </MetadataListItem>
463
+ </MetadataList>
464
+ </VStack>
465
+ </Section>
466
+ );
496
467
  }
497
468
 
498
469
  // ─── Timeline ───────────────────────────────────────────────────────────────
499
470
  function TimelineSection() {
500
- return (
501
- <Section>
502
- <VStack gap={4}>
503
- {/* gap={2}, matching the byte-identical header rows at :340 and
471
+ return (
472
+ <Section>
473
+ <VStack gap={4}>
474
+ {/* gap={2}, matching the byte-identical header rows at :340 and
504
475
  :408. Core's Stack has no default gap (Stack.tsx:211, pass-through
505
476
  at :255/:285), so this rendered 0px -- the heading and the Filters
506
477
  button touched -- while both siblings render 8px. */}
507
- <HStack gap={2} vAlign="center">
508
- <StackItem size="fill">
509
- <Heading level={2}>Timeline</Heading>
510
- </StackItem>
511
- <Button
512
- label="Filters"
513
- variant="ghost"
514
- icon={<Icon icon={Filter} />}
515
- isIconOnly
516
- />
517
- </HStack>
478
+ <HStack gap={2} vAlign="center">
479
+ <StackItem size="fill">
480
+ <Heading level={2}>Timeline</Heading>
481
+ </StackItem>
482
+ <Button label="Filters" variant="ghost" icon={<Icon icon={Filter} />} isIconOnly />
483
+ </HStack>
518
484
 
519
- <List density="spacious" style={itemsList}>
520
- {ACTIVITY.map(item => (
521
- <ListItem
522
- key={`${item.user}-${item.time}`}
523
- label={item.user}
524
- description={
525
- <VStack gap={2}>
526
- <Text type="body">{item.text}</Text>
527
- {item.changes && (
528
- <VStack gap={1}>
529
- {item.changes.map(change => (
530
- <HStack key={change} gap={2} vAlign="center">
531
- <Icon icon={SquarePen} size="sm" color="secondary" />
532
- <Text type="supporting" color="secondary">
533
- {change}
534
- </Text>
535
- </HStack>
536
- ))}
537
- </VStack>
538
- )}
539
- <HStack gap={3} vAlign="center">
540
- <HStack gap={1} vAlign="center">
541
- <Icon icon={ThumbsUp} size="xsm" color="secondary" />
542
- <Icon icon={Heart} size="xsm" color="secondary" />
543
- <Text
544
- type="supporting"
545
- color="secondary"
546
- hasTabularNumbers>
547
- {item.reactions}
548
- </Text>
549
- </HStack>
550
- <Text type="supporting" color="secondary">
551
- Like
552
- </Text>
553
- <Bullet />
554
- <Text type="supporting" color="secondary">
555
- Reply
556
- </Text>
557
- <Bullet />
558
- <Text
559
- type="supporting"
560
- color="secondary"
561
- hasTabularNumbers>
562
- {item.time}
563
- </Text>
564
- </HStack>
565
- </VStack>
566
- }
567
- startContent={<Avatar name={item.user} size="md" />}
568
- />
569
- ))}
570
- </List>
571
- </VStack>
572
- </Section>
573
- );
485
+ <List density="spacious" edge="outset">
486
+ {ACTIVITY.map((item) => (
487
+ <ListItem
488
+ key={`${item.user}-${item.time}`}
489
+ label={item.user}
490
+ description={
491
+ <VStack gap={2}>
492
+ <Text type="body">{item.text}</Text>
493
+ {item.changes && (
494
+ <VStack gap={1}>
495
+ {item.changes.map((change) => (
496
+ <HStack key={change} gap={2} vAlign="center">
497
+ <Icon icon={SquarePen} size="sm" color="secondary" />
498
+ <Text type="supporting" color="secondary">
499
+ {change}
500
+ </Text>
501
+ </HStack>
502
+ ))}
503
+ </VStack>
504
+ )}
505
+ <HStack gap={3} vAlign="center">
506
+ <HStack gap={1} vAlign="center">
507
+ <Icon icon={ThumbsUp} size="xsm" color="secondary" />
508
+ <Icon icon={Heart} size="xsm" color="secondary" />
509
+ <Text type="supporting" color="secondary" hasTabularNumbers>
510
+ {item.reactions}
511
+ </Text>
512
+ </HStack>
513
+ <Text type="supporting" color="secondary">
514
+ Like
515
+ </Text>
516
+ <Bullet />
517
+ <Text type="supporting" color="secondary">
518
+ Reply
519
+ </Text>
520
+ <Bullet />
521
+ <Text type="supporting" color="secondary" hasTabularNumbers>
522
+ {item.time}
523
+ </Text>
524
+ </HStack>
525
+ </VStack>
526
+ }
527
+ startContent={<Avatar name={item.user} size="md" />}
528
+ />
529
+ ))}
530
+ </List>
531
+ </VStack>
532
+ </Section>
533
+ );
574
534
  }
575
535
 
576
536
  // ─── Right Panel ────────────────────────────────────────────────────────────
577
537
  // Renders as a fixed-width side panel on desktop, and as a plain stacked section
578
538
  // on mobile (so it flows below the content instead of squishing beside it).
579
539
  function PanelContent() {
580
- return (
581
- <VStack gap={4}>
582
- <Collapsible trigger={<Heading level={3}>Notes</Heading>}>
583
- {/* "Show more" closes the sentence inside the Text below, so it is
540
+ return (
541
+ <VStack gap={4}>
542
+ <Collapsible trigger={<Heading level={3}>Notes</Heading>}>
543
+ {/* "Show more" closes the sentence inside the Text below, so it is
584
544
  prose and asks for `hasUnderline` — colour alone is not a
585
545
  sufficient cue inline (1.4.1 / F73). `type="inherit"` adopts the
586
546
  paragraph's size and leading. The two links in the header are
587
547
  navigation, so they pass neither. */}
588
- <Text type="body">
589
- Customer is a repeat buyer, 3rd order this quarter. Prefers
590
- moonlight and sage glazes. Requested gift wrapping for the mug set.
591
- Ships to a residential address in CA.{' '}
592
- <Link href="#/templates/detail-page" hasUnderline type="inherit">
593
- Show more
594
- </Link>
595
- </Text>
596
- </Collapsible>
597
- <Collapsible trigger={<Heading level={3}>Customer</Heading>}>
598
- <MetadataList>
599
- <MetadataListItem label="Name">Vlad Dracul</MetadataListItem>
600
- <MetadataListItem label="Address">
601
- 321 Smith Road, CA 38238
602
- </MetadataListItem>
603
- <MetadataListItem label="Phone">234-555-0134</MetadataListItem>
604
- <MetadataListItem label="Email">vlad@castle-dracula.transylvania</MetadataListItem>
605
- <MetadataListItem label="Billing Address">
606
- Same as shipping address
607
- </MetadataListItem>
608
- </MetadataList>
609
- </Collapsible>
610
- <Collapsible trigger={<Heading level={3}>Fraud analysis</Heading>}>
611
- <VStack gap={1}>
612
- <ProgressBar
613
- label="Risk level"
614
- value={15}
615
- variant="success"
616
- isLabelHidden
617
- />
618
- <Text type="body">Recommendation: Fulfill order</Text>
619
- <Text type="body">
620
- There is a low chance that you will receive a chargeback on this
621
- order.
622
- </Text>
623
- </VStack>
624
- </Collapsible>
625
- </VStack>
626
- );
548
+ <Text type="body">
549
+ Customer is a repeat buyer, 3rd order this quarter. Prefers moonlight and sage glazes.
550
+ Requested gift wrapping for the mug set. Ships to a residential address in CA.{" "}
551
+ <Link href="#/templates/detail-page" hasUnderline type="inherit">
552
+ Show more
553
+ </Link>
554
+ </Text>
555
+ </Collapsible>
556
+ <Collapsible trigger={<Heading level={3}>Customer</Heading>}>
557
+ <MetadataList>
558
+ <MetadataListItem label="Name">Vlad Dracul</MetadataListItem>
559
+ <MetadataListItem label="Address">321 Smith Road, CA 38238</MetadataListItem>
560
+ <MetadataListItem label="Phone">234-555-0134</MetadataListItem>
561
+ <MetadataListItem label="Email">vlad@castle-dracula.transylvania</MetadataListItem>
562
+ <MetadataListItem label="Billing Address">Same as shipping address</MetadataListItem>
563
+ </MetadataList>
564
+ </Collapsible>
565
+ <Collapsible trigger={<Heading level={3}>Fraud analysis</Heading>}>
566
+ <VStack gap={1}>
567
+ <ProgressBar label="Risk level" value={15} variant="success" isLabelHidden />
568
+ <Text type="body">Recommendation: Fulfill order</Text>
569
+ <Text type="body">
570
+ There is a low chance that you will receive a chargeback on this order.
571
+ </Text>
572
+ </VStack>
573
+ </Collapsible>
574
+ </VStack>
575
+ );
627
576
  }
628
577
 
629
578
  // Desktop: fixed-width side panel in the layout's `end` slot.
630
579
  function RightPanel() {
631
- return (
632
- <LayoutPanel width={320} padding={4} role="complementary">
633
- <PanelContent />
634
- </LayoutPanel>
635
- );
580
+ return (
581
+ <LayoutPanel width={320} padding={4} role="complementary">
582
+ <PanelContent />
583
+ </LayoutPanel>
584
+ );
636
585
  }
637
586
 
638
587
  // ─── Main Page ──────────────────────────────────────────────────────────────
639
588
  export default function DetailPage() {
640
- const [activeTab, setActiveTab] = useState('details');
641
- const isNarrow = useMediaQuery('(max-width: 1024px)');
642
- // Desktop: a fixed-width `end`-slot side panel that can be hidden.
643
- const [showSidePanel, setShowSidePanel] = useState(true);
644
- // Mobile: the panel opens as a full-screen dialog (the side slot would squish
645
- // the main content), driven by the same toolbar button.
646
- const [isPanelDialogOpen, setPanelDialogOpen] = useState(false);
589
+ const [activeTab, setActiveTab] = useState("details");
590
+ const isNarrow = useMediaQuery("(max-width: 1024px)");
591
+ // Desktop: a fixed-width `end`-slot side panel that can be hidden.
592
+ const [showSidePanel, setShowSidePanel] = useState(true);
593
+ // Mobile: the panel opens as a full-screen dialog (the side slot would squish
594
+ // the main content), driven by the same toolbar button.
595
+ const [isPanelDialogOpen, setPanelDialogOpen] = useState(false);
647
596
 
648
- const isPanelShown = isNarrow ? isPanelDialogOpen : showSidePanel;
649
- const togglePanel = () =>
650
- isNarrow
651
- ? setPanelDialogOpen(prev => !prev)
652
- : setShowSidePanel(prev => !prev);
597
+ const isPanelShown = isNarrow ? isPanelDialogOpen : showSidePanel;
598
+ const togglePanel = () =>
599
+ isNarrow ? setPanelDialogOpen((prev) => !prev) : setShowSidePanel((prev) => !prev);
653
600
 
654
- return (
655
- <>
656
- <Layout
657
- height="fill"
658
- contentWidth={1000}
659
- defaultHasDividers
660
- header={
661
- <PageHeader
662
- activeTab={activeTab}
663
- onTabChange={setActiveTab}
664
- isPanelOpen={isPanelShown}
665
- onTogglePanel={togglePanel}
666
- isNarrow={isNarrow}
667
- />
668
- }
669
- content={
670
- <LayoutContent role="main">
671
- <VStack gap={4}>
672
- <ItemsCard />
673
- <InvoiceCard />
674
- <TimelineSection />
675
- </VStack>
676
- </LayoutContent>
677
- }
678
- end={!isNarrow && showSidePanel ? <RightPanel /> : undefined}
679
- />
680
- {/* Mobile: the side panel content opens as a full-screen dialog. (A
681
- side drawer/sheet would be more idiomatic, but Astryx has no Drawer
682
- component yet — #2575 — so we use the fullscreen Dialog variant.) */}
683
- <Dialog
684
- variant="fullscreen"
685
- isOpen={isNarrow && isPanelDialogOpen}
686
- onOpenChange={setPanelDialogOpen}>
687
- <Layout
688
- height="auto"
689
- header={
690
- <DialogHeader
691
- title="Order details"
692
- onOpenChange={setPanelDialogOpen}
693
- />
694
- }
695
- content={
696
- <LayoutContent padding={4}>
697
- <PanelContent />
698
- </LayoutContent>
699
- }
700
- />
701
- </Dialog>
702
- </>
703
- );
601
+ return (
602
+ <>
603
+ <Layout
604
+ height="fill"
605
+ contentWidth={1000}
606
+ defaultHasDividers
607
+ header={
608
+ <PageHeader
609
+ activeTab={activeTab}
610
+ onTabChange={setActiveTab}
611
+ isPanelOpen={isPanelShown}
612
+ onTogglePanel={togglePanel}
613
+ isNarrow={isNarrow}
614
+ />
615
+ }
616
+ content={
617
+ <LayoutContent role="main">
618
+ <VStack gap={4}>
619
+ <ItemsCard />
620
+ <InvoiceCard />
621
+ <TimelineSection />
622
+ </VStack>
623
+ </LayoutContent>
624
+ }
625
+ end={!isNarrow && showSidePanel ? <RightPanel /> : undefined}
626
+ />
627
+ {/* Mobile: the side-panel content uses the full viewport so order details
628
+ are not squeezed beside narrow page chrome. Drawer remains available
629
+ for side-overlay flows that intentionally preserve page context. */}
630
+ <Dialog
631
+ variant="fullscreen"
632
+ isOpen={isNarrow && isPanelDialogOpen}
633
+ onOpenChange={setPanelDialogOpen}
634
+ >
635
+ <Layout
636
+ height="auto"
637
+ header={<DialogHeader title="Order details" onOpenChange={setPanelDialogOpen} />}
638
+ content={
639
+ <LayoutContent padding={4}>
640
+ <PanelContent />
641
+ </LayoutContent>
642
+ }
643
+ />
644
+ </Dialog>
645
+ </>
646
+ );
704
647
  }