@hirobius/design-system 0.19.0 → 0.20.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/CONSUMING.md +9 -6
- package/DESIGN.md +7 -5
- package/README.md +14 -11
- package/codemods/hds-prefix.mjs +231 -0
- package/codemods/jsx-fold.mjs +515 -0
- package/codemods/not-found-pattern.mjs +71 -0
- package/codemods/patterns-subpath.mjs +242 -81
- package/codemods/patterns-subpath.names.json +41 -15
- package/codemods/removed-0.20.json +194 -0
- package/codemods/tile-grid.mjs +283 -0
- package/codemods/unrewritable.mjs +291 -0
- package/dist/chunks/DemoStateContext-Cz5TmrYu.js +32 -0
- package/dist/chunks/empty-state-BBqhCx0S.js +1217 -0
- package/dist/chunks/{form-BvNNnJ0t.js → form-COP3vK9_.js} +29 -27
- package/dist/contexts.js +157 -78
- package/dist/form.js +1 -1
- package/dist/hirobius-ui.js +1826 -5503
- package/dist/manifest.js +12 -13
- package/dist/patterns.js +820 -561
- package/dist/styles.css +1 -1
- package/dist/tokens.css +1 -1
- package/dist/tokens.js +29 -8
- package/dist/types/src/app/components/avatar-group.d.ts +1 -2
- package/dist/types/src/app/components/badge.d.ts +8 -3
- package/dist/types/src/app/components/blockquote.d.ts +1 -2
- package/dist/types/src/app/components/box-sx.d.ts +110 -13
- package/dist/types/src/app/components/box.d.ts +6 -3
- package/dist/types/src/app/components/breadcrumb.d.ts +0 -5
- package/dist/types/src/app/components/button.d.ts +33 -4
- package/dist/types/src/app/components/card.d.ts +7 -8
- package/dist/types/src/app/components/combobox.d.ts +47 -11
- package/dist/types/src/app/components/container.d.ts +2 -2
- package/dist/types/src/app/components/data-table-section.d.ts +1 -1
- package/dist/types/src/app/components/dialog.d.ts +7 -1
- package/dist/types/src/app/components/disclosure.d.ts +1 -5
- package/dist/types/src/app/components/field.d.ts +1 -2
- package/dist/types/src/app/components/grid.d.ts +10 -6
- package/dist/types/src/app/components/hds-tooltip.d.ts +12 -2
- package/dist/types/src/app/components/inline-code.d.ts +1 -2
- package/dist/types/src/app/components/input.d.ts +8 -5
- package/dist/types/src/app/components/kbd.d.ts +1 -2
- package/dist/types/src/app/components/metadata-list.d.ts +1 -2
- package/dist/types/src/app/components/page-header.d.ts +0 -2
- package/dist/types/src/app/components/popover.d.ts +8 -0
- package/dist/types/src/app/components/progress.d.ts +33 -6
- package/dist/types/src/app/components/segmented-control.d.ts +2 -26
- package/dist/types/src/app/components/select.d.ts +10 -1
- package/dist/types/src/app/components/skeleton.d.ts +2 -3
- package/dist/types/src/app/components/spinner.d.ts +1 -2
- package/dist/types/src/app/components/stack.d.ts +4 -1
- package/dist/types/src/app/components/stat.d.ts +1 -2
- package/dist/types/src/app/components/status-dot.d.ts +18 -4
- package/dist/types/src/app/components/status-list-item.d.ts +1 -2
- package/dist/types/src/app/components/surface.d.ts +2 -3
- package/dist/types/src/app/components/switcher.d.ts +1 -1
- package/dist/types/src/app/components/table.d.ts +1 -15
- package/dist/types/src/app/components/tag.d.ts +0 -6
- package/dist/types/src/app/components/text.d.ts +1 -2
- package/dist/types/src/app/components/textarea.d.ts +1 -2
- package/dist/types/src/app/components/toast.d.ts +1 -2
- package/dist/types/src/app/components/tooltip.d.ts +1 -1
- package/dist/types/src/app/context/DemoStateContext.d.ts +1 -1
- package/dist/types/src/app/context/RouterContext.d.ts +5 -5
- package/dist/types/src/app/data/manifest-types.d.ts +6 -5
- package/dist/types/src/app/design-system/generated-token-refs.d.ts +8 -0
- package/dist/types/src/app/design-system/generated-token-values.d.ts +1 -0
- package/dist/types/src/app/design-system/tokens.d.ts +22 -3
- package/dist/types/src/icons.d.ts +1 -1
- package/dist/types/src/index.d.ts +33 -124
- package/dist/types/src/patterns.d.ts +20 -25
- package/dist/variables.css +6 -0
- package/docs/CONSUMING.md +21 -13
- package/hirobius.tokens.json +48 -15
- package/llms.txt +19 -20
- package/package.json +18 -22
- package/public/hds-manifest.json +2400 -6488
- package/public/llms/components.txt +41 -423
- package/public/llms/layout.txt +4 -4
- package/public/llms-full.txt +67 -448
- package/public/llms.txt +19 -20
- package/src/app/data/component-api.json +1379 -3271
- package/dist/chunks/LanguageContext-CH_bwzCq.js +0 -83
- package/dist/chunks/TokenDisplayContext-evZi_214.js +0 -18
- package/dist/chunks/activity-feed-DIMeUJpW.js +0 -21950
- package/dist/types/src/app/components/activity-feed.d.ts +0 -73
- package/dist/types/src/app/components/app-shell.d.ts +0 -40
- package/dist/types/src/app/components/aspect-ratio.d.ts +0 -25
- package/dist/types/src/app/components/bleed.d.ts +0 -39
- package/dist/types/src/app/components/button-group.d.ts +0 -22
- package/dist/types/src/app/components/calendar.d.ts +0 -22
- package/dist/types/src/app/components/carousel.d.ts +0 -26
- package/dist/types/src/app/components/center.d.ts +0 -36
- package/dist/types/src/app/components/cinematic-link.d.ts +0 -18
- package/dist/types/src/app/components/circular-progress.d.ts +0 -28
- package/dist/types/src/app/components/cluster.d.ts +0 -39
- package/dist/types/src/app/components/command-palette.d.ts +0 -46
- package/dist/types/src/app/components/component-instance-matrix.d.ts +0 -27
- package/dist/types/src/app/components/context-menu.d.ts +0 -78
- package/dist/types/src/app/components/cover.d.ts +0 -43
- package/dist/types/src/app/components/date-input.d.ts +0 -31
- package/dist/types/src/app/components/date-range-input.d.ts +0 -34
- package/dist/types/src/app/components/date-time-input.d.ts +0 -30
- package/dist/types/src/app/components/doc-link-card.d.ts +0 -32
- package/dist/types/src/app/components/error-boundary.d.ts +0 -25
- package/dist/types/src/app/components/file-input.d.ts +0 -30
- package/dist/types/src/app/components/foundation-swatch.d.ts +0 -45
- package/dist/types/src/app/components/frame.d.ts +0 -38
- package/dist/types/src/app/components/heading-stack.d.ts +0 -52
- package/dist/types/src/app/components/history-card.d.ts +0 -12
- package/dist/types/src/app/components/hover-card.d.ts +0 -46
- package/dist/types/src/app/components/icon-button.d.ts +0 -22
- package/dist/types/src/app/components/image-lightbox.d.ts +0 -14
- package/dist/types/src/app/components/input-group.d.ts +0 -23
- package/dist/types/src/app/components/multi-selector.d.ts +0 -40
- package/dist/types/src/app/components/nav-group.d.ts +0 -29
- package/dist/types/src/app/components/nav-item.d.ts +0 -33
- package/dist/types/src/app/components/not-found-pattern.d.ts +0 -1
- package/dist/types/src/app/components/overflow-list.d.ts +0 -21
- package/dist/types/src/app/components/selectable-card.d.ts +0 -29
- package/dist/types/src/app/components/side-nav.d.ts +0 -23
- package/dist/types/src/app/components/sketch.d.ts +0 -19
- package/dist/types/src/app/components/stacked-card-rail.d.ts +0 -32
- package/dist/types/src/app/components/stepper-field.d.ts +0 -12
- package/dist/types/src/app/components/stepper.d.ts +0 -33
- package/dist/types/src/app/components/text-lockup.d.ts +0 -38
- package/dist/types/src/app/components/tile-grid.d.ts +0 -17
- package/dist/types/src/app/components/time-input.d.ts +0 -24
- package/dist/types/src/app/components/toggle-button.d.ts +0 -32
- package/dist/types/src/app/components/token.d.ts +0 -90
- package/dist/types/src/app/components/tokenUtils.d.ts +0 -38
- package/dist/types/src/app/components/tokenizer.d.ts +0 -24
- package/dist/types/src/app/components/toolbar.d.ts +0 -36
- package/dist/types/src/app/components/top-nav.d.ts +0 -23
- package/dist/types/src/app/components/tree-list.d.ts +0 -30
- package/dist/types/src/app/data/nav-model.d.ts +0 -39
- package/dist/types/src/app/hooks/useFocusVisible.d.ts +0 -9
- package/dist/types/src/app/layouts/CaseStudyLayout.d.ts +0 -20
- package/dist/types/src/app/layouts/DocLayout.d.ts +0 -14
- package/dist/types/src/app/layouts/EmbeddedDocLayoutContext.d.ts +0 -6
- package/dist/types/src/app/layouts/HdsDocsShell.d.ts +0 -72
- package/dist/types/src/app/layouts/HdsSystemDocLayout.d.ts +0 -14
- package/dist/types/src/app/lib/hds-search.d.ts +0 -45
- package/dist/types/src/app/lib/nav-active.d.ts +0 -16
- package/dist/types/src/app/lib/navLevels.d.ts +0 -4
package/CONSUMING.md
CHANGED
|
@@ -43,7 +43,7 @@ Available subpaths:
|
|
|
43
43
|
|
|
44
44
|
| Import | What |
|
|
45
45
|
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
-
| `@hirobius/design-system` |
|
|
46
|
+
| `@hirobius/design-system` | The core components and templates (the `pattern` tier is only in `/patterns`) |
|
|
47
47
|
| `@hirobius/design-system/tokens.css` | Token CSS variables (import once at app root) |
|
|
48
48
|
| `@hirobius/design-system/tokens` | Token values as TS constants |
|
|
49
49
|
| `@hirobius/design-system/cn` | `cn()` class-merge helper |
|
|
@@ -51,8 +51,8 @@ Available subpaths:
|
|
|
51
51
|
| `@hirobius/design-system/contexts` | Theme / language / tenant / font providers |
|
|
52
52
|
| `@hirobius/design-system/brand` | Palette → HDS-semantic overlay bridge (static / SSR / Astro; see [`docs/CONSUMING.md` §12](docs/CONSUMING.md#12-static-astro-sites--the-brand-overlay-bridge)) |
|
|
53
53
|
| `@hirobius/design-system/scroll` | Opt-in scroll-motion primitives — `SmoothScroll` (Lenis), `useScrollProgress` (Motion). Requires the optional peer `lenis`. See [`docs/CONSUMING.md` §13](docs/CONSUMING.md#13-scroll-motion-the-scroll-subpath) |
|
|
54
|
-
| `@hirobius/design-system/patterns` | The
|
|
55
|
-
| `@hirobius/design-system/icons` | Curated Lucide icon set for `
|
|
54
|
+
| `@hirobius/design-system/patterns` | The `pattern`-tier components (nav shells, feeds, rails, pickers — see hds#254). The only entry that exports them: 0.20.0 removed their root re-exports (the `hds-patterns-subpath` codemod, see MIGRATIONS.md). |
|
|
55
|
+
| `@hirobius/design-system/icons` | Curated Lucide icon set for `Icon`, and so for `Button iconOnly` (`Ellipsis`, `Pencil`, `Trash2`, `X`, …). Names listed in the manifest `iconSet`. |
|
|
56
56
|
| `@hirobius/design-system/static.css` | CSS-only static-primitive layer — `.hds-badge`/`.hds-card`/`.hds-alert`/`.hds-divider`/`.hds-tag` classes, no React. See [`docs/CONSUMING.md` §14](docs/CONSUMING.md#14-css-only-static-primitives--badgecardalertdividertag-with-no-react) |
|
|
57
57
|
|
|
58
58
|
The package is **ESM-only**, so consume it with a modern bundler (Vite, Next.js,
|
|
@@ -113,8 +113,11 @@ Releases follow [semver](https://semver.org/) and are tracked in
|
|
|
113
113
|
pnpm update @hirobius/design-system # latest within your version range
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
For a
|
|
117
|
-
|
|
116
|
+
For a breaking release, bump the version explicitly and review the CHANGELOG
|
|
117
|
+
entry. Before 1.0 a breaking change (a removed export or token) ships in a 0.x
|
|
118
|
+
minor, which a caret range such as `^0.19` never picks up; from 1.0 on it ships
|
|
119
|
+
in a major. Each removal is listed in [MIGRATIONS.md](MIGRATIONS.md) with its
|
|
120
|
+
replacement and, where one applies, a codemod (ADR-014).
|
|
118
121
|
|
|
119
122
|
---
|
|
120
123
|
|
|
@@ -122,7 +125,7 @@ CHANGELOG entry. Token or export changes are released as majors.
|
|
|
122
125
|
|
|
123
126
|
```bash
|
|
124
127
|
pnpm changeset add # record a patch/minor/major bump + notes
|
|
125
|
-
pnpm changeset:version # apply bumps + regenerate CHANGELOG.md
|
|
128
|
+
pnpm changeset:version # apply bumps + regenerate CHANGELOG.md + refresh docs/api/api-baseline.json
|
|
126
129
|
# commit + push to main → the Release workflow publishes to public npm
|
|
127
130
|
```
|
|
128
131
|
|
package/DESIGN.md
CHANGED
|
@@ -90,7 +90,7 @@ Depth is communicated through 4 elevation roles bundled by `semantic.elevation.*
|
|
|
90
90
|
|
|
91
91
|
Cards default to `flat`. They lift to `raised` only on interactive hover or when explicitly elevated above siblings. Never combine `raised` with a border — depth is one mechanism (border OR shadow), not both stacked.
|
|
92
92
|
|
|
93
|
-
Overlays (Dialog, AlertDialog, Menu,
|
|
93
|
+
Overlays (Dialog, AlertDialog, Menu, Popover, Select, Tooltip) portal into the nearest `data-hds` scope, so they inherit its theme (for example `<div data-hds data-theme="dark">`); pass `container` on the Content part to override.
|
|
94
94
|
|
|
95
95
|
## Motion
|
|
96
96
|
|
|
@@ -121,7 +121,7 @@ Default most interactive feedback to `productive` (150ms, decelerate). Reserve `
|
|
|
121
121
|
|
|
122
122
|
| Token | Value | Purpose |
|
|
123
123
|
| --- | --- | --- |
|
|
124
|
-
| `semantic.motion.distance` | `24px` |
|
|
124
|
+
| `semantic.motion.distance` | `24px` | Scroll-reveal translateY travel distance. Equals semantic.space.scale.md today, but names motion travel, not a spacing tier (hds#242). |
|
|
125
125
|
<!-- auto:end:motion -->
|
|
126
126
|
|
|
127
127
|
## Components
|
|
@@ -129,7 +129,7 @@ Default most interactive feedback to `productive` (150ms, decelerate). Reserve `
|
|
|
129
129
|
<!-- auto:start:components -->
|
|
130
130
|
| Component | Radius | States | Guidance |
|
|
131
131
|
| --- | --- | --- | --- |
|
|
132
|
-
| **Buttons** (`Button`) | `8px` (`semantic.radius.action`) | default · hover · focus · active · disabled · loading | Three variants: primary (accent-filled), secondary (outline), tertiary (ghost). Primary uses `semantic.accent.*` ramp per state. Icon buttons (`
|
|
132
|
+
| **Buttons** (`Button`) | `8px` (`semantic.radius.action`) | default · hover · focus · active · disabled · loading | Three variants: primary (accent-filled), secondary (outline), tertiary (ghost). Primary uses `semantic.accent.*` ramp per state. Pressed is a 5% `semantic.color.state.pressed.overlay` wash over the fill (`role.pressed-overlay`: black in light, white in dark), not a brightness filter. Icon-only buttons (`iconOnly`) follow the same token surface. Icon-only actions use `Button iconOnly` with a `label` and an `Icon` from `@hirobius/design-system/icons` in `iconLeft`. |
|
|
133
133
|
| **Inputs** (`Input`) | `8px` (`semantic.radius.action`) | default · focus · filled · error · disabled · loading | Border-driven treatment; no filled background by default. Focus uses `semantic.color.border.accent` plus a 2px outline offset. Error swaps to `component.input.borderError`. |
|
|
134
134
|
| **Cards** (`Card`) | `12px` (`rounded-lg`, role radius + 4px) | default · hover (optional parallax) · pressed (when interactive) | Cards default to `elevation.flat` (1px border `border.subtle`, no shadow). Interactive cards lift to `elevation.raised` (shadow.subtle, no border) on hover. Bind via `semantic.elevation.{role}` — never raw box-shadow values. Radius: `rounded-lg` (role radius + 4 px, follows the tenant knob) — never a hard-coded value. Padding: `var(--semantic-space-surface-padding)`. Title: `heading3`. Meta: `caption` + `var(--semantic-color-content-secondary)`. Hover (interactive): `scale(1.02)` transform + lift to raised. Never: gradients, glow, frosted glass, tinted surfaces, decorative overlays, or inner shadows. |
|
|
135
135
|
| **Badges** (`Badge`) | `8px` (`primitive.radius.4`) | neutral · accent · feedback (error/success/warning/info) | Single-line status markers. Feedback colors come from `semantic.color.feedback.*`. Never used as decorative chrome. |
|
|
@@ -176,11 +176,13 @@ Pick by what the thing is. The first four rows are for a headline number or a st
|
|
|
176
176
|
| `Stat` | An inline number inside prose or a dense list. | `MetricTiles` when the number stands alone in a row of its own. |
|
|
177
177
|
| `Card.Metric` | A metric inside an existing `Card` only. | `MetricTiles` when there is no enclosing `Card`. |
|
|
178
178
|
| `StatusTile` | State with notes and a trailing badge, never a number. | `MetricTiles` when the tile is a number; `StatusTile` never carries a value. |
|
|
179
|
-
| `Card` | A titled or interactive content object (header, body, footer,
|
|
179
|
+
| `Card` | A titled or interactive content object (header, body, footer, `selectable`). | `Surface` when there is no title and nothing to press. |
|
|
180
180
|
| `Surface` | A plain padded background around content that has no header of its own. | `Card` when the content is titled or interactive. |
|
|
181
181
|
| `DestructiveSection` | The one irreversible action on a screen, with an explanation and a confirm. | `FormActions` `destructive` slot for a delete beside a form's own buttons. |
|
|
182
182
|
| `DataTableSection` | A titled table with a toolbar, row actions and an empty state. | `Table` alone when there is no heading, toolbar or empty state to show. |
|
|
183
183
|
|
|
184
|
+
Three more needs are props on an existing component, not components of their own. A card people pick from a set is a `Card` with `selectable`, `selected` and `onSelectedChange`, announced as a checkbox. A wrapping row of equal tiles that are not headline numbers is a `Grid` with `layout="auto-fill"` and `minItemWidth`. A state shown only as a colored marker, with no notes, is a `Badge` with `dot` and a `label` that names the state.
|
|
185
|
+
|
|
184
186
|
`Card` and `Surface` share one container radius, `rounded-lg` (`role.radius` + 4 px: 12 px by default, 4 px under `brutalist-demo`). Dialogs and alert dialogs use it too. Never read `--component-card-radius` for a container; it ignores the tenant.
|
|
185
187
|
|
|
186
188
|
`MetricTiles` has `min(tiles, 4)` columns, so a row never leaves an empty column, and every tile has one fixed min-height with the value in `heading2`, the label as an eyebrow and the sub line in `caption`. Its tone comes from the fixed feedback vocabulary: `neutral | success | warning | danger | info`.
|
|
@@ -215,6 +217,6 @@ Unresolved rules that the live repo cannot yet answer confidently are tracked in
|
|
|
215
217
|
<!-- auto:start:build-meta -->
|
|
216
218
|
---
|
|
217
219
|
|
|
218
|
-
> Generated from `hirobius.tokens.json` (
|
|
220
|
+
> Generated from `hirobius.tokens.json` (385 tokens) and `public/hds-manifest.json` by `scripts/build-design-md.mjs`.
|
|
219
221
|
> Hand-edit `DESIGN.source.md`; this file (`DESIGN.md`) is overwritten by `pnpm tokens`.
|
|
220
222
|
<!-- auto:end:build-meta -->
|
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ pnpm add @hirobius/design-system
|
|
|
12
12
|
|
|
13
13
|
<!-- auto:start:front-door-counts -->
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
15
|
+
- **54** public component modules, exported from `src/index.ts`
|
|
16
|
+
- **385** DTCG tokens in `hirobius.tokens.json`, compiled to CSS variables and TypeScript constants
|
|
17
|
+
- **340** Storybook stories in **71** story files
|
|
18
18
|
|
|
19
19
|
<!-- auto:end:front-door-counts -->
|
|
20
20
|
|
|
@@ -29,9 +29,12 @@ Agents: install the consumer skill with `npx skills add hirobius/hds --skill hds
|
|
|
29
29
|
|
|
30
30
|
<!-- auto:start:consumer-usage -->
|
|
31
31
|
|
|
32
|
-
The Ops dashboard (`hirobius/ops`) is the only
|
|
32
|
+
The Ops dashboard (`hirobius/ops`) is the only product app that uses components: **33** of its source files import from `@hirobius/design-system`, using **25** distinct components. The two screenshots below are Ops pages rendered from its `main` (measured at commit `76ef65e`).
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
| Consumer kind | Count | How it uses HDS |
|
|
35
|
+
| ----------------- | ----- | ----------------------------------------- |
|
|
36
|
+
| Product apps | 1 | Components and tokens |
|
|
37
|
+
| Token-level sites | 4 | Tokens and CSS only, no component imports |
|
|
35
38
|
|
|
36
39
|
[](docs/images/ops-library.png)
|
|
37
40
|
|
|
@@ -39,7 +42,7 @@ Other consumers: the split into product apps and token-level sites is not yet co
|
|
|
39
42
|
|
|
40
43
|
<!-- auto:end:consumer-usage -->
|
|
41
44
|
|
|
42
|
-
The numbers come from `pnpm consumer:usage --root <ops checkout>`, which measures that checkout and records its commit in `docs/data/consumer-usage.json
|
|
45
|
+
The numbers come from `pnpm consumer:usage --root <ops checkout>`, which measures that checkout's `src/` and records its commit in `docs/data/consumer-usage.json` (pass `--commit <sha>` when the root is a `git archive` export); without a root it reuses the committed snapshot. A deprecated alias counts as the component it points to (`HdsCheckbox` as `Checkbox`). The components that Ops's page- and video-clone prompts tell generated code to import are listed there as `promptContracts` and are not counted here. `scripts/__tests__/consumer-usage.test.mjs` fails if this section drifts from that snapshot. The product-app and token-level-site split is a declared figure in that file, not a measurement. It was confirmed on 2026-10-01 (hds#389: there is no second product app), so `consumersConfirmed` there is true and the table above shows it.
|
|
43
46
|
|
|
44
47
|
## Using the published package
|
|
45
48
|
|
|
@@ -51,7 +54,7 @@ guide: **[docs/CONSUMING.md](docs/CONSUMING.md)**.
|
|
|
51
54
|
> `@hirobius/design-system` entry under GitHub Packages
|
|
52
55
|
> (`/pkgs/npm/design-system`). That listing is **frozen and no longer updated** —
|
|
53
56
|
> publishing moved to the public npm registry in
|
|
54
|
-
> [#39](https://github.com/hirobius/
|
|
57
|
+
> [#39](https://github.com/hirobius/hds/pull/39). Always
|
|
55
58
|
> install from npm:
|
|
56
59
|
> **[npmjs.com/package/@hirobius/design-system](https://www.npmjs.com/package/@hirobius/design-system)**.
|
|
57
60
|
|
|
@@ -153,7 +156,7 @@ pnpm build-storybook
|
|
|
153
156
|
|
|
154
157
|
HDS is built around three structural rules:
|
|
155
158
|
|
|
156
|
-
- **Strict semantics** — public surfaces prefer system primitives such as `Stack`, `Grid`, `Surface`, and `
|
|
159
|
+
- **Strict semantics** — public surfaces prefer system primitives such as `Stack`, `Grid`, `Surface`, and `Text` instead of raw layout divs or ad hoc CSS.
|
|
157
160
|
- **Polymorphism** — primitives preserve semantic HTML while staying composable through governed APIs such as `forwardRef`, `as`, and layout slots.
|
|
158
161
|
- **12-column grid** — page structure follows a consistent editorial grid: readable center columns, intentional breakout zones, and explicit `gap` ownership rather than one-off spacing math.
|
|
159
162
|
|
|
@@ -173,10 +176,10 @@ What CI and the hooks check today, and nothing more:
|
|
|
173
176
|
|
|
174
177
|
- **Contrast:** `scripts/check-contrast.mjs` enforces WCAG AA contrast on the core token pairs, in light and dark. It runs in CI and the pre-commit hook.
|
|
175
178
|
- **Lint:** `jsx-a11y` rules run inside ESLint with zero warnings allowed.
|
|
176
|
-
- **Focus:** `scripts/check-focus-states.mjs` (`pnpm check:focus`) audits focus styles on interactive components. It runs in
|
|
177
|
-
- **Storybook:** the `addon-a11y` panel shows axe results for each story while you review it.
|
|
179
|
+
- **Focus:** `scripts/check-focus-states.mjs` (`pnpm check:focus`) audits focus styles on interactive components. It runs in `pretest` under `pnpm test`, so in the pre-push hook and in CI, and passes with 0 violations.
|
|
180
|
+
- **Storybook:** the `addon-a11y` panel shows axe results for each story while you review it. The gate is `scripts/check-storybook-axe.mjs`: CI scans every built story in light and dark, including the open Select and Combobox, and fails on serious or critical violations outside its allowlist (`scripts/axe-allowlist.json`). The allowlist has one entry: `aria-hidden-focus` on the open Select story, where Radix Select hides the page around its listbox while focus stays trapped inside it (hds#407).
|
|
178
181
|
|
|
179
|
-
|
|
182
|
+
Screen-reader output is checked by `tests/primitive-contracts/screen-reader.contract.test.tsx`, which runs in `pnpm test` and walks Dialog, Menu, Select, Combobox, Table and Alert with a virtual screen-reader (`@guidepup/virtual-screen-reader`) under jsdom, asserting the role, name and state phrases it reads and the alert's live-region announcement; it reads the jsdom accessibility tree, not what a desktop or mobile screen-reader says.
|
|
180
183
|
|
|
181
184
|
## Visual direction: Editorial Enterprise
|
|
182
185
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hds-prefix codemod (hds#389 R1, follows hds#315)
|
|
4
|
+
*
|
|
5
|
+
* 0.20.0 removed the six `Hds*` aliases from the package root: HdsCheckbox,
|
|
6
|
+
* HdsRadio, HdsSelect, HdsSlider, HdsToggle and HdsTooltip. Each is the same
|
|
7
|
+
* component under its bare name. This rewrites the import specifier only, and
|
|
8
|
+
* keeps the name the file already uses:
|
|
9
|
+
*
|
|
10
|
+
* - `import { HdsCheckbox } from '@hirobius/design-system'` becomes
|
|
11
|
+
* `import { Checkbox as HdsCheckbox } from '@hirobius/design-system'`;
|
|
12
|
+
* - `HdsSelect as Pick` becomes `Select as Pick`; `type` modifiers stay;
|
|
13
|
+
* - `export { HdsToggle } from '…'` becomes `export { Toggle as HdsToggle } from '…'`,
|
|
14
|
+
* so the file's own export name does not change.
|
|
15
|
+
*
|
|
16
|
+
* No other text in the file changes: JSX, values, `typeof`, spreads, string
|
|
17
|
+
* keys, comments (including comments inside the import braces) and imports from
|
|
18
|
+
* any other module stay as written, so every reference still resolves to the
|
|
19
|
+
* same component. Renaming the local binding to the bare name afterwards is
|
|
20
|
+
* optional and a manual edit. Only named imports and re-exports whose source is
|
|
21
|
+
* exactly the package root are rewritten, and running it twice changes nothing.
|
|
22
|
+
*
|
|
23
|
+
* node codemods/hds-prefix.mjs [--root <dir>] [--check] [--dry-run]
|
|
24
|
+
*
|
|
25
|
+
* --root <dir> directory to scan (default: current directory)
|
|
26
|
+
* --check write nothing; exit 1 when a rewrite is needed, or when a root
|
|
27
|
+
* star re-export, or a namespace import or dynamic `import()`,
|
|
28
|
+
* `require()` or `vi.mock`/`jest.mock` of the root that reads an
|
|
29
|
+
* Hds* name off the module, or a file that cannot be read to its
|
|
30
|
+
* end (codemods/unrewritable.mjs), needs a manual look
|
|
31
|
+
* --dry-run write nothing; print each changed line before (-) and after (+), exit 0
|
|
32
|
+
*/
|
|
33
|
+
import { readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
34
|
+
import { relative, resolve, join } from 'node:path';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
import { findUnrewritable as findHidden, maskSource } from './unrewritable.mjs';
|
|
37
|
+
|
|
38
|
+
const ROOT_PKG = '@hirobius/design-system';
|
|
39
|
+
const EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts']);
|
|
40
|
+
const SKIP_DIRS = new Set([
|
|
41
|
+
'node_modules',
|
|
42
|
+
'dist',
|
|
43
|
+
'build',
|
|
44
|
+
'.git',
|
|
45
|
+
'.next',
|
|
46
|
+
'.vercel',
|
|
47
|
+
'coverage',
|
|
48
|
+
'storybook-static',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/** The aliases 0.19.1 exported from the root, and the bare name each pointed at. */
|
|
52
|
+
export const RENAMES = Object.freeze({
|
|
53
|
+
HdsCheckbox: 'Checkbox',
|
|
54
|
+
HdsRadio: 'Radio',
|
|
55
|
+
HdsSelect: 'Select',
|
|
56
|
+
HdsSlider: 'Slider',
|
|
57
|
+
HdsToggle: 'Toggle',
|
|
58
|
+
HdsTooltip: 'Tooltip',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const ID = '[A-Za-z_$][\\w$]*';
|
|
62
|
+
// Matched on the masked source (codemods/unrewritable.mjs), where comments are
|
|
63
|
+
// blank and the specifier is a run of spaces between its quotes:
|
|
64
|
+
// at a line start or after `;`: import|export, type?, default?, { body }, from, quote.
|
|
65
|
+
const namedRe = () =>
|
|
66
|
+
new RegExp(
|
|
67
|
+
`(?:^|(?<=;))[ \\t]*(?:import|export)(?:\\s+type)?\\s*(?:${ID}\\s*,\\s*)?\\{([^}]*)\\}\\s*from\\s*(['"]) {${ROOT_PKG.length}}\\2`,
|
|
68
|
+
'gm',
|
|
69
|
+
);
|
|
70
|
+
// One specifier, between commas: `type`? name, then `as alias`?
|
|
71
|
+
const specRe = new RegExp(`^(\\s*(?:type\\s+)?)(${ID})(\\s+as\\s+${ID})?\\s*$`);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Pure transform of one file's source: rewrites the Hds* specifiers of each
|
|
75
|
+
* named import or re-export from the package root, and nothing else.
|
|
76
|
+
* @param {string} source
|
|
77
|
+
* @param {Record<string, string>} [renames]
|
|
78
|
+
* @returns {{ source: string, changed: boolean, sites: number, renamed: string[],
|
|
79
|
+
* edits: { line: number, before: string, after: string }[] }}
|
|
80
|
+
*/
|
|
81
|
+
export function transformSource(source, renames = RENAMES) {
|
|
82
|
+
const { code } = maskSource(source);
|
|
83
|
+
const renamed = new Set();
|
|
84
|
+
const changes = [];
|
|
85
|
+
let sites = 0;
|
|
86
|
+
|
|
87
|
+
for (const m of code.matchAll(namedRe())) {
|
|
88
|
+
const quoteAt = m.index + m[0].length - ROOT_PKG.length - 2;
|
|
89
|
+
if (!source.startsWith(ROOT_PKG, quoteAt + 1)) continue;
|
|
90
|
+
const bodyAt = m.index + m[0].indexOf('{') + 1;
|
|
91
|
+
let touched = false;
|
|
92
|
+
let at = bodyAt;
|
|
93
|
+
for (const spec of m[1].split(',')) {
|
|
94
|
+
const s = specRe.exec(spec);
|
|
95
|
+
const bare = s && Object.hasOwn(renames, s[2]) ? renames[s[2]] : undefined;
|
|
96
|
+
if (bare) {
|
|
97
|
+
const name = s[2];
|
|
98
|
+
const nameAt = at + s[1].length;
|
|
99
|
+
// Keep the name the file uses: `HdsCheckbox` -> `Checkbox as HdsCheckbox`.
|
|
100
|
+
changes.push({
|
|
101
|
+
start: nameAt,
|
|
102
|
+
end: nameAt + name.length,
|
|
103
|
+
text: s[3] ? bare : `${bare} as ${name}`,
|
|
104
|
+
});
|
|
105
|
+
renamed.add(name);
|
|
106
|
+
touched = true;
|
|
107
|
+
}
|
|
108
|
+
at += spec.length + 1;
|
|
109
|
+
}
|
|
110
|
+
if (touched) sites++;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let out = source;
|
|
114
|
+
for (const r of changes.sort((a, b) => b.start - a.start))
|
|
115
|
+
out = out.slice(0, r.start) + r.text + out.slice(r.end);
|
|
116
|
+
|
|
117
|
+
if (out === source) return { source, changed: false, sites: 0, renamed: [], edits: [] };
|
|
118
|
+
const before = source.split('\n');
|
|
119
|
+
const after = out.split('\n');
|
|
120
|
+
const edits = [];
|
|
121
|
+
before.forEach((line, i) => {
|
|
122
|
+
if (line !== after[i]) edits.push({ line: i + 1, before: line, after: after[i] });
|
|
123
|
+
});
|
|
124
|
+
return { source: out, changed: true, sites, renamed: [...renamed].sort(), edits };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* What the codemod cannot rewrite (codemods/unrewritable.mjs): a star re-export
|
|
129
|
+
* of the root, whose importers are in other files; and a namespace import or a
|
|
130
|
+
* dynamic `import()`, `require()`, `vi.mock` or `jest.mock` of the root in a file
|
|
131
|
+
* that reads an Hds* name off something (`HDS.HdsSlider`, `m['HdsToggle']`,
|
|
132
|
+
* `const { HdsRadio } = …`). The alias binding the codemod writes is not a read.
|
|
133
|
+
*/
|
|
134
|
+
export function findUnrewritable(source, renames = RENAMES) {
|
|
135
|
+
return findHidden(source, ROOT_PKG, Object.keys(renames));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function* walk(dir) {
|
|
139
|
+
for (const entry of readdirSync(dir)) {
|
|
140
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
141
|
+
const full = join(dir, entry);
|
|
142
|
+
const st = statSync(full);
|
|
143
|
+
if (st.isDirectory()) yield* walk(full);
|
|
144
|
+
else if (EXTS.has(full.slice(full.lastIndexOf('.')))) yield full;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Scan a directory. Writes only when `write` is true. */
|
|
149
|
+
export function runCodemod({ root, write = false, renames = RENAMES }) {
|
|
150
|
+
const files = [];
|
|
151
|
+
const manual = [];
|
|
152
|
+
const renamed = new Set();
|
|
153
|
+
let sites = 0;
|
|
154
|
+
for (const file of walk(root)) {
|
|
155
|
+
const src = readFileSync(file, 'utf8');
|
|
156
|
+
if (!src.includes(ROOT_PKG)) continue;
|
|
157
|
+
const r = transformSource(src, renames);
|
|
158
|
+
for (const stmt of findUnrewritable(r.source, renames))
|
|
159
|
+
manual.push({ file: relative(root, file), stmt });
|
|
160
|
+
if (!r.changed) continue;
|
|
161
|
+
files.push({ file: relative(root, file), sites: r.sites, renamed: r.renamed, edits: r.edits });
|
|
162
|
+
r.renamed.forEach((n) => renamed.add(n));
|
|
163
|
+
sites += r.sites;
|
|
164
|
+
if (write) writeFileSync(file, r.source);
|
|
165
|
+
}
|
|
166
|
+
return { files, sites, manual, names: [...renamed].sort() };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function main(argv) {
|
|
170
|
+
const args = { root: process.cwd(), check: false, dryRun: false };
|
|
171
|
+
for (let i = 0; i < argv.length; i++) {
|
|
172
|
+
if (argv[i] === '--root') args.root = resolve(argv[++i] ?? '');
|
|
173
|
+
else if (argv[i] === '--check') args.check = true;
|
|
174
|
+
else if (argv[i] === '--dry-run') args.dryRun = true;
|
|
175
|
+
else {
|
|
176
|
+
console.error(
|
|
177
|
+
`unknown argument: ${argv[i]}\nusage: hds-prefix [--root <dir>] [--check] [--dry-run]`,
|
|
178
|
+
);
|
|
179
|
+
return 2;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const write = !args.check && !args.dryRun;
|
|
183
|
+
const res = runCodemod({ root: args.root, write });
|
|
184
|
+
const names = res.names.map((n) => `${n} -> ${RENAMES[n]}`).join(', ') || 'none';
|
|
185
|
+
const summary = `${res.files.length} files, ${res.sites} import sites, ${res.names.length} names (${names})`;
|
|
186
|
+
// An unreadable file names its own fix; the rest need the bare names by hand.
|
|
187
|
+
const manualLines = res.manual.map(
|
|
188
|
+
(m) =>
|
|
189
|
+
` ${m.file}: ${m.stmt}${m.stmt.startsWith('unreadable ') ? '' : ' (use the bare names by hand)'}`,
|
|
190
|
+
);
|
|
191
|
+
if (args.check) {
|
|
192
|
+
if (res.files.length > 0 || res.manual.length > 0) {
|
|
193
|
+
console.error(`hds-prefix: rewrite needed: ${summary}`);
|
|
194
|
+
for (const f of res.files) console.error(` ${f.file}: ${f.renamed.join(', ')}`);
|
|
195
|
+
for (const l of manualLines) console.error(l);
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
console.log('hds-prefix: nothing to rewrite');
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
console.log(`hds-prefix: ${write ? 'rewrote' : 'would rewrite'} ${summary}`);
|
|
202
|
+
for (const f of res.files) {
|
|
203
|
+
console.log(` ${f.file}: ${f.renamed.join(', ')}`);
|
|
204
|
+
if (args.dryRun)
|
|
205
|
+
for (const e of f.edits) {
|
|
206
|
+
console.log(`- ${e.before}`);
|
|
207
|
+
console.log(`+ ${e.after}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (res.manual.length > 0) {
|
|
211
|
+
console.warn('hds-prefix: cannot rewrite these, do them by hand:');
|
|
212
|
+
for (const l of manualLines) console.warn(l);
|
|
213
|
+
}
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Compare real paths: npm/yarn link the bin and pnpm links the package directory, so
|
|
218
|
+
// argv[1] is a symlink while import.meta.url is already resolved.
|
|
219
|
+
const isEntry = (() => {
|
|
220
|
+
try {
|
|
221
|
+
return (
|
|
222
|
+
!!process.argv[1] &&
|
|
223
|
+
realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
|
|
224
|
+
);
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
})();
|
|
229
|
+
if (isEntry) {
|
|
230
|
+
process.exit(main(process.argv.slice(2)));
|
|
231
|
+
}
|