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.
Files changed (59) hide show
  1. package/BRAND.md +54 -3
  2. package/README.md +11 -4
  3. package/USAGE.md +1 -1
  4. package/astryx-dracula.d.ts +2 -2
  5. package/astryx-dracula.js +26 -4
  6. package/astryx-dracula.variants.d.ts +2 -2
  7. package/package.json +11 -11
  8. package/shared/auth-chrome-config.ts +0 -4
  9. package/shared/auth-chrome.tsx +2 -3
  10. package/shared/chaptered-doc-config.ts +3 -2
  11. package/shared/order-desk.tsx +52 -54
  12. package/shared/revenue-chart.tsx +1 -1
  13. package/shared/scene-castle.tsx +21 -13
  14. package/templates/ai-chat-landing.tsx +6 -9
  15. package/templates/ai-chat.tsx +5 -1
  16. package/templates/centered-hero.tsx +2 -2
  17. package/templates/classic-gallery.tsx +5 -0
  18. package/templates/contact-form.tsx +9 -0
  19. package/templates/dashboard-portfolio.tsx +17 -4
  20. package/templates/dashboard.tsx +5 -5
  21. package/templates/detail-page.tsx +20 -28
  22. package/templates/documentation-design.tsx +3 -0
  23. package/templates/documentation-technical.tsx +8 -5
  24. package/templates/documentation.tsx +13 -6
  25. package/templates/form-two-column.tsx +11 -2
  26. package/templates/form-wizard.template.mjs +15 -0
  27. package/templates/form-wizard.tsx +878 -0
  28. package/templates/gallery-hero.tsx +10 -4
  29. package/templates/ide.tsx +177 -96
  30. package/templates/incident-console.tsx +15 -5
  31. package/templates/kanban-board.tsx +46 -4
  32. package/templates/library.tsx +12 -3
  33. package/templates/login-split.tsx +3 -3
  34. package/templates/login-sso.tsx +9 -17
  35. package/templates/messaging-shell.tsx +109 -46
  36. package/templates/mixed-gallery.tsx +6 -0
  37. package/templates/payment-form.tsx +29 -28
  38. package/templates/product-detail.tsx +12 -3
  39. package/templates/product-gallery.tsx +8 -3
  40. package/templates/product-tour.tsx +11 -7
  41. package/templates/settings-dialog.tsx +5 -0
  42. package/templates/settings-sidebar.tsx +603 -584
  43. package/templates/settings.tsx +5 -2
  44. package/templates/shell-nav.tsx +23 -3
  45. package/templates/shell-side-nav.tsx +94 -81
  46. package/templates/shell-top-nav.tsx +98 -86
  47. package/templates/table-grouped.tsx +146 -132
  48. package/templates/{table-page-shoe-store-heatmap.template.mjs → table-page-shoe-store.template.mjs} +1 -1
  49. package/templates/{table-page-shoe-store-heatmap.tsx → table-page-shoe-store.tsx} +1 -1
  50. package/templates/table-page.tsx +8 -0
  51. package/templates/table-tree.template.mjs +16 -0
  52. package/templates/table-tree.tsx +707 -0
  53. package/templates/tech-report.tsx +8 -5
  54. package/templates/theme-showcase.tsx +18 -11
  55. package/templates/work-item-detail.template.mjs +8 -0
  56. package/templates/work-item-detail.tsx +1049 -0
  57. package/theme.css +38 -3
  58. package/theme.css.d.ts +2 -2
  59. package/tokens.css +41 -4
@@ -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
- {...(field.autoComplete ? inputAutoComplete(field.autoComplete) : {})}
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}>
@@ -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 — the open file supplies the h1. */}
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
- <AppShell
65
- contentPadding={0}
66
- sideNav={
67
- <SideNav
68
- collapsible
69
- resizable={{ defaultWidth: 300, minWidth: 220, maxWidth: 420 }}
70
- header={
71
- <SideNavHeading
72
- heading="Night Owl"
73
- icon={<NavIcon icon={<Icon icon={Sparkles} size="sm" />} />}
74
- headingHref={SELF_HASH}
75
- />
76
- }
77
- footer={
78
- <SideNavSection title="Account" isHeaderHidden>
79
- <SideNavItem label="Settings" icon={Settings} href={SELF_HASH} />
80
- <SideNavItem label="Sarah Chen" icon={CircleUserRound} href={SELF_HASH} />
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
- <SideNavSection title="Menu" isHeaderHidden>
85
- <SideNavItem label="New chat" icon={Plus} href={SELF_HASH} />
86
- <SideNavItem label="Search" icon={Search} href={SELF_HASH} />
87
- <SideNavItem label="Library" icon={BookOpen} href={SELF_HASH} />
88
- </SideNavSection>
89
- <Divider />
90
- <SideNavSection title="Workspaces" isHeaderHidden>
91
- {WORKSPACES.map((workspace) => (
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
- {workspace.chats.map((chat) => (
104
- // The hover-only MoreMenu was four dead actions unreachable
105
- // by keyboard and touch, so it goes: the status dot stays
106
- // always visible (its label doubles as the tooltip), and
107
- // selecting a conversation switches selection.
108
- <SideNavItem
109
- key={chat.label}
110
- label={chat.label}
111
- href={SELF_HASH}
112
- isSelected={chat.label === selectedChat}
113
- onClick={() => setSelectedChat(chat.label)}
114
- endContent={<StatusDot variant={chat.status} label={chat.statusLabel} />}
115
- />
116
- ))}
117
- </SideNavItem>
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
- </VStack>
136
- </LayoutContent>
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
- </AppShell>
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", width: 520 }}>
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 style={isDrawer || isCompact ? undefined : { width: 240 }}>
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
- <AppShell
141
- variant="surface"
142
- contentPadding={6}
143
- topNav={
144
- <TopNav
145
- label="Nocturne storefront navigation"
146
- heading={
147
- <TopNavHeading
148
- heading="Nocturne"
149
- logo={<NavIcon icon={<Icon icon={ShoppingBag} size="sm" />} />}
150
- />
151
- }
152
- centerContent={
153
- <>
154
- <TopNavMegaMenu
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
- {!isCompact && <Button label="Sign in" variant="ghost" />}
191
- <Button
192
- label="Checkout"
193
- variant="primary"
194
- tooltip="Cart, 3 items"
195
- icon={<Icon icon={ShoppingCart} size="sm" />}
196
- endContent={<Badge label={3} />}
197
- />
198
- </>
199
- }
200
- />
201
- }
202
- >
203
- <Center axis="horizontal">
204
- <Section variant="transparent" maxWidth={CONTENT_MAX_WIDTH} width="100%" padding={0}>
205
- <VStack gap={10}>
206
- {/* Skeleton shelves only — the routed collection supplies the h1. */}
207
- <Card variant="muted" padding={0} width="100%" height={360} />
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
- {SHELVES.map((shelf) => (
210
- <VStack key={shelf} gap={4}>
211
- <Card variant="muted" padding={0} width={200} height={24} />
212
- <Grid columns={{ minWidth: 160, repeat: "fit" }} gap={4}>
213
- {CATEGORY_TILES.map((tile) => (
214
- <VStack key={tile} gap={2}>
215
- <Card variant="muted" padding={0} width="100%" height={120} />
216
- <Card variant="muted" padding={0} width="60%" height={14} />
217
- </VStack>
218
- ))}
219
- </Grid>
220
- </VStack>
221
- ))}
222
- </VStack>
223
- </Section>
224
- </Center>
225
- </AppShell>
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
  }