@krapwoo/ds-viewer 0.1.1 → 0.3.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 +182 -78
- package/dist/cli/agentsFile.js +31 -0
- package/dist/cli/config.js +26 -1
- package/dist/cli/dev.js +50 -5
- package/dist/cli/doctor.js +492 -0
- package/dist/cli/explain.js +151 -0
- package/dist/cli/githubAction.js +68 -0
- package/dist/cli/glob.js +8 -0
- package/dist/cli/init.js +152 -18
- package/dist/cli/main.js +115 -4
- package/dist/cli/pageIndex.js +27 -1
- package/dist/cli/props.js +170 -21
- package/dist/cli/staticPage.js +356 -0
- package/dist/cli/sync.js +28 -5
- package/dist/cli/workspace.js +49 -21
- package/dist/native/catalog/comparison.js +283 -0
- package/dist/native/catalog/tokens.js +119 -0
- package/dist/native/catalog/types.js +1 -0
- package/native/catalog/CatalogShell.tsx +6 -1
- package/native/catalog/CatalogSidebar.tsx +98 -5
- package/native/catalog/ComparisonGrid.tsx +9 -7
- package/native/catalog/SpacingScaleGallery.tsx +1 -1
- package/native/catalog/TokenRow.tsx +3 -2
- package/native/catalog/catalogNavigation.ts +1 -1
- package/native/catalog/comparison.ts +63 -5
- package/native/catalog/index.ts +8 -6
- package/native/catalog/logoLayout.ts +12 -0
- package/native/catalog/pageApi.ts +21 -3
- package/native/catalog/tokens.ts +5 -0
- package/native/catalog/types.ts +9 -6
- package/package.json +12 -6
- package/starter-kit/WHEN_TO_USE.md +124 -0
- package/starter-kit/components/AnimatedChevron/AnimatedChevron.catalog.tsx +23 -0
- package/starter-kit/components/AnimatedChevron/AnimatedChevron.tsx +46 -0
- package/starter-kit/components/AnimatedChevron/index.ts +2 -0
- package/starter-kit/components/Avatar/Avatar.catalog.tsx +54 -0
- package/starter-kit/components/Avatar/Avatar.tsx +74 -0
- package/starter-kit/components/Avatar/index.ts +2 -0
- package/starter-kit/components/Badge/Badge.catalog.tsx +88 -0
- package/starter-kit/components/Badge/Badge.tsx +56 -0
- package/starter-kit/components/Badge/Badge.types.ts +18 -0
- package/starter-kit/components/Badge/index.ts +2 -0
- package/starter-kit/components/Banner/Banner.catalog.tsx +87 -0
- package/starter-kit/components/Banner/Banner.tsx +257 -0
- package/starter-kit/components/Banner/Banner.types.ts +38 -0
- package/starter-kit/components/Banner/index.ts +2 -0
- package/starter-kit/components/BottomSheet/BottomSheet.catalog.tsx +77 -0
- package/starter-kit/components/BottomSheet/BottomSheet.tsx +151 -0
- package/starter-kit/components/BottomSheet/index.ts +2 -0
- package/starter-kit/components/Button/Button.catalog.tsx +99 -0
- package/starter-kit/components/Button/Button.tsx +173 -0
- package/starter-kit/components/Button/Button.types.ts +52 -0
- package/starter-kit/components/Button/index.ts +2 -0
- package/starter-kit/components/ButtonGroup/ButtonGroup.catalog.tsx +39 -0
- package/starter-kit/components/ButtonGroup/ButtonGroup.tsx +84 -0
- package/starter-kit/components/ButtonGroup/index.ts +2 -0
- package/starter-kit/components/Card/Card.catalog.tsx +68 -0
- package/starter-kit/components/Card/Card.tsx +133 -0
- package/starter-kit/components/Card/index.ts +2 -0
- package/starter-kit/components/Checkbox/Checkbox.catalog.tsx +28 -0
- package/starter-kit/components/Checkbox/Checkbox.tsx +104 -0
- package/starter-kit/components/Checkbox/index.ts +2 -0
- package/starter-kit/components/Dialog/Dialog.catalog.tsx +41 -0
- package/starter-kit/components/Dialog/Dialog.tsx +82 -0
- package/starter-kit/components/Dialog/index.ts +2 -0
- package/starter-kit/components/Divider/Divider.catalog.tsx +35 -0
- package/starter-kit/components/Divider/Divider.tsx +20 -0
- package/starter-kit/components/Divider/index.ts +2 -0
- package/starter-kit/components/Dock/Dock.catalog.tsx +71 -0
- package/starter-kit/components/Dock/Dock.tsx +58 -0
- package/starter-kit/components/Dock/index.ts +2 -0
- package/starter-kit/components/Dropdown/Dropdown.catalog.tsx +69 -0
- package/starter-kit/components/Dropdown/Dropdown.tsx +162 -0
- package/starter-kit/components/Dropdown/index.ts +2 -0
- package/starter-kit/components/EmptyState/EmptyState.catalog.tsx +67 -0
- package/starter-kit/components/EmptyState/EmptyState.tsx +88 -0
- package/starter-kit/components/EmptyState/index.ts +2 -0
- package/starter-kit/components/FieldContainer/FieldContainer.catalog.tsx +76 -0
- package/starter-kit/components/FieldContainer/FieldContainer.tsx +99 -0
- package/starter-kit/components/FieldContainer/FieldContainer.types.ts +27 -0
- package/starter-kit/components/FieldContainer/index.ts +2 -0
- package/starter-kit/components/InputClearButton/InputClearButton.catalog.tsx +18 -0
- package/starter-kit/components/InputClearButton/InputClearButton.tsx +63 -0
- package/starter-kit/components/InputClearButton/index.ts +2 -0
- package/starter-kit/components/InputField/InputField.catalog.tsx +76 -0
- package/starter-kit/components/InputField/InputField.tsx +418 -0
- package/starter-kit/components/InputField/index.ts +2 -0
- package/starter-kit/components/List/List.catalog.tsx +30 -0
- package/starter-kit/components/List/List.tsx +36 -0
- package/starter-kit/components/List/index.ts +2 -0
- package/starter-kit/components/ListItem/ListItem.catalog.tsx +89 -0
- package/starter-kit/components/ListItem/ListItem.tsx +165 -0
- package/starter-kit/components/ListItem/index.ts +2 -0
- package/starter-kit/components/Loading/Loading.catalog.tsx +34 -0
- package/starter-kit/components/Loading/Loading.tsx +121 -0
- package/starter-kit/components/Loading/index.ts +2 -0
- package/starter-kit/components/Pill/Pill.catalog.tsx +79 -0
- package/starter-kit/components/Pill/Pill.tsx +181 -0
- package/starter-kit/components/Pill/Pill.types.ts +30 -0
- package/starter-kit/components/Pill/index.ts +2 -0
- package/starter-kit/components/PillRow/PillRow.catalog.tsx +91 -0
- package/starter-kit/components/PillRow/PillRow.tsx +140 -0
- package/starter-kit/components/PillRow/PillRow.types.ts +24 -0
- package/starter-kit/components/PillRow/index.ts +2 -0
- package/starter-kit/components/ProgressDots/ProgressDots.catalog.tsx +22 -0
- package/starter-kit/components/ProgressDots/ProgressDots.tsx +42 -0
- package/starter-kit/components/ProgressDots/index.ts +2 -0
- package/starter-kit/components/Radio/Radio.catalog.tsx +38 -0
- package/starter-kit/components/Radio/Radio.tsx +114 -0
- package/starter-kit/components/Radio/index.ts +2 -0
- package/starter-kit/components/Radio/usePressScale.ts +33 -0
- package/starter-kit/components/SearchField/SearchField.catalog.tsx +28 -0
- package/starter-kit/components/SearchField/SearchField.tsx +98 -0
- package/starter-kit/components/SearchField/index.ts +2 -0
- package/starter-kit/components/SectionHeader/SectionHeader.catalog.tsx +72 -0
- package/starter-kit/components/SectionHeader/SectionHeader.tsx +139 -0
- package/starter-kit/components/SectionHeader/index.ts +2 -0
- package/starter-kit/components/SegmentedToggle/SegmentedToggle.catalog.tsx +31 -0
- package/starter-kit/components/SegmentedToggle/SegmentedToggle.tsx +163 -0
- package/starter-kit/components/SegmentedToggle/index.ts +2 -0
- package/starter-kit/components/SegmentedToggle/useSlideAnim.ts +29 -0
- package/starter-kit/components/Shimmer/Breathing.catalog.tsx +29 -0
- package/starter-kit/components/Shimmer/Shimmer.catalog.tsx +89 -0
- package/starter-kit/components/Shimmer/Shimmer.tsx +158 -0
- package/starter-kit/components/Shimmer/Shimmer.types.ts +27 -0
- package/starter-kit/components/Shimmer/SkeletonGroup.catalog.tsx +39 -0
- package/starter-kit/components/Shimmer/index.ts +2 -0
- package/starter-kit/components/Surface/Surface.catalog.tsx +44 -0
- package/starter-kit/components/Surface/Surface.tsx +45 -0
- package/starter-kit/components/Surface/index.ts +2 -0
- package/starter-kit/components/Switch/Switch.catalog.tsx +33 -0
- package/starter-kit/components/Switch/Switch.tsx +129 -0
- package/starter-kit/components/Switch/index.ts +2 -0
- package/starter-kit/components/TextArea/TextArea.catalog.tsx +29 -0
- package/starter-kit/components/TextArea/TextArea.tsx +99 -0
- package/starter-kit/components/TextArea/index.ts +2 -0
- package/starter-kit/components/Toast/Toast.catalog.tsx +61 -0
- package/starter-kit/components/Toast/Toast.tsx +144 -0
- package/starter-kit/components/Toast/index.ts +2 -0
- package/starter-kit/components/Tooltip/Tooltip.catalog.tsx +121 -0
- package/starter-kit/components/Tooltip/Tooltip.tsx +195 -0
- package/starter-kit/components/Tooltip/index.ts +2 -0
- package/starter-kit/components/TopNav/TopNav.catalog.tsx +71 -0
- package/starter-kit/components/TopNav/TopNav.tsx +77 -0
- package/starter-kit/components/TopNav/index.ts +2 -0
- package/starter-kit/components/UnderlineTabs/UnderlineTabs.catalog.tsx +30 -0
- package/starter-kit/components/UnderlineTabs/UnderlineTabs.tsx +156 -0
- package/starter-kit/components/UnderlineTabs/index.ts +2 -0
- package/starter-kit/components/index.ts +36 -0
- package/starter-kit/components/statusColors.ts +27 -0
- package/starter-kit/icons/Icon.native.tsx +90 -0
- package/starter-kit/icons/index.ts +10 -0
- package/starter-kit/icons/paths.ts +623 -0
- package/starter-kit/icons/types.ts +69 -0
- package/starter-kit/pages/Colors.catalog.tsx +55 -0
- package/starter-kit/pages/Font.catalog.tsx +45 -0
- package/starter-kit/pages/Icons.catalog.tsx +51 -0
- package/starter-kit/pages/Manifest.catalog.tsx +24 -0
- package/starter-kit/pages/Motion.catalog.tsx +101 -0
- package/starter-kit/pages/Radius.catalog.tsx +35 -0
- package/starter-kit/pages/ReportIssue.catalog.tsx +148 -0
- package/starter-kit/pages/SavedTrips.catalog.tsx +247 -0
- package/starter-kit/pages/Shadow.catalog.tsx +34 -0
- package/starter-kit/pages/Spacing.catalog.tsx +12 -0
- package/starter-kit/pages/TripPlannerForm.catalog.tsx +48 -0
- package/starter-kit/pages/Typography.catalog.tsx +17 -0
- package/starter-kit/tokens/index.ts +50 -0
- package/starter-kit/tokens/motion.ts +90 -0
- package/starter-kit/tokens/palette.ts +111 -0
- package/starter-kit/tokens/scales.ts +106 -0
- package/starter-kit/tokens/semantic.ts +98 -0
- package/starter-kit/tokens/shadow.ts +36 -0
- package/starter-kit/tokens/typography.ts +72 -0
package/README.md
CHANGED
|
@@ -7,63 +7,102 @@ Drop it into a new project, rebrand the tokens, and build.
|
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
design-system-template/
|
|
10
|
-
├──
|
|
11
|
-
│ ├──
|
|
12
|
-
│ ├──
|
|
13
|
-
│ ├──
|
|
14
|
-
│
|
|
15
|
-
│ ├──
|
|
16
|
-
│ └──
|
|
17
|
-
├──
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
│ └── index.ts barrel (data + types only)
|
|
22
|
-
├── native/ ← React Native / Expo components + catalog
|
|
23
|
-
│ ├── components/… one folder per component (Component.tsx, .types.ts, index.ts)
|
|
24
|
-
│ └── catalog/ app-agnostic catalog framework (RN) + CatalogExample.tsx
|
|
25
|
-
└── native-preview/ ← dev-only Expo shell for browsing the catalog in a browser (not part
|
|
26
|
-
of the reusable template — see "Browsing the catalog" below)
|
|
10
|
+
├── starter-kit/ ← the starter kit: copied, never imported, into a new project by `init --new`
|
|
11
|
+
│ ├── tokens/ ONE source of truth (colors, spacing, radius, type, shadow). Platform-neutral.
|
|
12
|
+
│ ├── icons/ shared SVG path data + the native (react-native-svg) renderer
|
|
13
|
+
│ ├── components/… one folder per component: Component.tsx, .types.ts, index.ts, and a
|
|
14
|
+
│ │ finished Component.catalog.tsx documenting it
|
|
15
|
+
│ ├── pages/ standalone pages (token galleries, Icons, recipes, Manifest)
|
|
16
|
+
│ └── WHEN_TO_USE.md the deciding question behind every pair of similar components
|
|
17
|
+
├── native/catalog/ ← the viewer itself — app-agnostic, ships in the published package
|
|
18
|
+
└── kit-host/ ← this repo's own view of the starter kit; depends on this package via
|
|
19
|
+
`file:..`; `npm run kit:dev` opens it (not part of the published package —
|
|
20
|
+
see "Developing this repository" below)
|
|
27
21
|
```
|
|
28
22
|
|
|
29
23
|
## Using `@krapwoo/ds-viewer` in your own project
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
for browsing *your own* app's components, not this template's. In an existing Expo app:
|
|
25
|
+
**New project:**
|
|
33
26
|
|
|
34
27
|
```bash
|
|
35
|
-
npx @krapwoo/ds-viewer init
|
|
28
|
+
npx @krapwoo/ds-viewer init --new
|
|
36
29
|
npm install
|
|
30
|
+
npm run ds-viewer dev
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
`init`
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
`init --new` checks Expo/Node/package requirements, installs `react-native-svg` and
|
|
34
|
+
`react-native-safe-area-context` if your project doesn't have them yet (via `expo install`), then
|
|
35
|
+
copies the starter kit above into `src/ds/` — pass `--kit-root <path>` to put it somewhere else.
|
|
36
|
+
Every component already has a finished example page; `ds-viewer dev` opens the catalog immediately
|
|
37
|
+
with no further authoring needed. Running `init --new` again only adds whatever is still missing —
|
|
38
|
+
it never overwrites a file you've already edited.
|
|
39
|
+
|
|
40
|
+
**Existing project:**
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx @krapwoo/ds-viewer init --existing
|
|
44
|
+
npm install
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`init --existing` detects your component folders and token modules and, after you confirm the list
|
|
48
|
+
it prints (`--yes` skips the prompt), writes `ds-viewer.config.ts`, a draft
|
|
49
|
+
`<Export>.catalog.tsx` beside each detected component (marked "needs examples" — nothing is
|
|
50
|
+
invented), a `ds-viewer` package script, a `@krapwoo/ds-viewer` devDependency entry, and a
|
|
51
|
+
`.ds-viewer/` entry in `.gitignore`.
|
|
52
|
+
|
|
53
|
+
Running `npx @krapwoo/ds-viewer init` with neither `--new` nor `--existing` asks which one you
|
|
54
|
+
want, interactively. `init --yes` alone (no `--new`/`--existing`) skips that question and sets up
|
|
55
|
+
an existing project instead — the same default `--yes` always had in 0.1, so a script that already
|
|
56
|
+
calls `init --yes` keeps working unchanged. The `npm install` above is what actually fetches the
|
|
57
|
+
devDependency `init` added — `init` can only add the entry, not invoke your package manager.
|
|
58
|
+
|
|
59
|
+
Either way, then:
|
|
45
60
|
|
|
46
61
|
```bash
|
|
47
62
|
npm run ds-viewer dev # opens the catalog in a browser, re-syncing on every file change
|
|
48
63
|
npm run ds-viewer sync # regenerates component/token/page data without starting a server
|
|
64
|
+
npm run ds-viewer doctor # checks every page for drift and coverage gaps (--json, --ci)
|
|
65
|
+
npm run ds-viewer explain <Page> # prints why a page's specimens are laid out the way they are
|
|
49
66
|
```
|
|
50
67
|
|
|
51
|
-
`ds-viewer.config.ts` fields
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
`ds-viewer.config.ts` fields: `name`, `logo` (optional — a path to an image asset, shown in the
|
|
69
|
+
sidebar beside the name, or in place of it for a wide wordmark; validated at `sync`/`dev` time
|
|
70
|
+
and bundled into the preview workspace), `components`, `exclude`, `tokens`, `pages`, `groupOrder`, `starterKit` (new-project only —
|
|
71
|
+
records the kit's version), `updateCheck`, `doctor` — see `config/index.ts`'s `DsViewerConfig` for
|
|
72
|
+
each field's exact meaning. Write a page with `defineCatalogPage()` from `@krapwoo/ds-viewer` (same
|
|
73
|
+
shape as a `SectionDef`, minus `id`/`path`/`props`, plus `component`, `group`, and `propNotes`).
|
|
74
|
+
|
|
75
|
+
`logo`'s own shape decides how it renders, not a separate setting: a roughly square or modestly
|
|
76
|
+
wide image appears as a small mark beside the name; one more than twice as wide as it is tall
|
|
77
|
+
replaces the name entirely as a wordmark. A missing file just warns and falls back to the name
|
|
78
|
+
only. Use PNG if your project transforms SVG imports into components (Metro would otherwise try to
|
|
79
|
+
bundle the logo as a React component, not an image).
|
|
55
80
|
|
|
56
81
|
The package ships `native/catalog/` as TypeScript source, not pre-built JavaScript — your own
|
|
57
82
|
project's `tsc`/Metro compiles it under your own compiler options, the same as any other local file.
|
|
58
83
|
|
|
84
|
+
## Developing this repository
|
|
85
|
+
|
|
86
|
+
This repository views its own starter kit the same way a real user would: `kit-host/` is a small
|
|
87
|
+
Expo project that depends on `@krapwoo/ds-viewer` via `file:..` and points its `ds-viewer.config.ts`
|
|
88
|
+
at `../starter-kit/`. From the repo root:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm run kit:dev # builds the CLI, then opens the starter kit's own catalog
|
|
92
|
+
npm run check:catalog # the same headless check CI runs — every page, zero console errors
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`kit-host/` also hosts `viewer-pages/` — standalone catalog pages (not shipped in the package)
|
|
96
|
+
documenting the viewer framework's own pieces (`CatalogShell`, `SectionBlock`, `ComparisonGrid`,
|
|
97
|
+
…), shown under a "Viewer" group alongside the starter kit's own pages.
|
|
98
|
+
|
|
59
99
|
## Picking the right component
|
|
60
100
|
|
|
61
101
|
Several components look alike but solve different problems (InputField vs. SearchField vs.
|
|
62
|
-
Dropdown, Toast vs. Banner, Dialog vs. BottomSheet, …). See **[WHEN_TO_USE.md](./WHEN_TO_USE.md)**
|
|
102
|
+
Dropdown, Toast vs. Banner, Dialog vs. BottomSheet, …). See **[WHEN_TO_USE.md](./starter-kit/WHEN_TO_USE.md)**
|
|
63
103
|
for the deciding question behind each pair before reaching for the closest-looking one. For a
|
|
64
|
-
component's exact props
|
|
65
|
-
`
|
|
66
|
-
"Manifest" page.
|
|
104
|
+
component's exact props as structured data (not prose), see `@krapwoo/ds-viewer/generated` (built
|
|
105
|
+
live by `sync` from source), also rendered live at the catalog's "Manifest" page.
|
|
67
106
|
|
|
68
107
|
## The one rule
|
|
69
108
|
|
|
@@ -72,24 +111,24 @@ rebrand a two-file edit. Read tokens directly: `import { DS_SEMANTIC } from '...
|
|
|
72
111
|
|
|
73
112
|
## Peer dependencies
|
|
74
113
|
|
|
75
|
-
`react`, `react-native` assumed, plus: `react-native-svg` (icons +
|
|
114
|
+
`react`, `react-native` assumed, plus: `react-native-svg` (icons + Loading's circle variant),
|
|
76
115
|
`react-native-safe-area-context` (catalog shell only — not required by the components themselves).
|
|
77
116
|
|
|
78
117
|
## Rebranding (make it yours)
|
|
79
118
|
|
|
80
|
-
1. **Palette** — edit `tokens/palette.ts`: swap the six hue scales for your brand's (keep the 0–800
|
|
119
|
+
1. **Palette** — edit `starter-kit/tokens/palette.ts`: swap the six hue scales for your brand's (keep the 0–800
|
|
81
120
|
shape).
|
|
82
|
-
2. **Semantic** — in `tokens/semantic.ts`, re-point any role you want to shift (e.g. make `emphasis.info`
|
|
121
|
+
2. **Semantic** — in `starter-kit/tokens/semantic.ts`, re-point any role you want to shift (e.g. make `emphasis.info`
|
|
83
122
|
your brand blue). Every component re-themes automatically.
|
|
84
|
-
3. **Type** — adjust `tokens/typography.ts` (sizes/weights) and set your font family at the app root.
|
|
123
|
+
3. **Type** — adjust `starter-kit/tokens/typography.ts` (sizes/weights) and set your font family at the app root.
|
|
85
124
|
|
|
86
125
|
Nothing else needs touching — components reference roles, not values.
|
|
87
126
|
|
|
88
127
|
## Using the components
|
|
89
128
|
|
|
90
129
|
```tsx
|
|
91
|
-
import { Button } from '
|
|
92
|
-
import { Badge } from '
|
|
130
|
+
import { Button } from './src/ds/components/Button'; // after `init --new` (default --kit-root)
|
|
131
|
+
import { Badge } from './src/ds/components/Badge';
|
|
93
132
|
|
|
94
133
|
<Button label="Save" variant="primary" onPress={save} />
|
|
95
134
|
<Badge variant="positive" label="On time" leadingIcon="check" />
|
|
@@ -98,17 +137,10 @@ import { Badge } from '@ds/native/components/Badge';
|
|
|
98
137
|
## Browsing the catalog
|
|
99
138
|
|
|
100
139
|
`native/catalog/` is a framework-only package (`CatalogShell`, `SectionBlock`, `PropsTable`, …) —
|
|
101
|
-
it has no runnable app of its own
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
DS components (Button, Card, Banner, …).
|
|
106
|
-
- `native/catalog/CatalogFrameworkExample.tsx` — **"Design System DS Catalog"**: documents the
|
|
107
|
-
catalog framework's own eight pieces (`CatalogShell`, `CatalogSidebar`, `CatalogSearchInput`,
|
|
108
|
-
`SectionBlock`, `PropsTable`, `VariantGroup`, `TokenRow`, `DividedStack`) plus its own
|
|
109
|
-
Colors/Spacing/Type Scale token pages (`native/catalog/tokens.ts` — `CATALOG_*`, independent of
|
|
110
|
-
the host app's DS tokens) — a catalog of the catalog tool itself, useful when you're extending the
|
|
111
|
-
framework rather than the DS.
|
|
140
|
+
it has no runnable app of its own and no hand-assembled example anymore. A page is just a
|
|
141
|
+
`*.catalog.tsx` file next to its component (or a standalone one for tokens/recipes), written with
|
|
142
|
+
`defineCatalogPage()`; `ds-viewer dev` discovers them, generates their props tables from your real
|
|
143
|
+
component source, and serves the whole thing as one app you never have to add a route for.
|
|
112
144
|
|
|
113
145
|
Each catalog shows **one page at a time**: a persistent, searchable sidebar and the selected page.
|
|
114
146
|
On web the page is kept in the URL fragment (`#Button`), so refresh, deep links, and back/forward
|
|
@@ -133,27 +165,17 @@ Grid columns and list cells are at most 402px wide with 16px padding. A page's `
|
|
|
133
165
|
slots default to `wide`. The catalog's own look comes from `native/catalog/tokens.ts`, never from
|
|
134
166
|
the host app's tokens.
|
|
135
167
|
|
|
136
|
-
```tsx
|
|
137
|
-
import { CatalogExample } from '@ds/native/catalog/CatalogExample';
|
|
138
|
-
// or: import { CatalogFrameworkExample } from '@ds/native/catalog/CatalogFrameworkExample';
|
|
139
|
-
// e.g. render one from a `?ds=1` dev route, mirroring the pattern this template's source project used.
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
For a quick browser preview without a host app, see `native-preview/` — a minimal throwaway Expo
|
|
143
|
-
shell (not part of the reusable template) that renders both via `expo start --web`:
|
|
144
|
-
- `http://localhost:5181/` → CatalogExample ("Native App DS Template")
|
|
145
|
-
- `http://localhost:5181/?catalog=framework` → CatalogFrameworkExample ("Design System DS Catalog")
|
|
146
|
-
|
|
147
168
|
## Adding a component (the recipe)
|
|
148
169
|
|
|
149
|
-
Copy the shape of an existing pair, e.g. `
|
|
150
|
-
- `<Name>.tsx` — `StyleSheet.create`, import tokens from
|
|
151
|
-
|
|
170
|
+
Copy the shape of an existing pair, e.g. `starter-kit/components/Button/` or `starter-kit/components/Badge/`:
|
|
171
|
+
- `<Name>.tsx` — `StyleSheet.create`, import tokens from `../../tokens`, icons from
|
|
172
|
+
`../../icons/Icon.native`.
|
|
152
173
|
- `<Name>.types.ts` — export `<Name>Props` (+ any `<Name>Variant`).
|
|
153
174
|
- `index.ts` — re-export.
|
|
154
175
|
|
|
155
|
-
Then add a `
|
|
156
|
-
|
|
176
|
+
Then add a `<Name>.catalog.tsx` file next to it, written with `defineCatalogPage()` (description,
|
|
177
|
+
a11y, and its content — `id`/`path`/`props` are derived, never authored), and export it from
|
|
178
|
+
`src/ds/components/index.ts` the same way its siblings are. `SectionBlock` shows the
|
|
157
179
|
specimens first, then Guidance, Quick reference, and Props. Give it whichever of these fields
|
|
158
180
|
actually apply:
|
|
159
181
|
- `comparison: { rowLabel, columnLabel, rows, columns, cells, size? }` — when two props genuinely
|
|
@@ -184,22 +206,104 @@ data, not a component — see `ColorsGallery`/`SpacingGallery`/`TypographyGaller
|
|
|
184
206
|
output full width under a "Tokens" label and shows only a Quick reference card with the source
|
|
185
207
|
path.
|
|
186
208
|
|
|
209
|
+
## Keeping the catalog current
|
|
210
|
+
|
|
211
|
+
### Binding examples to props
|
|
212
|
+
|
|
213
|
+
A grid's row or column axis can name the real prop it demonstrates:
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
import { defineCatalogPage, grid } from '@krapwoo/ds-viewer';
|
|
217
|
+
|
|
218
|
+
comparison: grid(
|
|
219
|
+
'Variant', 'State',
|
|
220
|
+
{ prop: 'variant', items: [
|
|
221
|
+
{ key: 'primary', label: 'Primary' },
|
|
222
|
+
{ key: 'ghost', label: 'Ghost' },
|
|
223
|
+
] },
|
|
224
|
+
[{ key: 'default', label: 'Default' }, { key: 'disabled', label: 'Disabled' }],
|
|
225
|
+
(row, column) => <Button variant={row} disabled={column === 'disabled'} onPress={() => {}} />,
|
|
226
|
+
),
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Only a prop typed as a string-literal union (2 or more options) can be bound this way; each item's
|
|
230
|
+
`key` must then be a real option of that prop. An axis that doesn't name a single real prop (e.g.
|
|
231
|
+
"which of three optional props is set," or a continuous `number`) stays a plain array of
|
|
232
|
+
`{ key, label }` — just as valid, and never flagged by `doctor`. `variants`/`states` list items use
|
|
233
|
+
the existing `props` tag (`{ key: 'ghost', props: { variant: 'ghost' }, node: ... }`) the same way.
|
|
234
|
+
Binding is what lets `doctor` tell you precisely when a renamed or removed option leaves a stale
|
|
235
|
+
example behind, instead of you finding out by reading the diff.
|
|
236
|
+
|
|
237
|
+
### `npx ds-viewer doctor`
|
|
238
|
+
|
|
239
|
+
Reads every page with the TypeScript compiler — it never imports a page file or runs app code.
|
|
240
|
+
Human output is grouped by page, each issue with a one-line fix:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
Button
|
|
244
|
+
[error] bound-option-removed: The row "outline" is bound to "variant", which no longer has that option.
|
|
245
|
+
Fix: Delete this row, or update its key to a current option of "variant".
|
|
246
|
+
|
|
247
|
+
0 errors, 1 warning — 38 components (37 with examples), 2 unbound examples.
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- `--json` prints the same report as stable, versioned data: `{ version: 1, summary: { errors, warnings, components, withExamples, unboundExamples }, update: null, issues: [{ id, severity, page?, file?, line?, message, fix }] }`. `update` is always `null` in this release (filled in starting 0.4's update check); `file` is always relative to the project root. `line` is populated for a `page-parse-error` found by typechecking a page file (see below), and always `undefined` for every other issue id — threading a real position through every rule is future work.
|
|
251
|
+
- `--ci` exits 1 only when an error was found — a warning alone never fails CI, and `--ci` never makes a network call.
|
|
252
|
+
- `doctor.strict: true` in `ds-viewer.config.ts` promotes every warning to an error, for both the summary counts and `--ci`'s exit code.
|
|
253
|
+
- A page whose layout data (`specimenSize`, `group`, grid `rows`/`columns`, list items' `key`/`name`/`props`/`group`, `propNotes` keys) isn't a literal — written as something other than a literal value, array, object, or a same-file `const` reference — gets one "page not statically checkable" warning. A page file that fails to parse *or typecheck* is `page-parse-error` instead: every page file is also run through the TypeScript type checker (the same way a component file already is), so a genuine syntax or type error in a page is reported with its line.
|
|
254
|
+
|
|
255
|
+
## Known gaps
|
|
256
|
+
|
|
257
|
+
- The viewer's own dev-mode console warnings only cover `duplicate-page-id` (design §4's "the same rules appear as development warnings naming the page" — scoped to that one rule in 0.3). Bound-axis drift (`bound-option-removed`, `bound-axis-prop-removed`) has no viewer-side warning yet; `npx ds-viewer doctor` (and its `--ci` workflow) is the only place that catches it today.
|
|
258
|
+
|
|
259
|
+
### `npx ds-viewer explain <Page>`
|
|
260
|
+
|
|
261
|
+
Prints each layout decision with its reason, using the exact same pure functions the catalog itself
|
|
262
|
+
renders with:
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
Button
|
|
266
|
+
Grid (Variant × State)
|
|
267
|
+
5 rows × 3 columns (regular); 3 of 3 max regular columns used — fits a 1280px laptop.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Side-by-side placement depends on a real measured height, so by default `explain` reports it as
|
|
271
|
+
"decided in the viewer." Pass `--heights <firstBlockPx>,<secondBlockPx>` to check that decision here
|
|
272
|
+
too, assuming a 1280px laptop (952px content width) the same way `doctor`'s own column-limit check
|
|
273
|
+
does. `--json` is available on `explain` as well.
|
|
274
|
+
|
|
275
|
+
### The `AGENTS.md` section and GitHub Action
|
|
276
|
+
|
|
277
|
+
`npx @krapwoo/ds-viewer init` (either path) appends a short section to `AGENTS.md` between
|
|
278
|
+
`<!-- ds-viewer:start v1 -->`/`<!-- ds-viewer:end -->` markers — creating the file if it doesn't
|
|
279
|
+
exist — telling an AI working on the project to update a page alongside its component, bind
|
|
280
|
+
examples to props, never invent grid cells or groups, and run `doctor`/`explain` before finishing.
|
|
281
|
+
It also writes `.github/workflows/ds-viewer.yml`, a SHA-pinned Action that installs dependencies
|
|
282
|
+
(picking `npm ci`, `pnpm install --frozen-lockfile`, or `yarn install --immutable` from whichever
|
|
283
|
+
lockfile the project has at that moment) and then runs `npx ds-viewer doctor --ci` on every pull
|
|
284
|
+
request. Both are written once; re-running `init` adds
|
|
285
|
+
either one only if it's missing (an `AGENTS.md` without the start marker counts as missing the
|
|
286
|
+
section, even if the file already has other content) and never touches anything outside its own
|
|
287
|
+
markers or its own file.
|
|
288
|
+
|
|
187
289
|
## What's included
|
|
188
290
|
|
|
189
|
-
Tokens · Icons (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
291
|
+
Tokens · Icons (45) · and generic components:
|
|
292
|
+
AnimatedChevron · Avatar · Badge · Banner · BottomSheet · Button · ButtonGroup · Card · Checkbox ·
|
|
293
|
+
Dialog · Divider · Dock · Dropdown · EmptyState · FieldContainer · InputClearButton · InputField ·
|
|
294
|
+
List · ListItem · Loading · Pill · PillRow · ProgressDots · Radio · SearchField · SectionHeader ·
|
|
295
|
+
SegmentedToggle · Shimmer · Surface · Switch · TextArea · Toast · Tooltip · TopNav · UnderlineTabs.
|
|
193
296
|
|
|
194
|
-
Deliberately **not** included
|
|
195
|
-
|
|
297
|
+
Deliberately **not** included: any domain-specific component. Everything here depends only on
|
|
298
|
+
generic navigation/gesture stacks (`BottomSheet`, `Dialog`, `Dock`, `TopNav` are included) — what's
|
|
299
|
+
left out is app content, not app chrome.
|
|
196
300
|
|
|
197
301
|
## Porting to another platform later
|
|
198
302
|
|
|
199
|
-
The token layer (`tokens/`) and icon data (`icons/paths.ts`,
|
|
200
|
-
platform-neutral — no React Native or DOM imports. Only
|
|
201
|
-
`Icon.native.tsx` renderer are RN-specific. When you need this
|
|
202
|
-
(web, desktop via Tauri/Electron, etc.), that's a translation of
|
|
203
|
-
tokens and icon data — a `web/` (or other) tree was built this way once already for this exact
|
|
303
|
+
The token layer (`starter-kit/tokens/`) and icon data (`starter-kit/icons/paths.ts`,
|
|
304
|
+
`starter-kit/icons/types.ts`) are already platform-neutral — no React Native or DOM imports. Only
|
|
305
|
+
`starter-kit/components/` and the `Icon.native.tsx` renderer are RN-specific. When you need this
|
|
306
|
+
design system on another platform (web, desktop via Tauri/Electron, etc.), that's a translation of
|
|
307
|
+
`starter-kit/components/` against those same tokens and icon data — a `web/` (or other) tree was built this way once already for this exact
|
|
204
308
|
template and later removed to keep the template single-platform until it's actually needed; ask
|
|
205
309
|
for that port again when you're ready rather than maintaining an unused second tree in the meantime.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const START_MARKER = '<!-- ds-viewer:start v1 -->';
|
|
4
|
+
const END_MARKER = '<!-- ds-viewer:end -->';
|
|
5
|
+
// Design §4's four numbered lines, verbatim — no heading added: the design names only these four
|
|
6
|
+
// lines as the section's content.
|
|
7
|
+
const SECTION_BODY = [
|
|
8
|
+
"1. When you add or change a component's props or a token, update its \`*.catalog.tsx\` in the same change.",
|
|
9
|
+
"2. Bind examples to props (\`prop\` on grid axes, \`props\` on list items) so drift is detected.",
|
|
10
|
+
'3. Never invent grid cells or groups; author only combinations that exist.',
|
|
11
|
+
"4. Run \`npx ds-viewer doctor\` before finishing and fix every error. Use \`npx ds-viewer explain <Page>\` to check layout; see the README's page-authoring guide.",
|
|
12
|
+
].join('\n');
|
|
13
|
+
/** Design §4 "AGENTS.md section", §1 Ownership, §2 step 3. Appends between the exact markers,
|
|
14
|
+
* creating the file when absent — never touches a byte outside them, and never runs at all once
|
|
15
|
+
* the start marker is already present, even if the rest of the file looks nothing like what this
|
|
16
|
+
* function would have written (design: "An AGENTS.md without the markers counts as missing the
|
|
17
|
+
* section" — the converse is also true: a file WITH the marker is never rewritten in 0.3). */
|
|
18
|
+
export function ensureAgentsFileSection(projectRoot) {
|
|
19
|
+
const agentsPath = path.join(projectRoot, 'AGENTS.md');
|
|
20
|
+
const existing = existsSync(agentsPath) ? readFileSync(agentsPath, 'utf8') : '';
|
|
21
|
+
if (existing.includes(START_MARKER))
|
|
22
|
+
return [];
|
|
23
|
+
// Minor finding (Fable's review): a CRLF `AGENTS.md` must not end up with mixed line endings
|
|
24
|
+
// after the append — the separator and the whole section below use whichever newline the
|
|
25
|
+
// existing file already uses.
|
|
26
|
+
const newline = existing.includes('\r\n') ? '\r\n' : '\n';
|
|
27
|
+
const separator = existing === '' ? '' : existing.endsWith(`${newline}${newline}`) ? '' : existing.endsWith(newline) ? newline : `${newline}${newline}`;
|
|
28
|
+
const body = newline === '\n' ? SECTION_BODY : SECTION_BODY.replace(/\n/g, newline);
|
|
29
|
+
writeFileSync(agentsPath, `${existing}${separator}${START_MARKER}${newline}${body}${newline}${END_MARKER}${newline}`);
|
|
30
|
+
return [agentsPath];
|
|
31
|
+
}
|
package/dist/cli/config.js
CHANGED
|
@@ -55,6 +55,28 @@ export function loadConfig(configPath) {
|
|
|
55
55
|
};
|
|
56
56
|
return { config, warnings };
|
|
57
57
|
}
|
|
58
|
+
/** Design §1 Ownership table — a missing `logo` file is a warning, never a hard error: the
|
|
59
|
+
* sidebar simply falls back to the name-only header. Resolved relative to `projectRoot` with
|
|
60
|
+
* `path.resolve` — the same way `cli/workspace.ts` resolves `config.logo` to actually `require`
|
|
61
|
+
* it — not `path.join`, which nests an absolute `logo` value *under* `projectRoot` instead of
|
|
62
|
+
* using it directly, misreporting a real file as "not found" (Minor finding, Fable's
|
|
63
|
+
* implementation review, cli/config.ts:74 vs cli/workspace.ts:134). A `logo` that resolves
|
|
64
|
+
* outside `projectRoot` is also rejected here, with its own clear warning: `cli/workspace.ts`
|
|
65
|
+
* only watches folders under `projectRoot`, so letting it through would surface later as an
|
|
66
|
+
* unclear Metro bundling error instead. */
|
|
67
|
+
export function validateLogo(projectRoot, logo) {
|
|
68
|
+
if (!logo)
|
|
69
|
+
return {};
|
|
70
|
+
const resolved = path.resolve(projectRoot, logo);
|
|
71
|
+
if (!existsSync(resolved)) {
|
|
72
|
+
return { warning: `Logo not found: ${logo} — showing the name instead.` };
|
|
73
|
+
}
|
|
74
|
+
const relativeToRoot = path.relative(projectRoot, resolved);
|
|
75
|
+
if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
|
|
76
|
+
return { warning: `Logo is outside the project root: ${logo} — showing the name instead.` };
|
|
77
|
+
}
|
|
78
|
+
return { logo };
|
|
79
|
+
}
|
|
58
80
|
/** Loads `<projectRoot>/ds-viewer.config.ts`, attaches where it came from, and prints any
|
|
59
81
|
* "unknown field" warning so every CLI command that resolves a config reports them the same way. */
|
|
60
82
|
export function resolveConfig(projectRoot) {
|
|
@@ -62,5 +84,8 @@ export function resolveConfig(projectRoot) {
|
|
|
62
84
|
const { config, warnings } = loadConfig(configPath);
|
|
63
85
|
for (const warning of warnings)
|
|
64
86
|
console.warn(warning);
|
|
65
|
-
|
|
87
|
+
const { logo, warning: logoWarning } = validateLogo(projectRoot, config.logo);
|
|
88
|
+
if (logoWarning)
|
|
89
|
+
console.warn(logoWarning);
|
|
90
|
+
return { ...config, logo, projectRoot, configPath };
|
|
66
91
|
}
|
package/dist/cli/dev.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, watch } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
+
import { resolveConfig } from "./config.js";
|
|
4
5
|
import { sync } from "./sync.js";
|
|
5
6
|
import { writeWorkspace } from "./workspace.js";
|
|
6
7
|
import { findFreePort } from "./port.js";
|
|
@@ -40,19 +41,34 @@ export function watchTargetFolders(config) {
|
|
|
40
41
|
const patterns = [...config.components, ...(config.tokens ?? []), ...(config.pages ?? [])];
|
|
41
42
|
return [...new Set(patterns.map((pattern) => path.join(config.projectRoot, globBaseFolder(pattern))))].filter((folder) => existsSync(folder));
|
|
42
43
|
}
|
|
44
|
+
/** Re-resolves `<projectRoot>/ds-viewer.config.ts` from disk, syncs from it, and rewrites the
|
|
45
|
+
* generated workspace (entry.tsx, metro.config.js, …) from the freshly-resolved values — unlike
|
|
46
|
+
* plain `sync(config)`, which only ever regenerates `.ds-viewer/generated/` from whatever
|
|
47
|
+
* `ResolvedConfig` it was already given. `dev`'s config-file watcher (below) calls this instead
|
|
48
|
+
* of `sync` directly: without it, a changed `name` or `logo` never reached the generated
|
|
49
|
+
* workspace until `dev` was restarted, because `writeWorkspace` was never called again with the
|
|
50
|
+
* new value (controller end-to-end finding E1). Returns the freshly-resolved config so `dev` can
|
|
51
|
+
* keep watching with it. */
|
|
52
|
+
export function reloadWorkspace(projectRoot) {
|
|
53
|
+
const config = resolveConfig(projectRoot);
|
|
54
|
+
sync(config);
|
|
55
|
+
writeWorkspace(config);
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
43
58
|
/** Design §2 "`dev`": checks the local install, runs `sync`, writes the workspace, starts Expo
|
|
44
59
|
* web on the first free port from 5181 bound to localhost only, and watches the project for
|
|
45
60
|
* component/token/page changes to re-run `sync`. Metro's own HMR updates the rendered component
|
|
46
61
|
* when its source changes, but not the generated props table or page list (Task 17 Step 4 relies
|
|
47
62
|
* on this: editing `Button.tsx` must update its props table, not just hot-reload the component). */
|
|
48
|
-
export async function dev(
|
|
49
|
-
assertLocalInstall(
|
|
63
|
+
export async function dev(initialConfig) {
|
|
64
|
+
assertLocalInstall(initialConfig.projectRoot);
|
|
65
|
+
let config = initialConfig;
|
|
50
66
|
sync(config);
|
|
51
67
|
const workspace = writeWorkspace(config);
|
|
52
68
|
const port = await findFreePort(5181, 5199);
|
|
53
69
|
console.log(`Starting the catalog at http://localhost:${port}`);
|
|
54
70
|
let debounceTimer;
|
|
55
|
-
const
|
|
71
|
+
const onSourceChange = (_eventType, relativePath) => {
|
|
56
72
|
if (relativePath && !isWatchedPath(relativePath))
|
|
57
73
|
return;
|
|
58
74
|
clearTimeout(debounceTimer);
|
|
@@ -65,9 +81,25 @@ export async function dev(config) {
|
|
|
65
81
|
}
|
|
66
82
|
}, 200);
|
|
67
83
|
};
|
|
84
|
+
// A distinct debounce/handler from `onSourceChange` above: a config change needs
|
|
85
|
+
// `reloadWorkspace` (re-resolve, then rewrite the generated workspace too), not just `sync` with
|
|
86
|
+
// the config this closure already captured — otherwise a changed `name`/`logo` never reaches
|
|
87
|
+
// `entry.tsx`/`metro.config.js` until `dev` is restarted (controller end-to-end finding E1).
|
|
88
|
+
let configDebounceTimer;
|
|
89
|
+
const onConfigChange = () => {
|
|
90
|
+
clearTimeout(configDebounceTimer);
|
|
91
|
+
configDebounceTimer = setTimeout(() => {
|
|
92
|
+
try {
|
|
93
|
+
config = reloadWorkspace(config.projectRoot);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.warn(`Could not reload ds-viewer.config.ts after a change: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
}, 200);
|
|
99
|
+
};
|
|
68
100
|
const watchers = [
|
|
69
|
-
...watchTargetFolders(config).map((folder) => watch(folder, { recursive: true },
|
|
70
|
-
...(existsSync(config.configPath) ? [watch(config.configPath,
|
|
101
|
+
...watchTargetFolders(config).map((folder) => watch(folder, { recursive: true }, onSourceChange)),
|
|
102
|
+
...(existsSync(config.configPath) ? [watch(config.configPath, onConfigChange)] : []),
|
|
71
103
|
];
|
|
72
104
|
for (const watcher of watchers) {
|
|
73
105
|
watcher.on('error', (error) => console.warn(`File watcher error: ${error.message}`));
|
|
@@ -83,4 +115,17 @@ export async function dev(config) {
|
|
|
83
115
|
watcher.close();
|
|
84
116
|
process.exitCode = code ?? 0;
|
|
85
117
|
});
|
|
118
|
+
// Node's default SIGTERM/SIGINT behavior exits this process without touching its children —
|
|
119
|
+
// left alone, a signal sent only to `dev`'s own pid (not its process group) would leave the
|
|
120
|
+
// spawned `expo start` running, still bound to its port (Important finding, Fable correction
|
|
121
|
+
// pass: this is exactly what would hang `check:catalog`'s driving script, Task 11, if `dev`
|
|
122
|
+
// didn't clean up on its own).
|
|
123
|
+
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
124
|
+
process.on(signal, () => {
|
|
125
|
+
for (const watcher of watchers)
|
|
126
|
+
watcher.close();
|
|
127
|
+
child.kill(signal);
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
86
131
|
}
|