@hotelfriendag/design-tokens 0.3.3 → 0.3.4
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 +130 -463
- package/generate-tokens.cjs +8 -2
- package/package.json +1 -1
- package/pre-built/_tokens.scss +32 -32
- package/pre-built/tokens.js +32 -32
- package/pre-built/tokens.ts +32 -32
package/README.md
CHANGED
|
@@ -1,576 +1,243 @@
|
|
|
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.
|
|
71
16
|
|
|
72
|
-
|
|
17
|
+
## Wire it up (pick your stack)
|
|
73
18
|
|
|
74
|
-
|
|
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`.
|
|
20
|
+
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
- `bg-hf-accent` (= `#24AFE8` — brand) — Tailwind's `bg-blue-500` keeps its default
|
|
91
|
-
- `text-hf-base` (= 14px — body) — Tailwind's `text-base` keeps its default 16px
|
|
92
|
-
- `rounded-hf-sm` (= 6px) — Tailwind's `rounded-sm` keeps its default 2px
|
|
93
|
-
- `shadow-hf-modal` (= portal modal shadow)
|
|
94
|
-
- … etc. Your existing utility usage is **untouched**.
|
|
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.
|
|
95
33
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|---|---|---|
|
|
202
137
|
| `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
|
|
203
138
|
| `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Underline + segmented tabs | components.html#tabs |
|
|
204
|
-
| `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT
|
|
139
|
+
| `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
|
|
205
140
|
| `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
|
|
206
141
|
| `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
|
|
207
142
|
| `.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` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown
|
|
211
|
-
| `.skeleton` + `.hf-spin` | Loading
|
|
212
|
-
|
|
213
|
-
## How the AI uses this
|
|
214
|
-
|
|
215
|
-
After you drop one of the `ai-rules/*` files at the new project's root:
|
|
216
|
-
|
|
217
|
-
1. **Claude Code / Cursor / Copilot** automatically prepend it to every chat. The agent learns:
|
|
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")
|
|
225
|
-
|
|
226
|
-
2. **Visual reference for new projects**: open `components.html` in a browser — canonical reference with all `.hf-*` patterns rendered.
|
|
227
|
-
|
|
228
|
-
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.
|
|
229
|
-
|
|
230
|
-
## Refresh the bindings
|
|
231
|
-
|
|
232
|
-
When `tokens.figma.json` changes, regenerate the bindings:
|
|
233
|
-
|
|
234
|
-
```bash
|
|
235
|
-
cd docs/portable-design
|
|
236
|
-
node generate-tokens.cjs --target=tailwind-v4 > pre-built/tailwind.css # v4 (recommended)
|
|
237
|
-
node generate-tokens.cjs --target=tailwind > pre-built/tailwind.preset.js # v3 (legacy)
|
|
238
|
-
node generate-tokens.cjs --target=css > pre-built/tokens.css
|
|
239
|
-
node generate-tokens.cjs --target=scss > pre-built/_tokens.scss
|
|
240
|
-
node generate-tokens.cjs --target=ts > pre-built/tokens.ts
|
|
241
|
-
node generate-tokens.cjs --target=shadcn > pre-built/shadcn-tokens.css
|
|
242
|
-
```
|
|
143
|
+
| `.hf-toast` | Floating notification — 9px radius | components.html#alerts |
|
|
144
|
+
| `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
|
|
145
|
+
| `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown, portal shadow | components.html#dropdown |
|
|
146
|
+
| `.skeleton` + `.hf-spin` | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
|
|
243
147
|
|
|
244
|
-
|
|
148
|
+
## AI rules
|
|
245
149
|
|
|
246
|
-
|
|
150
|
+
The package ships agent rules under `ai-rules/`. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
|
|
247
151
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"tokens:build:v3": "node docs/portable-design/generate-tokens.cjs --target=tailwind > tailwind.preset.js",
|
|
253
|
-
"prebuild": "npm run tokens:build:v3 && npm run tokens:build:v4"
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
## Validation checklist
|
|
259
|
-
|
|
260
|
-
After porting to a new project, verify:
|
|
261
|
-
|
|
262
|
-
- [ ] `cat CLAUDE.md` (or `.cursorrules`) shows the design-system rules.
|
|
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.
|
|
268
|
-
|
|
269
|
-
## What's NOT portable
|
|
270
|
-
|
|
271
|
-
These files in the parent `docs/` are **specific to the HotelFriend portal** (legacy audit / drift detection) and should NOT be copied:
|
|
272
|
-
|
|
273
|
-
- `docs/migration-plan.md` — legacy SCSS cleanup queue (Yii portal only)
|
|
274
|
-
- `docs/design-tokens-audit.md` — drift report
|
|
275
|
-
- `docs/ui-elements-catalog.*` — observed components in the portal
|
|
276
|
-
- `docs/icon-audit.*` — FA→Lucide migration map
|
|
277
|
-
- `docs/component-anatomy.json` — measurements of legacy modals
|
|
278
|
-
- `scratch/` — Playwright walkers that crawl the portal
|
|
152
|
+
- `ai-rules/CLAUDE.md` → consumer root `CLAUDE.md` (Claude Code, autodetected)
|
|
153
|
+
- `ai-rules/cursorrules.template` → `.cursorrules`
|
|
154
|
+
- `ai-rules/github-copilot-instructions.md` → `.github/copilot-instructions.md`
|
|
155
|
+
- `ai-rules/system-prompt-compact.md` → compact prompt for ChatGPT / v0 / Lovable
|
|
279
156
|
|
|
280
|
-
|
|
157
|
+
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").
|
|
281
158
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## Drift detection & CI integration
|
|
287
|
-
|
|
288
|
-
### Pre-commit hook (recommended for any project that touches this bundle)
|
|
159
|
+
## Keeping a consumer up to date
|
|
289
160
|
|
|
290
161
|
```bash
|
|
291
|
-
#
|
|
292
|
-
pnpm add -D husky
|
|
293
|
-
pnpm husky init
|
|
294
|
-
cp docs/portable-design/scripts/pre-commit.sh .husky/pre-commit
|
|
295
|
-
chmod +x .husky/pre-commit
|
|
296
|
-
|
|
297
|
-
# Without husky (raw git hook)
|
|
298
|
-
cp docs/portable-design/scripts/pre-commit.sh .git/hooks/pre-commit
|
|
299
|
-
chmod +x .git/hooks/pre-commit
|
|
162
|
+
pnpm update @hotelfriendag/design-tokens # pull the latest published version
|
|
300
163
|
```
|
|
301
164
|
|
|
302
|
-
|
|
165
|
+
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>`.
|
|
303
166
|
|
|
304
|
-
|
|
167
|
+
## Drift detection & CI (for consumers)
|
|
305
168
|
|
|
306
|
-
|
|
169
|
+
**Stylelint config** — forbids raw hex / named colors / literal `box-shadow`:
|
|
307
170
|
|
|
308
171
|
```js
|
|
309
172
|
// .stylelintrc.cjs
|
|
310
173
|
module.exports = {
|
|
311
174
|
extends: [
|
|
312
175
|
'stylelint-config-standard',
|
|
313
|
-
'./node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs'
|
|
314
|
-
// — or, if pulled in via copy-folder:
|
|
315
|
-
// './docs/portable-design/pre-built/stylelint-design-system.cjs'
|
|
176
|
+
'./node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs',
|
|
316
177
|
],
|
|
317
178
|
overrides: [
|
|
318
|
-
// Generated files may contain hex (fallbacks, primitives) — opt-out
|
|
319
179
|
{ files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
|
|
320
180
|
],
|
|
321
181
|
};
|
|
322
182
|
```
|
|
323
183
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
```bash
|
|
327
|
-
cd docs/portable-design
|
|
328
|
-
node scripts/validate-tokens.cjs
|
|
329
|
-
# ✓ validate-tokens: all checks passed
|
|
330
|
-
# 168 CSS variables defined, all var() refs resolve, no bare hex.
|
|
331
|
-
```
|
|
184
|
+
**Pre-commit hook** — `scripts/pre-commit.sh` runs the token validator when bundle files are staged (husky-compatible).
|
|
332
185
|
|
|
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)
|
|
339
|
-
|
|
340
|
-
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:
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
# In any consumer project:
|
|
344
|
-
pnpm add @hotelfriendag/design-tokens
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
Then import per stack:
|
|
186
|
+
---
|
|
348
187
|
|
|
349
|
-
|
|
350
|
-
/* CSS / Tailwind v4 */
|
|
351
|
-
@import "@hotelfriendag/design-tokens/tailwind.css";
|
|
352
|
-
@import "@hotelfriendag/design-tokens/components.css";
|
|
353
|
-
@import "@hotelfriendag/design-tokens/status.css";
|
|
354
|
-
```
|
|
188
|
+
## Maintainer reference
|
|
355
189
|
|
|
356
|
-
|
|
357
|
-
// TypeScript / JavaScript (default export — uses compiled tokens.js + tokens.d.ts)
|
|
358
|
-
import { tokens } from '@hotelfriendag/design-tokens';
|
|
190
|
+
### Repo layout & precedence
|
|
359
191
|
|
|
360
|
-
// Or the TS source directly (Vite / Vue / Next handle this natively):
|
|
361
|
-
import { tokens } from '@hotelfriendag/design-tokens/tokens.ts';
|
|
362
192
|
```
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
193
|
+
design-system/
|
|
194
|
+
├── components.html PRIMARY · canonical visual reference (open in browser)
|
|
195
|
+
├── UI_DESIGN.md NARRATIVE · rationale, anatomy, decisions
|
|
196
|
+
├── tokens.figma.json TOKENS · Tokens Studio export — single source of truth
|
|
197
|
+
├── status-map.json domain→semantic status mapping (feeds status.css)
|
|
198
|
+
├── src/components.css SOURCE for .hf-* rules (edit here)
|
|
199
|
+
├── generate-tokens.cjs Node generator (no deps)
|
|
200
|
+
├── pre-built/ GENERATED — never hand-edit (CI drift gate)
|
|
201
|
+
└── ai-rules/ agent rule files
|
|
367
202
|
```
|
|
368
203
|
|
|
369
|
-
|
|
204
|
+
Precedence when files disagree:
|
|
205
|
+
1. **`components.html`** — visual decisions (colors, sizes, anatomy)
|
|
206
|
+
2. **`tokens.figma.json`** — token values (regenerate `pre-built/*` after edits)
|
|
207
|
+
3. **`UI_DESIGN.md`** — WHY decisions were made
|
|
208
|
+
4. **`pre-built/*`** — generated, never hand-edit
|
|
209
|
+
5. **`portal-audit.html`** — archival only (legacy portal snapshot)
|
|
370
210
|
|
|
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.
|
|
211
|
+
### Regenerating outputs
|
|
375
212
|
|
|
376
|
-
|
|
377
|
-
source repositories, and this repo stays private (only the built artifact is
|
|
378
|
-
public — see RFC-0001 for the rationale). The trade is a missing attestation
|
|
379
|
-
badge on the npm page in exchange for keeping the source code private.
|
|
213
|
+
`tokens.figma.json` is the single source of truth; `src/components.css` is the source for the `.hf-*` layer. After editing either:
|
|
380
214
|
|
|
381
215
|
```bash
|
|
382
|
-
#
|
|
383
|
-
npm
|
|
384
|
-
git push --follow-tags # workflow runs on the pushed tag and publishes
|
|
216
|
+
npm run build # regenerates all pre-built/* (tokens, tailwind v3+v4, scss, ts/js/dts, shadcn, status.css, components.css)
|
|
217
|
+
npm run validate # drift detector: every var() resolves, no bare hex
|
|
385
218
|
```
|
|
386
219
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
The Trusted Publisher binding on npmjs (Package → Settings → Trusted publishing) is configured for:
|
|
220
|
+
CI runs `git diff --exit-code -- pre-built/` so outputs must be reproducible from source. Edit `src/`, never `pre-built/`.
|
|
390
221
|
|
|
391
|
-
|
|
392
|
-
|---|---|
|
|
393
|
-
| Organization | `HotelFriendAG` |
|
|
394
|
-
| Repository | `design-system` |
|
|
395
|
-
| Workflow filename | `release.yml` |
|
|
222
|
+
### Publishing
|
|
396
223
|
|
|
397
|
-
|
|
224
|
+
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.
|
|
398
225
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
## Changelog
|
|
402
|
-
|
|
403
|
-
### Phase 3 — Distribution + governance scaffolding (2026-05-21)
|
|
404
|
-
|
|
405
|
-
Closes the remaining RFC-0001 governance items. The bundle is now SHIPPABLE as a versioned npm package with drift-detection enforcement.
|
|
406
|
-
|
|
407
|
-
- **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.
|
|
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)
|
|
416
|
-
|
|
417
|
-
### Phase 2 — Component-tier + status externalization + verification (2026-05-21)
|
|
418
|
-
|
|
419
|
-
Closes the RFC-0001 §5 + RFC-0002 §Q7 refinement TODOs that Phase 1B left behind. Three sub-phases — all shipped.
|
|
420
|
-
|
|
421
|
-
**Phase 2A — Status mapping externalized (`status-map.json`)**
|
|
422
|
-
|
|
423
|
-
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:
|
|
424
|
-
|
|
425
|
-
- **New file:** `status-map.json` — JSON dictionary `{domain.state}` → `semantic-status-name`
|
|
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
|
|
429
|
-
|
|
430
|
-
**Phase 2B — 4 component-tier tokens promoted**
|
|
431
|
-
|
|
432
|
-
Bare hex values that didn't fit a generic semantic slot but were used by exactly one component:
|
|
433
|
-
|
|
434
|
-
| Token | Value | Used by |
|
|
435
|
-
|---|---|---|
|
|
436
|
-
| `--color-hf-tab-fg-inactive` | `var(--color-hf-gray-700)` (#485B78) | `.hf-tab` default, `.hf-pill-tab` |
|
|
437
|
-
| `--color-hf-tab-count-fg` | `var(--color-hf-gray-700)` | `.hf-tab__count` (inactive tab) |
|
|
438
|
-
| `--color-hf-pagination-fg-inactive` | `var(--color-hf-gray-700)` | `.hf-pagination__item` default |
|
|
439
|
-
| `--color-hf-pagination-fg-active` | `var(--color-hf-gray-950)` (#252F4A) | `.hf-pagination__item.is-active` |
|
|
440
|
-
| `--color-hf-input-border` | `#DBDFE9` (raw — between gray-200 and gray-300) | `.hf-check`, `.hf-radio` |
|
|
441
|
-
| `--color-hf-menu-bg-hover` | `#F5F5F5` (raw — portal `$table__hover`) | `.hf-dropdown-item:hover/:focus-visible` |
|
|
442
|
-
|
|
443
|
-
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.
|
|
444
|
-
|
|
445
|
-
**Phase 2C — Verifier (`scripts/validate-tokens.cjs`)**
|
|
446
|
-
|
|
447
|
-
Drift-detection script. Three checks:
|
|
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)
|
|
451
|
-
|
|
452
|
-
Exits 1 on any failure — suitable for pre-commit hook or CI step.
|
|
226
|
+
`--provenance` is intentionally NOT used: npmjs rejects sigstore provenance from private source repositories, and this repo stays private (only the built artifact is public).
|
|
453
227
|
|
|
454
228
|
```bash
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
# 168 CSS variables defined, all var() refs resolve, no bare hex.
|
|
229
|
+
npm version patch # or minor / major → bumps package.json, creates v* tag, commits
|
|
230
|
+
git push --follow-tags # workflow runs on the tag and publishes
|
|
458
231
|
```
|
|
459
232
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
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.
|
|
463
|
-
|
|
464
|
-
**What was tracked as Phase 2 TODO but stays open for Phase 3 / future:**
|
|
465
|
-
|
|
466
|
-
- ✅ ~~Fully *generate* `pre-built/components.css` from sources~~ — done 2026-05-27 (ROADMAP #4): source moved to `src/components.css`, `--target=components-css` added to the generator, drift gate covers regeneration.
|
|
467
|
-
- Rewrite `components.html` demo HTML to use the new prefixed class refs (`bg-hf-accent` everywhere) and drop legacy aliases entirely
|
|
468
|
-
- Add ESLint/Stylelint plugin (RFC-0001 §4.6 forbid raw hex)
|
|
469
|
-
- Publish as `@hotelfriendag/design-tokens` npm package (RFC-0001 §4.4)
|
|
470
|
-
|
|
471
|
-
### Phase 1B — RFC-0002 semantic tier (2026-05-21)
|
|
472
|
-
|
|
473
|
-
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.
|
|
474
|
-
|
|
475
|
-
**Breaking changes (consumers must update):**
|
|
476
|
-
|
|
477
|
-
| Phase 1A name | Phase 1B replacement |
|
|
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):
|
|
494
|
-
|
|
495
|
-
```
|
|
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
|
-
```
|
|
233
|
+
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.
|
|
506
234
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
**New: tier-aware generator** (`generate-tokens.cjs`):
|
|
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
|
|
235
|
+
`prepublishOnly` re-runs the generator + validator so the published package is always self-consistent.
|
|
573
236
|
|
|
574
237
|
---
|
|
575
238
|
|
|
576
|
-
|
|
239
|
+
- **Changelog / phase history:** [`CHANGELOG.md`](CHANGELOG.md)
|
|
240
|
+
- **Remaining work:** [`ROADMAP.md`](ROADMAP.md)
|
|
241
|
+
- **First-consumer playbook:** [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md)
|
|
242
|
+
- **Architecture rationale:** [`RFC-0001`](RFC-0001-cross-project-design-system.md) · [`RFC-0002`](RFC-0002-semantic-tier-naming.md)
|
|
243
|
+
- **Visual reference:** `components.html` · **Token narrative:** `UI_DESIGN.md`
|
package/generate-tokens.cjs
CHANGED
|
@@ -236,7 +236,11 @@ function emitScss() {
|
|
|
236
236
|
lines.push('');
|
|
237
237
|
for (const { path: p, token } of all) {
|
|
238
238
|
if (typeof token.value !== 'string') continue;
|
|
239
|
-
|
|
239
|
+
// Resolve Tokens Studio refs ({primitive.color.blue.500}) to concrete values.
|
|
240
|
+
// SCSS variables are compile-time — there's no runtime var() chain — so semantic
|
|
241
|
+
// tokens must inline the primitive value, not emit the raw reference string.
|
|
242
|
+
const value = resolveValue(token, tokenIndex, 'inline');
|
|
243
|
+
lines.push(`$${camel(p)}: ${value};${token.description ? ' // ' + token.description : ''}`);
|
|
240
244
|
}
|
|
241
245
|
for (const { path: p, token } of all) {
|
|
242
246
|
if (token.type !== 'boxShadow') continue;
|
|
@@ -259,7 +263,9 @@ function nestTokens() {
|
|
|
259
263
|
cur[p[i]] = cur[p[i]] || {};
|
|
260
264
|
cur = cur[p[i]];
|
|
261
265
|
}
|
|
262
|
-
|
|
266
|
+
// Resolve Tokens Studio refs to concrete values — TS/JS consumers want a usable
|
|
267
|
+
// value (#24AFE8), not the raw reference string {primitive.color.blue.500}.
|
|
268
|
+
let v = resolveValue(token, tokenIndex, 'inline');
|
|
263
269
|
if (token.type === 'boxShadow' && typeof v === 'object' && !Array.isArray(v)) {
|
|
264
270
|
v = `${v.x}px ${v.y}px ${v.blur}px ${v.spread}px ${v.color}`;
|
|
265
271
|
}
|
package/package.json
CHANGED
package/pre-built/_tokens.scss
CHANGED
|
@@ -31,49 +31,49 @@ $colorSlate500: #7F8FA4; // Canceled, deleted, OOS — cloud-gray
|
|
|
31
31
|
$colorOlive700: #5B7A02; // Check-in (rare dark olive)
|
|
32
32
|
$colorWhite: #FFFFFF;
|
|
33
33
|
$colorBlack: #000000;
|
|
34
|
-
$colorAccent:
|
|
35
|
-
$colorAccentHover:
|
|
36
|
-
$colorAccentSubtle:
|
|
37
|
-
$colorAccentSubtler:
|
|
38
|
-
$colorOn-accent:
|
|
39
|
-
$colorBgPage:
|
|
40
|
-
$colorBgSection:
|
|
41
|
-
$colorBgMuted:
|
|
42
|
-
$colorBgSurface:
|
|
43
|
-
$colorFg:
|
|
44
|
-
$colorFgMuted:
|
|
45
|
-
$colorFgSubtle:
|
|
46
|
-
$colorFgFaint:
|
|
47
|
-
$colorBorder:
|
|
48
|
-
$colorBorderSubtle:
|
|
49
|
-
$colorBorderStrong:
|
|
50
|
-
$colorTabFg-inactive:
|
|
51
|
-
$colorTabCount-fg:
|
|
52
|
-
$colorPaginationFg-inactive:
|
|
53
|
-
$colorPaginationFg-active:
|
|
34
|
+
$colorAccent: #24AFE8; // Brand primary — links, primary buttons, focus
|
|
35
|
+
$colorAccentHover: #149AD1; // Primary button hover
|
|
36
|
+
$colorAccentSubtle: #E9F6FC; // Accent tint — selected nav, active row
|
|
37
|
+
$colorAccentSubtler: #EFF6FF; // Even softer accent — reset-filters bg
|
|
38
|
+
$colorOn-accent: #FFFFFF; // Text/icons ON accent bg (Radix-style foreground pair)
|
|
39
|
+
$colorBgPage: #FBFBFC; // Page background (outermost)
|
|
40
|
+
$colorBgSection: #F6F7FB; // Section bg, table header
|
|
41
|
+
$colorBgMuted: #F1F3F6; // Input bg / hover bg / muted surface
|
|
42
|
+
$colorBgSurface: #FFFFFF; // Card / modal / popover surface
|
|
43
|
+
$colorFg: #2B2B2B; // Default text / heading color
|
|
44
|
+
$colorFgMuted: #4B5675; // Secondary text, descriptions
|
|
45
|
+
$colorFgSubtle: #7E8EA6; // Helper / hint text
|
|
46
|
+
$colorFgFaint: #AEBCCF; // Placeholder + disabled input text
|
|
47
|
+
$colorBorder: #D1D6DD; // Default 1px border (buttons, inputs, chips)
|
|
48
|
+
$colorBorderSubtle: #E4E8EF; // Faint dividers, light borders
|
|
49
|
+
$colorBorderStrong: #AEBCCF; // Hover/focus border
|
|
50
|
+
$colorTabFg-inactive: #485B78; // Tab / nav-item text when NOT selected (.hf-tab, .hf-pill-tab default)
|
|
51
|
+
$colorTabCount-fg: #485B78; // Count-badge text inside an inactive tab
|
|
52
|
+
$colorPaginationFg-inactive: #485B78; // Pagination item text when not active
|
|
53
|
+
$colorPaginationFg-active: #252F4A; // Pagination item text on the subtle-gray ACTIVE bg
|
|
54
54
|
$colorInputBorder: #DBDFE9; // Input + checkbox/radio border — between gray-200 (#E4E8EF) and gray-300 (#D1D6DD); portal-exact, doesn't fit a primitive step
|
|
55
55
|
$colorMenuBg-hover: #F5F5F5; // Dropdown item hover bg — slightly warmer than bg-muted; portal $table__hover
|
|
56
|
-
$colorStatusSuccess:
|
|
56
|
+
$colorStatusSuccess: #59B59D; // Success, due-in, clean
|
|
57
57
|
$colorStatusSuccess-bg: rgba(89,181,157,0.15); // Success @15% — pill bg
|
|
58
|
-
$colorStatusWarning:
|
|
58
|
+
$colorStatusWarning: #FFBD5A; // Warning, waiting, new
|
|
59
59
|
$colorStatusWarning-bg: rgba(255,189,90,0.15);
|
|
60
|
-
$colorStatusWarning-strong:
|
|
60
|
+
$colorStatusWarning-strong: #FFC900; // Frontdesk out-of-order — more saturated yellow
|
|
61
61
|
$colorStatusWarning-strong-bg: rgba(255,201,0,0.15);
|
|
62
|
-
$colorStatusError:
|
|
62
|
+
$colorStatusError: #EA6565; // Error, no-show, dirty, action-required
|
|
63
63
|
$colorStatusError-bg: rgba(234,101,101,0.15);
|
|
64
|
-
$colorStatusError-strong:
|
|
65
|
-
$colorStatusError-alt:
|
|
66
|
-
$colorStatusInfo:
|
|
64
|
+
$colorStatusError-strong: #D9534F; // btn-danger
|
|
65
|
+
$colorStatusError-alt: #FB5A56; // btn-cancel-red, deleted variant
|
|
66
|
+
$colorStatusInfo: #24AFE8; // Info = accent
|
|
67
67
|
$colorStatusInfo-bg: rgba(36,175,232,0.15);
|
|
68
|
-
$colorStatusCancel:
|
|
68
|
+
$colorStatusCancel: #7F8FA4; // Cancellations, deleted, out-of-service
|
|
69
69
|
$colorStatusCancel-bg: rgba(127,143,164,0.15);
|
|
70
|
-
$colorStatusCoral:
|
|
70
|
+
$colorStatusCoral: #F87921; // Check-out, in-progress
|
|
71
71
|
$colorStatusCoral-bg: rgba(248,121,33,0.15);
|
|
72
|
-
$colorStatusViolet:
|
|
72
|
+
$colorStatusViolet: #5761D8; // Offer, inspected
|
|
73
73
|
$colorStatusViolet-bg: rgba(87,97,216,0.15);
|
|
74
|
-
$colorStatusOlive:
|
|
74
|
+
$colorStatusOlive: #5B7A02; // Check-in (rare deep olive)
|
|
75
75
|
$colorStatusOlive-bg: rgba(91,122,2,0.15);
|
|
76
|
-
$colorStatusOrange-deep:
|
|
76
|
+
$colorStatusOrange-deep: #A55505; // Due-out brown
|
|
77
77
|
$colorStatusOrange-deep-bg: rgba(165,85,5,0.15);
|
|
78
78
|
$fontFamilyPrimary: Roboto;
|
|
79
79
|
$fontWeightRegular: 400;
|
package/pre-built/tokens.js
CHANGED
|
@@ -55,36 +55,36 @@ const tokens = {
|
|
|
55
55
|
"white": "#FFFFFF",
|
|
56
56
|
"black": "#000000",
|
|
57
57
|
"accent": {
|
|
58
|
-
"default": "
|
|
59
|
-
"hover": "
|
|
60
|
-
"subtle": "
|
|
61
|
-
"subtler": "
|
|
58
|
+
"default": "#24AFE8",
|
|
59
|
+
"hover": "#149AD1",
|
|
60
|
+
"subtle": "#E9F6FC",
|
|
61
|
+
"subtler": "#EFF6FF"
|
|
62
62
|
},
|
|
63
|
-
"on-accent": "
|
|
63
|
+
"on-accent": "#FFFFFF",
|
|
64
64
|
"bg": {
|
|
65
|
-
"page": "
|
|
66
|
-
"section": "
|
|
67
|
-
"muted": "
|
|
68
|
-
"surface": "
|
|
65
|
+
"page": "#FBFBFC",
|
|
66
|
+
"section": "#F6F7FB",
|
|
67
|
+
"muted": "#F1F3F6",
|
|
68
|
+
"surface": "#FFFFFF"
|
|
69
69
|
},
|
|
70
70
|
"fg": {
|
|
71
|
-
"default": "
|
|
72
|
-
"muted": "
|
|
73
|
-
"subtle": "
|
|
74
|
-
"faint": "
|
|
71
|
+
"default": "#2B2B2B",
|
|
72
|
+
"muted": "#4B5675",
|
|
73
|
+
"subtle": "#7E8EA6",
|
|
74
|
+
"faint": "#AEBCCF"
|
|
75
75
|
},
|
|
76
76
|
"border": {
|
|
77
|
-
"default": "
|
|
78
|
-
"subtle": "
|
|
79
|
-
"strong": "
|
|
77
|
+
"default": "#D1D6DD",
|
|
78
|
+
"subtle": "#E4E8EF",
|
|
79
|
+
"strong": "#AEBCCF"
|
|
80
80
|
},
|
|
81
81
|
"tab": {
|
|
82
|
-
"fg-inactive": "
|
|
83
|
-
"count-fg": "
|
|
82
|
+
"fg-inactive": "#485B78",
|
|
83
|
+
"count-fg": "#485B78"
|
|
84
84
|
},
|
|
85
85
|
"pagination": {
|
|
86
|
-
"fg-inactive": "
|
|
87
|
-
"fg-active": "
|
|
86
|
+
"fg-inactive": "#485B78",
|
|
87
|
+
"fg-active": "#252F4A"
|
|
88
88
|
},
|
|
89
89
|
"input": {
|
|
90
90
|
"border": "#DBDFE9"
|
|
@@ -93,27 +93,27 @@ const tokens = {
|
|
|
93
93
|
"bg-hover": "#F5F5F5"
|
|
94
94
|
},
|
|
95
95
|
"status": {
|
|
96
|
-
"success": "
|
|
96
|
+
"success": "#59B59D",
|
|
97
97
|
"success-bg": "rgba(89,181,157,0.15)",
|
|
98
|
-
"warning": "
|
|
98
|
+
"warning": "#FFBD5A",
|
|
99
99
|
"warning-bg": "rgba(255,189,90,0.15)",
|
|
100
|
-
"warning-strong": "
|
|
100
|
+
"warning-strong": "#FFC900",
|
|
101
101
|
"warning-strong-bg": "rgba(255,201,0,0.15)",
|
|
102
|
-
"error": "
|
|
102
|
+
"error": "#EA6565",
|
|
103
103
|
"error-bg": "rgba(234,101,101,0.15)",
|
|
104
|
-
"error-strong": "
|
|
105
|
-
"error-alt": "
|
|
106
|
-
"info": "
|
|
104
|
+
"error-strong": "#D9534F",
|
|
105
|
+
"error-alt": "#FB5A56",
|
|
106
|
+
"info": "#24AFE8",
|
|
107
107
|
"info-bg": "rgba(36,175,232,0.15)",
|
|
108
|
-
"cancel": "
|
|
108
|
+
"cancel": "#7F8FA4",
|
|
109
109
|
"cancel-bg": "rgba(127,143,164,0.15)",
|
|
110
|
-
"coral": "
|
|
110
|
+
"coral": "#F87921",
|
|
111
111
|
"coral-bg": "rgba(248,121,33,0.15)",
|
|
112
|
-
"violet": "
|
|
112
|
+
"violet": "#5761D8",
|
|
113
113
|
"violet-bg": "rgba(87,97,216,0.15)",
|
|
114
|
-
"olive": "
|
|
114
|
+
"olive": "#5B7A02",
|
|
115
115
|
"olive-bg": "rgba(91,122,2,0.15)",
|
|
116
|
-
"orange-deep": "
|
|
116
|
+
"orange-deep": "#A55505",
|
|
117
117
|
"orange-deep-bg": "rgba(165,85,5,0.15)"
|
|
118
118
|
}
|
|
119
119
|
},
|
package/pre-built/tokens.ts
CHANGED
|
@@ -52,36 +52,36 @@ export const tokens = {
|
|
|
52
52
|
"white": "#FFFFFF",
|
|
53
53
|
"black": "#000000",
|
|
54
54
|
"accent": {
|
|
55
|
-
"default": "
|
|
56
|
-
"hover": "
|
|
57
|
-
"subtle": "
|
|
58
|
-
"subtler": "
|
|
55
|
+
"default": "#24AFE8",
|
|
56
|
+
"hover": "#149AD1",
|
|
57
|
+
"subtle": "#E9F6FC",
|
|
58
|
+
"subtler": "#EFF6FF"
|
|
59
59
|
},
|
|
60
|
-
"on-accent": "
|
|
60
|
+
"on-accent": "#FFFFFF",
|
|
61
61
|
"bg": {
|
|
62
|
-
"page": "
|
|
63
|
-
"section": "
|
|
64
|
-
"muted": "
|
|
65
|
-
"surface": "
|
|
62
|
+
"page": "#FBFBFC",
|
|
63
|
+
"section": "#F6F7FB",
|
|
64
|
+
"muted": "#F1F3F6",
|
|
65
|
+
"surface": "#FFFFFF"
|
|
66
66
|
},
|
|
67
67
|
"fg": {
|
|
68
|
-
"default": "
|
|
69
|
-
"muted": "
|
|
70
|
-
"subtle": "
|
|
71
|
-
"faint": "
|
|
68
|
+
"default": "#2B2B2B",
|
|
69
|
+
"muted": "#4B5675",
|
|
70
|
+
"subtle": "#7E8EA6",
|
|
71
|
+
"faint": "#AEBCCF"
|
|
72
72
|
},
|
|
73
73
|
"border": {
|
|
74
|
-
"default": "
|
|
75
|
-
"subtle": "
|
|
76
|
-
"strong": "
|
|
74
|
+
"default": "#D1D6DD",
|
|
75
|
+
"subtle": "#E4E8EF",
|
|
76
|
+
"strong": "#AEBCCF"
|
|
77
77
|
},
|
|
78
78
|
"tab": {
|
|
79
|
-
"fg-inactive": "
|
|
80
|
-
"count-fg": "
|
|
79
|
+
"fg-inactive": "#485B78",
|
|
80
|
+
"count-fg": "#485B78"
|
|
81
81
|
},
|
|
82
82
|
"pagination": {
|
|
83
|
-
"fg-inactive": "
|
|
84
|
-
"fg-active": "
|
|
83
|
+
"fg-inactive": "#485B78",
|
|
84
|
+
"fg-active": "#252F4A"
|
|
85
85
|
},
|
|
86
86
|
"input": {
|
|
87
87
|
"border": "#DBDFE9"
|
|
@@ -90,27 +90,27 @@ export const tokens = {
|
|
|
90
90
|
"bg-hover": "#F5F5F5"
|
|
91
91
|
},
|
|
92
92
|
"status": {
|
|
93
|
-
"success": "
|
|
93
|
+
"success": "#59B59D",
|
|
94
94
|
"success-bg": "rgba(89,181,157,0.15)",
|
|
95
|
-
"warning": "
|
|
95
|
+
"warning": "#FFBD5A",
|
|
96
96
|
"warning-bg": "rgba(255,189,90,0.15)",
|
|
97
|
-
"warning-strong": "
|
|
97
|
+
"warning-strong": "#FFC900",
|
|
98
98
|
"warning-strong-bg": "rgba(255,201,0,0.15)",
|
|
99
|
-
"error": "
|
|
99
|
+
"error": "#EA6565",
|
|
100
100
|
"error-bg": "rgba(234,101,101,0.15)",
|
|
101
|
-
"error-strong": "
|
|
102
|
-
"error-alt": "
|
|
103
|
-
"info": "
|
|
101
|
+
"error-strong": "#D9534F",
|
|
102
|
+
"error-alt": "#FB5A56",
|
|
103
|
+
"info": "#24AFE8",
|
|
104
104
|
"info-bg": "rgba(36,175,232,0.15)",
|
|
105
|
-
"cancel": "
|
|
105
|
+
"cancel": "#7F8FA4",
|
|
106
106
|
"cancel-bg": "rgba(127,143,164,0.15)",
|
|
107
|
-
"coral": "
|
|
107
|
+
"coral": "#F87921",
|
|
108
108
|
"coral-bg": "rgba(248,121,33,0.15)",
|
|
109
|
-
"violet": "
|
|
109
|
+
"violet": "#5761D8",
|
|
110
110
|
"violet-bg": "rgba(87,97,216,0.15)",
|
|
111
|
-
"olive": "
|
|
111
|
+
"olive": "#5B7A02",
|
|
112
112
|
"olive-bg": "rgba(91,122,2,0.15)",
|
|
113
|
-
"orange-deep": "
|
|
113
|
+
"orange-deep": "#A55505",
|
|
114
114
|
"orange-deep-bg": "rgba(165,85,5,0.15)"
|
|
115
115
|
}
|
|
116
116
|
},
|