astryx-dracula 0.5.0 → 0.6.2

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 (129) hide show
  1. package/BRAND.md +3 -3
  2. package/README.md +7 -4
  3. package/USAGE.md +49 -49
  4. package/astryx-dracula.d.ts +2 -2
  5. package/astryx-dracula.js +25 -9
  6. package/astryx-dracula.variants.d.ts +2 -2
  7. package/astryx.integration.mjs +1 -1
  8. package/icons.mjs +21 -17
  9. package/icons.tsx +69 -62
  10. package/package.json +125 -120
  11. package/shared/auth-chrome-config.ts +6 -6
  12. package/shared/auth-chrome.tsx +143 -144
  13. package/shared/auth-copy.ts +14 -14
  14. package/shared/chaptered-doc-config.ts +48 -51
  15. package/shared/chaptered-doc.tsx +267 -289
  16. package/shared/chart-hues.ts +25 -10
  17. package/shared/chart-labels.tsx +31 -30
  18. package/shared/chart-legend.tsx +55 -56
  19. package/shared/chart-panel-style.ts +3 -3
  20. package/shared/data-bar.tsx +97 -102
  21. package/shared/format-money.ts +1 -1
  22. package/shared/gallery-image.ts +6 -6
  23. package/shared/login-demo.ts +1 -1
  24. package/shared/metric-card.tsx +101 -105
  25. package/shared/metric-delta.tsx +28 -27
  26. package/shared/order-desk.tsx +205 -216
  27. package/shared/revenue-chart.tsx +145 -165
  28. package/shared/scene-castle.tsx +206 -291
  29. package/shared/scene-frame.tsx +33 -32
  30. package/shared/scene-hues.ts +17 -17
  31. package/shared/scene-tile.tsx +88 -92
  32. package/shared/settings-data.ts +119 -125
  33. package/shared/settings-rows.tsx +122 -159
  34. package/shared/sparkline.tsx +75 -88
  35. package/shared/sso-icons.tsx +10 -22
  36. package/shared/stars.tsx +16 -25
  37. package/templates/ai-chat-landing.template.mjs +5 -4
  38. package/templates/ai-chat-landing.tsx +473 -460
  39. package/templates/ai-chat.template.mjs +5 -4
  40. package/templates/ai-chat.tsx +543 -612
  41. package/templates/blank.template.mjs +5 -4
  42. package/templates/blank.tsx +18 -14
  43. package/templates/centered-hero.template.mjs +5 -4
  44. package/templates/centered-hero.tsx +55 -68
  45. package/templates/classic-gallery.template.mjs +5 -4
  46. package/templates/classic-gallery.tsx +97 -104
  47. package/templates/contact-form.template.mjs +5 -4
  48. package/templates/contact-form.tsx +247 -282
  49. package/templates/dashboard-portfolio.template.mjs +5 -4
  50. package/templates/dashboard-portfolio.tsx +717 -548
  51. package/templates/dashboard.template.mjs +5 -4
  52. package/templates/dashboard.tsx +596 -567
  53. package/templates/detail-page.template.mjs +5 -4
  54. package/templates/detail-page.tsx +546 -603
  55. package/templates/documentation-design.template.mjs +5 -4
  56. package/templates/documentation-design.tsx +231 -243
  57. package/templates/documentation-technical.template.mjs +5 -4
  58. package/templates/documentation-technical.tsx +236 -239
  59. package/templates/documentation.template.mjs +5 -4
  60. package/templates/documentation.tsx +267 -279
  61. package/templates/editor.template.mjs +5 -4
  62. package/templates/editor.tsx +844 -877
  63. package/templates/file-explorer.template.mjs +5 -4
  64. package/templates/file-explorer.tsx +566 -590
  65. package/templates/form-two-column.template.mjs +5 -4
  66. package/templates/form-two-column.tsx +195 -210
  67. package/templates/gallery-hero.template.mjs +5 -4
  68. package/templates/gallery-hero.tsx +93 -114
  69. package/templates/ide.template.mjs +5 -4
  70. package/templates/ide.tsx +407 -453
  71. package/templates/incident-console.template.mjs +5 -4
  72. package/templates/incident-console.tsx +593 -575
  73. package/templates/kanban-board.template.mjs +5 -4
  74. package/templates/kanban-board.tsx +665 -683
  75. package/templates/library.template.mjs +5 -4
  76. package/templates/library.tsx +409 -430
  77. package/templates/login-card.template.mjs +5 -4
  78. package/templates/login-card.tsx +63 -72
  79. package/templates/login-split.template.mjs +5 -4
  80. package/templates/login-split.tsx +192 -193
  81. package/templates/login-sso.template.mjs +5 -4
  82. package/templates/login-sso.tsx +257 -258
  83. package/templates/login.template.mjs +5 -4
  84. package/templates/login.tsx +53 -49
  85. package/templates/messaging-shell.template.mjs +5 -4
  86. package/templates/messaging-shell.tsx +556 -574
  87. package/templates/mixed-gallery.template.mjs +5 -4
  88. package/templates/mixed-gallery.tsx +60 -58
  89. package/templates/payment-form.template.mjs +5 -4
  90. package/templates/payment-form.tsx +882 -991
  91. package/templates/product-detail.template.mjs +5 -4
  92. package/templates/product-detail.tsx +261 -272
  93. package/templates/product-gallery.template.mjs +5 -4
  94. package/templates/product-gallery.tsx +126 -137
  95. package/templates/product-tour.template.mjs +5 -4
  96. package/templates/product-tour.tsx +741 -773
  97. package/templates/settings-dialog.template.mjs +5 -4
  98. package/templates/settings-dialog.tsx +688 -717
  99. package/templates/settings-sidebar.template.mjs +5 -4
  100. package/templates/settings-sidebar.tsx +762 -807
  101. package/templates/settings.template.mjs +5 -4
  102. package/templates/settings.tsx +289 -304
  103. package/templates/shell-nav.template.mjs +5 -4
  104. package/templates/shell-nav.tsx +212 -219
  105. package/templates/shell-side-nav.template.mjs +5 -4
  106. package/templates/shell-side-nav.tsx +135 -157
  107. package/templates/shell-top-nav.template.mjs +5 -4
  108. package/templates/shell-top-nav.tsx +182 -203
  109. package/templates/side-gallery.template.mjs +5 -4
  110. package/templates/side-gallery.tsx +85 -90
  111. package/templates/table-grouped.template.mjs +5 -4
  112. package/templates/table-grouped.tsx +1000 -1053
  113. package/templates/table-page-chart.template.mjs +5 -4
  114. package/templates/table-page-chart.tsx +400 -398
  115. package/templates/table-page-heatmap-status.template.mjs +5 -4
  116. package/templates/table-page-heatmap-status.tsx +483 -512
  117. package/templates/table-page-shoe-store-heatmap.template.mjs +5 -4
  118. package/templates/table-page-shoe-store-heatmap.tsx +748 -746
  119. package/templates/table-page.template.mjs +5 -4
  120. package/templates/table-page.tsx +396 -405
  121. package/templates/table.template.mjs +5 -4
  122. package/templates/table.tsx +92 -93
  123. package/templates/tech-report.template.mjs +5 -4
  124. package/templates/tech-report.tsx +1316 -1304
  125. package/templates/theme-showcase.template.mjs +5 -4
  126. package/templates/theme-showcase.tsx +871 -951
  127. package/theme.css +35 -77
  128. package/theme.css.d.ts +9 -0
  129. package/tokens.css +373 -380
