astryx-dracula 0.2.1 → 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 +17 -12
- 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 +66 -33
- package/templates/ai-chat.tsx +36 -19
- package/templates/blank.tsx +4 -3
- package/templates/centered-hero.tsx +14 -125
- package/templates/classic-gallery.tsx +20 -66
- package/templates/contact-form.tsx +59 -39
- package/templates/dashboard-portfolio.tsx +143 -226
- package/templates/dashboard.tsx +122 -122
- package/templates/detail-page.tsx +95 -91
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +83 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +29 -19
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +52 -74
- package/templates/editor.tsx +112 -84
- package/templates/file-explorer.tsx +131 -84
- package/templates/form-two-column.tsx +61 -125
- package/templates/gallery-hero.tsx +15 -171
- package/templates/ide.tsx +22 -20
- package/templates/incident-console.tsx +104 -70
- package/templates/kanban-board.tsx +99 -42
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +37 -42
- package/templates/login-card.tsx +58 -87
- package/templates/login-split.tsx +88 -126
- package/templates/login-sso.tsx +105 -64
- package/templates/login.tsx +107 -53
- package/templates/messaging-shell.tsx +171 -144
- package/templates/mixed-gallery.tsx +29 -130
- package/templates/payment-form.tsx +368 -290
- package/templates/product-detail.tsx +39 -58
- package/templates/product-gallery.tsx +30 -41
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +135 -282
- package/templates/settings-sidebar.tsx +178 -268
- package/templates/settings.tsx +41 -14
- package/templates/shell-nav.tsx +31 -24
- package/templates/shell-side-nav.tsx +29 -20
- package/templates/shell-top-nav.tsx +80 -44
- package/templates/side-gallery.tsx +38 -94
- package/templates/table-grouped.tsx +62 -34
- package/templates/table-page-chart.tsx +23 -302
- package/templates/table-page-heatmap-status.tsx +156 -92
- package/templates/table-page-shoe-store-heatmap.tsx +18 -293
- package/templates/table-page.tsx +23 -7
- package/templates/table.tsx +23 -8
- 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 +299 -237
- package/theme.css +76 -14
- package/tokens.css +425 -30
package/templates/settings.tsx
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
//
|
|
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
|
+
*/
|
|
4
20
|
|
|
5
21
|
import {useState} from 'react';
|
|
6
22
|
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
@@ -34,6 +50,12 @@ const NAV_ITEMS = [
|
|
|
34
50
|
'API',
|
|
35
51
|
];
|
|
36
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
|
+
|
|
37
59
|
const SETTINGS_ITEMS: SearchableItem[] = [
|
|
38
60
|
{id: '1', label: 'Username'},
|
|
39
61
|
{id: '2', label: 'First name'},
|
|
@@ -71,17 +93,17 @@ export default function SettingsTemplate() {
|
|
|
71
93
|
return (
|
|
72
94
|
<Layout
|
|
73
95
|
height="fill"
|
|
74
|
-
contentWidth={
|
|
96
|
+
contentWidth={1200}
|
|
75
97
|
header={
|
|
76
|
-
<LayoutHeader hasDivider>
|
|
77
|
-
<HStack vAlign="center">
|
|
98
|
+
<LayoutHeader hasDivider padding={6}>
|
|
99
|
+
<HStack vAlign="center" wrap="wrap">
|
|
78
100
|
<StackItem size="fill">
|
|
79
|
-
<Heading level={1}>Settings</Heading>
|
|
101
|
+
<Heading level={1} type="display-2">Settings</Heading>
|
|
80
102
|
</StackItem>
|
|
81
103
|
<Typeahead
|
|
82
104
|
label="Search"
|
|
83
105
|
isLabelHidden
|
|
84
|
-
placeholder="Search coven settings
|
|
106
|
+
placeholder="Search coven settings…"
|
|
85
107
|
searchSource={settingsSearchSource}
|
|
86
108
|
value={searchValue}
|
|
87
109
|
onChange={setSearchValue}
|
|
@@ -108,7 +130,7 @@ export default function SettingsTemplate() {
|
|
|
108
130
|
)
|
|
109
131
|
}
|
|
110
132
|
content={
|
|
111
|
-
<LayoutContent padding={
|
|
133
|
+
<LayoutContent padding={6}>
|
|
112
134
|
<VStack gap={4}>
|
|
113
135
|
{/* Mobile: the sidebar nav collapses to a horizontal, centered
|
|
114
136
|
tab bar above the content. */}
|
|
@@ -128,9 +150,9 @@ export default function SettingsTemplate() {
|
|
|
128
150
|
</TabList>
|
|
129
151
|
</VStack>
|
|
130
152
|
)}
|
|
131
|
-
<Grid columns={{minWidth:
|
|
153
|
+
<Grid columns={{minWidth: 280}} gap={8}>
|
|
132
154
|
<VStack gap={1}>
|
|
133
|
-
<Heading level={
|
|
155
|
+
<Heading level={2}>Basic information</Heading>
|
|
134
156
|
<Text type="body" color="secondary">
|
|
135
157
|
View and update your crypt details and coven account information.
|
|
136
158
|
</Text>
|
|
@@ -153,6 +175,8 @@ export default function SettingsTemplate() {
|
|
|
153
175
|
/>
|
|
154
176
|
<TextInput
|
|
155
177
|
label="Email address"
|
|
178
|
+
type="email"
|
|
179
|
+
{...inputAutoComplete('email')}
|
|
156
180
|
value={email}
|
|
157
181
|
onChange={setEmail}
|
|
158
182
|
/>
|
|
@@ -164,9 +188,9 @@ export default function SettingsTemplate() {
|
|
|
164
188
|
|
|
165
189
|
<Divider />
|
|
166
190
|
|
|
167
|
-
<Grid columns={{minWidth:
|
|
191
|
+
<Grid columns={{minWidth: 280}} gap={8}>
|
|
168
192
|
<VStack gap={1}>
|
|
169
|
-
<Heading level={
|
|
193
|
+
<Heading level={2}>Change password</Heading>
|
|
170
194
|
<Text type="body" color="secondary">
|
|
171
195
|
Update your password to keep your coffin sealed.
|
|
172
196
|
</Text>
|
|
@@ -175,18 +199,21 @@ export default function SettingsTemplate() {
|
|
|
175
199
|
<TextInput
|
|
176
200
|
label="Verify current password"
|
|
177
201
|
type="password"
|
|
202
|
+
{...inputAutoComplete('current-password')}
|
|
178
203
|
value={currentPw}
|
|
179
204
|
onChange={setCurrentPw}
|
|
180
205
|
/>
|
|
181
206
|
<TextInput
|
|
182
207
|
label="New password"
|
|
183
208
|
type="password"
|
|
209
|
+
{...inputAutoComplete('new-password')}
|
|
184
210
|
value={newPw}
|
|
185
211
|
onChange={setNewPw}
|
|
186
212
|
/>
|
|
187
213
|
<TextInput
|
|
188
214
|
label="Confirm password"
|
|
189
215
|
type="password"
|
|
216
|
+
{...inputAutoComplete('new-password')}
|
|
190
217
|
value={confirmPw}
|
|
191
218
|
onChange={setConfirmPw}
|
|
192
219
|
/>
|
|
@@ -198,14 +225,14 @@ export default function SettingsTemplate() {
|
|
|
198
225
|
|
|
199
226
|
<Divider />
|
|
200
227
|
|
|
201
|
-
<Grid columns={{minWidth:
|
|
228
|
+
<Grid columns={{minWidth: 280}} gap={8}>
|
|
202
229
|
<VStack gap={1}>
|
|
203
|
-
<Heading level={
|
|
230
|
+
<Heading level={2}>Advanced settings</Heading>
|
|
204
231
|
<Text type="body" color="secondary">
|
|
205
232
|
Configure detailed coven preferences and warding options.
|
|
206
233
|
</Text>
|
|
207
234
|
</VStack>
|
|
208
|
-
<VStack gap={
|
|
235
|
+
<VStack gap={4}>
|
|
209
236
|
<CheckboxInput
|
|
210
237
|
label="Data Export Access"
|
|
211
238
|
description="Allow export of personal data and backups."
|
package/templates/shell-nav.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
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] > 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
|
+
// 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.
|
|
4
6
|
|
|
5
7
|
import {Fragment, useState, useMemo, useEffect} from 'react';
|
|
6
8
|
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
@@ -77,9 +79,9 @@ const MENUS: {label: string; groups: MenuEntry[][]}[] = [
|
|
|
77
79
|
['New Window', '⇧⌘N'],
|
|
78
80
|
],
|
|
79
81
|
[
|
|
80
|
-
['Open
|
|
82
|
+
['Open…', '⌘O'],
|
|
81
83
|
['Save', '⌘S'],
|
|
82
|
-
['Save As
|
|
84
|
+
['Save As…', '⇧⌘S'],
|
|
83
85
|
],
|
|
84
86
|
[['Close Editor', '⌘W']],
|
|
85
87
|
],
|
|
@@ -141,20 +143,20 @@ const MENUS: {label: string; groups: MenuEntry[][]}[] = [
|
|
|
141
143
|
];
|
|
142
144
|
|
|
143
145
|
const CODE_LINES = [
|
|
144
|
-
'38%',
|
|
145
|
-
'62%',
|
|
146
|
-
'54%',
|
|
147
|
-
'0%',
|
|
148
|
-
'46%',
|
|
149
|
-
'70%',
|
|
150
|
-
'58%',
|
|
151
|
-
'34%',
|
|
152
|
-
'0%',
|
|
153
|
-
'50%',
|
|
154
|
-
'66%',
|
|
155
|
-
'42%',
|
|
156
|
-
'60%',
|
|
157
|
-
'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%'},
|
|
158
160
|
];
|
|
159
161
|
|
|
160
162
|
const EDITOR_TABS = ['AppShell.tsx', 'TopNav.tsx', 'theme.ts'];
|
|
@@ -203,8 +205,11 @@ export default function ShellNav() {
|
|
|
203
205
|
button={{label: menu.label, variant: 'ghost', size: 'sm'}}
|
|
204
206
|
hasChevron={false}
|
|
205
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. */}
|
|
206
211
|
{menu.groups.map((group, gi) => (
|
|
207
|
-
<Fragment key={
|
|
212
|
+
<Fragment key={group[0][0]}>
|
|
208
213
|
{gi > 0 && <Divider />}
|
|
209
214
|
{group.map(([label, shortcut]) => (
|
|
210
215
|
<DropdownMenuItem
|
|
@@ -249,7 +254,8 @@ export default function ShellNav() {
|
|
|
249
254
|
height="fill"
|
|
250
255
|
header={
|
|
251
256
|
<LayoutHeader hasDivider padding={6}>
|
|
252
|
-
|
|
257
|
+
{/* Wraps rather than clipping: three 132px tabs need ~410px. */}
|
|
258
|
+
<HStack gap={2} wrap="wrap">
|
|
253
259
|
{EDITOR_TABS.map(tab => (
|
|
254
260
|
<Card
|
|
255
261
|
key={tab}
|
|
@@ -264,18 +270,19 @@ export default function ShellNav() {
|
|
|
264
270
|
}
|
|
265
271
|
content={
|
|
266
272
|
<LayoutContent padding={6}>
|
|
273
|
+
{/* Skeleton chrome only — the open file supplies the h1. */}
|
|
267
274
|
<VStack gap={2}>
|
|
268
|
-
{CODE_LINES.map(
|
|
269
|
-
width === '0%' ? (
|
|
275
|
+
{CODE_LINES.map(line =>
|
|
276
|
+
line.width === '0%' ? (
|
|
270
277
|
<Card
|
|
271
|
-
key={
|
|
278
|
+
key={line.id}
|
|
272
279
|
variant="muted"
|
|
273
280
|
padding={0}
|
|
274
281
|
width={1}
|
|
275
282
|
height={14}
|
|
276
283
|
/>
|
|
277
284
|
) : (
|
|
278
|
-
<HStack key={
|
|
285
|
+
<HStack key={line.id} gap={3} vAlign="center">
|
|
279
286
|
<Card
|
|
280
287
|
variant="muted"
|
|
281
288
|
padding={0}
|
|
@@ -285,7 +292,7 @@ export default function ShellNav() {
|
|
|
285
292
|
<Card
|
|
286
293
|
variant="muted"
|
|
287
294
|
padding={0}
|
|
288
|
-
width={width}
|
|
295
|
+
width={line.width}
|
|
289
296
|
height={14}
|
|
290
297
|
/>
|
|
291
298
|
</HStack>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
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"
|
|
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.
|
|
4
6
|
|
|
5
7
|
import {useState} from 'react';
|
|
6
8
|
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
@@ -50,39 +52,43 @@ const WORKSPACES: Workspace[] = [
|
|
|
50
52
|
icon: User,
|
|
51
53
|
chats: [
|
|
52
54
|
{
|
|
53
|
-
label: '
|
|
55
|
+
label: 'Full-moon rite planning',
|
|
54
56
|
status: 'success',
|
|
55
57
|
statusLabel: 'Active',
|
|
56
58
|
},
|
|
57
59
|
{
|
|
58
|
-
label: '
|
|
60
|
+
label: 'Potion recipes for the week',
|
|
59
61
|
status: 'neutral',
|
|
60
62
|
statusLabel: 'Idle',
|
|
61
63
|
},
|
|
62
64
|
{
|
|
63
|
-
label: '
|
|
65
|
+
label: 'Grimoire recommendations',
|
|
64
66
|
status: 'warning',
|
|
65
67
|
statusLabel: 'Needs review',
|
|
66
68
|
},
|
|
67
|
-
{label: '
|
|
69
|
+
{label: 'Crypt workout plan', status: 'neutral', statusLabel: 'Idle'},
|
|
68
70
|
],
|
|
69
71
|
},
|
|
70
72
|
{
|
|
71
|
-
name: '
|
|
73
|
+
name: 'Night Watch',
|
|
72
74
|
icon: Building2,
|
|
73
75
|
chats: [
|
|
74
|
-
{label: 'Q3 roadmap draft', status: 'accent', statusLabel: 'In progress'},
|
|
75
76
|
{
|
|
76
|
-
label: '
|
|
77
|
+
label: 'Crypt expansion draft',
|
|
78
|
+
status: 'info',
|
|
79
|
+
statusLabel: 'In progress',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: 'Neonate welcoming rite',
|
|
77
83
|
status: 'success',
|
|
78
84
|
statusLabel: 'Active',
|
|
79
85
|
},
|
|
80
86
|
{
|
|
81
|
-
label: '
|
|
87
|
+
label: 'Tribute ledger review',
|
|
82
88
|
status: 'warning',
|
|
83
89
|
statusLabel: 'Needs review',
|
|
84
90
|
},
|
|
85
|
-
{label: '
|
|
91
|
+
{label: 'Night-watch summary', status: 'neutral', statusLabel: 'Idle'},
|
|
86
92
|
],
|
|
87
93
|
},
|
|
88
94
|
{
|
|
@@ -90,12 +96,12 @@ const WORKSPACES: Workspace[] = [
|
|
|
90
96
|
icon: CodeXml,
|
|
91
97
|
chats: [
|
|
92
98
|
{
|
|
93
|
-
label: '
|
|
94
|
-
status: '
|
|
99
|
+
label: 'Coven theme migration notes',
|
|
100
|
+
status: 'info',
|
|
95
101
|
statusLabel: 'In progress',
|
|
96
102
|
},
|
|
97
103
|
{
|
|
98
|
-
label: '
|
|
104
|
+
label: 'Crypt loading states',
|
|
99
105
|
status: 'success',
|
|
100
106
|
statusLabel: 'Active',
|
|
101
107
|
},
|
|
@@ -105,16 +111,18 @@ const WORKSPACES: Workspace[] = [
|
|
|
105
111
|
},
|
|
106
112
|
];
|
|
107
113
|
|
|
108
|
-
const SELECTED_CHAT = '
|
|
114
|
+
const SELECTED_CHAT = 'Coven theme migration notes';
|
|
109
115
|
// Same-route hash: demo links stay focusable anchors without escaping the
|
|
110
116
|
// template through the hash router (bare "#" would drop back to the home page).
|
|
111
117
|
const SELF_HASH = '#/templates/shell-side-nav';
|
|
112
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.
|
|
113
121
|
const MESSAGES = [
|
|
114
|
-
{role: 'assistant', width: '78%', height: 104},
|
|
115
|
-
{role: 'user', width: '48%', height: 48},
|
|
116
|
-
{role: 'assistant', width: '64%', height: 132},
|
|
117
|
-
{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},
|
|
118
126
|
];
|
|
119
127
|
|
|
120
128
|
function ConversationItem({
|
|
@@ -206,10 +214,11 @@ export default function ShellSideNav() {
|
|
|
206
214
|
contentWidth={768}
|
|
207
215
|
content={
|
|
208
216
|
<LayoutContent padding={6}>
|
|
217
|
+
{/* Skeleton chrome only — the routed conversation supplies the h1. */}
|
|
209
218
|
<VStack gap={5}>
|
|
210
|
-
{MESSAGES.map(
|
|
219
|
+
{MESSAGES.map(message => (
|
|
211
220
|
<HStack
|
|
212
|
-
key={
|
|
221
|
+
key={message.id}
|
|
213
222
|
hAlign={message.role === 'assistant' ? 'start' : 'end'}>
|
|
214
223
|
<Card
|
|
215
224
|
variant="muted"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// A[cp=6 @topNav=(TN > TNI"Shop" + TNI"Brands" + TNI"Sale" + TNI"Service")] > V[g=10] > C.muted[p=0] + (V[g=4] > C.muted[p=0] + (G[c={min:160} g=4] > (V[g=2] > C.muted[p=0] + C.muted[p=0])*6))*3
|
|
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.
|
|
4
6
|
|
|
5
7
|
import type {CSSProperties} from 'react';
|
|
6
8
|
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
@@ -21,7 +23,9 @@ import {IconButton} from '@astryxdesign/core/IconButton';
|
|
|
21
23
|
import {Button} from '@astryxdesign/core/Button';
|
|
22
24
|
import {Badge} from '@astryxdesign/core/Badge';
|
|
23
25
|
import {Card} from '@astryxdesign/core/Card';
|
|
26
|
+
import {Center} from '@astryxdesign/core/Center';
|
|
24
27
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
28
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
25
29
|
import {Stack, VStack} from '@astryxdesign/core/Stack';
|
|
26
30
|
import {
|
|
27
31
|
ShoppingBag,
|
|
@@ -44,7 +48,7 @@ import {
|
|
|
44
48
|
} from 'lucide-react';
|
|
45
49
|
|
|
46
50
|
// Cap + center the page body so wide screens show whitespace gutters.
|
|
47
|
-
const
|
|
51
|
+
const CONTENT_MAX_WIDTH = 1100;
|
|
48
52
|
// Same-route hash: demo links stay focusable anchors without escaping the
|
|
49
53
|
// template through the hash router (bare "#" would drop back to the home page).
|
|
50
54
|
const SELF_HASH = '#/templates/shell-top-nav';
|
|
@@ -52,51 +56,60 @@ const SELF_HASH = '#/templates/shell-top-nav';
|
|
|
52
56
|
// Brands size to their own content (different widths); since both anchor to
|
|
53
57
|
// the centered nav, switching between them resizes the panel — which reads
|
|
54
58
|
// as flashing/jumping. Fixed item + featured widths make the panels
|
|
55
|
-
// 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.
|
|
56
62
|
const megaItems: CSSProperties = {gridColumn: '1 / -1', width: 520};
|
|
57
63
|
const megaFeatured: CSSProperties = {width: 240};
|
|
58
64
|
|
|
65
|
+
// Three identical shelves; the ids keep React keys stable if they reorder.
|
|
66
|
+
const SHELVES = ['new-in', 'featured', 'sale'];
|
|
67
|
+
|
|
59
68
|
type MegaItem = {name: string; tagline: string; icon: IconType};
|
|
60
69
|
|
|
61
70
|
// Shop and Brands each render 8 items — the mega menu's built-in 2-column grid
|
|
62
71
|
// lays them out as 2 columns × 4 rows, alongside a featured card.
|
|
63
72
|
const SHOP_ITEMS: MegaItem[] = [
|
|
64
|
-
{name: 'New Arrivals', tagline: '
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
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},
|
|
68
81
|
{
|
|
69
|
-
name: '
|
|
70
|
-
tagline: '
|
|
82
|
+
name: 'Potions',
|
|
83
|
+
tagline: 'Salves, scents & powders',
|
|
71
84
|
icon: Smile,
|
|
72
85
|
},
|
|
73
86
|
{
|
|
74
|
-
name: '
|
|
75
|
-
tagline: '
|
|
87
|
+
name: 'Relics',
|
|
88
|
+
tagline: 'Satchels, hats & moonshades',
|
|
76
89
|
icon: ShoppingBag,
|
|
77
90
|
},
|
|
78
91
|
{name: 'Sale', tagline: 'Up to 50% off', icon: BadgePercent},
|
|
79
|
-
{name: 'Gift
|
|
92
|
+
{name: 'Gift Tokens', tagline: 'The perfect tribute', icon: Gift},
|
|
80
93
|
];
|
|
81
94
|
|
|
82
95
|
const BRAND_ITEMS: MegaItem[] = [
|
|
83
|
-
{name: 'Aether', tagline: '
|
|
84
|
-
{name: '
|
|
85
|
-
{name: 'Loomwell', tagline: 'Everyday knitwear', icon: Zap},
|
|
86
|
-
{name: 'Verdant', tagline: '
|
|
87
|
-
{name: 'Studio Mara', tagline: 'Modern tailoring', icon: Star},
|
|
88
|
-
{name: 'Atelier Kos', tagline: '
|
|
89
|
-
{name: 'Rue & Co', tagline: '
|
|
90
|
-
{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},
|
|
91
104
|
];
|
|
92
105
|
|
|
93
106
|
const CATEGORY_TILES = [
|
|
94
|
-
'New Arrivals',
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
107
|
+
'New Moon Arrivals',
|
|
108
|
+
'Gowns & Cloaks',
|
|
109
|
+
'Doublets & Tailoring',
|
|
110
|
+
'Keep & Crypt',
|
|
111
|
+
'Potions',
|
|
112
|
+
'Relics',
|
|
100
113
|
];
|
|
101
114
|
|
|
102
115
|
// Wraps the 8 items in a fixed-width 2-column grid so every mega menu's item
|
|
@@ -104,9 +117,12 @@ const CATEGORY_TILES = [
|
|
|
104
117
|
function MegaItems({items}: {items: MegaItem[]}) {
|
|
105
118
|
// In the mobile drawer the fixed 520px panel would overflow the ~350px
|
|
106
119
|
// drawer, and the 2-column grid overlaps item text there — so the drawer
|
|
107
|
-
// 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.
|
|
108
123
|
const isDrawer = useTopNavRenderMode() === 'drawer';
|
|
109
|
-
|
|
124
|
+
const isCompact = useMediaQuery('(max-width: 1024px)');
|
|
125
|
+
if (isDrawer || isCompact) {
|
|
110
126
|
return (
|
|
111
127
|
<VStack gap={1}>
|
|
112
128
|
{items.map(item => (
|
|
@@ -146,8 +162,9 @@ function MegaFeatured(props: {
|
|
|
146
162
|
linkHref: string;
|
|
147
163
|
}) {
|
|
148
164
|
const isDrawer = useTopNavRenderMode() === 'drawer';
|
|
165
|
+
const isCompact = useMediaQuery('(max-width: 1024px)');
|
|
149
166
|
return (
|
|
150
|
-
<Stack style={isDrawer ? undefined : megaFeatured}>
|
|
167
|
+
<Stack style={isDrawer || isCompact ? undefined : megaFeatured}>
|
|
151
168
|
<TopNavMegaMenuFeaturedCard {...props} />
|
|
152
169
|
</Stack>
|
|
153
170
|
);
|
|
@@ -223,23 +240,42 @@ export default function ShellTopNav() {
|
|
|
223
240
|
}
|
|
224
241
|
/>
|
|
225
242
|
}>
|
|
226
|
-
<
|
|
227
|
-
<
|
|
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} />
|
|
228
252
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
))}
|
|
240
276
|
</VStack>
|
|
241
|
-
|
|
242
|
-
</
|
|
277
|
+
</Section>
|
|
278
|
+
</Center>
|
|
243
279
|
</AppShell>
|
|
244
280
|
);
|
|
245
281
|
}
|