@fusion.dev/helix-docs 0.0.0-dev

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/content/accessibility.mdx +117 -0
  3. package/content/angular.mdx +60 -0
  4. package/content/browser-support.mdx +83 -0
  5. package/content/building-on-the-headless-tier.mdx +143 -0
  6. package/content/charting.mdx +93 -0
  7. package/content/coding-agents.mdx +139 -0
  8. package/content/color-modes.mdx +87 -0
  9. package/content/color.mdx +92 -0
  10. package/content/composition.mdx +171 -0
  11. package/content/elevation-and-material.mdx +93 -0
  12. package/content/events.mdx +125 -0
  13. package/content/forms.mdx +170 -0
  14. package/content/iconography.mdx +80 -0
  15. package/content/installation.mdx +125 -0
  16. package/content/intent-reference.mdx +107 -0
  17. package/content/introduction.mdx +69 -0
  18. package/content/localization.mdx +95 -0
  19. package/content/metrics.mdx +399 -0
  20. package/content/motion.mdx +91 -0
  21. package/content/other-frameworks.mdx +70 -0
  22. package/content/overlays.mdx +102 -0
  23. package/content/overriding-a-component.mdx +160 -0
  24. package/content/react.mdx +144 -0
  25. package/content/recipes.mdx +131 -0
  26. package/content/routing.mdx +208 -0
  27. package/content/sandbox.mdx +76 -0
  28. package/content/server-rendering.mdx +103 -0
  29. package/content/shape-and-borders.mdx +80 -0
  30. package/content/size-and-density.mdx +86 -0
  31. package/content/space-and-size.mdx +89 -0
  32. package/content/state-and-control.mdx +132 -0
  33. package/content/surfaces-and-separation.mdx +99 -0
  34. package/content/svelte.mdx +45 -0
  35. package/content/theme-builder.mdx +66 -0
  36. package/content/theming.mdx +111 -0
  37. package/content/tiers.mdx +159 -0
  38. package/content/tokens.mdx +88 -0
  39. package/content/typography.mdx +117 -0
  40. package/content/using-helix-with-an-agent.mdx +231 -0
  41. package/content/using-the-elements.mdx +125 -0
  42. package/content/versioning.mdx +72 -0
  43. package/content/visual-axes.mdx +75 -0
  44. package/content/vue.mdx +80 -0
  45. package/content/without-a-bundler.mdx +119 -0
  46. package/dist/catalog.d.ts +186 -0
  47. package/dist/catalog.js +296 -0
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.js +6 -0
  50. package/dist/naming.d.ts +14 -0
  51. package/dist/naming.js +28 -0
  52. package/dist/pages.generated.d.ts +7 -0
  53. package/dist/pages.generated.js +284 -0
  54. package/dist/reference.d.ts +105 -0
  55. package/dist/reference.js +181 -0
  56. package/dist/registry.d.ts +89 -0
  57. package/dist/registry.js +70 -0
  58. package/dist/sections.d.ts +44 -0
  59. package/dist/sections.js +36 -0
  60. package/dist/sources.generated.d.ts +6 -0
  61. package/dist/sources.generated.js +5158 -0
  62. package/package.json +46 -0