@@ -5,327 +5,305 @@
5
5
  // Scene systems stay local too: product-tour keeps BAT_WING/BAT_BODY/FIR +
6
6
  // NightScene/PaletteScene/TypeScene/PagesScene, tech-report keeps its own.
7
7
 
8
+ import { AppShell } from "@astryxdesign/core/AppShell";
9
+ import { AspectRatio } from "@astryxdesign/core/AspectRatio";
10
+ import { Button } from "@astryxdesign/core/Button";
11
+ import { CodeBlock } from "@astryxdesign/core/CodeBlock";
12
+ import { Divider } from "@astryxdesign/core/Divider";
13
+ import { useMediaQuery } from "@astryxdesign/core/hooks";
14
+ import { Icon } from "@astryxdesign/core/Icon";
8
15
  import {
9
- buildOutlineByChapter,
10
- outlinePanel,
11
- sceneClip,
12
- scrollToSection,
13
- sectionId,
14
- type ChapterGroup,
15
- type DocChapter,
16
- type DocSection,
17
- } from 'astryx-dracula/shared/chaptered-doc-config';
18
- import {useState, type CSSProperties, type ReactNode} from 'react';
19
-
20
- import {AppShell} from '@astryxdesign/core/AppShell';
21
- import {MobileNavToggle} from '@astryxdesign/core/MobileNav';
22
- import {
23
- SideNav,
24
- SideNavHeading,
25
- SideNavItem,
26
- SideNavSection,
27
- } from '@astryxdesign/core/SideNav';
28
- import {NavIcon} from '@astryxdesign/core/NavIcon';
16
+ HStack,
17
+ Layout,
18
+ LayoutContent,
19
+ LayoutPanel,
20
+ StackItem,
21
+ VStack,
22
+ } from "@astryxdesign/core/Layout";
23
+ import { List, ListItem } from "@astryxdesign/core/List";
24
+ import { MobileNavToggle } from "@astryxdesign/core/MobileNav";
25
+ import { NavIcon } from "@astryxdesign/core/NavIcon";
26
+ import { Outline } from "@astryxdesign/core/Outline";
27
+ import { Selector } from "@astryxdesign/core/Selector";
28
+ import { SideNav, SideNavHeading, SideNavItem, SideNavSection } from "@astryxdesign/core/SideNav";
29
+ import { Heading, Text } from "@astryxdesign/core/Text";
29
30
  import {
30
- HStack,
31
- Layout,
32
- LayoutContent,
33
- LayoutPanel,
34
- StackItem,
35
- VStack,
36
- } from '@astryxdesign/core/Layout';
37
- import {Heading, Text} from '@astryxdesign/core/Text';
38
- import {Button} from '@astryxdesign/core/Button';
39
- import {Icon} from '@astryxdesign/core/Icon';
40
- import type {IconType} from '@astryxdesign/core/Icon';
41
- import {AspectRatio} from '@astryxdesign/core/AspectRatio';
42
- import {CodeBlock} from '@astryxdesign/core/CodeBlock';
43
- import {Divider} from '@astryxdesign/core/Divider';
44
- import {List, ListItem} from '@astryxdesign/core/List';
45
- import {Outline} from '@astryxdesign/core/Outline';
46
- import {Selector} from '@astryxdesign/core/Selector';
47
- import {useMediaQuery} from '@astryxdesign/core/hooks';
48
-
49
- import {ChevronLeft, ChevronRight} from 'lucide-react';
31
+ buildOutlineByChapter,
32
+ type ChapterGroup,
33
+ type DocChapter,
34
+ type DocSection,
35
+ outlinePanel,
36
+ sceneClip,
37
+ scrollToSection,
38
+ sectionId,
39
+ } from "astryx-dracula/shared/chaptered-doc-config";
40
+ import { ChevronLeft, ChevronRight } from "lucide-react";
41
+ import { type ReactNode, useState } from "react";
50
42
 
