astryx-dracula 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/BRAND.md +30 -18
  2. package/README.md +22 -11
  3. package/USAGE.md +29 -10
  4. package/astryx-dracula.js +19 -13
  5. package/astryx-dracula.variants.d.ts +15 -0
  6. package/package.json +59 -17
  7. package/shared/auth-chrome-config.ts +18 -0
  8. package/shared/auth-chrome.tsx +25 -0
  9. package/shared/auth-copy.ts +22 -0
  10. package/shared/chaptered-doc-config.ts +92 -0
  11. package/shared/chaptered-doc.tsx +332 -0
  12. package/shared/chart-hues.ts +27 -0
  13. package/shared/chart-labels.tsx +44 -0
  14. package/shared/chart-legend.tsx +100 -0
  15. package/shared/chart-panel-style.ts +13 -0
  16. package/shared/data-bar.tsx +160 -0
  17. package/shared/gallery-image.ts +20 -0
  18. package/shared/login-demo.ts +7 -0
  19. package/shared/metric-delta.tsx +36 -0
  20. package/shared/order-desk.tsx +273 -0
  21. package/shared/revenue-chart.tsx +174 -0
  22. package/shared/scene-castle.tsx +322 -0
  23. package/shared/scene-frame.tsx +88 -0
  24. package/shared/scene-hues.ts +49 -0
  25. package/shared/scene-tile.tsx +128 -0
  26. package/shared/settings-data.ts +125 -0
  27. package/shared/settings-rows.tsx +179 -0
  28. package/shared/sparkline.tsx +94 -0
  29. package/shared/sso-icons.tsx +30 -0
  30. package/templates/ai-chat-landing.tsx +67 -32
  31. package/templates/ai-chat.tsx +41 -20
  32. package/templates/blank.tsx +5 -2
  33. package/templates/centered-hero.tsx +15 -124
  34. package/templates/classic-gallery.tsx +21 -65
  35. package/templates/contact-form.tsx +62 -40
  36. package/templates/dashboard-portfolio.tsx +146 -227
  37. package/templates/dashboard.tsx +123 -121
  38. package/templates/detail-page.tsx +101 -95
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +85 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +33 -21
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +54 -74
  45. package/templates/editor.tsx +119 -89
  46. package/templates/file-explorer.tsx +133 -84
  47. package/templates/form-two-column.tsx +62 -124
  48. package/templates/gallery-hero.tsx +17 -171
  49. package/templates/ide.tsx +27 -21
  50. package/templates/incident-console.tsx +105 -69
  51. package/templates/kanban-board.tsx +100 -41
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +38 -41
  54. package/templates/login-card.tsx +59 -86
  55. package/templates/login-split.tsx +88 -128
  56. package/templates/login-sso.tsx +107 -68
  57. package/templates/login.tsx +108 -52
  58. package/templates/messaging-shell.tsx +172 -143
  59. package/templates/mixed-gallery.tsx +31 -130
  60. package/templates/payment-form.tsx +369 -289
  61. package/templates/product-detail.tsx +40 -57
  62. package/templates/product-gallery.tsx +31 -40
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +145 -286
  66. package/templates/settings-sidebar.tsx +179 -267
  67. package/templates/settings.tsx +42 -13
  68. package/templates/shell-nav.tsx +32 -23
  69. package/templates/shell-side-nav.tsx +30 -19
  70. package/templates/shell-top-nav.tsx +81 -43
  71. package/templates/side-gallery.tsx +39 -93
  72. package/templates/table-grouped.tsx +63 -33
  73. package/templates/table-page-chart.tsx +24 -297
  74. package/templates/table-page-heatmap-status.tsx +157 -91
  75. package/templates/table-page-shoe-store-heatmap.tsx +19 -288
  76. package/templates/table-page.tsx +24 -6
  77. package/templates/table.tsx +24 -7
  78. package/templates/tech-report.template.mjs +6 -0
  79. package/templates/tech-report.tsx +1486 -0
  80. package/templates/theme-showcase.template.mjs +1 -1
  81. package/templates/theme-showcase.tsx +313 -249
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
@@ -1,9 +1,20 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LC > V[g=8] > (V[g=2] > Hd"Button"[level=1] + Tx"March 30, 2026"[t=supporting]) + (C.muted[p=0] > Ctr[h=360]) + (V[g=4] > Hd"Usage"[level=2] + Tx"Usage"[t=body] + Hd"Best practices"[level=3] + T) + D + (V[g=4] > Hd"Examples"[level=2] + Tx"Explore"[t=body]) + (V[g=8] > (C[p=0] > (S[p=3] > Tx"Semantics"[t=body]) + Ctr[h=280] + (S.muted[p=3] > V[g=3] > (TL > Tab"Description"! + Tab"Code") + Tx"We have four"[t=body])) + (C[p=0] > (S[p=3] > Tx"Default button with badge"[t=body]) + Ctr[h=280] + (S.muted[p=3] > V[g=3] > (TL > Tab"Description"! + Tab"Code") + Tx"Buttons can include"[t=body])))) + (LP > Outline)
2
4
 
