@hirobius/design-system 0.18.0 → 0.19.1
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 +150 -0
- package/DESIGN.md +220 -0
- package/README.md +14 -2
- package/dist/chunks/{LanguageContext-CH_bwzCq.js → LanguageContext-BxdCwiFg.js} +20 -8
- package/dist/chunks/{TokenDisplayContext-evZi_214.js → TokenDisplayContext-DKYJMVWk.js} +10 -2
- package/dist/chunks/{activity-feed-C8_e-Btq.js → activity-feed-BGQ6v3wc.js} +5248 -3163
- package/dist/chunks/{form-DnGRy27o.js → form-COP3vK9_.js} +31 -29
- package/dist/contexts.js +4 -4
- package/dist/form.js +5 -5
- package/dist/hirobius-ui.js +2790 -3257
- package/dist/icons.js +53 -0
- package/dist/manifest.js +15 -14
- package/dist/patterns.js +284 -101
- package/dist/static.css +3 -3
- package/dist/styles.css +1 -1
- package/dist/tokens.css +1 -1
- package/dist/types/src/app/components/alert-dialog.d.ts +9 -1
- package/dist/types/src/app/components/alert.d.ts +4 -0
- package/dist/types/src/app/components/avatar.d.ts +4 -0
- package/dist/types/src/app/components/badge.d.ts +4 -0
- package/dist/types/src/app/components/box.d.ts +5 -0
- package/dist/types/src/app/components/breadcrumb.d.ts +4 -0
- package/dist/types/src/app/components/button-group.d.ts +4 -0
- package/dist/types/src/app/components/button.d.ts +3 -0
- package/dist/types/src/app/components/card.d.ts +9 -0
- package/dist/types/src/app/components/checkbox.d.ts +4 -0
- package/dist/types/src/app/components/combobox.d.ts +10 -0
- package/dist/types/src/app/components/command-palette.d.ts +3 -1
- package/dist/types/src/app/components/container.d.ts +5 -0
- package/dist/types/src/app/components/context-menu.d.ts +25 -2
- package/dist/types/src/app/components/data-table-section.d.ts +45 -0
- package/dist/types/src/app/components/destructive-section.d.ts +32 -0
- package/dist/types/src/app/components/dialog.d.ts +20 -2
- package/dist/types/src/app/components/disclosure.d.ts +3 -0
- package/dist/types/src/app/components/divider.d.ts +4 -0
- package/dist/types/src/app/components/empty-state.d.ts +4 -0
- package/dist/types/src/app/components/field.d.ts +4 -0
- package/dist/types/src/app/components/form-actions.d.ts +26 -0
- package/dist/types/src/app/components/grid.d.ts +4 -0
- package/dist/types/src/app/components/hds-tooltip.d.ts +14 -2
- package/dist/types/src/app/components/hover-card.d.ts +16 -1
- package/dist/types/src/app/components/icon.d.ts +4 -0
- package/dist/types/src/app/components/inline-link.d.ts +4 -0
- package/dist/types/src/app/components/input.d.ts +5 -0
- package/dist/types/src/app/components/kbd.d.ts +3 -0
- package/dist/types/src/app/components/menu.d.ts +26 -2
- package/dist/types/src/app/components/metric-tiles.d.ts +40 -0
- package/dist/types/src/app/components/page-header.d.ts +34 -0
- package/dist/types/src/app/components/pagination.d.ts +3 -0
- package/dist/types/src/app/components/popover.d.ts +14 -2
- package/dist/types/src/app/components/progress.d.ts +4 -0
- package/dist/types/src/app/components/radio.d.ts +5 -0
- package/dist/types/src/app/components/select.d.ts +16 -0
- package/dist/types/src/app/components/skeleton.d.ts +4 -0
- package/dist/types/src/app/components/slider.d.ts +3 -0
- package/dist/types/src/app/components/spinner.d.ts +4 -0
- package/dist/types/src/app/components/stack.d.ts +4 -0
- package/dist/types/src/app/components/stat.d.ts +4 -0
- package/dist/types/src/app/components/status-tile.d.ts +4 -0
- package/dist/types/src/app/components/table.d.ts +36 -4
- package/dist/types/src/app/components/tabs.d.ts +4 -0
- package/dist/types/src/app/components/tag.d.ts +3 -0
- package/dist/types/src/app/components/text.d.ts +4 -0
- package/dist/types/src/app/components/textarea.d.ts +3 -0
- package/dist/types/src/app/components/toast.d.ts +4 -0
- package/dist/types/src/app/components/toggle.d.ts +4 -0
- package/dist/types/src/app/components/tooltip.d.ts +1 -1
- package/dist/types/src/app/components/visually-hidden.d.ts +3 -0
- package/dist/types/src/app/context/hds-portal.d.ts +52 -0
- package/dist/types/src/app/context/hds-theme.d.ts +10 -0
- package/dist/types/src/app/data/manifest-types.d.ts +27 -0
- package/dist/types/src/app/design-system/generated-token-refs.d.ts +2 -0
- package/dist/types/src/icons.d.ts +13 -0
- package/dist/types/src/index.d.ts +20 -1
- package/dist/types/src/patterns.d.ts +5 -0
- package/dist/variables.css +4 -0
- package/docs/CONSUMING.md +758 -0
- package/hirobius.tokens.json +17 -0
- package/llms.txt +261 -0
- package/package.json +22 -4
- package/public/hds-manifest.json +1605 -105
- package/public/llms/components.txt +1090 -0
- package/public/llms/layout.txt +54 -0
- package/public/llms/scroll.txt +22 -0
- package/public/llms/tokens.txt +32 -0
- package/public/llms-full.txt +1561 -0
- package/public/llms.txt +261 -0
- package/src/app/data/component-api.json +6156 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
# Consuming `@hirobius/design-system`
|
|
2
|
+
|
|
3
|
+
How to install and use the Hirobius Design System (HDS) in another project. This
|
|
4
|
+
is the canonical consumption guide — point other teams here.
|
|
5
|
+
|
|
6
|
+
| | |
|
|
7
|
+
| ------------------------------- | -------------------------------------------------------------------------- |
|
|
8
|
+
| **Package** | `@hirobius/design-system` |
|
|
9
|
+
| **Min. version for this guide** | `0.7.0` (router optional, fonts bundled, scoped base styles) |
|
|
10
|
+
| **Module format** | ESM only (`"type": "module"`) |
|
|
11
|
+
| **Registry** | **Public npm** (`https://registry.npmjs.org`) — no auth, no `.npmrc` |
|
|
12
|
+
| **Peers** | `react` ^18.3 ‖ ^19 · `react-dom` (match) · `react-router` ^7 _(optional)_ |
|
|
13
|
+
|
|
14
|
+
> **What changed in 0.7.0** — three things make HDS drop-in for a plain app:
|
|
15
|
+
>
|
|
16
|
+
> 1. **`react-router` is now optional** — components route through an adapter and
|
|
17
|
+
> fall back to plain anchors when no router is provided (§5).
|
|
18
|
+
> 2. **Fonts are bundled** — `tokens.css` embeds the typefaces; no font files to copy (§3).
|
|
19
|
+
> 3. **Base styles are scoped to `[data-hds]`** — add that attribute to your root
|
|
20
|
+
> or section so HDS doesn't fight a host app's resets/fonts (§4, §6).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1–2. Install
|
|
25
|
+
|
|
26
|
+
HDS publishes to the **public npm registry**. There is no `.npmrc`, no token, and
|
|
27
|
+
no registry configuration — install it like any other package:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @hirobius/design-system
|
|
31
|
+
npm install react react-dom # required peers
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
(`pnpm add` / `yarn add` work identically.)
|
|
35
|
+
|
|
36
|
+
`react-router` is an **optional** peer. You only install it if you want HDS's
|
|
37
|
+
nav/link components to drive client-side navigation through your router — see
|
|
38
|
+
§5. With no router, links render as plain `<a>` and work everywhere.
|
|
39
|
+
|
|
40
|
+
## 3. Import a stylesheet (once, at the app root)
|
|
41
|
+
|
|
42
|
+
HDS ships **three** stylesheets. Pick one by how much you want HDS to own:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// RECOMMENDED for embedding in an existing app (e.g. MUI): tokens + component
|
|
46
|
+
// styles + utilities + embedded fonts, with NO global reset. Styles every HDS
|
|
47
|
+
// component and changes ZERO host-element styles.
|
|
48
|
+
import '@hirobius/design-system/styles.css';
|
|
49
|
+
|
|
50
|
+
// OR batteries-included: everything in styles.css PLUS a global reset
|
|
51
|
+
// (Tailwind preflight). Best for an HDS-first app you fully own.
|
|
52
|
+
import '@hirobius/design-system/tokens.css';
|
|
53
|
+
|
|
54
|
+
// OR vars-only: just the design-token custom properties, no components,
|
|
55
|
+
// no utilities, no reset. For theming another system from HDS tokens.
|
|
56
|
+
import '@hirobius/design-system/variables.css';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Stylesheet | Tokens | Components + utilities | Embedded fonts | Global reset |
|
|
60
|
+
| ------------------- | :----: | :--------------------: | :------------: | :-------------------: |
|
|
61
|
+
| **`styles.css`** | ✅ | ✅ | ✅ | ❌ none |
|
|
62
|
+
| **`tokens.css`** | ✅ | ✅ | ✅ | ⚠️ Tailwind preflight |
|
|
63
|
+
| **`variables.css`** | ✅ | ❌ | ❌ | ❌ none |
|
|
64
|
+
|
|
65
|
+
All three:
|
|
66
|
+
|
|
67
|
+
- **Need no Tailwind config** in the consumer — utilities ship compiled.
|
|
68
|
+
- **Need no font files** — the woff2 are inlined (the file is correspondingly
|
|
69
|
+
larger; woff2 is already compressed so gzip recovers most of it).
|
|
70
|
+
|
|
71
|
+
HDS's own base styles (type baseline, resets) are scoped to the `[data-hds]`
|
|
72
|
+
subtree (§4) in **all** bundles, so they never touch host elements. The only
|
|
73
|
+
difference is `tokens.css`'s **global** Tailwind preflight, which `styles.css`
|
|
74
|
+
omits — so `styles.css` is the safe default when HDS shares a page with another
|
|
75
|
+
framework's CSS (§6). Importing any component also pulls a stylesheet in as a
|
|
76
|
+
side-effect, but importing one explicitly at the entry point is the canonical,
|
|
77
|
+
order-stable setup.
|
|
78
|
+
|
|
79
|
+
## 4. Mark your HDS scope with `data-hds`
|
|
80
|
+
|
|
81
|
+
As of 0.7.0 the base styles (element resets, the body/heading type baseline, the
|
|
82
|
+
theme-change transition) are scoped to a **`[data-hds]`** subtree so they don't
|
|
83
|
+
collide with a host app's own resets or fonts. Add the attribute to the element
|
|
84
|
+
that should host HDS:
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<!-- whole app uses HDS -->
|
|
88
|
+
<html data-hds>
|
|
89
|
+
…
|
|
90
|
+
</html>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// or just a section of an otherwise non-HDS app
|
|
95
|
+
<div data-hds>
|
|
96
|
+
<Button>Inside HDS scope</Button>
|
|
97
|
+
</div>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Without `data-hds`, components still get their own token-driven styling, but the
|
|
101
|
+
global type baseline and resets won't apply (text falls back to the host font).
|
|
102
|
+
Put `data-hds` as high as makes sense — on `<html>`/`<body>` for an
|
|
103
|
+
HDS-first app, or on a wrapper for a section. Overlays (Dialog, AlertDialog,
|
|
104
|
+
Menu, ContextMenu, Popover, Select, HoverCard, Tooltip) inherit the nearest
|
|
105
|
+
`data-hds` scope, so a `<div data-hds data-theme="dark">` themes them too; pass
|
|
106
|
+
`container` on the Content part to portal elsewhere. If the scope element
|
|
107
|
+
creates a containing block or stacking context (`transform`, `filter`, `z-index`,
|
|
108
|
+
`isolation`), pass `container={null}` (portal to `document.body`) or a dedicated
|
|
109
|
+
element so `position: fixed` overlays still cover the viewport. Scoping at
|
|
110
|
+
`<html>` still works. See [ADR-016](adr/016-scoped-base-styles.md).
|
|
111
|
+
|
|
112
|
+
## 5. Routing — optional, via the adapter seam
|
|
113
|
+
|
|
114
|
+
HDS components never import a router. Navigation comes from an injectable
|
|
115
|
+
adapter:
|
|
116
|
+
|
|
117
|
+
- **No router (default).** Do nothing. Links are real `<a href>`; in-app
|
|
118
|
+
navigation falls back to `window.location`. Perfect for a plain Vite/React
|
|
119
|
+
app or any page that doesn't need SPA nav.
|
|
120
|
+
- **react-router / Next.js / any router.** Wrap your app once and bridge your
|
|
121
|
+
router into the adapter:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { HdsRouterProvider, type HdsRouterAdapter } from '@hirobius/design-system';
|
|
125
|
+
import { useNavigate, useLocation, Link } from 'react-router';
|
|
126
|
+
|
|
127
|
+
function HdsRouting({ children }: { children: React.ReactNode }) {
|
|
128
|
+
const navigate = useNavigate();
|
|
129
|
+
const { pathname } = useLocation();
|
|
130
|
+
const adapter: HdsRouterAdapter = {
|
|
131
|
+
navigate: (href, opts) => navigate(href, opts),
|
|
132
|
+
currentPath: pathname,
|
|
133
|
+
LinkComponent: ({ to, children, ...rest }) => (
|
|
134
|
+
<Link to={to} {...rest}>
|
|
135
|
+
{children}
|
|
136
|
+
</Link>
|
|
137
|
+
),
|
|
138
|
+
};
|
|
139
|
+
return <HdsRouterProvider adapter={adapter}>{children}</HdsRouterProvider>;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`useHdsRouter()` is also exported if you build your own components on the seam.
|
|
144
|
+
|
|
145
|
+
## 6. Coexisting with MUI / Emotion / another global CSS
|
|
146
|
+
|
|
147
|
+
If your app runs MUI `<CssBaseline>` + Emotion (or any opinionated global CSS),
|
|
148
|
+
**don't** apply HDS globally:
|
|
149
|
+
|
|
150
|
+
- Scope HDS to a section with `data-hds` (§4) rather than `<html>`, so HDS's
|
|
151
|
+
base styles only affect that subtree.
|
|
152
|
+
- Don't expect HDS and `<CssBaseline>` to share `<body>` cleanly — pick one type
|
|
153
|
+
system per surface. HDS's namespaced custom properties (`--semantic-*`,
|
|
154
|
+
`--primitive-*`, `--hds-*`) won't collide with MUI's, but the two opinionated
|
|
155
|
+
resets will fight if both target `body`.
|
|
156
|
+
- **Styled path (recommended for MUI/host embedding):** import
|
|
157
|
+
`@hirobius/design-system/styles.css` instead of `tokens.css`. It ships the full
|
|
158
|
+
component styling (tokens + utilities + fonts) but **no global reset** — HDS's
|
|
159
|
+
own base is scoped to `[data-hds]`, so it styles every HDS component and
|
|
160
|
+
**cannot** restyle your host's `*`, `body`, headings, `button`, `a`, or form
|
|
161
|
+
controls. This is the clean way to run HDS next to `<CssBaseline>`: no reset
|
|
162
|
+
fight, full component fidelity.
|
|
163
|
+
- **Vars-only path:** import `@hirobius/design-system/variables.css` for just the
|
|
164
|
+
design-token custom properties — no components, no utilities, no reset. Use
|
|
165
|
+
when you only want to theme another system (MUI palette, etc.) from HDS tokens
|
|
166
|
+
and aren't rendering HDS components on that surface.
|
|
167
|
+
- **MUI theme preset:** `@hirobius/design-system/mui` maps HDS tokens to an MUI
|
|
168
|
+
palette so MUI and HDS share one source of truth (status colors line up with
|
|
169
|
+
`<Button tone>`). It imports no MUI code — the return type is structural:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { createTheme, ThemeProvider } from '@mui/material';
|
|
173
|
+
import { hdsMuiThemeOptions } from '@hirobius/design-system/mui';
|
|
174
|
+
|
|
175
|
+
const theme = createTheme({ cssVariables: true, ...hdsMuiThemeOptions() });
|
|
176
|
+
// render inside a `data-hds` scope (+ variables.css/styles.css) so the vars resolve
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Palette values are HDS token `var(--…)` references, so light/dark follow
|
|
180
|
+
`[data-theme]` automatically. Requires MUI v6+ (`cssVariables: true`).
|
|
181
|
+
|
|
182
|
+
- **Leaf imports stay light:** importing a primitive (e.g. `Button`) does **not**
|
|
183
|
+
pull `react-router`, `react-hook-form`, `zod`, or `@hookform/resolvers` into
|
|
184
|
+
your bundle — those are optional peers used only by the router seam / the
|
|
185
|
+
`/form` subpath, and aren't in the main bundle graph. A consumer with only
|
|
186
|
+
`react`/`react-dom` installed builds cleanly (verified in `smoke:consumer`).
|
|
187
|
+
- **Only `tokens.css` carries a global reset.** If you specifically want HDS's
|
|
188
|
+
batteries-included reset (Tailwind preflight, resets `margin`/`padding`/`border`
|
|
189
|
+
globally), import `tokens.css`. Otherwise prefer `styles.css` — same components,
|
|
190
|
+
no host reset.
|
|
191
|
+
|
|
192
|
+
## 7. Use it
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { Button, Card, Badge } from '@hirobius/design-system';
|
|
196
|
+
import { cn } from '@hirobius/design-system/cn';
|
|
197
|
+
import { tokens } from '@hirobius/design-system/tokens';
|
|
198
|
+
import manifest from '@hirobius/design-system/manifest';
|
|
199
|
+
|
|
200
|
+
export function Example() {
|
|
201
|
+
return (
|
|
202
|
+
<div data-hds>
|
|
203
|
+
<Card>
|
|
204
|
+
<Badge tone="accent">New</Badge>
|
|
205
|
+
<Button className={cn('mt-4')}>Get started</Button>
|
|
206
|
+
</Card>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Subpath exports
|
|
213
|
+
|
|
214
|
+
| Import | What you get |
|
|
215
|
+
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
216
|
+
| `@hirobius/design-system` | All public components + the router seam (`HdsRouterProvider`, `useHdsRouter`) |
|
|
217
|
+
| `@hirobius/design-system/styles.css` | Components + utilities + tokens + fonts, NO global reset (host-safe; recommended for embedding) |
|
|
218
|
+
| `@hirobius/design-system/tokens.css` | The complete stylesheet — styles.css PLUS a global Tailwind-preflight reset |
|
|
219
|
+
| `@hirobius/design-system/variables.css` | Design tokens as CSS custom properties ONLY — no reset/preflight (host-safe) |
|
|
220
|
+
| `@hirobius/design-system/tokens` | Design-token values as typed TS |
|
|
221
|
+
| `@hirobius/design-system/cn` | The `cn()` className-merge helper |
|
|
222
|
+
| `@hirobius/design-system/manifest` | Machine-readable component inventory (`hds-manifest.json`) |
|
|
223
|
+
| `@hirobius/design-system/contexts` | React context providers, incl. the router seam (see below) |
|
|
224
|
+
| `@hirobius/design-system/form` | Optional React Hook Form + Zod form adapter (see §8.5) |
|
|
225
|
+
| `@hirobius/design-system/mui` | Optional Material UI theme preset — maps HDS tokens to an MUI palette (see §6) |
|
|
226
|
+
| `@hirobius/design-system/brand` | Framework-free palette → HDS-semantic overlay bridge for static/SSR targets (see §12) |
|
|
227
|
+
| `@hirobius/design-system/scroll` | Opt-in scroll-motion: `SmoothScroll` (Lenis) + `useScrollProgress` (Motion). Optional peer `lenis` (see §13) |
|
|
228
|
+
| `@hirobius/design-system/static.css` | CSS-only static-primitive layer — `.hds-badge`/`.hds-card`/`.hds-alert`/`.hds-divider`/`.hds-tag` classes, no React (see §14) |
|
|
229
|
+
|
|
230
|
+
### Semantic feedback / status tokens
|
|
231
|
+
|
|
232
|
+
For status UI (Saved / Applied / Interviewing / Offer / Rejected, or any
|
|
233
|
+
success/warning/error/in-progress state), theme from HDS's feedback tokens
|
|
234
|
+
instead of hardcoding hex. Each is a CSS custom property that auto-switches
|
|
235
|
+
light/dark and is AA-verified for small text on both the page and card
|
|
236
|
+
(`raised`) surfaces (asserted by `scripts/check-contrast.mjs`):
|
|
237
|
+
|
|
238
|
+
| Intent | Text color var | Tinted-surface var |
|
|
239
|
+
| ----------- | -------------------------------------- | ----------------------------------------- |
|
|
240
|
+
| success | `--semantic-color-feedback-success` | `--semantic-color-feedback-bg-success` |
|
|
241
|
+
| warning | `--semantic-color-feedback-warning` | `--semantic-color-feedback-bg-warning` |
|
|
242
|
+
| info | `--semantic-color-feedback-info` | `--semantic-color-feedback-bg-info` |
|
|
243
|
+
| error | `--semantic-color-feedback-error` | `--semantic-color-feedback-bg-error` |
|
|
244
|
+
| in-progress | `--semantic-color-feedback-inProgress` | `--semantic-color-feedback-bg-inProgress` |
|
|
245
|
+
|
|
246
|
+
```css
|
|
247
|
+
.status-offer {
|
|
248
|
+
color: var(--semantic-color-feedback-success);
|
|
249
|
+
}
|
|
250
|
+
.status-pending {
|
|
251
|
+
color: var(--semantic-color-feedback-inProgress);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The same values are available as typed TS via `@hirobius/design-system/tokens`
|
|
256
|
+
(`hds.color.feedback.*`), and the `Button` and `Badge` components expose them
|
|
257
|
+
directly through a `tone` prop so you rarely need the raw vars:
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
<Button tone="danger">Delete</Button> {/* AA in light + dark */}
|
|
261
|
+
<Badge tone="inProgress">Interviewing</Badge>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## 8. Optional providers — theming / i18n / multi-tenant / fonts
|
|
265
|
+
|
|
266
|
+
Only needed if you use those features. Mount them above your app:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import {
|
|
270
|
+
TenantProvider,
|
|
271
|
+
LanguageProvider,
|
|
272
|
+
ThemeProvider,
|
|
273
|
+
FontProvider,
|
|
274
|
+
} from '@hirobius/design-system/contexts';
|
|
275
|
+
|
|
276
|
+
<TenantProvider>
|
|
277
|
+
<LanguageProvider>
|
|
278
|
+
<ThemeProvider>
|
|
279
|
+
<FontProvider>{/* app */}</FontProvider>
|
|
280
|
+
</ThemeProvider>
|
|
281
|
+
</LanguageProvider>
|
|
282
|
+
</TenantProvider>;
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`ThemeProvider` toggles light/dark by setting `data-theme` + `.dark` on
|
|
286
|
+
`<html>`. It does not set `data-hds` — add that yourself (§4).
|
|
287
|
+
|
|
288
|
+
## 8.5 Optional — typed, schema-validated forms (React Hook Form + Zod)
|
|
289
|
+
|
|
290
|
+
The main barrel is validation-agnostic: `FormField` takes a plain `error`
|
|
291
|
+
string, so you bring your own validation. If you want a batteries-included
|
|
292
|
+
layer, the `/form` subpath wires [React Hook Form](https://react-hook-form.com)
|
|
293
|
+
to [Zod](https://zod.dev). It's opt-in — `react-hook-form`, `zod`, and
|
|
294
|
+
`@hookform/resolvers` are **optional peer deps**, so they only land in your
|
|
295
|
+
bundle if you import this entry.
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pnpm add react-hook-form zod @hookform/resolvers
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { z } from 'zod';
|
|
303
|
+
import { useHdsForm, HdsForm, HdsFormField } from '@hirobius/design-system/form';
|
|
304
|
+
import { Button } from '@hirobius/design-system';
|
|
305
|
+
|
|
306
|
+
const schema = z.object({
|
|
307
|
+
email: z.string().email('Enter a valid email'),
|
|
308
|
+
role: z.string().min(1, 'Pick a role'),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
function ApplyForm() {
|
|
312
|
+
const form = useHdsForm(schema, { defaultValues: { email: '', role: '' } });
|
|
313
|
+
return (
|
|
314
|
+
<HdsForm form={form} onSubmit={(values) => save(values)}>
|
|
315
|
+
<HdsFormField name="email" label="Email" description="We never share it.">
|
|
316
|
+
{(props) => <input type="email" {...props} />}
|
|
317
|
+
</HdsFormField>
|
|
318
|
+
<HdsFormField name="role" label="Role">
|
|
319
|
+
{(props) => (
|
|
320
|
+
<select {...props}>
|
|
321
|
+
<option value="">Choose…</option>
|
|
322
|
+
<option value="ic">Individual contributor</option>
|
|
323
|
+
<option value="lead">Lead</option>
|
|
324
|
+
</select>
|
|
325
|
+
)}
|
|
326
|
+
</HdsFormField>
|
|
327
|
+
<Button type="submit">Apply</Button>
|
|
328
|
+
</HdsForm>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
- `useHdsForm(schema, options?)` — RHF's `useForm` with the Zod resolver and
|
|
334
|
+
`onTouched` validation pre-wired; the schema's parsed type drives the field
|
|
335
|
+
types so `onSubmit` gets a fully-typed, validated value object.
|
|
336
|
+
- `HdsForm` — wraps the form in RHF's `FormProvider`, routes submit through
|
|
337
|
+
`handleSubmit`, and sets `noValidate` so Zod is the single source of
|
|
338
|
+
validation (no native browser popups racing your schema).
|
|
339
|
+
- `HdsFormField` — a **render-prop**: spread the supplied props onto your
|
|
340
|
+
control. It binds the control to RHF by `name` and shows the field's Zod
|
|
341
|
+
error through the same label/error/aria markup as the core `FormField`. (It's
|
|
342
|
+
a render-prop, not a cloned child, so RHF's callback ref attaches cleanly.)
|
|
343
|
+
|
|
344
|
+
**SSR / Astro / Next.js RSC:** this layer is client-only (RHF uses hooks +
|
|
345
|
+
refs). In Next's app router it carries a `'use client'` marker; in Astro, mount
|
|
346
|
+
the form inside a hydrated island (e.g. `client:load`).
|
|
347
|
+
|
|
348
|
+
## 9. TypeScript & bundler requirements
|
|
349
|
+
|
|
350
|
+
- **ESM-only.** Use a modern bundler (Vite, Next, Rspack, etc.). `require()` /
|
|
351
|
+
CommonJS resolution will not work.
|
|
352
|
+
- **Types ship from source.** The package serves types directly from its `src/`
|
|
353
|
+
TypeScript (no emitted `.d.ts`), so the consuming `tsconfig.json` should use
|
|
354
|
+
`"moduleResolution": "bundler"` (or `"node16"`/`"nodenext"`).
|
|
355
|
+
|
|
356
|
+
## 10. Extending HDS — the consumption contract
|
|
357
|
+
|
|
358
|
+
HDS is **authoritative**. Consumers conform to its structure, extend in their
|
|
359
|
+
own namespace, and route gaps upstream — never the reverse. Follow these four
|
|
360
|
+
rules and token collisions between the package and your app become **structurally
|
|
361
|
+
impossible** — no shared registry, no coordination, no luck required.
|
|
362
|
+
|
|
363
|
+
The line is namespace ownership:
|
|
364
|
+
|
|
365
|
+
| DS-owned (never declare here) | Consumer-owned (yours to define) |
|
|
366
|
+
| --------------------------------------------------------------------------- | --------------------------------------------- |
|
|
367
|
+
| `--primitive-*` · `--semantic-*` · `--component-*` · `--role-*` · `--hds-*` | `--<prefix>-*` (pick one prefix for your app) |
|
|
368
|
+
| `data-hds` · `data-theme` · `data-tenant` | your own `data-*` attributes |
|
|
369
|
+
|
|
370
|
+
Any release may add names inside DS-owned space; consumers may never declare
|
|
371
|
+
there. That single invariant is what makes overlap impossible by construction.
|
|
372
|
+
|
|
373
|
+
### C1 — The DS owns its namespaces
|
|
374
|
+
|
|
375
|
+
`--primitive-*`, `--semantic-*`, `--component-*`, `--role-*`, `--hds-*` and the
|
|
376
|
+
`data-hds` / `data-theme` / `data-tenant` attributes belong to the package.
|
|
377
|
+
**Never declare a token inside them**, and treat every release as free to add
|
|
378
|
+
names there. Declaring `--primitive-color-lilac-*` in your app, for example,
|
|
379
|
+
would silently override package tokens the day the DS ships its own — with no
|
|
380
|
+
error anywhere.
|
|
381
|
+
|
|
382
|
+
### C2 — Extend under your own prefix
|
|
383
|
+
|
|
384
|
+
App-local tokens, aliases, and component styles live in a **consumer namespace**
|
|
385
|
+
and may reference DS tokens freely:
|
|
386
|
+
|
|
387
|
+
```css
|
|
388
|
+
:root {
|
|
389
|
+
--lb-lilac-500: #6f3fd4; /* app-owned value */
|
|
390
|
+
--lb-cta-radius: var(--semantic-radius-action); /* references a DS token */
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Pick one prefix for your app (`--lb-*` here) and keep every
|
|
395
|
+
local declaration inside it.
|
|
396
|
+
|
|
397
|
+
### C3 — Brand through the tenant mechanism
|
|
398
|
+
|
|
399
|
+
Re-theming goes through the DS's own structure, not around it: author a **tenant
|
|
400
|
+
overlay** conforming to
|
|
401
|
+
[`hirobius.tenant-tokens.schema.json`](../hirobius.tenant-tokens.schema.json)
|
|
402
|
+
(**semantic tier only** — the primitive tier is off-limits, per rule R1), and
|
|
403
|
+
activate it with `data-tenant`. A local mirror of a not-yet-merged overlay is
|
|
404
|
+
fine as a stopgap, but mark it temporary and delete it once the overlay ships in
|
|
405
|
+
the package.
|
|
406
|
+
|
|
407
|
+
### C4 — Route gaps upstream, don't patch around them
|
|
408
|
+
|
|
409
|
+
A missing primitive, a token you need, or a fundamental flaw becomes an **issue
|
|
410
|
+
and PR against the DS** — not a permanent downstream fork. Every alias target
|
|
411
|
+
must exist in the base `hirobius.tokens.json` (rule R5), so a new brand ramp is
|
|
412
|
+
added upstream first, then the tenant overlay aliases it.
|
|
413
|
+
|
|
414
|
+
> **Optional (no DS obligation):** keep a downstream **drift-guard test** that
|
|
415
|
+
> pins the token paths your app consumes, so your CI fails when an upgrade moves
|
|
416
|
+
> something. The consumer adapts; it never gates a DS release.
|
|
417
|
+
|
|
418
|
+
**Reference implementation:** the first external consumer follows all four —
|
|
419
|
+
it consumes tokens via
|
|
420
|
+
`@hirobius/design-system/variables.css`, keeps every extension under `--lb-*`,
|
|
421
|
+
mirrors its brand through a tenant overlay, and pins consumed paths with a
|
|
422
|
+
CI-enforced drift guard.
|
|
423
|
+
|
|
424
|
+
## 11. Lint discipline — the consumer ESLint plugin
|
|
425
|
+
|
|
426
|
+
The design system's own token/layout discipline is enforced inside this repo
|
|
427
|
+
by `scripts/check-hardcoded-colors.mjs`, `scripts/check-hardcoded-spacing.mjs`,
|
|
428
|
+
and `scripts/check-layout-discipline.mjs` — internal scripts that only run
|
|
429
|
+
here. `@hirobius/eslint-plugin-hds` (`scripts/eslint-plugin-hds/`) ships the
|
|
430
|
+
same discipline as an installable ESLint plugin for consumer apps, flagging
|
|
431
|
+
raw hex/px values in `style`, `className`, and `Box` `sx` props before they
|
|
432
|
+
reach code review.
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
pnpm add -D "@hirobius/eslint-plugin-hds@github:hirobius/hirobius-design-system#path:/scripts/eslint-plugin-hds"
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
```js
|
|
439
|
+
// eslint.config.mjs
|
|
440
|
+
import hds from '@hirobius/eslint-plugin-hds';
|
|
441
|
+
|
|
442
|
+
export default [
|
|
443
|
+
{
|
|
444
|
+
files: ['**/*.{ts,tsx,jsx}'],
|
|
445
|
+
languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } },
|
|
446
|
+
},
|
|
447
|
+
...hds.configs.recommended,
|
|
448
|
+
];
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Four rules ship in `recommended`: `no-raw-hex` (error), `no-raw-px-spacing`
|
|
452
|
+
(error), `sx-token-first` (error — raw hex/px inside `Box` `sx`, where token
|
|
453
|
+
keys are mandatory), and `prefer-hds-layout-primitive` (warn — ad-hoc
|
|
454
|
+
`display: flex`/`grid` where Stack/Grid likely fits). Full rule docs, examples,
|
|
455
|
+
and the "why `scripts/eslint-plugin-hds/` and not a top-level workspace
|
|
456
|
+
package" packaging note live in
|
|
457
|
+
[`scripts/eslint-plugin-hds/README.md`](../scripts/eslint-plugin-hds/README.md).
|
|
458
|
+
|
|
459
|
+
### Agent skill
|
|
460
|
+
|
|
461
|
+
An agent building a screen with HDS can load one consumer skill instead of reading
|
|
462
|
+
the manifest, `llms.txt` and this guide separately. It carries the allow-list of
|
|
463
|
+
importable components, the screen layout recipe, the do/don't rules, and the lint
|
|
464
|
+
step (§11) as its final required step. It is generated from the manifest by
|
|
465
|
+
`pnpm skill:generate` and drift-checked by `pnpm check:consumer-skill`.
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
npx skills add hirobius/hds --skill hds-consumer
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Troubleshooting
|
|
472
|
+
|
|
473
|
+
| Symptom | Cause / fix |
|
|
474
|
+
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
475
|
+
| `npm ERR! 404` on install | Check the package name spelling; if pinning a version, confirm it's published on npm (`npm view @hirobius/design-system version`). Public npm needs no auth (see §1–2). |
|
|
476
|
+
| `ERR_REQUIRE_ESM` / `require() of ES Module` | Consumer is CommonJS — switch to an ESM bundler (§9). |
|
|
477
|
+
| Components render unstyled / wrong font | Import `@hirobius/design-system/styles.css` (or `tokens.css`) at the app root (§3) **and** add `data-hds` to your root or section (§4). |
|
|
478
|
+
| Text uses the host font, not Satoshi | Missing `data-hds` on an ancestor (§4). |
|
|
479
|
+
| In-app links do a full page reload | Expected with no router. Inject your router via `<HdsRouterProvider>` for SPA nav (§5). |
|
|
480
|
+
| Host app's spacing/layout shifted after adding HDS | Tailwind preflight ships global this release (§6); scope HDS to a section and isolate where possible. |
|
|
481
|
+
| Brand overlay has no effect on an Astro page | Inject the overlay on an ancestor of the themed markup (usually `<html>`), and ensure `variables.css` is imported once (§12). |
|
|
482
|
+
|
|
483
|
+
## 12. Static Astro sites + the brand overlay bridge
|
|
484
|
+
|
|
485
|
+
HDS is built for a React runtime, but its **tokens are just CSS custom
|
|
486
|
+
properties**, so a static Astro site can render HDS-consistent UI with **zero
|
|
487
|
+
React** — import the vars, set a few attributes, and let per-page markup read the
|
|
488
|
+
tokens through Tailwind utilities or plain CSS. Interactive pieces hydrate as
|
|
489
|
+
`client:*` islands only where you actually need them.
|
|
490
|
+
|
|
491
|
+
This is the path the `hirobius/clients` factory uses: one HDS token spine, many
|
|
492
|
+
brand skins, no parallel token system.
|
|
493
|
+
|
|
494
|
+
### 12.1 Wire the token spine once (in your base layout)
|
|
495
|
+
|
|
496
|
+
```astro
|
|
497
|
+
---
|
|
498
|
+
// src/layouts/Base.astro
|
|
499
|
+
import '@hirobius/design-system/variables.css'; // tokens only — no reset, host-safe
|
|
500
|
+
import { brandOverlayStyle } from '@hirobius/design-system/brand';
|
|
501
|
+
import { client } from '../client.config';
|
|
502
|
+
|
|
503
|
+
// Map the site's brand palette onto HDS semantics (see §12.2).
|
|
504
|
+
const style = brandOverlayStyle(client.palette);
|
|
505
|
+
---
|
|
506
|
+
<html lang="en" data-hds data-theme="light" style={style}>
|
|
507
|
+
<head><slot name="head" /></head>
|
|
508
|
+
<body><slot /></body>
|
|
509
|
+
</html>
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
- `data-hds` opts the subtree into HDS's base type/reset (§4).
|
|
513
|
+
- `data-theme="light|dark"` picks the mode; `data-brand="slug"` is available too
|
|
514
|
+
if you prefer a checked-in overlay block over the inline style (§12.3).
|
|
515
|
+
- The inline `style` wins the cascade, so the brand always overrides defaults.
|
|
516
|
+
|
|
517
|
+
### 12.2 The brand bridge — palette → HDS semantics
|
|
518
|
+
|
|
519
|
+
`@hirobius/design-system/brand` is **framework-free** (no React, no Node), so it
|
|
520
|
+
runs in an Astro build or an edge function. Give it a small palette; it returns
|
|
521
|
+
the HDS semantic custom properties, deriving interactive accent states with CSS
|
|
522
|
+
`color-mix()`:
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
import {
|
|
526
|
+
brandOverlayStyle,
|
|
527
|
+
brandOverlayCss,
|
|
528
|
+
brandOverlayVars,
|
|
529
|
+
} from '@hirobius/design-system/brand';
|
|
530
|
+
|
|
531
|
+
const palette = {
|
|
532
|
+
primary: '#2f6f3e', // → --semantic-accent-rest + surface/border-accent
|
|
533
|
+
onPrimary: '#ffffff', // → --semantic-color-content-onAccent
|
|
534
|
+
bg: '#f7f8f3', // → --semantic-color-surface-page
|
|
535
|
+
fg: '#1f2a1c', // → --semantic-color-content-primary
|
|
536
|
+
muted: '#e7ecdd', // → --semantic-color-surface-raised
|
|
537
|
+
accent: '#a7c957', // optional — kept brand-level (--brand-accent)
|
|
538
|
+
radius: '8px', // optional — --semantic-radius-action
|
|
539
|
+
fontHeading: '"Work Sans", sans-serif', // optional → display family
|
|
540
|
+
fontBody: 'Inter, sans-serif', // optional → primary family
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
brandOverlayStyle(palette); // "…k:v;k:v" for <html style>
|
|
544
|
+
brandOverlayCss('[data-brand="acme"]', palette); // a scoped CSS rule block
|
|
545
|
+
brandOverlayVars(palette); // the raw { --var: value } record
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Only the accent ramp, page/content colours, and (when given) radius + fonts are
|
|
549
|
+
overridden; everything else inherits HDS defaults, so a partial palette still
|
|
550
|
+
yields a coherent theme. `color-mix()` is Baseline-supported (evergreen engines
|
|
551
|
+
since 2023).
|
|
552
|
+
|
|
553
|
+
### 12.3 Multi-brand: `data-brand` blocks
|
|
554
|
+
|
|
555
|
+
For several brands on one deploy (per-niche reskins), emit a `data-brand` block
|
|
556
|
+
per brand with `brandOverlayCss` into a global stylesheet and switch by
|
|
557
|
+
attribute — no inline style, no rebuild:
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
// build a static stylesheet from your brand registry
|
|
561
|
+
const css = brands.map((b) => brandOverlayCss(`[data-brand="${b.slug}"]`, b.palette)).join('\n');
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
```astro
|
|
565
|
+
<html data-hds data-brand={Astro.url.searchParams.get('brand') ?? 'default'}>
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### 12.4 Hydrate only what's interactive
|
|
569
|
+
|
|
570
|
+
Static primitives (badge, card, alert, divider, tag, the layout primitives, and
|
|
571
|
+
`Box`/`sx`) render from tokens with no JS. Reach for a React island **only** for
|
|
572
|
+
genuinely interactive components:
|
|
573
|
+
|
|
574
|
+
```astro
|
|
575
|
+
---
|
|
576
|
+
import { ContactForm } from '@hirobius/design-system';
|
|
577
|
+
---
|
|
578
|
+
<ContactForm client:visible />
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Import `@hirobius/design-system/styles.css` once (alongside `variables.css`, or
|
|
582
|
+
instead of it) when you hydrate components that need HDS's compiled component
|
|
583
|
+
CSS. For a purely static page, `variables.css` + your own Tailwind utilities is
|
|
584
|
+
enough.
|
|
585
|
+
|
|
586
|
+
## 13. Scroll motion — the `/scroll` subpath
|
|
587
|
+
|
|
588
|
+
Opt-in scroll-motion primitives. `lenis` is an **optional peer dependency** — install it only if you use `SmoothScroll`:
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
pnpm add lenis # required only for SmoothScroll; useScrollProgress needs nothing extra
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Layering (cheapest first):**
|
|
595
|
+
|
|
596
|
+
1. **CSS scroll-driven animation** (`animation-timeline: view()/scroll()` + `position: sticky`) — reveal / pin / parallax / scale with **zero JS**. Prefer this.
|
|
597
|
+
2. **`useScrollProgress(ref)`** — a Motion-based `0→1` MotionValue for effects you need in React (canvas/WebGL, cross-element choreography). No new dependency; works with or without `SmoothScroll`.
|
|
598
|
+
3. **`SmoothScroll`** — Lenis momentum ("inertia") scrolling, the one thing CSS/Motion can't do. Wrap the app/site root once. It **skips Lenis entirely under `prefers-reduced-motion`** (native scroll) and is SSR-safe.
|
|
599
|
+
|
|
600
|
+
```tsx
|
|
601
|
+
import { SmoothScroll, useScrollProgress } from '@hirobius/design-system/scroll';
|
|
602
|
+
import { motion, useTransform } from 'motion/react';
|
|
603
|
+
import { useRef } from 'react';
|
|
604
|
+
|
|
605
|
+
function Root() {
|
|
606
|
+
return (
|
|
607
|
+
<SmoothScroll>
|
|
608
|
+
{' '}
|
|
609
|
+
{/* momentum scroll; reduced-motion aware */}
|
|
610
|
+
<Hero />
|
|
611
|
+
</SmoothScroll>
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function Hero() {
|
|
616
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
617
|
+
const progress = useScrollProgress(ref); // 0 → 1 through the viewport
|
|
618
|
+
const scale = useTransform(progress, [0, 1], [0.4, 1]);
|
|
619
|
+
return <motion.div ref={ref} style={{ scale }} />;
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
`SmoothScroll` drives the real scroll position, so CSS `animation-timeline` and `useScrollProgress` keep working underneath it unchanged. See `docs/adr/021-animation-engine-motion-core-gsap-downstream.md` for why Lenis is opt-in and GSAP is out.
|
|
624
|
+
|
|
625
|
+
## 14. CSS-only static primitives — Badge/Card/Alert/Divider/Tag with NO React
|
|
626
|
+
|
|
627
|
+
§12.4 promised that "static primitives (badge, card, alert, divider, tag, …)
|
|
628
|
+
render from tokens with no JS" — `@hirobius/design-system/static.css` is that
|
|
629
|
+
promise, made concrete. It ships plain `.hds-badge` / `.hds-card` /
|
|
630
|
+
`.hds-alert` / `.hds-divider` / `.hds-tag` classes bound to the **same**
|
|
631
|
+
`--semantic-*` / `--component-*` / `--role-*` custom properties the React
|
|
632
|
+
`Badge`/`Card`/`Alert`/`Divider`/`Tag` components use — so a `.astro`/`.html`
|
|
633
|
+
page with zero React still renders on-token, theme-aware UI. This is the path
|
|
634
|
+
the `hirobius/clients` factory uses for non-interactive primitives on its
|
|
635
|
+
static Astro pages.
|
|
636
|
+
|
|
637
|
+
Import it once alongside `variables.css` (both are host-safe — no reset, no
|
|
638
|
+
bare element selectors, every rule scoped to `.hds-*`):
|
|
639
|
+
|
|
640
|
+
```astro
|
|
641
|
+
---
|
|
642
|
+
// src/layouts/Base.astro
|
|
643
|
+
import '@hirobius/design-system/variables.css'; // tokens only
|
|
644
|
+
import '@hirobius/design-system/static.css'; // .hds-* primitive classes
|
|
645
|
+
---
|
|
646
|
+
<html lang="en" data-hds data-theme="light">
|
|
647
|
+
<body><slot /></body>
|
|
648
|
+
</html>
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
`data-theme="dark"` (or `data-brand="slug"` via the brand overlay bridge, §12)
|
|
652
|
+
retheme every class below automatically — no JS, no rebuild.
|
|
653
|
+
|
|
654
|
+
### Badge
|
|
655
|
+
|
|
656
|
+
```html
|
|
657
|
+
<span class="hds-badge">Neutral</span>
|
|
658
|
+
<span class="hds-badge hds-badge--info">Info</span>
|
|
659
|
+
<span class="hds-badge hds-badge--success">Success</span>
|
|
660
|
+
<span class="hds-badge hds-badge--danger">Danger</span>
|
|
661
|
+
<span class="hds-badge hds-badge--warning">Warning</span>
|
|
662
|
+
<span class="hds-badge hds-badge--in-progress">In progress</span>
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Card
|
|
666
|
+
|
|
667
|
+
Root surface only — `variant` (`--accent`) and `tone` (`--danger`/`--success`/
|
|
668
|
+
`--warning`/`--info`), matching the React `Card`'s structural axis. Compose
|
|
669
|
+
header/body/footer with your own markup inside it; reach for the real
|
|
670
|
+
`<Card>` compound parts (`Card.Header`, `Card.Progress`, `Card.Metric`, …) if
|
|
671
|
+
you need the full slot anatomy — that still requires React.
|
|
672
|
+
|
|
673
|
+
```html
|
|
674
|
+
<div class="hds-card">Borderless default.</div>
|
|
675
|
+
<div class="hds-card hds-card--bordered">Opt-in neutral 1px border.</div>
|
|
676
|
+
<div class="hds-card hds-card--accent">Highlighted / "recommended" entry.</div>
|
|
677
|
+
<div class="hds-card hds-card--danger">Status-driven — the border is the signal.</div>
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Alert
|
|
681
|
+
|
|
682
|
+
`role="alert"` is an ARIA live-region role, not a styling concern — keep it on
|
|
683
|
+
the markup. Add `.hds-alert--with-title` alongside a tone modifier when using
|
|
684
|
+
the title + body pattern (it switches the cross-axis alignment from centered
|
|
685
|
+
to top-aligned, matching the React component's `hasTitle` behavior).
|
|
686
|
+
|
|
687
|
+
```html
|
|
688
|
+
<div class="hds-alert hds-alert--info" role="alert">
|
|
689
|
+
<span class="hds-alert__body">Heads up — this is an info alert.</span>
|
|
690
|
+
</div>
|
|
691
|
+
|
|
692
|
+
<div class="hds-alert hds-alert--danger hds-alert--with-title" role="alert">
|
|
693
|
+
<span>
|
|
694
|
+
<span class="hds-alert__title">Something went wrong</span>
|
|
695
|
+
<span class="hds-alert__body">The build failed on step 3.</span>
|
|
696
|
+
</span>
|
|
697
|
+
</div>
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Divider
|
|
701
|
+
|
|
702
|
+
```html
|
|
703
|
+
<hr class="hds-divider hds-divider--horizontal" />
|
|
704
|
+
<hr class="hds-divider hds-divider--horizontal hds-divider--strong" />
|
|
705
|
+
|
|
706
|
+
<div style="display: flex; align-items: center; gap: 12px;">
|
|
707
|
+
<span>Left</span>
|
|
708
|
+
<hr class="hds-divider hds-divider--vertical" aria-orientation="vertical" />
|
|
709
|
+
<span>Right</span>
|
|
710
|
+
</div>
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Tag
|
|
714
|
+
|
|
715
|
+
Two-part anatomy — a 44px accessible hit-target button (`.hds-tag`) wrapping
|
|
716
|
+
the compact visible pill (`.hds-tag__pill`), same split as the React
|
|
717
|
+
component. `.hds-tag--active` is the selected/pressed state (`aria-pressed`
|
|
718
|
+
still belongs on the markup):
|
|
719
|
+
|
|
720
|
+
```html
|
|
721
|
+
<button type="button" class="hds-tag" aria-pressed="false">
|
|
722
|
+
<span class="hds-tag__pill">Filter</span>
|
|
723
|
+
</button>
|
|
724
|
+
|
|
725
|
+
<button type="button" class="hds-tag hds-tag--active" aria-pressed="true">
|
|
726
|
+
<span class="hds-tag__pill">Selected</span>
|
|
727
|
+
</button>
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### What's out of scope
|
|
731
|
+
|
|
732
|
+
`static.css` covers non-interactive/CSS-only-interactive primitives. Anything
|
|
733
|
+
with real state (forms, dialogs, dropdowns, `ContactForm`) still needs a React
|
|
734
|
+
island (§12.4) — this layer doesn't attempt to reimplement component logic in
|
|
735
|
+
CSS, only the visual surface of the five primitives above.
|
|
736
|
+
|
|
737
|
+
## 15. Icons — the `/icons` subpath
|
|
738
|
+
|
|
739
|
+
`IconButton` and `Icon` take an `icon` prop. Get one from the curated subpath;
|
|
740
|
+
`lucide-react` does not need to be installed separately.
|
|
741
|
+
|
|
742
|
+
```tsx
|
|
743
|
+
import { IconButton } from '@hirobius/design-system';
|
|
744
|
+
import { Ellipsis } from '@hirobius/design-system/icons';
|
|
745
|
+
|
|
746
|
+
<IconButton icon={Ellipsis} label="Row actions" />;
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
The full name list is `iconSet.names` in the manifest (and the Icons section of
|
|
750
|
+
`llms.txt`). Canonical Lucide 0.487.0 names only: `MoreHorizontal` is
|
|
751
|
+
`Ellipsis`, `MoreVertical` is `EllipsisVertical`, `AlertTriangle` is
|
|
752
|
+
`TriangleAlert`, `Home` is `House`, `Filter` is `Funnel`. Icon-only actions (row
|
|
753
|
+
menus, close, edit) use `IconButton`; do not hand-roll a button with a glyph. For
|
|
754
|
+
an icon outside the set, install `lucide-react@0.487.0` so the `LucideIcon` type
|
|
755
|
+
stays identical.
|
|
756
|
+
|
|
757
|
+
`Calendar` and `Menu` share names with HDS components; alias them:
|
|
758
|
+
`import { Calendar as CalendarIcon, Menu as MenuIcon } from '@hirobius/design-system/icons'`.
|