51
43
  function ChapterRail({
52
- groups,
53
- activeId,
54
- onSelect,
55
- heading,
56
- subheading,
57
- headingHref,
58
- headingIcon,
44
+ groups,
45
+ activeId,
46
+ onSelect,
47
+ heading,
48
+ subheading,
49
+ headingHref,
50
+ headingIcon,
59
51
  }: {
60
- groups: ChapterGroup[];
61
- activeId: string;
62
- onSelect: (id: string) => void;
63
- heading: string;
64
- subheading: string;
65
- headingHref: string;
66
- headingIcon: ReactNode;
52
+ groups: ChapterGroup[];
53
+ activeId: string;
54
+ onSelect: (id: string) => void;
55
+ heading: string;
56
+ subheading: string;
57
+ headingHref: string;
58
+ headingIcon: ReactNode;
67
59
  }) {
68
- // Resizable like the shell-side-nav template: 264 default, 220-400 range.
69
- // The AppShell drawer owns the rail below 1024px (see MobileNavToggle),
70
- // so the handle only matters at desktop widths.
71
- return (
72
- <SideNav
73
- collapsible
74
- resizable={{defaultWidth: 264, minWidth: 220, maxWidth: 400}}
75
- header={
76
- <SideNavHeading
77
- icon={<NavIcon icon={headingIcon} />}
78
- heading={heading}
79
- subheading={subheading}
80
- headingHref={headingHref}
81
- />
82
- }>
83
- {groups.map(group => (
84
- <SideNavSection key={group.title} title={group.title}>
85
- {group.chapters.map(chapter => (
86
- <SideNavItem
87
- key={chapter.id}
88
- label={chapter.title}
89
- icon={chapter.icon}
90
- isSelected={chapter.id === activeId}
91
- onClick={() => onSelect(chapter.id)}
92
- />
93
- ))}
94
- </SideNavSection>
95
- ))}
96
- </SideNav>
97
- );
60
+ // Resizable like the shell-side-nav template: 264 default, 220-400 range.
61
+ // The AppShell drawer owns the rail below 1024px (see MobileNavToggle),
62
+ // so the handle only matters at desktop widths.
63
+ return (
64
+ <SideNav
65
+ collapsible
66
+ resizable={{ defaultWidth: 264, minWidth: 220, maxWidth: 400 }}
67
+ header={
68
+ <SideNavHeading
69
+ icon={<NavIcon icon={headingIcon} />}
70
+ heading={heading}
71
+ subheading={subheading}
72
+ headingHref={headingHref}
73
+ />
74
+ }
75
+ >
76
+ {groups.map((group) => (
77
+ <SideNavSection key={group.title} title={group.title}>
78
+ {group.chapters.map((chapter) => (
79
+ <SideNavItem
80
+ key={chapter.id}
81
+ label={chapter.title}
82
+ icon={chapter.icon}
83
+ isSelected={chapter.id === activeId}
84
+ onClick={() => onSelect(chapter.id)}
85
+ />
86
+ ))}
87
+ </SideNavSection>
88
+ ))}
89
+ </SideNav>
90
+ );
98
91
  }
