@krapwoo/ds-viewer 0.14.2 → 0.16.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/README.md +249 -3
- package/config/index.ts +3 -0
- package/dist/cli/config.js +9 -1
- package/dist/cli/doctor.js +452 -19
- package/dist/cli/explain.js +26 -0
- package/dist/cli/staticPage.js +99 -10
- package/dist/cli/tokenDefinition.js +125 -0
- package/dist/native/catalog/catalogNavigation.js +27 -11
- package/dist/native/catalog/pageApi.js +557 -26
- package/dist/native/catalog/tokenDefinition.js +205 -0
- package/native/catalog/CatalogSearchInput.tsx +18 -3
- package/native/catalog/CatalogShell.tsx +318 -46
- package/native/catalog/CatalogSidebar.tsx +37 -1
- package/native/catalog/FamilyPage.tsx +567 -137
- package/native/catalog/GroupPage.tsx +230 -0
- package/native/catalog/JumpRow.tsx +81 -0
- package/native/catalog/ReferenceDetails.tsx +144 -16
- package/native/catalog/SectionBlock.tsx +49 -5
- package/native/catalog/SourceReferencePicker.tsx +180 -0
- package/native/catalog/StructuredTokenPage.tsx +592 -0
- package/native/catalog/Swatch.tsx +29 -2
- package/native/catalog/TokenLayouts.tsx +30 -9
- package/native/catalog/catalogNavigation.ts +30 -10
- package/native/catalog/catalogReferences.ts +212 -0
- package/native/catalog/familyModel.ts +10 -4
- package/native/catalog/index.ts +22 -0
- package/native/catalog/pageApi.ts +598 -23
- package/native/catalog/tokenDefinition.ts +430 -0
- package/native/catalog/types.ts +77 -5
- package/package.json +1 -1
- package/starter-kit/components/AnimatedChevron/AnimatedChevron.catalog.tsx +1 -0
- package/starter-kit/components/Avatar/Avatar.catalog.tsx +1 -0
- package/starter-kit/components/BottomSheet/BottomSheet.catalog.tsx +1 -0
- package/starter-kit/components/ButtonGroup/ButtonGroup.catalog.tsx +1 -0
- package/starter-kit/components/Divider/Divider.catalog.tsx +1 -0
- package/starter-kit/components/Pill/Pill.catalog.tsx +1 -0
- package/starter-kit/components/TopNav/TopNav.catalog.tsx +1 -0
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ identified.
|
|
|
132
132
|
|
|
133
133
|
`ds-viewer.config.ts` fields: `name`, `logo` (optional — a path to an image asset, shown in the
|
|
134
134
|
sidebar beside the name, or in place of it for a wide wordmark; validated at `sync`/`dev` time
|
|
135
|
-
and bundled into the preview workspace), `components`, `exclude`, `tokens`, `pages`, `groupOrder`, `starterKit` (new-project only —
|
|
135
|
+
and bundled into the preview workspace), `components`, `exclude`, `tokens`, `pages`, `groupOrder`, `patternGroups`, `starterKit` (new-project only —
|
|
136
136
|
records the kit's version), `updateCheck`, `doctor` — see `config/index.ts`'s `DsViewerConfig` for
|
|
137
137
|
each field's exact meaning. Write a page with `defineCatalogPage()` from `@krapwoo/ds-viewer` (same
|
|
138
138
|
shape as a `SectionDef`, minus `id`/`path`/`props`, plus `component`, `group`, and `propNotes`).
|
|
@@ -230,7 +230,8 @@ Each catalog shows **one page at a time**: a persistent, searchable sidebar and
|
|
|
230
230
|
On web the page is kept in the URL fragment (`#Button`), so refresh, deep links, and back/forward
|
|
231
231
|
work. Desktop and laptop screens only. `SectionBlock` renders one page: breadcrumb, title,
|
|
232
232
|
description, previous/next, then the specimens, then always-visible
|
|
233
|
-
**Guidance** and **Quick reference** (source path, accessibility
|
|
233
|
+
**Guidance** and **Quick reference** (source path, accessibility, and — when the page has any —
|
|
234
|
+
**Composition** and the derived **"also used by"** inverse of it), then **Props** in their own box
|
|
234
235
|
(two columns, filled across first, when there are 4+ props and room). Token pages show only
|
|
235
236
|
Quick reference with the source path. Guidance, Quick reference, and — only for a page with
|
|
236
237
|
confirmed `composedOf` entries — Composition lay out from the reference card's own measured inner
|
|
@@ -238,7 +239,12 @@ width, not viewport width alone: three columns side by side only when there's co
|
|
|
238
239
|
*and* the card is wide enough for all three (Composition gets its own column only then; otherwise
|
|
239
240
|
it stays nested under Quick reference, as it always has), two when there's room for Guidance and
|
|
240
241
|
Quick reference side by side, or one stacked column — in full document order, nothing dropped —
|
|
241
|
-
when the card is too narrow even for two.
|
|
242
|
+
when the card is too narrow even for two. A Composition name becomes a real link whenever it
|
|
243
|
+
resolves to a real catalog address (a page, a family member, or either one's alias — the same
|
|
244
|
+
resolution a family member's own "Uses" line already uses); an unresolvable name stays honest plain
|
|
245
|
+
text. "Also used by" is derived, never authored, the ordinary-page counterpart of a family member's
|
|
246
|
+
own line of the same name (see "Family pages" below): nothing renders when nothing else uses the
|
|
247
|
+
page. The specimens use one of four layouts:
|
|
242
248
|
- **Grid** — two props that combine freely, from an explicit `comparison` (e.g. Button's
|
|
243
249
|
Variant × State).
|
|
244
250
|
- **Grouped rows** — one row per variant holding that variant's own configurations (e.g.
|
|
@@ -260,6 +266,44 @@ card narrower than its minimum scrolls inside itself rather than squeezing its c
|
|
|
260
266
|
(`itemsFill`) slots default to `wide`. The catalog's own look comes from `native/catalog/tokens.ts`,
|
|
261
267
|
never from the host app's tokens.
|
|
262
268
|
|
|
269
|
+
### Source reference
|
|
270
|
+
|
|
271
|
+
A page's `navigation` field is `'browse'` (the default — omit it) or `'reference'`:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
export default defineCatalogPage({
|
|
275
|
+
id: 'ButtonSource', description: 'The native declaration behind Button.',
|
|
276
|
+
navigation: 'reference',
|
|
277
|
+
// ...
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
A `'reference'` page keeps every one of its addresses — its own id, its aliases, and (on a family
|
|
282
|
+
page) every member's own id/alias — fully routable; `navigation` only ever removes it from the
|
|
283
|
+
sidebar, the pager, and the hash fallback's own browse sequence. A family page's members inherit
|
|
284
|
+
their own page's `navigation`; there's no separate per-member field. The moment any page in the
|
|
285
|
+
catalog opts in, the sidebar grows a quiet **Source reference** entry: a catalog-wide search over
|
|
286
|
+
every registered page's and member's own id, title, detected component export, alias, and source
|
|
287
|
+
path — never a page-local member search, and never a second, hand-maintained registry (it's
|
|
288
|
+
derived from the same `SectionDef[]`/`FamilyDef` every other part of the viewer already reads). A
|
|
289
|
+
legacy catalog with no `'reference'` page keeps its present sidebar and page set, unchanged.
|
|
290
|
+
Selecting a result opens its real page (and, for a member, focuses it directly) and offers a real
|
|
291
|
+
`Back to <origin>` return link to wherever that selection actually started from — any page, not
|
|
292
|
+
only another reference destination. Every other already-true reference fact (a Composition name,
|
|
293
|
+
a derived "also used by" user, a family member's own `related`/anatomy name) becomes the same kind
|
|
294
|
+
of real, canonical, focused-return link once it resolves. Only loading a reference page's own URL
|
|
295
|
+
directly, with no recorded origin, offers `Browse catalog` to the first ordinary browsing page
|
|
296
|
+
instead of inventing one that was never visited; a catalog with no browsable page at all offers
|
|
297
|
+
neither.
|
|
298
|
+
|
|
299
|
+
Resolution for the Source reference picker, and for every Composition/"also used by"/`related`/
|
|
300
|
+
anatomy name across the whole catalog (see "Browsing the catalog" above and "Family pages" below),
|
|
301
|
+
shares one rule: an exact registered page id, member id, or alias always wins; only when none of
|
|
302
|
+
those claim the name does a unique, detected component export resolve it instead — letting a name
|
|
303
|
+
find an ordinary page whose own id differs from its component (e.g. a `PillButton` page whose
|
|
304
|
+
component export is `Button`). A component name shared by more than one page or member is
|
|
305
|
+
ambiguous and is never guessed; an unknown name stays plain, honest text rather than a dead link.
|
|
306
|
+
|
|
263
307
|
## Adding a component (the recipe)
|
|
264
308
|
|
|
265
309
|
Copy the shape of an existing pair, e.g. `starter-kit/components/Button/` or `starter-kit/components/Badge/`:
|
|
@@ -273,6 +317,8 @@ a11y, and its content — `id`/`path`/`props` are derived, never authored), and
|
|
|
273
317
|
`src/ds/components/index.ts` the same way its siblings are. `SectionBlock` shows the
|
|
274
318
|
specimens first, then Guidance, Quick reference, and Props. Give it whichever of these fields
|
|
275
319
|
actually apply:
|
|
320
|
+
- `notes: string[]` — page-level notes, listed as a "Notes" entry in the page's Quick reference
|
|
321
|
+
(for facts about the source that aren't part of the description, guidance or accessibility).
|
|
276
322
|
- `comparison: { rowLabel, columnLabel, rows, columns, cells, size? }` — when two props genuinely
|
|
277
323
|
combine. Author every row × column cell with a real instance, or an `unavailableReason` when that
|
|
278
324
|
combination does not exist. Never multiply `variants` by `states` to fill it. Keep the column axis
|
|
@@ -449,6 +495,13 @@ path. For anything beyond a single list of tokens, use the token-page layouts be
|
|
|
449
495
|
title, not the id — so "Control heights" sorts under "C", not under whatever the id starts with.
|
|
450
496
|
- **Point token pages at their token file.** A token page has no component, so its Source would be
|
|
451
497
|
the page file; set `source: 'src/design-system/tokens.ts'` (project-relative) instead.
|
|
498
|
+
- **Parts and empty pages.** `doctor` warns about a page that is only a built-in part of one other
|
|
499
|
+
page (`part-own-page`) and about a page that shows nothing (`nothing-to-show`); see the rule list
|
|
500
|
+
under `doctor`. To keep such a page on purpose, set `standalone: '<why>'`, for example
|
|
501
|
+
`standalone: 'Not a part of GitFileCard; it draws its own counts.'`. The reason must be non-blank
|
|
502
|
+
and is only a note in the source — the viewer ignores it. Family and group pages, token pages and
|
|
503
|
+
`navigation: 'reference'` pages are never checked. Both checks count `composedOf` written on a
|
|
504
|
+
family or group member as well as on a page.
|
|
452
505
|
- **Coverage only counts the component's own options.** `doctor`'s `option-not-covered` checks
|
|
453
506
|
string-literal unions declared in the component's own folder or a token folder. A union
|
|
454
507
|
declared in another component's folder, such as a shared `IconName`, is that component's
|
|
@@ -486,6 +539,9 @@ export default defineCatalogPage({
|
|
|
486
539
|
});
|
|
487
540
|
```
|
|
488
541
|
|
|
542
|
+
Every family page shows a one-line "Family page." explainer under its description, so a reader
|
|
543
|
+
knows its parts are separate components, unlike variants.
|
|
544
|
+
|
|
489
545
|
- **`whole`** is a member id, and must equal the page's own `id`. It renders first, with no header
|
|
490
546
|
of its own (the page title already names it) and no number.
|
|
491
547
|
- **`sections`** (`{ label, kind: 'blocks' | 'list', inWhole, members }`) group the rest: `blocks`
|
|
@@ -557,6 +613,10 @@ export default defineCatalogPage({
|
|
|
557
613
|
for behavior a web catalog can't demonstrate (a timer, a native gesture). A member's own
|
|
558
614
|
`notShown` works the same way, under its specimens. Like `aliases`, the page-level field only
|
|
559
615
|
takes effect on a page with `members`.
|
|
616
|
+
- **Notes and API.** A member's notes, "Uses …" line, guidance, accessibility, props and
|
|
617
|
+
reference excerpt open in one shared side panel (a modal dialog on the right, full width at
|
|
618
|
+
768px and narrower) from a "Notes and API" link under the member — Esc, the × button or a click outside closes
|
|
619
|
+
it and returns focus to the link; a member with nothing to show has no link.
|
|
560
620
|
- **`apiLabel`** (default `'API'`) renames the disclosure's reference-excerpt part for a host app
|
|
561
621
|
that generates one (e.g. `'Swift API'`) — never hardcode a specific language's wording. Also
|
|
562
622
|
only takes effect on a page with `members`.
|
|
@@ -564,6 +624,74 @@ export default defineCatalogPage({
|
|
|
564
624
|
and at least one specimen, the same as a normal page's component — `doctor` checks each one.
|
|
565
625
|
See `viewer-host/viewer-pages/FamilyPage.catalog.tsx` in this repo for a complete, worked example.
|
|
566
626
|
|
|
627
|
+
### Group pages
|
|
628
|
+
|
|
629
|
+
A page can also document several unrelated-but-adjacent components on one page — e.g. two SwiftUI
|
|
630
|
+
button styles used together on one screen, or the handful of request-card bodies a switch statement
|
|
631
|
+
picks between — with no single "whole" that owns the others. Give it `members` with **no `whole`
|
|
632
|
+
and no `sections`** (both omitted, not just empty):
|
|
633
|
+
|
|
634
|
+
```tsx
|
|
635
|
+
export default defineCatalogPage({
|
|
636
|
+
id: 'OnboardingButtons', group: 'Components', description: 'Two separate button styles used together on onboarding screens.',
|
|
637
|
+
members: [
|
|
638
|
+
{ id: 'OnboardingPrimaryButtonStyle', source: 'Onboarding.swift:172', fidelity: 'replicated', relationship: 'Style', description: '...', whenToUse: '...', a11y: '...', variants: { items: [/* ... */] } },
|
|
639
|
+
{ id: 'OnboardingSecondaryButtonStyle', source: 'Onboarding.swift:298', fidelity: 'replicated', relationship: 'Style', description: '...', whenToUse: '...', a11y: '...', variants: { items: [/* ... */] } },
|
|
640
|
+
],
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Every group page shows a one-line "Group page." explainer under its description, so a reader knows
|
|
645
|
+
the members are separate components with their own APIs, unlike variants.
|
|
646
|
+
|
|
647
|
+
**Group vs. family:** reach for a family when the members genuinely compose one thing (a view and
|
|
648
|
+
its own subviews) — the page then opens with that thing's own specimens, before its parts. Reach
|
|
649
|
+
for a group when the members are independently meaningful and would otherwise each get their own
|
|
650
|
+
tiny page with nothing to compare them against; a group page shows every member's own section with
|
|
651
|
+
no shared "whole" and no Compare block (layout B) — just the page head, a jump row, then each
|
|
652
|
+
top-level member's own section in authored order.
|
|
653
|
+
|
|
654
|
+
- **Shape.** `sections` and page-level `anatomy` are never used on a group page — `doctor`'s
|
|
655
|
+
`family-invalid` rule reports either one if present. Every member keeps its full `CatalogMember`
|
|
656
|
+
shape (the same fields a family member has).
|
|
657
|
+
- **Top-level vs. nested.** A member whose `parent` names another member *on the same page* is one
|
|
658
|
+
of that member's own parts, nested underneath it; a member with no `parent`, or whose `parent`
|
|
659
|
+
names a member defined on a *different* page (see "External parts" below), is **top-level**. The
|
|
660
|
+
jump row lists exactly the top-level members, in authored order, each as a chip linking to its
|
|
661
|
+
own address; its muted qualifier is "Part N of `<Family>`" when the member is an external part,
|
|
662
|
+
"+k parts" when it has its own nested parts on this page, or nothing.
|
|
663
|
+
- **Embedded families.** A top-level member with parts of its own on the page may carry its own
|
|
664
|
+
`anatomy` (`CatalogMember.anatomy` — the same `{ picture, groups }` shape a family's page-level
|
|
665
|
+
`anatomy` has, but scoped to that one member's own descendants). Its parts number independently of
|
|
666
|
+
every other top-level member's: `1`, `2`, … among siblings, then `1.1`, `1.2`, … for children —
|
|
667
|
+
the same rule a family uses under its own whole, just re-anchored per top-level member instead of
|
|
668
|
+
once per page. `doctor`'s `group-anatomy-member` rule reports an embedded anatomy item naming
|
|
669
|
+
anything other than that member's own descendant.
|
|
670
|
+
- **External parts.** A family's own `inWhole` section may list a member id that is defined on a
|
|
671
|
+
group page instead of its own `members`, as long as that member's own `parent` equals the
|
|
672
|
+
family's whole — an *external part*. The family's outline numbers it exactly like an own part (the
|
|
673
|
+
same number shows on both pages); on the family page its row shows that number, its name, and a
|
|
674
|
+
"Details on `<group title>`" link to the member's own address, with no specimens or duplicated
|
|
675
|
+
description. On the group page, that member's own section heading shows "Part N of `<Family>`",
|
|
676
|
+
linking back to the family page. An id that no page defines at all is reported by
|
|
677
|
+
`family-invalid` ("not a member of this page"); an id that is defined but doesn't resolve to
|
|
678
|
+
exactly one group-page member with a matching parent (defined on several pages, on a page that
|
|
679
|
+
isn't a group page, or with a different `parent`) is reported by `doctor`'s
|
|
680
|
+
`family-external-part` rule. Listing the same external id in two sections, or twice in one, is a
|
|
681
|
+
`family-invalid` "in more than one section" error.
|
|
682
|
+
- **Routing, search, aliases.** A group page's own id and `aliases` resolve to the page, exactly
|
|
683
|
+
like a family's; every member id and alias on a group page resolves to that page with that member
|
|
684
|
+
focused, exactly like a family member's does (a member id or alias equal to its own page's id
|
|
685
|
+
could never be addressed, so `family-invalid` reports it). The sidebar, pager and search list the group page
|
|
686
|
+
once (its own title) — members are reachable by address and search, never as separate sidebar
|
|
687
|
+
rows, the same convention a family's own members already follow.
|
|
688
|
+
- No whole, no Compare block, no `relatedPages`/`composedOf`-at-the-page-level concept of its own
|
|
689
|
+
(a non-empty `relatedPages` on a group page is reported by `family-invalid` as unused) —
|
|
690
|
+
everything else (fidelity badges, `whenToUse`/`a11y`, `composedOf`'s own "Uses …" line, derived
|
|
691
|
+
"Also used by", `notShown`, `apiLabel`) works exactly the same as it does for a family member, see
|
|
692
|
+
above. See `viewer-host/viewer-pages/GroupPage.catalog.tsx` in this repo for a worked example,
|
|
693
|
+
including an embedded family and an external part of a small fixture family page.
|
|
694
|
+
|
|
567
695
|
### Token pages
|
|
568
696
|
|
|
569
697
|
A token page shows each token at its real value with its role. Pick the layout by the token's
|
|
@@ -614,6 +742,107 @@ shape:
|
|
|
614
742
|
<MotionSpecimen kind="spring" spring={UNIT_SPRING_CONFIG} valueRange="unit" />
|
|
615
743
|
```
|
|
616
744
|
|
|
745
|
+
### `tokenDefinition`
|
|
746
|
+
|
|
747
|
+
An opt-in, structured alternative to `render`/`tokenGallery`/`tokenSections` for a token page's
|
|
748
|
+
*real entries* — name, value, provenance, source — instead of a hand-built preview. Omitting it
|
|
749
|
+
leaves every existing page exactly as it was; nothing here changes unless a page opts in.
|
|
750
|
+
|
|
751
|
+
```tsx
|
|
752
|
+
import { defineCatalogPage, type TokenDefinition } from '@krapwoo/ds-viewer';
|
|
753
|
+
|
|
754
|
+
const COLORS: TokenDefinition = {
|
|
755
|
+
kind: 'colors',
|
|
756
|
+
sections: [
|
|
757
|
+
{
|
|
758
|
+
id: 'semantic', title: 'Semantic',
|
|
759
|
+
entries: [
|
|
760
|
+
{ id: 'brand', name: 'Brand', kind: 'colors', value: { provenance: 'named', resolution: { status: 'resolved', value: { color: DS_SEMANTIC.brand } } } },
|
|
761
|
+
],
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
export default defineCatalogPage({ group: 'Tokens', description: 'Semantic and palette colors.', tokenDefinition: COLORS });
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
- **Eight kinds**, each with one fixed canonical title: `colors` → Colors, `typography` →
|
|
770
|
+
Typography, `spacing` → Spacing, `radius` → Radius, `dimensions` → Dimensions, `shadows` →
|
|
771
|
+
Shadows, `opacity` → Opacity, `motion` → Motion. Omit the page's own `title` to get it for free;
|
|
772
|
+
an explicit `title` must match (`doctor`'s `token-definition-title-conflict`, below).
|
|
773
|
+
- **The runtime import is the value authority, not `tokens.json`.** An entry's `value` is whatever
|
|
774
|
+
real literal/import you give it at the call site — this is not a reader of your build's
|
|
775
|
+
`tokens.json`/token-scan output. That's also why a static check (`doctor`) can only ever read an
|
|
776
|
+
entry's literal metadata, never its real value — see "Static honesty" below.
|
|
777
|
+
- **Provenance, resolution and `named` are three independent things**, and every entry shows its
|
|
778
|
+
own provenance as a plain muted caption (never a badge/pill): `'named'` ("Named token") is a real
|
|
779
|
+
exported design-system token; `'inline'` ("Inline value") is a one-off literal with no shared
|
|
780
|
+
name; `'platform'` ("Platform-provided") means the real value comes from the OS at runtime, not a
|
|
781
|
+
literal this catalog can read; `'unresolved-finding'` ("Unresolved finding") is a real,
|
|
782
|
+
source-backed literal/expression found in code with no shipped design token behind it yet.
|
|
783
|
+
`resolution` is separate: `{ status: 'resolved', value }` when there's a real sample to draw, or
|
|
784
|
+
`{ status: 'unresolved', reason, expression? }` with the real reason (and, when one exists, the
|
|
785
|
+
real source expression) a human would read at the call site — never a fabricated sample or an
|
|
786
|
+
invented classification. `named: true` says a `'platform'`/`'unresolved-finding'` value is *also*
|
|
787
|
+
exposed through a real named token (shown as "Platform-provided · named token"); omit it for
|
|
788
|
+
`'named'`/`'inline'` provenance, which already say so by themselves. When every entry in a
|
|
789
|
+
section shares the same provenance/named pair, it's shown once for the whole section instead of
|
|
790
|
+
repeating on every entry.
|
|
791
|
+
- **Appearances and scoped ids.** A color entry's `appearance: 'light' | 'dark'` renders an
|
|
792
|
+
independent sample — not a global color-scheme context; omit it for a color with one appearance.
|
|
793
|
+
An entry's `id` only needs to be unique *within its own section* — the same id may recur in a
|
|
794
|
+
different section of the same page (e.g. two scoped "Palette" cards), which is the same
|
|
795
|
+
`TokenSection.id` precedent token galleries already use.
|
|
796
|
+
- **Groups inside a section.** A section may add `groups: [{ id, title, desc?, entries }]` — each
|
|
797
|
+
renders under its own sub-heading, after the section's own entries, in the same grid the section
|
|
798
|
+
itself uses. A group id is unique within its own section; an entry id is unique within its own
|
|
799
|
+
group, a separate scope from the section's own entries and every other group. The section-level
|
|
800
|
+
provenance consolidation (above) considers the section's own entries *and* every one of its
|
|
801
|
+
groups together, so a shared provenance/named pair is still shown only once for the whole section.
|
|
802
|
+
- **The category jump row.** A page with more than one section opens with one chip per section —
|
|
803
|
+
its title plus how many entries it holds (its own, plus every group's) — so "Attention" might
|
|
804
|
+
read "Attention, 3 values". Each chip scrolls that section into view; a single-section page shows
|
|
805
|
+
no jump row, since there's nothing to jump between.
|
|
806
|
+
- **Light/dark pairing.** Within one entries list (a section's own, or one group's), two `colors`
|
|
807
|
+
entries sharing the same `name` — one `appearance: 'light'`, the other `'dark'` — render as a
|
|
808
|
+
single swatch: a split chip (light half left, dark half right), the bare shared name once, and
|
|
809
|
+
`<light value> · <dark value>` as the value line. An entry with no matching opposite appearance
|
|
810
|
+
(or a third same-named entry once both appearances are already paired) renders exactly as any
|
|
811
|
+
other unpaired entry.
|
|
812
|
+
- **Motion's `native-expression` kind is a real limit, not a gap:** a SwiftUI
|
|
813
|
+
`.spring(duration:bounce:)`/`.spring(response:dampingFraction:)` (or any other conditional/
|
|
814
|
+
symbolic value) is shown as its own real text, never converted into a guessed
|
|
815
|
+
stiffness/damping/mass spring — that conversion genuinely isn't supported.
|
|
816
|
+
- **Conflicts.** `tokenDefinition` replaces, and so conflicts with, an explicit `render`,
|
|
817
|
+
`tokenGallery`, `tokenSections`, or `members` (a family page) on the same page — remove one of
|
|
818
|
+
them. These, and an explicit `title` that disagrees with the kind's canonical title, are always
|
|
819
|
+
`doctor` errors (`token-definition-conflict`, `token-definition-title-conflict`), independent of
|
|
820
|
+
`doctor.strict`. A duplicate section id, a duplicate group id within a section, or a duplicate
|
|
821
|
+
entry id within one section or one group, is `token-definition-duplicate-id` (also always an
|
|
822
|
+
error) — the same id may still recur in a different section/group/the section's own entries.
|
|
823
|
+
- **One page per kind.** Two or more pages declaring the same literal `tokenDefinition.kind` each
|
|
824
|
+
get a `token-definition-duplicate-kind` error naming the other page(s) — a page with no
|
|
825
|
+
`tokenDefinition` is never involved. Give one of them its own kind, or merge them into one page.
|
|
826
|
+
- **Aliases.** A `tokenDefinition` page's own `aliases` resolve to the page, the same way a family
|
|
827
|
+
page's `aliases` already do. A section's own `aliases` resolve to the page too, with that section
|
|
828
|
+
scrolled into view. Every one of these joins the catalog's existing alias-collision check
|
|
829
|
+
(`family-invalid`) — an alias must still name exactly one thing, catalog-wide.
|
|
830
|
+
- **Static honesty.** `npx ds-viewer doctor` reads a `tokenDefinition`'s `kind` and each section's
|
|
831
|
+
`id`/`title`/`desc`/`wide`/entry `id`s — and, the same way, each group's own `id`/`title`/entry
|
|
832
|
+
`id`s — the same literal-only way it reads every other page field — it never imports or executes a
|
|
833
|
+
page file, so it can never see a real `value`. When a section's (or a group's) own `entries` isn't
|
|
834
|
+
a literal array (an imported/computed list), or contains an entry whose own `id` isn't a literal,
|
|
835
|
+
that list's ids are read on a best-effort basis and the summary lists it as a section with
|
|
836
|
+
runtime-only entries (not statically validated). That is the normal case when values are imported
|
|
837
|
+
from your own token modules, so it is informational: it never fails `--ci` and `doctor.strict`
|
|
838
|
+
does not promote it. Literal sections still get every check. A `tokenDefinition` whose own
|
|
839
|
+
`kind`/`sections` metadata isn't literal at all makes the whole page not statically checkable —
|
|
840
|
+
reported only as the existing `page-not-checkable` warning, never a separate
|
|
841
|
+
`tokenDefinition`-specific one.
|
|
842
|
+
- **Migration.** A legacy `render`/`tokenGallery`/`tokenSections` token page keeps working
|
|
843
|
+
unchanged — `tokenDefinition` is additive, never required. To convert one, remove `render`/
|
|
844
|
+
`tokenSections` and author the same data as `tokenDefinition`'s entries instead.
|
|
845
|
+
|
|
617
846
|
## Keeping the catalog current
|
|
618
847
|
|
|
619
848
|
### Binding examples to props
|
|
@@ -683,6 +912,23 @@ Button
|
|
|
683
912
|
non-`inWhole` section, naming every one of them so you can act without opening the file. Give
|
|
684
913
|
each its own catalog page and name it in the family's `relatedPages` list instead; the section
|
|
685
914
|
keeps working until you do.
|
|
915
|
+
- `part-own-page` — a standalone page for a component that exactly one other page declares in
|
|
916
|
+
`composedOf` as `relationship: 'built-in'`, and that page doesn't list it as a member. Fold it
|
|
917
|
+
into that page's family, or record why it isn't part of it with `standalone`. A component built
|
|
918
|
+
into two or more pages is exempt, and `'slot'`/`'related'` entries never count. Pages in a pattern
|
|
919
|
+
group (example screens built from components) never count as the whole: by default the group
|
|
920
|
+
named `Patterns`; set `patternGroups` in `ds-viewer.config.ts` to name your own (e.g.
|
|
921
|
+
`patternGroups: ['Screens']`).
|
|
922
|
+
- `nothing-to-show` — a plain page with no variants, states, `render` or comparison, and no
|
|
923
|
+
members or token content (`previewWidths` alone doesn't count; it only sizes `render`). The
|
|
924
|
+
message names the pages that list it in `composedOf` — on the page or on one of its members —
|
|
925
|
+
or says only pattern pages use it, or that nothing does. List it as a documentation-only part
|
|
926
|
+
of a page that uses it, set `navigation: 'reference'`, or add a `standalone` reason. A page
|
|
927
|
+
whose specimens can't be read literally is never reported. A component page with no specimens
|
|
928
|
+
usually also gets `component-no-examples` (unless the component is documented elsewhere, on
|
|
929
|
+
another page or as a family member); the two say different things (no examples vs. no reason to
|
|
930
|
+
be its own page) and clear together once it has a specimen.
|
|
931
|
+
- `standalone-reason-empty` — `standalone` is blank, so it records no reason and opts nothing out.
|
|
686
932
|
|
|
687
933
|
The catalog also warns at render time when a page under-declares the width its examples need.
|
|
688
934
|
A `PhoneFrame` given a cell narrower than the 375px device scales the whole phone down, and its
|
package/config/index.ts
CHANGED
|
@@ -16,6 +16,9 @@ export interface DsViewerConfig {
|
|
|
16
16
|
pages?: string[];
|
|
17
17
|
/** Sidebar group display order; groups not listed here follow alphabetically. */
|
|
18
18
|
groupOrder?: string[];
|
|
19
|
+
/** Sidebar groups whose pages are patterns: example screens built from components. `doctor`
|
|
20
|
+
* never treats a pattern page as the whole a component is a part of. @default ['Patterns'] */
|
|
21
|
+
patternGroups?: string[];
|
|
19
22
|
/** Starter-kit projects only. `root` is the folder `init --new` copied the kit into (default
|
|
20
23
|
* `src/ds`); absent on a config written before 0.4, in which case `cli/kitRoot.ts` infers it
|
|
21
24
|
* from the first `components` glob instead. */
|
package/dist/cli/config.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { defineConfig } from "../config/index.js";
|
|
5
5
|
const KNOWN_FIELDS = new Set([
|
|
6
|
-
'name', 'logo', 'components', 'exclude', 'tokens', 'pages', 'groupOrder', 'starterKit', 'updateCheck', 'doctor',
|
|
6
|
+
'name', 'logo', 'components', 'exclude', 'tokens', 'pages', 'groupOrder', 'patternGroups', 'starterKit', 'updateCheck', 'doctor',
|
|
7
7
|
]);
|
|
8
8
|
/**
|
|
9
9
|
* Loads `ds-viewer.config.ts` with a bundled TypeScript loader. This is a guard rail, not a
|
|
@@ -41,6 +41,13 @@ export function loadConfig(configPath) {
|
|
|
41
41
|
if (!KNOWN_FIELDS.has(key))
|
|
42
42
|
warnings.push(`Unknown config field "${key}" is ignored.`);
|
|
43
43
|
}
|
|
44
|
+
let patternGroups;
|
|
45
|
+
if (raw.patternGroups !== undefined) {
|
|
46
|
+
if (Array.isArray(raw.patternGroups) && raw.patternGroups.every((name) => typeof name === 'string'))
|
|
47
|
+
patternGroups = raw.patternGroups;
|
|
48
|
+
else
|
|
49
|
+
warnings.push('"patternGroups" must be an array of group names; using the default [\'Patterns\'].');
|
|
50
|
+
}
|
|
44
51
|
const config = {
|
|
45
52
|
name: raw.name ?? '',
|
|
46
53
|
logo: raw.logo,
|
|
@@ -49,6 +56,7 @@ export function loadConfig(configPath) {
|
|
|
49
56
|
tokens: raw.tokens ?? [],
|
|
50
57
|
pages: raw.pages ?? [],
|
|
51
58
|
groupOrder: raw.groupOrder ?? [],
|
|
59
|
+
patternGroups,
|
|
52
60
|
starterKit: raw.starterKit,
|
|
53
61
|
updateCheck: raw.updateCheck ?? true,
|
|
54
62
|
doctor: raw.doctor ?? { strict: false },
|