3
- import {useCallback, useState, useMemo, type CSSProperties} from 'react';
5
+ /**
6
+ * Documentation detail — one component, its usage, its guidance and its live examples. (Frame/responsive/container: see XLE header above.)
7
+ */
8
+
9
+ import {
10
+ useCallback,
11
+ useState,
12
+ useMemo,
13
+ type CSSProperties,
14
+ type ReactNode,
15
+ } from 'react';
4
16
  import {Heading, Text} from '@astryxdesign/core/Text';
5
17
  import {Button} from '@astryxdesign/core/Button';
6
- import {IconButton} from '@astryxdesign/core/IconButton';
7
18
  import {Card} from '@astryxdesign/core/Card';
8
19
  import {Avatar} from '@astryxdesign/core/Avatar';
9
20
  import {Badge} from '@astryxdesign/core/Badge';
@@ -12,7 +23,7 @@ import {Banner} from '@astryxdesign/core/Banner';
12
23
  import {CodeBlock} from '@astryxdesign/core/CodeBlock';
13
24
  import {TabList, Tab} from '@astryxdesign/core/TabList';
14
25
  import {Selector} from '@astryxdesign/core/Selector';
15
- import {HStack, VStack, StackItem} from '@astryxdesign/core/Stack';
26
+ import {HStack, VStack} from '@astryxdesign/core/Stack';
16
27
  import {Layout, LayoutContent, LayoutPanel} from '@astryxdesign/core/Layout';
17
28
  import {useMediaQuery} from '@astryxdesign/core/hooks';
18
29
  import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
@@ -23,14 +34,12 @@ import {Icon} from '@astryxdesign/core/Icon';
23
34
  import {Section} from '@astryxdesign/core/Section';
24
35
  import {Center} from '@astryxdesign/core/Center';
25
36
  import {Outline, type OutlineItem} from '@astryxdesign/core/Outline';
26
- import {ExternalLink, Expand, Plus} from 'lucide-react';
37
+ import {Plus} from 'lucide-react';
38
+ import {outlinePanel} from 'astryx-dracula/shared/chaptered-doc-config';
27
39
 
28
- const tabListFlush: CSSProperties = {marginInlineStart: '-12px'};
29
- const outlinePanel: CSSProperties = {
30
- position: 'sticky',
31
- top: 24,
32
- alignSelf: 'start',
33
- paddingBlockStart: 120,
40
+ // Flush the tab list with the example card's 12px inset (Section padding 3).
41
+ const tabListFlush: CSSProperties = {
42
+ marginInlineStart: 'calc(var(--spacing-3) * -1)',
34
43
  };
35
44
 
36
45
  const COMPONENT_OUTLINE_ITEMS: OutlineItem[] = [
@@ -52,7 +61,9 @@ function DialogPreview() {
52
61
  const [isOpen, setIsOpen] = useState(false);
53
62
  return (
54
63
  <VStack gap={3}>
55
- <Heading level={3}>Dialog</Heading>
64
+ <Text type="body" weight="semibold">
65
+ Dialog
66
+ </Text>
56
67
  <Button
57
68
  label="Open Dialog"
58
69
  variant="primary"
@@ -439,7 +450,7 @@ const COMPONENT_DOCS: Record<
439
450
  title: 'Default button with badge',
440
451
  description:
441
452
  'Buttons can include a badge to highlight new or updated actions.',
442
- code: `<Button\n label="Button"\n variant="default"\n/>`,
453
+ code: `<Button\n label="Button"\n variant="secondary"\n/>`,
443
454
  },
444
455
  ],
445
456
  },
@@ -494,6 +505,57 @@ function getComponentDocs(key: string) {
494
505
  };
495
506
  }
