@motion-proto/live-tokens 0.82.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/live-tokens-create-component/SKILL.md +16 -4
- package/.claude/skills/live-tokens-create-page/SKILL.md +19 -17
- package/.claude/skills/live-tokens-create-page/references/interaction-sources.md +3 -3
- package/.claude/skills/live-tokens-pick-component/SKILL.md +10 -83
- package/CHANGELOG.md +84 -0
- package/README.md +1 -1
- package/bin/cli.mjs +21 -8
- package/bin/lib/catalogue.mjs +140 -41
- package/bin/rules/componentStructure.mjs +12 -4
- package/dist-plugin/{chunk-REBHE3ZM.js → chunk-FD4FVDOC.js} +59 -9
- package/dist-plugin/{chunk-D4WRIKEZ.js → chunk-M6BWSS6C.js} +1 -1
- package/dist-plugin/{chunk-RFVYPNRO.js → chunk-V2OVSC5Z.js} +22 -1
- package/dist-plugin/index.cjs +85 -14
- package/dist-plugin/index.js +3 -3
- package/dist-plugin/migrateData/index.cjs +59 -9
- package/dist-plugin/migrateData/index.js +2 -2
- package/dist-plugin/setColors/index.cjs +59 -9
- package/dist-plugin/setColors/index.js +1 -1
- package/dist-plugin/setGeometry/index.cjs +63 -11
- package/dist-plugin/setGeometry/index.d.cts +1 -1
- package/dist-plugin/setGeometry/index.d.ts +1 -1
- package/dist-plugin/setGeometry/index.js +5 -3
- package/dist-plugin/tokensCssMigrations/index.cjs +22 -1
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +3 -1
- package/src/editor/component-editor/DialogEditor.svelte +2 -1
- package/src/editor/component-editor/ImageLightboxEditor.svelte +2 -1
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +4 -1
- package/src/editor/component-editor/scaffolding/types.ts +13 -8
- package/src/editor/core/components/aliasKinds.ts +5 -1
- package/src/editor/core/sketch/sketchLayer.ts +1 -1
- package/src/editor/core/themes/migrations/2026-09-20-imagelightbox-scrim.ts +28 -0
- package/src/editor/core/themes/migrations/2026-09-20-scrim-color-and-opacity.ts +45 -0
- package/src/editor/core/themes/migrations/index.ts +4 -0
- package/src/editor/docs/content/creating-components.md +3 -0
- package/src/editor/docs/content.generated.ts +1 -1
- package/src/editor/skill-atlas/SkillAtlas.svelte +65 -38
- package/src/editor/skill-atlas/SkillValue.svelte +202 -0
- package/src/editor/skill-atlas/evalResults.ts +46 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +4 -4
- package/src/editor/skill-atlas/trees/create-component.ts +18 -18
- package/src/editor/skill-atlas/trees/create-page.ts +28 -32
- package/src/editor/skill-atlas/trees/pick-component.ts +66 -196
- package/src/editor/ui/UIVariantSelector.svelte +1 -1
- package/src/editor/ui/variantScales.ts +11 -0
- package/src/live-tokens/data/themes/autumn.json +6 -4
- package/src/live-tokens/data/themes/halloween.json +6 -4
- package/src/live-tokens/data/themes/midnight-study.json +6 -4
- package/src/live-tokens/data/themes/ocean.json +6 -4
- package/src/live-tokens/data/themes/royal-velvet.json +6 -4
- package/src/live-tokens/data/themes/sketchy.json +6 -4
- package/src/live-tokens/data/themes/spring-meadow.json +6 -4
- package/src/live-tokens/data/themes/sunset.json +6 -4
- package/src/system/components/Badge.svelte +5 -2
- package/src/system/components/Button.svelte +10 -2
- package/src/system/components/Callout.svelte +5 -2
- package/src/system/components/Card.svelte +8 -3
- package/src/system/components/CodeSnippet.svelte +6 -2
- package/src/system/components/CollapsibleSection.svelte +6 -2
- package/src/system/components/CornerBadge.svelte +5 -2
- package/src/system/components/Dialog.svelte +12 -4
- package/src/system/components/IconButton.svelte +5 -2
- package/src/system/components/Image.svelte +5 -2
- package/src/system/components/ImageLightbox.svelte +44 -4
- package/src/system/components/InlineEditActions.svelte +5 -2
- package/src/system/components/Input.svelte +6 -2
- package/src/system/components/MenuSelect.svelte +9 -2
- package/src/system/components/Notification.svelte +5 -2
- package/src/system/components/Panel.svelte +5 -2
- package/src/system/components/ProgressBar.svelte +5 -2
- package/src/system/components/RadioButton.svelte +5 -2
- package/src/system/components/SectionDivider.svelte +5 -2
- package/src/system/components/SegmentedControl.svelte +5 -2
- package/src/system/components/SideNavigation.svelte +5 -2
- package/src/system/components/Slider.svelte +6 -2
- package/src/system/components/TabBar.svelte +5 -2
- package/src/system/components/Table.svelte +4 -2
- package/src/system/components/Toggle.svelte +5 -2
- package/src/system/components/Tooltip.svelte +5 -2
- package/src/system/styles/tokens.css +12 -4
- package/src/testing-js/{chunk-ZMZQZ33J.js → chunk-WQJ6QB6Y.js} +8 -4
- package/src/testing-js/chunk-WQJ6QB6Y.js.map +1 -0
- package/src/testing-js/{chunk-Q3YIAAG3.js → chunk-XXABJNUU.js} +2 -2
- package/src/testing-js/component-behavior.contract.js +1 -1
- package/src/testing-js/component-editor.contract.js +1 -1
- package/src/testing-js/component-render.contract.js +1 -1
- package/src/testing-js/index.js +2 -2
- package/src/testing-js/page-compliance.contract.js +1 -1
- package/src/testing-js/vitest.js +2 -2
- package/src/testing-js/chunk-ZMZQZ33J.js.map +0 -1
- /package/src/testing-js/{chunk-Q3YIAAG3.js.map → chunk-XXABJNUU.js.map} +0 -0
|
@@ -101,9 +101,9 @@ Name a role as the shipped component that paints the same thing names it. A fill
|
|
|
101
101
|
|
|
102
102
|
## Runtime component
|
|
103
103
|
|
|
104
|
-
Create `src/system/components/StatCard.svelte`.
|
|
104
|
+
Create `src/system/components/StatCard.svelte`. A component in another directory is listed by `components` and `report`, and checked by `check-component <id>`, when that directory is named in `"componentDirs"` in `live-tokens.config.json`. Use Svelte 5 props and snippets, semantic HTML, and the behavior the task requires.
|
|
105
105
|
|
|
106
|
-
Open the file with a `<script module lang="ts">` block that exports a `catalogue` entry in the shape every shipped component carries. `npx live-tokens components` prints it beside the id, and `components <id>` prints it with the props.
|
|
106
|
+
Open the file with a `<script module lang="ts">` block that exports a `catalogue` entry in the shape every shipped component carries. `npx live-tokens components` prints it beside the id, and `components <id>` prints it with the props. Every value is a literal: a string in single, double, or backtick quotes, an object of strings, or an array. No `${}` interpolation, no concatenation, no identifier reference. Each `whenNotToUse` row is `{ when, use? }`: `when` states the condition that rules this component out, and `use`, where a sibling covers it, names that sibling by its component id (`table`, never `Table`); `check-component` reports `missing-description` when no component has that id. An optional `constraints` array states each rule of use as one sentence. An optional `props` map adds one line per prop whose values carry a choice, such as `variant`; each key names a prop the file declares, and the text says what the values mean.
|
|
107
107
|
|
|
108
108
|
```svelte
|
|
109
109
|
<script module lang="ts">
|
|
@@ -111,12 +111,24 @@ Open the file with a `<script module lang="ts">` block that exports a `catalogue
|
|
|
111
111
|
|
|
112
112
|
export const catalogue = {
|
|
113
113
|
description: 'A figure with its label.',
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
whenToUse: 'one number the reader takes in at a glance.',
|
|
115
|
+
whenNotToUse: [
|
|
116
|
+
{ when: 'the reader scans and compares many records.', use: 'table' },
|
|
117
|
+
{ when: 'the figure belongs to a titled block of content.', use: 'card' },
|
|
118
|
+
],
|
|
119
|
+
constraints: ['The label names the figure in the words the page uses for it.'],
|
|
116
120
|
} satisfies CatalogueEntry;
|
|
117
121
|
</script>
|
|
118
122
|
```
|
|
119
123
|
|
|
124
|
+
An agent chooses by these sentences, so word them the way the shipped entries are worded:
|
|
125
|
+
|
|
126
|
+
- `description` is one sentence that says what the component is.
|
|
127
|
+
- `whenToUse` states the one condition that makes this component right.
|
|
128
|
+
- Each `when` states one condition that rules this component out. It reads correctly after "do not use <Name> when".
|
|
129
|
+
- A row takes a `use` when another component fits that condition, and no `use` when none does.
|
|
130
|
+
- When the nearest sibling is one of the project's own components, give that sibling a row that names the new component. An agent reaches a component by following `use`.
|
|
131
|
+
|
|
120
132
|
Declare every editable property in a literal `:global(:root)` block, each assigned a token. The plugin parses the Svelte source to seed `component-configs/<id>/default.json`, so the block holds plain declarations with no SCSS loop or interpolation.
|
|
121
133
|
|
|
122
134
|
```svelte
|
|
@@ -12,7 +12,7 @@ Assemble the page from the shipped components at their defaults and the theme's
|
|
|
12
12
|
1. Read the project first: the existing pages and where they live, how `App.svelte` wires routes, `--columns-count` in `tokens.css`, and the catalogue from `npx live-tokens components`.
|
|
13
13
|
2. Read the page top to bottom and name each section by its purpose. Take each section's column spans from the Page layouts table.
|
|
14
14
|
3. Build the page grid and place each section on it. Separate the sections with the smallest difference that separates them.
|
|
15
|
-
4. Give each section its container
|
|
15
|
+
4. Give each section its container. `npx live-tokens components <id>` prints a component's constraints.
|
|
16
16
|
5. Match a shipped component to each need. When two could fit, read **live-tokens-pick-component**. When nothing in the catalogue fits, read **live-tokens-create-component**.
|
|
17
17
|
6. Write the page CSS in design tokens.
|
|
18
18
|
7. Set the hierarchy: one text style per element, the shipped size on every control, one primary action, and one space step per position.
|
|
@@ -70,26 +70,28 @@ Show related items side by side when the width permits. A line of copy runs 45 t
|
|
|
70
70
|
|
|
71
71
|
`references/layout-sources.md` names the sources for these laws.
|
|
72
72
|
|
|
73
|
-
## Containers by purpose
|
|
74
|
-
|
|
75
|
-
- `Panel` is a stage: a canvas, a player, a preview. `minHeight` holds its height while what it shows changes.
|
|
76
|
-
- An empty stage shows a heading that names the condition and one `secondary` Button that fills it. An error goes in a `Callout variant="danger"`.
|
|
77
|
-
- `Card` is a titled block of content. Its `title` prop is the title, and the card's own tokens type it.
|
|
78
|
-
- A container in a tool UI labels itself: `Card variant="bare"` with the label in the body as `--body-sm-*` in `--text-secondary`.
|
|
79
|
-
- A form puts the essential fields first and the secondary fields in a `CollapsibleSection`. Its actions sit on the bottom edge.
|
|
80
|
-
- A row of fields is a flex row with `gap: var(--space-20)`. Each field's wrapper takes `flex: 1`.
|
|
81
|
-
- A toolbar is a flex row of Buttons on the section's bottom edge, with no container around it. Group the Buttons left and right with `justify-content: space-between`, the primary last. A `danger` Button sits apart from the group it could be mistaken for.
|
|
82
|
-
- A vertical stack of Buttons sets `fullWidth` on each Button. A row omits it.
|
|
83
|
-
- `MenuSelect` renders its list open. For a picker, toggle it from a Button with a trailing chevron (`icon="fa-solid fa-chevron-down" iconPosition="right"`) and position the list under the Button at `top: 100%` with a `--space-*` margin.
|
|
84
|
-
|
|
85
73
|
## Components
|
|
86
74
|
|
|
87
75
|
- Use a shipped component when one fits. Import it from `@motion-proto/live-tokens/components/<Name>.svelte`.
|
|
88
|
-
- `npx live-tokens components <id>` prints
|
|
76
|
+
- `npx live-tokens components <id>` prints a component's declared props, the values each union accepts, and its catalogue entry, including its constraints and when not to use it. `--json` prints the same as data. The list includes the project's own components.
|
|
89
77
|
- Pass only the props a component declares.
|
|
90
78
|
- A shipped component fills its parent. To size one, size the element the page wraps it in.
|
|
91
79
|
- A native element with no chrome of its own needs no component: an `<input type="file">` behind a Button, a `<canvas>`, an `<img>` inside a stage.
|
|
92
80
|
- Text inside a `Card` or a `CollapsibleSection` takes the container's type on nested `p`, `ul`, `ol`, and `li`. When the page owns that type, as full-bleed media does, pass `prose={false}`.
|
|
81
|
+
- An empty stage shows a heading that names the condition and one `secondary` Button that fills it. An error goes in a `Callout variant="danger"`.
|
|
82
|
+
- A container in a tool UI labels itself: `Card variant="bare"` with the label in the body as `--body-sm-*` in `--text-secondary`.
|
|
83
|
+
- A row of fields is a flex row with `gap: var(--space-20)`; each field's wrapper takes `flex: 1`.
|
|
84
|
+
- A toolbar is a flex row of Buttons on the section's bottom edge, with no container around it. Group the Buttons left and right with `justify-content: space-between`. A `danger` Button sits apart from the group it could be mistaken for. A vertical stack of Buttons sets `fullWidth` on each Button; a row omits it.
|
|
85
|
+
- For a `MenuSelect` picker, toggle it from a Button with a trailing chevron (`icon="fa-solid fa-chevron-down" iconPosition="right"`) and position the list under the Button at `top: 100%` with a `--space-*` margin.
|
|
86
|
+
|
|
87
|
+
## Rules the checker enforces
|
|
88
|
+
|
|
89
|
+
`check-page` fixes what it can and reports the rest by rule id.
|
|
90
|
+
|
|
91
|
+
- `multiple-primary`
|
|
92
|
+
- `danger-without-dialog`
|
|
93
|
+
- `control-size`
|
|
94
|
+
- `native-control`
|
|
93
95
|
|
|
94
96
|
## Tokens
|
|
95
97
|
|
|
@@ -119,11 +121,11 @@ Use the semantic element for each place: one `h1`, an `h2` for each section, `h3
|
|
|
119
121
|
|
|
120
122
|
### Size
|
|
121
123
|
|
|
122
|
-
Omit `size` on every control and container.
|
|
124
|
+
Omit `size` on every control and container. `check-page` reports a `size` attribute on a shipped component as `control-size`.
|
|
123
125
|
|
|
124
126
|
### Emphasis
|
|
125
127
|
|
|
126
|
-
|
|
128
|
+
`npx live-tokens components button` names each `variant` and the role it carries.
|
|
127
129
|
|
|
128
130
|
In a row of actions the primary sits last, on the right. Up to four actions are individual Buttons. Five or more collapse into a `MenuSelect` behind one Button.
|
|
129
131
|
|
|
@@ -178,7 +180,7 @@ The checkers cannot see a layout. Open the page at the width it is built for and
|
|
|
178
180
|
- The containers in a section align at the bottom.
|
|
179
181
|
- The actions sit where the eye goes last, with the one primary at the end.
|
|
180
182
|
- Every row of actions holds an action that leaves without committing.
|
|
181
|
-
-
|
|
183
|
+
- Every action that destroys saved work meets the Button constraint `danger-without-dialog`.
|
|
182
184
|
- An action that runs longer than a moment shows progress in a `ProgressBar` or a `Notification`.
|
|
183
185
|
- Every field has a default, and Reset restores it.
|
|
184
186
|
- Secondary settings sit in a `CollapsibleSection`. Every control is in view.
|
|
@@ -14,9 +14,9 @@ to keep a later edit from reopening it.
|
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| Visibility of system status | Page | Verify: an action that runs longer than a moment shows progress in a `ProgressBar` or a `Notification`. |
|
|
16
16
|
| Match between system and the real world | Page | Verify: labels use the user's words. |
|
|
17
|
-
| User control and freedom | Page | Verify: every row of actions holds an action that leaves without committing. Emphasis:
|
|
17
|
+
| User control and freedom | Page | Verify: every row of actions holds an action that leaves without committing. Emphasis: `multiple-primary`. |
|
|
18
18
|
| Consistency and standards | Page | One size, one primary action, one text style per element. The components carry the rest. |
|
|
19
|
-
| Error prevention | Page | Verify:
|
|
19
|
+
| Error prevention | Page | Verify: `danger-without-dialog`. Verify: every field has a default and Reset restores it. |
|
|
20
20
|
| Recognition rather than recall | Component | `MenuSelect` lists the options; `Input` carries its label and hint; `Tooltip` defines in place. |
|
|
21
21
|
| Flexibility and efficiency of use | Product | Shortcuts and customisation are product decisions. |
|
|
22
22
|
| Aesthetic and minimalist design | Page | Layout: each element serves a purpose no other element serves. Verify: secondary settings sit in a `CollapsibleSection`. |
|
|
@@ -40,7 +40,7 @@ control. Undo, version history, and autosave are the product's.
|
|
|
40
40
|
| Discoverability | Page | Verify: every control is in view. |
|
|
41
41
|
| Explorable interfaces | Page | Verify: every row of actions holds an action that leaves without committing. |
|
|
42
42
|
| Fitts's law | Component | The shipped default is the large target; SKILL.md's one-size rule keeps it. A toolbar sits on the section's bottom edge. |
|
|
43
|
-
| Protect users' work | Page | Verify:
|
|
43
|
+
| Protect users' work | Page | Verify: `danger-without-dialog`. |
|
|
44
44
|
| Readability | Component | live-tokens-set-colors gates every text pair at WCAG AA. |
|
|
45
45
|
| Simplicity | Page | Verify: secondary settings sit in a `CollapsibleSection`; no capability is removed for the sake of simplicity. |
|
|
46
46
|
| Visible navigation | Component | `SideNavigation` follows the current path. |
|
|
@@ -1,96 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: live-tokens-pick-component
|
|
3
|
-
description: Recommend which shipped @motion-proto/live-tokens component fits a UX need,
|
|
3
|
+
description: Recommend which shipped @motion-proto/live-tokens component fits a UX need, by reading each component's catalogue entry. Called by live-tokens-create-page when more than one component could fit, and by live-tokens-create-component before it authors anything. Use when the user asks which component to use, or what the difference between two components is. Use when the user asks how to show or capture a UX outcome. Edits no file. For size, emphasis, or placement, read live-tokens-create-page. When the catalogue lacks a component with chrome, read live-tokens-create-component.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Picking a live-tokens component
|
|
7
7
|
|
|
8
|
-
When more than one
|
|
8
|
+
When more than one component could fit, read the catalogue entries. Each entry states the condition that makes the component right and the conditions that rule it out.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Procedure
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- The pair that confirms or cancels an inline edit: `InlineEditActions`.
|
|
19
|
-
|
|
20
|
-
## Single-selection family
|
|
21
|
-
|
|
22
|
-
Four components pick one option from a set. The test is the option count, whether the selection swaps the content below, and how much the choice asks of the reader.
|
|
23
|
-
|
|
24
|
-
| Component | Test | Option count |
|
|
25
|
-
|---|---|---|
|
|
26
|
-
| `SegmentedControl` | An inline switch between views of the same data. It sits in a row of controls. | 2 to 4 |
|
|
27
|
-
| `TabBar` | The content area below swaps. | 2 to 7 |
|
|
28
|
-
| `RadioButton` | The reader reads every option as text inside a larger form. | any |
|
|
29
|
-
| `MenuSelect` | The options would overflow a row. | any |
|
|
30
|
-
|
|
31
|
-
- When a label would wrap in a `SegmentedControl`, use `RadioButton` rows.
|
|
32
|
-
- The URL changes: `SideNavigation`. Sections inside one page: `TabBar`.
|
|
33
|
-
|
|
34
|
-
## Text entry
|
|
35
|
-
|
|
36
|
-
The test is whether the answer comes from a predefined list of options.
|
|
37
|
-
|
|
38
|
-
- A predefined list (a status, a currency, a size, a country): the single-selection family, by its own test. Up to four options sit in a row; more go in a `MenuSelect`, which scrolls.
|
|
39
|
-
- No list (a name, a search string, an amount, a message, a street address): `Input`. Validation keeps a typed answer well-formed.
|
|
40
|
-
- A long list the reader would rather filter by typing (a city): no shipped component filters a list. Use `Input` with validation, or author a filtering select with **live-tokens-create-component**.
|
|
41
|
-
- A number where the position on a track carries the meaning (a volume, a price range, a percentage): `Slider`. A number the reader knows and would rather type: `Input` with `type="number"`.
|
|
42
|
-
|
|
43
|
-
## On and off
|
|
44
|
-
|
|
45
|
-
Three components express a binary choice. The test is whether the two states have names of their own.
|
|
46
|
-
|
|
47
|
-
| Component | Test |
|
|
48
|
-
|---|---|
|
|
49
|
-
| `Toggle` | A setting that takes effect at once. The label names the setting. The switch position is the state. |
|
|
50
|
-
| `SegmentedControl` | Two named alternatives the reader compares (Light / Dark, List / Grid). Both labels show at once. |
|
|
51
|
-
| `RadioButton` pair | A yes or no the reader answers inside a larger form. |
|
|
52
|
-
|
|
53
|
-
When the two states share the feature's one name, use `Toggle`. "Email notifications" has no "off" label.
|
|
54
|
-
|
|
55
|
-
## Container family
|
|
56
|
-
|
|
57
|
-
Four components hold a block of content. The test is what the block is to the reader: one item, a section of the page, secondary content that stays collapsed until opened, or a decision.
|
|
58
|
-
|
|
59
|
-
| Component | Modality | Test |
|
|
60
|
-
|---|---|---|
|
|
61
|
-
| `Card` | Inline, always open | One item, or each item in a set: a product, a record, a plan. It has a title and can react to hover. |
|
|
62
|
-
| `Panel` | Inline, always open | One section of the page's content in a frame: a stage, a list, a form, a block of copy. `minHeight` holds its height while the content changes. |
|
|
63
|
-
| `CollapsibleSection` | Inline, collapsed until the reader opens it | Secondary content most readers skip. |
|
|
64
|
-
| `Dialog` | Modal, blocks the page | A decision the page cannot continue without: a destructive confirmation, payment, sign-in. |
|
|
65
|
-
|
|
66
|
-
A set of items is one `Card` per item. A routine form goes inline in a `Panel`.
|
|
67
|
-
|
|
68
|
-
## Messaging family
|
|
69
|
-
|
|
70
|
-
Five components carry a message. The test is what the message is about, what brings it on, and whether the reader dismisses it.
|
|
71
|
-
|
|
72
|
-
| Component | Scope | Trigger | Dismissable | Test |
|
|
73
|
-
|---|---|---|---|---|
|
|
74
|
-
| `Callout` | A section | Always present | No | Something the reader must know about the content around it |
|
|
75
|
-
| `Notification` | The system | An action or event | Yes | Feedback about something that just happened |
|
|
76
|
-
| `Tooltip` | An element | Hover or focus | On leave | A definition or hint the reader can do without |
|
|
77
|
-
| `Badge` | An element | Always present | No | A standing label read at a glance ("Beta", "New", "v2") |
|
|
78
|
-
| `CornerBadge` | A parent's corner | Always present | No | A count or status marker on the thing it describes |
|
|
79
|
-
|
|
80
|
-
`Badge` and `CornerBadge` differ in position only.
|
|
81
|
-
|
|
82
|
-
## Display family
|
|
83
|
-
|
|
84
|
-
Each pair holds a block the reader views and one the reader interacts with. The test is which of the two the page needs.
|
|
85
|
-
|
|
86
|
-
- A picture the page shows: `Image`. A picture whose detail the reader must open, or a gallery: `ImageLightbox`.
|
|
87
|
-
- Records the reader scans and compares: `Table`. A set of items the reader acts on: one `Card` per item.
|
|
88
|
-
- A read-out of progress: `ProgressBar`. A number the reader sets: `Slider`.
|
|
89
|
-
- Text the reader runs or pastes (an install command, a key, an id): `CodeSnippet`. Prose the reader only reads: a paragraph in its `Card` or `Panel`.
|
|
90
|
-
- A titled break between the sections of one page: `SectionDivider`. Movement between pages: `SideNavigation`.
|
|
12
|
+
1. Run `npx live-tokens components --json`. The list carries the catalogue entry of every shipped component and of each of the project's own components.
|
|
13
|
+
2. Read each plausible candidate's `whenToUse`, `whenNotToUse`, and `constraints`.
|
|
14
|
+
3. Drop a candidate whose `whenNotToUse` names a condition the requirement meets.
|
|
15
|
+
4. When a dropped row names a `use`, weigh that component the same way.
|
|
16
|
+
5. Choose the surviving candidate whose `whenToUse` condition the requirement meets.
|
|
17
|
+
6. When no candidate fits, follow "Nothing fits".
|
|
91
18
|
|
|
92
19
|
## Nothing fits
|
|
93
20
|
|
|
94
|
-
A native element with no chrome of its own needs no component: an `<input type="file">` behind a Button, a `<canvas>`, an `<img>` inside a stage. When nothing in the catalogue fits a piece with chrome
|
|
21
|
+
A native element with no chrome of its own needs no component: an `<input type="file">` behind a Button, a `<canvas>`, an `<img>` inside a stage. When nothing in the catalogue fits a piece with chrome, such as a `DatePicker`, a search-filtered list, or a `Stepper`, author the component with **live-tokens-create-component**. No shipped component filters a list by typing, so a searchable picker takes this same route. Size, emphasis, and placement are **live-tokens-create-page**'s.
|
|
95
22
|
|
|
96
23
|
`npx live-tokens components <id>` prints one component's catalogue entry, its declared props, and the values each union accepts. `--json` returns the same as data.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.84.0 — A scrim's colour and strength are separate tokens
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`--scrim-color` and `--scrim-opacity-low`, `--scrim-opacity`,
|
|
8
|
+
`--scrim-opacity-high`.** A theme holds one and moves the other: a light
|
|
9
|
+
`--scrim-color` gives a scrim that pales the page. The three `--scrim-*` stops
|
|
10
|
+
remain, composed from the two. The additive tokens-css migration
|
|
11
|
+
`2026-09-20-scrim-color-and-opacity` inserts the four names into an existing
|
|
12
|
+
`tokens.css`, and the dev plugin applies it on its own. The stops already in
|
|
13
|
+
that file keep their values; to have them follow `--scrim-color`, write each
|
|
14
|
+
as `color-mix(in srgb, var(--scrim-color) calc(var(--scrim-opacity) * 100%), transparent)`
|
|
15
|
+
with its own opacity stop.
|
|
16
|
+
|
|
17
|
+
- **The editor picks an opacity from its stops.** A property whose name ends in
|
|
18
|
+
`-opacity` takes a picker of the three scrim strengths, low, medium, and high.
|
|
19
|
+
The suffix used to fall under the fill rule, which offered a palette and wrote
|
|
20
|
+
a colour into a number.
|
|
21
|
+
|
|
22
|
+
### Changed (breaking)
|
|
23
|
+
|
|
24
|
+
- **`--dialog-scrim-surface` and `--imagelightbox-scrim-surface` each split into
|
|
25
|
+
`-scrim-color` and `-scrim-opacity`.** Each screen mixes the pair where it
|
|
26
|
+
paints. ImageLightbox's `scrim` prop writes the colour and `scrimOpacity`
|
|
27
|
+
writes the strength, and the opacity now stands on its own. Migration
|
|
28
|
+
`2026-09-20-scrim-color-and-opacity` splits the old key in a saved component
|
|
29
|
+
config: a `--scrim-*` stop becomes that stop's opacity with `--scrim-color`,
|
|
30
|
+
and a hand-written fill becomes the colour at full strength. A project whose
|
|
31
|
+
`tokens.css` gave the scrim stops their own colour sets `--scrim-color` to it,
|
|
32
|
+
since Dialog and ImageLightbox now read that token.
|
|
33
|
+
|
|
34
|
+
## 0.83.0 — The catalogue entry names what rules a component out
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **`ImageLightbox` takes per-instance tile and scrim props.** Any CSS fill, written into
|
|
39
|
+
`--imagelightbox-tile-surface` for that one tile, the way `fit` writes
|
|
40
|
+
`--imagelightbox-tile-object-fit`. A theme's value still governs every tile
|
|
41
|
+
that passes nothing, and the default stays transparent. Art with transparency
|
|
42
|
+
wants a ground: without one the page shows through the art's own gaps while a
|
|
43
|
+
`box` shadow still casts from the rectangle around them. The open stage is
|
|
44
|
+
unaffected — it sits on the scrim and paints no tile fill.
|
|
45
|
+
|
|
46
|
+
`border` and `borderWidth` do the same for `--imagelightbox-tile-border` and
|
|
47
|
+
`--imagelightbox-tile-border-width`. Both are needed where a theme leaves the
|
|
48
|
+
width at zero, since a colour alone paints nothing.
|
|
49
|
+
|
|
50
|
+
`scrim` and `scrimOpacity` set the layer behind the open image, writing
|
|
51
|
+
`--imagelightbox-scrim-surface`. An opacity is mixed into the colour, so
|
|
52
|
+
`scrimOpacity` needs `scrim` to have something to mix. This is the one
|
|
53
|
+
reachable way to set it per page: the modal portals to `<body>`, out of reach
|
|
54
|
+
of any wrapper around the tile.
|
|
55
|
+
|
|
56
|
+
- **`CatalogueEntry` carries `constraints`.** `constraints` is an optional
|
|
57
|
+
`string[]` of rules of use, one sentence each. `components <id>` prints one
|
|
58
|
+
`Not for:` line per `whenNotToUse` row and one `Rule:` line per constraint.
|
|
59
|
+
|
|
60
|
+
- **Skill Atlas: a "Measured value" tab.** It reports an eval of twelve component
|
|
61
|
+
choices, run with the skills and without. Both arms chose correctly; the
|
|
62
|
+
skills cut the turns and the time. `#measured-value` links to it.
|
|
63
|
+
|
|
64
|
+
### Changed (breaking)
|
|
65
|
+
|
|
66
|
+
- **`--imagelightbox-overlay-surface` is `--imagelightbox-scrim-surface`, and it
|
|
67
|
+
reads `--scrim-high`.** Dialog already called the layer that dims the page a
|
|
68
|
+
scrim and read the `--scrim-*` scale; ImageLightbox called it an overlay and
|
|
69
|
+
mixed its own colour, so a theme could not move both with one value. The
|
|
70
|
+
editor row reads "scrim color". Migration `2026-09-20-imagelightbox-scrim`
|
|
71
|
+
renames the key; the shipped presets carry the new one.
|
|
72
|
+
|
|
73
|
+
- **`CatalogueEntry` requires `description`, `whenToUse`, and `whenNotToUse`.**
|
|
74
|
+
`notFor` is gone. `whenToUse` is a required string: the condition that makes
|
|
75
|
+
the component the right choice. `whenNotToUse` is a required array of
|
|
76
|
+
`{ when, use? }` rows,
|
|
77
|
+
each naming a condition that rules the component out and, where a sibling
|
|
78
|
+
fits instead, that sibling's component id. A component with no disqualifying
|
|
79
|
+
condition declares `whenNotToUse: []`. `constraints` is an optional
|
|
80
|
+
`string[]`. A runtime's `catalogue` export, and any consumer's, must supply
|
|
81
|
+
all three required fields; `missing-description` reports any as missing, or a
|
|
82
|
+
`whenNotToUse` row's `use` that names no component.
|
|
83
|
+
- **`components` and `components --json` drop `tokens` from the list form.**
|
|
84
|
+
The id form (`components <id>`) is unchanged. The `--json` list drops from
|
|
85
|
+
~179 KB to ~37 KB across the 26 shipped components.
|
|
86
|
+
|
|
3
87
|
## 0.82.0 — The demo ships from the package
|
|
4
88
|
|
|
5
89
|
### Added
|
package/README.md
CHANGED
|
@@ -366,7 +366,7 @@ A project created with `create` runs the forced form from `postinstall` and giti
|
|
|
366
366
|
|
|
367
367
|
Ask "TabBar or SegmentedControl?", "how do I let someone pick one of four options?", or "what is the difference between a Callout and a Notification?".
|
|
368
368
|
|
|
369
|
-
The skill
|
|
369
|
+
The skill runs `npx live-tokens components --json` and reads each candidate's catalogue entry: the condition that makes it the right component, the conditions that rule it out with the component to use in its place, and its rules of use. The entries hold the decision, so a component you author joins the same query. It answers the question and writes nothing. Read it before authoring anything new.
|
|
370
370
|
|
|
371
371
|
### `live-tokens-create-page`
|
|
372
372
|
|
package/bin/cli.mjs
CHANGED
|
@@ -21,7 +21,13 @@ import process from 'node:process';
|
|
|
21
21
|
import { COMPONENT_RULES, checkComponent, discoverComponents, formatReport } from './check-component.mjs';
|
|
22
22
|
import { PAGE_RULES, checkPages, discoverPages } from './check-page.mjs';
|
|
23
23
|
import { resolvePageTestTargets } from './lib/pageRoutes.mjs';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
describeComponents,
|
|
26
|
+
describeTokens,
|
|
27
|
+
formatComponents,
|
|
28
|
+
formatTokens,
|
|
29
|
+
withoutTokens,
|
|
30
|
+
} from './lib/catalogue.mjs';
|
|
25
31
|
import { applyFixes } from './lib/fixers.mjs';
|
|
26
32
|
import { buildReport, formatReport as formatProjectReport } from './lib/report.mjs';
|
|
27
33
|
import { loadVocabulary } from './lib/tokenVocabulary.mjs';
|
|
@@ -58,11 +64,14 @@ Commands:
|
|
|
58
64
|
create <dir> [--force] Scaffold a new Svelte + Vite app wired up with
|
|
59
65
|
live-tokens (editor, components, design tokens)
|
|
60
66
|
setup-claude [--force] Install bundled Claude Code skills into ./.claude/skills/
|
|
61
|
-
components [id] [--json]
|
|
67
|
+
components [id] [--json]
|
|
68
|
+
List every component the project has, shipped and
|
|
62
69
|
its own (src/system/components plus any
|
|
63
|
-
"componentDirs" in live-tokens.config.json)
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
"componentDirs" in live-tokens.config.json), each
|
|
71
|
+
with its catalogue entry (description, whenToUse,
|
|
72
|
+
whenNotToUse, constraints). The list form omits
|
|
73
|
+
tokens. With an id, that component's
|
|
74
|
+
props, variants, tokens, and defaults
|
|
66
75
|
tokens [--scale <name>] [--json]
|
|
67
76
|
List every design token the project's tokens.css
|
|
68
77
|
declares, by scale, with its value
|
|
@@ -265,10 +274,14 @@ function formatFixes({ applied, skipped }) {
|
|
|
265
274
|
|
|
266
275
|
if (command === 'components') {
|
|
267
276
|
const opts = parseCheckFlags(rest);
|
|
268
|
-
const list = describeComponents(loadVocabulary());
|
|
269
277
|
const id = opts.rest[0];
|
|
270
|
-
|
|
271
|
-
|
|
278
|
+
const list = describeComponents(loadVocabulary());
|
|
279
|
+
if (id) {
|
|
280
|
+
if (!list.some((c) => c.id === id)) fail(formatComponents(list, { id }));
|
|
281
|
+
writeOut(opts.json ? JSON.stringify(list.find((c) => c.id === id), null, 2) : formatComponents(list, { id }));
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
284
|
+
writeOut(opts.json ? JSON.stringify(withoutTokens(list), null, 2) : formatComponents(list, {}));
|
|
272
285
|
process.exit(0);
|
|
273
286
|
}
|
|
274
287
|
|
package/bin/lib/catalogue.mjs
CHANGED
|
@@ -47,9 +47,11 @@ function literalValue(raw) {
|
|
|
47
47
|
return inner.replace(/\s+/g, ' ').trim();
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// Brace matching skips over quoted literals so a
|
|
51
|
-
// (or a description containing a stray brace) never closes the
|
|
52
|
-
|
|
50
|
+
// Brace/bracket matching skips over quoted literals so a closer inside a
|
|
51
|
+
// description (or a description containing a stray brace) never closes the
|
|
52
|
+
// group early. Shared by object literals (`{`/`}`) and array literals
|
|
53
|
+
// (`[`/`]`).
|
|
54
|
+
function findBalanced(text, openIndex, open = '{', close = '}') {
|
|
53
55
|
let depth = 0;
|
|
54
56
|
for (let i = openIndex; i < text.length; i++) {
|
|
55
57
|
const ch = text[i];
|
|
@@ -59,8 +61,8 @@ function findBalanced(text, openIndex) {
|
|
|
59
61
|
while (i < text.length && text[i] !== quote) i += text[i] === '\\' ? 2 : 1;
|
|
60
62
|
continue;
|
|
61
63
|
}
|
|
62
|
-
if (ch ===
|
|
63
|
-
else if (ch ===
|
|
64
|
+
if (ch === open) depth++;
|
|
65
|
+
else if (ch === close) {
|
|
64
66
|
depth--;
|
|
65
67
|
if (depth === 0) return { content: text.slice(openIndex + 1, i), end: i + 1 };
|
|
66
68
|
}
|
|
@@ -68,26 +70,127 @@ function findBalanced(text, openIndex) {
|
|
|
68
70
|
return null;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
function
|
|
73
|
+
function skipSeparators(text, i) {
|
|
74
|
+
while (i < text.length && /[\s,]/.test(text[i])) i++;
|
|
75
|
+
return i;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The comma that ends the expression starting at `i`. A key or a literal
|
|
79
|
+
// inside an expression the reader cannot read must stay unread with it.
|
|
80
|
+
function expressionEnd(text, i) {
|
|
81
|
+
let depth = 0;
|
|
82
|
+
for (; i < text.length; i++) {
|
|
83
|
+
const ch = text[i];
|
|
84
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
85
|
+
const quote = ch;
|
|
86
|
+
i++;
|
|
87
|
+
while (i < text.length && text[i] !== quote) i += text[i] === '\\' ? 2 : 1;
|
|
88
|
+
} else if ('([{'.includes(ch)) depth++;
|
|
89
|
+
else if (')]}'.includes(ch)) depth--;
|
|
90
|
+
else if (ch === ',' && depth === 0) return i;
|
|
91
|
+
}
|
|
92
|
+
return text.length;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function readWholeExpression(text, i) {
|
|
96
|
+
const end = expressionEnd(text, i);
|
|
97
|
+
const expression = text.slice(i, end);
|
|
98
|
+
const result = readValue(expression, 0);
|
|
99
|
+
const literal = result && !expression.slice(result.end).trim();
|
|
100
|
+
return { value: literal ? result.value : undefined, end };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* One literal value starting at `text[i]`: a quoted string, a `[...]` array of
|
|
105
|
+
* values, or a `{...}` object of `key: value` pairs — the subset `catalogueOf`
|
|
106
|
+
* reads, recursively, so a key nested inside a `whenNotToUse` row, `props`, or a
|
|
107
|
+
* `{ rule, text }` constraint is read at its own depth and never mistaken for
|
|
108
|
+
* a top-level field. An identifier, a template with `${}`, a concatenation,
|
|
109
|
+
* or any other expression is outside the subset and reads as absent (`null`),
|
|
110
|
+
* the same tolerance `catalogueOf` always gave a non-literal field.
|
|
111
|
+
*/
|
|
112
|
+
function readValue(text, i) {
|
|
113
|
+
i = skipSeparators(text, i);
|
|
114
|
+
const ch = text[i];
|
|
115
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
116
|
+
const m = STRING_LITERAL.exec(text.slice(i));
|
|
117
|
+
if (!m || m.index !== 0) return null;
|
|
118
|
+
const value = literalValue(m[0]);
|
|
119
|
+
return value === undefined ? null : { value, end: i + m[0].length };
|
|
120
|
+
}
|
|
121
|
+
if (ch === '[') {
|
|
122
|
+
const balanced = findBalanced(text, i, '[', ']');
|
|
123
|
+
if (!balanced) return null;
|
|
124
|
+
const items = [];
|
|
125
|
+
let j = 0;
|
|
126
|
+
while (j < balanced.content.length) {
|
|
127
|
+
const item = readWholeExpression(balanced.content, j);
|
|
128
|
+
if (item.value !== undefined) items.push(item.value);
|
|
129
|
+
j = item.end + 1;
|
|
130
|
+
}
|
|
131
|
+
return { value: items, end: balanced.end };
|
|
132
|
+
}
|
|
133
|
+
if (ch === '{') {
|
|
134
|
+
const balanced = findBalanced(text, i, '{', '}');
|
|
135
|
+
if (!balanced) return null;
|
|
136
|
+
return { value: readObject(balanced.content), end: balanced.end };
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** `key: value` pairs read by depth: the scan resumes after each value's
|
|
142
|
+
* whole expression, read or unread, so a key inside a value's own text is
|
|
143
|
+
* never read as one of `body`'s own fields. */
|
|
144
|
+
function readObject(body) {
|
|
72
145
|
const fields = {};
|
|
73
|
-
const
|
|
146
|
+
const keyRe = /([A-Za-z_$][A-Za-z0-9_$]*)\s*:/g;
|
|
74
147
|
let m;
|
|
75
|
-
while ((m =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
148
|
+
while ((m = keyRe.exec(body))) {
|
|
149
|
+
const result = readWholeExpression(body, keyRe.lastIndex);
|
|
150
|
+
if (result.value !== undefined && !(m[1] in fields)) fields[m[1]] = result.value;
|
|
151
|
+
keyRe.lastIndex = result.end;
|
|
79
152
|
}
|
|
80
153
|
return fields;
|
|
81
154
|
}
|
|
82
155
|
|
|
156
|
+
function isPlainObject(v) {
|
|
157
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** An object's own string-valued entries; a non-string value is dropped, key
|
|
161
|
+
* by key, the same tolerance a top-level field gets. */
|
|
162
|
+
function stringEntries(v) {
|
|
163
|
+
if (!isPlainObject(v)) return undefined;
|
|
164
|
+
const out = {};
|
|
165
|
+
for (const [key, value] of Object.entries(v)) if (typeof value === 'string') out[key] = value;
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** `whenNotToUse` entries: a `{ when, use? }` object with `when` a string and
|
|
170
|
+
* `use`, if present, a string. A row whose `when` is not a string literal is
|
|
171
|
+
* dropped from the array. */
|
|
172
|
+
function whenNotToUseEntries(v) {
|
|
173
|
+
if (!Array.isArray(v)) return undefined;
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const item of v) {
|
|
176
|
+
if (isPlainObject(item) && typeof item.when === 'string') {
|
|
177
|
+
out.push(typeof item.use === 'string' ? { when: item.when, use: item.use } : { when: item.when });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function constraintEntries(v) {
|
|
184
|
+
if (!Array.isArray(v)) return undefined;
|
|
185
|
+
return v.filter((item) => typeof item === 'string');
|
|
186
|
+
}
|
|
187
|
+
|
|
83
188
|
/**
|
|
84
|
-
* Bounded, non-evaluating parse of the runtime file's `catalogue` export
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* concatenation is not a string literal, so it is silently absent rather
|
|
90
|
-
* than evaluated. Returns `null` when the file has no such export.
|
|
189
|
+
* Bounded, non-evaluating parse of the runtime file's `catalogue` export
|
|
190
|
+
* inside the `<script module>` block's `export const catalogue = { ... }`,
|
|
191
|
+
* the same way `builtInIds` and `componentProps` read the rest of the
|
|
192
|
+
* vocabulary without importing the module. Returns `null` when the file has
|
|
193
|
+
* no such export.
|
|
91
194
|
*/
|
|
92
195
|
export function catalogueOf(source) {
|
|
93
196
|
const moduleBlock = source.match(/<script\s+module[^>]*>([\s\S]*?)<\/script>/);
|
|
@@ -98,26 +201,16 @@ export function catalogueOf(source) {
|
|
|
98
201
|
const balanced = findBalanced(moduleBlock[1], openIndex);
|
|
99
202
|
if (!balanced) return null;
|
|
100
203
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const fields = parseFieldObject(body);
|
|
115
|
-
const catalogue = {
|
|
116
|
-
description: fields.description,
|
|
117
|
-
useFor: fields.useFor,
|
|
118
|
-
notFor: fields.notFor,
|
|
119
|
-
};
|
|
120
|
-
if (props) catalogue.props = props;
|
|
204
|
+
const fields = readObject(balanced.content);
|
|
205
|
+
const catalogue = {};
|
|
206
|
+
if (typeof fields.description === 'string') catalogue.description = fields.description;
|
|
207
|
+
if (typeof fields.whenToUse === 'string') catalogue.whenToUse = fields.whenToUse;
|
|
208
|
+
const whenNotToUse = whenNotToUseEntries(fields.whenNotToUse);
|
|
209
|
+
if (whenNotToUse) catalogue.whenNotToUse = whenNotToUse;
|
|
210
|
+
const constraints = constraintEntries(fields.constraints);
|
|
211
|
+
if (constraints && constraints.length) catalogue.constraints = constraints;
|
|
212
|
+
const props = stringEntries(fields.props);
|
|
213
|
+
if (props && Object.keys(props).length) catalogue.props = props;
|
|
121
214
|
return catalogue;
|
|
122
215
|
}
|
|
123
216
|
|
|
@@ -190,6 +283,11 @@ export function describeComponents(vocab, { root = process.cwd() } = {}) {
|
|
|
190
283
|
return out.sort((a, b) => a.origin.localeCompare(b.origin) || a.id.localeCompare(b.id));
|
|
191
284
|
}
|
|
192
285
|
|
|
286
|
+
/** The list form's payload. `tokens` is most of a full listing's bytes, and the id form keeps it. */
|
|
287
|
+
export function withoutTokens(list) {
|
|
288
|
+
return list.map(({ tokens, ...rest }) => rest);
|
|
289
|
+
}
|
|
290
|
+
|
|
193
291
|
export function describeTokens(vocab, { root = process.cwd() } = {}) {
|
|
194
292
|
return {
|
|
195
293
|
tokensCss: vocab.tokensCssPath ? relative(root, vocab.tokensCssPath) : null,
|
|
@@ -203,11 +301,12 @@ export function describeTokens(vocab, { root = process.cwd() } = {}) {
|
|
|
203
301
|
|
|
204
302
|
function describeLines(c) {
|
|
205
303
|
if (!c.catalogue) return [];
|
|
206
|
-
const { description,
|
|
304
|
+
const { description, whenToUse, whenNotToUse, constraints, props } = c.catalogue;
|
|
207
305
|
const lines = [];
|
|
208
306
|
if (description) lines.push(description);
|
|
209
|
-
if (
|
|
210
|
-
|
|
307
|
+
if (whenToUse) lines.push(`When to use: ${whenToUse}`);
|
|
308
|
+
for (const row of whenNotToUse ?? []) lines.push(row.use ? `Not for: ${row.when} Use ${row.use}.` : `Not for: ${row.when}`);
|
|
309
|
+
for (const constraint of constraints ?? []) lines.push(`Rule: ${constraint}`);
|
|
211
310
|
for (const [prop, text] of Object.entries(props ?? {})) lines.push(`${prop}: ${text}`);
|
|
212
311
|
return lines;
|
|
213
312
|
}
|