@@ -0,0 +1,284 @@
1
+ export const docPages = [
2
+ {
3
+ description: 'What the library guarantees, what it cannot, and where the line is.',
4
+ section: 'Foundations',
5
+ slug: 'accessibility',
6
+ title: 'Accessibility',
7
+ },
8
+ {
9
+ description: 'The schema Angular needs before it will compile an unknown tag.',
10
+ parent: 'installation',
11
+ section: 'Getting started',
12
+ slug: 'angular',
13
+ title: 'Angular',
14
+ },
15
+ {
16
+ description: 'The platform features this is built on, and what is actually tested.',
17
+ order: 5,
18
+ section: 'Getting started',
19
+ slug: 'browser-support',
20
+ title: 'Browser support',
21
+ },
22
+ {
23
+ description: 'Using the headless tier as the foundation for a design system of your own.',
24
+ section: 'Essentials',
25
+ slug: 'building-on-the-headless-tier',
26
+ title: 'Building on the headless tier',
27
+ },
28
+ {
29
+ description: 'The data channel nobody expects, and why it is a real table.',
30
+ section: 'Essentials',
31
+ slug: 'charting',
32
+ title: 'Charting',
33
+ },
34
+ {
35
+ description: 'Every surface here is readable by a machine, and where each one is.',
36
+ order: 4,
37
+ section: 'Getting started',
38
+ slug: 'coding-agents',
39
+ title: 'Coding agents',
40
+ },
41
+ {
42
+ description: 'The roles, the ramps, and why the palette is solved rather than picked.',
43
+ section: 'Foundations',
44
+ slug: 'color',
45
+ title: 'Color',
46
+ },
47
+ {
48
+ description: 'One attribute, two modes, and no token restated to honor it.',
49
+ section: 'Foundations',
50
+ slug: 'color-modes',
51
+ title: 'Color modes',
52
+ },
53
+ {
54
+ description: 'Families, parts and groups, and which to reach for.',
55
+ section: 'Essentials',
56
+ slug: 'composition',
57
+ title: 'Composition',
58
+ },
59
+ {
60
+ description: 'Five layers, four shadows, and the glass the material tokens make possible.',
61
+ section: 'Foundations',
62
+ slug: 'elevation-and-material',
63
+ title: 'Elevation and material',
64
+ },
65
+ {
66
+ description: 'The request and the notification, why one name is prefixed and the other is not.',
67
+ section: 'Essentials',
68
+ slug: 'events',
69
+ title: 'Events',
70
+ },
71
+ {
72
+ description: 'Real form participation, labels that resolve across a shadow root, and the disabled trap.',
73
+ section: 'Essentials',
74
+ slug: 'forms',
75
+ title: 'Forms and validation',
76
+ },
77
+ {
78
+ description: '103 glyphs, one geometry, and how to draw one that is not in the set.',
79
+ section: 'Foundations',
80
+ slug: 'iconography',
81
+ title: 'Iconography',
82
+ },
83
+ {
84
+ description: 'What to install, the order the stylesheets go in, and how to check it worked.',
85
+ order: 3,
86
+ section: 'Getting started',
87
+ slug: 'installation',
88
+ title: 'Installation',
89
+ },
90
+ {
91
+ description: 'Every input the theme engine takes, and what each one decides.',
92
+ section: 'Theming',
93
+ slug: 'intent-reference',
94
+ title: 'Intent reference',
95
+ },
96
+ {
97
+ description: 'What Helix is, and the four packages it stacks into.',
98
+ order: 1,
99
+ section: 'Getting started',
100
+ slug: 'introduction',
101
+ title: 'Introduction',
102
+ },
103
+ {
104
+ description: 'Locale-aware controls, translatable strings, and a layout that already flips.',
105
+ section: 'Essentials',
106
+ slug: 'localization',
107
+ title: 'Localization and RTL',
108
+ },
109
+ {
110
+ description: 'What Helix costs a page, measured rather than claimed.',
111
+ order: 7,
112
+ section: 'Getting started',
113
+ slug: 'metrics',
114
+ title: 'Metrics',
115
+ },
116
+ {
117
+ description: 'Four durations, five curves, and the rule every animating component has to answer.',
118
+ section: 'Foundations',
119
+ slug: 'motion',
120
+ title: 'Motion',
121
+ },
122
+ {
123
+ description: 'The three rules every framework reduces to, and what they look like in each.',
124
+ parent: 'installation',
125
+ section: 'Getting started',
126
+ slug: 'other-frameworks',
127
+ title: 'Other frameworks',
128
+ trailing: true,
129
+ },
130
+ {
131
+ description: 'The top layer, what dismisses what, and where focus goes.',
132
+ section: 'Essentials',
133
+ slug: 'overlays',
134
+ title: 'Overlays and layering',
135
+ },
136
+ {
137
+ description: 'The three ways in past a shadow root, and which one to reach for.',
138
+ section: 'Theming',
139
+ slug: 'overriding-a-component',
140
+ title: 'Overriding a component',
141
+ },
142
+ {
143
+ description: 'The typings, the hydration bridge, and how state moves.',
144
+ parent: 'installation',
145
+ section: 'Getting started',
146
+ slug: 'react',
147
+ title: 'React',
148
+ },
149
+ {
150
+ description: 'Helix styling for markup this library does not render, with no JavaScript.',
151
+ section: 'Foundations',
152
+ slug: 'recipes',
153
+ title: 'Recipes',
154
+ },
155
+ {
156
+ description: 'Why a component renders its own anchor, and how to keep a router.',
157
+ section: 'Essentials',
158
+ slug: 'routing',
159
+ title: 'Routing and navigation',
160
+ },
161
+ {
162
+ description: 'The editor beside the preview, and how to build one of your own.',
163
+ section: 'Tooling',
164
+ slug: 'sandbox',
165
+ title: 'Sandbox',
166
+ },
167
+ {
168
+ description: 'What renders without a DOM, and what has to wait for one.',
169
+ section: 'Essentials',
170
+ slug: 'server-rendering',
171
+ title: 'Server rendering',
172
+ },
173
+ {
174
+ description: 'Five radii, two widths, and the two tokens that are a policy rather than a value.',
175
+ section: 'Foundations',
176
+ slug: 'shape-and-borders',
177
+ title: 'Shape and borders',
178
+ },
179
+ {
180
+ description: 'Why no control has a size, and what to write instead.',
181
+ section: 'Theming',
182
+ slug: 'size-and-density',
183
+ title: 'Size and density',
184
+ },
185
+ {
186
+ description: "One spacing scale, and why a control's height is not on it.",
187
+ section: 'Foundations',
188
+ slug: 'space-and-size',
189
+ title: 'Space and size',
190
+ },
191
+ {
192
+ description: 'Why a declared property is a seed, and how to take a decision when you want it.',
193
+ section: 'Essentials',
194
+ slug: 'state-and-control',
195
+ title: 'State and control',
196
+ },
197
+ {
198
+ description: 'The four ways a surface is told apart, and the ground a component is not.',
199
+ section: 'Foundations',
200
+ slug: 'surfaces-and-separation',
201
+ title: 'Surfaces and separation',
202
+ },
203
+ {
204
+ description: 'Typed tags, and the framework that needs the least said about it.',
205
+ parent: 'installation',
206
+ section: 'Getting started',
207
+ slug: 'svelte',
208
+ title: 'Svelte',
209
+ },
210
+ {
211
+ description: 'The tool that turns intent inputs into a token set you can see.',
212
+ section: 'Tooling',
213
+ slug: 'theme-builder',
214
+ title: 'Theme builder',
215
+ },
216
+ {
217
+ description: 'Deriving an accessible token set from intent inputs.',
218
+ section: 'Theming',
219
+ slug: 'theming',
220
+ title: 'Deriving a theme',
221
+ },
222
+ {
223
+ description: 'Why the headless and styled tiers are separate packages.',
224
+ order: 2,
225
+ section: 'Getting started',
226
+ slug: 'tiers',
227
+ title: 'Tiers',
228
+ },
229
+ {
230
+ description: 'The two layers, the surface ladder, and what is enforced.',
231
+ pinned: true,
232
+ section: 'Theming',
233
+ slug: 'tokens',
234
+ title: 'Tokens',
235
+ },
236
+ {
237
+ description: 'Three families, one scale, and a heading that is a real heading.',
238
+ section: 'Foundations',
239
+ slug: 'typography',
240
+ title: 'Typography',
241
+ },
242
+ {
243
+ description: 'The four things that look like a library bug and are not, and where an agent should read from.',
244
+ parent: 'coding-agents',
245
+ section: 'Getting started',
246
+ slug: 'using-helix-with-an-agent',
247
+ title: 'Using Helix with an agent',
248
+ },
249
+ {
250
+ description: 'Binding events, registering elements, and the traps in both.',
251
+ pinned: true,
252
+ section: 'Essentials',
253
+ slug: 'using-the-elements',
254
+ title: 'Using the elements',
255
+ },
256
+ {
257
+ description: 'What a version number means here, and which packages move together.',
258
+ order: 6,
259
+ section: 'Getting started',
260
+ slug: 'versioning',
261
+ title: 'Versioning',
262
+ },
263
+ {
264
+ description: 'Four words with fixed vocabularies, one escape hatch, and the word that is refused.',
265
+ section: 'Theming',
266
+ slug: 'visual-axes',
267
+ title: 'Visual axes',
268
+ },
269
+ {
270
+ description: 'The one compiler setting Vue needs, and why props and events already work.',
271
+ parent: 'installation',
272
+ section: 'Getting started',
273
+ slug: 'vue',
274
+ title: 'Vue',
275
+ },
276
+ {
277
+ description: 'A page that uses Helix with no build step, and what that costs.',
278
+ parent: 'installation',
279
+ section: 'Getting started',
280
+ slug: 'without-a-bundler',
281
+ title: 'Without a bundler',
282
+ trailing: true,
283
+ },
284
+ ];
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The component reference, rendered from a manifest to markdown.
3
+ *
4
+ * **Pure, and it lives here rather than in a site.** A manifest-to-markdown
5
+ * renderer is documentation for Helix: a consumer installing `@fusion.dev/helix-docs`
6
+ * should get the component reference along with the prose, and a site should be
7
+ * one of its readers rather than its owner. It also has to be pure, because a
8
+ * content package that touched the filesystem would trace itself into every
9
+ * consumer's server bundle. Reading the manifest is the caller's job.
10
+ *
11
+ * **The styled tier only, which is not a shortcut.** A styled component extends
12
+ * its headless base, and the analyzer resolves inheritance across packages, so
13
+ * the styled entry already carries every inherited member with the base it came
14
+ * from named against it. Documenting both tiers would print most of the library
15
+ * twice to say the same thing.
16
+ *
17
+ * **First paragraph in a table cell, and the whole thing everywhere else.** A
18
+ * cell holding four paragraphs is a table nobody reads, so a property's own
19
+ * reasoning stays on the component and on `/components/<family>`, which renders
20
+ * the manifest in a browser. A component's description is not in a cell: it is
21
+ * prose under a heading, and truncating that threw away everything `Date
22
+ * Picker` says about its three modes.
23
+ */
24
+ import type { HelixDeclaration, HelixManifest } from '@fusion.dev/helix-cem';
25
+ /**
26
+ * An example, as much of one as a markdown reference prints.
27
+ *
28
+ * Structural rather than the catalog's `Example`, so this module does not
29
+ * import from one that imports it. Anything the catalog returns satisfies it.
30
+ */
31
+ export interface ReferenceExample {
32
+ css: string;
33
+ description: string;
34
+ html: string;
35
+ label: string;
36
+ }
37
+ /** What to put above the reference, for a caller that generates a file. */
38
+ export interface ReferenceOptions {
39
+ /**
40
+ * Examples to print under each family, keyed by family slug.
41
+ *
42
+ * **The reference `.md` carries the examples and the HTML page does not**,
43
+ * and the difference is deliberate rather than an omission on one side. That
44
+ * page answers what a component can do, and the sandbox beside it answers
45
+ * what the component looks like; a reference that started collecting pictures
46
+ * would stop being the one place you can trust to be complete. An agent has
47
+ * no sandbox to be sent to, and to it an example is text rather than a
48
+ * picture, so the `.md` is the only surface that has to answer both.
49
+ */
50
+ examples?: Record<string, ReferenceExample[]>;
51
+ /** Only these families. Omitted renders everything in the manifest. */
52
+ families?: string[];
53
+ /**
54
+ * Frontmatter, emitted above the heading.
55
+ *
56
+ * Provenance rather than decoration: a generated document should say what it
57
+ * was generated from, so a reader who finds one detached from this repository
58
+ * can still tell where to look. Keys are written in the order given, because
59
+ * a reader scans the first line and `title` is what they want there.
60
+ */
61
+ frontmatter?: Array<[string, string]>;
62
+ /** Prose under the heading, already wrapped. Omitted where absent. */
63
+ lede?: string[];
64
+ /** Top-level heading. */
65
+ title?: string;
66
+ }
67
+ /**
68
+ * The family and part a declaration belongs to, from where its file is.
69
+ *
70
+ * Not from the tag name. A tag is a string the family prefix can only be
71
+ * guessed out of: `helix-button-group` is one family and `helix-tab` is a
72
+ * part of another, and nothing in the name says which. The path says both.
73
+ */
74
+ export declare const place: (path: string | undefined) => {
75
+ family: string;
76
+ part: string;
77
+ };
78
+ /** One family's declarations, in the order a reference prints them. */
79
+ export interface ReferenceFamily {
80
+ family: string;
81
+ parts: Array<{
82
+ declaration: HelixDeclaration;
83
+ part: string;
84
+ }>;
85
+ }
86
+ /**
87
+ * Every family in a manifest, alphabetical, each with its parts.
88
+ *
89
+ * Root first inside a family, then the rest alphabetically. A family reads from
90
+ * the thing that holds the others, and `item` before `root` is alphabetical
91
+ * order telling you about the alphabet rather than about the component.
92
+ */
93
+ export declare const referenceFamilies: (manifest: HelixManifest) => ReferenceFamily[];
94
+ /**
95
+ * A manifest, as the markdown reference for everything in it.
96
+ *
97
+ * The caller supplies the manifest and whatever preamble belongs above it, so
98
+ * the same renderer serves the whole-library document in `docs/` and a
99
+ * per-family page without either one knowing about the other.
100
+ *
101
+ * **A single family gets no heading naming it**, because the title already
102
+ * does: a document about the accordion should not say Accordion twice before
103
+ * its first sentence. More than one and the headings are what separate them.
104
+ */
105
+ export declare const reference: (manifest: HelixManifest, options?: ReferenceOptions) => string;
@@ -0,0 +1,181 @@
1
+ import { startCase } from './naming.js';
2
+ /**
3
+ * Whether a member belongs in a reference at all.
4
+ *
5
+ * Public only, and nothing inherited from `HelixElement`. Every component in
6
+ * the library extends it, so its members would appear on all of them: an
7
+ * `internals` field and two lifecycle methods that exist so components do not
8
+ * have to think about the shadow root. Printing them everywhere says the same
9
+ * infrastructural thing once per component and buries the four properties a
10
+ * consumer came to read.
11
+ *
12
+ * `HelixFormElement` is deliberately not on that list. What it contributes is
13
+ * form participation, which is the API of a form control.
14
+ */
15
+ const INFRASTRUCTURE = new Set(['HelixElement']);
16
+ const isPublic = (member) => member.privacy !== 'private' &&
17
+ member.privacy !== 'protected' &&
18
+ !INFRASTRUCTURE.has(member.inheritedFrom?.name ?? '');
19
+ /** A cell's worth of a description: the first paragraph, on one line. */
20
+ const cell = (text) => (text ?? '').split('\n\n')[0]?.replace(/\s+/g, ' ').replace(/\|/g, '\\|').trim() ?? '';
21
+ /** A type, escaped so its unions do not end the column they are in. */
22
+ const code = (text) => text ? `\`${text.replace(/\|/g, '\\|')}\`` : '-';
23
+ /** A markdown table, or nothing at all where there are no rows to put in one. */
24
+ const table = (depth, heading, columns, rows) => {
25
+ if (rows.length === 0) {
26
+ return '';
27
+ }
28
+ const head = `| ${columns.join(' | ')} |`;
29
+ const rule = `| ${columns.map(() => '---').join(' | ')} |`;
30
+ const body = rows.map((row) => `| ${row.join(' | ')} |`).join('\n');
31
+ return `\n${'#'.repeat(depth)} ${heading}\n\n${head}\n${rule}\n${body}\n`;
32
+ };
33
+ /**
34
+ * The family and part a declaration belongs to, from where its file is.
35
+ *
36
+ * Not from the tag name. A tag is a string the family prefix can only be
37
+ * guessed out of: `helix-button-group` is one family and `helix-tab` is a
38
+ * part of another, and nothing in the name says which. The path says both.
39
+ */
40
+ export const place = (path) => {
41
+ const [, family = '', part = ''] = /^src\/([^/]+)\/([^/]+)\//.exec(path ?? '') ?? [];
42
+ return { family, part };
43
+ };
44
+ /**
45
+ * Every family in a manifest, alphabetical, each with its parts.
46
+ *
47
+ * Root first inside a family, then the rest alphabetically. A family reads from
48
+ * the thing that holds the others, and `item` before `root` is alphabetical
49
+ * order telling you about the alphabet rather than about the component.
50
+ */
51
+ export const referenceFamilies = (manifest) => {
52
+ const families = new Map();
53
+ for (const module of manifest.modules ?? []) {
54
+ for (const declaration of module.declarations ?? []) {
55
+ if (!declaration.tagName) {
56
+ continue;
57
+ }
58
+ const { family, part } = place(module.path);
59
+ if (!family) {
60
+ continue;
61
+ }
62
+ families.set(family, [...(families.get(family) ?? []), { declaration, part }]);
63
+ }
64
+ }
65
+ return [...families]
66
+ .sort(([a], [b]) => a.localeCompare(b))
67
+ .map(([family, parts]) => ({
68
+ family,
69
+ parts: [...parts].sort((a, b) => a.part === 'root' ? -1 : b.part === 'root' ? 1 : a.part.localeCompare(b.part)),
70
+ }));
71
+ };
72
+ /** The tables for one element, in the order they are printed. */
73
+ const element = (depth, declaration, part) => {
74
+ const { attributes = [], cssParts = [], cssProperties = [], description, events = [], members = [], slots = [], summary, superclass, tagName, } = declaration;
75
+ const lines = [`${'#'.repeat(depth)} ${startCase(part)} (\`${tagName}\`)`, ''];
76
+ if (summary) {
77
+ lines.push(summary, '');
78
+ }
79
+ if (superclass?.name) {
80
+ lines.push(`Extends \`${superclass.name}\`.`, '');
81
+ }
82
+ if (description) {
83
+ lines.push(description.trim(), '');
84
+ }
85
+ const attributeOf = new Map(attributes.map((one) => [one.fieldName ?? one.name, one.name]));
86
+ const named = (rows, kind) => rows
87
+ .filter((row) => row.kind === kind && isPublic(row))
88
+ .sort((a, b) => a.name.localeCompare(b.name));
89
+ lines.push(table(depth + 1, 'Properties', ['Property', 'Attribute', 'Type', 'Default', 'From', 'Description'], named(members, 'field').map((field) => [
90
+ `\`${field.name}\``,
91
+ attributeOf.has(field.name) ? `\`${attributeOf.get(field.name)}\`` : '-',
92
+ code(field.type?.text),
93
+ field.default ? `\`${field.default}\`` : '-',
94
+ field.inheritedFrom?.name ? `\`${field.inheritedFrom.name}\`` : '-',
95
+ cell(field.description),
96
+ ])).trimEnd(), table(depth + 1, 'Methods', ['Method', 'From', 'Description'], named(members, 'method').map((method) => [
97
+ `\`${method.name}()\``,
98
+ method.inheritedFrom?.name ? `\`${method.inheritedFrom.name}\`` : '-',
99
+ cell(method.description),
100
+ ])).trimEnd(), table(depth + 1, 'Events', ['Event', 'Type', 'Description'], events.map((event) => [`\`${event.name}\``, code(event.type?.text), cell(event.description)])).trimEnd(), table(depth + 1, 'Slots', ['Slot', 'Description'], slots.map((slot) => [
101
+ slot.name ? `\`${slot.name}\`` : '*(default)*',
102
+ cell(slot.description),
103
+ ])).trimEnd(), table(depth + 1, 'Parts', ['Part', 'Description'], cssParts.map((one) => [`\`${one.name}\``, cell(one.description)])).trimEnd(),
104
+ /* A "From" column, because a hook can now arrive from a component this one
105
+ borrowed a stylesheet from rather than be its own. Without it a borrowed
106
+ `--icon-button-radii` reads on the Copy Button page as a hook Copy Button
107
+ named, and a consumer who guesses `--copy-button-radii` from that gets
108
+ nothing. See 290. */
109
+ table(depth + 1, 'Custom properties', ['Property', 'From', 'Description'], cssProperties.map((one) => [
110
+ `\`${one.name}\``,
111
+ one.inheritedFrom?.name ? `\`${one.inheritedFrom.name}\`` : '-',
112
+ cell(one.description),
113
+ ])).trimEnd(), '');
114
+ return lines;
115
+ };
116
+ /**
117
+ * The leading `<!-- @summary -->` comment, which the description above already
118
+ * carries. It is how an example declares its own one-liner to the analyzer, so
119
+ * it belongs in the file and not a second time in the prose beside it.
120
+ */
121
+ const withoutSummary = (html) => html.replace(/^<!--\s*@summary[\s\S]*?-->\n*/, '');
122
+ /** One family's examples, as the markdown a reader or an agent can run. */
123
+ const shown = (depth, examples) => {
124
+ if (examples.length === 0) {
125
+ return [];
126
+ }
127
+ const lines = [`${'#'.repeat(depth)} Examples`, ''];
128
+ for (const example of examples) {
129
+ lines.push(`${'#'.repeat(depth + 1)} ${example.label}`, '');
130
+ if (example.description) {
131
+ lines.push(example.description, '');
132
+ }
133
+ lines.push('```html', withoutSummary(example.html), '```', '');
134
+ if (example.css) {
135
+ lines.push('```css', example.css, '```', '');
136
+ }
137
+ }
138
+ return lines;
139
+ };
140
+ /**
141
+ * A manifest, as the markdown reference for everything in it.
142
+ *
143
+ * The caller supplies the manifest and whatever preamble belongs above it, so
144
+ * the same renderer serves the whole-library document in `docs/` and a
145
+ * per-family page without either one knowing about the other.
146
+ *
147
+ * **A single family gets no heading naming it**, because the title already
148
+ * does: a document about the accordion should not say Accordion twice before
149
+ * its first sentence. More than one and the headings are what separate them.
150
+ */
151
+ export const reference = (manifest, options = {}) => {
152
+ const front = options.frontmatter ?? [];
153
+ const lines = [
154
+ ...(front.length > 0
155
+ ? ['---', ...front.map(([key, value]) => `${key}: ${value}`), '---', '']
156
+ : []),
157
+ `# ${options.title ?? 'Component reference'}`,
158
+ '',
159
+ ...(options.lede ?? []),
160
+ ];
161
+ const wanted = options.families;
162
+ const families = referenceFamilies(manifest).filter((entry) => wanted === undefined || wanted.includes(entry.family));
163
+ /* Everything under a family heading sits one level deeper than it would in a
164
+ document that has no family heading to sit under. Without this a page about
165
+ one component opens at `###`, which is a heading level with nothing above
166
+ it and reads as a fragment of a larger file. */
167
+ const depth = families.length > 1 ? 3 : 2;
168
+ for (const { family, parts } of families) {
169
+ if (families.length > 1) {
170
+ lines.push(`## ${startCase(family)}`, '');
171
+ }
172
+ for (const { declaration, part } of parts) {
173
+ lines.push(...element(depth, declaration, part));
174
+ }
175
+ lines.push(...shown(depth, options.examples?.[family] ?? []));
176
+ }
177
+ return `${lines
178
+ .join('\n')
179
+ .replace(/\n{3,}/g, '\n\n')
180
+ .trimEnd()}\n`;
181
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Documentation registry for Helix.
3
+ *
4
+ * Docs are a package rather than a folder inside a site so that the docs are
5
+ * pinned to the version of the code they describe. A site renders this
6
+ * registry; it does not own it.
7
+ *
8
+ * **There is no version axis in here.** The package version is the version:
9
+ * `@fusion.dev/helix-docs@0.2.0` documents Helix 0.2.0, because every package in a
10
+ * domain moves in lockstep. A `versions` array beside that would be a second
11
+ * source of truth with nothing keeping the two in step, and the first time they
12
+ * disagreed the registry would win over the package that shipped it.
13
+ *
14
+ * A site serving several versions installs several versions, which is the
15
+ * mechanism npm already provides and the only one that cannot drift.
16
+ *
17
+ * **Nothing about a page is written here.** `pages.generated.ts` is derived
18
+ * from each file's frontmatter by `bun run docs`, because the frontmatter is
19
+ * what a `.md` consumer receives and a second copy would be a second source of
20
+ * truth. This module is the types and the reading of them; `sections.ts` is the
21
+ * spine that frontmatter has nowhere to live.
22
+ */
23
+ import { docPages } from './pages.generated.js';
24
+ import { type DocSection, docSections } from './sections.js';
25
+ export interface DocPage {
26
+ /** One-line summary, used for nav tooltips and search results. */
27
+ description: string;
28
+ /**
29
+ * Position within a section that is a path.
30
+ *
31
+ * Absent everywhere else, where the order is pinned-entry-first and then
32
+ * alphabetical rather than something an author states.
33
+ */
34
+ order?: number;
35
+ /**
36
+ * Slug of the page this one sits under, for the one level of nesting that
37
+ * exists. Installation and its per-framework pages are the case.
38
+ */
39
+ parent?: string;
40
+ /**
41
+ * Whether this is its section's entry page, which sorts first and exempts
42
+ * itself from the alphabetical rule. At most one per section.
43
+ */
44
+ pinned?: boolean;
45
+ /** Title of the `DocSection` this belongs to. */
46
+ section: string;
47
+ /** Path segment, unique across the package. */
48
+ slug: string;
49
+ title: string;
50
+ /**
51
+ * Whether this page is the general answer rather than a named one.
52
+ *
53
+ * The mirror of `pinned`: that sorts a section's entry page first, this sorts
54
+ * a catch-all after the specific pages it is the alternative to. Installation
55
+ * is the case. Angular, React, Svelte and Vue are lookups and belong in the
56
+ * alphabet a reader scans; Other frameworks and Without a bundler answer
57
+ * "none of the above" and read as nonsense in the middle of it, which is
58
+ * where the alphabet was putting Other frameworks.
59
+ *
60
+ * Trailing pages stay alphabetical among themselves, so this states a rank
61
+ * and never a position.
62
+ */
63
+ trailing?: boolean;
64
+ }
65
+ /** One section of the sidebar, with the pages under it in render order. */
66
+ export interface DocNavSection extends DocSection {
67
+ pages: DocNavPage[];
68
+ }
69
+ /** A page in the sidebar, with any children beneath it. */
70
+ export interface DocNavPage extends DocPage {
71
+ children: DocPage[];
72
+ }
73
+ export type { DocSection };
74
+ export { docPages as pages, docSections as sections };
75
+ export declare const findPage: (slug: string) => DocPage | undefined;
76
+ /**
77
+ * The whole sidebar: every declared section, its pages in render order, and
78
+ * each page's children beneath it.
79
+ *
80
+ * A section with no pages cannot appear, because `bun run check:docs` refuses
81
+ * to generate one. Children are alphabetical, because nesting exists for the
82
+ * per-framework installation pages and a reader picking their framework out of
83
+ * a list is looking one up rather than reading a sequence, and then the
84
+ * `trailing` ones, which are the pages that answer "none of the above" and
85
+ * which the alphabet was interleaving with the answers they follow from.
86
+ */
87
+ export declare const navigation: () => DocNavSection[];
88
+ /** Path to a page's MDX, relative to the package root. */
89
+ export declare const contentPath: (page: DocPage) => string;