@hotelfriendag/design-tokens 0.3.3 → 0.4.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 +330 -432
- package/UI_DESIGN.md +48 -5
- package/ai-rules/CLAUDE.md +5 -5
- package/ai-rules/cursorrules.template +2 -2
- package/ai-rules/github-copilot-instructions.md +6 -6
- package/ai-rules/system-prompt-compact.md +6 -6
- package/components.html +278 -1
- package/generate-tokens.cjs +8 -2
- package/package.json +1 -1
- package/pre-built/_tokens.scss +35 -32
- package/pre-built/components.css +306 -6
- package/pre-built/stylelint-design-system.cjs +12 -16
- package/pre-built/tailwind.additive.css +3 -0
- package/pre-built/tailwind.css +3 -0
- package/pre-built/tailwind.preset.js +5 -2
- package/pre-built/tokens.css +3 -0
- package/pre-built/tokens.d.ts +3 -0
- package/pre-built/tokens.js +36 -33
- package/pre-built/tokens.ts +36 -33
- package/scripts/validate-tokens.cjs +19 -1
- package/src/components.css +306 -6
- package/tokens.figma.json +5 -2
package/README.md
CHANGED
|
@@ -1,576 +1,474 @@
|
|
|
1
1
|
# HotelFriend Design System
|
|
2
2
|
|
|
3
|
-
Cross-project design foundation: tokens, generated outputs for every stack (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), the `.hf-*` component layer, and AI-tool rules.
|
|
3
|
+
Cross-project design foundation: tokens, generated outputs for every stack (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), the `.hf-*` component layer, and AI-tool rules. Distributed as the public npm package **`@hotelfriendag/design-tokens`**.
|
|
4
4
|
|
|
5
|
-
> **Extracted on 2026-05-25
|
|
5
|
+
> **Source repo:** `HotelFriendAG/design-system` (private). Extracted on 2026-05-25 from [`hotelfriend/backend-hf`](https://bitbucket.org/hotelfriend/backend-hf) `docs/portable-design/` to be the single source of truth other HotelFriend projects consume.
|
|
6
6
|
>
|
|
7
|
-
> **Status:** RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe
|
|
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
|
-
##
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
portable-design/
|
|
13
|
-
│
|
|
14
|
-
├── components.html ← PRIMARY · canonical visual reference (open in browser)
|
|
15
|
-
├── UI_DESIGN.md ← NARRATIVE · rationale, anatomy, decisions (AI reads this first)
|
|
16
|
-
├── tokens.figma.json ← TOKENS · atomic Tokens Studio export → feeds Figma + generators
|
|
17
|
-
│
|
|
18
|
-
├── pre-built/ ← GENERATED · drop one into your stack's build pipeline
|
|
19
|
-
│ ├── tailwind.css · Tailwind v4 @theme block (recommended)
|
|
20
|
-
│ ├── tailwind.preset.js · Tailwind v3 preset (legacy)
|
|
21
|
-
│ ├── tokens.css · vanilla CSS custom properties
|
|
22
|
-
│ ├── _tokens.scss · SCSS variables
|
|
23
|
-
│ ├── tokens.ts · TypeScript const
|
|
24
|
-
│ ├── shadcn-tokens.css · shadcn/ui contract
|
|
25
|
-
│ └── components.css · .hf-* component primitives (generated from src/components.css)
|
|
26
|
-
│
|
|
27
|
-
├── src/ ← hand-edited sources for the generator (currently: components.css only)
|
|
28
|
-
│
|
|
29
|
-
├── states-canonical.json ← curated interactive states for new components (use this)
|
|
30
|
-
├── states.json ← raw portal extraction (160 KB — prefer states-canonical.json)
|
|
31
|
-
├── generate-tokens.cjs ← Node script (no deps) — turns tokens into Tailwind/CSS/SCSS/TS/shadcn
|
|
32
|
-
│
|
|
33
|
-
├── ai-rules/ ← drop ONE into the new project's root
|
|
34
|
-
│ ├── CLAUDE.md · for Claude Code (autodetected)
|
|
35
|
-
│ ├── cursorrules.template · rename to .cursorrules
|
|
36
|
-
│ ├── github-copilot-instructions.md · put at .github/copilot-instructions.md
|
|
37
|
-
│ └── system-prompt-compact.md · compact prompt for ChatGPT/v0/Lovable
|
|
38
|
-
│
|
|
39
|
-
├── portal-audit.html ← ARCHIVAL · legacy portal audit snapshot (NOT for new code)
|
|
40
|
-
└── README.md ← you are here (quickstart)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Precedence rule — when files disagree
|
|
44
|
-
|
|
45
|
-
1. **`components.html`** — canonical for **visual decisions** (colors, sizes, anatomy)
|
|
46
|
-
2. **`tokens.figma.json`** — canonical for **token values**. Regenerate `pre-built/*` after editing
|
|
47
|
-
3. **`UI_DESIGN.md`** — canonical for **WHY decisions were made** (history, trade-offs, portal drift notes)
|
|
48
|
-
4. **`pre-built/*`** — **generated**, never hand-edit. Re-run `generate-tokens.cjs` after JSON changes
|
|
49
|
-
5. **`portal-audit.html`** — **archival only**. Shows what the legacy portal currently looks like — useful for migration tracking, NOT for new product UI
|
|
50
|
-
|
|
51
|
-
When `components.html` and `UI_DESIGN.md` disagree, **components.html wins** and `UI_DESIGN.md` is stale.
|
|
52
|
-
|
|
53
|
-
## 60-second quickstart
|
|
9
|
+
## Install
|
|
54
10
|
|
|
55
11
|
```bash
|
|
56
|
-
|
|
57
|
-
cp -r /path/to/portable-design ../new-project/docs/
|
|
58
|
-
|
|
59
|
-
# 2. Wire the CSS into your build (pick ONE)
|
|
60
|
-
# Tailwind v4: @import the pre-built tailwind.css
|
|
61
|
-
# Vanilla CSS: link tokens.css + components.css
|
|
62
|
-
# SCSS: @import _tokens.scss
|
|
63
|
-
|
|
64
|
-
# 3. Wire AI to follow the system (pick ONE)
|
|
65
|
-
cp docs/portable-design/ai-rules/CLAUDE.md ../../CLAUDE.md # Claude Code
|
|
66
|
-
cp docs/portable-design/ai-rules/cursorrules.template ../../.cursorrules
|
|
67
|
-
mkdir -p ../../.github && cp docs/portable-design/ai-rules/github-copilot-instructions.md ../../.github/copilot-instructions.md
|
|
12
|
+
pnpm add @hotelfriendag/design-tokens
|
|
68
13
|
```
|
|
69
14
|
|
|
70
|
-
|
|
15
|
+
Public package — no `.npmrc`, no auth, no CI secrets. Works from any project, any CI.
|
|
16
|
+
|
|
17
|
+
## Wire it up (pick your stack)
|
|
71
18
|
|
|
72
|
-
|
|
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`.
|
|
73
20
|
|
|
74
|
-
|
|
21
|
+
### Tailwind v4 (recommended — e.g. `ui-hf`)
|
|
75
22
|
|
|
76
23
|
```css
|
|
77
|
-
/*
|
|
24
|
+
/* your main CSS entry, where @import "tailwindcss" lives */
|
|
78
25
|
@import "tailwindcss";
|
|
79
|
-
@import "./docs/portable-design/pre-built/tailwind.css"; /* @theme — adds --color-hf-*, --text-hf-*, etc. */
|
|
80
|
-
@import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
|
|
81
26
|
|
|
82
|
-
/*
|
|
83
|
-
|
|
84
|
-
@
|
|
85
|
-
@source not "./docs/portable-design/portal-audit.html";
|
|
27
|
+
@import "@hotelfriendag/design-tokens/tailwind.css"; /* @theme tokens, hf- prefix */
|
|
28
|
+
@import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
|
|
29
|
+
@import "@hotelfriendag/design-tokens/status.css"; /* .status-{domain}-{state} */
|
|
86
30
|
```
|
|
87
31
|
|
|
88
|
-
|
|
32
|
+
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.
|
|
89
33
|
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
- `shadow-hf-modal` (= portal modal shadow)
|
|
94
|
-
- … etc. Your existing utility usage is **untouched**.
|
|
95
|
-
|
|
96
|
-
**For projects whose integration scripts ask for an "additive" target by name**, `--target=tailwind-v4-additive` is an explicit alias of `--target=tailwind-v4`. Identical output — the prefix made the additive filter unnecessary.
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
node generate-tokens.cjs --target=tailwind-v4-additive > pre-built/tailwind.additive.css
|
|
100
|
-
# (same bytes as --target=tailwind-v4)
|
|
34
|
+
```html
|
|
35
|
+
<button class="bg-hf-accent hover:bg-hf-accent-hover text-white h-10 px-5 rounded-hf-sm text-hf-base font-semibold">Save</button>
|
|
36
|
+
<span class="hf-pill status-booking-confirmed">Confirmed</span>
|
|
101
37
|
```
|
|
102
38
|
|
|
103
|
-
|
|
39
|
+
### SCSS (Yii / Laravel / WP — e.g. `backend-hf`)
|
|
104
40
|
|
|
105
|
-
|
|
41
|
+
The package ships `pre-built/_tokens.scss` with 135 compile-time variables — `$colorAccent`, `$colorFg`, `$colorBorder`, `$radiusSm`, `$shadowModal`, … Semantic tokens are resolved to concrete values (e.g. `$colorAccent: #24AFE8;`), so no runtime cascade is needed. `node_modules` must be on your Sass load path.
|
|
106
42
|
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
@
|
|
110
|
-
@import "./docs/portable-design/pre-built/tailwind.css"; /* @theme tokens */
|
|
111
|
-
@import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
|
|
112
|
-
```
|
|
43
|
+
```scss
|
|
44
|
+
// Dart Sass (sass-embedded / dart-sass) — node_modules on loadPaths, then:
|
|
45
|
+
@use '@hotelfriendag/design-tokens/pre-built/tokens' as ds;
|
|
113
46
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<div className="hf-modal__header">
|
|
121
|
-
<h2 className="hf-modal__title">Edit Guest</h2>
|
|
122
|
-
<button className="hf-modal__close">✕</button>
|
|
123
|
-
</div>
|
|
124
|
-
<div className="hf-modal__body">…</div>
|
|
125
|
-
<div className="hf-modal__footer"><button>Cancel</button><button>Save</button></div>
|
|
126
|
-
</div>
|
|
47
|
+
.my-btn {
|
|
48
|
+
background: ds.$colorAccent; // #24AFE8
|
|
49
|
+
color: #fff;
|
|
50
|
+
border-radius: ds.$radiusSm; // 6px
|
|
51
|
+
box-shadow: ds.$shadowModal;
|
|
52
|
+
}
|
|
127
53
|
```
|
|
128
54
|
|
|
129
|
-
|
|
55
|
+
```scss
|
|
56
|
+
// webpack sass-loader — bare path resolves node_modules (older sass-loader: prefix with ~)
|
|
57
|
+
@import '@hotelfriendag/design-tokens/pre-built/tokens';
|
|
130
58
|
|
|
131
|
-
|
|
132
|
-
// tailwind.config.js
|
|
133
|
-
const hfPreset = require('./docs/portable-design/pre-built/tailwind.preset.js');
|
|
134
|
-
module.exports = {
|
|
135
|
-
presets: [hfPreset],
|
|
136
|
-
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
|
|
137
|
-
};
|
|
59
|
+
.my-btn { background: $colorAccent; border-radius: $radiusSm; }
|
|
138
60
|
```
|
|
139
61
|
|
|
140
|
-
|
|
141
|
-
<link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
|
|
142
|
-
```
|
|
62
|
+
For the `.hf-*` component primitives, also pull the generated CSS once (it references `var(--color-hf-*)` — emit `tokens.css` into the page too so the custom properties exist at runtime):
|
|
143
63
|
|
|
144
|
-
|
|
64
|
+
```scss
|
|
65
|
+
@use '@hotelfriendag/design-tokens/pre-built/tokens'; // SCSS variables
|
|
66
|
+
```
|
|
67
|
+
```css
|
|
68
|
+
@import "@hotelfriendag/design-tokens/tokens.css"; /* :root custom properties */
|
|
69
|
+
@import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
|
|
70
|
+
@import "@hotelfriendag/design-tokens/status.css"; /* status pills */
|
|
71
|
+
```
|
|
145
72
|
|
|
146
|
-
|
|
73
|
+
> Why both? SCSS `$variables` are compile-time (good for your own rules); the `.hf-*` component CSS resolves `var(--color-hf-*)` at runtime, so the page needs `tokens.css` loaded for those custom properties to exist.
|
|
147
74
|
|
|
148
|
-
### Vue 3 / Nuxt
|
|
75
|
+
### Vue 3 / Nuxt (vanilla CSS variables)
|
|
149
76
|
|
|
150
77
|
```ts
|
|
151
78
|
// nuxt.config.ts
|
|
152
79
|
export default defineNuxtConfig({
|
|
153
80
|
css: [
|
|
154
|
-
'
|
|
155
|
-
'
|
|
81
|
+
'@hotelfriendag/design-tokens/tokens.css', // :root custom properties
|
|
82
|
+
'@hotelfriendag/design-tokens/components.css', // .hf-* primitives
|
|
83
|
+
'@hotelfriendag/design-tokens/status.css',
|
|
156
84
|
],
|
|
157
85
|
});
|
|
158
86
|
```
|
|
159
87
|
|
|
160
|
-
|
|
88
|
+
Then use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
|
|
161
89
|
|
|
162
|
-
###
|
|
90
|
+
### TypeScript / CSS-in-JS
|
|
163
91
|
|
|
164
|
-
```
|
|
165
|
-
//
|
|
166
|
-
@import 'docs/portable-design/pre-built/tokens';
|
|
167
|
-
@import 'docs/portable-design/pre-built/components.css';
|
|
92
|
+
```ts
|
|
93
|
+
import { tokens } from '@hotelfriendag/design-tokens'; // compiled tokens.js + .d.ts
|
|
168
94
|
|
|
169
|
-
.
|
|
170
|
-
background: $
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
95
|
+
const Button = styled.button`
|
|
96
|
+
background: ${tokens.color.accent.default}; // #24AFE8 (semantic + primitive share tokens.color.*)
|
|
97
|
+
border-radius: ${tokens.radius.sm}; // 6px
|
|
98
|
+
`;
|
|
174
99
|
```
|
|
175
100
|
|
|
176
|
-
###
|
|
101
|
+
### Next.js + shadcn/ui
|
|
177
102
|
|
|
178
|
-
|
|
179
|
-
import { tokens } from './docs/portable-design/pre-built/tokens';
|
|
103
|
+
Append `shadcn-tokens.css` to `app/globals.css` (after `@import "tailwindcss"`). shadcn components pick up `--primary`, `--background`, `--ring`, etc.
|
|
180
104
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
height: ${tokens.size.btnDefault};
|
|
184
|
-
border-radius: ${tokens.borderRadius.sm};
|
|
185
|
-
`;
|
|
105
|
+
```css
|
|
106
|
+
@import "@hotelfriendag/design-tokens/shadcn-tokens.css";
|
|
186
107
|
```
|
|
187
108
|
|
|
188
109
|
### Vanilla / static HTML
|
|
189
110
|
|
|
190
111
|
```html
|
|
191
|
-
<link rel="stylesheet" href="
|
|
192
|
-
<link rel="stylesheet" href="
|
|
112
|
+
<link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/tokens.css">
|
|
113
|
+
<link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/components.css">
|
|
114
|
+
<link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/status.css">
|
|
193
115
|
|
|
194
116
|
<span class="hf-pill status-booking-confirmed">Confirmed</span>
|
|
195
|
-
<button class="hf-modal__close">✕</button>
|
|
196
117
|
```
|
|
197
118
|
|
|
119
|
+
### No-npm fallback
|
|
120
|
+
|
|
121
|
+
If a consumer can't reach npmjs.com, the repo also works as a **git submodule** (relative `@import` paths). See the appendix in [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md).
|
|
122
|
+
|
|
123
|
+
## What you get
|
|
124
|
+
|
|
125
|
+
- `bg-hf-accent` = `#24AFE8` (brand) — Tailwind's `bg-blue-500` keeps its default
|
|
126
|
+
- `text-hf-base` = 14px — Tailwind's `text-base` keeps its default 16px
|
|
127
|
+
- `rounded-hf-sm` = 6px — Tailwind's `rounded-sm` keeps its default 2px
|
|
128
|
+
- `shadow-hf-modal`, `text-hf-fg`, `border-hf-border`, `bg-hf-bg-surface`, … — full semantic set
|
|
129
|
+
- Your existing utility usage is **untouched** (collision-safe prefix).
|
|
130
|
+
|
|
131
|
+
App code should reference **semantic** tokens (`accent`, `fg`, `bg-*`, `border`, `status-*`) — primitives (`hf-blue-500`, `hf-gray-300`) are an implementation detail the theming layer can swap.
|
|
132
|
+
|
|
198
133
|
## Component primitives shipped in `components.css`
|
|
199
134
|
|
|
200
135
|
| Class | Anatomy | See |
|
|
201
136
|
|---|---|---|
|
|
137
|
+
| `.hf-btn` + `--primary` / `--danger` / `--outline-primary` / `--outline-default` / `--cancel` (sizes `--sm` / `--icon`) | Buttons — 40px h, 6px radius, token-driven chrome | components.html#buttons |
|
|
138
|
+
| `.hf-input` / `.hf-textarea` / `.hf-select` (+ `.hf-select-wrap`) | Form controls — 39px h, accent focus ring, token caret | components.html#inputs |
|
|
139
|
+
| `.hf-form-field` + `__label` / `__hint` / `__error` (`--required`, `--error`) | Field wrapper — label + control + hint/error | components.html#inputs |
|
|
140
|
+
| `.hf-switch` | Toggle — styled checkbox, 40×22 track, accent when on | components.html#inputs |
|
|
141
|
+
| `.hf-card` + `__header/__title/__description/__body/__footer` (`--flat`) | Elevated card — 12px radius, card shadow | components.html#cards |
|
|
142
|
+
| `.hf-drawer` + `__backdrop/__panel/__header/__body/__footer` (`--left`) | Side-panel — backdrop + sliding panel | components.html#layout |
|
|
202
143
|
| `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
|
|
203
144
|
| `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Underline + segmented tabs | components.html#tabs |
|
|
204
|
-
| `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT
|
|
145
|
+
| `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
|
|
205
146
|
| `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
|
|
206
147
|
| `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
|
|
207
148
|
| `.hf-alert--tinted` / `--banner` / `--compact` | Modifiers — bg tint / full-width strip / compact-in-card | components.html#alerts |
|
|
208
|
-
| `.hf-toast` | Floating notification
|
|
209
|
-
| `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled
|
|
210
|
-
| `.hf-dropdown-menu` + `
|
|
211
|
-
| `.skeleton` + `.hf-spin` | Loading
|
|
149
|
+
| `.hf-toast` | Floating notification — 9px radius | components.html#alerts |
|
|
150
|
+
| `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
|
|
151
|
+
| `.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 |
|
|
152
|
+
| `.skeleton` + `.hf-spin` | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
|
|
212
153
|
|
|
213
|
-
##
|
|
154
|
+
## Usage examples
|
|
214
155
|
|
|
215
|
-
|
|
156
|
+
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.).
|
|
216
157
|
|
|
217
|
-
|
|
218
|
-
- canonical palette (primary `#24AFE8`, status colors, neutrals)
|
|
219
|
-
- typography (Roboto, sizes 11/13/14/15/16/18/20/22/26/30, weights 400/500/600)
|
|
220
|
-
- spacing scale (multiples of 4)
|
|
221
|
-
- radius scale (6/8/9/12/99)
|
|
222
|
-
- shadow set (default/subtle/wrapper/card/modal/outline/hover)
|
|
223
|
-
- component library (`.hf-modal`, `.hf-alert`, `.hf-pill`, `.hf-tab`, `.hf-pagination`, `.hf-check`, `.hf-dropdown-menu`, etc.)
|
|
224
|
-
- hard rules ("never hard-code hex", "always provide hover/focus/disabled", "no mixed icon sets")
|
|
158
|
+
### Status pill
|
|
225
159
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
160
|
+
```html
|
|
161
|
+
<span class="hf-pill status-booking-confirmed">Confirmed</span>
|
|
162
|
+
<span class="hf-pill status-order-waiting">Waiting</span>
|
|
163
|
+
<span class="hf-pill status-room-item-cleaning-dirty">Dirty</span>
|
|
164
|
+
```
|
|
231
165
|
|
|
232
|
-
|
|
166
|
+
### Tabs
|
|
233
167
|
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
168
|
+
```html
|
|
169
|
+
<div class="flex border-b border-hf-border">
|
|
170
|
+
<button class="hf-tab is-active">Overview</button>
|
|
171
|
+
<button class="hf-tab">Bookings <span class="hf-tab__count">12</span></button>
|
|
172
|
+
<button class="hf-tab">Guests</button>
|
|
173
|
+
<button class="hf-tab" disabled>Reports</button>
|
|
174
|
+
</div>
|
|
175
|
+
<!-- compact sub-filter variant: add .hf-tab--sm -->
|
|
242
176
|
```
|
|
243
177
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Or wire token regeneration into `package.json`:
|
|
178
|
+
### Pagination
|
|
247
179
|
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
"
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
180
|
+
```html
|
|
181
|
+
<div class="hf-pagination">
|
|
182
|
+
<button class="hf-pagination__item" disabled aria-label="Previous">‹</button>
|
|
183
|
+
<button class="hf-pagination__item is-active">1</button>
|
|
184
|
+
<button class="hf-pagination__item">2</button>
|
|
185
|
+
<button class="hf-pagination__item">3</button>
|
|
186
|
+
<span class="hf-pagination__ellipsis">…</span>
|
|
187
|
+
<button class="hf-pagination__item">12</button>
|
|
188
|
+
<button class="hf-pagination__item" aria-label="Next">›</button>
|
|
189
|
+
</div>
|
|
256
190
|
```
|
|
257
191
|
|
|
258
|
-
|
|
192
|
+
### Modal
|
|
259
193
|
|
|
260
|
-
|
|
194
|
+
```html
|
|
195
|
+
<div class="hf-modal max-w-[500px] mx-auto">
|
|
196
|
+
<div class="hf-modal__header">
|
|
197
|
+
<h2 class="hf-modal__title">Edit Guest</h2>
|
|
198
|
+
<button class="hf-modal__close" aria-label="Close">✕</button>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="hf-modal__body">…</div>
|
|
201
|
+
<div class="hf-modal__footer">
|
|
202
|
+
<button class="hf-btn hf-btn--cancel">Cancel</button>
|
|
203
|
+
<button class="hf-btn hf-btn--primary">Save</button>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<!-- footer with top border: add .hf-modal--with-footer-border on .hf-modal -->
|
|
207
|
+
```
|
|
261
208
|
|
|
262
|
-
|
|
263
|
-
- [ ] `node docs/portable-design/generate-tokens.cjs --target=css` runs cleanly and produces stable output.
|
|
264
|
-
- [ ] AI agent answers "what's the primary color?" with `#24AFE8`.
|
|
265
|
-
- [ ] First button generated by AI matches `.btn-primary` (40px / 15px / 600 / `#24AFE8` / 6px radius).
|
|
266
|
-
- [ ] Status badges resolve via `--status-{domain}-{state}-color` tokens.
|
|
267
|
-
- [ ] `.hf-modal`, `.hf-alert`, `.hf-pagination` render correctly when `pre-built/components.css` is loaded.
|
|
209
|
+
### Alert
|
|
268
210
|
|
|
269
|
-
|
|
211
|
+
```html
|
|
212
|
+
<div class="hf-alert hf-alert--success">
|
|
213
|
+
<div class="hf-alert__icon"><!-- icon svg --></div>
|
|
214
|
+
<div class="hf-alert__body">
|
|
215
|
+
<div class="hf-alert__title">Saved successfully</div>
|
|
216
|
+
<p class="hf-alert__text">Booking #1284 has been updated.</p>
|
|
217
|
+
</div>
|
|
218
|
+
<button class="hf-alert__close" aria-label="Dismiss">✕</button>
|
|
219
|
+
</div>
|
|
220
|
+
<!-- variants: --success | --info | --warn | --error · modifiers: --tinted | --banner | --compact -->
|
|
221
|
+
```
|
|
270
222
|
|
|
271
|
-
|
|
223
|
+
### Toast
|
|
272
224
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
-
|
|
278
|
-
|
|
225
|
+
```html
|
|
226
|
+
<div class="hf-toast">
|
|
227
|
+
<span class="hf-toast__icon"><!-- icon svg --></span>
|
|
228
|
+
<span class="hf-toast__text">Booking <strong>#2841</strong> confirmed</span>
|
|
229
|
+
<button class="hf-toast__close" aria-label="Dismiss">✕</button>
|
|
230
|
+
</div>
|
|
231
|
+
```
|
|
279
232
|
|
|
280
|
-
|
|
233
|
+
### Dropdown menu
|
|
281
234
|
|
|
282
|
-
|
|
235
|
+
```html
|
|
236
|
+
<div class="hf-dropdown-menu w-52">
|
|
237
|
+
<div class="hf-dropdown-menu__header">Actions</div>
|
|
238
|
+
<div class="hf-dropdown-menu__item">View details</div>
|
|
239
|
+
<div class="hf-dropdown-menu__item">Edit <span class="hf-dropdown-menu__shortcut">⌘E</span></div>
|
|
240
|
+
<div class="hf-dropdown-menu__item is-disabled">Refresh</div>
|
|
241
|
+
<div class="hf-dropdown-menu__divider"></div>
|
|
242
|
+
<div class="hf-dropdown-menu__item hf-dropdown-menu__item--danger">Delete</div>
|
|
243
|
+
</div>
|
|
244
|
+
<!-- pre-0.4 flat names (.hf-dropdown-header / -item / -divider) still work as deprecated aliases -->
|
|
245
|
+
```
|
|
283
246
|
|
|
284
|
-
|
|
247
|
+
### Checkbox & radio
|
|
285
248
|
|
|
286
|
-
|
|
249
|
+
```html
|
|
250
|
+
<input type="checkbox" class="hf-check" checked />
|
|
251
|
+
<input type="checkbox" class="hf-check" disabled />
|
|
252
|
+
<input type="radio" name="grp" class="hf-radio" checked />
|
|
253
|
+
```
|
|
287
254
|
|
|
288
|
-
###
|
|
255
|
+
### Buttons
|
|
289
256
|
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
cp docs/portable-design/scripts/pre-commit.sh .git/hooks/pre-commit
|
|
299
|
-
chmod +x .git/hooks/pre-commit
|
|
257
|
+
```html
|
|
258
|
+
<button class="hf-btn hf-btn--primary">Save</button>
|
|
259
|
+
<button class="hf-btn hf-btn--outline-primary">Preview</button>
|
|
260
|
+
<button class="hf-btn hf-btn--outline-default">Settings</button>
|
|
261
|
+
<button class="hf-btn hf-btn--cancel">Cancel</button>
|
|
262
|
+
<button class="hf-btn hf-btn--danger">Delete</button>
|
|
263
|
+
<button class="hf-btn hf-btn--primary hf-btn--sm">Small</button>
|
|
264
|
+
<button class="hf-btn hf-btn--icon hf-btn--outline-default" aria-label="Edit">✎</button>
|
|
300
265
|
```
|
|
301
266
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
### Stylelint config (recommended for consumer projects)
|
|
267
|
+
### Form controls & field
|
|
305
268
|
|
|
306
|
-
|
|
269
|
+
```html
|
|
270
|
+
<div class="hf-form-field">
|
|
271
|
+
<label class="hf-form-field__label hf-form-field__label--required">Email</label>
|
|
272
|
+
<input type="email" class="hf-input" placeholder="guest@example.com" />
|
|
273
|
+
<span class="hf-form-field__hint">We'll never share it.</span>
|
|
274
|
+
</div>
|
|
307
275
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
// Generated files may contain hex (fallbacks, primitives) — opt-out
|
|
319
|
-
{ files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
|
|
320
|
-
],
|
|
321
|
-
};
|
|
322
|
-
```
|
|
276
|
+
<div class="hf-form-field hf-form-field--error">
|
|
277
|
+
<label class="hf-form-field__label">Room</label>
|
|
278
|
+
<div class="hf-select-wrap">
|
|
279
|
+
<select class="hf-select">
|
|
280
|
+
<option>Deluxe</option>
|
|
281
|
+
<option>Suite</option>
|
|
282
|
+
</select>
|
|
283
|
+
</div>
|
|
284
|
+
<span class="hf-form-field__error">Please choose a room.</span>
|
|
285
|
+
</div>
|
|
323
286
|
|
|
324
|
-
|
|
287
|
+
<textarea class="hf-textarea" placeholder="Notes…"></textarea>
|
|
325
288
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
node scripts/validate-tokens.cjs
|
|
329
|
-
# ✓ validate-tokens: all checks passed
|
|
330
|
-
# 168 CSS variables defined, all var() refs resolve, no bare hex.
|
|
289
|
+
<!-- toggle: a styled checkbox -->
|
|
290
|
+
<input type="checkbox" class="hf-switch" checked />
|
|
331
291
|
```
|
|
332
292
|
|
|
333
|
-
|
|
334
|
-
1. `var(--*)` reference that doesn't resolve to a defined CSS variable
|
|
335
|
-
2. Bare hex literal in `components.css` / `status.css` (outside comments + `var()` fallbacks)
|
|
336
|
-
3. Token reference in doc code-blocks that doesn't exist in `pre-built/*`
|
|
337
|
-
|
|
338
|
-
## NPM package (npmjs.com — public)
|
|
293
|
+
### Card
|
|
339
294
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
295
|
+
```html
|
|
296
|
+
<section class="hf-card">
|
|
297
|
+
<header class="hf-card__header">
|
|
298
|
+
<div>
|
|
299
|
+
<h3 class="hf-card__title">Channel settings</h3>
|
|
300
|
+
<p class="hf-card__description">Enable the sales channels for this property.</p>
|
|
301
|
+
</div>
|
|
302
|
+
<button class="hf-btn hf-btn--outline-default hf-btn--sm">Edit</button>
|
|
303
|
+
</header>
|
|
304
|
+
<div class="hf-card__body">…</div>
|
|
305
|
+
<footer class="hf-card__footer">
|
|
306
|
+
<button class="hf-btn hf-btn--cancel">Cancel</button>
|
|
307
|
+
<button class="hf-btn hf-btn--primary">Save</button>
|
|
308
|
+
</footer>
|
|
309
|
+
</section>
|
|
310
|
+
<!-- borderless/flat variant: add .hf-card--flat -->
|
|
345
311
|
```
|
|
346
312
|
|
|
347
|
-
|
|
313
|
+
### Drawer (side-panel)
|
|
348
314
|
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
315
|
+
```html
|
|
316
|
+
<div class="hf-drawer">
|
|
317
|
+
<div class="hf-drawer__backdrop"></div>
|
|
318
|
+
<aside class="hf-drawer__panel">
|
|
319
|
+
<header class="hf-drawer__header">
|
|
320
|
+
<h2 class="hf-drawer__title">Reservation #2841</h2>
|
|
321
|
+
<button class="hf-drawer__close" aria-label="Close">✕</button>
|
|
322
|
+
</header>
|
|
323
|
+
<div class="hf-drawer__body">…</div>
|
|
324
|
+
<footer class="hf-drawer__footer">
|
|
325
|
+
<button class="hf-btn hf-btn--cancel">Close</button>
|
|
326
|
+
<button class="hf-btn hf-btn--primary">Check in</button>
|
|
327
|
+
</footer>
|
|
328
|
+
</aside>
|
|
329
|
+
</div>
|
|
330
|
+
<!-- slide from the left: add .hf-drawer--left on .hf-drawer -->
|
|
354
331
|
```
|
|
355
332
|
|
|
356
|
-
|
|
357
|
-
// TypeScript / JavaScript (default export — uses compiled tokens.js + tokens.d.ts)
|
|
358
|
-
import { tokens } from '@hotelfriendag/design-tokens';
|
|
333
|
+
## Cheat-sheet — common tokens & utilities
|
|
359
334
|
|
|
360
|
-
|
|
361
|
-
import { tokens } from '@hotelfriendag/design-tokens/tokens.ts';
|
|
362
|
-
```
|
|
335
|
+
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.
|
|
363
336
|
|
|
364
|
-
|
|
365
|
-
// SCSS
|
|
366
|
-
@import '@hotelfriendag/design-tokens/_tokens';
|
|
367
|
-
```
|
|
337
|
+
**Brand & text color**
|
|
368
338
|
|
|
369
|
-
|
|
339
|
+
| Utility | Token | Value |
|
|
340
|
+
|---|---|---|
|
|
341
|
+
| `bg-hf-accent` / `text-hf-accent` | `--color-hf-accent` | `#24AFE8` (brand) |
|
|
342
|
+
| `hover:bg-hf-accent-hover` | `--color-hf-accent-hover` | `#149AD1` |
|
|
343
|
+
| `bg-hf-accent-subtle` / `-subtler` | `--color-hf-accent-subtle` | light tints |
|
|
344
|
+
| `text-hf-fg` | `--color-hf-fg` | body `#2B2B2B` |
|
|
345
|
+
| `text-hf-fg-muted` / `-subtle` / `-faint` | `--color-hf-fg-*` | secondary → placeholder |
|
|
346
|
+
| `text-hf-on-accent` | `--color-hf-on-accent` | `#FFFFFF` (text on accent) |
|
|
370
347
|
|
|
371
|
-
|
|
372
|
-
and the workflow builds, validates, then `npm publish`-es to npmjs.com via
|
|
373
|
-
**Trusted Publishing (OIDC)**. No `NPM_TOKEN` secret, no PATs: GitHub Actions
|
|
374
|
-
mints a short-lived OIDC token that npm exchanges for publish credentials.
|
|
348
|
+
**Surfaces & borders**
|
|
375
349
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
350
|
+
| Utility | Token |
|
|
351
|
+
|---|---|
|
|
352
|
+
| `bg-hf-bg-surface` | card / modal / popover (white) |
|
|
353
|
+
| `bg-hf-bg-page` / `-section` / `-muted` | page → section → muted grays |
|
|
354
|
+
| `border-hf-border` / `-subtle` / `-strong` | default → faint → hover border |
|
|
380
355
|
|
|
381
|
-
|
|
382
|
-
# From the repo root, on main:
|
|
383
|
-
npm version patch # or minor / major → updates package.json, creates v* tag, commits
|
|
384
|
-
git push --follow-tags # workflow runs on the pushed tag and publishes
|
|
385
|
-
```
|
|
356
|
+
**Scales**
|
|
386
357
|
|
|
387
|
-
|
|
358
|
+
- **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
|
|
359
|
+
- **Radius:** `rounded-hf-sm` 6 · `-md` 8 · `-lg` 9 · `-xl` 12 · `-pill` 99
|
|
360
|
+
- **Shadow:** `shadow-hf-subtle` · `shadow-hf-card` · `shadow-hf-modal` · `shadow-hf-hover`
|
|
361
|
+
- **Status:** `text-hf-status-{success|warning|error|info|cancel}` + `bg-hf-status-{…}-bg` — or just use `.hf-pill .status-{domain}-{state}`
|
|
388
362
|
|
|
389
|
-
|
|
363
|
+
## Visual reference (live)
|
|
390
364
|
|
|
391
|
-
|
|
392
|
-
|---|---|
|
|
393
|
-
| Organization | `HotelFriendAG` |
|
|
394
|
-
| Repository | `design-system` |
|
|
395
|
-
| Workflow filename | `release.yml` |
|
|
365
|
+
`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:
|
|
396
366
|
|
|
397
|
-
|
|
367
|
+
**→ https://unpkg.com/@hotelfriendag/design-tokens/components.html**
|
|
398
368
|
|
|
399
|
-
|
|
369
|
+
Always serves the latest; pin a version with `…/design-tokens@0.4.0/components.html`. Or open the copy in your own project:
|
|
400
370
|
|
|
401
|
-
|
|
371
|
+
```bash
|
|
372
|
+
open node_modules/@hotelfriendag/design-tokens/components.html # macOS
|
|
373
|
+
xdg-open node_modules/@hotelfriendag/design-tokens/components.html # Linux
|
|
374
|
+
start node_modules\@hotelfriendag\design-tokens\components.html # Windows
|
|
375
|
+
```
|
|
402
376
|
|
|
403
|
-
|
|
377
|
+
> Use the **unpkg** link, not jsDelivr — jsDelivr serves `.html` as `text/plain` (shows source); unpkg serves it as `text/html` (renders).
|
|
404
378
|
|
|
405
|
-
|
|
379
|
+
## AI rules
|
|
406
380
|
|
|
407
|
-
|
|
408
|
-
- **Phase 3B** — `pre-built/stylelint-design-system.cjs` ships as a shareable Stylelint config. Forbids raw hex, named CSS colors, literal `box-shadow` declarations, and arbitrary-value Tailwind hex. Consumer projects extend it in their `.stylelintrc.cjs`.
|
|
409
|
-
- **Phase 3C** — `scripts/pre-commit.sh` ships as a Git pre-commit hook scaffold (husky-compatible). Runs `validate-tokens.cjs` when bundle files are staged. Skips silently when no bundle changes are present.
|
|
410
|
-
- **Phase 3D** — `package.json` defines the npm package skeleton:
|
|
411
|
-
- Exports map for every generated file + ai-rules + JSON sources
|
|
412
|
-
- `scripts.build` chains all generator targets (8 in total)
|
|
413
|
-
- `scripts.validate` runs the drift detector
|
|
414
|
-
- `scripts.prepublishOnly` re-builds + validates before publish
|
|
415
|
-
- `publishConfig` points at GitHub Packages registry (private/restricted access)
|
|
381
|
+
The package ships agent rules under `ai-rules/`. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
|
|
416
382
|
|
|
417
|
-
|
|
383
|
+
- `ai-rules/CLAUDE.md` → consumer root `CLAUDE.md` (Claude Code, autodetected)
|
|
384
|
+
- `ai-rules/cursorrules.template` → `.cursorrules`
|
|
385
|
+
- `ai-rules/github-copilot-instructions.md` → `.github/copilot-instructions.md`
|
|
386
|
+
- `ai-rules/system-prompt-compact.md` → compact prompt for ChatGPT / v0 / Lovable
|
|
418
387
|
|
|
419
|
-
|
|
388
|
+
If the consumer already has a `CLAUDE.md`, reference the package rules from it (e.g. point at `node_modules/@hotelfriendag/design-tokens/ai-rules/CLAUDE.md`) instead of overwriting. The agent learns the canonical palette, typography, spacing/radius/shadow scales, the `.hf-*` library, and the hard rules ("never hard-code hex", "always provide hover/focus/disabled").
|
|
420
389
|
|
|
421
|
-
|
|
390
|
+
## Keeping a consumer up to date
|
|
422
391
|
|
|
423
|
-
|
|
392
|
+
```bash
|
|
393
|
+
pnpm update @hotelfriendag/design-tokens # pull the latest published version
|
|
394
|
+
```
|
|
424
395
|
|
|
425
|
-
|
|
426
|
-
- **New target:** `node generate-tokens.cjs --target=status-css > pre-built/status.css` emits the `.status-{domain}-{state}` CSS rules with `var(--color-hf-status-*)` references
|
|
427
|
-
- **`pre-built/components.css`** — the `.status-*` block REMOVED; now you `@import` both files
|
|
428
|
-
- Adding a new status is JSON-only — no CSS edit
|
|
396
|
+
A convenient consumer script: `"design-system:update": "pnpm update @hotelfriendag/design-tokens && pnpm ls @hotelfriendag/design-tokens"`. Commit the bumped lockfile with `chore: bump design-system to <version>`.
|
|
429
397
|
|
|
430
|
-
|
|
398
|
+
## Drift detection & CI (for consumers)
|
|
431
399
|
|
|
432
|
-
|
|
400
|
+
**Stylelint config** — forbids raw hex / named colors / literal `box-shadow`:
|
|
433
401
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
402
|
+
```js
|
|
403
|
+
// .stylelintrc.cjs
|
|
404
|
+
module.exports = {
|
|
405
|
+
extends: [
|
|
406
|
+
'stylelint-config-standard',
|
|
407
|
+
'./node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs',
|
|
408
|
+
],
|
|
409
|
+
overrides: [
|
|
410
|
+
{ files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
```
|
|
442
414
|
|
|
443
|
-
|
|
415
|
+
**Pre-commit hook** — `scripts/pre-commit.sh` runs the token validator when bundle files are staged (husky-compatible).
|
|
444
416
|
|
|
445
|
-
|
|
417
|
+
---
|
|
446
418
|
|
|
447
|
-
|
|
448
|
-
1. Every `var(--*)` in `pre-built/*.css` resolves to a defined CSS variable
|
|
449
|
-
2. No bare hex literals in `components.css` / `status.css` (outside comments + `var()` fallbacks; `#fff`/`#000` allowed)
|
|
450
|
-
3. Every token reference in doc code-blocks exists in `pre-built/*` (anti-RFC-0001 §2.2 drift)
|
|
419
|
+
## Maintainer reference
|
|
451
420
|
|
|
452
|
-
|
|
421
|
+
### Repo layout & precedence
|
|
453
422
|
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
423
|
+
```
|
|
424
|
+
design-system/
|
|
425
|
+
├── components.html PRIMARY · canonical visual reference (open in browser)
|
|
426
|
+
├── UI_DESIGN.md NARRATIVE · rationale, anatomy, decisions
|
|
427
|
+
├── tokens.figma.json TOKENS · Tokens Studio export — single source of truth
|
|
428
|
+
├── status-map.json domain→semantic status mapping (feeds status.css)
|
|
429
|
+
├── src/components.css SOURCE for .hf-* rules (edit here)
|
|
430
|
+
├── generate-tokens.cjs Node generator (no deps)
|
|
431
|
+
├── pre-built/ GENERATED — never hand-edit (CI drift gate)
|
|
432
|
+
└── ai-rules/ agent rule files
|
|
458
433
|
```
|
|
459
434
|
|
|
460
|
-
|
|
435
|
+
Precedence when files disagree:
|
|
436
|
+
1. **`components.html`** — visual decisions (colors, sizes, anatomy)
|
|
437
|
+
2. **`tokens.figma.json`** — token values (regenerate `pre-built/*` after edits)
|
|
438
|
+
3. **`UI_DESIGN.md`** — WHY decisions were made
|
|
439
|
+
4. **`pre-built/*`** — generated, never hand-edit
|
|
440
|
+
5. **`portal-audit.html`** — archival only (legacy portal snapshot)
|
|
461
441
|
|
|
462
|
-
|
|
442
|
+
### Regenerating outputs
|
|
463
443
|
|
|
464
|
-
|
|
444
|
+
`tokens.figma.json` is the single source of truth; `src/components.css` is the source for the `.hf-*` layer. After editing either:
|
|
465
445
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
446
|
+
```bash
|
|
447
|
+
npm run build # regenerates all pre-built/* (tokens, tailwind v3+v4, scss, ts/js/dts, shadcn, status.css, components.css)
|
|
448
|
+
npm run validate # drift detector: every var() resolves, no bare hex
|
|
449
|
+
```
|
|
470
450
|
|
|
471
|
-
|
|
451
|
+
CI runs `git diff --exit-code -- pre-built/` so outputs must be reproducible from source. Edit `src/`, never `pre-built/`.
|
|
472
452
|
|
|
473
|
-
|
|
453
|
+
### Publishing
|
|
474
454
|
|
|
475
|
-
**
|
|
455
|
+
Releases are tag-triggered via `.github/workflows/release.yml` — push a `v*` tag and the workflow builds, validates, then `npm publish`-es to npmjs.com via **Trusted Publishing (OIDC)**. No `NPM_TOKEN` secret, no PATs: GitHub Actions mints a short-lived OIDC token that npm exchanges for publish credentials.
|
|
476
456
|
|
|
477
|
-
|
|
478
|
-
|---|---|
|
|
479
|
-
| `--color-hf-primary*` | `--color-hf-accent` / `-hover` / `-subtle` / `-subtler` |
|
|
480
|
-
| `--color-hf-text-primary` | `--color-hf-fg` |
|
|
481
|
-
| `--color-hf-text-secondary` | `--color-hf-fg-muted` |
|
|
482
|
-
| `--color-hf-text-useful` | `--color-hf-fg-subtle` |
|
|
483
|
-
| `--color-hf-text-placeholder` | `--color-hf-fg-faint` |
|
|
484
|
-
| `--color-hf-neutral-light-grey` | `--color-hf-bg-page` |
|
|
485
|
-
| `--color-hf-neutral-bg-accent` | `--color-hf-bg-section` |
|
|
486
|
-
| `--color-hf-neutral-very-light` | `--color-hf-bg-muted` |
|
|
487
|
-
| `--color-hf-neutral-border` | `--color-hf-border` |
|
|
488
|
-
| `--color-hf-neutral-light-blue-gray` | `--color-hf-border-subtle` |
|
|
489
|
-
| `--color-hf-neutral-border-hover` | `--color-hf-border-strong` |
|
|
490
|
-
| `--color-hf-status-success` etc. | unchanged ✓ (already canonical) |
|
|
491
|
-
| `--color-hf-badge-{domain}-{state}-*` | **removed** — see status-map note below |
|
|
492
|
-
|
|
493
|
-
**New: SEMANTIC status colors** (replace per-domain `badge.*` tokens):
|
|
457
|
+
`--provenance` is intentionally NOT used: npmjs rejects sigstore provenance from private source repositories, and this repo stays private (only the built artifact is public).
|
|
494
458
|
|
|
459
|
+
```bash
|
|
460
|
+
npm version patch # or minor / major → bumps package.json, creates v* tag, commits
|
|
461
|
+
git push --follow-tags # workflow runs on the tag and publishes
|
|
495
462
|
```
|
|
496
|
-
--color-hf-status-success / -bg / -strong
|
|
497
|
-
--color-hf-status-warning / -bg / -strong
|
|
498
|
-
--color-hf-status-error / -bg / -strong / -alt
|
|
499
|
-
--color-hf-status-info / -bg
|
|
500
|
-
--color-hf-status-cancel / -bg
|
|
501
|
-
--color-hf-status-coral / -bg (portal-specific)
|
|
502
|
-
--color-hf-status-violet / -bg (portal-specific)
|
|
503
|
-
--color-hf-status-olive / -bg (portal-specific)
|
|
504
|
-
--color-hf-status-orange-deep / -bg (portal-specific)
|
|
505
|
-
```
|
|
506
463
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
- Recognises `primitive.*` and `semantic.*` top-level groups in `tokens.figma.json`
|
|
511
|
-
- Resolves Tokens Studio `{path.to.token}` references — CSS targets emit `var()` chain (so theming works at runtime), TS/SCSS/v3-preset inline the resolved value
|
|
512
|
-
- All existing `--prefix=` / `--target=` flags unchanged
|
|
513
|
-
|
|
514
|
-
**Files affected:**
|
|
515
|
-
- `tokens.figma.json` — restructured: `primitive.color.{family}.{step}` + `semantic.color.{slot}.{variant}`
|
|
516
|
-
- `generate-tokens.cjs` — alias resolution + tier path normalisation
|
|
517
|
-
- `pre-built/*` — all regenerated; tokens.css now has primitives + semantic aliases
|
|
518
|
-
- `pre-built/components.css` — fully rewritten; binds chrome to semantic, hardcodes status-map (TODO externalize)
|
|
519
|
-
- `components.html` — **NOT touched** (still self-contained with its own pre-Phase 1B @theme). Phase 2 will reconcile.
|
|
520
|
-
|
|
521
|
-
### Phase 1A — RFC-0001 collision-safe prefixing (2026-05-21)
|
|
522
|
-
|
|
523
|
-
Solves the biggest RFC-0001 critique (§2.1, §4.2) — namespace collisions with Tailwind v4 defaults that silently resized utilities used thousands of times in `ui-hf`.
|
|
524
|
-
|
|
525
|
-
**Breaking change for `ui-hf` and any other consumer:** all token names now carry the `hf-` prefix INSIDE the category segment.
|
|
526
|
-
|
|
527
|
-
- `--color-primary` → `--color-hf-primary` (Tailwind utility: `bg-hf-primary`)
|
|
528
|
-
- `--text-base` → `--text-hf-base` (utility: `text-hf-base` = 14px; Tailwind's default `text-base` keeps 16px — no more silent override)
|
|
529
|
-
- `--radius-sm` → `--radius-hf-sm` (utility: `rounded-hf-sm` = 6px; default `rounded-sm` stays 2px)
|
|
530
|
-
- `--spacing-7` → `--spacing-hf-7` (utility: `p-hf-7` = 30px; default `p-7` stays 28px)
|
|
531
|
-
- `--font-sans` → `--font-hf-sans` (utility: `font-hf-sans` = Roboto)
|
|
532
|
-
- `--shadow-modal` → `--shadow-hf-modal` (utility: `shadow-hf-modal`)
|
|
533
|
-
- All `--color-badge-{domain}-{state}-*` → `--color-hf-badge-{domain}-{state}-*`
|
|
534
|
-
|
|
535
|
-
**Why the prefix goes INSIDE the category** (e.g. `--color-hf-primary`, not `--hf-color-primary`):
|
|
536
|
-
Tailwind v4 only generates utility classes from CSS variables that start with its known utility-prefixes (`--color-*`, `--text-*`, `--spacing-*`, `--radius-*`, `--shadow-*`, `--font-*`, etc.). A variable like `--hf-color-primary` would NOT generate any utility. The current convention preserves the utility-class derivation while keeping the namespace conflict-free.
|
|
537
|
-
|
|
538
|
-
**Files touched:**
|
|
539
|
-
- `tokens.figma.json` — unchanged (the prefix is applied at generation time, not in source)
|
|
540
|
-
- `generate-tokens.cjs` — new `--prefix=` argument; PREFIX defaults to `hf-`. Override with `--prefix=` (empty) for legacy unprefixed output
|
|
541
|
-
- `pre-built/*.css/scss/ts/js` — all regenerated with `--hf-` prefix in CSS targets (Tailwind v3 / SCSS / TS / shadcn unchanged for now)
|
|
542
|
-
- `pre-built/components.css` — all `var(--*)` refs updated to new prefixed names
|
|
543
|
-
- Docs — all `var(--color-primary)` etc. updated to `var(--color-hf-primary)` form
|
|
544
|
-
|
|
545
|
-
**What's NOT changed yet (Phase 1B):**
|
|
546
|
-
- `components.html` `@theme {}` block still uses unprefixed names (it's a self-contained demo — no collision risk in standalone use). Phase 1B will reconcile when the semantic tier lands.
|
|
547
|
-
- Tailwind v3 preset, SCSS, TS outputs stay unprefixed (legacy paths — less risk of collision in those ecosystems).
|
|
548
|
-
|
|
549
|
-
### Phase 0 — RFC-0001 quick wins (2026-05-21)
|
|
550
|
-
|
|
551
|
-
Addresses critique from `RFC-0001-cross-project-design-system.md` §2 + §8 — small fixes that don't require architectural decisions:
|
|
552
|
-
|
|
553
|
-
- **ESM compatibility** — renamed `generate-tokens.js` → `generate-tokens.cjs` so the script runs under Node in `"type":"module"` projects (Vite/Vue 3/Next).
|
|
554
|
-
- **Token name fix** — `color.primary.default` JSON key no longer emits `--color-primary-default` (awkward `bg-primary-default` utility). Generator now strips trailing `default`/`DEFAULT` so emit is `--color-primary` → utility `bg-primary` works. Same logic applies to any future `.default` sub-keys.
|
|
555
|
-
- **Shadow token namespace** — JSON group `boxShadow` → `shadow` so emit is `--shadow-modal` (was `--box-shadow-modal`). Matches what `components.html` and docs already reference.
|
|
556
|
-
- **`components.css` — RFC §5 conformance:**
|
|
557
|
-
- **No more hardcoded hex** (RFC §5a) — all values reference `var(--color-*)` / `var(--font-*)` / `var(--border-radius-*)` with hex fallbacks. The 4 remaining bare hex (`#fff`, `#252F4A`, `#DBDFE9`, `#F5F5F5`) are commented as Phase 1 TODOs for the semantic tier.
|
|
558
|
-
- **One namespace** (RFC §5c) — dropped the parallel `:root { --status-* }` declarations; consumes canonical `--color-badge-*` tokens directly.
|
|
559
|
-
- **Class names match token paths** (RFC §5d) — breaking renames:
|
|
560
|
-
- `.status-clean-*` → `.status-room-item-cleaning-*`
|
|
561
|
-
- `.status-fd-*` → `.status-front-desk-*`
|
|
562
|
-
- `.status-order-action` → `.status-order-action-required`
|
|
563
|
-
- **Doc/output snapshot** — added a smoke test verifying every `var(--*)` referenced in docs exists in `pre-built/tokens.css` or `pre-built/components.css`. Currently 0 missing.
|
|
564
|
-
- **Existing-project integration warning** — new "⚠️ Existing-project integration" section above documents the Tailwind v4 collision issue + `@source not` exclusion until Phase 1 lands prefixed tokens.
|
|
565
|
-
|
|
566
|
-
### Still open (deferred to Phase 1+, per RFC roadmap)
|
|
567
|
-
|
|
568
|
-
- Token namespace prefix (`--hf-*`) + `--target=tailwind-v4-additive` (RFC §4.2)
|
|
569
|
-
- Three-tier token architecture: primitive → semantic → component (RFC §4.1)
|
|
570
|
-
- `text-text-primary` awkward utility — fix when semantic tier introduces `--hf-color-fg` (RFC §8.3)
|
|
571
|
-
- ✅ ~~`pre-built/components.css` is hand-mirrored from `components.html` rather than generated (RFC §5b)~~ — resolved 2026-05-27 (ROADMAP #4): now generated from `src/components.css` by `--target=components-css`.
|
|
572
|
-
- Two competing visual SSOTs (`components.html` vs `UI_DESIGN.md`) — Phase 2 will pick one
|
|
464
|
+
The Trusted Publisher binding on npmjs (Package → Settings → Trusted publishing) is configured for org `HotelFriendAG`, repo `design-system`, workflow `release.yml`. Changing any of those requires updating the binding before the next release.
|
|
465
|
+
|
|
466
|
+
`prepublishOnly` re-runs the generator + validator so the published package is always self-consistent.
|
|
573
467
|
|
|
574
468
|
---
|
|
575
469
|
|
|
576
|
-
|
|
470
|
+
- **Changelog / phase history:** [`CHANGELOG.md`](CHANGELOG.md)
|
|
471
|
+
- **Remaining work:** [`ROADMAP.md`](ROADMAP.md)
|
|
472
|
+
- **First-consumer playbook:** [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md)
|
|
473
|
+
- **Architecture rationale:** [`RFC-0001`](RFC-0001-cross-project-design-system.md) · [`RFC-0002`](RFC-0002-semantic-tier-naming.md)
|
|
474
|
+
- **Visual reference:** `components.html` · **Token narrative:** `UI_DESIGN.md`
|