496
507
 
508
+ const EXAMPLE_PREVIEWS: Record<string, ReactNode[]> = {
509
+ button: [
510
+ <HStack key="semantics" gap={3} vAlign="center" wrap="wrap">
511
+ <Button label="Flat" variant="ghost" />
512
+ <Button label="Default" variant="secondary" />
513
+ <Button label="Primary" variant="primary" />
514
+ <Button label="Destructive" variant="destructive" />
515
+ </HStack>,
516
+ <Button key="badge" label="Button" variant="secondary" />,
517
+ ],
518
+ };
519
+
520
+ const COMPONENT_PREVIEWS: Record<string, ReactNode> = {
521
+ button: (
522
+ <Button
523
+ label="Button"
524
+ variant="secondary"
525
+ icon={<Icon icon={Plus} />}
526
+ endContent={<Badge label="New" variant="cyan" />}
527
+ />
528
+ ),
529
+ avatar: <Avatar name="Vlad" size="lg" />,
530
+ badge: <Badge label="Design" variant="yellow" />,
531
+ card: (
532
+ <Card>
533
+ <VStack gap={2}>
534
+ <Text type="body" weight="semibold">
535
+ Castle Card
536
+ </Text>
537
+ <Text type="body" color="secondary">
538
+ Cards group related content and actions.
539
+ </Text>
540
+ </VStack>
541
+ </Card>
542
+ ),
543
+ banner: (
544
+ <Banner status="info" title="Information">
545
+ <Text type="body">This is an informational banner message.</Text>
546
+ </Banner>
547
+ ),
548
+ dialog: <DialogPreview />,
549
+ text: <Text type="body">Body text</Text>,
550
+ divider: <Divider />,
551
+ token: <Token label="Dracula" />,
552
+ tooltip: (
553
+ <Tooltip content="Primary action">
554
+ <Button label="Hover me" variant="primary" />
555
+ </Tooltip>
556
+ ),
557
+ };
558
+
497
559
  // ---------------------------------------------------------------------------
498
560
  // ComponentDetailView
499
561
  // ---------------------------------------------------------------------------
@@ -514,55 +576,6 @@ function ComponentDetailView({activeNav}: {activeNav: string}) {
514
576
  }
515
577
  }, []);
516
578
 
517
- const EXAMPLE_PREVIEWS: Record<string, React.ReactNode[]> = {
518
- button: [
519
- <HStack key="semantics" gap={3} vAlign="center" wrap="wrap">
520
- <Button label="Flat" variant="ghost" />
521
- <Button label="Default" variant="secondary" />
522
- <Button label="Primary" variant="primary" />
523
- <Button label="Destructive" variant="destructive" />
524
- </HStack>,
525
- <Button key="badge" label="Button" variant="secondary" />,
526
- ],
527
- };
528
-
529
- const COMPONENT_PREVIEWS: Record<string, React.ReactNode> = {
530
- button: (
531
- <Button
532
- label="Button"
533
- variant="secondary"
534
- icon={<Icon icon={Plus} />}
535
- endContent={<Badge label="New" variant="info" />}
536
- />
537
- ),
538
- avatar: <Avatar name="Vlad" size="lg" />,
539
- badge: <Badge label="Success" variant="success" />,
540
- card: (
541
- <Card>
542
- <VStack gap={2}>
543
- <Heading level={4}>Castle Card</Heading>
544
- <Text type="body" color="secondary">
545
- Cards group related content and actions.
546
- </Text>
547
- </VStack>
548
- </Card>
549
- ),
550
- banner: (
551
- <Banner status="info" title="Information">
552
- <Text type="body">This is an informational banner message.</Text>
553
- </Banner>
554
- ),
555
- dialog: <DialogPreview />,
556
- text: <Text type="body">Body text</Text>,
557
- divider: <Divider />,
558
- token: <Token label="Dracula" />,
559
- tooltip: (
560
- <Tooltip content="Primary action">
561
- <Button label="Hover me" variant="primary" />
562
- </Tooltip>
563
- ),
564
- };
565
-
566
579
  const docs = useMemo(() => getComponentDocs(activeNav), [activeNav]);
567
580
  const previews = EXAMPLE_PREVIEWS[activeNav] ?? [];
