astryx-dracula 0.4.0 → 0.5.1

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 (121) hide show
  1. package/astryx.integration.mjs +1 -1
  2. package/icons.mjs +17 -17
  3. package/icons.tsx +61 -62
  4. package/package.json +120 -116
  5. package/shared/auth-chrome-config.ts +6 -6
  6. package/shared/auth-chrome.tsx +168 -19
  7. package/shared/auth-copy.ts +14 -14
  8. package/shared/chaptered-doc-config.ts +48 -51
  9. package/shared/chaptered-doc.tsx +270 -292
  10. package/shared/chart-hues.ts +10 -15
  11. package/shared/chart-labels.tsx +31 -30
  12. package/shared/chart-legend.tsx +55 -57
  13. package/shared/chart-panel-style.ts +3 -3
  14. package/shared/data-bar.tsx +97 -102
  15. package/shared/format-money.ts +3 -0
  16. package/shared/gallery-image.ts +6 -6
  17. package/shared/login-demo.ts +1 -1
  18. package/shared/metric-card.tsx +123 -0
  19. package/shared/metric-delta.tsx +28 -27
  20. package/shared/order-desk.tsx +205 -216
  21. package/shared/revenue-chart.tsx +145 -165
  22. package/shared/scene-castle.tsx +206 -291
  23. package/shared/scene-frame.tsx +33 -32
  24. package/shared/scene-hues.ts +17 -25
  25. package/shared/scene-tile.tsx +88 -100
  26. package/shared/settings-data.ts +132 -104
  27. package/shared/settings-rows.tsx +143 -163
  28. package/shared/sparkline.tsx +75 -88
  29. package/shared/sso-icons.tsx +10 -22
  30. package/shared/stars.tsx +19 -0
  31. package/templates/ai-chat-landing.template.mjs +4 -4
  32. package/templates/ai-chat-landing.tsx +468 -447
  33. package/templates/ai-chat.template.mjs +4 -4
  34. package/templates/ai-chat.tsx +561 -645
  35. package/templates/blank.template.mjs +4 -4
  36. package/templates/blank.tsx +14 -12
  37. package/templates/centered-hero.template.mjs +4 -4
  38. package/templates/centered-hero.tsx +52 -67
  39. package/templates/classic-gallery.template.mjs +4 -4
  40. package/templates/classic-gallery.tsx +92 -101
  41. package/templates/contact-form.template.mjs +4 -4
  42. package/templates/contact-form.tsx +243 -280
  43. package/templates/dashboard-portfolio.template.mjs +4 -4
  44. package/templates/dashboard-portfolio.tsx +723 -634
  45. package/templates/dashboard.template.mjs +4 -4
  46. package/templates/dashboard.tsx +592 -682
  47. package/templates/detail-page.template.mjs +4 -4
  48. package/templates/detail-page.tsx +537 -598
  49. package/templates/documentation-design.template.mjs +4 -4
  50. package/templates/documentation-design.tsx +242 -709
  51. package/templates/documentation-technical.template.mjs +4 -4
  52. package/templates/documentation-technical.tsx +233 -258
  53. package/templates/documentation.template.mjs +4 -4
  54. package/templates/documentation.tsx +263 -277
  55. package/templates/editor.template.mjs +4 -4
  56. package/templates/editor.tsx +847 -899
  57. package/templates/file-explorer.template.mjs +4 -4
  58. package/templates/file-explorer.tsx +562 -602
  59. package/templates/form-two-column.template.mjs +4 -4
  60. package/templates/form-two-column.tsx +187 -208
  61. package/templates/gallery-hero.template.mjs +4 -4
  62. package/templates/gallery-hero.tsx +89 -112
  63. package/templates/ide.template.mjs +4 -4
  64. package/templates/ide.tsx +405 -462
  65. package/templates/incident-console.template.mjs +4 -4
  66. package/templates/incident-console.tsx +559 -581
  67. package/templates/kanban-board.template.mjs +4 -4
  68. package/templates/kanban-board.tsx +660 -729
  69. package/templates/library.template.mjs +4 -4
  70. package/templates/library.tsx +405 -452
  71. package/templates/login-card.template.mjs +4 -4
  72. package/templates/login-card.tsx +59 -163
  73. package/templates/login-split.template.mjs +4 -4
  74. package/templates/login-split.tsx +196 -232
  75. package/templates/login-sso.template.mjs +4 -4
  76. package/templates/login-sso.tsx +253 -277
  77. package/templates/login.template.mjs +4 -4
  78. package/templates/login.tsx +51 -149
  79. package/templates/messaging-shell.template.mjs +4 -4
  80. package/templates/messaging-shell.tsx +596 -624
  81. package/templates/mixed-gallery.template.mjs +4 -4
  82. package/templates/mixed-gallery.tsx +56 -56
  83. package/templates/payment-form.template.mjs +4 -4
  84. package/templates/payment-form.tsx +877 -990
  85. package/templates/product-detail.template.mjs +4 -4
  86. package/templates/product-detail.tsx +251 -302
  87. package/templates/product-gallery.template.mjs +4 -4
  88. package/templates/product-gallery.tsx +122 -135
  89. package/templates/product-tour.template.mjs +4 -4
  90. package/templates/product-tour.tsx +737 -794
  91. package/templates/settings-dialog.template.mjs +4 -4
  92. package/templates/settings-dialog.tsx +694 -747
  93. package/templates/settings-sidebar.template.mjs +4 -4
  94. package/templates/settings-sidebar.tsx +768 -848
  95. package/templates/settings.template.mjs +4 -4
  96. package/templates/settings.tsx +306 -230
  97. package/templates/shell-nav.template.mjs +4 -4
  98. package/templates/shell-nav.tsx +217 -277
  99. package/templates/shell-side-nav.template.mjs +4 -4
  100. package/templates/shell-side-nav.tsx +130 -231
  101. package/templates/shell-top-nav.template.mjs +4 -4
  102. package/templates/shell-top-nav.tsx +192 -248
  103. package/templates/side-gallery.template.mjs +4 -4
  104. package/templates/side-gallery.tsx +87 -94
  105. package/templates/table-grouped.template.mjs +4 -4
  106. package/templates/table-grouped.tsx +999 -1201
  107. package/templates/table-page-chart.template.mjs +4 -4
  108. package/templates/table-page-chart.tsx +398 -397
  109. package/templates/table-page-heatmap-status.template.mjs +4 -4
  110. package/templates/table-page-heatmap-status.tsx +479 -527
  111. package/templates/table-page-shoe-store-heatmap.template.mjs +4 -4
  112. package/templates/table-page-shoe-store-heatmap.tsx +746 -746
  113. package/templates/table-page.template.mjs +4 -4
  114. package/templates/table-page.tsx +393 -431
  115. package/templates/table.template.mjs +4 -4
  116. package/templates/table.tsx +93 -99
  117. package/templates/tech-report.template.mjs +4 -4
  118. package/templates/tech-report.tsx +1320 -1324
  119. package/templates/theme-showcase.template.mjs +4 -4
  120. package/templates/theme-showcase.tsx +869 -1246
  121. package/tokens.css +340 -343
