@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.
- package/LICENSE +21 -0
- package/content/accessibility.mdx +117 -0
- package/content/angular.mdx +60 -0
- package/content/browser-support.mdx +83 -0
- package/content/building-on-the-headless-tier.mdx +143 -0
- package/content/charting.mdx +93 -0
- package/content/coding-agents.mdx +139 -0
- package/content/color-modes.mdx +87 -0
- package/content/color.mdx +92 -0
- package/content/composition.mdx +171 -0
- package/content/elevation-and-material.mdx +93 -0
- package/content/events.mdx +125 -0
- package/content/forms.mdx +170 -0
- package/content/iconography.mdx +80 -0
- package/content/installation.mdx +125 -0
- package/content/intent-reference.mdx +107 -0
- package/content/introduction.mdx +69 -0
- package/content/localization.mdx +95 -0
- package/content/metrics.mdx +399 -0
- package/content/motion.mdx +91 -0
- package/content/other-frameworks.mdx +70 -0
- package/content/overlays.mdx +102 -0
- package/content/overriding-a-component.mdx +160 -0
- package/content/react.mdx +144 -0
- package/content/recipes.mdx +131 -0
- package/content/routing.mdx +208 -0
- package/content/sandbox.mdx +76 -0
- package/content/server-rendering.mdx +103 -0
- package/content/shape-and-borders.mdx +80 -0
- package/content/size-and-density.mdx +86 -0
- package/content/space-and-size.mdx +89 -0
- package/content/state-and-control.mdx +132 -0
- package/content/surfaces-and-separation.mdx +99 -0
- package/content/svelte.mdx +45 -0
- package/content/theme-builder.mdx +66 -0
- package/content/theming.mdx +111 -0
- package/content/tiers.mdx +159 -0
- package/content/tokens.mdx +88 -0
- package/content/typography.mdx +117 -0
- package/content/using-helix-with-an-agent.mdx +231 -0
- package/content/using-the-elements.mdx +125 -0
- package/content/versioning.mdx +72 -0
- package/content/visual-axes.mdx +75 -0
- package/content/vue.mdx +80 -0
- package/content/without-a-bundler.mdx +119 -0
- package/dist/catalog.d.ts +186 -0
- package/dist/catalog.js +296 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/naming.d.ts +14 -0
- package/dist/naming.js +28 -0
- package/dist/pages.generated.d.ts +7 -0
- package/dist/pages.generated.js +284 -0
- package/dist/reference.d.ts +105 -0
- package/dist/reference.js +181 -0
- package/dist/registry.d.ts +89 -0
- package/dist/registry.js +70 -0
- package/dist/sections.d.ts +44 -0
- package/dist/sections.js +36 -0
- package/dist/sources.generated.d.ts +6 -0
- package/dist/sources.generated.js +5158 -0
- 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;
|