99
92
 
100
93
  // ─── Chapter body ────────────────────────────────────────────────────────────
101
94
 
102
- function SectionBlock({
103
- chapterId,
104
- section,
105
- }: {
106
- chapterId: string;
107
- section: DocSection;
108
- }) {
109
- return (
110
- <VStack gap={3}>
111
- <Heading level={2} id={sectionId(chapterId, section.key)}>
112
- {section.heading}
113
- </Heading>
114
- <Text type="body" color="secondary" display="block">
115
- {section.body}
116
- </Text>
117
- {section.bullets != null && (
118
- <List listStyle="disc">
119
- {section.bullets.map(bullet => (
120
- <ListItem key={bullet} label={bullet} />
121
- ))}
122
- </List>
123
- )}
124
- {section.code != null && (
125
- <CodeBlock
126
- code={section.code.source}
127
- language={section.code.language}
128
- title={section.code.title}
129
- width="100%"
130
- />
131
- )}
132
- </VStack>
133
- );
95
+ function SectionBlock({ chapterId, section }: { chapterId: string; section: DocSection }) {
96
+ return (
97
+ <VStack gap={3}>
98
+ <Heading level={2} id={sectionId(chapterId, section.key)}>
99
+ {section.heading}
100
+ </Heading>
101
+ <Text type="body" color="secondary" display="block">
102
+ {section.body}
103
+ </Text>
104
+ {section.bullets != null && (
105
+ <List listStyle="disc">
106
+ {section.bullets.map((bullet) => (
107
+ <ListItem key={bullet} label={bullet} />
108
+ ))}
109
+ </List>
110
+ )}
111
+ {section.code != null && (
112
+ <CodeBlock
113
+ code={section.code.source}
114
+ language={section.code.language}
115
+ title={section.code.title}
116
+ width="100%"
117
+ />
118
+ )}
119
+ </VStack>
120
+ );
134
121
  }
135
122
 
136
123
  function ChapterNav({
137
- chapters,
138
- chapter,
139
- onSelect,
124
+ chapters,
125
+ chapter,
126
+ onSelect,
140
127
  }: {
141
- chapters: DocChapter[];
142
- chapter: DocChapter;
143
- onSelect: (id: string) => void;
128
+ chapters: DocChapter[];
129
+ chapter: DocChapter;
130
+ onSelect: (id: string) => void;
144
131
  }) {
145
- const index = chapters.findIndex(entry => entry.id === chapter.id);
146
- const previous = index > 0 ? chapters[index - 1] : undefined;
147
- const next = index < chapters.length - 1 ? chapters[index + 1] : undefined;
148
- return (
149
- <HStack gap={3} hAlign="between" vAlign="center">
150
- {previous != null ? (
151
- <Button
152
- label={previous.title}
153
- variant="secondary"
154
- icon={<Icon icon={ChevronLeft} size="sm" />}
155
- onClick={() => onSelect(previous.id)}
156
- />
157
- ) : (
158
- <StackItem size="fill" />
159
- )}
160
- {next != null ? (
161
- <Button
162
- label={next.title}
163
- variant="secondary"
164
- endContent={<Icon icon={ChevronRight} size="sm" />}
165
- onClick={() => onSelect(next.id)}
166
- />
167
- ) : (
168
- <StackItem size="fill" />
169
- )}
170
- </HStack>
171
- );
132
+ const index = chapters.findIndex((entry) => entry.id === chapter.id);
133
+ const previous = index > 0 ? chapters[index - 1] : undefined;
134
+ const next = index < chapters.length - 1 ? chapters[index + 1] : undefined;
135
+ return (
136
+ <HStack gap={3} hAlign="between" vAlign="center">
137
+ {previous != null ? (
138
+ <Button
139
+ label={previous.title}
140
+ variant="secondary"
141
+ icon={<Icon icon={ChevronLeft} size="sm" />}
142
+ onClick={() => onSelect(previous.id)}
143
+ />
144
+ ) : (
145
+ <StackItem size="fill" />
146
+ )}
147
+ {next != null ? (
148
+ <Button
149
+ label={next.title}
150
+ variant="secondary"
151
+ endContent={<Icon icon={ChevronRight} size="sm" />}
152
+ onClick={() => onSelect(next.id)}
153
+ />
154
+ ) : (
155
+ <StackItem size="fill" />
156
+ )}
157
+ </HStack>
158
+ );
172
159
  }
173
160
 
174
161
  // ─── Page ────────────────────────────────────────────────────────────────────
175
162
 
176
- export function ChapterArtFrame({children}: {children: ReactNode}) {
177
- return (
178
- <AspectRatio ratio={16 / 9} style={sceneClip}>
179
- {children}
180
- </AspectRatio>
181
- );
163
+ export function ChapterArtFrame({ children }: { children: ReactNode }) {
164
+ return (
165
+ <AspectRatio ratio={16 / 9} style={sceneClip}>
166
+ {children}
167
+ </AspectRatio>
168
+ );
182
169
  }
183
170
 
184
171
  export default function ChapteredDoc({
185
- groups,
186
- defaultChapter,
187
- railHeading,
188
- railSubheading,
189
- railHref,
190
- railIcon,
191
- eyebrow,
192
- badge,
193
- art,
194
- footer,
172
+ groups,
173
+ defaultChapter,
174
+ railHeading,
175
+ railSubheading,
176
+ railHref,
177
+ railIcon,
178
+ eyebrow,
179
+ badge,
180
+ art,
181
+ footer,
195
182
  }: {
196
- groups: ChapterGroup[];
197
- defaultChapter: string;
198
- railHeading: string;
199
- railSubheading: string;
200
- railHref: string;
201
- railIcon: ReactNode;
202
- eyebrow: string;
203
- badge: (chapter: DocChapter, index: number, total: number) => ReactNode;
204
- art: (chapterId: string) => ReactNode;
205
- footer?: ReactNode;
183
+ groups: ChapterGroup[];
184
+ defaultChapter: string;
185
+ railHeading: string;
186
+ railSubheading: string;
187
+ railHref: string;
188
+ railIcon: ReactNode;
189
+ eyebrow: string;
190
+ badge: (chapter: DocChapter, index: number, total: number) => ReactNode;
191
+ art: (chapterId: string) => ReactNode;
192
+ footer?: ReactNode;
206
193
  }) {
207
- const chapters = groups.flatMap(group => group.chapters);
208
- const [outlineByChapter] = useState(() => buildOutlineByChapter(chapters));
209
- const [chapterId, setChapterId] = useState(defaultChapter);
210
- const [activeSection, setActiveSection] = useState(
211
- outlineByChapter[defaultChapter]?.[0]?.id ?? '',
212
- );
194
+ const chapters = groups.flatMap((group) => group.chapters);
195
+ const [outlineByChapter] = useState(() => buildOutlineByChapter(chapters));
196
+ const [chapterId, setChapterId] = useState(defaultChapter);
197
+ const [activeSection, setActiveSection] = useState(
198
+ outlineByChapter[defaultChapter]?.[0]?.id ?? "",
199
+ );
213
200
 
214
- // Responsive contract: below 1024px the outline stops being a column, since
215
- // a narrow viewport has nothing to outline against.
216
- const isNarrow = useMediaQuery('(max-width: 1024px)');
201
+ // Responsive contract: below 1024px the outline stops being a column, since
202
+ // a narrow viewport has nothing to outline against.
203
+ const isNarrow = useMediaQuery("(max-width: 1024px)");
217
204
 
218
- const chapter = chapters.find(entry => entry.id === chapterId) ?? chapters[0];
219
- const outlineItems = outlineByChapter[chapter.id] ?? [];
205
+ const chapter = chapters.find((entry) => entry.id === chapterId) ?? chapters[0];
206
+ const outlineItems = outlineByChapter[chapter.id] ?? [];
220
207
 
221
- const openChapter = (id: string) => {
222
- setChapterId(id);
223
- setActiveSection(outlineByChapter[id]?.[0]?.id ?? '');
224
- // A chapter reads as a new page, so the document goes back to the top
225
- // instead of keeping the previous chapter's scroll offset.
226
- window.scrollTo({top: 0});
227
- };
208
+ const openChapter = (id: string) => {
209
+ setChapterId(id);
210
+ setActiveSection(outlineByChapter[id]?.[0]?.id ?? "");
211
+ // A chapter reads as a new page, so the document goes back to the top
212
+ // instead of keeping the previous chapter's scroll offset.
213
+ window.scrollTo({ top: 0 });
214
+ };
228
215
 
229
- return (
230
- <AppShell
231
- height="auto"
232
- contentPadding={0}
233
- mobileNav={{hasToggle: false}}
234
- sideNav={
235
- <ChapterRail
236
- groups={groups}
237
- activeId={chapter.id}
238
- onSelect={openChapter}
239
- heading={railHeading}
240
- subheading={railSubheading}
241
- headingHref={railHref}
242
- headingIcon={railIcon}
243
- />
244
- }>
245
- <Layout
246
- height="auto"
247
- contentWidth={960}
248
- end={
249
- isNarrow ? undefined : (
250
- <LayoutPanel
251
- isScrollable={false}
252
- label="On this page"
253
- role="complementary"
254
- style={outlinePanel}>
255
- <Outline
256
- items={outlineItems}
257
- onActiveIdChange={setActiveSection}
258
- />
259
- </LayoutPanel>
260
- )
261
- }
262
- content={
263
- <LayoutContent isScrollable={false} padding={8}>
264
- <VStack gap={8}>
265
- <VStack gap={2}>
266
- <HStack gap={2} vAlign="center">
267
- <MobileNavToggle />
268
- <Text type="supporting" color="secondary">
269
- {eyebrow}
270
- </Text>
271
- </HStack>
272
- <Heading level={1} type="display-2">
273
- {chapter.title}
274
- </Heading>
275
- <Text type="supporting" color="secondary" hasTabularNumbers>
276
- {badge(
277
- chapter,
278
- chapters.findIndex(entry => entry.id === chapter.id),
279
- chapters.length,
280
- )}
281
- </Text>
282
- {isNarrow && (
283
- <Selector
284
- label="On this page"
285
- isLabelHidden
286
- options={outlineItems.map(item => ({
287
- value: item.id,
288
- label: item.label,
289
- }))}
290
- value={activeSection}
291
- onChange={(id: string) => {
292
- setActiveSection(id);
293
- scrollToSection(id);
294
- }}
295
- width="100%"
296
- />
297
- )}
298
- </VStack>
216
+ return (
217
+ <AppShell
218
+ height="auto"
219
+ contentPadding={0}
220
+ mobileNav={{ hasToggle: false }}
221
+ sideNav={
222
+ <ChapterRail
223
+ groups={groups}
224
+ activeId={chapter.id}
225
+ onSelect={openChapter}
226
+ heading={railHeading}
227
+ subheading={railSubheading}
228
+ headingHref={railHref}
229
+ headingIcon={railIcon}
230
+ />
231
+ }
232
+ >
233
+ <Layout
234
+ height="auto"
235
+ contentWidth={960}
236
+ end={
237
+ isNarrow ? undefined : (
238
+ <LayoutPanel
239
+ isScrollable={false}
240
+ label="On this page"
241
+ role="complementary"
242
+ style={outlinePanel}
243
+ >
244
+ <Outline items={outlineItems} onActiveIdChange={setActiveSection} />
245
+ </LayoutPanel>
246
+ )
247
+ }
248
+ content={
249
+ <LayoutContent isScrollable={false} padding={8}>
250
+ <VStack gap={8}>
251
+ <VStack gap={2}>
252
+ <HStack gap={2} vAlign="center">
253
+ <MobileNavToggle />
254
+ <Text type="supporting" color="secondary">
255
+ {eyebrow}
256
+ </Text>
257
+ </HStack>
258
+ <Heading level={1} type="display-2">
259
+ {chapter.title}
260
+ </Heading>
261
+ <Text type="supporting" color="secondary" hasTabularNumbers>
262
+ {badge(
263
+ chapter,
264
+ chapters.findIndex((entry) => entry.id === chapter.id),
265
+ chapters.length,
266
+ )}
267
+ </Text>
268
+ {isNarrow && (
269
+ <Selector
270
+ label="On this page"
271
+ isLabelHidden
272
+ options={outlineItems.map((item) => ({
273
+ value: item.id,
274
+ label: item.label,
275
+ }))}
276
+ value={activeSection}
277
+ onChange={(id: string) => {
278
+ setActiveSection(id);
279
+ scrollToSection(id);
280
+ }}
281
+ width="100%"
282
+ />
283
+ )}
284
+ </VStack>
299
285
 
300
- <Text type="large" color="secondary" display="block">
301
- {chapter.intro}
302
- </Text>
286
+ <Text type="large" color="secondary" display="block">
287
+ {chapter.intro}
288
+ </Text>
303
289
 
304
- {art(chapter.id)}
290
+ {art(chapter.id)}
305
291
 
306
- <VStack gap={8}>
307
- {chapter.sections.map(section => (
308
- <SectionBlock
309
- key={section.key}
310
- chapterId={chapter.id}
311
- section={section}
312
- />
313
- ))}
314
- </VStack>
292
+ <VStack gap={8}>
293
+ {chapter.sections.map((section) => (
294
+ <SectionBlock key={section.key} chapterId={chapter.id} section={section} />
295
+ ))}
296
+ </VStack>
315
297
 
316
- <Divider />
298
+ <Divider />
317
299
 
318
- <ChapterNav
319
- chapters={chapters}
320
- chapter={chapter}
321
- onSelect={openChapter}
322
- />
300
+ <ChapterNav chapters={chapters} chapter={chapter} onSelect={openChapter} />
323
301
 
324
- {footer}
325
- </VStack>
326
- </LayoutContent>
327
- }
328
- />
329
- </AppShell>
330
- );
302
+ {footer}
303
+ </VStack>
304
+ </LayoutContent>
305
+ }
306
+ />
307
+ </AppShell>
308
+ );
331
309
  }
@@ -5,18 +5,33 @@
5
5
  // Purple is deliberately absent: purple means tappable, so it must never encode
6
6
  // data. `muted` is a COMMENT-blue grey because it backs the `{label: 'Other'}`
7
7
  // legend row (dashboard.tsx:229, :253) -- a labelled data category, not chrome.
8
+ //
9
+ // Every var() carries an explicit fallback equal to the theme's own pin for
10
+ // that token. Core moved the canonical `--color-data-*` defaults out of the
11
+ // generated theme CSS and into its own dataVars group, so a bare
12
+ // `var(--color-data-*)` is no longer guaranteed to resolve: the theme's pin is
13
+ // the only thing painting it. The fallback is the same value, so a theme that
14
+ // drops the token still paints the right colour instead of nothing. The
15
+ // fallback-vs-pin sync is enforced by scripts/check.ts, so the two cannot drift.
8
16
 
9
17
  export const CHART_HUES = {
10
- cyan: 'var(--color-data-categorical-cyan)',
11
- orange: 'var(--color-data-categorical-orange)',
12
- green: 'var(--color-data-categorical-green)',
13
- pink: 'var(--color-data-categorical-pink)',
14
- muted: 'var(--color-data-categorical-blue)',
15
- // Added because --color-data-categorical-red exists in the palette
16
- // (tokens.css:276) and heat maps need a severity colour, but no module
17
- // exposed it. Without this, a chart could not reach red through the
18
- // sanctioned source at all.
19
- red: 'var(--color-data-categorical-red)',
18
+ cyan: "var(--color-data-categorical-cyan, #8BE9FD)",
19
+ orange: "var(--color-data-categorical-orange, #FFB86C)",
20
+ green: "var(--color-data-categorical-green, #50FA7B)",
21
+ pink: "var(--color-data-categorical-pink, #FF79C6)",
22
+ muted: "var(--color-data-categorical-blue, #6272A4)",
23
+ // Added because --color-data-categorical-red exists in the palette
24
+ // (tokens.css:276) and heat maps need a severity colour, but no module
25
+ // exposed it. Without this, a chart could not reach red through the
26
+ // sanctioned source at all.
27
+ red: "var(--color-data-categorical-red, #FF5555)",
20
28
  } as const;
21
29
 
22
30
  export type ChartHue = (typeof CHART_HUES)[keyof typeof CHART_HUES];
31
+
32
+ /**
33
+ * Any `--color-data-*` colour: a categorical hue, or a step of a sequential
34
+ * ramp. The fallback inside the var() is part of the string, which is why the
35
+ * template-literal type below still admits it.
36
+ */
37
+ export type DataColor = `var(--color-data-${string})`;