@@ -5,328 +5,306 @@
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 type { IconType } from "@astryxdesign/core/Icon";
15
+ import { Icon } from "@astryxdesign/core/Icon";
8
16
  import {
9
- buildOutlineByChapter,
10
- outlinePanel,
11
- sceneClip,
12
- sceneFill,
13
- scrollToSection,
14
- sectionId,
15
- type ChapterGroup,
16
- type DocChapter,
17
- type DocSection,
18
- } from 'astryx-dracula/shared/chaptered-doc-config';
19
- import {useState, type CSSProperties, type ReactNode} from 'react';
20
-
21
- import {AppShell} from '@astryxdesign/core/AppShell';
22
- import {MobileNavToggle} from '@astryxdesign/core/MobileNav';
23
- import {
24
- SideNav,
25
- SideNavHeading,
26
- SideNavItem,
27
- SideNavSection,
28
- } from '@astryxdesign/core/SideNav';
29
- import {NavIcon} from '@astryxdesign/core/NavIcon';
17
+ HStack,
18
+ Layout,
19
+ LayoutContent,
20
+ LayoutPanel,
21
+ StackItem,
22
+ VStack,
23
+ } from "@astryxdesign/core/Layout";
24
+ import { List, ListItem } from "@astryxdesign/core/List";
25
+ import { MobileNavToggle } from "@astryxdesign/core/MobileNav";
26
+ import { NavIcon } from "@astryxdesign/core/NavIcon";
27
+ import { Outline } from "@astryxdesign/core/Outline";
28
+ import { Selector } from "@astryxdesign/core/Selector";
29
+ import { SideNav, SideNavHeading, SideNavItem, SideNavSection } from "@astryxdesign/core/SideNav";
30
+ import { Heading, Text } from "@astryxdesign/core/Text";
30
31
  import {
31
- HStack,
32
- Layout,
33
- LayoutContent,
34
- LayoutPanel,
35
- StackItem,
36
- VStack,
37
- } from '@astryxdesign/core/Layout';
38
- import {Heading, Text} from '@astryxdesign/core/Text';
39
- import {Button} from '@astryxdesign/core/Button';
40
- import {Icon} from '@astryxdesign/core/Icon';
41
- import type {IconType} from '@astryxdesign/core/Icon';
42
- import {AspectRatio} from '@astryxdesign/core/AspectRatio';
43
- import {CodeBlock} from '@astryxdesign/core/CodeBlock';
44
- import {Divider} from '@astryxdesign/core/Divider';
45
- import {List, ListItem} from '@astryxdesign/core/List';
46
- import {Outline} from '@astryxdesign/core/Outline';
47
- import {Selector} from '@astryxdesign/core/Selector';
48
- import {useMediaQuery} from '@astryxdesign/core/hooks';
49
-
50
- import {ChevronLeft, ChevronRight} from 'lucide-react';
32
+ buildOutlineByChapter,
33
+ type ChapterGroup,
34
+ type DocChapter,
35
+ type DocSection,
36
+ outlinePanel,
37
+ sceneClip,
38
+ scrollToSection,
39
+ sectionId,
40
+ } from "astryx-dracula/shared/chaptered-doc-config";
41
+ import { ChevronLeft, ChevronRight } from "lucide-react";
42
+ import { type CSSProperties, type ReactNode, useState } from "react";
51
43
 
