astryx-dracula 0.2.0 → 0.3.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.
- package/BRAND.md +30 -18
- package/README.md +22 -11
- package/USAGE.md +29 -10
- package/astryx-dracula.js +19 -13
- package/astryx-dracula.variants.d.ts +15 -0
- package/package.json +59 -17
- package/shared/auth-chrome-config.ts +18 -0
- package/shared/auth-chrome.tsx +25 -0
- package/shared/auth-copy.ts +22 -0
- package/shared/chaptered-doc-config.ts +92 -0
- package/shared/chaptered-doc.tsx +332 -0
- package/shared/chart-hues.ts +27 -0
- package/shared/chart-labels.tsx +44 -0
- package/shared/chart-legend.tsx +100 -0
- package/shared/chart-panel-style.ts +13 -0
- package/shared/data-bar.tsx +160 -0
- package/shared/gallery-image.ts +20 -0
- package/shared/login-demo.ts +7 -0
- package/shared/metric-delta.tsx +36 -0
- package/shared/order-desk.tsx +273 -0
- package/shared/revenue-chart.tsx +174 -0
- package/shared/scene-castle.tsx +322 -0
- package/shared/scene-frame.tsx +88 -0
- package/shared/scene-hues.ts +49 -0
- package/shared/scene-tile.tsx +128 -0
- package/shared/settings-data.ts +125 -0
- package/shared/settings-rows.tsx +179 -0
- package/shared/sparkline.tsx +94 -0
- package/shared/sso-icons.tsx +30 -0
- package/templates/ai-chat-landing.tsx +67 -32
- package/templates/ai-chat.tsx +41 -20
- package/templates/blank.tsx +5 -2
- package/templates/centered-hero.tsx +15 -124
- package/templates/classic-gallery.tsx +21 -65
- package/templates/contact-form.tsx +62 -40
- package/templates/dashboard-portfolio.tsx +146 -227
- package/templates/dashboard.tsx +123 -121
- package/templates/detail-page.tsx +101 -95
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +85 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +33 -21
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +54 -74
- package/templates/editor.tsx +119 -89
- package/templates/file-explorer.tsx +133 -84
- package/templates/form-two-column.tsx +62 -124
- package/templates/gallery-hero.tsx +17 -171
- package/templates/ide.tsx +27 -21
- package/templates/incident-console.tsx +105 -69
- package/templates/kanban-board.tsx +100 -41
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +38 -41
- package/templates/login-card.tsx +59 -86
- package/templates/login-split.tsx +88 -128
- package/templates/login-sso.tsx +107 -68
- package/templates/login.tsx +108 -52
- package/templates/messaging-shell.tsx +172 -143
- package/templates/mixed-gallery.tsx +31 -130
- package/templates/payment-form.tsx +369 -289
- package/templates/product-detail.tsx +40 -57
- package/templates/product-gallery.tsx +31 -40
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +145 -286
- package/templates/settings-sidebar.tsx +179 -267
- package/templates/settings.tsx +42 -13
- package/templates/shell-nav.tsx +32 -23
- package/templates/shell-side-nav.tsx +30 -19
- package/templates/shell-top-nav.tsx +81 -43
- package/templates/side-gallery.tsx +39 -93
- package/templates/table-grouped.tsx +63 -33
- package/templates/table-page-chart.tsx +24 -297
- package/templates/table-page-heatmap-status.tsx +157 -91
- package/templates/table-page-shoe-store-heatmap.tsx +19 -288
- package/templates/table-page.tsx +24 -6
- package/templates/table.tsx +24 -7
- package/templates/tech-report.template.mjs +6 -0
- package/templates/tech-report.tsx +1486 -0
- package/templates/theme-showcase.template.mjs +1 -1
- package/templates/theme-showcase.tsx +313 -249
- package/theme.css +76 -14
- package/tokens.css +425 -30
package/templates/settings.tsx
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// L > (LH[divider] > H[a=center wrap] > (SI[fill] > Hd"Settings"[level=1 t=display-2]) + TY"Search") + (LP[w=260 p=2] > UL > LI*6) + (LC[p=6] > V[g=4] > (V[a=center] > TabList) + ((G[c={min:280} g=8] > (V[g=1] > Hd"Basic information"[level=2] + Tx"View and update your details"[t=body]) + (V[g=4] > TI"Username" + TI"Email address" + (H > B.primary"Save"))) + D)*3)
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Settings — one scrolling page of account sections.
|
|
7
|
+
*
|
|
8
|
+
* Frame: Layout header (title + settings search) | optional 260px section nav |
|
|
9
|
+
* content column of three section grids, divided.
|
|
10
|
+
*
|
|
11
|
+
* Responsive contract:
|
|
12
|
+
* > 768px the section nav is a 260px LayoutPanel beside the content
|
|
13
|
+
* <= 768px the panel is dropped, the nav collapses to a centered TabList
|
|
14
|
+
* above the content, the header search wraps under the title, and
|
|
15
|
+
* every section grid falls to one column (280px floor)
|
|
16
|
+
*
|
|
17
|
+
* Seam rule: nav/content whitespace pairs use hasDivider=false (LayoutPanel
|
|
18
|
+
* beside LayoutContent); dialog chrome and inset panels keep their divider.
|
|
19
|
+
*/
|
|
2
20
|
|
|
3
21
|
import {useState} from 'react';
|
|
4
22
|
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
@@ -32,6 +50,12 @@ const NAV_ITEMS = [
|
|
|
32
50
|
'API',
|
|
33
51
|
];
|
|
34
52
|
|
|
53
|
+
// WCAG 1.3.5 wants autocomplete on identity fields. TextInput forwards unknown
|
|
54
|
+
// props to the <input>, but its prop type omits input-only attributes, so the
|
|
55
|
+
// attribute is spread in through a widened record.
|
|
56
|
+
const inputAutoComplete = (value: string) =>
|
|
57
|
+
({autoComplete: value}) as Record<string, string>;
|
|
58
|
+
|
|
35
59
|
const SETTINGS_ITEMS: SearchableItem[] = [
|
|
36
60
|
{id: '1', label: 'Username'},
|
|
37
61
|
{id: '2', label: 'First name'},
|
|
@@ -69,17 +93,17 @@ export default function SettingsTemplate() {
|
|
|
69
93
|
return (
|
|
70
94
|
<Layout
|
|
71
95
|
height="fill"
|
|
72
|
-
contentWidth={
|
|
96
|
+
contentWidth={1200}
|
|
73
97
|
header={
|
|
74
|
-
<LayoutHeader hasDivider>
|
|
75
|
-
<HStack vAlign="center">
|
|
98
|
+
<LayoutHeader hasDivider padding={6}>
|
|
99
|
+
<HStack vAlign="center" wrap="wrap">
|
|
76
100
|
<StackItem size="fill">
|
|
77
|
-
<Heading level={1}>Settings</Heading>
|
|
101
|
+
<Heading level={1} type="display-2">Settings</Heading>
|
|
78
102
|
</StackItem>
|
|
79
103
|
<Typeahead
|
|
80
104
|
label="Search"
|
|
81
105
|
isLabelHidden
|
|
82
|
-
placeholder="Search coven settings
|
|
106
|
+
placeholder="Search coven settings…"
|
|
83
107
|
searchSource={settingsSearchSource}
|
|
84
108
|
value={searchValue}
|
|
85
109
|
onChange={setSearchValue}
|
|
@@ -106,7 +130,7 @@ export default function SettingsTemplate() {
|
|
|
106
130
|
)
|
|
107
131
|
}
|
|
108
132
|
content={
|
|
109
|
-
<LayoutContent padding={
|
|
133
|
+
<LayoutContent padding={6}>
|
|
110
134
|
<VStack gap={4}>
|
|
111
135
|
{/* Mobile: the sidebar nav collapses to a horizontal, centered
|
|
112
136
|
tab bar above the content. */}
|
|
@@ -126,9 +150,9 @@ export default function SettingsTemplate() {
|
|
|
126
150
|
</TabList>
|
|
127
151
|
</VStack>
|
|
128
152
|
)}
|
|
129
|
-
<Grid columns={{minWidth:
|
|
153
|
+
<Grid columns={{minWidth: 280}} gap={8}>
|
|
130
154
|
<VStack gap={1}>
|
|
131
|
-
<Heading level={
|
|
155
|
+
<Heading level={2}>Basic information</Heading>
|
|
132
156
|
<Text type="body" color="secondary">
|
|
133
157
|
View and update your crypt details and coven account information.
|
|
134
158
|
</Text>
|
|
@@ -151,6 +175,8 @@ export default function SettingsTemplate() {
|
|
|
151
175
|
/>
|
|
152
176
|
<TextInput
|
|
153
177
|
label="Email address"
|
|
178
|
+
type="email"
|
|
179
|
+
{...inputAutoComplete('email')}
|
|
154
180
|
value={email}
|
|
155
181
|
onChange={setEmail}
|
|
156
182
|
/>
|
|
@@ -162,9 +188,9 @@ export default function SettingsTemplate() {
|
|
|
162
188
|
|
|
163
189
|
<Divider />
|
|
164
190
|
|
|
165
|
-
<Grid columns={{minWidth:
|
|
191
|
+
<Grid columns={{minWidth: 280}} gap={8}>
|
|
166
192
|
<VStack gap={1}>
|
|
167
|
-
<Heading level={
|
|
193
|
+
<Heading level={2}>Change password</Heading>
|
|
168
194
|
<Text type="body" color="secondary">
|
|
169
195
|
Update your password to keep your coffin sealed.
|
|
170
196
|
</Text>
|
|
@@ -173,18 +199,21 @@ export default function SettingsTemplate() {
|
|
|
173
199
|
<TextInput
|
|
174
200
|
label="Verify current password"
|
|
175
201
|
type="password"
|
|
202
|
+
{...inputAutoComplete('current-password')}
|
|
176
203
|
value={currentPw}
|
|
177
204
|
onChange={setCurrentPw}
|
|
178
205
|
/>
|
|
179
206
|
<TextInput
|
|
180
207
|
label="New password"
|
|
181
208
|
type="password"
|
|
209
|
+
{...inputAutoComplete('new-password')}
|
|
182
210
|
value={newPw}
|
|
183
211
|
onChange={setNewPw}
|
|
184
212
|
/>
|
|
185
213
|
<TextInput
|
|
186
214
|
label="Confirm password"
|
|
187
215
|
type="password"
|
|
216
|
+
{...inputAutoComplete('new-password')}
|
|
188
217
|
value={confirmPw}
|
|
189
218
|
onChange={setConfirmPw}
|
|
190
219
|
/>
|
|
@@ -196,14 +225,14 @@ export default function SettingsTemplate() {
|
|
|
196
225
|
|
|
197
226
|
<Divider />
|
|
198
227
|
|
|
199
|
-
<Grid columns={{minWidth:
|
|
228
|
+
<Grid columns={{minWidth: 280}} gap={8}>
|
|
200
229
|
<VStack gap={1}>
|
|
201
|
-
<Heading level={
|
|
230
|
+
<Heading level={2}>Advanced settings</Heading>
|
|
202
231
|
<Text type="body" color="secondary">
|
|
203
232
|
Configure detailed coven preferences and warding options.
|
|
204
233
|
</Text>
|
|
205
234
|
</VStack>
|
|
206
|
-
<VStack gap={
|
|
235
|
+
<VStack gap={4}>
|
|
207
236
|
<CheckboxInput
|
|
208
237
|
label="Data Export Access"
|
|
209
238
|
description="Allow export of personal data and backups."
|
package/templates/shell-nav.tsx
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// A[cp=0 @topNav=(TN) @sideNav=(SN > TL)] > L > (LH[divider] > H[g=2 wrap] > C.muted[p=0]*3) + (LC[p=6] > V[g=2] > (H[g=3 a=center] > C.muted[p=0] + C.muted[p=0])*3 + C.muted[p=0] + (H[g=3 a=center] > C.muted[p=0] + C.muted[p=0])*4 + C.muted[p=0] + (H[g=3 a=center] > C.muted[p=0] + C.muted[p=0])*5)
|
|
4
|
+
// Note: skeleton placeholders carry no Hd by design (skill skeleton-shell
|
|
5
|
+
// exemption) — routed pages supply the h1.
|
|
2
6
|
|
|
3
7
|
import {Fragment, useState, useMemo, useEffect} from 'react';
|
|
4
8
|
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
@@ -75,9 +79,9 @@ const MENUS: {label: string; groups: MenuEntry[][]}[] = [
|
|
|
75
79
|
['New Window', '⇧⌘N'],
|
|
76
80
|
],
|
|
77
81
|
[
|
|
78
|
-
['Open
|
|
82
|
+
['Open…', '⌘O'],
|
|
79
83
|
['Save', '⌘S'],
|
|
80
|
-
['Save As
|
|
84
|
+
['Save As…', '⇧⌘S'],
|
|
81
85
|
],
|
|
82
86
|
[['Close Editor', '⌘W']],
|
|
83
87
|
],
|
|
@@ -139,20 +143,20 @@ const MENUS: {label: string; groups: MenuEntry[][]}[] = [
|
|
|
139
143
|
];
|
|
140
144
|
|
|
141
145
|
const CODE_LINES = [
|
|
142
|
-
'38%',
|
|
143
|
-
'62%',
|
|
144
|
-
'54%',
|
|
145
|
-
'0%',
|
|
146
|
-
'46%',
|
|
147
|
-
'70%',
|
|
148
|
-
'58%',
|
|
149
|
-
'34%',
|
|
150
|
-
'0%',
|
|
151
|
-
'50%',
|
|
152
|
-
'66%',
|
|
153
|
-
'42%',
|
|
154
|
-
'60%',
|
|
155
|
-
'28%',
|
|
146
|
+
{id: 'line-1', width: '38%'},
|
|
147
|
+
{id: 'line-2', width: '62%'},
|
|
148
|
+
{id: 'line-3', width: '54%'},
|
|
149
|
+
{id: 'line-4', width: '0%'},
|
|
150
|
+
{id: 'line-5', width: '46%'},
|
|
151
|
+
{id: 'line-6', width: '70%'},
|
|
152
|
+
{id: 'line-7', width: '58%'},
|
|
153
|
+
{id: 'line-8', width: '34%'},
|
|
154
|
+
{id: 'line-9', width: '0%'},
|
|
155
|
+
{id: 'line-10', width: '50%'},
|
|
156
|
+
{id: 'line-11', width: '66%'},
|
|
157
|
+
{id: 'line-12', width: '42%'},
|
|
158
|
+
{id: 'line-13', width: '60%'},
|
|
159
|
+
{id: 'line-14', width: '28%'},
|
|
156
160
|
];
|
|
157
161
|
|
|
158
162
|
const EDITOR_TABS = ['AppShell.tsx', 'TopNav.tsx', 'theme.ts'];
|
|
@@ -201,8 +205,11 @@ export default function ShellNav() {
|
|
|
201
205
|
button={{label: menu.label, variant: 'ghost', size: 'sm'}}
|
|
202
206
|
hasChevron={false}
|
|
203
207
|
menuWidth={MENU_WIDTH}>
|
|
208
|
+
{/* Keyed by the group's first label, not its position:
|
|
209
|
+
an index key remounts every group when the menu
|
|
210
|
+
data shifts. `gi` only decides the divider. */}
|
|
204
211
|
{menu.groups.map((group, gi) => (
|
|
205
|
-
<Fragment key={
|
|
212
|
+
<Fragment key={group[0][0]}>
|
|
206
213
|
{gi > 0 && <Divider />}
|
|
207
214
|
{group.map(([label, shortcut]) => (
|
|
208
215
|
<DropdownMenuItem
|
|
@@ -247,7 +254,8 @@ export default function ShellNav() {
|
|
|
247
254
|
height="fill"
|
|
248
255
|
header={
|
|
249
256
|
<LayoutHeader hasDivider padding={6}>
|
|
250
|
-
|
|
257
|
+
{/* Wraps rather than clipping: three 132px tabs need ~410px. */}
|
|
258
|
+
<HStack gap={2} wrap="wrap">
|
|
251
259
|
{EDITOR_TABS.map(tab => (
|
|
252
260
|
<Card
|
|
253
261
|
key={tab}
|
|
@@ -262,18 +270,19 @@ export default function ShellNav() {
|
|
|
262
270
|
}
|
|
263
271
|
content={
|
|
264
272
|
<LayoutContent padding={6}>
|
|
273
|
+
{/* Skeleton chrome only — the open file supplies the h1. */}
|
|
265
274
|
<VStack gap={2}>
|
|
266
|
-
{CODE_LINES.map(
|
|
267
|
-
width === '0%' ? (
|
|
275
|
+
{CODE_LINES.map(line =>
|
|
276
|
+
line.width === '0%' ? (
|
|
268
277
|
<Card
|
|
269
|
-
key={
|
|
278
|
+
key={line.id}
|
|
270
279
|
variant="muted"
|
|
271
280
|
padding={0}
|
|
272
281
|
width={1}
|
|
273
282
|
height={14}
|
|
274
283
|
/>
|
|
275
284
|
) : (
|
|
276
|
-
<HStack key={
|
|
285
|
+
<HStack key={line.id} gap={3} vAlign="center">
|
|
277
286
|
<Card
|
|
278
287
|
variant="muted"
|
|
279
288
|
padding={0}
|
|
@@ -283,7 +292,7 @@ export default function ShellNav() {
|
|
|
283
292
|
<Card
|
|
284
293
|
variant="muted"
|
|
285
294
|
padding={0}
|
|
286
|
-
width={width}
|
|
295
|
+
width={line.width}
|
|
287
296
|
height={14}
|
|
288
297
|
/>
|
|
289
298
|
</HStack>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// A[cp=0 @sideNav=(SN > SNI"New chat" + SNI"Search" + SNI"Library" + D + (SNI"Personal" > SNI"Full-moon rite planning")*3)] > L > (LC[p=6] > V[g=5] > (H > C.muted[p=0])*4) + (LF > TI"Message Night Owl")
|
|
4
|
+
// Note: skeleton placeholders carry no Hd by design (skill skeleton-shell
|
|
5
|
+
// exemption) — routed pages supply the h1.
|
|
2
6
|
|
|
3
7
|
import {useState} from 'react';
|
|
4
8
|
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
@@ -48,39 +52,43 @@ const WORKSPACES: Workspace[] = [
|
|
|
48
52
|
icon: User,
|
|
49
53
|
chats: [
|
|
50
54
|
{
|
|
51
|
-
label: '
|
|
55
|
+
label: 'Full-moon rite planning',
|
|
52
56
|
status: 'success',
|
|
53
57
|
statusLabel: 'Active',
|
|
54
58
|
},
|
|
55
59
|
{
|
|
56
|
-
label: '
|
|
60
|
+
label: 'Potion recipes for the week',
|
|
57
61
|
status: 'neutral',
|
|
58
62
|
statusLabel: 'Idle',
|
|
59
63
|
},
|
|
60
64
|
{
|
|
61
|
-
label: '
|
|
65
|
+
label: 'Grimoire recommendations',
|
|
62
66
|
status: 'warning',
|
|
63
67
|
statusLabel: 'Needs review',
|
|
64
68
|
},
|
|
65
|
-
{label: '
|
|
69
|
+
{label: 'Crypt workout plan', status: 'neutral', statusLabel: 'Idle'},
|
|
66
70
|
],
|
|
67
71
|
},
|
|
68
72
|
{
|
|
69
|
-
name: '
|
|
73
|
+
name: 'Night Watch',
|
|
70
74
|
icon: Building2,
|
|
71
75
|
chats: [
|
|
72
|
-
{label: 'Q3 roadmap draft', status: 'accent', statusLabel: 'In progress'},
|
|
73
76
|
{
|
|
74
|
-
label: '
|
|
77
|
+
label: 'Crypt expansion draft',
|
|
78
|
+
status: 'info',
|
|
79
|
+
statusLabel: 'In progress',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: 'Neonate welcoming rite',
|
|
75
83
|
status: 'success',
|
|
76
84
|
statusLabel: 'Active',
|
|
77
85
|
},
|
|
78
86
|
{
|
|
79
|
-
label: '
|
|
87
|
+
label: 'Tribute ledger review',
|
|
80
88
|
status: 'warning',
|
|
81
89
|
statusLabel: 'Needs review',
|
|
82
90
|
},
|
|
83
|
-
{label: '
|
|
91
|
+
{label: 'Night-watch summary', status: 'neutral', statusLabel: 'Idle'},
|
|
84
92
|
],
|
|
85
93
|
},
|
|
86
94
|
{
|
|
@@ -88,12 +96,12 @@ const WORKSPACES: Workspace[] = [
|
|
|
88
96
|
icon: CodeXml,
|
|
89
97
|
chats: [
|
|
90
98
|
{
|
|
91
|
-
label: '
|
|
92
|
-
status: '
|
|
99
|
+
label: 'Coven theme migration notes',
|
|
100
|
+
status: 'info',
|
|
93
101
|
statusLabel: 'In progress',
|
|
94
102
|
},
|
|
95
103
|
{
|
|
96
|
-
label: '
|
|
104
|
+
label: 'Crypt loading states',
|
|
97
105
|
status: 'success',
|
|
98
106
|
statusLabel: 'Active',
|
|
99
107
|
},
|
|
@@ -103,16 +111,18 @@ const WORKSPACES: Workspace[] = [
|
|
|
103
111
|
},
|
|
104
112
|
];
|
|
105
113
|
|
|
106
|
-
const SELECTED_CHAT = '
|
|
114
|
+
const SELECTED_CHAT = 'Coven theme migration notes';
|
|
107
115
|
// Same-route hash: demo links stay focusable anchors without escaping the
|
|
108
116
|
// template through the hash router (bare "#" would drop back to the home page).
|
|
109
117
|
const SELF_HASH = '#/templates/shell-side-nav';
|
|
110
118
|
|
|
119
|
+
// `id` carries the React key: the bubbles are positional placeholders, and an
|
|
120
|
+
// index key would remount every row below an inserted one.
|
|
111
121
|
const MESSAGES = [
|
|
112
|
-
{role: 'assistant', width: '78%', height: 104},
|
|
113
|
-
{role: 'user', width: '48%', height: 48},
|
|
114
|
-
{role: 'assistant', width: '64%', height: 132},
|
|
115
|
-
{role: 'user', width: '38%', height: 40},
|
|
122
|
+
{id: 'context', role: 'assistant', width: '78%', height: 104},
|
|
123
|
+
{id: 'follow-up', role: 'user', width: '48%', height: 48},
|
|
124
|
+
{id: 'answer', role: 'assistant', width: '64%', height: 132},
|
|
125
|
+
{id: 'reply', role: 'user', width: '38%', height: 40},
|
|
116
126
|
];
|
|
117
127
|
|
|
118
128
|
function ConversationItem({
|
|
@@ -204,10 +214,11 @@ export default function ShellSideNav() {
|
|
|
204
214
|
contentWidth={768}
|
|
205
215
|
content={
|
|
206
216
|
<LayoutContent padding={6}>
|
|
217
|
+
{/* Skeleton chrome only — the routed conversation supplies the h1. */}
|
|
207
218
|
<VStack gap={5}>
|
|
208
|
-
{MESSAGES.map(
|
|
219
|
+
{MESSAGES.map(message => (
|
|
209
220
|
<HStack
|
|
210
|
-
key={
|
|
221
|
+
key={message.id}
|
|
211
222
|
hAlign={message.role === 'assistant' ? 'start' : 'end'}>
|
|
212
223
|
<Card
|
|
213
224
|
variant="muted"
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// A[cp=6 @topNav=(TN > TNI"Shop" + TNI"Brands" + TNI"Sale" + TNI"Service")] > Ctr[axis=horizontal] > S[mw=1100 w=100% p=0] > V[g=10] > C.muted[p=0 w=100% h=360] + (V[g=4] > C.muted[p=0 w=200 h=24] + (G[c={min:160} g=4] > (V[g=2] > C.muted[p=0 w=100% h=120] + C.muted[p=0 w=60% h=14])*6))*3
|
|
4
|
+
// Note: skeleton placeholders carry no Hd by design (skill skeleton-shell
|
|
5
|
+
// exemption) — routed pages supply the h1.
|
|
2
6
|
|
|
3
7
|
import type {CSSProperties} from 'react';
|
|
4
8
|
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
@@ -19,7 +23,9 @@ import {IconButton} from '@astryxdesign/core/IconButton';
|
|
|
19
23
|
import {Button} from '@astryxdesign/core/Button';
|
|
20
24
|
import {Badge} from '@astryxdesign/core/Badge';
|
|
21
25
|
import {Card} from '@astryxdesign/core/Card';
|
|
26
|
+
import {Center} from '@astryxdesign/core/Center';
|
|
22
27
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
28
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
23
29
|
import {Stack, VStack} from '@astryxdesign/core/Stack';
|
|
24
30
|
import {
|
|
25
31
|
ShoppingBag,
|
|
@@ -42,7 +48,7 @@ import {
|
|
|
42
48
|
} from 'lucide-react';
|
|
43
49
|
|
|
44
50
|
// Cap + center the page body so wide screens show whitespace gutters.
|
|
45
|
-
const
|
|
51
|
+
const CONTENT_MAX_WIDTH = 1100;
|
|
46
52
|
// Same-route hash: demo links stay focusable anchors without escaping the
|
|
47
53
|
// template through the hash router (bare "#" would drop back to the home page).
|
|
48
54
|
const SELF_HASH = '#/templates/shell-top-nav';
|
|
@@ -50,51 +56,60 @@ const SELF_HASH = '#/templates/shell-top-nav';
|
|
|
50
56
|
// Brands size to their own content (different widths); since both anchor to
|
|
51
57
|
// the centered nav, switching between them resizes the panel — which reads
|
|
52
58
|
// as flashing/jumping. Fixed item + featured widths make the panels
|
|
53
|
-
// pixel-identical so the transition is seamless.
|
|
59
|
+
// pixel-identical so the transition is seamless. The pair needs roughly
|
|
60
|
+
// 800px of clear space, so narrower viewports fall back to natural-width
|
|
61
|
+
// single columns (see MegaItems) instead of painting past the edge.
|
|
54
62
|
const megaItems: CSSProperties = {gridColumn: '1 / -1', width: 520};
|
|
55
63
|
const megaFeatured: CSSProperties = {width: 240};
|
|
56
64
|
|
|
65
|
+
// Three identical shelves; the ids keep React keys stable if they reorder.
|
|
66
|
+
const SHELVES = ['new-in', 'featured', 'sale'];
|
|
67
|
+
|
|
57
68
|
type MegaItem = {name: string; tagline: string; icon: IconType};
|
|
58
69
|
|
|
59
70
|
// Shop and Brands each render 8 items — the mega menu's built-in 2-column grid
|
|
60
71
|
// lays them out as 2 columns × 4 rows, alongside a featured card.
|
|
61
72
|
const SHOP_ITEMS: MegaItem[] = [
|
|
62
|
-
{name: 'New Arrivals', tagline: '
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
{name: 'New Moon Arrivals', tagline: 'Fresh from the kiln', icon: Sparkles},
|
|
74
|
+
{
|
|
75
|
+
name: 'Gowns & Cloaks',
|
|
76
|
+
tagline: 'Dresses, knitwear & moonlit layers',
|
|
77
|
+
icon: SwatchBook,
|
|
78
|
+
},
|
|
79
|
+
{name: 'Doublets & Tailoring', tagline: 'Shirts, cloaks & more', icon: Tag},
|
|
80
|
+
{name: 'Keep & Crypt', tagline: 'Bedding, candlelight & décor', icon: House},
|
|
66
81
|
{
|
|
67
|
-
name: '
|
|
68
|
-
tagline: '
|
|
82
|
+
name: 'Potions',
|
|
83
|
+
tagline: 'Salves, scents & powders',
|
|
69
84
|
icon: Smile,
|
|
70
85
|
},
|
|
71
86
|
{
|
|
72
|
-
name: '
|
|
73
|
-
tagline: '
|
|
87
|
+
name: 'Relics',
|
|
88
|
+
tagline: 'Satchels, hats & moonshades',
|
|
74
89
|
icon: ShoppingBag,
|
|
75
90
|
},
|
|
76
91
|
{name: 'Sale', tagline: 'Up to 50% off', icon: BadgePercent},
|
|
77
|
-
{name: 'Gift
|
|
92
|
+
{name: 'Gift Tokens', tagline: 'The perfect tribute', icon: Gift},
|
|
78
93
|
];
|
|
79
94
|
|
|
80
95
|
const BRAND_ITEMS: MegaItem[] = [
|
|
81
|
-
{name: 'Aether', tagline: '
|
|
82
|
-
{name: '
|
|
83
|
-
{name: 'Loomwell', tagline: 'Everyday knitwear', icon: Zap},
|
|
84
|
-
{name: 'Verdant', tagline: '
|
|
85
|
-
{name: 'Studio Mara', tagline: 'Modern tailoring', icon: Star},
|
|
86
|
-
{name: 'Atelier Kos', tagline: '
|
|
87
|
-
{name: 'Rue & Co', tagline: '
|
|
88
|
-
{name: 'Halden', tagline: '
|
|
96
|
+
{name: 'Aether', tagline: 'Moonlit essentials', icon: Sparkles},
|
|
97
|
+
{name: 'Northcrypt', tagline: 'Cold-weather & travel', icon: Cloud},
|
|
98
|
+
{name: 'Loomwell', tagline: 'Everyday knitwear, coven-stitched', icon: Zap},
|
|
99
|
+
{name: 'Verdant', tagline: 'Earth-kept basics', icon: Sun},
|
|
100
|
+
{name: 'Studio Mara', tagline: 'Modern tailoring for the night', icon: Star},
|
|
101
|
+
{name: 'Atelier Kos', tagline: 'Small-batch ateliers', icon: Flame},
|
|
102
|
+
{name: 'Rue & Co', tagline: 'Old-town streetwear', icon: Globe},
|
|
103
|
+
{name: 'Halden', tagline: 'Spare, lasting staples', icon: Moon},
|
|
89
104
|
];
|
|
90
105
|
|
|
91
106
|
const CATEGORY_TILES = [
|
|
92
|
-
'New Arrivals',
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
107
|
+
'New Moon Arrivals',
|
|
108
|
+
'Gowns & Cloaks',
|
|
109
|
+
'Doublets & Tailoring',
|
|
110
|
+
'Keep & Crypt',
|
|
111
|
+
'Potions',
|
|
112
|
+
'Relics',
|
|
98
113
|
];
|
|
99
114
|
|
|
100
115
|
// Wraps the 8 items in a fixed-width 2-column grid so every mega menu's item
|
|
@@ -102,9 +117,12 @@ const CATEGORY_TILES = [
|
|
|
102
117
|
function MegaItems({items}: {items: MegaItem[]}) {
|
|
103
118
|
// In the mobile drawer the fixed 520px panel would overflow the ~350px
|
|
104
119
|
// drawer, and the 2-column grid overlaps item text there — so the drawer
|
|
105
|
-
// gets a natural-width single column.
|
|
120
|
+
// gets a natural-width single column. The same goes for viewports too
|
|
121
|
+
// narrow to seat the locked desktop panel, which would otherwise paint
|
|
122
|
+
// past the edge: below 1024px the popover keeps the natural-width column.
|
|
106
123
|
const isDrawer = useTopNavRenderMode() === 'drawer';
|
|
107
|
-
|
|
124
|
+
const isCompact = useMediaQuery('(max-width: 1024px)');
|
|
125
|
+
if (isDrawer || isCompact) {
|
|
108
126
|
return (
|
|
109
127
|
<VStack gap={1}>
|
|
110
128
|
{items.map(item => (
|
|
@@ -144,8 +162,9 @@ function MegaFeatured(props: {
|
|
|
144
162
|
linkHref: string;
|
|
145
163
|
}) {
|
|
146
164
|
const isDrawer = useTopNavRenderMode() === 'drawer';
|
|
165
|
+
const isCompact = useMediaQuery('(max-width: 1024px)');
|
|
147
166
|
return (
|
|
148
|
-
<Stack style={isDrawer ? undefined : megaFeatured}>
|
|
167
|
+
<Stack style={isDrawer || isCompact ? undefined : megaFeatured}>
|
|
149
168
|
<TopNavMegaMenuFeaturedCard {...props} />
|
|
150
169
|
</Stack>
|
|
151
170
|
);
|
|
@@ -221,23 +240,42 @@ export default function ShellTopNav() {
|
|
|
221
240
|
}
|
|
222
241
|
/>
|
|
223
242
|
}>
|
|
224
|
-
<
|
|
225
|
-
<
|
|
243
|
+
<Center axis="horizontal">
|
|
244
|
+
<Section
|
|
245
|
+
variant="transparent"
|
|
246
|
+
maxWidth={CONTENT_MAX_WIDTH}
|
|
247
|
+
width="100%"
|
|
248
|
+
padding={0}>
|
|
249
|
+
<VStack gap={10}>
|
|
250
|
+
{/* Skeleton shelves only — the routed collection supplies the h1. */}
|
|
251
|
+
<Card variant="muted" padding={0} width="100%" height={360} />
|
|
226
252
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
253
|
+
{SHELVES.map(shelf => (
|
|
254
|
+
<VStack key={shelf} gap={4}>
|
|
255
|
+
<Card variant="muted" padding={0} width={200} height={24} />
|
|
256
|
+
<Grid columns={{minWidth: 160, repeat: 'fit'}} gap={4}>
|
|
257
|
+
{CATEGORY_TILES.map(tile => (
|
|
258
|
+
<VStack key={tile} gap={2}>
|
|
259
|
+
<Card
|
|
260
|
+
variant="muted"
|
|
261
|
+
padding={0}
|
|
262
|
+
width="100%"
|
|
263
|
+
height={120}
|
|
264
|
+
/>
|
|
265
|
+
<Card
|
|
266
|
+
variant="muted"
|
|
267
|
+
padding={0}
|
|
268
|
+
width="60%"
|
|
269
|
+
height={14}
|
|
270
|
+
/>
|
|
271
|
+
</VStack>
|
|
272
|
+
))}
|
|
273
|
+
</Grid>
|
|
274
|
+
</VStack>
|
|
275
|
+
))}
|
|
238
276
|
</VStack>
|
|
239
|
-
|
|
240
|
-
</
|
|
277
|
+
</Section>
|
|
278
|
+
</Center>
|
|
241
279
|
</AppShell>
|
|
242
280
|
);
|
|
243
281
|
}
|