@hotelfriendag/design-tokens 0.3.4 → 0.6.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 +324 -5
- package/UI_DESIGN.md +65 -6
- package/ai-rules/CLAUDE.md +50 -28
- package/ai-rules/cursorrules.template +12 -11
- package/ai-rules/github-copilot-instructions.md +23 -12
- package/ai-rules/system-prompt-compact.md +16 -10
- package/components.html +612 -81
- package/generate-tokens.cjs +218 -2
- package/package.json +9 -1
- package/pre-built/_ionic.scss +51 -0
- package/pre-built/_tokens.scss +6 -2
- package/pre-built/components.css +521 -13
- package/pre-built/dark.css +30 -0
- package/pre-built/stylelint-design-system.cjs +12 -16
- package/pre-built/tailwind.additive.css +5 -1
- package/pre-built/tailwind.css +5 -1
- package/pre-built/tailwind.preset.js +38 -34
- package/pre-built/themes.json +75 -0
- package/pre-built/tokens.css +6 -2
- package/pre-built/tokens.d.ts +4 -0
- package/pre-built/tokens.js +8 -4
- package/pre-built/tokens.ts +8 -4
- package/pre-built/utilities.css +165 -0
- package/scripts/validate-tokens.cjs +191 -5
- package/src/components.css +521 -13
- package/states-canonical.json +1 -1
- package/tokens.figma.json +30 -5
package/README.md
CHANGED
|
@@ -6,6 +6,15 @@ Cross-project design foundation: tokens, generated outputs for every stack (CSS
|
|
|
6
6
|
>
|
|
7
7
|
> **Status:** RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe `hf-` prefix + drift CI). Published on npmjs.com via Trusted Publishing. See [`ROADMAP.md`](ROADMAP.md) for remaining items and [`CHANGELOG.md`](CHANGELOG.md) for phase history.
|
|
8
8
|
|
|
9
|
+
## Contract
|
|
10
|
+
|
|
11
|
+
[`CONTRACT.md`](CONTRACT.md) is the **normative design-system contract** — the single home for
|
|
12
|
+
versioning & breaking-change policy, the governed variation model (density / theme / brand),
|
|
13
|
+
error-UX mapping, the accessibility responsibility split, i18n posture, performance budgets, the
|
|
14
|
+
consumer build pipeline, token/Figma governance, the supported-stack matrix, and the proposal
|
|
15
|
+
process. Read it before proposing changes or integrating; this README and the integration playbooks
|
|
16
|
+
are the *consumption* detail under it.
|
|
17
|
+
|
|
9
18
|
## Install
|
|
10
19
|
|
|
11
20
|
```bash
|
|
@@ -16,7 +25,7 @@ Public package — no `.npmrc`, no auth, no CI secrets. Works from any project,
|
|
|
16
25
|
|
|
17
26
|
## Wire it up (pick your stack)
|
|
18
27
|
|
|
19
|
-
The package exposes each generated file via a subpath export, e.g. `@hotelfriendag/design-tokens/tailwind.css`, `/components.css`, `/status.css`, `/_tokens.scss`, `/tokens.css`, `/tokens.ts`, `/shadcn-tokens.css`.
|
|
28
|
+
The package exposes each generated file via a subpath export, e.g. `@hotelfriendag/design-tokens/tailwind.css`, `/components.css`, `/status.css`, `/_tokens.scss`, `/tokens.css`, `/tokens.ts`, `/shadcn-tokens.css`, `/dark.css`, `/utilities.css`, `/_ionic.scss`, `/themes.json`.
|
|
20
29
|
|
|
21
30
|
### Tailwind v4 (recommended — e.g. `ui-hf`)
|
|
22
31
|
|
|
@@ -32,7 +41,7 @@ The package exposes each generated file via a subpath export, e.g. `@hotelfriend
|
|
|
32
41
|
Put the DS imports **after** any project-local `@theme {}` block so DS values win `--color-hf-*` collisions. Import `tailwind.css` only — `tailwind.additive.css` is byte-identical (the `hf-` prefix made the additive filter unnecessary), importing both duplicates every declaration.
|
|
33
42
|
|
|
34
43
|
```html
|
|
35
|
-
<button class="bg-hf-accent hover:bg-hf-accent-hover text-
|
|
44
|
+
<button class="bg-hf-accent hover:bg-hf-accent-hover text-hf-on-accent h-10 px-5 rounded-hf-sm text-hf-base font-semibold">Save</button>
|
|
36
45
|
<span class="hf-pill status-booking-confirmed">Confirmed</span>
|
|
37
46
|
```
|
|
38
47
|
|
|
@@ -46,7 +55,7 @@ The package ships `pre-built/_tokens.scss` with 135 compile-time variables — `
|
|
|
46
55
|
|
|
47
56
|
.my-btn {
|
|
48
57
|
background: ds.$colorAccent; // #24AFE8
|
|
49
|
-
color: #
|
|
58
|
+
color: ds.$colorOn-accent; // #0B2F46 — AA text-on-accent (decision D1)
|
|
50
59
|
border-radius: ds.$radiusSm; // 6px
|
|
51
60
|
box-shadow: ds.$shadowModal;
|
|
52
61
|
}
|
|
@@ -87,6 +96,26 @@ export default defineNuxtConfig({
|
|
|
87
96
|
|
|
88
97
|
Then use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
|
|
89
98
|
|
|
99
|
+
### Dark theme
|
|
100
|
+
|
|
101
|
+
`dark.css` is a `[data-theme="dark"]` override of the ~15 semantic tokens (surfaces, text, borders, accent tints). Import it after `tokens.css` and flip `<html data-theme="dark">`:
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
@import "@hotelfriendag/design-tokens/tokens.css";
|
|
105
|
+
@import "@hotelfriendag/design-tokens/dark.css"; /* [data-theme="dark"] overrides */
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Then `<html data-theme="dark">` (or any container) switches the whole subtree. `pre-built/themes.json` is the machine-readable list of which semantic vars a theme overrides (the validation contract for custom themes).
|
|
109
|
+
|
|
110
|
+
### Utilities (non-Tailwind)
|
|
111
|
+
|
|
112
|
+
For Angular / plain-SCSS apps without Tailwind, `utilities.css` ships atomic helpers `.bg-hf-{name}`, `.text-hf-{name}`, `.border-hf-{name}`, `.shadow-hf-{name}` for the semantic tier (primitives via `tokens.css` vars directly). Requires `tokens.css` loaded:
|
|
113
|
+
|
|
114
|
+
```css
|
|
115
|
+
@import "@hotelfriendag/design-tokens/tokens.css";
|
|
116
|
+
@import "@hotelfriendag/design-tokens/utilities.css"; /* .bg-hf-*, .text-hf-*, .border-hf-*, .shadow-hf-* */
|
|
117
|
+
```
|
|
118
|
+
|
|
90
119
|
### TypeScript / CSS-in-JS
|
|
91
120
|
|
|
92
121
|
```ts
|
|
@@ -134,17 +163,307 @@ App code should reference **semantic** tokens (`accent`, `fg`, `bg-*`, `border`,
|
|
|
134
163
|
|
|
135
164
|
| Class | Anatomy | See |
|
|
136
165
|
|---|---|---|
|
|
166
|
+
| `.hf-btn` + `--primary` / `--danger` / `--outline-primary` / `--outline-default` / `--cancel` (sizes `--sm` / `--icon`) | Buttons — 40px h, 6px radius, token-driven chrome | components.html#buttons |
|
|
167
|
+
| `.hf-input` / `.hf-textarea` / `.hf-select` (+ `.hf-select-wrap`) | Form controls — 39px h, accent focus ring, token caret | components.html#inputs |
|
|
168
|
+
| `.hf-form-field` + `__label` / `__hint` / `__error` (`--required`, `--error`) | Field wrapper — label + control + hint/error | components.html#inputs |
|
|
169
|
+
| `.hf-switch` | Toggle — styled checkbox, 40×22 track, accent when on | components.html#inputs |
|
|
170
|
+
| `.hf-card` + `__header/__title/__description/__body/__footer` (`--flat`) | Elevated card — 12px radius, card shadow | components.html#cards |
|
|
171
|
+
| `.hf-drawer` + `__backdrop/__panel/__header/__body/__footer` (`--left`) | Side-panel — backdrop + sliding panel | components.html#layout |
|
|
172
|
+
| `.hf-table` (+ `__num`, `--static`) | Table — thead section bg, subtle row dividers, warm hover; style a plain `<table>` | components.html#table |
|
|
173
|
+
| `.hf-empty` + `__icon/__title/__text/__action` (`--compact`, `--row`) | Empty state — page-level, in-card, in-table-row | components.html#empty |
|
|
137
174
|
| `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
|
|
138
175
|
| `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Underline + segmented tabs | components.html#tabs |
|
|
139
176
|
| `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
|
|
140
177
|
| `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
|
|
141
178
|
| `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
|
|
142
179
|
| `.hf-alert--tinted` / `--banner` / `--compact` | Modifiers — bg tint / full-width strip / compact-in-card | components.html#alerts |
|
|
143
|
-
| `.hf-toast` | Floating notification — 9px radius | components.html#alerts |
|
|
180
|
+
| `.hf-toast` + `--success/--error/--warn/--info` | Floating notification — 9px radius; variant colors the icon + 3px left edge | components.html#alerts |
|
|
144
181
|
| `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
|
|
145
|
-
| `.hf-dropdown-menu` + `
|
|
182
|
+
| `.hf-dropdown-menu` + `__header / __item / __item-icon / __shortcut / __divider` (+ `__item--danger`) | 9px radius dropdown, portal shadow (legacy flat `.hf-dropdown-{header\|item\|divider}` kept as deprecated aliases) | components.html#dropdown |
|
|
146
183
|
| `.skeleton` + `.hf-spin` | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
|
|
147
184
|
|
|
185
|
+
## Usage examples
|
|
186
|
+
|
|
187
|
+
Copy-paste markup for the main `.hf-*` components — framework-agnostic (works in HTML, Vue, JSX; swap `class`/`className`). Icons are illustrative; wire your own (Lucide, etc.).
|
|
188
|
+
|
|
189
|
+
### Status pill
|
|
190
|
+
|
|
191
|
+
```html
|
|
192
|
+
<span class="hf-pill status-booking-confirmed">Confirmed</span>
|
|
193
|
+
<span class="hf-pill status-order-waiting">Waiting</span>
|
|
194
|
+
<span class="hf-pill status-room-item-cleaning-dirty">Dirty</span>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Tabs
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<div class="flex border-b border-hf-border">
|
|
201
|
+
<button class="hf-tab is-active">Overview</button>
|
|
202
|
+
<button class="hf-tab">Bookings <span class="hf-tab__count">12</span></button>
|
|
203
|
+
<button class="hf-tab">Guests</button>
|
|
204
|
+
<button class="hf-tab" disabled>Reports</button>
|
|
205
|
+
</div>
|
|
206
|
+
<!-- compact sub-filter variant: add .hf-tab--sm -->
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Pagination
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<div class="hf-pagination">
|
|
213
|
+
<button class="hf-pagination__item" disabled aria-label="Previous">‹</button>
|
|
214
|
+
<button class="hf-pagination__item is-active">1</button>
|
|
215
|
+
<button class="hf-pagination__item">2</button>
|
|
216
|
+
<button class="hf-pagination__item">3</button>
|
|
217
|
+
<span class="hf-pagination__ellipsis">…</span>
|
|
218
|
+
<button class="hf-pagination__item">12</button>
|
|
219
|
+
<button class="hf-pagination__item" aria-label="Next">›</button>
|
|
220
|
+
</div>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Modal
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<div class="hf-modal max-w-[500px] mx-auto">
|
|
227
|
+
<div class="hf-modal__header">
|
|
228
|
+
<h2 class="hf-modal__title">Edit Guest</h2>
|
|
229
|
+
<button class="hf-modal__close" aria-label="Close">✕</button>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="hf-modal__body">…</div>
|
|
232
|
+
<div class="hf-modal__footer">
|
|
233
|
+
<button class="hf-btn hf-btn--cancel">Cancel</button>
|
|
234
|
+
<button class="hf-btn hf-btn--primary">Save</button>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
<!-- footer with top border: add .hf-modal--with-footer-border on .hf-modal -->
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Alert
|
|
241
|
+
|
|
242
|
+
```html
|
|
243
|
+
<div class="hf-alert hf-alert--success">
|
|
244
|
+
<div class="hf-alert__icon"><!-- icon svg --></div>
|
|
245
|
+
<div class="hf-alert__body">
|
|
246
|
+
<div class="hf-alert__title">Saved successfully</div>
|
|
247
|
+
<p class="hf-alert__text">Booking #1284 has been updated.</p>
|
|
248
|
+
</div>
|
|
249
|
+
<button class="hf-alert__close" aria-label="Dismiss">✕</button>
|
|
250
|
+
</div>
|
|
251
|
+
<!-- variants: --success | --info | --warn | --error · modifiers: --tinted | --banner | --compact -->
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Toast
|
|
255
|
+
|
|
256
|
+
```html
|
|
257
|
+
<div class="hf-toast hf-toast--success">
|
|
258
|
+
<span class="hf-toast__icon"><!-- icon svg --></span>
|
|
259
|
+
<span class="hf-toast__text">Booking <strong>#2841</strong> confirmed</span>
|
|
260
|
+
<button class="hf-toast__close" aria-label="Dismiss">✕</button>
|
|
261
|
+
</div>
|
|
262
|
+
<!-- variants color the icon + 3px left edge: --success | --error | --warn | --info -->
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Dropdown menu
|
|
266
|
+
|
|
267
|
+
```html
|
|
268
|
+
<div class="hf-dropdown-menu w-52">
|
|
269
|
+
<div class="hf-dropdown-menu__header">Actions</div>
|
|
270
|
+
<div class="hf-dropdown-menu__item">View details</div>
|
|
271
|
+
<div class="hf-dropdown-menu__item">Edit <span class="hf-dropdown-menu__shortcut">⌘E</span></div>
|
|
272
|
+
<div class="hf-dropdown-menu__item is-disabled">Refresh</div>
|
|
273
|
+
<div class="hf-dropdown-menu__divider"></div>
|
|
274
|
+
<div class="hf-dropdown-menu__item hf-dropdown-menu__item--danger">Delete</div>
|
|
275
|
+
</div>
|
|
276
|
+
<!-- pre-0.4 flat names (.hf-dropdown-header / -item / -divider) still work as deprecated aliases -->
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Checkbox & radio
|
|
280
|
+
|
|
281
|
+
```html
|
|
282
|
+
<input type="checkbox" class="hf-check" checked />
|
|
283
|
+
<input type="checkbox" class="hf-check" disabled />
|
|
284
|
+
<input type="radio" name="grp" class="hf-radio" checked />
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Buttons
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<button class="hf-btn hf-btn--primary">Save</button>
|
|
291
|
+
<button class="hf-btn hf-btn--outline-primary">Preview</button>
|
|
292
|
+
<button class="hf-btn hf-btn--outline-default">Settings</button>
|
|
293
|
+
<button class="hf-btn hf-btn--cancel">Cancel</button>
|
|
294
|
+
<button class="hf-btn hf-btn--danger">Delete</button>
|
|
295
|
+
<button class="hf-btn hf-btn--primary hf-btn--sm">Small</button>
|
|
296
|
+
<button class="hf-btn hf-btn--icon hf-btn--outline-default" aria-label="Edit">✎</button>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Density / touch mode
|
|
300
|
+
|
|
301
|
+
Buttons and inputs read their height from a density scope — switch a whole app (or one container) with `data-density`; no per-component change. Default = desktop (40px); `touch` (POS/mobile) = 48px controls + 44px tap.
|
|
302
|
+
|
|
303
|
+
```html
|
|
304
|
+
<html data-density="touch"> <!-- 48px controls -->
|
|
305
|
+
<div data-density="comfortable">…</div> <!-- 44px, scoped -->
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| density | button | small | input |
|
|
309
|
+
|---|---|---|---|
|
|
310
|
+
| default | 40px | 32px | 39px |
|
|
311
|
+
| `comfortable` | 44px | 36px | 44px |
|
|
312
|
+
| `touch` | 48px | 40px | 48px |
|
|
313
|
+
|
|
314
|
+
### Form controls & field
|
|
315
|
+
|
|
316
|
+
```html
|
|
317
|
+
<div class="hf-form-field">
|
|
318
|
+
<label class="hf-form-field__label hf-form-field__label--required">Email</label>
|
|
319
|
+
<input type="email" class="hf-input" placeholder="guest@example.com" />
|
|
320
|
+
<span class="hf-form-field__hint">We'll never share it.</span>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<div class="hf-form-field hf-form-field--error">
|
|
324
|
+
<label class="hf-form-field__label">Room</label>
|
|
325
|
+
<div class="hf-select-wrap">
|
|
326
|
+
<select class="hf-select">
|
|
327
|
+
<option>Deluxe</option>
|
|
328
|
+
<option>Suite</option>
|
|
329
|
+
</select>
|
|
330
|
+
</div>
|
|
331
|
+
<span class="hf-form-field__error">Please choose a room.</span>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<textarea class="hf-textarea" placeholder="Notes…"></textarea>
|
|
335
|
+
|
|
336
|
+
<!-- toggle: a styled checkbox -->
|
|
337
|
+
<input type="checkbox" class="hf-switch" checked />
|
|
338
|
+
|
|
339
|
+
<!-- standalone invalid (no .hf-form-field wrapper) — for framework form state, e.g. Angular.
|
|
340
|
+
Either trigger gives the red border + red focus ring: -->
|
|
341
|
+
<input class="hf-input" aria-invalid="true" />
|
|
342
|
+
<input class="hf-input hf-input--error" /> <!-- alias class, same effect -->
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Card
|
|
346
|
+
|
|
347
|
+
```html
|
|
348
|
+
<section class="hf-card">
|
|
349
|
+
<header class="hf-card__header">
|
|
350
|
+
<div>
|
|
351
|
+
<h3 class="hf-card__title">Channel settings</h3>
|
|
352
|
+
<p class="hf-card__description">Enable the sales channels for this property.</p>
|
|
353
|
+
</div>
|
|
354
|
+
<button class="hf-btn hf-btn--outline-default hf-btn--sm">Edit</button>
|
|
355
|
+
</header>
|
|
356
|
+
<div class="hf-card__body">…</div>
|
|
357
|
+
<footer class="hf-card__footer">
|
|
358
|
+
<button class="hf-btn hf-btn--cancel">Cancel</button>
|
|
359
|
+
<button class="hf-btn hf-btn--primary">Save</button>
|
|
360
|
+
</footer>
|
|
361
|
+
</section>
|
|
362
|
+
<!-- borderless/flat variant: add .hf-card--flat -->
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Drawer (side-panel)
|
|
366
|
+
|
|
367
|
+
```html
|
|
368
|
+
<div class="hf-drawer">
|
|
369
|
+
<div class="hf-drawer__backdrop"></div>
|
|
370
|
+
<aside class="hf-drawer__panel">
|
|
371
|
+
<header class="hf-drawer__header">
|
|
372
|
+
<h2 class="hf-drawer__title">Reservation #2841</h2>
|
|
373
|
+
<button class="hf-drawer__close" aria-label="Close">✕</button>
|
|
374
|
+
</header>
|
|
375
|
+
<div class="hf-drawer__body">…</div>
|
|
376
|
+
<footer class="hf-drawer__footer">
|
|
377
|
+
<button class="hf-btn hf-btn--cancel">Close</button>
|
|
378
|
+
<button class="hf-btn hf-btn--primary">Check in</button>
|
|
379
|
+
</footer>
|
|
380
|
+
</aside>
|
|
381
|
+
</div>
|
|
382
|
+
<!-- slide from the left: add .hf-drawer--left on .hf-drawer -->
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Table
|
|
386
|
+
|
|
387
|
+
```html
|
|
388
|
+
<!-- style a plain <table>; wrap in a bordered container for the card look -->
|
|
389
|
+
<div class="rounded-lg border border-hf-border overflow-hidden">
|
|
390
|
+
<table class="hf-table">
|
|
391
|
+
<thead>
|
|
392
|
+
<tr><th>Guest</th><th>Status</th><th class="hf-table__num">Total</th></tr>
|
|
393
|
+
</thead>
|
|
394
|
+
<tbody>
|
|
395
|
+
<tr>
|
|
396
|
+
<td>Amanda Peterson</td>
|
|
397
|
+
<td><span class="hf-pill status-booking-confirmed">Confirmed</span></td>
|
|
398
|
+
<td class="hf-table__num">€ 1,240</td>
|
|
399
|
+
</tr>
|
|
400
|
+
</tbody>
|
|
401
|
+
</table>
|
|
402
|
+
</div>
|
|
403
|
+
<!-- disable the row hover (static summary tables): add .hf-table--static -->
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Empty state
|
|
407
|
+
|
|
408
|
+
```html
|
|
409
|
+
<!-- empty when the request SUCCEEDS with no results; use .skeleton while it's IN FLIGHT -->
|
|
410
|
+
<div class="hf-empty">
|
|
411
|
+
<div class="hf-empty__icon"><!-- icon svg --></div>
|
|
412
|
+
<h3 class="hf-empty__title">No bookings found</h3>
|
|
413
|
+
<p class="hf-empty__text">Try adjusting your filters.</p>
|
|
414
|
+
<div class="hf-empty__action">
|
|
415
|
+
<button class="hf-btn hf-btn--primary">New booking</button>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
<!-- --compact (in-card) · --row (inside an .hf-table colspan cell) -->
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Cheat-sheet — common tokens & utilities
|
|
422
|
+
|
|
423
|
+
Tailwind v4 derives utilities from the `@theme` tokens (`bg-hf-*`, `text-hf-*`, `rounded-hf-*`, `shadow-hf-*`). Vanilla CSS uses the `var(--color-hf-*)` form; SCSS uses `$colorAccent`, `$radiusSm`, … See [`UI_DESIGN.md`](UI_DESIGN.md) §9 for the full token list.
|
|
424
|
+
|
|
425
|
+
**Brand & text color**
|
|
426
|
+
|
|
427
|
+
| Utility | Token | Value |
|
|
428
|
+
|---|---|---|
|
|
429
|
+
| `bg-hf-accent` / `text-hf-accent` | `--color-hf-accent` | `#24AFE8` (brand) |
|
|
430
|
+
| `hover:bg-hf-accent-hover` | `--color-hf-accent-hover` | `#149AD1` |
|
|
431
|
+
| `bg-hf-accent-subtle` / `-subtler` | `--color-hf-accent-subtle` | light tints |
|
|
432
|
+
| `text-hf-fg` | `--color-hf-fg` | body `#2B2B2B` |
|
|
433
|
+
| `text-hf-fg-muted` / `-subtle` / `-faint` | `--color-hf-fg-*` | secondary → placeholder |
|
|
434
|
+
| `text-hf-on-accent` | `--color-hf-on-accent` | `#0B2F46` (AA text on accent — D1) |
|
|
435
|
+
|
|
436
|
+
**Surfaces & borders**
|
|
437
|
+
|
|
438
|
+
| Utility | Token |
|
|
439
|
+
|---|---|
|
|
440
|
+
| `bg-hf-bg-surface` | card / modal / popover (white) |
|
|
441
|
+
| `bg-hf-bg-page` / `-section` / `-muted` | page → section → muted grays |
|
|
442
|
+
| `border-hf-border` / `-subtle` / `-strong` | default → faint → hover border |
|
|
443
|
+
|
|
444
|
+
**Scales**
|
|
445
|
+
|
|
446
|
+
- **Text:** `text-hf-xs` 11 · `text-hf-sm` 13 · `text-hf-base` 14 · `text-hf-md` 15 · `text-hf-lg` 16 · `text-hf-xl` 18
|
|
447
|
+
- **Radius:** `rounded-hf-sm` 6 · `-md` 8 · `-lg` 9 · `-xl` 12 · `-pill` 99
|
|
448
|
+
- **Shadow:** `shadow-hf-subtle` · `shadow-hf-card` · `shadow-hf-modal` · `shadow-hf-hover`
|
|
449
|
+
- **Status:** `text-hf-status-{success|warning|error|info|cancel}` + `bg-hf-status-{…}-bg` — or just use `.hf-pill .status-{domain}-{state}`
|
|
450
|
+
|
|
451
|
+
## Visual reference (live)
|
|
452
|
+
|
|
453
|
+
`components.html` is the canonical rendered showcase of every `.hf-*` component. The package is public on npm, so **unpkg serves it rendered in the browser** — no hosting needed:
|
|
454
|
+
|
|
455
|
+
**→ https://unpkg.com/@hotelfriendag/design-tokens/components.html**
|
|
456
|
+
|
|
457
|
+
Always serves the latest; pin a version with `…/design-tokens@0.5.0/components.html`. Or open the copy in your own project:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
open node_modules/@hotelfriendag/design-tokens/components.html # macOS
|
|
461
|
+
xdg-open node_modules/@hotelfriendag/design-tokens/components.html # Linux
|
|
462
|
+
start node_modules\@hotelfriendag\design-tokens\components.html # Windows
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
> Use the **unpkg** link, not jsDelivr — jsDelivr serves `.html` as `text/plain` (shows source); unpkg serves it as `text/html` (renders).
|
|
466
|
+
|
|
148
467
|
## AI rules
|
|
149
468
|
|
|
150
469
|
The package ships agent rules under `ai-rules/`. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
|
package/UI_DESIGN.md
CHANGED
|
@@ -241,11 +241,14 @@ box-shadow: 0 3px 4px rgba(0, 0, 0, 0.03);
|
|
|
241
241
|
|
|
242
242
|
### Buttons
|
|
243
243
|
|
|
244
|
+
> **Design-system API (v0.4+):** these portal recipes now ship as the `.hf-btn` primitive in `pre-built/components.css`. Use `.hf-btn` + a modifier in new code; the legacy portal classes below are the source these were extracted from:
|
|
245
|
+
> `.btn-primary`→`.hf-btn--primary` · `.btn-hf-outline-primary`→`.hf-btn--outline-primary` · neutral `.btn`→`.hf-btn--outline-default` · `.btn-cancel`→`.hf-btn--cancel` · `.btn-delete`→`.hf-btn--danger` · `.btn-sm`→`.hf-btn--sm` · icon-only→`.hf-btn--icon`.
|
|
246
|
+
|
|
244
247
|
Live observation: 25+ unique button variants across 13 sidebar sections + 7 sub-tabs + 7 edit forms. Below is the canonical set; legacy variants (e.g. `btn.btn-b-gray`, `btn-text-blue`) exist but should be migrated.
|
|
245
248
|
|
|
246
249
|
| Style | Class | Look |
|
|
247
250
|
| ----------- | ------------------------------ | ------------------------------------------------------------ |
|
|
248
|
-
| **Primary** | `.btn.btn-primary` | Solid `#24AFE8` bg, white
|
|
251
|
+
| **Primary** | `.btn.btn-primary` | Solid `#24AFE8` bg, AA dark text `#0B2F46` (founder decision D1 — white failed WCAG AA at 2.51:1), 40px h, 15/600. Hover: `#149AD1` bg. |
|
|
249
252
|
| **Primary sm** | `.btn.btn-primary.btn-sm` | Solid bg, 32px h, 15/600, padding `7px 14px`. |
|
|
250
253
|
| **Primary search** | `.btn.btn-primary.pr13` | 38px h, padding `10px 13px` — used as inline search submit. |
|
|
251
254
|
| **Outline** | `.btn.btn-hf-outline-primary` | White bg, `#24AFE8` border + text. 40px h, 15/600. |
|
|
@@ -430,6 +433,7 @@ These are the **canonical component classes** shipped in `pre-built/components.c
|
|
|
430
433
|
- **Anatomy:** white bg, 9px radius, `0 6px 18px rgba(0,0,0,.10)` shadow, 1px border `rgba(72,91,120,.15)`
|
|
431
434
|
- **Layout:** inline-flex with `__icon` (20×20) + `__text` (flex:1) + optional `__close` (20×20)
|
|
432
435
|
- **Sizing:** min-width 280px, max-width 420px
|
|
436
|
+
- **Variants (v0.6):** `--success` / `--error` / `--warn` / `--info` — bound to the `--color-hf-status-*` families (same structure as `.hf-alert--*`). The variant sets the accent color via `currentColor` (which tints the `__icon`) and adds a 3px left edge; the message text stays `--color-hf-fg`. Use these instead of hand-coloring the icon.
|
|
433
437
|
- **Positioning:** typically `position: fixed; bottom: 16px; right: 16px; z-index: 50`
|
|
434
438
|
- **Reference:** `components.html#alerts` · **Portal source:** `.alert-box` (different DOM structure) · **Decision:** New modern primitive — portal `.alert-box` is positioned but uses heavier `.alert` chrome. Toast is lighter.
|
|
435
439
|
|
|
@@ -445,29 +449,84 @@ These are the **canonical component classes** shipped in `pre-built/components.c
|
|
|
445
449
|
|
|
446
450
|
### `.hf-dropdown-menu` — Dropdown
|
|
447
451
|
- **Anatomy:** 9px radius, white bg, `box-shadow: 0 1px 10px 0 rgba(0,0,0,.1)` (portal's `$dropdown_shadow`), 1px border `rgba(228,232,239,.6)`, padding `6px 0`, min-width 180px
|
|
448
|
-
- **Children
|
|
452
|
+
- **Children** (canonical BEM, v0.4+):
|
|
449
453
|
- `__header` — uppercase 11px/600 `#99A1B7` section label
|
|
450
454
|
- `__item` — flex row, padding `8px 16px`, 14px text, gap 10px
|
|
451
|
-
- `
|
|
452
|
-
- `
|
|
455
|
+
- `__item-icon` — 16px, color `#99A1B7` (faint), becomes `#50627E` on item hover
|
|
456
|
+
- `__shortcut` — right-aligned 12px text (for ⌘keys or counts)
|
|
453
457
|
- `__divider` — 1px `#E4E8EF` separator
|
|
454
458
|
- **Item states:**
|
|
455
459
|
- Default → hover `bg: #F5F5F5`
|
|
456
460
|
- `.is-active` → `bg: #EFF6FF; color: #24AFE8` (primary tint + primary text)
|
|
457
461
|
- `:focus-visible` → outline 2px primary inset
|
|
458
|
-
-
|
|
462
|
+
- `__item--danger` → red text, red-tint hover
|
|
459
463
|
- `.is-disabled` / `[aria-disabled="true"]` → `#AEBCCF` color, pointer-events none
|
|
460
|
-
- **
|
|
464
|
+
- **Deprecated aliases (still work, remove in v1.0):** the pre-0.4 flat names `.hf-dropdown-header`, `.hf-dropdown-item`, `.hf-dropdown-item__icon`, `.hf-dropdown-item__shortcut`, `.hf-dropdown-divider`, `.hf-dropdown-item.danger`.
|
|
465
|
+
- **Reference:** `components.html#dropdown` · **Portal source:** `.dropdown-menu.open` in `_custom.scss` · **Decision:** 1:1 portal match for shadow/radius/colors. **Adapted:** uses flat `<div>` structure instead of Bootstrap 3 `<ul><li><a>` (modern, framework-agnostic); renamed to BEM children of `.hf-dropdown-menu` in v0.4 for consistency with `.hf-modal__*` / `.hf-alert__*`.
|
|
466
|
+
|
|
467
|
+
### `.hf-btn` — Buttons (v0.4+)
|
|
468
|
+
- **Anatomy:** `inline-flex` center, height 40px, padding `0 20px`, 6px radius, 15px/600, `border: 1px solid transparent`, 200ms transitions. `:focus-visible` → 2px accent outline (offset 2px). `:disabled` / `.is-disabled` → opacity .6, not-allowed.
|
|
469
|
+
- **Variants:**
|
|
470
|
+
- `--primary` — accent bg, AA dark text `--color-hf-on-accent` (`#0B2F46`, 5.55:1 — founder decision D1; white failed AA); hover `--color-hf-accent-hover` (`#149AD1`)
|
|
471
|
+
- `--danger` — `--color-hf-status-error-strong` (`#D9534F`) bg; hover `--color-hf-status-error-strong-hover` (`#C9302C`)
|
|
472
|
+
- `--outline-primary` — white bg, accent text + border; hover bg-muted
|
|
473
|
+
- `--outline-default` — white bg, steel text, neutral border, `--shadow-hf-outline`; hover bg-muted
|
|
474
|
+
- `--cancel` — transparent, muted text; hover bg-muted
|
|
475
|
+
- **Sizes/shapes:** `--sm` (32px / 14px), `--icon` (square — width = height), `--block` (full width)
|
|
476
|
+
- **Density (v0.5):** height follows the `[data-density]` scope — desktop 40 / `comfortable` 44 / `touch` 48px (vars `--hf-btn-h` / `-sm`). Set `data-density="touch"` on a container/`<html>` for POS/mobile; see `ADOPTION.md`.
|
|
477
|
+
- **Reference:** `components.html#buttons` · **Portal source:** `.btn-primary` / `.btn-hf-outline-*` / `.btn-cancel` / `.btn-delete` · **Decision:** consolidates 25+ portal button recipes into one base + modifiers. Compact portal-only families (reset-filters, icon-square) stay Tailwind-utility recipes until promoted.
|
|
478
|
+
|
|
479
|
+
### `.hf-input` / `.hf-textarea` / `.hf-select` — Form controls (v0.4+)
|
|
480
|
+
- **Anatomy:** width 100%, height 39px, padding `0 12px`, 6px radius, 1px `--color-hf-input-border` (`#DBDFE9`), 14px text. Placeholder `--color-hf-fg-faint`.
|
|
481
|
+
- **Focus:** accent border + 3px `--color-hf-accent-subtle` ring (accessible — portal had no ring).
|
|
482
|
+
- **Invalid (v0.6):** a red border + red focus ring fires from any of three equivalent triggers — inside an `.hf-form-field--error` wrapper, the standalone `[aria-invalid="true"]` attribute, or the alias class `.hf-input--error` / `.hf-textarea--error` / `.hf-select--error`. The standalone forms let framework form wrappers (Angular) flag a control without the `.hf-form-field` wrapper.
|
|
483
|
+
- **Disabled:** bg-muted, faint text, not-allowed.
|
|
484
|
+
- **Textarea:** auto height, min-height 80px, vertical resize.
|
|
485
|
+
- **Select:** `appearance:none` + wrapper `.hf-select-wrap` draws a token-colored caret (clip-path triangle, same technique as `.hf-pill--dd` — no data-URI, so no bare hex).
|
|
486
|
+
- **Reference:** `components.html#inputs` · **Portal source:** `.form-control` · **Decision:** 1:1 geometry; adds the accessible focus ring the portal lacks.
|
|
487
|
+
|
|
488
|
+
### `.hf-switch` — Toggle (v0.4+)
|
|
489
|
+
- **Anatomy:** styled `<input type="checkbox">`, 40×22 track (999px radius), 18px white knob with `--shadow-hf-hover`. Off = `--color-hf-border`; `:checked` = accent bg + knob slides 18px right. `:focus-visible` outline; `:disabled` opacity .5.
|
|
490
|
+
- **Reference:** `components.html#inputs` · **Decision:** new lightweight token-driven primitive (portal shipped only a Bootstrap-Switch).
|
|
491
|
+
|
|
492
|
+
### `.hf-form-field` — Field wrapper (v0.4+)
|
|
493
|
+
- **Anatomy:** `flex column gap 6px`. `__label` (14px/500), `__label--required::after` red `*`, `__hint` (13px subtle), `__error` (13px error).
|
|
494
|
+
- **State:** `--error` colours the contained `.hf-input` / `.hf-textarea` / `.hf-select` border + focus ring in `--color-hf-status-error`.
|
|
495
|
+
- **Reference:** `components.html#inputs` · **Decision:** new primitive — standardises label + control + hint/error so consumers stop re-inventing field structure.
|
|
496
|
+
|
|
497
|
+
### `.hf-card` — Card (v0.4+)
|
|
498
|
+
- **Anatomy:** white bg, 1px `--color-hf-border-subtle`, 12px radius, `--shadow-hf-card`. `__header` (20px, bottom border) + `__title` (18px/600) + `__description` (14px muted) + `__body` (20px) + `__footer` (20px, top border, right-aligned).
|
|
499
|
+
- **Variant:** `--flat` removes the shadow.
|
|
500
|
+
- **Reference:** `components.html#cards` · **Portal source:** `.dashboard-card` · **Decision:** elevated card; for the lighter look use the bordered pattern `bg-white rounded-lg border p-5`.
|
|
501
|
+
|
|
502
|
+
### `.hf-drawer` — Side-panel (v0.4+)
|
|
503
|
+
- **Anatomy:** `position:fixed; inset:0; z-index:10051` (= `zIndex.modalDialog`), flex justify-end. `__backdrop` (`rgba(0,0,0,.4)`), `__panel` (max-width 480px, full height, `--shadow-hf-modal`, slide-in keyframe). `--left` slides from the left. `__header` / `__title` / `__close` / `__body` (scroll) / `__footer` mirror `.hf-modal`.
|
|
504
|
+
- **Reference:** `components.html#layout` · **Decision:** new primitive — consolidates the repeated backdrop + sliding-panel pattern (reservation / room / guest drawers).
|
|
505
|
+
|
|
506
|
+
### `.hf-table` — Table (v0.6+)
|
|
507
|
+
- **Anatomy:** style a plain `<table class="hf-table">` with element selectors — `thead` gets `--color-hf-bg-section` bg; `th` are uppercase 13px/500 `--color-hf-tab-fg-inactive` (steel) with `padding: 12px 20px`; `td` get a 1px `--color-hf-border-subtle` top divider (the first body row has none). Row hover = `--color-hf-menu-bg-hover` (the legacy portal `$table__hover`).
|
|
508
|
+
- **Helpers:** `.hf-table__num` on a th/td right-aligns (numeric / action columns). `--static` disables the row hover (summary tables).
|
|
509
|
+
- **Usage:** wrap in `bg-white rounded-lg border overflow-hidden` for the card look. An empty result renders `.hf-empty--row` inside a `<td colspan>` cell.
|
|
510
|
+
- **Reference:** `components.html#table` · **Decision:** new primitive — the thead-bg + divider + hover recipe was hand-rolled on every dashboard page; no new token (reuses existing semantic + component-tier tokens).
|
|
511
|
+
|
|
512
|
+
### `.hf-empty` — Empty state (v0.6+)
|
|
513
|
+
- **Anatomy:** centered flex column — `__icon` (64px disc, `--color-hf-bg-muted` bg, faint icon) + `__title` (18px/600) + `__text` (14px muted, max-w 28rem) + `__action` (button row).
|
|
514
|
+
- **Modifiers:** `--compact` (in-card — 40px transparent icon, tighter padding, 14px title) · `--row` (inside an `.hf-table` `<td colspan>` — single centered line + optional secondary action).
|
|
515
|
+
- **Usage rule:** empty states are for when the request **succeeds with no results**; use `.skeleton` while the request is **in flight** — they are not interchangeable (documented in the CSS).
|
|
516
|
+
- **Reference:** `components.html#empty` · **Decision:** new primitive — promotes the three demo recipes (page / in-card / in-table) into shipped CSS; no new token.
|
|
461
517
|
|
|
462
518
|
### `.skeleton` / `.hf-spin` — Loading primitives
|
|
463
519
|
- **Skeleton:** linear-gradient shimmer animation, 1.4s infinite, 4px radius. Use as block element with width/height utilities (e.g. `<div class="skeleton h-3 w-3/4">`)
|
|
464
520
|
- **Spinner:** `@keyframes hf-spin` rotate animation. Apply `.hf-spin` to an SVG.
|
|
521
|
+
- **Reduced motion (v0.6):** a `@media (prefers-reduced-motion: reduce)` block stops the infinite shimmer + spin (skeleton keeps a flat muted block) and neutralizes the drawer slide-in.
|
|
465
522
|
- **Reference:** `components.html#empty` · **Decision:** New primitives — portal has no equivalent.
|
|
466
523
|
|
|
467
524
|
---
|
|
468
525
|
|
|
469
526
|
## 6. Forms
|
|
470
527
|
|
|
528
|
+
> **Design-system API (v0.4+):** these ship as `.hf-input` / `.hf-textarea` / `.hf-select` + `.hf-form-field` + `.hf-switch` in `pre-built/components.css` (see §5.1). Use those in new code; the portal-observed specs below are the source. The DS focus ring (accent border + 3px `--color-hf-accent-subtle`) is the accessible variant.
|
|
529
|
+
|
|
471
530
|
- **Inputs (Text / Select):** Height `39px`, Padding `6px 12px`, Radius `6px`, Border `1px solid #d1d6dd`. Placeholder `#AEBCCF`. **Focus (portal-exact):** border darkens only to `#B2BAC4`, **no ring**. **Focus (accessible, recommended for new projects):** add `box-shadow: 0 0 0 2px rgba(36,175,232,0.2)` for WCAG compliance.
|
|
472
531
|
- **Field Anatomy:**
|
|
473
532
|
1. Label (`14px / 500`, color `#2B2B2B`, margin-bottom `8px`).
|
package/ai-rules/CLAUDE.md
CHANGED
|
@@ -4,29 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
## Project: {{PROJECT_NAME}}
|
|
6
6
|
|
|
7
|
-
This project follows the **HotelFriend Design System
|
|
7
|
+
This project follows the **HotelFriend Design System** (@hotelfriendag/design-tokens — version per the installed package, see CHANGELOG.md). The design system is the SSOT for all UI work — tokens, components, states, spacing, typography. Do NOT introduce off-system colors, sizes, or one-off shadows.
|
|
8
8
|
|
|
9
9
|
## Where the design system lives in this repo
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
11
|
+
Files are shipped in the `@hotelfriendag/design-tokens` npm package. After `pnpm add @hotelfriendag/design-tokens` they are available via subpath exports:
|
|
12
|
+
|
|
13
|
+
- **`node_modules/@hotelfriendag/design-tokens/components.html`** — **PRIMARY visual reference** (open in browser, or use the public URL https://unpkg.com/@hotelfriendag/design-tokens/components.html). Canonical rendering of every component. When in doubt, look here.
|
|
14
|
+
- `node_modules/@hotelfriendag/design-tokens/UI_DESIGN.md` — narrative SSOT (read this first for context, anatomy, decision rationale)
|
|
15
|
+
- `node_modules/@hotelfriendag/design-tokens/states-canonical.json` — canonical interactive states for 24 components (modal, alert, pagination, dropdown items, checkboxes etc.)
|
|
16
|
+
- Import paths (use `@hotelfriendag/design-tokens/<file>` in build tools):
|
|
17
|
+
- `@hotelfriendag/design-tokens/tailwind.css` (Tailwind v4, recommended) — `@theme {}` token bindings
|
|
18
|
+
- `@hotelfriendag/design-tokens/tokens.css` (vanilla CSS variables)
|
|
19
|
+
- `@hotelfriendag/design-tokens/components.css` — `.hf-*` component primitives
|
|
20
|
+
- `@hotelfriendag/design-tokens/status.css` — `.status-{domain}-{state}` rules (generated from status-map.json)
|
|
21
|
+
- `@hotelfriendag/design-tokens/pre-built/_tokens.scss` (SCSS variables — Dart Sass: `@use '@hotelfriendag/design-tokens/pre-built/tokens' as ds`)
|
|
22
|
+
- `@hotelfriendag/design-tokens/tokens.ts` (TypeScript const)
|
|
23
|
+
- `@hotelfriendag/design-tokens/shadcn-tokens.css` (shadcn/ui contract)
|
|
24
|
+
- `portal-audit.html` — **archival** legacy portal snapshot. Do NOT copy patterns from here for new code.
|
|
24
25
|
|
|
25
26
|
## Hard rules — never violate these
|
|
26
27
|
|
|
27
|
-
1. **Colors:** Use only tokens defined in
|
|
28
|
-
2. **Status badges:** Use `.hf-pill .status-{domain}-{state}
|
|
29
|
-
3. **Component primitives — reuse, never rebuild:** Before writing a modal, alert, dropdown, pagination, tabs, checkbox, or
|
|
28
|
+
1. **Colors:** Use only tokens defined in `@hotelfriendag/design-tokens/tailwind.css` / `@hotelfriendag/design-tokens/tokens.css`. Never hard-code hex codes. The primary brand color is `#24AFE8` (CSS var `--color-hf-accent`, SCSS `$colorAccent`, Tailwind `bg-hf-accent`). The four status colors are success `#59B59D`, warning `#FFBD5A`, error `#EA6565`, coral `#F87921`.
|
|
29
|
+
2. **Status badges:** Use `.hf-pill .status-{domain}-{state}` (rules in `status.css`). Each class sets `color`/`background` from a semantic slot — `var(--color-hf-status-{role})` and `var(--color-hf-status-{role}-bg)` (15% alpha) — and the domain→role map lives in `status-map.json`. Cancellation reasons (`canceled-by-hotel/guest/hf/pos`) all share the `.status-cancel` alias. Do NOT invent new status colors.
|
|
30
|
+
3. **Component primitives — reuse, never rebuild:** Before writing a button, input, modal, alert, dropdown, pagination, tabs, checkbox, toggle, card, or drawer, **check `@hotelfriendag/design-tokens/components.css` for an existing `.hf-*` primitive**. Available: `.hf-btn` (`--primary` / `--danger` / `--outline-primary` / `--outline-default` / `--cancel`, sizes `--sm` / `--icon`); `.hf-input` / `.hf-textarea` / `.hf-select` (wrap `<select>` in `.hf-select-wrap`); `.hf-switch`; `.hf-form-field` (`__label` / `__hint` / `__error`, state `--error`); `.hf-card`; `.hf-drawer`; `.hf-modal`; `.hf-alert`; `.hf-pagination`; `.hf-dropdown-menu`; `.hf-check` / `.hf-radio`; `.hf-pill`; `.hf-tab`; `.hf-toast` (`--success` / `--error` / `--warn` / `--info`); `.hf-table`; `.hf-empty` (`--compact` / `--row`); invalid state `[aria-invalid="true"]` / `.hf-input--error`. Use BEM-style children (`__header`, `__body`, `__footer`, `__title`, `__close`, `__item`, …) and modifiers (`--primary`, `--success`, `--tinted`, `--banner`, `--compact`, `--sm`). For dropdowns the canonical children are `.hf-dropdown-menu__header / __item / __item-icon / __shortcut / __divider` and modifier `.hf-dropdown-menu__item--danger` (the older flat `.hf-dropdown-{header|item|divider}` names still work as deprecated aliases). See `UI_DESIGN.md` §5.1 for the full list.
|
|
30
31
|
4. **Typography:** Family is **Roboto**. Sizes from canonical scale only: 11/13/14/15/16/18/20/24/26/30 px. Weights only 400 / 500 / 600. Line-heights 1.2 (headings), 1.5 (body), 1 (chips).
|
|
31
32
|
5. **Spacing:** Multiples of 4 only. Default section gap is 20px. Never write 7px / 13px / 18px — round to the scale. (Exception: portal-exact padding `7px 10px 9px` for `.hf-alert` `help-block` is documented in components.html.)
|
|
32
33
|
6. **Border radius:** `6px` (buttons/inputs/checkboxes/modals/alerts), `8px` (pagination items), `9px` (dropdowns/toasts/pill-tabs), `12px` (large cards), `99px` (status pills are 6px square — pills are NOT round). Never invent intermediate values.
|
|
@@ -35,29 +36,50 @@ This project follows the **HotelFriend Design System v0.1.0**. The design system
|
|
|
35
36
|
9. **Pagination active state:** Pagination active is a **subtle gray** (`bg: #E4E8EF; color: #252F4A`) — NOT primary blue. This is portal-exact and easy to get wrong. Use `.hf-pagination__item.is-active`.
|
|
36
37
|
10. **Components:** Before creating a new component, check `components.html` for an existing equivalent (open in browser, search section IDs `#buttons`, `#inputs`, `#cards`, `#status`, `#tabs`, `#dropdown`, `#modal`, `#alerts`, `#pagination`, `#empty`, `#layout`). Reuse beats duplicate.
|
|
37
38
|
|
|
39
|
+
## Accessibility — required markup
|
|
40
|
+
|
|
41
|
+
The design system ships CSS only; the **JS behaviors and ARIA wiring below are YOUR responsibility**
|
|
42
|
+
(focus trap, arrow-key navigation, Escape/dismissal, focus return, `aria-live`). Always supply the
|
|
43
|
+
required markup for these components:
|
|
44
|
+
|
|
45
|
+
- **Modal** — `role="dialog"` + `aria-modal="true"` + a labelled title (`aria-labelledby`) + a focus trap + Escape-to-close.
|
|
46
|
+
- **Drawer** — same as modal (dialog semantics, focus trap, Escape).
|
|
47
|
+
- **Tabs** — `role="tablist"` / `role="tab"` / `role="tabpanel"` + `aria-selected` + arrow-key navigation.
|
|
48
|
+
- **Dropdown menu** — `role="menu"` / `role="menuitem"` + arrow keys + Escape + focus return to the trigger on close.
|
|
49
|
+
- **Switch** — a native `<input type="checkbox">` (or `role="switch"`) with an associated `<label>`.
|
|
50
|
+
- **Checkbox / radio** — native `<input type="checkbox|radio">` (`.hf-check` / `.hf-radio` style native inputs).
|
|
51
|
+
- **Icon-only buttons** — `aria-label` (the icon is not an accessible name).
|
|
52
|
+
- **Pagination** — wrap in `<nav aria-label="Pagination">`.
|
|
53
|
+
- **Toasts** — render inside an `aria-live="polite"` region.
|
|
54
|
+
- **Tables** — `<th scope="col">` / `<th scope="row">` on header cells.
|
|
55
|
+
|
|
56
|
+
WCAG 2.1 **AA is the floor.** Keep `:focus-visible` outlines (the design system ships them — never
|
|
57
|
+
remove without a visual replacement) and don't rely on placeholder text to convey required
|
|
58
|
+
information (placeholder contrast is intentionally below AA).
|
|
59
|
+
|
|
38
60
|
## When the user asks for UI work
|
|
39
61
|
|
|
40
|
-
1. **Read `UI_DESIGN.md`** — start here for new sessions.
|
|
41
|
-
2. **Open `components.html`** in a browser — see the canonical rendering.
|
|
42
|
-
3. **Pick existing `.hf-*` primitives** from
|
|
43
|
-
4. **States matter:** every interactive element needs default + hover + focus + disabled + loading. See `states-canonical.json` for the exact declarations to mirror.
|
|
62
|
+
1. **Read `UI_DESIGN.md`** (at `node_modules/@hotelfriendag/design-tokens/UI_DESIGN.md`) — start here for new sessions.
|
|
63
|
+
2. **Open `components.html`** in a browser (`node_modules/@hotelfriendag/design-tokens/components.html`, or https://unpkg.com/@hotelfriendag/design-tokens/components.html) — see the canonical rendering.
|
|
64
|
+
3. **Pick existing `.hf-*` primitives** from `@hotelfriendag/design-tokens/components.css` first. Use Tailwind utility classes only for layout (flex/grid/spacing) — not for component chrome (colors, shadows, borders).
|
|
65
|
+
4. **States matter:** every interactive element needs default + hover + focus + disabled + loading. See `states-canonical.json` (`node_modules/@hotelfriendag/design-tokens/states-canonical.json`) for the exact declarations to mirror.
|
|
44
66
|
5. **No "raw HTML" answers** — wrap markup in the documented component class system. If the design system lacks something, propose a NEW token or primitive in a comment, do not silently introduce one.
|
|
45
67
|
|
|
46
68
|
## When the user asks for "the same look as the portal"
|
|
47
69
|
|
|
48
|
-
The visual reference is `components.html
|
|
70
|
+
The visual reference is `components.html` (`node_modules/@hotelfriendag/design-tokens/components.html` or https://unpkg.com/@hotelfriendag/design-tokens/components.html). If the user asks "make a settings page like the portal", do this in order:
|
|
49
71
|
|
|
50
72
|
1. Open `components.html`, find Page Layouts section (`#layout`). Pick the matching pattern (List / Detail / Form / Dashboard).
|
|
51
73
|
2. Replicate the structure (Title bar → Tabs → Filters → Data grid → Footer pagination).
|
|
52
|
-
3. Use
|
|
74
|
+
3. Use `.hf-btn` + a variant (`.hf-btn--primary`, `.hf-btn--outline-primary`, `.hf-btn--cancel`, `.hf-btn--danger`) for buttons.
|
|
53
75
|
4. Use `.hf-pill .status-{domain}-{state}` for status indicators.
|
|
54
|
-
5. Wrap data sections in `.bg-white rounded-lg border border-border p-5`
|
|
76
|
+
5. Wrap data sections in `.hf-card` (`__header` / `__body` / `__footer`), or the lighter bordered pattern `.bg-white rounded-lg border border-border p-5` — both common in the portal.
|
|
55
77
|
6. Use `.hf-pagination` for pager.
|
|
56
78
|
7. Use `.hf-modal` / `.hf-alert` / `.hf-dropdown-menu` for floating UI.
|
|
57
79
|
|
|
58
80
|
## Anti-patterns
|
|
59
81
|
|
|
60
|
-
- ❌ `<button class="bg-blue-500 text-white px-4 py-2 rounded">Save</button>` (Tailwind native classes for what should be a `.btn.btn
|
|
82
|
+
- ❌ `<button class="bg-blue-500 text-white px-4 py-2 rounded">Save</button>` (Tailwind native classes for what should be a `.hf-btn.hf-btn--primary`)
|
|
61
83
|
- ❌ Inventing colors like `#1F8FCE` because "it's close to primary"
|
|
62
84
|
- ❌ Border-radius `8px` because "it looks better than 6px" (8px is reserved for pagination items only)
|
|
63
85
|
- ❌ Adding a new shadow `0 4px 12px rgba(...)` instead of `var(--shadow-hf-modal)`
|
|
@@ -67,7 +89,7 @@ The visual reference is `components.html`. If the user asks "make a settings pag
|
|
|
67
89
|
- ❌ Pagination with `bg-hf-primary text-white` active — use `.hf-pagination__item.is-active` (subtle gray)
|
|
68
90
|
- ❌ Using native `<input type="checkbox" class="accent-primary">` instead of `<input type="checkbox" class="hf-check">` — accent-color doesn't match portal's filled-blue-with-white-tick design
|
|
69
91
|
- ❌ Mixing icon sets (Lucide + Font Awesome + Heroicons in the same screen)
|
|
70
|
-
- ❌ Copying patterns from `portal-audit.html` — that's the legacy snapshot, NOT canonical reference
|
|
92
|
+
- ❌ Copying patterns from `portal-audit.html` (`node_modules/@hotelfriendag/design-tokens/portal-audit.html`) — that's the legacy snapshot, NOT canonical reference
|
|
71
93
|
|
|
72
94
|
## When in doubt
|
|
73
95
|
|
|
@@ -75,6 +97,6 @@ Ask the user. Better to pause for 5 seconds than ship a one-off color that drift
|
|
|
75
97
|
|
|
76
98
|
---
|
|
77
99
|
|
|
78
|
-
**System version:**
|
|
100
|
+
**System version:** @hotelfriendag/design-tokens — version per the installed package (see CHANGELOG.md)
|
|
79
101
|
**Maintained:** alongside `UI_DESIGN.md`
|
|
80
|
-
**Refresh policy:** when `tokens.figma.json` changes, run `node generate-tokens.cjs
|
|
102
|
+
**Refresh policy:** when `tokens.figma.json` changes in the design-system repo, run `npm run build` (which runs `node generate-tokens.cjs` for all targets). The `pre-built/components.css` file is generated from `src/components.css` by `node generate-tokens.cjs --target=components-css`; hand-edits to `pre-built/` are wiped by the next build and CI fails on drift. Consumer repos update with `pnpm update @hotelfriendag/design-tokens`.
|