52
- export function ChapterRail({
53
- groups,
54
- activeId,
55
- onSelect,
56
- heading,
57
- subheading,
58
- headingHref,
59
- headingIcon,
44
+ function ChapterRail({
45
+ groups,
46
+ activeId,
47
+ onSelect,
48
+ heading,
49
+ subheading,
50
+ headingHref,
51
+ headingIcon,
60
52
  }: {
61
- groups: ChapterGroup[];
62
- activeId: string;
63
- onSelect: (id: string) => void;
64
- heading: string;
65
- subheading: string;
66
- headingHref: string;
67
- headingIcon: ReactNode;
53
+ groups: ChapterGroup[];
54
+ activeId: string;
55
+ onSelect: (id: string) => void;
56
+ heading: string;
57
+ subheading: string;
58
+ headingHref: string;
59
+ headingIcon: ReactNode;
68
60
  }) {
69
- // Resizable like the shell-side-nav template: 264 default, 220-400 range.
70
- // The AppShell drawer owns the rail below 1024px (see MobileNavToggle),
71
- // so the handle only matters at desktop widths.
72
- return (
73
- <SideNav
74
- collapsible
75
- resizable={{defaultWidth: 264, minWidth: 220, maxWidth: 400}}
76
- header={
77
- <SideNavHeading
78
- icon={<NavIcon icon={headingIcon} />}
79
- heading={heading}
80
- subheading={subheading}
81
- headingHref={headingHref}
82
- />
83
- }>
84
- {groups.map(group => (
85
- <SideNavSection key={group.title} title={group.title}>
86
- {group.chapters.map(chapter => (
87
- <SideNavItem
88
- key={chapter.id}
89
- label={chapter.title}
90
- icon={chapter.icon}
91
- isSelected={chapter.id === activeId}
92
- onClick={() => onSelect(chapter.id)}
93
- />
94
- ))}
95
- </SideNavSection>
96
- ))}
97
- </SideNav>
98
- );
61
+ // Resizable like the shell-side-nav template: 264 default, 220-400 range.
62
+ // The AppShell drawer owns the rail below 1024px (see MobileNavToggle),
63
+ // so the handle only matters at desktop widths.
64
+ return (
65
+ <SideNav
66
+ collapsible
67
+ resizable={{ defaultWidth: 264, minWidth: 220, maxWidth: 400 }}
68
+ header={
69
+ <SideNavHeading
70
+ icon={<NavIcon icon={headingIcon} />}
71
+ heading={heading}
72
+ subheading={subheading}
73
+ headingHref={headingHref}
74
+ />
75
+ }
76
+ >
77
+ {groups.map((group) => (
78
+ <SideNavSection key={group.title} title={group.title}>
79
+ {group.chapters.map((chapter) => (
80
+ <SideNavItem
81
+ key={chapter.id}
82
+ label={chapter.title}
83
+ icon={chapter.icon}
84
+ isSelected={chapter.id === activeId}
85
+ onClick={() => onSelect(chapter.id)}
86
+ />
87
+ ))}
88
+ </SideNavSection>
89
+ ))}
90
+ </SideNav>
91
+ );
99
92
  }
100
93
 
101
94
  // ─── Chapter body ────────────────────────────────────────────────────────────
102
95
 
103
- export function SectionBlock({
104
- chapterId,
105
- section,
106
- }: {
107
- chapterId: string;
108
- section: DocSection;
109
- }) {
110
- return (
111
- <VStack gap={3}>
112
- <Heading level={2} id={sectionId(chapterId, section.key)}>
113
- {section.heading}
114
- </Heading>
115
- <Text type="body" color="secondary" display="block">
116
- {section.body}
117
- </Text>
118
- {section.bullets != null && (
119
- <List listStyle="disc">
120
- {section.bullets.map(bullet => (
121
- <ListItem key={bullet} label={bullet} />
122
- ))}
123
- </List>
124
- )}
125
- {section.code != null && (
126
- <CodeBlock
127
- code={section.code.source}
128
- language={section.code.language}
129
- title={section.code.title}
130
- width="100%"
131
- />
132
- )}
133
- </VStack>
134
- );
96
+ function SectionBlock({ chapterId, section }: { chapterId: string; section: DocSection }) {
97
+ return (
98
+ <VStack gap={3}>
99
+ <Heading level={2} id={sectionId(chapterId, section.key)}>
100
+ {section.heading}
101
+ </Heading>
102
+ <Text type="body" color="secondary" display="block">
103
+ {section.body}
104
+ </Text>
105
+ {section.bullets != null && (
106
+ <List listStyle="disc">
107
+ {section.bullets.map((bullet) => (
108
+ <ListItem key={bullet} label={bullet} />
109
+ ))}
110
+ </List>
111
+ )}
112
+ {section.code != null && (
113
+ <CodeBlock
114
+ code={section.code.source}
115
+ language={section.code.language}
116
+ title={section.code.title}
117
+ width="100%"
118
+ />
119
+ )}
120
+ </VStack>
121
+ );
135
122
  }
136
123
 
137
- export function ChapterNav({
138
- chapters,
139
- chapter,
140
- onSelect,
124
+ function ChapterNav({
125
+ chapters,
126
+ chapter,
127
+ onSelect,
141
128
  }: {
142
- chapters: DocChapter[];
143
- chapter: DocChapter;
144
- onSelect: (id: string) => void;
129
+ chapters: DocChapter[];
130
+ chapter: DocChapter;
131
+ onSelect: (id: string) => void;
145
132
  }) {
146
- const index = chapters.findIndex(entry => entry.id === chapter.id);
147
- const previous = index > 0 ? chapters[index - 1] : undefined;
148
- const next = index < chapters.length - 1 ? chapters[index + 1] : undefined;
149
- return (
150
- <HStack gap={3} hAlign="between" vAlign="center">
151
- {previous != null ? (
152
- <Button
153
- label={previous.title}
154
- variant="secondary"
155
- icon={<Icon icon={ChevronLeft} size="sm" />}
156
- onClick={() => onSelect(previous.id)}
157
- />
158
- ) : (
159
- <StackItem size="fill" />
160
- )}
161
- {next != null ? (
162
- <Button
163
- label={next.title}
164
- variant="secondary"
165
- endContent={<Icon icon={ChevronRight} size="sm" />}
166
- onClick={() => onSelect(next.id)}
167
- />
168
- ) : (
169
- <StackItem size="fill" />
170
- )}
171
- </HStack>
172
- );
133
+ const index = chapters.findIndex((entry) => entry.id === chapter.id);
134
+ const previous = index > 0 ? chapters[index - 1] : undefined;
135
+ const next = index < chapters.length - 1 ? chapters[index + 1] : undefined;
136
+ return (
137
+ <HStack gap={3} hAlign="between" vAlign="center">
138
+ {previous != null ? (
139
+ <Button
140
+ label={previous.title}
141
+ variant="secondary"
142
+ icon={<Icon icon={ChevronLeft} size="sm" />}
143
+ onClick={() => onSelect(previous.id)}
144
+ />
145
+ ) : (
146
+ <StackItem size="fill" />
147
+ )}
148
+ {next != null ? (
149
+ <Button
150
+ label={next.title}
151
+ variant="secondary"
152
+ endContent={<Icon icon={ChevronRight} size="sm" />}
153
+ onClick={() => onSelect(next.id)}
154
+ />
155
+ ) : (
156
+ <StackItem size="fill" />
157
+ )}
158
+ </HStack>
159
+ );
173
160
  }
174
161
 
175
162
  // ─── Page ────────────────────────────────────────────────────────────────────
176
163
 
177
- export function ChapterArtFrame({children}: {children: ReactNode}) {
178
- return (
179
- <AspectRatio ratio={16 / 9} style={sceneClip}>
180
- {children}
181
- </AspectRatio>
182
- );
164
+ export function ChapterArtFrame({ children }: { children: ReactNode }) {
165
+ return (
166
+ <AspectRatio ratio={16 / 9} style={sceneClip}>
167
+ {children}
168
+ </AspectRatio>
169
+ );
183
170
  }
184
171
 
185
172
  export default function ChapteredDoc({
186
- groups,
187
- defaultChapter,
188
- railHeading,
189
- railSubheading,
190
- railHref,
191
- railIcon,
192
- eyebrow,
193
- badge,
194
- art,
195
- footer,
173
+ groups,
174
+ defaultChapter,
175
+ railHeading,
176
+ railSubheading,
177
+ railHref,
178
+ railIcon,
179
+ eyebrow,
180
+ badge,
181
+ art,
182
+ footer,
196
183
  }: {
197
- groups: ChapterGroup[];
198
- defaultChapter: string;
199
- railHeading: string;
200
- railSubheading: string;
201
- railHref: string;
202
- railIcon: ReactNode;
203
- eyebrow: string;
204
- badge: (chapter: DocChapter, index: number, total: number) => ReactNode;
205
- art: (chapterId: string) => ReactNode;
206
- footer?: ReactNode;
184
+ groups: ChapterGroup[];
185
+ defaultChapter: string;
186
+ railHeading: string;
187
+ railSubheading: string;
188
+ railHref: string;
189
+ railIcon: ReactNode;
190
+ eyebrow: string;
191
+ badge: (chapter: DocChapter, index: number, total: number) => ReactNode;
192
+ art: (chapterId: string) => ReactNode;
193
+ footer?: ReactNode;
207
194
  }) {
208
- const chapters = groups.flatMap(group => group.chapters);
209
- const [outlineByChapter] = useState(() => buildOutlineByChapter(chapters));
210
- const [chapterId, setChapterId] = useState(defaultChapter);
211
- const [activeSection, setActiveSection] = useState(
212
- outlineByChapter[defaultChapter]?.[0]?.id ?? '',
213
- );
195
+ const chapters = groups.flatMap((group) => group.chapters);
196
+ const [outlineByChapter] = useState(() => buildOutlineByChapter(chapters));
197
+ const [chapterId, setChapterId] = useState(defaultChapter);
198
+ const [activeSection, setActiveSection] = useState(
199
+ outlineByChapter[defaultChapter]?.[0]?.id ?? "",
200
+ );
214
201
 
215
- // Responsive contract: below 1024px the outline stops being a column, since
216
- // a narrow viewport has nothing to outline against.
217
- const isNarrow = useMediaQuery('(max-width: 1024px)');
202
+ // Responsive contract: below 1024px the outline stops being a column, since
203
+ // a narrow viewport has nothing to outline against.
204
+ const isNarrow = useMediaQuery("(max-width: 1024px)");
218
205
 
219
- const chapter = chapters.find(entry => entry.id === chapterId) ?? chapters[0];
220
- const outlineItems = outlineByChapter[chapter.id] ?? [];
206
+ const chapter = chapters.find((entry) => entry.id === chapterId) ?? chapters[0];
207
+ const outlineItems = outlineByChapter[chapter.id] ?? [];
221
208
 
222
- const openChapter = (id: string) => {
223
- setChapterId(id);
224
- setActiveSection(outlineByChapter[id]?.[0]?.id ?? '');
225
- // A chapter reads as a new page, so the document goes back to the top
226
- // instead of keeping the previous chapter's scroll offset.
227
- window.scrollTo({top: 0});
228
- };
209
+ const openChapter = (id: string) => {
210
+ setChapterId(id);
211
+ setActiveSection(outlineByChapter[id]?.[0]?.id ?? "");
212
+ // A chapter reads as a new page, so the document goes back to the top
213
+ // instead of keeping the previous chapter's scroll offset.
214
+ window.scrollTo({ top: 0 });
215
+ };
229
216
 
230
- return (
231
- <AppShell
232
- height="auto"
233
- contentPadding={0}
234
- mobileNav={{hasToggle: false}}
235
- sideNav={
236
- <ChapterRail
237
- groups={groups}
238
- activeId={chapter.id}
239
- onSelect={openChapter}
240
- heading={railHeading}
241
- subheading={railSubheading}
242
- headingHref={railHref}
243
- headingIcon={railIcon}
244
- />
245
- }>
246
- <Layout
247
- height="auto"
248
- contentWidth={960}
249
- end={
250
- isNarrow ? undefined : (
251
- <LayoutPanel
252
- isScrollable={false}
253
- label="On this page"
254
- role="complementary"
255
- style={outlinePanel}>
256
- <Outline
257
- items={outlineItems}
258
- onActiveIdChange={setActiveSection}
259
- />
260
- </LayoutPanel>
261
- )
262
- }
263
- content={
264
- <LayoutContent isScrollable={false} padding={8}>
265
- <VStack gap={8}>
266
- <VStack gap={2}>
267
- <HStack gap={2} vAlign="center">
268
- <MobileNavToggle />
269
- <Text type="supporting" color="secondary">
270
- {eyebrow}
271
- </Text>
272
- </HStack>
273
- <Heading level={1} type="display-2">
274
- {chapter.title}
275
- </Heading>
276
- <Text type="supporting" color="secondary" hasTabularNumbers>
277
- {badge(
278
- chapter,
279
- chapters.findIndex(entry => entry.id === chapter.id),
280
- chapters.length,
281
- )}
282
- </Text>
283
- {isNarrow && (
284
- <Selector
285
- label="On this page"
286
- isLabelHidden
287
- options={outlineItems.map(item => ({
288
- value: item.id,
289
- label: item.label,
290
- }))}
291
- value={activeSection}
292
- onChange={(id: string) => {
293
- setActiveSection(id);
294
- scrollToSection(id);
295
- }}
296
- width="100%"
297
- />
298
- )}
299
- </VStack>
217
+ return (
218
+ <AppShell
219
+ height="auto"
220
+ contentPadding={0}
221
+ mobileNav={{ hasToggle: false }}
222
+ sideNav={
223
+ <ChapterRail
224
+ groups={groups}
225
+ activeId={chapter.id}
226
+ onSelect={openChapter}
227
+ heading={railHeading}
228
+ subheading={railSubheading}
229
+ headingHref={railHref}
230
+ headingIcon={railIcon}
231
+ />
232
+ }
233
+ >
234
+ <Layout
235
+ height="auto"
236
+ contentWidth={960}
237
+ end={
238
+ isNarrow ? undefined : (
239
+ <LayoutPanel
240
+ isScrollable={false}
241
+ label="On this page"
242
+ role="complementary"
243
+ style={outlinePanel}
244
+ >
245
+ <Outline items={outlineItems} onActiveIdChange={setActiveSection} />
246
+ </LayoutPanel>
247
+ )
248
+ }
249
+ content={
250
+ <LayoutContent isScrollable={false} padding={8}>
251
+ <VStack gap={8}>
252
+ <VStack gap={2}>
253
+ <HStack gap={2} vAlign="center">
254
+ <MobileNavToggle />
255
+ <Text type="supporting" color="secondary">
256
+ {eyebrow}
257
+ </Text>
258
+ </HStack>
259
+ <Heading level={1} type="display-2">
260
+ {chapter.title}
261
+ </Heading>
262
+ <Text type="supporting" color="secondary" hasTabularNumbers>
263
+ {badge(
264
+ chapter,
265
+ chapters.findIndex((entry) => entry.id === chapter.id),
266
+ chapters.length,
267
+ )}
268
+ </Text>
269
+ {isNarrow && (
270
+ <Selector
271
+ label="On this page"
272
+ isLabelHidden
273
+ options={outlineItems.map((item) => ({
274
+ value: item.id,
275
+ label: item.label,
276
+ }))}
277
+ value={activeSection}
278
+ onChange={(id: string) => {
279
+ setActiveSection(id);
280
+ scrollToSection(id);
281
+ }}
282
+ width="100%"
283
+ />
284
+ )}
285
+ </VStack>
300
286
 
301
- <Text type="large" color="secondary" display="block">
302
- {chapter.intro}
303
- </Text>
287
+ <Text type="large" color="secondary" display="block">
288
+ {chapter.intro}
289
+ </Text>
304
290
 
305
- {art(chapter.id)}
291
+ {art(chapter.id)}
306
292
 
307
- <VStack gap={8}>
308
- {chapter.sections.map(section => (
309
- <SectionBlock
310
- key={section.key}
311
- chapterId={chapter.id}
312
- section={section}
313
- />
314
- ))}
315
- </VStack>
293
+ <VStack gap={8}>
294
+ {chapter.sections.map((section) => (
295
+ <SectionBlock key={section.key} chapterId={chapter.id} section={section} />
296
+ ))}
297
+ </VStack>
316
298
 
317
- <Divider />
299
+ <Divider />
318
300
 
319
- <ChapterNav
320
- chapters={chapters}
321
- chapter={chapter}
322
- onSelect={openChapter}
323
- />
301
+ <ChapterNav chapters={chapters} chapter={chapter} onSelect={openChapter} />
324
302
 
325
- {footer}
326
- </VStack>
327
- </LayoutContent>
328
- }
329
- />
330
- </AppShell>
331
- );
303
+ {footer}
304
+ </VStack>
305
+ </LayoutContent>
306
+ }
307
+ />
308
+ </AppShell>
309
+ );
332
310
  }
@@ -7,21 +7,16 @@
7
7
  // legend row (dashboard.tsx:229, :253) -- a labelled data category, not chrome.
8
8
 
9
9
  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)',
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)",
20
20
  } as const;
21
21
 
22
22
  export type ChartHue = (typeof CHART_HUES)[keyof typeof CHART_HUES];
23
-
24
- // The value list exists for the gate: it reads the object's VALUES, never a
25
- // grep across shared/. A text-level filter cannot separate the sanctioned rows
26
- // from a drifting neighbour, which is why the check is here and not in a lint.
27
- export const CHART_HUE_VALUES: readonly ChartHue[] = Object.values(CHART_HUES);