astryx-dracula 0.6.2 → 0.7.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 +54 -3
- package/README.md +11 -4
- package/USAGE.md +1 -1
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +26 -4
- package/astryx-dracula.variants.d.ts +2 -2
- package/package.json +11 -11
- package/shared/auth-chrome-config.ts +0 -4
- package/shared/auth-chrome.tsx +2 -3
- package/shared/chaptered-doc-config.ts +3 -2
- package/shared/order-desk.tsx +52 -54
- package/shared/revenue-chart.tsx +1 -1
- package/shared/scene-castle.tsx +21 -13
- package/templates/ai-chat-landing.tsx +6 -9
- package/templates/ai-chat.tsx +5 -1
- package/templates/centered-hero.tsx +2 -2
- package/templates/classic-gallery.tsx +5 -0
- package/templates/contact-form.tsx +9 -0
- package/templates/dashboard-portfolio.tsx +17 -4
- package/templates/dashboard.tsx +5 -5
- package/templates/detail-page.tsx +20 -28
- package/templates/documentation-design.tsx +3 -0
- package/templates/documentation-technical.tsx +8 -5
- package/templates/documentation.tsx +13 -6
- package/templates/form-two-column.tsx +11 -2
- package/templates/form-wizard.template.mjs +15 -0
- package/templates/form-wizard.tsx +878 -0
- package/templates/gallery-hero.tsx +10 -4
- package/templates/ide.tsx +177 -96
- package/templates/incident-console.tsx +15 -5
- package/templates/kanban-board.tsx +46 -4
- package/templates/library.tsx +12 -3
- package/templates/login-split.tsx +3 -3
- package/templates/login-sso.tsx +9 -17
- package/templates/messaging-shell.tsx +109 -46
- package/templates/mixed-gallery.tsx +6 -0
- package/templates/payment-form.tsx +29 -28
- package/templates/product-detail.tsx +12 -3
- package/templates/product-gallery.tsx +8 -3
- package/templates/product-tour.tsx +11 -7
- package/templates/settings-dialog.tsx +5 -0
- package/templates/settings-sidebar.tsx +603 -584
- package/templates/settings.tsx +5 -2
- package/templates/shell-nav.tsx +23 -3
- package/templates/shell-side-nav.tsx +94 -81
- package/templates/shell-top-nav.tsx +98 -86
- package/templates/table-grouped.tsx +146 -132
- package/templates/{table-page-shoe-store-heatmap.template.mjs → table-page-shoe-store.template.mjs} +1 -1
- package/templates/{table-page-shoe-store-heatmap.tsx → table-page-shoe-store.tsx} +1 -1
- package/templates/table-page.tsx +8 -0
- package/templates/table-tree.template.mjs +16 -0
- package/templates/table-tree.tsx +707 -0
- package/templates/tech-report.tsx +8 -5
- package/templates/theme-showcase.tsx +18 -11
- package/templates/work-item-detail.template.mjs +8 -0
- package/templates/work-item-detail.tsx +1049 -0
- package/theme.css +38 -3
- package/theme.css.d.ts +2 -2
- package/tokens.css +41 -4
package/templates/settings.tsx
CHANGED
|
@@ -40,7 +40,6 @@ import { Heading, Text } from "@astryxdesign/core/Text";
|
|
|
40
40
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
41
41
|
import type { SearchableItem, SearchSource } from "@astryxdesign/core/Typeahead";
|
|
42
42
|
import { Typeahead } from "@astryxdesign/core/Typeahead";
|
|
43
|
-
import { inputAutoComplete } from "astryx-dracula/shared/auth-chrome-config";
|
|
44
43
|
import { Search } from "lucide-react";
|
|
45
44
|
import { useState } from "react";
|
|
46
45
|
|
|
@@ -216,7 +215,7 @@ function SettingsSection({
|
|
|
216
215
|
key={field.key}
|
|
217
216
|
label={field.label}
|
|
218
217
|
type={field.inputType}
|
|
219
|
-
{
|
|
218
|
+
autoComplete={field.autoComplete}
|
|
220
219
|
value={textValues[field.valueKey]}
|
|
221
220
|
onChange={(value) => onTextChange(field.valueKey, value)}
|
|
222
221
|
/>
|
|
@@ -254,6 +253,10 @@ export default function SettingsTemplate() {
|
|
|
254
253
|
return (
|
|
255
254
|
<Layout
|
|
256
255
|
height="fill"
|
|
256
|
+
// A `fill` Layout is height:100%, which resolves against a definite
|
|
257
|
+
// ancestor. A standalone page has none, so it anchors the viewport
|
|
258
|
+
// itself; LayoutContent then scrolls instead of the document.
|
|
259
|
+
style={{ height: "100dvh" }}
|
|
257
260
|
contentWidth={1200}
|
|
258
261
|
header={
|
|
259
262
|
<LayoutHeader hasDivider padding={6}>
|
package/templates/shell-nav.tsx
CHANGED
|
@@ -17,13 +17,14 @@ import { Layout, LayoutContent, LayoutHeader } from "@astryxdesign/core/Layout";
|
|
|
17
17
|
import { SideNav } from "@astryxdesign/core/SideNav";
|
|
18
18
|
import { Skeleton } from "@astryxdesign/core/Skeleton";
|
|
19
19
|
import { HStack, VStack } from "@astryxdesign/core/Stack";
|
|
20
|
-
import { Text } from "@astryxdesign/core/Text";
|
|
20
|
+
import { Heading, Text } from "@astryxdesign/core/Text";
|
|
21
21
|
import { TopNav } from "@astryxdesign/core/TopNav";
|
|
22
22
|
import type { TreeListItemData } from "@astryxdesign/core/TreeList";
|
|
23
23
|
import { TreeList } from "@astryxdesign/core/TreeList";
|
|
24
24
|
import { createStaticSource } from "@astryxdesign/core/Typeahead";
|
|
25
|
+
import { VisuallyHidden } from "@astryxdesign/core/VisuallyHidden";
|
|
25
26
|
import { FileText, Folder, Search } from "lucide-react";
|
|
26
|
-
import { Fragment, useMemo, useState } from "react";
|
|
27
|
+
import { Fragment, useEffect, useMemo, useState } from "react";
|
|
27
28
|
|
|
28
29
|
const folder = (id: string, children: TreeListItemData[], isExpanded = true): TreeListItemData => ({
|
|
29
30
|
id,
|
|
@@ -157,8 +158,26 @@ export default function ShellNav() {
|
|
|
157
158
|
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
|
|
158
159
|
const searchSource = useMemo(() => createStaticSource(COMMANDS), []);
|
|
159
160
|
|
|
161
|
+
// The trigger's tooltip advertises ⌘K, and CommandPalette binds no global
|
|
162
|
+
// shortcut of its own — without this the promise was only decorative.
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
165
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
setIsPaletteOpen(true);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
171
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
160
174
|
return (
|
|
161
175
|
<>
|
|
176
|
+
{/* The page is chrome and a skeleton preview, so nothing on screen is a
|
|
177
|
+
heading. A top-level heading still has to exist for the document. */}
|
|
178
|
+
<VisuallyHidden>
|
|
179
|
+
<Heading level={1}>Shell navigation</Heading>
|
|
180
|
+
</VisuallyHidden>
|
|
162
181
|
<AppShell
|
|
163
182
|
contentPadding={0}
|
|
164
183
|
topNav={
|
|
@@ -224,7 +243,8 @@ export default function ShellNav() {
|
|
|
224
243
|
}
|
|
225
244
|
content={
|
|
226
245
|
<LayoutContent padding={6}>
|
|
227
|
-
{/* Skeleton chrome only
|
|
246
|
+
{/* Skeleton chrome only. The page-level h1 is the hidden one at the root;
|
|
247
|
+
the open file is a list row, not a heading. */}
|
|
228
248
|
<VStack gap={2}>
|
|
229
249
|
{CODE_LINES.map((line) =>
|
|
230
250
|
line.width === "0%" ? (
|
|
@@ -15,7 +15,9 @@ import { NavIcon } from "@astryxdesign/core/NavIcon";
|
|
|
15
15
|
import { SideNav, SideNavHeading, SideNavItem, SideNavSection } from "@astryxdesign/core/SideNav";
|
|
16
16
|
import { HStack, VStack } from "@astryxdesign/core/Stack";
|
|
17
17
|
import { StatusDot } from "@astryxdesign/core/StatusDot";
|
|
18
|
+
import { Heading } from "@astryxdesign/core/Text";
|
|
18
19
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
20
|
+
import { VisuallyHidden } from "@astryxdesign/core/VisuallyHidden";
|
|
19
21
|
import { BookOpen, CircleUserRound, Plus, Search, Settings, Sparkles, User } from "lucide-react";
|
|
20
22
|
import { useState } from "react";
|
|
21
23
|
|
|
@@ -61,93 +63,104 @@ export default function ShellSideNav() {
|
|
|
61
63
|
const [selectedChat, setSelectedChat] = useState(SELECTED_CHAT);
|
|
62
64
|
const [draft, setDraft] = useState("");
|
|
63
65
|
return (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
<>
|
|
67
|
+
{/* Chrome and a skeleton preview render no heading of their own. */}
|
|
68
|
+
<VisuallyHidden>
|
|
69
|
+
<Heading level={1}>Side navigation shell</Heading>
|
|
70
|
+
</VisuallyHidden>
|
|
71
|
+
<AppShell
|
|
72
|
+
contentPadding={0}
|
|
73
|
+
sideNav={
|
|
74
|
+
<SideNav
|
|
75
|
+
collapsible
|
|
76
|
+
resizable={{ defaultWidth: 300, minWidth: 220, maxWidth: 420 }}
|
|
77
|
+
header={
|
|
78
|
+
<SideNavHeading
|
|
79
|
+
heading="Night Owl"
|
|
80
|
+
icon={<NavIcon icon={<Icon icon={Sparkles} size="sm" />} />}
|
|
81
|
+
headingHref={SELF_HASH}
|
|
82
|
+
/>
|
|
83
|
+
}
|
|
84
|
+
footer={
|
|
85
|
+
<SideNavSection title="Account" isHeaderHidden>
|
|
86
|
+
<SideNavItem label="Settings" icon={Settings} href={SELF_HASH} />
|
|
87
|
+
<SideNavItem label="Sarah Chen" icon={CircleUserRound} href={SELF_HASH} />
|
|
88
|
+
</SideNavSection>
|
|
89
|
+
}
|
|
90
|
+
>
|
|
91
|
+
<SideNavSection title="Menu" isHeaderHidden>
|
|
92
|
+
<SideNavItem label="New chat" icon={Plus} href={SELF_HASH} />
|
|
93
|
+
<SideNavItem label="Search" icon={Search} href={SELF_HASH} />
|
|
94
|
+
<SideNavItem label="Library" icon={BookOpen} href={SELF_HASH} />
|
|
81
95
|
</SideNavSection>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<SideNavItem
|
|
93
|
-
key={workspace.name}
|
|
94
|
-
label={workspace.name}
|
|
95
|
-
icon={workspace.icon}
|
|
96
|
-
collapsible={{ defaultIsCollapsed: false }}
|
|
97
|
-
>
|
|
98
|
-
{/* The nested rows are direct children, not wrapped in a
|
|
96
|
+
<Divider />
|
|
97
|
+
<SideNavSection title="Workspaces" isHeaderHidden>
|
|
98
|
+
{WORKSPACES.map((workspace) => (
|
|
99
|
+
<SideNavItem
|
|
100
|
+
key={workspace.name}
|
|
101
|
+
label={workspace.name}
|
|
102
|
+
icon={workspace.icon}
|
|
103
|
+
collapsible={{ defaultIsCollapsed: false }}
|
|
104
|
+
>
|
|
105
|
+
{/* The nested rows are direct children, not wrapped in a
|
|
99
106
|
VStack: core 0.7.2 made nested SideNavItems span the
|
|
100
107
|
full width of the nav list with the indent inside the row,
|
|
101
108
|
and any non-SideNavItem content in between (a VStack is
|
|
102
109
|
exactly that) takes the children out of that flow. */}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
))}
|
|
119
|
-
</SideNavSection>
|
|
120
|
-
</SideNav>
|
|
121
|
-
}
|
|
122
|
-
>
|
|
123
|
-
<Layout
|
|
124
|
-
height="fill"
|
|
125
|
-
contentWidth={768}
|
|
126
|
-
content={
|
|
127
|
-
<LayoutContent padding={6}>
|
|
128
|
-
{/* Skeleton chrome only — the routed conversation supplies the h1. */}
|
|
129
|
-
<VStack gap={5}>
|
|
130
|
-
{MESSAGES.map((message) => (
|
|
131
|
-
<HStack key={message.id} hAlign={message.role === "assistant" ? "start" : "end"}>
|
|
132
|
-
<Card variant="muted" padding={0} width={message.width} height={message.height} />
|
|
133
|
-
</HStack>
|
|
110
|
+
{workspace.chats.map((chat) => (
|
|
111
|
+
// The hover-only MoreMenu was four dead actions unreachable
|
|
112
|
+
// by keyboard and touch, so it goes: the status dot stays
|
|
113
|
+
// always visible (its label doubles as the tooltip), and
|
|
114
|
+
// selecting a conversation switches selection.
|
|
115
|
+
<SideNavItem
|
|
116
|
+
key={chat.label}
|
|
117
|
+
label={chat.label}
|
|
118
|
+
href={SELF_HASH}
|
|
119
|
+
isSelected={chat.label === selectedChat}
|
|
120
|
+
onClick={() => setSelectedChat(chat.label)}
|
|
121
|
+
endContent={<StatusDot variant={chat.status} label={chat.statusLabel} />}
|
|
122
|
+
/>
|
|
123
|
+
))}
|
|
124
|
+
</SideNavItem>
|
|
134
125
|
))}
|
|
135
|
-
</
|
|
136
|
-
</
|
|
137
|
-
}
|
|
138
|
-
footer={
|
|
139
|
-
<LayoutFooter>
|
|
140
|
-
<TextInput
|
|
141
|
-
label="Message Night Owl"
|
|
142
|
-
isLabelHidden
|
|
143
|
-
placeholder="Message Night Owl…"
|
|
144
|
-
value={draft}
|
|
145
|
-
onChange={setDraft}
|
|
146
|
-
width="100%"
|
|
147
|
-
/>
|
|
148
|
-
</LayoutFooter>
|
|
126
|
+
</SideNavSection>
|
|
127
|
+
</SideNav>
|
|
149
128
|
}
|
|
150
|
-
|
|
151
|
-
|
|
129
|
+
>
|
|
130
|
+
<Layout
|
|
131
|
+
height="fill"
|
|
132
|
+
contentWidth={768}
|
|
133
|
+
content={
|
|
134
|
+
<LayoutContent padding={6}>
|
|
135
|
+
{/* Skeleton chrome only — the routed conversation supplies the h1. */}
|
|
136
|
+
<VStack gap={5}>
|
|
137
|
+
{MESSAGES.map((message) => (
|
|
138
|
+
<HStack key={message.id} hAlign={message.role === "assistant" ? "start" : "end"}>
|
|
139
|
+
<Card
|
|
140
|
+
variant="muted"
|
|
141
|
+
padding={0}
|
|
142
|
+
width={message.width}
|
|
143
|
+
height={message.height}
|
|
144
|
+
/>
|
|
145
|
+
</HStack>
|
|
146
|
+
))}
|
|
147
|
+
</VStack>
|
|
148
|
+
</LayoutContent>
|
|
149
|
+
}
|
|
150
|
+
footer={
|
|
151
|
+
<LayoutFooter>
|
|
152
|
+
<TextInput
|
|
153
|
+
label="Message Night Owl"
|
|
154
|
+
isLabelHidden
|
|
155
|
+
placeholder="Message Night Owl…"
|
|
156
|
+
value={draft}
|
|
157
|
+
onChange={setDraft}
|
|
158
|
+
width="100%"
|
|
159
|
+
/>
|
|
160
|
+
</LayoutFooter>
|
|
161
|
+
}
|
|
162
|
+
/>
|
|
163
|
+
</AppShell>
|
|
164
|
+
</>
|
|
152
165
|
);
|
|
153
166
|
}
|
|
@@ -19,6 +19,7 @@ import { IconButton } from "@astryxdesign/core/IconButton";
|
|
|
19
19
|
import { NavIcon } from "@astryxdesign/core/NavIcon";
|
|
20
20
|
import { Section } from "@astryxdesign/core/Section";
|
|
21
21
|
import { Stack, VStack } from "@astryxdesign/core/Stack";
|
|
22
|
+
import { Heading } from "@astryxdesign/core/Text";
|
|
22
23
|
import {
|
|
23
24
|
TopNav,
|
|
24
25
|
TopNavHeading,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
TopNavMegaMenuItem,
|
|
29
30
|
useTopNavRenderMode,
|
|
30
31
|
} from "@astryxdesign/core/TopNav";
|
|
32
|
+
import { VisuallyHidden } from "@astryxdesign/core/VisuallyHidden";
|
|
31
33
|
import { House, Search, ShoppingBag, ShoppingCart, Sparkles, SwatchBook, Tag } from "lucide-react";
|
|
32
34
|
|
|
33
35
|
// Cap + center the page body so wide screens show whitespace gutters.
|
|
@@ -45,6 +47,10 @@ const SELF_HASH = "#/templates/shell-top-nav";
|
|
|
45
47
|
// Three identical shelves; the ids keep React keys stable if they reorder.
|
|
46
48
|
const SHELVES = ["new-in", "featured", "sale"];
|
|
47
49
|
|
|
50
|
+
// The cart badge and its tooltip are one fact stated twice, so they read the
|
|
51
|
+
// same constant: a hardcoded numeral in each is how they drift apart.
|
|
52
|
+
const CART_ITEM_COUNT = 3;
|
|
53
|
+
|
|
48
54
|
type MegaItem = { name: string; tagline: string; icon: IconType };
|
|
49
55
|
|
|
50
56
|
// Shop and Brands each render 4 items — the mega menu's built-in 2-column
|
|
@@ -99,7 +105,7 @@ function MegaItems({ items }: { items: MegaItem[] }) {
|
|
|
99
105
|
);
|
|
100
106
|
}
|
|
101
107
|
return (
|
|
102
|
-
<Stack style={{ gridColumn: "1 / -1"
|
|
108
|
+
<Stack width={520} style={{ gridColumn: "1 / -1" }}>
|
|
103
109
|
<Grid columns={2} gap={2}>
|
|
104
110
|
{items.map((item) => (
|
|
105
111
|
<TopNavMegaMenuItem
|
|
@@ -125,7 +131,7 @@ function MegaFeatured(props: {
|
|
|
125
131
|
const isDrawer = useTopNavRenderMode() === "drawer";
|
|
126
132
|
const isCompact = useMediaQuery("(max-width: 1024px)");
|
|
127
133
|
return (
|
|
128
|
-
<Stack
|
|
134
|
+
<Stack width={isDrawer || isCompact ? undefined : 240}>
|
|
129
135
|
<TopNavMegaMenuFeaturedCard {...props} />
|
|
130
136
|
</Stack>
|
|
131
137
|
);
|
|
@@ -137,91 +143,97 @@ export default function ShellTopNav() {
|
|
|
137
143
|
// button there restores room for search, checkout, and the toggle.
|
|
138
144
|
const isCompact = useMediaQuery("(max-width: 640px)");
|
|
139
145
|
return (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
label="Shop"
|
|
156
|
-
items={<MegaItems items={SHOP_ITEMS} />}
|
|
157
|
-
featured={
|
|
158
|
-
<MegaFeatured
|
|
159
|
-
title="The Midnight Edit"
|
|
160
|
-
description="Layered staples in moonlit purples."
|
|
161
|
-
linkLabel="Shop the edit"
|
|
162
|
-
linkHref={SELF_HASH}
|
|
163
|
-
/>
|
|
164
|
-
}
|
|
165
|
-
/>
|
|
166
|
-
<TopNavMegaMenu
|
|
167
|
-
label="Brands"
|
|
168
|
-
items={<MegaItems items={BRAND_ITEMS} />}
|
|
169
|
-
featured={
|
|
170
|
-
<MegaFeatured
|
|
171
|
-
title="Meet Studio Mara"
|
|
172
|
-
description="Modern tailoring, made to last."
|
|
173
|
-
linkLabel="Discover the label"
|
|
174
|
-
linkHref={SELF_HASH}
|
|
175
|
-
/>
|
|
176
|
-
}
|
|
177
|
-
/>
|
|
178
|
-
<TopNavItem label="Sale" href={SELF_HASH} />
|
|
179
|
-
<TopNavItem label="Service" href={SELF_HASH} />
|
|
180
|
-
</>
|
|
181
|
-
}
|
|
182
|
-
endContent={
|
|
183
|
-
<>
|
|
184
|
-
<IconButton
|
|
185
|
-
label="Search products"
|
|
186
|
-
tooltip="Search"
|
|
187
|
-
variant="ghost"
|
|
188
|
-
icon={<Icon icon={Search} size="sm" />}
|
|
146
|
+
<>
|
|
147
|
+
{/* A storefront chrome preview renders no heading of its own. */}
|
|
148
|
+
<VisuallyHidden>
|
|
149
|
+
<Heading level={1}>Top navigation shell</Heading>
|
|
150
|
+
</VisuallyHidden>
|
|
151
|
+
<AppShell
|
|
152
|
+
variant="surface"
|
|
153
|
+
contentPadding={6}
|
|
154
|
+
topNav={
|
|
155
|
+
<TopNav
|
|
156
|
+
label="Nocturne storefront navigation"
|
|
157
|
+
heading={
|
|
158
|
+
<TopNavHeading
|
|
159
|
+
heading="Nocturne"
|
|
160
|
+
logo={<NavIcon icon={<Icon icon={ShoppingBag} size="sm" />} />}
|
|
189
161
|
/>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
162
|
+
}
|
|
163
|
+
centerContent={
|
|
164
|
+
<>
|
|
165
|
+
<TopNavMegaMenu
|
|
166
|
+
label="Shop"
|
|
167
|
+
items={<MegaItems items={SHOP_ITEMS} />}
|
|
168
|
+
featured={
|
|
169
|
+
<MegaFeatured
|
|
170
|
+
title="The Midnight Edit"
|
|
171
|
+
description="Layered staples in moonlit purples."
|
|
172
|
+
linkLabel="Shop the edit"
|
|
173
|
+
linkHref={SELF_HASH}
|
|
174
|
+
/>
|
|
175
|
+
}
|
|
176
|
+
/>
|
|
177
|
+
<TopNavMegaMenu
|
|
178
|
+
label="Brands"
|
|
179
|
+
items={<MegaItems items={BRAND_ITEMS} />}
|
|
180
|
+
featured={
|
|
181
|
+
<MegaFeatured
|
|
182
|
+
title="Meet Studio Mara"
|
|
183
|
+
description="Modern tailoring, made to last."
|
|
184
|
+
linkLabel="Discover the label"
|
|
185
|
+
linkHref={SELF_HASH}
|
|
186
|
+
/>
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
189
|
+
<TopNavItem label="Sale" href={SELF_HASH} />
|
|
190
|
+
<TopNavItem label="Service" href={SELF_HASH} />
|
|
191
|
+
</>
|
|
192
|
+
}
|
|
193
|
+
endContent={
|
|
194
|
+
<>
|
|
195
|
+
<IconButton
|
|
196
|
+
label="Search products"
|
|
197
|
+
tooltip="Search"
|
|
198
|
+
variant="ghost"
|
|
199
|
+
icon={<Icon icon={Search} size="sm" />}
|
|
200
|
+
/>
|
|
201
|
+
{!isCompact && <Button label="Sign in" variant="ghost" />}
|
|
202
|
+
<Button
|
|
203
|
+
label="Checkout"
|
|
204
|
+
variant="primary"
|
|
205
|
+
tooltip={`Cart, ${CART_ITEM_COUNT} items`}
|
|
206
|
+
icon={<Icon icon={ShoppingCart} size="sm" />}
|
|
207
|
+
endContent={<Badge label={CART_ITEM_COUNT} />}
|
|
208
|
+
/>
|
|
209
|
+
</>
|
|
210
|
+
}
|
|
211
|
+
/>
|
|
212
|
+
}
|
|
213
|
+
>
|
|
214
|
+
<Center axis="horizontal">
|
|
215
|
+
<Section variant="transparent" maxWidth={CONTENT_MAX_WIDTH} width="100%" padding={0}>
|
|
216
|
+
<VStack gap={10}>
|
|
217
|
+
{/* Skeleton shelves only — the routed collection supplies the h1. */}
|
|
218
|
+
<Card variant="muted" padding={0} width="100%" height={360} />
|
|
208
219
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
220
|
+
{SHELVES.map((shelf) => (
|
|
221
|
+
<VStack key={shelf} gap={4}>
|
|
222
|
+
<Card variant="muted" padding={0} width={200} height={24} />
|
|
223
|
+
<Grid columns={{ minWidth: 160, repeat: "fit" }} gap={4}>
|
|
224
|
+
{CATEGORY_TILES.map((tile) => (
|
|
225
|
+
<VStack key={tile} gap={2}>
|
|
226
|
+
<Card variant="muted" padding={0} width="100%" height={120} />
|
|
227
|
+
<Card variant="muted" padding={0} width="60%" height={14} />
|
|
228
|
+
</VStack>
|
|
229
|
+
))}
|
|
230
|
+
</Grid>
|
|
231
|
+
</VStack>
|
|
232
|
+
))}
|
|
233
|
+
</VStack>
|
|
234
|
+
</Section>
|
|
235
|
+
</Center>
|
|
236
|
+
</AppShell>
|
|
237
|
+
</>
|
|
226
238
|
);
|
|
227
239
|
}
|