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
@@ -31,6 +31,7 @@ import { Tab, TabList } from "@astryxdesign/core/TabList";
31
31
  import { Heading, Text } from "@astryxdesign/core/Text";
32
32
  import { TextInput } from "@astryxdesign/core/TextInput";
33
33
  import { Toolbar } from "@astryxdesign/core/Toolbar";
34
+ import { VisuallyHidden } from "@astryxdesign/core/VisuallyHidden";
34
35
  import type { InfoRow } from "astryx-dracula/shared/settings-data";
35
36
  import {
36
37
  actionNoWrap,
@@ -50,10 +51,13 @@ import { ArrowLeft, ChevronRight, Lock, Monitor, ShieldCheck, Wrench } from "luc
50
51
  import { type CSSProperties, useState } from "react";
51
52
 
52
53
  // Anchor the page to the viewport height so the sidebar + content fill the
53
- // screen. Layout height="fill" is min-height:100% which collapses when the
54
- // host container is content-sized; Layout has no viewport-height prop.
54
+ // screen. `Layout height="fill"` is height:100%, which collapses to auto
55
+ // against a content-sized host; 100dvh is definite and needs no ancestor.
56
+ // `height`, not `minHeight`: a min-height lets the Layout grow with its
57
+ // content and hands the scroll back to the document, which is the failure
58
+ // the fill is supposed to prevent.
55
59
  const fillViewport: CSSProperties = {
56
- minHeight: "100dvh",
60
+ height: "100dvh",
57
61
  };
58
62
  const rowPadding: CSSProperties = {
59
63
  paddingBlock: "var(--spacing-4)",
@@ -157,642 +161,657 @@ export default function SettingsSidebar() {
157
161
  // Mobile, nav view: show only the menu (full width, no sidebar slot).
158
162
  if (isNarrow && mobileView === "nav") {
159
163
  return (
160
- <Layout
161
- height="fill"
162
- style={fillViewport}
163
- // padding={0}, not padding={2}. This wraps the SAME navList element
164
- // the desktop LayoutPanel renders with padding={0}, and the navList's
165
- // own paddingBlock/paddingInline props supply the 16px/12px inset.
166
- // The extra 8px here is what made the same nav list measure 20px on
167
- // mobile against 12px on desktop -- and against 24px further down,
168
- // disagreed with each other too. The container owns no gutter; the
169
- // list owns its own.
170
- content={<LayoutContent padding={0}>{navList}</LayoutContent>}
171
- />
164
+ <>
165
+ {/* This branch is the whole page on a phone and the nav list carries no
166
+ heading of its own, so the document would have none. */}
167
+ <VisuallyHidden>
168
+ <Heading level={1}>Settings</Heading>
169
+ </VisuallyHidden>
170
+ <Layout
171
+ height="fill"
172
+ style={fillViewport}
173
+ // padding={0}, not padding={2}. This wraps the SAME navList element
174
+ // the desktop LayoutPanel renders with padding={0}, and the navList's
175
+ // own paddingBlock/paddingInline props supply the 16px/12px inset.
176
+ // The extra 8px here is what made the same nav list measure 20px on
177
+ // mobile against 12px on desktop -- and against 24px further down,
178
+ // disagreed with each other too. The container owns no gutter; the
179
+ // list owns its own.
180
+ content={<LayoutContent padding={0}>{navList}</LayoutContent>}
181
+ />
182
+ </>
172
183
  );
173
184
  }
174
185
 
175
186
  return (
176
- <Layout
177
- height="fill"
178
- contentWidth={1200}
179
- style={fillViewport}
180
- start={
181
- isNarrow ? undefined : (
182
- <LayoutPanel hasDivider padding={0}>
183
- {navList}
184
- </LayoutPanel>
185
- )
186
- }
187
- content={
188
- <LayoutContent padding={6}>
189
- <VStack gap={0}>
190
- {/* Mobile detail view: a back button sits beside the section title
187
+ <>
188
+ {/* The nav list is the whole page at narrow width, and lists no heading. */}
189
+ {isNarrow && (
190
+ <VisuallyHidden>
191
+ <Heading level={1}>Settings</Heading>
192
+ </VisuallyHidden>
193
+ )}
194
+ <Layout
195
+ height="fill"
196
+ contentWidth={1200}
197
+ style={fillViewport}
198
+ start={
199
+ isNarrow ? undefined : (
200
+ <LayoutPanel hasDivider padding={0}>
201
+ {navList}
202
+ </LayoutPanel>
203
+ )
204
+ }
205
+ content={
206
+ <LayoutContent padding={6}>
207
+ <VStack gap={0}>
208
+ {/* Mobile detail view: a back button sits beside the section title
191
209
  (the per-section headings below are hidden on mobile). Toolbar's
192
210
  start slot edge-compensates the ghost button so its icon aligns
193
211
  flush with the content edge. */}
194
- {isNarrow && (
195
- <Toolbar
196
- label={`Back to Account settings: ${activeNav === HOSTING_TOOLS_TITLE ? "Hosting tools" : activeNav}`}
197
- gap={2}
198
- startContent={
199
- <>
200
- <Button
201
- label="Back to Account settings"
202
- variant="ghost"
203
- size="sm"
204
- isIconOnly
205
- icon={<Icon icon={ArrowLeft} size="sm" />}
206
- onClick={() => setMobileView("nav")}
207
- />
208
- <Heading level={1}>
209
- {activeNav === HOSTING_TOOLS_TITLE ? "Hosting tools" : activeNav}
210
- </Heading>
211
- </>
212
- }
213
- />
214
- )}
215
- {activeNav === "Login & security" && (
216
- <VStack gap={6}>
217
- {!isNarrow && <Heading level={1}>Login &amp; security</Heading>}
218
-
219
- <TabList value={activeTab} onChange={setActiveTab} hasDivider>
220
- <Tab value="login" label="Login" />
221
- <Tab value="shared" label="Shared access" />
222
- </TabList>
223
-
224
- {activeTab === "login" && (
225
- <VStack gap={8}>
226
- <VStack gap={2}>
227
- <Heading level={2}>Login</Heading>
228
- {LOGIN_ROWS.map((row) => (
229
- <InfoRowItem
230
- key={row.label}
231
- {...row}
232
- hasDivider={false}
233
- style={rowPadding}
234
- />
235
- ))}
236
- </VStack>
237
-
238
- <VStack gap={2}>
239
- <Heading level={2}>Social accounts</Heading>
240
- {SOCIAL_ROWS.map((row) => (
241
- <InfoRowItem
242
- key={row.label}
243
- {...row}
244
- hasDivider={false}
245
- style={rowPadding}
246
- />
247
- ))}
248
- </VStack>
249
-
250
- <VStack gap={2}>
251
- <Heading level={2}>Device history</Heading>
252
- {DEVICE_ROWS.map((device) => (
253
- <HStack key={device.location} gap={3} vAlign="start" style={rowPadding}>
254
- <Icon icon={Monitor} />
255
- <StackItem size="fill">
256
- <VStack gap={1}>
257
- <HStack gap={2} vAlign="center" wrap="wrap">
258
- <Text type="body" weight="semibold">
259
- {device.label}
212
+ {isNarrow && (
213
+ <Toolbar
214
+ label={`Back to Account settings: ${activeNav === HOSTING_TOOLS_TITLE ? "Hosting tools" : activeNav}`}
215
+ gap={2}
216
+ startContent={
217
+ <>
218
+ <Button
219
+ label="Back to Account settings"
220
+ variant="ghost"
221
+ size="sm"
222
+ isIconOnly
223
+ icon={<Icon icon={ArrowLeft} size="sm" />}
224
+ onClick={() => setMobileView("nav")}
225
+ />
226
+ <Heading level={1}>
227
+ {activeNav === HOSTING_TOOLS_TITLE ? "Hosting tools" : activeNav}
228
+ </Heading>
229
+ </>
230
+ }
231
+ />
232
+ )}
233
+ {activeNav === "Login & security" && (
234
+ <VStack gap={6}>
235
+ {!isNarrow && <Heading level={1}>Login &amp; security</Heading>}
236
+
237
+ <TabList value={activeTab} onChange={setActiveTab} hasDivider>
238
+ <Tab value="login" label="Login" />
239
+ <Tab value="shared" label="Shared access" />
240
+ </TabList>
241
+
242
+ {activeTab === "login" && (
243
+ <VStack gap={8}>
244
+ <VStack gap={2}>
245
+ <Heading level={2}>Login</Heading>
246
+ {LOGIN_ROWS.map((row) => (
247
+ <InfoRowItem
248
+ key={row.label}
249
+ {...row}
250
+ hasDivider={false}
251
+ style={rowPadding}
252
+ />
253
+ ))}
254
+ </VStack>
255
+
256
+ <VStack gap={2}>
257
+ <Heading level={2}>Social accounts</Heading>
258
+ {SOCIAL_ROWS.map((row) => (
259
+ <InfoRowItem
260
+ key={row.label}
261
+ {...row}
262
+ hasDivider={false}
263
+ style={rowPadding}
264
+ />
265
+ ))}
266
+ </VStack>
267
+
268
+ <VStack gap={2}>
269
+ <Heading level={2}>Device history</Heading>
270
+ {DEVICE_ROWS.map((device) => (
271
+ <HStack key={device.location} gap={3} vAlign="start" style={rowPadding}>
272
+ <Icon icon={Monitor} />
273
+ <StackItem size="fill">
274
+ <VStack gap={1}>
275
+ <HStack gap={2} vAlign="center" wrap="wrap">
276
+ <Text type="body" weight="semibold">
277
+ {device.label}
278
+ </Text>
279
+ {device.isCurrent && (
280
+ <HStack gap={1} vAlign="center">
281
+ <StatusDot variant="success" label="Current session" />
282
+ <Text type="supporting" color="secondary">
283
+ Current session
284
+ </Text>
285
+ </HStack>
286
+ )}
287
+ </HStack>
288
+ <Text
289
+ type="supporting"
290
+ color="secondary"
291
+ display="block"
292
+ hasTabularNumbers
293
+ >
294
+ {device.location}
260
295
  </Text>
261
- {device.isCurrent && (
262
- <HStack gap={1} vAlign="center">
263
- <StatusDot variant="success" label="Current session" />
264
- <Text type="supporting" color="secondary">
265
- Current session
266
- </Text>
267
- </HStack>
268
- )}
269
- </HStack>
270
- <Text
271
- type="supporting"
272
- color="secondary"
273
- display="block"
274
- hasTabularNumbers
275
- >
276
- {device.location}
277
- </Text>
278
- </VStack>
279
- </StackItem>
280
- {device.action && (
281
- <Button
282
- label={device.action}
283
- variant="secondary"
284
- size="sm"
285
- onClick={() => {}}
286
- style={actionNoWrap}
287
- />
288
- )}
296
+ </VStack>
297
+ </StackItem>
298
+ {device.action && (
299
+ /* No handler: the showcase has no backend, and an
300
+ empty onClick makes an unwired control look wired.
301
+ The shared rows take the same stance. */
302
+ <Button
303
+ label={device.action}
304
+ variant="secondary"
305
+ size="sm"
306
+ style={actionNoWrap}
307
+ />
308
+ )}
309
+ </HStack>
310
+ ))}
311
+ </VStack>
312
+
313
+ <VStack gap={2}>
314
+ <Heading level={2}>Account</Heading>
315
+ <HStack hAlign="between" vAlign="start" style={rowPadding}>
316
+ <VStack gap={1}>
317
+ <Text type="body" weight="semibold" display="block">
318
+ Deactivate your account
319
+ </Text>
320
+ <Text type="supporting" color="secondary" display="block">
321
+ This action cannot be undone
322
+ </Text>
323
+ </VStack>
324
+ <Button
325
+ label="Deactivate"
326
+ variant="secondary"
327
+ size="sm"
328
+ style={actionNoWrap}
329
+ />
289
330
  </HStack>
290
- ))}
291
- </VStack>
292
-
293
- <VStack gap={2}>
294
- <Heading level={2}>Account</Heading>
295
- <HStack hAlign="between" vAlign="start" style={rowPadding}>
296
- <VStack gap={1}>
297
- <Text type="body" weight="semibold" display="block">
298
- Deactivate your account
299
- </Text>
300
- <Text type="supporting" color="secondary" display="block">
301
- This action cannot be undone
302
- </Text>
303
- </VStack>
304
- <Button
305
- label="Deactivate"
306
- variant="secondary"
307
- size="sm"
308
- onClick={() => {}}
309
- style={actionNoWrap}
310
- />
311
- </HStack>
331
+ </VStack>
312
332
  </VStack>
313
- </VStack>
314
- )}
315
-
316
- {activeTab === "shared" && (
317
- <VStack gap={8}>
318
- <VStack gap={2}>
319
- <Heading level={2}>Shared access</Heading>
320
- <Text type="body" color="secondary">
321
- Review each request carefully before approving access. We&apos;ll email your
322
- kin or coven-mate a 4-digit code that lets them enter your crypt from their
323
- trusted device.
324
- </Text>
333
+ )}
334
+
335
+ {activeTab === "shared" && (
336
+ <VStack gap={8}>
337
+ <VStack gap={2}>
338
+ <Heading level={2}>Shared access</Heading>
339
+ <Text type="body" color="secondary">
340
+ Review each request carefully before approving access. We&apos;ll email
341
+ your kin or coven-mate a 4-digit code that lets them enter your crypt from
342
+ their trusted device.
343
+ </Text>
344
+ </VStack>
345
+
346
+ <Card variant="muted">
347
+ <HStack gap={4} vAlign="start">
348
+ <Center width={48} height={48} style={iconBox}>
349
+ <Icon icon={Lock} />
350
+ </Center>
351
+ <VStack gap={1}>
352
+ <Text type="body">Adding devices from people you trust</Text>
353
+ <Text type="body" color="secondary">
354
+ When you approve a request, you grant someone full access to your
355
+ account. They&apos;ll be able to change reservations and send messages
356
+ on your behalf.
357
+ </Text>
358
+ </VStack>
359
+ </HStack>
360
+ </Card>
325
361
  </VStack>
326
-
327
- <Card variant="muted">
328
- <HStack gap={4} vAlign="start">
329
- <Center width={48} height={48} style={iconBox}>
330
- <Icon icon={Lock} />
331
- </Center>
332
- <VStack gap={1}>
333
- <Text type="body">Adding devices from people you trust</Text>
334
- <Text type="body" color="secondary">
335
- When you approve a request, you grant someone full access to your
336
- account. They&apos;ll be able to change reservations and send messages
337
- on your behalf.
338
- </Text>
339
- </VStack>
340
- </HStack>
341
- </Card>
342
- </VStack>
343
- )}
344
- </VStack>
345
- )}
346
-
347
- {activeNav === "Languages & currency" && (
348
- <VStack gap={6}>
349
- {!isNarrow && <Heading level={1}>Languages &amp; currency</Heading>}
350
- <VStack gap={0}>
351
- <ExpandableRow
352
- label="Preferred language"
353
- value={LANGUAGES.find((l) => l.value === language)?.label ?? language}
354
- isExpanded={expandedRow === "language"}
355
- onEdit={() => setExpandedRow("language")}
356
- onCancel={() => setExpandedRow(null)}
357
- onSave={() => setExpandedRow(null)}
358
- hasDivider={false}
359
- style={rowPadding}
360
- >
361
- <Selector
362
- label="Language"
363
- isLabelHidden
364
- size="lg"
365
- value={language}
366
- onChange={setLanguage}
367
- options={LANGUAGES}
368
- />
369
- </ExpandableRow>
370
- <ExpandableRow
371
- label="Preferred currency"
372
- value={CURRENCIES.find((c) => c.value === currency)?.label ?? currency}
373
- isExpanded={expandedRow === "currency"}
374
- onEdit={() => setExpandedRow("currency")}
375
- onCancel={() => setExpandedRow(null)}
376
- onSave={() => setExpandedRow(null)}
377
- hasDivider={false}
378
- style={rowPadding}
379
- >
380
- <Selector
381
- label="Currency"
382
- isLabelHidden
383
- size="lg"
384
- value={currency}
385
- onChange={setCurrency}
386
- options={CURRENCIES}
387
- />
388
- </ExpandableRow>
389
- <ExpandableRow
390
- label="Time zone"
391
- value={TIMEZONES.find((t) => t.value === timezone)?.label ?? timezone}
392
- isExpanded={expandedRow === "timezone"}
393
- onEdit={() => setExpandedRow("timezone")}
394
- onCancel={() => setExpandedRow(null)}
395
- onSave={() => setExpandedRow(null)}
396
- hasDivider={false}
397
- style={rowPadding}
398
- >
399
- <Selector
362
+ )}
363
+ </VStack>
364
+ )}
365
+
366
+ {activeNav === "Languages & currency" && (
367
+ <VStack gap={6}>
368
+ {!isNarrow && <Heading level={1}>Languages &amp; currency</Heading>}
369
+ <VStack gap={0}>
370
+ <ExpandableRow
371
+ label="Preferred language"
372
+ value={LANGUAGES.find((l) => l.value === language)?.label ?? language}
373
+ isExpanded={expandedRow === "language"}
374
+ onEdit={() => setExpandedRow("language")}
375
+ onCancel={() => setExpandedRow(null)}
376
+ onSave={() => setExpandedRow(null)}
377
+ hasDivider={false}
378
+ style={rowPadding}
379
+ >
380
+ <Selector
381
+ label="Language"
382
+ isLabelHidden
383
+ size="lg"
384
+ value={language}
385
+ onChange={setLanguage}
386
+ options={LANGUAGES}
387
+ />
388
+ </ExpandableRow>
389
+ <ExpandableRow
390
+ label="Preferred currency"
391
+ value={CURRENCIES.find((c) => c.value === currency)?.label ?? currency}
392
+ isExpanded={expandedRow === "currency"}
393
+ onEdit={() => setExpandedRow("currency")}
394
+ onCancel={() => setExpandedRow(null)}
395
+ onSave={() => setExpandedRow(null)}
396
+ hasDivider={false}
397
+ style={rowPadding}
398
+ >
399
+ <Selector
400
+ label="Currency"
401
+ isLabelHidden
402
+ size="lg"
403
+ value={currency}
404
+ onChange={setCurrency}
405
+ options={CURRENCIES}
406
+ />
407
+ </ExpandableRow>
408
+ <ExpandableRow
400
409
  label="Time zone"
401
- isLabelHidden
402
- size="lg"
403
- value={timezone}
404
- onChange={setTimezone}
405
- options={TIMEZONES}
406
- />
407
- </ExpandableRow>
410
+ value={TIMEZONES.find((t) => t.value === timezone)?.label ?? timezone}
411
+ isExpanded={expandedRow === "timezone"}
412
+ onEdit={() => setExpandedRow("timezone")}
413
+ onCancel={() => setExpandedRow(null)}
414
+ onSave={() => setExpandedRow(null)}
415
+ hasDivider={false}
416
+ style={rowPadding}
417
+ >
418
+ <Selector
419
+ label="Time zone"
420
+ isLabelHidden
421
+ size="lg"
422
+ value={timezone}
423
+ onChange={setTimezone}
424
+ options={TIMEZONES}
425
+ />
426
+ </ExpandableRow>
427
+ </VStack>
408
428
  </VStack>
409
- </VStack>
410
- )}
411
-
412
- {activeNav === "Personal information" && (
413
- <VStack gap={6}>
414
- {!isNarrow && <Heading level={1}>Personal information</Heading>}
415
- <VStack gap={0}>
416
- <ExpandableRow
417
- label="Legal name"
418
- value={legalName}
419
- isExpanded={expandedRow === "legalName"}
420
- onEdit={() => setExpandedRow("legalName")}
421
- onCancel={() => setExpandedRow(null)}
422
- onSave={() => setExpandedRow(null)}
423
- hasDivider={false}
424
- style={rowPadding}
425
- >
426
- <TextInput
429
+ )}
430
+
431
+ {activeNav === "Personal information" && (
432
+ <VStack gap={6}>
433
+ {!isNarrow && <Heading level={1}>Personal information</Heading>}
434
+ <VStack gap={0}>
435
+ <ExpandableRow
427
436
  label="Legal name"
428
- isLabelHidden
429
437
  value={legalName}
430
- onChange={setLegalName}
431
- />
432
- </ExpandableRow>
433
- <ExpandableRow
434
- label="Preferred first name"
435
- value={preferredName || "Not provided"}
436
- isExpanded={expandedRow === "preferredName"}
437
- onEdit={() => setExpandedRow("preferredName")}
438
- onCancel={() => setExpandedRow(null)}
439
- onSave={() => setExpandedRow(null)}
440
- hasDivider={false}
441
- style={rowPadding}
442
- >
443
- <TextInput
438
+ isExpanded={expandedRow === "legalName"}
439
+ onEdit={() => setExpandedRow("legalName")}
440
+ onCancel={() => setExpandedRow(null)}
441
+ onSave={() => setExpandedRow(null)}
442
+ hasDivider={false}
443
+ style={rowPadding}
444
+ >
445
+ <TextInput
446
+ label="Legal name"
447
+ isLabelHidden
448
+ value={legalName}
449
+ onChange={setLegalName}
450
+ />
451
+ </ExpandableRow>
452
+ <ExpandableRow
444
453
  label="Preferred first name"
445
- isLabelHidden
446
- value={preferredName}
447
- onChange={setPreferredName}
448
- />
449
- </ExpandableRow>
450
- <ExpandableRow
451
- label="Email address"
452
- value={email}
453
- isExpanded={expandedRow === "email"}
454
- onEdit={() => setExpandedRow("email")}
455
- onCancel={() => setExpandedRow(null)}
456
- onSave={() => setExpandedRow(null)}
457
- hasDivider={false}
458
- style={rowPadding}
459
- >
460
- <TextInput
454
+ value={preferredName || "Not provided"}
455
+ isExpanded={expandedRow === "preferredName"}
456
+ onEdit={() => setExpandedRow("preferredName")}
457
+ onCancel={() => setExpandedRow(null)}
458
+ onSave={() => setExpandedRow(null)}
459
+ hasDivider={false}
460
+ style={rowPadding}
461
+ >
462
+ <TextInput
463
+ label="Preferred first name"
464
+ isLabelHidden
465
+ value={preferredName}
466
+ onChange={setPreferredName}
467
+ />
468
+ </ExpandableRow>
469
+ <ExpandableRow
461
470
  label="Email address"
462
- isLabelHidden
463
471
  value={email}
464
- onChange={setEmail}
465
- />
466
- </ExpandableRow>
467
- <ExpandableRow
468
- label="Phone number"
469
- value={phone}
470
- isExpanded={expandedRow === "phone"}
471
- onEdit={() => setExpandedRow("phone")}
472
- onCancel={() => setExpandedRow(null)}
473
- onSave={() => setExpandedRow(null)}
474
- hasDivider={false}
475
- style={rowPadding}
476
- >
477
- <TextInput
472
+ isExpanded={expandedRow === "email"}
473
+ onEdit={() => setExpandedRow("email")}
474
+ onCancel={() => setExpandedRow(null)}
475
+ onSave={() => setExpandedRow(null)}
476
+ hasDivider={false}
477
+ style={rowPadding}
478
+ >
479
+ <TextInput
480
+ label="Email address"
481
+ isLabelHidden
482
+ value={email}
483
+ onChange={setEmail}
484
+ />
485
+ </ExpandableRow>
486
+ <ExpandableRow
478
487
  label="Phone number"
479
- isLabelHidden
480
488
  value={phone}
481
- onChange={setPhone}
489
+ isExpanded={expandedRow === "phone"}
490
+ onEdit={() => setExpandedRow("phone")}
491
+ onCancel={() => setExpandedRow(null)}
492
+ onSave={() => setExpandedRow(null)}
493
+ hasDivider={false}
494
+ style={rowPadding}
495
+ >
496
+ <TextInput
497
+ label="Phone number"
498
+ isLabelHidden
499
+ value={phone}
500
+ onChange={setPhone}
501
+ />
502
+ </ExpandableRow>
503
+ <InfoRowItem
504
+ label="Identity verification"
505
+ value="Verified"
506
+ action=""
507
+ hasDivider={false}
508
+ style={rowPadding}
482
509
  />
483
- </ExpandableRow>
484
- <InfoRowItem
485
- label="Identity verification"
486
- value="Verified"
487
- action=""
488
- hasDivider={false}
489
- style={rowPadding}
490
- />
491
- <ExpandableRow
492
- label="Residential address"
493
- value={address || "Not provided"}
494
- isExpanded={expandedRow === "address"}
495
- onEdit={() => setExpandedRow("address")}
496
- onCancel={() => setExpandedRow(null)}
497
- onSave={() => setExpandedRow(null)}
498
- hasDivider={false}
499
- style={rowPadding}
500
- >
501
- <TextInput
510
+ <ExpandableRow
502
511
  label="Residential address"
503
- isLabelHidden
504
- value={address}
505
- onChange={setAddress}
506
- />
507
- </ExpandableRow>
508
- <ExpandableRow
509
- label="Mailing address"
510
- value={mailingAddress || "Not provided"}
511
- isExpanded={expandedRow === "mailingAddress"}
512
- onEdit={() => setExpandedRow("mailingAddress")}
513
- onCancel={() => setExpandedRow(null)}
514
- onSave={() => setExpandedRow(null)}
515
- hasDivider={false}
516
- style={rowPadding}
517
- >
518
- <TextInput
512
+ value={address || "Not provided"}
513
+ isExpanded={expandedRow === "address"}
514
+ onEdit={() => setExpandedRow("address")}
515
+ onCancel={() => setExpandedRow(null)}
516
+ onSave={() => setExpandedRow(null)}
517
+ hasDivider={false}
518
+ style={rowPadding}
519
+ >
520
+ <TextInput
521
+ label="Residential address"
522
+ isLabelHidden
523
+ value={address}
524
+ onChange={setAddress}
525
+ />
526
+ </ExpandableRow>
527
+ <ExpandableRow
519
528
  label="Mailing address"
520
- isLabelHidden
521
- value={mailingAddress}
522
- onChange={setMailingAddress}
523
- />
524
- </ExpandableRow>
525
- <ExpandableRow
526
- label="Emergency contact"
527
- value={emergencyContact}
528
- isExpanded={expandedRow === "emergencyContact"}
529
- onEdit={() => setExpandedRow("emergencyContact")}
530
- onCancel={() => setExpandedRow(null)}
531
- onSave={() => setExpandedRow(null)}
532
- hasDivider={false}
533
- style={rowPadding}
534
- >
535
- <TextInput
529
+ value={mailingAddress || "Not provided"}
530
+ isExpanded={expandedRow === "mailingAddress"}
531
+ onEdit={() => setExpandedRow("mailingAddress")}
532
+ onCancel={() => setExpandedRow(null)}
533
+ onSave={() => setExpandedRow(null)}
534
+ hasDivider={false}
535
+ style={rowPadding}
536
+ >
537
+ <TextInput
538
+ label="Mailing address"
539
+ isLabelHidden
540
+ value={mailingAddress}
541
+ onChange={setMailingAddress}
542
+ />
543
+ </ExpandableRow>
544
+ <ExpandableRow
536
545
  label="Emergency contact"
537
- isLabelHidden
538
546
  value={emergencyContact}
539
- onChange={setEmergencyContact}
540
- />
541
- </ExpandableRow>
547
+ isExpanded={expandedRow === "emergencyContact"}
548
+ onEdit={() => setExpandedRow("emergencyContact")}
549
+ onCancel={() => setExpandedRow(null)}
550
+ onSave={() => setExpandedRow(null)}
551
+ hasDivider={false}
552
+ style={rowPadding}
553
+ >
554
+ <TextInput
555
+ label="Emergency contact"
556
+ isLabelHidden
557
+ value={emergencyContact}
558
+ onChange={setEmergencyContact}
559
+ />
560
+ </ExpandableRow>
561
+ </VStack>
562
+
563
+ <Card padding={4}>
564
+ <VStack gap={4}>
565
+ {INFO_TILES.map((tile) => (
566
+ <HStack key={tile.title} gap={3} vAlign="start">
567
+ <Center width={48} height={48} style={iconBox}>
568
+ <Icon icon={tile.icon} />
569
+ </Center>
570
+ <VStack gap={1}>
571
+ <Text type="body" weight="semibold" display="block">
572
+ {tile.title}
573
+ </Text>
574
+ <Text type="body" color="secondary" display="block">
575
+ {tile.body}
576
+ </Text>
577
+ </VStack>
578
+ </HStack>
579
+ ))}
580
+ </VStack>
581
+ </Card>
542
582
  </VStack>
583
+ )}
543
584
 
544
- <Card padding={4}>
545
- <VStack gap={4}>
546
- {INFO_TILES.map((tile) => (
547
- <HStack key={tile.title} gap={3} vAlign="start">
548
- <Center width={48} height={48} style={iconBox}>
549
- <Icon icon={tile.icon} />
550
- </Center>
551
- <VStack gap={1}>
552
- <Text type="body" weight="semibold" display="block">
553
- {tile.title}
554
- </Text>
555
- <Text type="body" color="secondary" display="block">
556
- {tile.body}
557
- </Text>
558
- </VStack>
559
- </HStack>
560
- ))}
561
- </VStack>
562
- </Card>
563
- </VStack>
564
- )}
585
+ {activeNav === "Privacy" && (
586
+ <VStack gap={6}>
587
+ {!isNarrow && <Heading level={1}>Privacy</Heading>}
565
588
 
566
- {activeNav === "Privacy" && (
567
- <VStack gap={6}>
568
- {!isNarrow && <Heading level={1}>Privacy</Heading>}
589
+ <VStack gap={8}>
590
+ <VStack gap={2}>
591
+ <Heading level={2}>Messages</Heading>
592
+ <VStack style={rowPadding}>
593
+ <Switch
594
+ label="Show people when I've read their messages."
595
+ value={readReceipts}
596
+ onChange={setReadReceipts}
597
+ labelPosition="start"
598
+ labelSpacing="spread"
599
+ />
600
+ </VStack>
601
+ <HStack hAlign="between" vAlign="center" style={rowPadding}>
602
+ <Text type="body" weight="semibold">
603
+ Blocked people
604
+ </Text>
605
+ <Link href={SELF_HASH}>View</Link>
606
+ </HStack>
607
+ </VStack>
569
608
 
570
- <VStack gap={8}>
571
- <VStack gap={2}>
572
- <Heading level={2}>Messages</Heading>
573
- <VStack style={rowPadding}>
574
- <Switch
575
- label="Show people when I've read their messages."
576
- value={readReceipts}
577
- onChange={setReadReceipts}
578
- labelPosition="start"
579
- labelSpacing="spread"
580
- />
609
+ <VStack gap={2}>
610
+ <Heading level={2}>Listings</Heading>
611
+ <VStack style={rowPadding}>
612
+ <Switch
613
+ label="List my wares in the scrying mirrors"
614
+ description="Turning this on means scrying engines, like Google, will show your wares to seekers."
615
+ value={searchEngines}
616
+ onChange={setSearchEngines}
617
+ labelPosition="start"
618
+ labelSpacing="spread"
619
+ />
620
+ </VStack>
581
621
  </VStack>
582
- <HStack hAlign="between" vAlign="center" style={rowPadding}>
583
- <Text type="body" weight="semibold">
584
- Blocked people
622
+
623
+ <VStack gap={4}>
624
+ <Heading level={2}>Reviews</Heading>
625
+ <Text type="body" color="secondary">
626
+ Choose what&apos;s shared when you write a review.{" "}
627
+ <Link href={SELF_HASH} type="body" hasUnderline>
628
+ Learn more
629
+ </Link>
585
630
  </Text>
586
- <Link href={SELF_HASH}>View</Link>
587
- </HStack>
588
- </VStack>
631
+ <VStack gap={4}>
632
+ <Switch
633
+ label="Show my home haunt and country"
634
+ description="Ex: City and country"
635
+ value={showCity}
636
+ onChange={setShowCity}
637
+ labelPosition="start"
638
+ labelSpacing="spread"
639
+ />
640
+ <Switch
641
+ label="Show my journey type"
642
+ description="Ex: Rested with kin or familiars"
643
+ value={showTripType}
644
+ onChange={setShowTripType}
645
+ labelPosition="start"
646
+ labelSpacing="spread"
647
+ />
648
+ <Switch
649
+ label="Show my length of stay"
650
+ description="Ex: A few nights, about a week, etc."
651
+ value={showStayLength}
652
+ onChange={setShowStayLength}
653
+ labelPosition="start"
654
+ labelSpacing="spread"
655
+ />
656
+ <Switch
657
+ label="Show my booked revels"
658
+ description="Ex: Midnight feasts and tasting rituals"
659
+ value={showServices}
660
+ onChange={setShowServices}
661
+ labelPosition="start"
662
+ labelSpacing="spread"
663
+ />
664
+ </VStack>
665
+ </VStack>
589
666
 
590
- <VStack gap={2}>
591
- <Heading level={2}>Listings</Heading>
592
- <VStack style={rowPadding}>
667
+ <VStack gap={4}>
668
+ <Heading level={2}>Data privacy</Heading>
669
+ <Card>
670
+ <HStack hAlign="between" vAlign="center">
671
+ <Text type="body">Request my personal data</Text>
672
+ <Button
673
+ label="Request"
674
+ variant="secondary"
675
+ size="sm"
676
+ style={actionNoWrap}
677
+ />
678
+ </HStack>
679
+ </Card>
593
680
  <Switch
594
- label="List my wares in the scrying mirrors"
595
- description="Turning this on means scrying engines, like Google, will show your wares to seekers."
596
- value={searchEngines}
597
- onChange={setSearchEngines}
681
+ label="Help improve AI-powered features"
682
+ description="When this is on, we use your data to develop and improve AI models."
683
+ value={aiFeatures}
684
+ onChange={setAiFeatures}
598
685
  labelPosition="start"
599
686
  labelSpacing="spread"
600
687
  />
688
+ <Card>
689
+ <HStack hAlign="between" vAlign="center">
690
+ <Text type="body">Delete my account</Text>
691
+ <Button
692
+ label="Delete"
693
+ variant="secondary"
694
+ size="sm"
695
+ style={actionNoWrap}
696
+ />
697
+ </HStack>
698
+ </Card>
699
+ <Card variant="muted">
700
+ <HStack gap={4} vAlign="start">
701
+ <Center width={48} height={48} style={iconBox}>
702
+ <Icon icon={ShieldCheck} />
703
+ </Center>
704
+ <VStack gap={1}>
705
+ <Text type="body">Committed to privacy</Text>
706
+ <Text type="body" color="secondary">
707
+ We&apos;re committed to keeping your data protected. See details in
708
+ our{" "}
709
+ <Link href={SELF_HASH} type="supporting" hasUnderline>
710
+ Privacy Policy
711
+ </Link>
712
+ .
713
+ </Text>
714
+ </VStack>
715
+ </HStack>
716
+ </Card>
601
717
  </VStack>
602
718
  </VStack>
719
+ </VStack>
720
+ )}
603
721
 
604
- <VStack gap={4}>
605
- <Heading level={2}>Reviews</Heading>
606
- <Text type="body" color="secondary">
607
- Choose what&apos;s shared when you write a review.{" "}
608
- <Link href={SELF_HASH} type="body" hasUnderline>
609
- Learn more
610
- </Link>
611
- </Text>
612
- <VStack gap={4}>
613
- <Switch
614
- label="Show my home haunt and country"
615
- description="Ex: City and country"
616
- value={showCity}
617
- onChange={setShowCity}
618
- labelPosition="start"
619
- labelSpacing="spread"
620
- />
621
- <Switch
622
- label="Show my journey type"
623
- description="Ex: Rested with kin or familiars"
624
- value={showTripType}
625
- onChange={setShowTripType}
626
- labelPosition="start"
627
- labelSpacing="spread"
628
- />
722
+ {activeNav === "Notifications" && (
723
+ <VStack gap={6}>
724
+ {!isNarrow && <Heading level={1}>Notifications</Heading>}
725
+ <VStack gap={2}>
726
+ <Heading level={2}>Messages</Heading>
727
+ <VStack gap={4} style={rowPadding}>
629
728
  <Switch
630
- label="Show my length of stay"
631
- description="Ex: A few nights, about a week, etc."
632
- value={showStayLength}
633
- onChange={setShowStayLength}
729
+ label="Email notifications"
730
+ description="Coven updates, journey reminders, and crypt activity."
731
+ value={emailNotif}
732
+ onChange={setEmailNotif}
634
733
  labelPosition="start"
635
734
  labelSpacing="spread"
636
735
  />
637
736
  <Switch
638
- label="Show my booked revels"
639
- description="Ex: Midnight feasts and tasting rituals"
640
- value={showServices}
641
- onChange={setShowServices}
737
+ label="Push notifications"
738
+ description="Swift ravens for messages and booking requests."
739
+ value={pushNotif}
740
+ onChange={setPushNotif}
642
741
  labelPosition="start"
643
742
  labelSpacing="spread"
644
743
  />
645
744
  </VStack>
646
745
  </VStack>
746
+ </VStack>
747
+ )}
647
748
 
648
- <VStack gap={4}>
649
- <Heading level={2}>Data privacy</Heading>
650
- <Card>
651
- <HStack hAlign="between" vAlign="center">
652
- <Text type="body">Request my personal data</Text>
653
- <Button
654
- label="Request"
655
- variant="secondary"
656
- size="sm"
657
- onClick={() => {}}
658
- style={actionNoWrap}
659
- />
660
- </HStack>
661
- </Card>
662
- <Switch
663
- label="Help improve AI-powered features"
664
- description="When this is on, we use your data to develop and improve AI models."
665
- value={aiFeatures}
666
- onChange={setAiFeatures}
667
- labelPosition="start"
668
- labelSpacing="spread"
669
- />
670
- <Card>
671
- <HStack hAlign="between" vAlign="center">
672
- <Text type="body">Delete my account</Text>
673
- <Button
674
- label="Delete"
675
- variant="secondary"
676
- size="sm"
677
- onClick={() => {}}
678
- style={actionNoWrap}
679
- />
680
- </HStack>
681
- </Card>
682
- <Card variant="muted">
683
- <HStack gap={4} vAlign="start">
684
- <Center width={48} height={48} style={iconBox}>
685
- <Icon icon={ShieldCheck} />
686
- </Center>
687
- <VStack gap={1}>
688
- <Text type="body">Committed to privacy</Text>
689
- <Text type="body" color="secondary">
690
- We&apos;re committed to keeping your data protected. See details in our{" "}
691
- <Link href={SELF_HASH} type="supporting" hasUnderline>
692
- Privacy Policy
693
- </Link>
694
- .
695
- </Text>
696
- </VStack>
697
- </HStack>
698
- </Card>
749
+ {activeNav === "Taxes" && (
750
+ <VStack gap={6}>
751
+ {!isNarrow && <Heading level={1}>Taxes</Heading>}
752
+ <VStack gap={2}>
753
+ <Heading level={2}>Tax scrolls</Heading>
754
+ {TAX_ROWS.map((row) => (
755
+ <InfoRowItem key={row.label} {...row} hasDivider={false} style={rowPadding} />
756
+ ))}
699
757
  </VStack>
700
758
  </VStack>
701
- </VStack>
702
- )}
703
-
704
- {activeNav === "Notifications" && (
705
- <VStack gap={6}>
706
- {!isNarrow && <Heading level={1}>Notifications</Heading>}
707
- <VStack gap={2}>
708
- <Heading level={2}>Messages</Heading>
709
- <VStack gap={4} style={rowPadding}>
710
- <Switch
711
- label="Email notifications"
712
- description="Coven updates, journey reminders, and crypt activity."
713
- value={emailNotif}
714
- onChange={setEmailNotif}
715
- labelPosition="start"
716
- labelSpacing="spread"
717
- />
718
- <Switch
719
- label="Push notifications"
720
- description="Swift ravens for messages and booking requests."
721
- value={pushNotif}
722
- onChange={setPushNotif}
723
- labelPosition="start"
724
- labelSpacing="spread"
725
- />
759
+ )}
760
+
761
+ {activeNav === "Payments" && (
762
+ <VStack gap={6}>
763
+ {!isNarrow && <Heading level={1}>Payments</Heading>}
764
+ <VStack gap={2}>
765
+ <Heading level={2}>Payout crypt</Heading>
766
+ {PAYOUT_ROWS.map((row) => (
767
+ <InfoRowItem key={row.label} {...row} hasDivider={false} style={rowPadding} />
768
+ ))}
726
769
  </VStack>
727
770
  </VStack>
728
- </VStack>
729
- )}
730
-
731
- {activeNav === "Taxes" && (
732
- <VStack gap={6}>
733
- {!isNarrow && <Heading level={1}>Taxes</Heading>}
734
- <VStack gap={2}>
735
- <Heading level={2}>Tax scrolls</Heading>
736
- {TAX_ROWS.map((row) => (
737
- <InfoRowItem key={row.label} {...row} hasDivider={false} style={rowPadding} />
738
- ))}
739
- </VStack>
740
- </VStack>
741
- )}
742
-
743
- {activeNav === "Payments" && (
744
- <VStack gap={6}>
745
- {!isNarrow && <Heading level={1}>Payments</Heading>}
746
- <VStack gap={2}>
747
- <Heading level={2}>Payout crypt</Heading>
748
- {PAYOUT_ROWS.map((row) => (
749
- <InfoRowItem key={row.label} {...row} hasDivider={false} style={rowPadding} />
750
- ))}
751
- </VStack>
752
- </VStack>
753
- )}
754
-
755
- {activeNav === "Travel for work" && (
756
- <VStack gap={6}>
757
- {!isNarrow && <Heading level={1}>Travel for work</Heading>}
758
- <VStack gap={2}>
759
- <Heading level={2}>Work haunts</Heading>
760
- <VStack style={rowPadding}>
761
- <Switch
762
- label="Show night-errand options at checkout"
763
- description="Add a coven address to expense night errands and unlock travel-ready wares."
764
- value={workTrips}
765
- onChange={setWorkTrips}
766
- labelPosition="start"
767
- labelSpacing="spread"
768
- />
771
+ )}
772
+
773
+ {activeNav === "Travel for work" && (
774
+ <VStack gap={6}>
775
+ {!isNarrow && <Heading level={1}>Travel for work</Heading>}
776
+ <VStack gap={2}>
777
+ <Heading level={2}>Work haunts</Heading>
778
+ <VStack style={rowPadding}>
779
+ <Switch
780
+ label="Show night-errand options at checkout"
781
+ description="Add a coven address to expense night errands and unlock travel-ready wares."
782
+ value={workTrips}
783
+ onChange={setWorkTrips}
784
+ labelPosition="start"
785
+ labelSpacing="spread"
786
+ />
787
+ </VStack>
769
788
  </VStack>
770
789
  </VStack>
771
- </VStack>
772
- )}
773
-
774
- {activeNav === "Professional hosting tools" && (
775
- <VStack gap={6}>
776
- {!isNarrow && <Heading level={1}>Professional hosting tools</Heading>}
777
- <Card variant="muted">
778
- <HStack gap={4} vAlign="start">
779
- <Center width={48} height={48} style={iconBox}>
780
- <Icon icon={Wrench} />
781
- </Center>
782
- <VStack gap={1}>
783
- <Text type="body">Hosting grimoire</Text>
784
- <Text type="body" color="secondary">
785
- Manage multiple listings, route tasks to co-hosts, and review consolidated
786
- payouts from one place.
787
- </Text>
788
- </VStack>
789
- </HStack>
790
- </Card>
791
- </VStack>
792
- )}
793
- </VStack>
794
- </LayoutContent>
795
- }
796
- />
790
+ )}
791
+
792
+ {activeNav === "Professional hosting tools" && (
793
+ <VStack gap={6}>
794
+ {!isNarrow && <Heading level={1}>Professional hosting tools</Heading>}
795
+ <Card variant="muted">
796
+ <HStack gap={4} vAlign="start">
797
+ <Center width={48} height={48} style={iconBox}>
798
+ <Icon icon={Wrench} />
799
+ </Center>
800
+ <VStack gap={1}>
801
+ <Text type="body">Hosting grimoire</Text>
802
+ <Text type="body" color="secondary">
803
+ Manage multiple listings, route tasks to co-hosts, and review consolidated
804
+ payouts from one place.
805
+ </Text>
806
+ </VStack>
807
+ </HStack>
808
+ </Card>
809
+ </VStack>
810
+ )}
811
+ </VStack>
812
+ </LayoutContent>
813
+ }
814
+ />
815
+ </>
797
816
  );
798
817
  }