@hotelfriendag/design-tokens 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +558 -0
- package/README.uk.md +377 -0
- package/RFC-0001-cross-project-design-system.md +273 -0
- package/RFC-0002-semantic-tier-naming.md +296 -0
- package/UI_DESIGN.md +608 -0
- package/ai-rules/CLAUDE.md +80 -0
- package/ai-rules/cursorrules.template +39 -0
- package/ai-rules/github-copilot-instructions.md +45 -0
- package/ai-rules/system-prompt-compact.md +43 -0
- package/components.html +3018 -0
- package/generate-tokens.cjs +665 -0
- package/package.json +98 -0
- package/portal-audit.html +2306 -0
- package/pre-built/_tokens.scss +138 -0
- package/pre-built/components.css +515 -0
- package/pre-built/shadcn-tokens.css +67 -0
- package/pre-built/status.css +51 -0
- package/pre-built/stylelint-design-system.cjs +69 -0
- package/pre-built/tailwind.additive.css +158 -0
- package/pre-built/tailwind.css +158 -0
- package/pre-built/tailwind.preset.js +207 -0
- package/pre-built/tokens.css +185 -0
- package/pre-built/tokens.d.ts +240 -0
- package/pre-built/tokens.js +243 -0
- package/pre-built/tokens.ts +240 -0
- package/scripts/integration-smoke.sh +91 -0
- package/scripts/pre-commit.sh +55 -0
- package/scripts/validate-tokens.cjs +113 -0
- package/states-canonical.json +240 -0
- package/states.json +2950 -0
- package/status-map.json +47 -0
- package/tokens.figma.json +230 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HotelFriend AG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
# HotelFriend Design System
|
|
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.
|
|
4
|
+
|
|
5
|
+
> **Extracted on 2026-05-25** from [`hotelfriend/backend-hf`](https://bitbucket.org/hotelfriend/backend-hf) `docs/portable-design/` to make this the single source of truth that other HotelFriend projects consume.
|
|
6
|
+
>
|
|
7
|
+
> **Status:** RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe prefixing + drift CI). Versioned package distribution (`@hotelfriendag/design-tokens` via GitHub Packages) is the next step — see `RFC-0001-cross-project-design-system.md` §9 for the full status checklist.
|
|
8
|
+
|
|
9
|
+
## File hierarchy (read in this order)
|
|
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 (extracted from components.html)
|
|
26
|
+
│
|
|
27
|
+
├── states-canonical.json ← curated interactive states for new components (use this)
|
|
28
|
+
├── states.json ← raw portal extraction (160 KB — prefer states-canonical.json)
|
|
29
|
+
├── generate-tokens.cjs ← Node script (no deps) — turns tokens into Tailwind/CSS/SCSS/TS/shadcn
|
|
30
|
+
│
|
|
31
|
+
├── ai-rules/ ← drop ONE into the new project's root
|
|
32
|
+
│ ├── CLAUDE.md · for Claude Code (autodetected)
|
|
33
|
+
│ ├── cursorrules.template · rename to .cursorrules
|
|
34
|
+
│ ├── github-copilot-instructions.md · put at .github/copilot-instructions.md
|
|
35
|
+
│ └── system-prompt-compact.md · compact prompt for ChatGPT/v0/Lovable
|
|
36
|
+
│
|
|
37
|
+
├── portal-audit.html ← ARCHIVAL · legacy portal audit snapshot (NOT for new code)
|
|
38
|
+
└── README.md ← you are here (quickstart)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Precedence rule — when files disagree
|
|
42
|
+
|
|
43
|
+
1. **`components.html`** — canonical for **visual decisions** (colors, sizes, anatomy)
|
|
44
|
+
2. **`tokens.figma.json`** — canonical for **token values**. Regenerate `pre-built/*` after editing
|
|
45
|
+
3. **`UI_DESIGN.md`** — canonical for **WHY decisions were made** (history, trade-offs, portal drift notes)
|
|
46
|
+
4. **`pre-built/*`** — **generated**, never hand-edit. Re-run `generate-tokens.cjs` after JSON changes
|
|
47
|
+
5. **`portal-audit.html`** — **archival only**. Shows what the legacy portal currently looks like — useful for migration tracking, NOT for new product UI
|
|
48
|
+
|
|
49
|
+
When `components.html` and `UI_DESIGN.md` disagree, **components.html wins** and `UI_DESIGN.md` is stale.
|
|
50
|
+
|
|
51
|
+
## 60-second quickstart
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 1. Copy this whole folder into the new project (anywhere; we suggest docs/)
|
|
55
|
+
cp -r /path/to/portable-design ../new-project/docs/
|
|
56
|
+
|
|
57
|
+
# 2. Wire the CSS into your build (pick ONE)
|
|
58
|
+
# Tailwind v4: @import the pre-built tailwind.css
|
|
59
|
+
# Vanilla CSS: link tokens.css + components.css
|
|
60
|
+
# SCSS: @import _tokens.scss
|
|
61
|
+
|
|
62
|
+
# 3. Wire AI to follow the system (pick ONE)
|
|
63
|
+
cp docs/portable-design/ai-rules/CLAUDE.md ../../CLAUDE.md # Claude Code
|
|
64
|
+
cp docs/portable-design/ai-rules/cursorrules.template ../../.cursorrules
|
|
65
|
+
mkdir -p ../../.github && cp docs/portable-design/ai-rules/github-copilot-instructions.md ../../.github/copilot-instructions.md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## ⚠️ Existing-project integration
|
|
69
|
+
|
|
70
|
+
> **Status:** ✅ Solved in Phase 1A (RFC-0001 §4.2). All emitted tokens carry the `hf-` prefix INSIDE the category segment (`--color-hf-*`, `--text-hf-*`, `--radius-hf-*`, `--spacing-hf-*`, `--font-hf-*`, `--shadow-hf-*`). None of them can collide with Tailwind v4 defaults. The full `@import` is now safe in existing projects.
|
|
71
|
+
|
|
72
|
+
**Recommended setup for any project (greenfield or existing):**
|
|
73
|
+
|
|
74
|
+
```css
|
|
75
|
+
/* app/globals.css */
|
|
76
|
+
@import "tailwindcss";
|
|
77
|
+
@import "./docs/portable-design/pre-built/tailwind.css"; /* @theme — adds --color-hf-*, --text-hf-*, etc. */
|
|
78
|
+
@import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
|
|
79
|
+
|
|
80
|
+
/* Optional: exclude the bundle's own demo HTML from your Tailwind scan
|
|
81
|
+
so legacy classes / bg-[#hex] from the showcase don't leak into your bundle. */
|
|
82
|
+
@source not "./docs/portable-design/components.html";
|
|
83
|
+
@source not "./docs/portable-design/portal-audit.html";
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
What this gives you:
|
|
87
|
+
|
|
88
|
+
- `bg-hf-accent` (= `#24AFE8` — brand) — Tailwind's `bg-blue-500` keeps its default
|
|
89
|
+
- `text-hf-base` (= 14px — body) — Tailwind's `text-base` keeps its default 16px
|
|
90
|
+
- `rounded-hf-sm` (= 6px) — Tailwind's `rounded-sm` keeps its default 2px
|
|
91
|
+
- `shadow-hf-modal` (= portal modal shadow)
|
|
92
|
+
- … etc. Your existing utility usage is **untouched**.
|
|
93
|
+
|
|
94
|
+
**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.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
node generate-tokens.cjs --target=tailwind-v4-additive > pre-built/tailwind.additive.css
|
|
98
|
+
# (same bytes as --target=tailwind-v4)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Stack-specific snippets
|
|
102
|
+
|
|
103
|
+
### React + Tailwind v4 (recommended, greenfield)
|
|
104
|
+
|
|
105
|
+
```css
|
|
106
|
+
/* app/globals.css */
|
|
107
|
+
@import "tailwindcss";
|
|
108
|
+
@import "./docs/portable-design/pre-built/tailwind.css"; /* @theme tokens */
|
|
109
|
+
@import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```jsx
|
|
113
|
+
<button className="bg-hf-primary hover:bg-hf-primary-hover text-white h-10 px-5 rounded-hf text-hf-md font-semibold">
|
|
114
|
+
Save
|
|
115
|
+
</button>
|
|
116
|
+
<span className="hf-pill status-booking-confirmed">Confirmed</span>
|
|
117
|
+
<div className="hf-modal max-w-[500px]">
|
|
118
|
+
<div className="hf-modal__header">
|
|
119
|
+
<h2 className="hf-modal__title">Edit Guest</h2>
|
|
120
|
+
<button className="hf-modal__close">✕</button>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="hf-modal__body">…</div>
|
|
123
|
+
<div className="hf-modal__footer"><button>Cancel</button><button>Save</button></div>
|
|
124
|
+
</div>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### React + Tailwind v3 (legacy)
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
// tailwind.config.js
|
|
131
|
+
const hfPreset = require('./docs/portable-design/pre-built/tailwind.preset.js');
|
|
132
|
+
module.exports = {
|
|
133
|
+
presets: [hfPreset],
|
|
134
|
+
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Next.js + shadcn/ui
|
|
143
|
+
|
|
144
|
+
Append `pre-built/shadcn-tokens.css` to your `app/globals.css`. shadcn components automatically pick up `--primary`, `--background`, `--ring`, etc.
|
|
145
|
+
|
|
146
|
+
### Vue 3 / Nuxt
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// nuxt.config.ts
|
|
150
|
+
export default defineNuxtConfig({
|
|
151
|
+
css: [
|
|
152
|
+
'~/docs/portable-design/pre-built/tokens.css',
|
|
153
|
+
'~/docs/portable-design/pre-built/components.css',
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
|
|
159
|
+
|
|
160
|
+
### SCSS-based (Yii / Laravel / WP)
|
|
161
|
+
|
|
162
|
+
```scss
|
|
163
|
+
// _app.scss
|
|
164
|
+
@import 'docs/portable-design/pre-built/tokens';
|
|
165
|
+
@import 'docs/portable-design/pre-built/components.css';
|
|
166
|
+
|
|
167
|
+
.my-btn {
|
|
168
|
+
background: $colorPrimaryDefault;
|
|
169
|
+
height: $sizeBtnDefault;
|
|
170
|
+
border-radius: $borderRadiusSm;
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### TS / CSS-in-JS
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { tokens } from './docs/portable-design/pre-built/tokens';
|
|
178
|
+
|
|
179
|
+
const Button = styled.button`
|
|
180
|
+
background: ${tokens.color.primary.default};
|
|
181
|
+
height: ${tokens.size.btnDefault};
|
|
182
|
+
border-radius: ${tokens.borderRadius.sm};
|
|
183
|
+
`;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Vanilla / static HTML
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<link rel="stylesheet" href="docs/portable-design/pre-built/tokens.css">
|
|
190
|
+
<link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
|
|
191
|
+
|
|
192
|
+
<span class="hf-pill status-booking-confirmed">Confirmed</span>
|
|
193
|
+
<button class="hf-modal__close">✕</button>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Component primitives shipped in `components.css`
|
|
197
|
+
|
|
198
|
+
| Class | Anatomy | See |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
|
|
201
|
+
| `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Underline + segmented tabs | components.html#tabs |
|
|
202
|
+
| `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT primary!) — 34×34, 8px radius | components.html#pagination |
|
|
203
|
+
| `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
|
|
204
|
+
| `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
|
|
205
|
+
| `.hf-alert--tinted` / `--banner` / `--compact` | Modifiers — bg tint / full-width strip / compact-in-card | components.html#alerts |
|
|
206
|
+
| `.hf-toast` | Floating notification (bottom-right) — 9px radius | components.html#alerts |
|
|
207
|
+
| `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled primary on check | components.html#inputs |
|
|
208
|
+
| `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown with portal-shadow `0 1px 10px rgba(0,0,0,.1)` | components.html#dropdown |
|
|
209
|
+
| `.skeleton` + `.hf-spin` | Loading state primitives (shimmer + rotation keyframes) | components.html#empty |
|
|
210
|
+
|
|
211
|
+
## How the AI uses this
|
|
212
|
+
|
|
213
|
+
After you drop one of the `ai-rules/*` files at the new project's root:
|
|
214
|
+
|
|
215
|
+
1. **Claude Code / Cursor / Copilot** automatically prepend it to every chat. The agent learns:
|
|
216
|
+
- canonical palette (primary `#24AFE8`, status colors, neutrals)
|
|
217
|
+
- typography (Roboto, sizes 11/13/14/15/16/18/20/22/26/30, weights 400/500/600)
|
|
218
|
+
- spacing scale (multiples of 4)
|
|
219
|
+
- radius scale (6/8/9/12/99)
|
|
220
|
+
- shadow set (default/subtle/wrapper/card/modal/outline/hover)
|
|
221
|
+
- component library (`.hf-modal`, `.hf-alert`, `.hf-pill`, `.hf-tab`, `.hf-pagination`, `.hf-check`, `.hf-dropdown-menu`, etc.)
|
|
222
|
+
- hard rules ("never hard-code hex", "always provide hover/focus/disabled", "no mixed icon sets")
|
|
223
|
+
|
|
224
|
+
2. **Visual reference for new projects**: open `components.html` in a browser — canonical reference with all `.hf-*` patterns rendered.
|
|
225
|
+
|
|
226
|
+
3. **For chat-based AI** (ChatGPT, v0, Lovable): paste `ai-rules/system-prompt-compact.md` or `UI_DESIGN.md` §9 JSON token reference as a system prompt before asking for UI work.
|
|
227
|
+
|
|
228
|
+
## Refresh the bindings
|
|
229
|
+
|
|
230
|
+
When `tokens.figma.json` changes, regenerate the bindings:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
cd docs/portable-design
|
|
234
|
+
node generate-tokens.cjs --target=tailwind-v4 > pre-built/tailwind.css # v4 (recommended)
|
|
235
|
+
node generate-tokens.cjs --target=tailwind > pre-built/tailwind.preset.js # v3 (legacy)
|
|
236
|
+
node generate-tokens.cjs --target=css > pre-built/tokens.css
|
|
237
|
+
node generate-tokens.cjs --target=scss > pre-built/_tokens.scss
|
|
238
|
+
node generate-tokens.cjs --target=ts > pre-built/tokens.ts
|
|
239
|
+
node generate-tokens.cjs --target=shadcn > pre-built/shadcn-tokens.css
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
> `pre-built/components.css` is **not generated** — it's hand-extracted from `components.html`. If you change the `<style type="text/tailwindcss">` `@layer components` block in components.html, manually re-sync the extracted file. The `pre-built/components.css` file header has the source coordinates.
|
|
243
|
+
|
|
244
|
+
Or wire token regeneration into `package.json`:
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"scripts": {
|
|
249
|
+
"tokens:build:v4": "node docs/portable-design/generate-tokens.cjs --target=tailwind-v4 > tailwind.css",
|
|
250
|
+
"tokens:build:v3": "node docs/portable-design/generate-tokens.cjs --target=tailwind > tailwind.preset.js",
|
|
251
|
+
"prebuild": "npm run tokens:build:v3 && npm run tokens:build:v4"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Validation checklist
|
|
257
|
+
|
|
258
|
+
After porting to a new project, verify:
|
|
259
|
+
|
|
260
|
+
- [ ] `cat CLAUDE.md` (or `.cursorrules`) shows the design-system rules.
|
|
261
|
+
- [ ] `node docs/portable-design/generate-tokens.cjs --target=css` runs cleanly and produces stable output.
|
|
262
|
+
- [ ] AI agent answers "what's the primary color?" with `#24AFE8`.
|
|
263
|
+
- [ ] First button generated by AI matches `.btn-primary` (40px / 15px / 600 / `#24AFE8` / 6px radius).
|
|
264
|
+
- [ ] Status badges resolve via `--status-{domain}-{state}-color` tokens.
|
|
265
|
+
- [ ] `.hf-modal`, `.hf-alert`, `.hf-pagination` render correctly when `pre-built/components.css` is loaded.
|
|
266
|
+
|
|
267
|
+
## What's NOT portable
|
|
268
|
+
|
|
269
|
+
These files in the parent `docs/` are **specific to the HotelFriend portal** (legacy audit / drift detection) and should NOT be copied:
|
|
270
|
+
|
|
271
|
+
- `docs/migration-plan.md` — legacy SCSS cleanup queue (Yii portal only)
|
|
272
|
+
- `docs/design-tokens-audit.md` — drift report
|
|
273
|
+
- `docs/ui-elements-catalog.*` — observed components in the portal
|
|
274
|
+
- `docs/icon-audit.*` — FA→Lucide migration map
|
|
275
|
+
- `docs/component-anatomy.json` — measurements of legacy modals
|
|
276
|
+
- `scratch/` — Playwright walkers that crawl the portal
|
|
277
|
+
|
|
278
|
+
They live in the source repo for housekeeping; a new project doesn't need them.
|
|
279
|
+
|
|
280
|
+
The `portal-audit.html` inside this bundle is the only legacy artifact that ships with the portable folder — keep it for historical reference and migration tracking. New code should never copy patterns from `portal-audit.html`.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Drift detection & CI integration
|
|
285
|
+
|
|
286
|
+
### Pre-commit hook (recommended for any project that touches this bundle)
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# With husky (recommended)
|
|
290
|
+
pnpm add -D husky
|
|
291
|
+
pnpm husky init
|
|
292
|
+
cp docs/portable-design/scripts/pre-commit.sh .husky/pre-commit
|
|
293
|
+
chmod +x .husky/pre-commit
|
|
294
|
+
|
|
295
|
+
# Without husky (raw git hook)
|
|
296
|
+
cp docs/portable-design/scripts/pre-commit.sh .git/hooks/pre-commit
|
|
297
|
+
chmod +x .git/hooks/pre-commit
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The hook runs `validate-tokens.cjs` when any tokens / pre-built / docs / components.html file is staged. Fails the commit if there's drift. Silent on success.
|
|
301
|
+
|
|
302
|
+
### Stylelint config (recommended for consumer projects)
|
|
303
|
+
|
|
304
|
+
A shareable Stylelint config that forbids raw hex / named colors / literal `box-shadow` declarations:
|
|
305
|
+
|
|
306
|
+
```js
|
|
307
|
+
// .stylelintrc.cjs
|
|
308
|
+
module.exports = {
|
|
309
|
+
extends: [
|
|
310
|
+
'stylelint-config-standard',
|
|
311
|
+
'./node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs'
|
|
312
|
+
// — or, if pulled in via copy-folder:
|
|
313
|
+
// './docs/portable-design/pre-built/stylelint-design-system.cjs'
|
|
314
|
+
],
|
|
315
|
+
overrides: [
|
|
316
|
+
// Generated files may contain hex (fallbacks, primitives) — opt-out
|
|
317
|
+
{ files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
|
|
318
|
+
],
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Standalone validator (one-shot CI check)
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
cd docs/portable-design
|
|
326
|
+
node scripts/validate-tokens.cjs
|
|
327
|
+
# ✓ validate-tokens: all checks passed
|
|
328
|
+
# 168 CSS variables defined, all var() refs resolve, no bare hex.
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Use this in CI alongside lint/test. Exits non-zero on:
|
|
332
|
+
1. `var(--*)` reference that doesn't resolve to a defined CSS variable
|
|
333
|
+
2. Bare hex literal in `components.css` / `status.css` (outside comments + `var()` fallbacks)
|
|
334
|
+
3. Token reference in doc code-blocks that doesn't exist in `pre-built/*`
|
|
335
|
+
|
|
336
|
+
## NPM package (npmjs.com — public)
|
|
337
|
+
|
|
338
|
+
The bundle ships as [`@hotelfriendag/design-tokens`](https://www.npmjs.com/package/@hotelfriendag/design-tokens) on the public npmjs.com registry — no `.npmrc`, no tokens, no CI secrets:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# In any consumer project:
|
|
342
|
+
pnpm add @hotelfriendag/design-tokens
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Then import per stack:
|
|
346
|
+
|
|
347
|
+
```css
|
|
348
|
+
/* CSS / Tailwind v4 */
|
|
349
|
+
@import "@hotelfriendag/design-tokens/tailwind.css";
|
|
350
|
+
@import "@hotelfriendag/design-tokens/components.css";
|
|
351
|
+
@import "@hotelfriendag/design-tokens/status.css";
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
// TypeScript / JavaScript (default export — uses compiled tokens.js + tokens.d.ts)
|
|
356
|
+
import { tokens } from '@hotelfriendag/design-tokens';
|
|
357
|
+
|
|
358
|
+
// Or the TS source directly (Vite / Vue / Next handle this natively):
|
|
359
|
+
import { tokens } from '@hotelfriendag/design-tokens/tokens.ts';
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
```scss
|
|
363
|
+
// SCSS
|
|
364
|
+
@import '@hotelfriendag/design-tokens/_tokens';
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Publishing (maintainer)
|
|
368
|
+
|
|
369
|
+
Releases are tag-triggered via `.github/workflows/release.yml` — push a `v*` tag
|
|
370
|
+
and the workflow builds, validates, then `npm publish`-es to npmjs.com using
|
|
371
|
+
the `NPM_TOKEN` repository secret. No manual `npm publish` step required.
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# From the repo root, on main:
|
|
375
|
+
npm version patch # or minor / major → updates package.json, creates v* tag, commits
|
|
376
|
+
git push --follow-tags # workflow runs on the pushed tag and publishes
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The `prepublishOnly` script re-runs the generator + validator so the published package is always self-consistent. If you need to publish manually from a local machine (bootstrap or hotfix), `pnpm publish` works the same way after `pnpm version <bump>` (you'll need to be logged in: `npm login` first).
|
|
380
|
+
|
|
381
|
+
> Historical: versions `0.2.x` were briefly published to GitHub Packages while the registry decision was pending. `0.3.0+` is published exclusively to npmjs.com; consumers should install from there.
|
|
382
|
+
|
|
383
|
+
## Changelog
|
|
384
|
+
|
|
385
|
+
### Phase 3 — Distribution + governance scaffolding (2026-05-21)
|
|
386
|
+
|
|
387
|
+
Closes the remaining RFC-0001 governance items. The bundle is now SHIPPABLE as a versioned npm package with drift-detection enforcement.
|
|
388
|
+
|
|
389
|
+
- **Phase 3A** — `components.html` Phase 2D reconciliation closed. Embedded `@layer components` stays standalone (Tailwind v4 browser CDN doesn't support nested `@import`). The @theme block already mirrors `pre-built/tailwind.css` (canonical hf-prefixed) + legacy aliases for demo continuity.
|
|
390
|
+
- **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`.
|
|
391
|
+
- **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.
|
|
392
|
+
- **Phase 3D** — `package.json` defines the npm package skeleton:
|
|
393
|
+
- Exports map for every generated file + ai-rules + JSON sources
|
|
394
|
+
- `scripts.build` chains all generator targets (8 in total)
|
|
395
|
+
- `scripts.validate` runs the drift detector
|
|
396
|
+
- `scripts.prepublishOnly` re-builds + validates before publish
|
|
397
|
+
- `publishConfig` points at GitHub Packages registry (private/restricted access)
|
|
398
|
+
|
|
399
|
+
### Phase 2 — Component-tier + status externalization + verification (2026-05-21)
|
|
400
|
+
|
|
401
|
+
Closes the RFC-0001 §5 + RFC-0002 §Q7 refinement TODOs that Phase 1B left behind. Three sub-phases — all shipped.
|
|
402
|
+
|
|
403
|
+
**Phase 2A — Status mapping externalized (`status-map.json`)**
|
|
404
|
+
|
|
405
|
+
The domain→semantic-status mapping (which `.status-booking-confirmed` resolves to → semantic `info`, etc.) lived as a hardcoded block in `components.css`. That re-introduced the drift RFC-0001 §5b warned about. Now:
|
|
406
|
+
|
|
407
|
+
- **New file:** `status-map.json` — JSON dictionary `{domain.state}` → `semantic-status-name`
|
|
408
|
+
- **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
|
|
409
|
+
- **`pre-built/components.css`** — the `.status-*` block REMOVED; now you `@import` both files
|
|
410
|
+
- Adding a new status is JSON-only — no CSS edit
|
|
411
|
+
|
|
412
|
+
**Phase 2B — 4 component-tier tokens promoted**
|
|
413
|
+
|
|
414
|
+
Bare hex values that didn't fit a generic semantic slot but were used by exactly one component:
|
|
415
|
+
|
|
416
|
+
| Token | Value | Used by |
|
|
417
|
+
|---|---|---|
|
|
418
|
+
| `--color-hf-tab-fg-inactive` | `var(--color-hf-gray-700)` (#485B78) | `.hf-tab` default, `.hf-pill-tab` |
|
|
419
|
+
| `--color-hf-tab-count-fg` | `var(--color-hf-gray-700)` | `.hf-tab__count` (inactive tab) |
|
|
420
|
+
| `--color-hf-pagination-fg-inactive` | `var(--color-hf-gray-700)` | `.hf-pagination__item` default |
|
|
421
|
+
| `--color-hf-pagination-fg-active` | `var(--color-hf-gray-950)` (#252F4A) | `.hf-pagination__item.is-active` |
|
|
422
|
+
| `--color-hf-input-border` | `#DBDFE9` (raw — between gray-200 and gray-300) | `.hf-check`, `.hf-radio` |
|
|
423
|
+
| `--color-hf-menu-bg-hover` | `#F5F5F5` (raw — portal `$table__hover`) | `.hf-dropdown-item:hover/:focus-visible` |
|
|
424
|
+
|
|
425
|
+
Naming convention: `{component}.{slot}` under `semantic.color` in `tokens.figma.json`. Generator emits `--color-hf-{component}-{slot}` which gives Tailwind utilities `bg-hf-tab-fg-inactive` etc.
|
|
426
|
+
|
|
427
|
+
**Phase 2C — Verifier (`scripts/validate-tokens.cjs`)**
|
|
428
|
+
|
|
429
|
+
Drift-detection script. Three checks:
|
|
430
|
+
1. Every `var(--*)` in `pre-built/*.css` resolves to a defined CSS variable
|
|
431
|
+
2. No bare hex literals in `components.css` / `status.css` (outside comments + `var()` fallbacks; `#fff`/`#000` allowed)
|
|
432
|
+
3. Every token reference in doc code-blocks exists in `pre-built/*` (anti-RFC-0001 §2.2 drift)
|
|
433
|
+
|
|
434
|
+
Exits 1 on any failure — suitable for pre-commit hook or CI step.
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
node scripts/validate-tokens.cjs
|
|
438
|
+
# ✓ validate-tokens: all checks passed
|
|
439
|
+
# 168 CSS variables defined, all var() refs resolve, no bare hex.
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Phase 2D — `components.html` reconciled with Phase 1B+2 tokens**
|
|
443
|
+
|
|
444
|
+
The embedded `@theme {}` block now mirrors `pre-built/tailwind.css` (canonical hf-prefixed three-tier model) + adds legacy aliases for the demo's existing class refs. Result: BOTH `bg-primary` AND `bg-hf-accent` resolve to `#24AFE8` in the demo. The demo continues to render without a 500+ class rewrite, and the canonical `hf-` naming is documented in the @theme source.
|
|
445
|
+
|
|
446
|
+
**What was tracked as Phase 2 TODO but stays open for Phase 3 / future:**
|
|
447
|
+
|
|
448
|
+
- Fully *generate* `pre-built/components.css` from sources (currently still hand-authored, but verified by `validate-tokens.cjs`)
|
|
449
|
+
- Rewrite `components.html` demo HTML to use the new prefixed class refs (`bg-hf-accent` everywhere) and drop legacy aliases entirely
|
|
450
|
+
- Add ESLint/Stylelint plugin (RFC-0001 §4.6 forbid raw hex)
|
|
451
|
+
- Publish as `@hotelfriendag/design-tokens` npm package (RFC-0001 §4.4)
|
|
452
|
+
|
|
453
|
+
### Phase 1B — RFC-0002 semantic tier (2026-05-21)
|
|
454
|
+
|
|
455
|
+
Introduces the three-tier model: **primitive → semantic → (Phase 2) component**. Per RFC-0002. App code should now reference SEMANTIC tokens (`var(--color-hf-accent)`, `var(--color-hf-fg)`, etc.) — primitives (`hf-blue-500`, `hf-gray-300`) are an implementation detail. Themes swap the semantic layer without touching primitives.
|
|
456
|
+
|
|
457
|
+
**Breaking changes (consumers must update):**
|
|
458
|
+
|
|
459
|
+
| Phase 1A name | Phase 1B replacement |
|
|
460
|
+
|---|---|
|
|
461
|
+
| `--color-hf-primary*` | `--color-hf-accent` / `-hover` / `-subtle` / `-subtler` |
|
|
462
|
+
| `--color-hf-text-primary` | `--color-hf-fg` |
|
|
463
|
+
| `--color-hf-text-secondary` | `--color-hf-fg-muted` |
|
|
464
|
+
| `--color-hf-text-useful` | `--color-hf-fg-subtle` |
|
|
465
|
+
| `--color-hf-text-placeholder` | `--color-hf-fg-faint` |
|
|
466
|
+
| `--color-hf-neutral-light-grey` | `--color-hf-bg-page` |
|
|
467
|
+
| `--color-hf-neutral-bg-accent` | `--color-hf-bg-section` |
|
|
468
|
+
| `--color-hf-neutral-very-light` | `--color-hf-bg-muted` |
|
|
469
|
+
| `--color-hf-neutral-border` | `--color-hf-border` |
|
|
470
|
+
| `--color-hf-neutral-light-blue-gray` | `--color-hf-border-subtle` |
|
|
471
|
+
| `--color-hf-neutral-border-hover` | `--color-hf-border-strong` |
|
|
472
|
+
| `--color-hf-status-success` etc. | unchanged ✓ (already canonical) |
|
|
473
|
+
| `--color-hf-badge-{domain}-{state}-*` | **removed** — see status-map note below |
|
|
474
|
+
|
|
475
|
+
**New: SEMANTIC status colors** (replace per-domain `badge.*` tokens):
|
|
476
|
+
|
|
477
|
+
```
|
|
478
|
+
--color-hf-status-success / -bg / -strong
|
|
479
|
+
--color-hf-status-warning / -bg / -strong
|
|
480
|
+
--color-hf-status-error / -bg / -strong / -alt
|
|
481
|
+
--color-hf-status-info / -bg
|
|
482
|
+
--color-hf-status-cancel / -bg
|
|
483
|
+
--color-hf-status-coral / -bg (portal-specific)
|
|
484
|
+
--color-hf-status-violet / -bg (portal-specific)
|
|
485
|
+
--color-hf-status-olive / -bg (portal-specific)
|
|
486
|
+
--color-hf-status-orange-deep / -bg (portal-specific)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Domain→semantic mapping for CSS classes (e.g. `.status-booking-confirmed` → uses `--color-hf-status-info`) currently lives **hardcoded in `pre-built/components.css`** with `TODO Phase 2` markers. RFC-0002 §Q7 refinement requires extraction to `status-map.json` consumed by the generator — tracked as Phase 2.
|
|
490
|
+
|
|
491
|
+
**New: tier-aware generator** (`generate-tokens.cjs`):
|
|
492
|
+
- Recognises `primitive.*` and `semantic.*` top-level groups in `tokens.figma.json`
|
|
493
|
+
- 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
|
|
494
|
+
- All existing `--prefix=` / `--target=` flags unchanged
|
|
495
|
+
|
|
496
|
+
**Files affected:**
|
|
497
|
+
- `tokens.figma.json` — restructured: `primitive.color.{family}.{step}` + `semantic.color.{slot}.{variant}`
|
|
498
|
+
- `generate-tokens.cjs` — alias resolution + tier path normalisation
|
|
499
|
+
- `pre-built/*` — all regenerated; tokens.css now has primitives + semantic aliases
|
|
500
|
+
- `pre-built/components.css` — fully rewritten; binds chrome to semantic, hardcodes status-map (TODO externalize)
|
|
501
|
+
- `components.html` — **NOT touched** (still self-contained with its own pre-Phase 1B @theme). Phase 2 will reconcile.
|
|
502
|
+
|
|
503
|
+
### Phase 1A — RFC-0001 collision-safe prefixing (2026-05-21)
|
|
504
|
+
|
|
505
|
+
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`.
|
|
506
|
+
|
|
507
|
+
**Breaking change for `ui-hf` and any other consumer:** all token names now carry the `hf-` prefix INSIDE the category segment.
|
|
508
|
+
|
|
509
|
+
- `--color-primary` → `--color-hf-primary` (Tailwind utility: `bg-hf-primary`)
|
|
510
|
+
- `--text-base` → `--text-hf-base` (utility: `text-hf-base` = 14px; Tailwind's default `text-base` keeps 16px — no more silent override)
|
|
511
|
+
- `--radius-sm` → `--radius-hf-sm` (utility: `rounded-hf-sm` = 6px; default `rounded-sm` stays 2px)
|
|
512
|
+
- `--spacing-7` → `--spacing-hf-7` (utility: `p-hf-7` = 30px; default `p-7` stays 28px)
|
|
513
|
+
- `--font-sans` → `--font-hf-sans` (utility: `font-hf-sans` = Roboto)
|
|
514
|
+
- `--shadow-modal` → `--shadow-hf-modal` (utility: `shadow-hf-modal`)
|
|
515
|
+
- All `--color-badge-{domain}-{state}-*` → `--color-hf-badge-{domain}-{state}-*`
|
|
516
|
+
|
|
517
|
+
**Why the prefix goes INSIDE the category** (e.g. `--color-hf-primary`, not `--hf-color-primary`):
|
|
518
|
+
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.
|
|
519
|
+
|
|
520
|
+
**Files touched:**
|
|
521
|
+
- `tokens.figma.json` — unchanged (the prefix is applied at generation time, not in source)
|
|
522
|
+
- `generate-tokens.cjs` — new `--prefix=` argument; PREFIX defaults to `hf-`. Override with `--prefix=` (empty) for legacy unprefixed output
|
|
523
|
+
- `pre-built/*.css/scss/ts/js` — all regenerated with `--hf-` prefix in CSS targets (Tailwind v3 / SCSS / TS / shadcn unchanged for now)
|
|
524
|
+
- `pre-built/components.css` — all `var(--*)` refs updated to new prefixed names
|
|
525
|
+
- Docs — all `var(--color-primary)` etc. updated to `var(--color-hf-primary)` form
|
|
526
|
+
|
|
527
|
+
**What's NOT changed yet (Phase 1B):**
|
|
528
|
+
- `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.
|
|
529
|
+
- Tailwind v3 preset, SCSS, TS outputs stay unprefixed (legacy paths — less risk of collision in those ecosystems).
|
|
530
|
+
|
|
531
|
+
### Phase 0 — RFC-0001 quick wins (2026-05-21)
|
|
532
|
+
|
|
533
|
+
Addresses critique from `RFC-0001-cross-project-design-system.md` §2 + §8 — small fixes that don't require architectural decisions:
|
|
534
|
+
|
|
535
|
+
- **ESM compatibility** — renamed `generate-tokens.js` → `generate-tokens.cjs` so the script runs under Node in `"type":"module"` projects (Vite/Vue 3/Next).
|
|
536
|
+
- **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.
|
|
537
|
+
- **Shadow token namespace** — JSON group `boxShadow` → `shadow` so emit is `--shadow-modal` (was `--box-shadow-modal`). Matches what `components.html` and docs already reference.
|
|
538
|
+
- **`components.css` — RFC §5 conformance:**
|
|
539
|
+
- **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.
|
|
540
|
+
- **One namespace** (RFC §5c) — dropped the parallel `:root { --status-* }` declarations; consumes canonical `--color-badge-*` tokens directly.
|
|
541
|
+
- **Class names match token paths** (RFC §5d) — breaking renames:
|
|
542
|
+
- `.status-clean-*` → `.status-room-item-cleaning-*`
|
|
543
|
+
- `.status-fd-*` → `.status-front-desk-*`
|
|
544
|
+
- `.status-order-action` → `.status-order-action-required`
|
|
545
|
+
- **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.
|
|
546
|
+
- **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.
|
|
547
|
+
|
|
548
|
+
### Still open (deferred to Phase 1+, per RFC roadmap)
|
|
549
|
+
|
|
550
|
+
- Token namespace prefix (`--hf-*`) + `--target=tailwind-v4-additive` (RFC §4.2)
|
|
551
|
+
- Three-tier token architecture: primitive → semantic → component (RFC §4.1)
|
|
552
|
+
- `text-text-primary` awkward utility — fix when semantic tier introduces `--hf-color-fg` (RFC §8.3)
|
|
553
|
+
- `pre-built/components.css` is hand-mirrored from `components.html` rather than generated (RFC §5b)
|
|
554
|
+
- Two competing visual SSOTs (`components.html` vs `UI_DESIGN.md`) — Phase 2 will pick one
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
Built from the HotelFriend Design System v0.1.0 — see `UI_DESIGN.md` §9 for the JSON token reference and `components.html` for the canonical visual reference.
|