568
581
 
@@ -588,7 +601,7 @@ function ComponentDetailView({activeNav}: {activeNav: string}) {
588
601
  <LayoutContent isScrollable={false} padding={8}>
589
602
  <VStack gap={8}>
590
603
  <VStack gap={2}>
591
- <Text type="display-1">{getComponentName(activeNav)}</Text>
604
+ <Heading level={1} type="display-2">{getComponentName(activeNav)}</Heading>
592
605
  <Text type="supporting" color="secondary" hasTabularNumbers>
593
606
  March 30, 2026 · Updated 5:40 p.m. PST
594
607
  </Text>
@@ -608,7 +621,7 @@ function ComponentDetailView({activeNav}: {activeNav: string}) {
608
621
  <Center height={360}>
609
622
  {COMPONENT_PREVIEWS[activeNav] ?? (
610
623
  <Text type="supporting" color="secondary">
611
- Preview coming soon
624
+ Quiet in the crypt — no preview haunts this shelf yet.
612
625
  </Text>
613
626
  )}
614
627
  </Center>
@@ -618,15 +631,14 @@ function ComponentDetailView({activeNav}: {activeNav: string}) {
618
631
  <Heading id="usage" level={2}>
619
632
  Usage
620
633
  </Heading>
621
- <Text type="large" weight="normal">
622
- {docs.usage}
623
- </Text>
634
+ <Text type="body">{docs.usage}</Text>
624
635
  <Heading id="best-practices" level={3}>
625
636
  Best practices
626
637
  </Heading>
627
638
  <Table
628
639
  data={docs.bestPractices as Record<string, unknown>[]}
629
640
  dividers="none"
641
+ hasHover
630
642
  columns={[
631
643
  {
632
644
  key: 'type',
@@ -635,7 +647,7 @@ function ComponentDetailView({activeNav}: {activeNav: string}) {
635
647
  renderCell: (item: Record<string, unknown>) => (
636
648
  <Badge
637
649
  label={item.type === 'do' ? 'Do' : "Don't"}
638
- variant={item.type === 'do' ? 'success' : 'error'}
650
+ variant={item.type === 'do' ? 'green' : 'red'}
639
651
  />
640
652
  ),
641
653
  },
@@ -659,7 +671,7 @@ function ComponentDetailView({activeNav}: {activeNav: string}) {
659
671
  <Heading id="examples" level={2}>
660
672
  Examples
661
673
  </Heading>
662
- <Text type="large" weight="normal">
674
+ <Text type="body">
663
675
  Explore common configurations, variations, and states for this
664
676
  component.
665
677
  </Text>
@@ -669,39 +681,16 @@ function ComponentDetailView({activeNav}: {activeNav: string}) {
669
681
  const tabKey = `${activeNav}-${i}`;
670
682
  const activeTab = exampleTabs[tabKey] ?? 'description';
671
683
  return (
672
- <Card key={i} padding={0}>
684
+ <Card key={example.title} padding={0}>
673
685
  <Section padding={3} variant="transparent">
674
- <HStack gap={3} vAlign="center" wrap="wrap">
675
- <StackItem size="fill">
676
- <Text type="body" weight="medium">
677
- {example.title}
678
- </Text>
679
- </StackItem>
680
- <HStack gap={1} vAlign="center" wrap="wrap">
681
- <Button
682
- label="Open in Craft"
683
- variant="ghost"
684
- size="sm"
685
- icon={<Icon icon={ExternalLink} />}
686
- />
687
- <Button
688
- label="Send to CLI"
689
- variant="ghost"
690
- size="sm"
691
- />
692
- <IconButton
693
- label="Fullscreen"
694
- variant="ghost"
695
- size="sm"
696
- icon={<Icon icon={Expand} />}
697
- />
698
- </HStack>
699
- </HStack>
686
+ <Text type="body" weight="semibold">
687
+ {example.title}
688
+ </Text>
700
689
  </Section>
701
690
  <Center height={280}>
702
691
  {previews[i] ?? (
703
692
  <Text type="supporting" color="secondary">
704
- Preview coming soon
693
+ Quiet in the crypt — no preview haunts this shelf yet.
705
694
  </Text>
706
695
  )}
707
696
  </Center>
@@ -2,5 +2,5 @@ export default {
2
2
  type: 'page',
3
3
  name: 'Technical Documentation',
4
4
  description: 'Install steps, theming setup, and next steps',
5
- category: 'marketing',
5
+ category: 'docs',
6
6
  };
@@ -1,6 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LC > V[g=8] > (V[g=2] > Hd"Getting started with Astryx Dracula"[level=1] + Tx"Last updated March 30, 2026"[t=supporting]) + (C > V[g=3] > (H[j=between] > (H[g=2] > Ic + Tx"AI Assistance"[t=body]) + (H > B"Copy prompt" + DM)) + Tx"Prompt description"[t=body]) + (V[g=4] > Hd"Prerequisites"[level=2] + UL) + D + (V[g=4] > Hd"Install the package"[level=2] + Tx"Description"[t=body] + (V[g=2] > Tx"Step"[t=body] + Cd)*3) + D + (V[g=4] > Hd"Configure theming"[level=2] + Tx"Description"[t=body] + Cd + Tx"Note"[t=body]) + D + (V[g=4] > Hd"Next steps"[level=2] + UL)) + (LP > Outline)
2
4
 
3
- import {useCallback, useState, type CSSProperties} from 'react';
5
+ import {useCallback, useState} from 'react';
4
6
  import {Heading, Text} from '@astryxdesign/core/Text';
5
7
  import {Button} from '@astryxdesign/core/Button';
6
8
  import {Card} from '@astryxdesign/core/Card';
@@ -15,11 +17,31 @@ import {Icon} from '@astryxdesign/core/Icon';
15
17
  import {Outline, type OutlineItem} from '@astryxdesign/core/Outline';
16
18
  import {useMediaQuery} from '@astryxdesign/core/hooks';
17
19
  import {Sparkles, ClipboardCopy, ChevronDown} from 'lucide-react';
20
+ import {outlinePanel} from 'astryx-dracula/shared/chaptered-doc-config';
18
21
 
19
22
  // ---------------------------------------------------------------------------
20
23
  // Main component
21
24
  // ---------------------------------------------------------------------------
22
25
 
26
+ /**
27
+ * Documentation technical — the getting-started guide: prerequisites, install steps, theming, and next steps. (Frame/responsive/container: see XLE header above.)
28
+ */
29
+
30
+ // Same prompt in the copy action and the visible body: one string, so the
31
+ // two can never drift.
32
+ const SETUP_PROMPT =
33
+ 'Help me get set up with Astryx Dracula. Based on my project, do the following: 1. Install @astryxdesign/core and the StyleX compiler. 2. Wrap my app in ThemeProvider. 3. Replace one existing component with an Astryx equivalent. After setup, suggest relevant next steps based on my project.';
34
+
35
+ // Copies the setup prompt in the shape each tool wants to receive it. The
36
+ // menu cannot navigate anywhere from a static template, so the action says
37
+ // what it does.
38
+ const PROMPT_TARGETS = ['v0', 'Claude', 'ChatGPT', 'Cursor'].map(target => ({
39
+ label: `Copy for ${target}`,
40
+ onClick: () => {
41
+ void navigator.clipboard.writeText(SETUP_PROMPT);
42
+ },
43
+ }));
44
+
23
45
  const OUTLINE_ITEMS: OutlineItem[] = [
24
46
  {id: 'prerequisites', label: 'Prerequisites', level: 2},
25
47
  {id: 'install-package', label: 'Install the package', level: 2},
@@ -32,13 +54,6 @@ const OUTLINE_OPTIONS = OUTLINE_ITEMS.map(item => ({
32
54
  label: item.label,
33
55
  }));
34
56
 
35
- const outlinePanel: CSSProperties = {
36
- position: 'sticky',
37
- top: 24,
38
- alignSelf: 'start',
39
- paddingBlockStart: 120,
40
- };
41
-
42
57
  export default function DocumentationTechnical() {
43
58
  const [activeId, setActiveId] = useState<string | undefined>(
44
59
  OUTLINE_ITEMS[0]?.id,
@@ -73,7 +88,7 @@ export default function DocumentationTechnical() {
73
88
  <LayoutContent isScrollable={false} padding={8}>
74
89
  <VStack gap={8}>
75
90
  <VStack gap={2}>
76
- <Text type="display-1">Getting started with Astryx Dracula</Text>
91
+ <Heading level={1}>Getting started with Astryx Dracula</Heading>
77
92
  <Text type="supporting" color="secondary" hasTabularNumbers>
78
93
  Last updated March 30, 2026
79
94
  </Text>
@@ -106,9 +121,7 @@ export default function DocumentationTechnical() {
106
121
  size="sm"
107
122
  icon={<Icon icon={ClipboardCopy} />}
108
123
  onClick={() => {
109
- void navigator.clipboard.writeText(
110
- 'Help me get set up with Astryx Dracula. Based on my project, do the following: 1. Install @astryxdesign/core and the StyleX compiler. 2. Wrap my app in ThemeProvider. 3. Replace one existing component with an Astryx equivalent. After setup, suggest relevant next steps based on my project.',
111
- );
124
+ void navigator.clipboard.writeText(SETUP_PROMPT);
112
125
  }}
113
126
  />
114
127
  <DropdownMenu
@@ -119,12 +132,7 @@ export default function DocumentationTechnical() {
119
132
  isIconOnly: true,
120
133
  icon: <Icon icon={ChevronDown} />,
121
134
  }}
122
- items={[
123
- {label: 'Open in v0', onClick: () => {}},
124
- {label: 'Open in Claude', onClick: () => {}},
125
- {label: 'Open in ChatGPT', onClick: () => {}},
126
- {label: 'Open in Cursor', onClick: () => {}},
127
- ]}
135
+ items={PROMPT_TARGETS}
128
136
  />
129
137
  </HStack>
130
138
  <Text type="body" color="secondary">
@@ -165,17 +173,18 @@ export default function DocumentationTechnical() {
165
173
  gives you access to all components, tokens, and utilities.
166
174
  </Text>
167
175
  <VStack gap={2}>
168
- <Text type="body" weight="bold">
176
+ <Text type="body" weight="semibold">
169
177
  Step 1: Install the core package
170
178
  </Text>
171
179
  <CodeBlock
172
180
  code="npm install @astryxdesign/core"
173
181
  language="bash"
174
182
  width="100%"
183
+ hasCopyButton
175
184
  />
176
185
  </VStack>
177
186
  <VStack gap={2}>
178
- <Text type="body" weight="bold">
187
+ <Text type="body" weight="semibold">
179
188
  Step 2: Import the precompiled styles
180
189
  </Text>
181
190
  <Text type="body" color="secondary">
@@ -188,10 +197,11 @@ export default function DocumentationTechnical() {
188
197
  import '@astryxdesign/core/astryx.css';`}
189
198
  language="tsx"
190
199
  width="100%"
200
+ hasCopyButton
191
201
  />
192
202
  </VStack>
193
203
  <VStack gap={2}>
194
- <Text type="body" weight="bold">
204
+ <Text type="body" weight="semibold">
195
205
  Step 3: Import your first component
196
206
  </Text>
197
207
  <CodeBlock
@@ -202,6 +212,7 @@ export default function App() {
202
212
  }`}
203
213
  language="tsx"
204
214
  width="100%"
215
+ hasCopyButton
205
216
  />
206
217
  </VStack>
207
218
  </VStack>
@@ -229,6 +240,7 @@ export default function App({ children }) {
229
240
  }`}
230
241
  language="tsx"
231
242
  width="100%"
243
+ hasCopyButton
232
244
  />
233
245
  <Text type="body" color="secondary">
234
246
  See the theming guide for the full list of customizable tokens.
@@ -2,5 +2,5 @@ export default {
2
2
  type: 'page',
3
3
  name: 'Component Catalog',
4
4
  description: 'Hero plus categorized component grid',
5
- category: 'marketing',
5
+ category: 'docs',
6
6
  };
@@ -1,4 +1,14 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // Counts: 31 total, Core 18 / Layout 4 / Navigation 4 / Form 5. Derived at
4
+ // render from COMPONENT_CATEGORIES, so these numerals must move together with
5
+ // the array -- the gate in scripts/check.ts reads the rendered count, not this
6
+ // comment.
7
+ // L > LC > V[g=10] > (C[p=10] > (H[g=8 a=center] > (V[g=4] > Hd"The coven grimoire"[level=1 t=display-1] + Tx.lg"Every incantation in the Astryx spellbook"[t=large] + (H > B.primary"Enter the grimoire")))) + (V[g=4] > (H[j=between a=center] > Hd"Core"[level=2] + Tx"18 spells"[t=body]) + (H[j=between a=center] > Hd"Layout"[level=2] + Tx"4 spells"[t=body]) + (H[j=between a=center] > Hd"Navigation"[level=2] + Tx"4 spells"[t=body]) + (H[j=between a=center] > Hd"Form"[level=2] + Tx"5 spells"[t=body]) + (G[c={min:260} g=4] > (CC[p=4] > V[g=3] > C[p=0] + (V[g=1] > Tx"Avatar"[t=body] + Tx"Avatars represent"[t=body]))*4))
8
+
9
+ /**
10
+ * Documentation catalog — every component shelf in the grimoire. Frame-first layout (see `bunx astryx docs layout`): (Frame/responsive/container: see XLE header above.)
11
+ */
2
12
 
3
13
  import type {CSSProperties} from 'react';
4
14
  import {Heading, Text} from '@astryxdesign/core/Text';
@@ -8,25 +18,25 @@ import {ClickableCard} from '@astryxdesign/core/ClickableCard';
8
18
  import {HStack, VStack, StackItem} from '@astryxdesign/core/Stack';
9
19
  import {Layout, LayoutContent} from '@astryxdesign/core/Layout';
10
20
  import {Grid} from '@astryxdesign/core/Grid';
21
+ import {SceneTile} from 'astryx-dracula/shared/scene-tile';
22
+ import {galleryImageClip} from 'astryx-dracula/shared/gallery-image';
23
+ import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
11
24
 
12
- const previewArt: CSSProperties = {
13
- width: '100%',
14
- height: '100%',
15
- display: 'block',
16
- };
17
- const previewClip: CSSProperties = {
18
- borderRadius: 'var(--radius-element)',
19
- overflow: 'clip',
20
- };
21
25
  // Negative margin offsets each card's 8px padding so the grid content stays
22
26
  // visually aligned while giving every card a padded hover/click target.
23
27
  const cardGrid: CSSProperties = {
24
- margin: -8,
28
+ margin: 'calc(var(--spacing-2) * -1)',
25
29
  };
26
30
 
27
- // Dracula accent per grimoire shelf.
28
- const SHELF_HUES: Record<string, string> = {
29
- Core: 'var(--dracula-purple)',
31
+ // Same-route hash: demo links stay focusable anchors without escaping the
32
+ // template through the hash router (bare "#" would drop back to the home page).
33
+ const SELF_HASH = '#/templates/documentation';
34
+
35
+ // Decorative accent per grimoire shelf. None of them is purple: the sigils
36
+ // are not interactive, and purple belongs to the things that are. Art lives
37
+ // in shared/scene-tile (lg fork: centered glyph on a flat field).
38
+ const SHELF_HUES: Record<string, SceneHue> = {
39
+ Core: 'var(--dracula-pink)',
30
40
  Layout: 'var(--dracula-cyan)',
31
41
  Navigation: 'var(--dracula-green)',
32
42
  Form: 'var(--dracula-yellow)',
@@ -214,50 +224,16 @@ const COMPONENT_CATEGORIES = [
214
224
  },
215
225
  ];
216
226
 
217
- // ---------------------------------------------------------------------------
218
- // Preview art
219
- // ---------------------------------------------------------------------------
220
-
221
- function PreviewArt({name, shelf}: {name: string; shelf: string}) {
222
- const hue = SHELF_HUES[shelf] ?? 'var(--dracula-purple)';
223
- return (
224
- <svg
225
- viewBox="0 0 400 300"
226
- preserveAspectRatio="xMidYMid slice"
227
- style={previewArt}
228
- role="img"
229
- aria-label={`${name} sigil`}>
230
- <rect width="400" height="300" fill="var(--dracula-bg-light)" />
231
- <circle cx="316" cy="66" r="26" fill={hue} opacity={0.85} />
232
- <circle
233
- cx="306"
234
- cy="60"
235
- r="21"
236
- fill="var(--dracula-bg-light)"
237
- opacity={0.5}
238
- />
239
- <path
240
- d="M0 208 Q110 168 210 198 T400 184 V300 H0 Z"
241
- fill="var(--dracula-current-line)"
242
- />
243
- <path
244
- d="M0 236 Q130 206 260 230 T400 222 V300 H0 Z"
245
- fill="var(--dracula-bg)"
246
- />
247
- <g
248
- transform="translate(140 150)"
249
- fill="none"
250
- stroke="var(--dracula-comment)"
251
- strokeWidth="5"
252
- strokeLinecap="round"
253
- strokeLinejoin="round">
254
- <rect x="-44" y="-44" width="88" height="88" rx="5" />
255
- <circle cx="18" cy="-18" r="2.5" fill={hue} stroke="none" />
256
- <path d="M-34 30 L-8 0 L10 18 L20 8 L34 24" />
257
- </g>
258
- </svg>
259
- );
260
- }
227
+ // Every count in this template is DERIVED from the array above, never written as
228
+ // a numeral. The hero copy and the per-category "N spells" line both read this
229
+ // and `items.length`, so adding a component can no longer make the page lie
230
+ // about its own size -- which is what happened while the hero copy said
231
+ // "twenty-eight" against 31 entries and the XLE header said 15 for a category
232
+ // of 18.
233
+ const COMPONENT_TOTAL = COMPONENT_CATEGORIES.reduce(
234
+ (sum, category) => sum + category.items.length,
235
+ 0,
236
+ );
261
237
 
262
238
  // ---------------------------------------------------------------------------
263
239
  // Main component
@@ -271,26 +247,26 @@ export default function DocumentationCatalog() {
271
247
  content={
272
248
  <LayoutContent padding={8}>
273
249
  <VStack gap={10}>
274
- <Card variant="gray" padding={10}>
250
+ <Card variant="transparent" padding={10}>
275
251
  <HStack gap={8} vAlign="center">
276
252
  <StackItem size="fill">
277
253
  <VStack gap={4}>
278
- <Text type="display-1">The coven grimoire</Text>
254
+ <Heading level={1} type="display-1">
255
+ The coven grimoire
256
+ </Heading>
279
257
  <Text type="large" weight="normal" color="secondary">
280
- Every incantation in the Astryx spellbook — thirty-one
281
- components for building beautiful, accessible products
282
- after dark.
258
+ Every incantation in the Astryx spellbook, with{' '}
259
+ {COMPONENT_TOTAL} components for building beautiful,
260
+ accessible products after dark.
283
261
  </Text>
284
262
  <HStack>
285
263
  <Button
286
264
  label="Enter the grimoire"
287
265
  variant="primary"
288
- size="lg"
289
266
  />
290
267
  </HStack>
291
268
  </VStack>
292
269
  </StackItem>
293
- <StackItem size="fill" />
294
270
  </HStack>
295
271
  </Card>
296
272
 
@@ -299,34 +275,38 @@ export default function DocumentationCatalog() {
299
275
  <HStack justify="between" vAlign="center">
300
276
  <Heading level={2}>{category.label}</Heading>
301
277
  <Text
302
- type="supporting"
278
+ type="body"
303
279
  color="secondary"
304
280
  hasTabularNumbers>
305
281
  {category.items.length}{' '}
306
282
  {category.items.length === 1 ? 'spell' : 'spells'}
307
283
  </Text>
308
284
  </HStack>
309
- <Grid columns={{minWidth: 260}} gap={2} style={cardGrid}>
285
+ <Grid columns={{minWidth: 260}} gap={4} style={cardGrid}>
310
286
  {category.items.map(item => (
311
287
  <ClickableCard
312
288
  key={item.key}
313
289
  label={`Open ${item.name}`}
314
- onClick={() => {}}
290
+ href={SELF_HASH}
315
291
  variant="transparent"
316
- padding={2}>
292
+ padding={4}>
317
293
  <VStack gap={3}>
318
294
  <Card
319
295
  variant="muted"
320
296
  padding={0}
321
297
  minHeight={160}
322
- style={previewClip}>
323
- <PreviewArt
324
- name={item.name}
325
- shelf={category.label}
298
+ style={galleryImageClip}>
299
+ <SceneTile
300
+ label={`${item.name} sigil`}
301
+ hue={
302
+ SHELF_HUES[category.label] ??
303
+ 'var(--dracula-comment)'
304
+ }
305
+ size="lg"
326
306
  />
327
307
  </Card>
328
- <VStack gap={0.5}>
329
- <Text type="body" weight="bold">
308
+ <VStack gap={1}>
309
+ <Text type="body" weight="semibold">
330
310
  {item.name}
331
311
  </Text>
332
312
  <Text type="body" color="secondary" maxLines={3}>