@latty-ds/tokens 0.2.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 +225 -0
- package/dist/configure.d.ts +54 -0
- package/dist/configure.js +371 -0
- package/dist/constants/colors.constants.d.ts +43 -0
- package/dist/constants/index.d.ts +8 -0
- package/dist/constants/spacing.constants.d.ts +15 -0
- package/dist/constants/tokens.constants.d.ts +2 -0
- package/dist/elevation/elevation.d.ts +29 -0
- package/dist/elevation/index.d.ts +8 -0
- package/dist/index.js +1 -0
- package/dist/semantic.css +294 -0
- package/dist/tokens.css +192 -0
- package/dist/tokens.js +1 -0
- package/dist/tokens.json +377 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/palette.types.d.ts +84 -0
- package/dist/types/public-types.d.ts +68 -0
- package/dist/types/spacing.types.d.ts +33 -0
- package/dist/types/utils.d.ts +6 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Latty Contributors
|
|
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,225 @@
|
|
|
1
|
+
# @latty-ds/tokens
|
|
2
|
+
|
|
3
|
+
Design token package for the Latty Design System. Generates CSS custom properties, a JSON token object, and a browser-runtime API from a single configuration file.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
The build pipeline takes `tokens.config.json` as input and runs in three steps:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
tokens.config.json
|
|
13
|
+
↓
|
|
14
|
+
tsup (compile build script)
|
|
15
|
+
↓
|
|
16
|
+
node dist-scripts/build-tokens.js
|
|
17
|
+
↓
|
|
18
|
+
dist/tokens.css ← primitive CSS custom properties
|
|
19
|
+
dist/semantic.css ← semantic CSS custom properties (var() references)
|
|
20
|
+
dist/tokens.json ← full token object (primitives + semantic map)
|
|
21
|
+
dist/tokens.js ← ES module export of token object
|
|
22
|
+
↓
|
|
23
|
+
tsc (generate public type definitions)
|
|
24
|
+
↓
|
|
25
|
+
dist/public-types.d.ts
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
In addition, `tsup` also bundles `src/configure/` into `dist/configure.js` — a browser-safe module that runs the same generation at runtime, so consumers can customise the palette without a build step.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Configuring your palette
|
|
33
|
+
|
|
34
|
+
Edit `tokens.config.json` to set the base hex for each semantic color. The build generates a full 10-step OKLCH scale (50–900, plus a desaturated muted variant) from each value.
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"color": {
|
|
39
|
+
"primary": "#05b8e1",
|
|
40
|
+
"secondary": "#e26d05",
|
|
41
|
+
"success": "#22c55e",
|
|
42
|
+
"warning": "#eeb308",
|
|
43
|
+
"error": "#ef4444",
|
|
44
|
+
"info": "#0ea5e9"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then rebuild:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm --filter @latty-ds/tokens build
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Consuming tokens
|
|
58
|
+
|
|
59
|
+
### Option A — Static CSS import
|
|
60
|
+
|
|
61
|
+
Import once in your app's global stylesheet. Requires a build step.
|
|
62
|
+
|
|
63
|
+
```css
|
|
64
|
+
/* Primitive tokens (color scales, spacing, elevation, …) */
|
|
65
|
+
@import '@latty-ds/tokens/tokens.css';
|
|
66
|
+
|
|
67
|
+
/* Semantic tokens (intent-based, reference primitives via var()) */
|
|
68
|
+
@import '@latty-ds/tokens/semantic.css';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Option B — Runtime `configure()` (no build step for the consumer)
|
|
72
|
+
|
|
73
|
+
Call `configure()` once at your app's entry point. It generates the full token set from the provided config and injects it into `document.head`. Useful when the palette must be determined at runtime (e.g. white-labelling, per-tenant theming).
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { configure } from '@latty-ds/tokens/configure';
|
|
77
|
+
|
|
78
|
+
configure({
|
|
79
|
+
colors: {
|
|
80
|
+
primary: '#6366f1',
|
|
81
|
+
secondary: '#f59e0b'
|
|
82
|
+
},
|
|
83
|
+
font: { family: 'Inter, sans-serif' },
|
|
84
|
+
border: { radius: '0.375rem' }
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
All fields are optional — omitted values fall back to the defaults in `tokens.config.json`. Can be called again to hot-swap the theme; the existing `<style>` element is replaced.
|
|
89
|
+
|
|
90
|
+
### Option C — SSR (`createStyleSheet`)
|
|
91
|
+
|
|
92
|
+
For server-side rendering, use `createStyleSheet()` to get the CSS string and inject it yourself.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { createStyleSheet } from '@latty-ds/tokens/configure';
|
|
96
|
+
|
|
97
|
+
const css = createStyleSheet({ colors: { primary: '#6366f1' } });
|
|
98
|
+
// inject `css` into a <style> tag in your server-rendered HTML
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Option D — JS token object
|
|
102
|
+
|
|
103
|
+
Import the static token object for programmatic access (e.g. in a Tailwind config or a JS-driven style system).
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { tokens } from '@latty-ds/tokens';
|
|
107
|
+
|
|
108
|
+
console.log(tokens.color.primary['500']); // "#0097be"
|
|
109
|
+
console.log(tokens.spacing.rem['4']); // "1rem"
|
|
110
|
+
console.log(tokens.elevation['2']); // "0 4px 6px -1px rgb(…)"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Token reference
|
|
116
|
+
|
|
117
|
+
### Primitive tokens (`tokens.css`)
|
|
118
|
+
|
|
119
|
+
| Prefix | Example | Description |
|
|
120
|
+
| --------------------------------------- | ------------------------------ | --------------------------------- |
|
|
121
|
+
| `--lt-color-[name]-[step]` | `--lt-color-primary-500` | Color palette steps 50–900 |
|
|
122
|
+
| `--lt-color-[name]-muted-[step]` | `--lt-color-primary-muted-300` | Desaturated variant of each color |
|
|
123
|
+
| `--lt-color-neutral-[step]` | `--lt-color-neutral-100` | Greyscale scale |
|
|
124
|
+
| `--lt-color-white` / `--lt-color-black` | | System colors |
|
|
125
|
+
| `--lt-spacing-[n]` | `--lt-spacing-4` → `1rem` | Spacing scale (rem), n = 0–24 |
|
|
126
|
+
| `--lt-spacing-px-[n]` | `--lt-spacing-px-4` → `16px` | Spacing scale (px) |
|
|
127
|
+
| `--lt-border-radius` | `0.5rem` | Global border radius |
|
|
128
|
+
| `--lt-elevation-[n]` | `--lt-elevation-2` | Box shadows, n = 0–5 |
|
|
129
|
+
| `--lt-typography-fontFamily` | `"Hanken Grotesk", sans-serif` | Base font family |
|
|
130
|
+
|
|
131
|
+
Color names: `primary`, `secondary`, `success`, `warning`, `error`, `info` (each with a `-muted` variant).
|
|
132
|
+
|
|
133
|
+
### Semantic tokens (`semantic.css`)
|
|
134
|
+
|
|
135
|
+
Semantic tokens encode intent, not raw values. They reference primitives via `var()` so theming only requires overriding this layer.
|
|
136
|
+
|
|
137
|
+
| Group | Example | Intent |
|
|
138
|
+
| -------------------- | ----------------------------------- | ------------------------------ |
|
|
139
|
+
| `--lt-text-*` | `--lt-text-default` | Foreground / text colors |
|
|
140
|
+
| `--lt-bg-*` | `--lt-bg-surface` | Background fills |
|
|
141
|
+
| `--lt-border-*` | `--lt-border-focus` | Stroke colors |
|
|
142
|
+
| `--lt-interactive-*` | `--lt-interactive-primary-bg-hover` | Stateful component backgrounds |
|
|
143
|
+
|
|
144
|
+
Key semantic tokens:
|
|
145
|
+
|
|
146
|
+
```css
|
|
147
|
+
/* Text */
|
|
148
|
+
--lt-text-default → --lt-color-neutral-900 /* body text */
|
|
149
|
+
--lt-text-subtle → --lt-color-neutral-600 /* secondary / captions */
|
|
150
|
+
--lt-text-muted → --lt-color-neutral-400 /* placeholder, hints */
|
|
151
|
+
--lt-text-inverse → --lt-color-white /* text on dark bg */
|
|
152
|
+
--lt-text-[variant] → --lt-color-[variant]-700 /* colored label text */
|
|
153
|
+
--lt-text-on-[variant] → white or neutral-900 /* text on solid colored bg */
|
|
154
|
+
|
|
155
|
+
/* Background */
|
|
156
|
+
--lt-bg-default → --lt-color-white
|
|
157
|
+
--lt-bg-surface → --lt-color-neutral-100 /* cards, panels */
|
|
158
|
+
--lt-bg-[variant] → --lt-color-[variant]-500 /* solid colored bg */
|
|
159
|
+
--lt-bg-[variant]-subtle → --lt-color-[variant]-100 /* tinted bg (chips, badges) */
|
|
160
|
+
|
|
161
|
+
/* Border */
|
|
162
|
+
--lt-border-default → --lt-color-neutral-200
|
|
163
|
+
--lt-border-focus → --lt-color-primary-400 /* focus ring */
|
|
164
|
+
--lt-border-[variant] → --lt-color-[variant]-200 /* subtle colored border */
|
|
165
|
+
--lt-border-[variant]-strong → --lt-color-[variant]-500
|
|
166
|
+
|
|
167
|
+
/* Interactive */
|
|
168
|
+
--lt-interactive-[variant]-bg → --lt-color-[variant]-500
|
|
169
|
+
--lt-interactive-[variant]-bg-hover → --lt-color-[variant]-600
|
|
170
|
+
--lt-interactive-[variant]-bg-active → --lt-color-[variant]-700
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`[variant]` = `primary` | `secondary` | `success` | `warning` | `error` | `info`
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Package exports
|
|
178
|
+
|
|
179
|
+
| Export | File | Use |
|
|
180
|
+
| ------------------------------- | ------------------- | -------------------------------------------- |
|
|
181
|
+
| `@latty-ds/tokens` | `dist/index.js` | JS token object |
|
|
182
|
+
| `@latty-ds/tokens/configure` | `dist/configure.js` | Runtime `configure()` / `createStyleSheet()` |
|
|
183
|
+
| `@latty-ds/tokens/tokens.css` | `dist/tokens.css` | Primitive CSS custom properties |
|
|
184
|
+
| `@latty-ds/tokens/semantic.css` | `dist/semantic.css` | Semantic CSS custom properties |
|
|
185
|
+
| `@latty-ds/tokens/tokens.json` | `dist/tokens.json` | Full token object as JSON |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Source structure
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
src/
|
|
193
|
+
constants/ Shared constants (color steps, spacing base, default radius/font)
|
|
194
|
+
types/ TypeScript type definitions (Tokens, LattyConfig, palette types, …)
|
|
195
|
+
|
|
196
|
+
colors/ OKLCH color palette generator (main + muted scales)
|
|
197
|
+
spacing/ Spacing scale generator (rem + px, 0–24 steps)
|
|
198
|
+
elevation/ Box-shadow elevation generator (0–5 levels)
|
|
199
|
+
semantic/ Semantic token map (intent → primitive reference)
|
|
200
|
+
|
|
201
|
+
core/ Orchestration — combines generators into a Tokens object;
|
|
202
|
+
exports tokensToCss and semanticTokensToCss
|
|
203
|
+
|
|
204
|
+
configure/ Public browser-runtime API (configure, createStyleSheet)
|
|
205
|
+
Includes unit tests
|
|
206
|
+
|
|
207
|
+
build/ Node.js build pipeline (not browser-safe)
|
|
208
|
+
colors.ts Color token builder helper
|
|
209
|
+
tokens.ts Entry point — reads config, writes dist/ files
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Build the package (all three steps)
|
|
218
|
+
pnpm --filter @latty-ds/tokens build
|
|
219
|
+
|
|
220
|
+
# Run tests
|
|
221
|
+
pnpm test
|
|
222
|
+
|
|
223
|
+
# Clean dist
|
|
224
|
+
pnpm --filter @latty-ds/tokens clean
|
|
225
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
interface LattyConfig {
|
|
2
|
+
/** Built-in names: primary, secondary, success, warning, error, info. Any additional name generates a new palette. */
|
|
3
|
+
colors?: Record<string, string>;
|
|
4
|
+
font?: {
|
|
5
|
+
family?: string;
|
|
6
|
+
};
|
|
7
|
+
border?: {
|
|
8
|
+
radius?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Controls which semantic token layer is emitted.
|
|
12
|
+
* - `'light'` (default) — light tokens only; ignores OS preference
|
|
13
|
+
* - `'dark'` — dark tokens only; ignores OS preference
|
|
14
|
+
* - `'system'` — light at `:root`, dark inside `@media (prefers-color-scheme: dark)` + `[data-theme]` overrides
|
|
15
|
+
* - `'auto'` — alias for `'system'`, deprecated
|
|
16
|
+
*/
|
|
17
|
+
theme?: 'light' | 'dark' | 'system' | 'auto';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generates a complete CSS stylesheet string (primitive + semantic tokens)
|
|
22
|
+
* from the provided config. Useful for SSR — inject the returned string into
|
|
23
|
+
* a `<style>` tag server-side.
|
|
24
|
+
*/
|
|
25
|
+
declare const createStyleSheet: (userConfig?: LattyConfig) => string;
|
|
26
|
+
/**
|
|
27
|
+
* Injects design tokens into the document as a `<style>` element.
|
|
28
|
+
* Call once at your app's entry point — before any components render.
|
|
29
|
+
*
|
|
30
|
+
* Add `data-lt` to `<html>` to enable automatic FOUC prevention: configure()
|
|
31
|
+
* hides the document during token injection and reveals it on the next frame.
|
|
32
|
+
* For zero FOUC on initial load, also add this one-liner before your module script:
|
|
33
|
+
* `<style>html[data-lt]:not([data-lt-ready]){visibility:hidden}</style>`
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```html
|
|
37
|
+
* <html lang="en" data-lt>
|
|
38
|
+
* <head>
|
|
39
|
+
* <style>html[data-lt]:not([data-lt-ready]){visibility:hidden}</style>
|
|
40
|
+
* <script type="module">
|
|
41
|
+
* import { configure } from '@latty-ds/tokens/configure';
|
|
42
|
+
* configure({
|
|
43
|
+
* colors: { primary: '#6366f1', secondary: '#f59e0b' },
|
|
44
|
+
* font: { family: 'Inter, sans-serif' },
|
|
45
|
+
* border: { radius: '0.375rem' },
|
|
46
|
+
* });
|
|
47
|
+
* </script>
|
|
48
|
+
* </head>
|
|
49
|
+
* </html>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare const configure: (userConfig?: LattyConfig) => void;
|
|
53
|
+
|
|
54
|
+
export { type LattyConfig, configure, createStyleSheet };
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// src/constants/colors.constants.ts
|
|
2
|
+
var STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
|
3
|
+
var L_BY_STEP = {
|
|
4
|
+
50: 0.93,
|
|
5
|
+
100: 0.89,
|
|
6
|
+
200: 0.81,
|
|
7
|
+
300: 0.76,
|
|
8
|
+
400: 0.7,
|
|
9
|
+
500: 0.62,
|
|
10
|
+
600: 0.54,
|
|
11
|
+
700: 0.42,
|
|
12
|
+
800: 0.36,
|
|
13
|
+
900: 0.27
|
|
14
|
+
};
|
|
15
|
+
var L_BY_STEP_NEUTRAL = {
|
|
16
|
+
50: 0.985,
|
|
17
|
+
100: 0.965,
|
|
18
|
+
200: 0.92,
|
|
19
|
+
300: 0.84,
|
|
20
|
+
400: 0.74,
|
|
21
|
+
500: 0.62,
|
|
22
|
+
600: 0.52,
|
|
23
|
+
700: 0.42,
|
|
24
|
+
800: 0.32,
|
|
25
|
+
900: 0.22
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/constants/spacing.constants.ts
|
|
29
|
+
var SPACE_BASE_PX = 4;
|
|
30
|
+
var SPACE_STEPS = 24;
|
|
31
|
+
var REM_BASE_PX = 16;
|
|
32
|
+
|
|
33
|
+
// src/constants/tokens.constants.ts
|
|
34
|
+
var DEFAULT_BORDER_RADIUS = "0.5rem";
|
|
35
|
+
var DEFAULT_FONT_FAMILY = '"Hanken Grotesk", sans-serif';
|
|
36
|
+
|
|
37
|
+
// src/spacing/spacing.ts
|
|
38
|
+
var buildSpacing = () => {
|
|
39
|
+
const px = {};
|
|
40
|
+
const rem = {};
|
|
41
|
+
for (let i = 0; i <= SPACE_STEPS; i++) {
|
|
42
|
+
const v = i * SPACE_BASE_PX;
|
|
43
|
+
px[i] = `${v}px`;
|
|
44
|
+
rem[i] = `${v / REM_BASE_PX}rem`;
|
|
45
|
+
}
|
|
46
|
+
return { px, rem };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/elevation/elevation.ts
|
|
50
|
+
var buildElevation = (neutral) => {
|
|
51
|
+
const shadowColor = neutral["900"] || "#171717";
|
|
52
|
+
const hex = shadowColor.replace("#", "");
|
|
53
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
54
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
55
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
56
|
+
const rgb = `${r} ${g} ${b}`;
|
|
57
|
+
return {
|
|
58
|
+
"0": "none",
|
|
59
|
+
"1": `0 1px 3px 0 rgb(${rgb} / 0.1), 0 1px 2px 0 rgb(${rgb} / 0.06)`,
|
|
60
|
+
"2": `0 4px 6px -1px rgb(${rgb} / 0.1), 0 2px 4px -1px rgb(${rgb} / 0.06)`,
|
|
61
|
+
"3": `0 10px 15px -3px rgb(${rgb} / 0.1), 0 4px 6px -2px rgb(${rgb} / 0.05)`,
|
|
62
|
+
"4": `0 20px 25px -5px rgb(${rgb} / 0.1), 0 10px 10px -5px rgb(${rgb} / 0.04)`,
|
|
63
|
+
"5": `0 25px 50px -12px rgb(${rgb} / 0.25)`
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/colors/palette.ts
|
|
68
|
+
import { formatHex, converter } from "culori";
|
|
69
|
+
var toOklch = converter("oklch");
|
|
70
|
+
var toRgb = converter("rgb");
|
|
71
|
+
var clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
72
|
+
var rampFromBase = (base, opts) => {
|
|
73
|
+
const chromaScale = opts?.chromaScale ?? 1;
|
|
74
|
+
const lPivot = opts?.lPivot ?? L_BY_STEP[500];
|
|
75
|
+
const midBoost = opts?.midBoost ?? 0;
|
|
76
|
+
const darkClamp = opts?.darkClamp ?? 0.06;
|
|
77
|
+
const minChromaFactor = opts?.minChromaFactor ?? 0.08;
|
|
78
|
+
const out = {};
|
|
79
|
+
const h = (base.h % 360 + 360) % 360;
|
|
80
|
+
const cMax = clamp(base.c * chromaScale, 0, 0.5);
|
|
81
|
+
const c500 = L_BY_STEP[500];
|
|
82
|
+
const c50 = L_BY_STEP[50];
|
|
83
|
+
const c900 = L_BY_STEP[900];
|
|
84
|
+
for (const step of STEPS) {
|
|
85
|
+
const canonical = L_BY_STEP[step];
|
|
86
|
+
let lTarget;
|
|
87
|
+
if (canonical >= c500) {
|
|
88
|
+
const t2 = (canonical - c500) / (c50 - c500);
|
|
89
|
+
lTarget = lPivot + t2 * (c50 - lPivot);
|
|
90
|
+
} else {
|
|
91
|
+
const t2 = (canonical - c500) / (c900 - c500);
|
|
92
|
+
lTarget = lPivot + t2 * (c900 - lPivot);
|
|
93
|
+
}
|
|
94
|
+
lTarget = clamp(lTarget, 0, 1);
|
|
95
|
+
const t = STEPS.indexOf(step) / (STEPS.length - 1);
|
|
96
|
+
const peak = clamp(1 - Math.pow(Math.abs(t - 0.55) / 0.55, 1.7), 0, 1);
|
|
97
|
+
const cMin = cMax * minChromaFactor;
|
|
98
|
+
let c = clamp(cMin + (cMax - cMin) * peak, 0, 0.5);
|
|
99
|
+
c = clamp(c * (1 + midBoost * peak), 0, 0.5);
|
|
100
|
+
if (step >= 800) c = Math.min(c, darkClamp);
|
|
101
|
+
const color = { mode: "oklch", l: lTarget, c, h };
|
|
102
|
+
const rgb = toRgb(color);
|
|
103
|
+
out[step] = formatHex(rgb);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
};
|
|
107
|
+
var generatePalettes = (hex, useMuted) => {
|
|
108
|
+
const base = toOklch(hex);
|
|
109
|
+
if (!base) throw new Error(`Invalid color: ${hex}`);
|
|
110
|
+
const main = rampFromBase(base, {
|
|
111
|
+
chromaScale: 1,
|
|
112
|
+
lPivot: base.l,
|
|
113
|
+
midBoost: 0,
|
|
114
|
+
darkClamp: 0.06
|
|
115
|
+
});
|
|
116
|
+
main[500] = formatHex(hex) ?? hex;
|
|
117
|
+
const muted = useMuted ? rampFromBase(base, {
|
|
118
|
+
chromaScale: 0.2,
|
|
119
|
+
lPivot: clamp(base.l + 0.04, 0, 1),
|
|
120
|
+
midBoost: -0.2,
|
|
121
|
+
darkClamp: 0.045,
|
|
122
|
+
minChromaFactor: 0.06
|
|
123
|
+
}) : null;
|
|
124
|
+
return { main, muted };
|
|
125
|
+
};
|
|
126
|
+
var generateNeutralScale = () => {
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const step of STEPS) {
|
|
129
|
+
const l = L_BY_STEP_NEUTRAL[step];
|
|
130
|
+
const color = { mode: "oklch", l, c: 0, h: 0 };
|
|
131
|
+
out[step] = formatHex(toRgb(color));
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
};
|
|
135
|
+
var generateBWColors = () => {
|
|
136
|
+
return {
|
|
137
|
+
white: "#FFFFFF",
|
|
138
|
+
black: "#000000"
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/build/colors.ts
|
|
143
|
+
var toStepObj = (pal) => STEPS.reduce((acc, s) => {
|
|
144
|
+
acc[`${s}`] = pal[s];
|
|
145
|
+
return acc;
|
|
146
|
+
}, {});
|
|
147
|
+
var buildColorTokens = (cfg) => {
|
|
148
|
+
const color = {};
|
|
149
|
+
for (const [name, value] of Object.entries(cfg.color)) {
|
|
150
|
+
const palettes = generatePalettes(value, true);
|
|
151
|
+
color[name] = toStepObj(palettes.main);
|
|
152
|
+
if (palettes.muted) {
|
|
153
|
+
color[`${name}-muted`] = toStepObj(palettes.muted);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return color;
|
|
157
|
+
};
|
|
158
|
+
var addSystemColors = (color) => {
|
|
159
|
+
color.neutral = toStepObj(generateNeutralScale());
|
|
160
|
+
return { ...color, ...generateBWColors() };
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/core/generate.ts
|
|
164
|
+
var buildTokens = (cfg) => {
|
|
165
|
+
let color = buildColorTokens(cfg);
|
|
166
|
+
color = addSystemColors(color);
|
|
167
|
+
const spacing = buildSpacing();
|
|
168
|
+
const border = { radius: DEFAULT_BORDER_RADIUS, square: false };
|
|
169
|
+
const typography = { fontFamily: DEFAULT_FONT_FAMILY };
|
|
170
|
+
const elevation = buildElevation(color.neutral);
|
|
171
|
+
return { color, spacing, border, typography, elevation };
|
|
172
|
+
};
|
|
173
|
+
var tokensToCss = (tokens) => {
|
|
174
|
+
const lines = [];
|
|
175
|
+
const walker = (value, parts) => {
|
|
176
|
+
if (typeof value === "string") {
|
|
177
|
+
lines.push(` --lt-${parts.join("-")}: ${value};`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!value || typeof value !== "object") return;
|
|
181
|
+
for (const key of Object.keys(value).sort()) {
|
|
182
|
+
const next = value[key];
|
|
183
|
+
if (parts.length === 1 && parts[0] === "spacing" && (key === "rem" || key === "px")) {
|
|
184
|
+
if (key === "rem") walker(next, ["spacing"]);
|
|
185
|
+
else walker(next, ["spacing", "px"]);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
walker(next, [...parts, key]);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
walker(tokens, []);
|
|
192
|
+
return `:root{
|
|
193
|
+
${lines.join("\n")}
|
|
194
|
+
}
|
|
195
|
+
`;
|
|
196
|
+
};
|
|
197
|
+
var semanticTokensToCss = (map, wrapper) => {
|
|
198
|
+
const lines = Object.entries(map).sort(([a], [b]) => a.localeCompare(b)).map(([key, ref]) => ` --lt-${key}: var(--lt-${ref});`);
|
|
199
|
+
if (!wrapper) {
|
|
200
|
+
return `/* Semantic tokens */
|
|
201
|
+
:root {
|
|
202
|
+
${lines.join("\n")}
|
|
203
|
+
}
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
if (wrapper.startsWith("@")) {
|
|
207
|
+
const indented = lines.map((l) => " " + l);
|
|
208
|
+
return `${wrapper} {
|
|
209
|
+
:root {
|
|
210
|
+
${indented.join("\n")}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
return `${wrapper} {
|
|
216
|
+
${lines.join("\n")}
|
|
217
|
+
}
|
|
218
|
+
`;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/semantic/semantic-tokens.ts
|
|
222
|
+
var VARIANTS = ["primary", "secondary", "success", "warning", "error", "info"];
|
|
223
|
+
var relativeLuminance = (hex) => {
|
|
224
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
225
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
226
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
227
|
+
const lin = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
228
|
+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
229
|
+
};
|
|
230
|
+
var accessibleTextOn = (bgHex) => {
|
|
231
|
+
const L = relativeLuminance(bgHex);
|
|
232
|
+
const whiteContrast = 1.05 / (L + 0.05);
|
|
233
|
+
const darkContrast = (L + 0.05) / 0.059;
|
|
234
|
+
return whiteContrast >= darkContrast ? "color-white" : "color-neutral-900";
|
|
235
|
+
};
|
|
236
|
+
var buildLightSemanticTokens = (opts = {}) => {
|
|
237
|
+
const map = {};
|
|
238
|
+
map["text-default"] = "color-neutral-900";
|
|
239
|
+
map["text-subtle"] = "color-neutral-600";
|
|
240
|
+
map["text-muted"] = "color-neutral-400";
|
|
241
|
+
map["text-disabled"] = "color-neutral-400";
|
|
242
|
+
map["text-inverse"] = "color-white";
|
|
243
|
+
map["text-neutral"] = "color-neutral-700";
|
|
244
|
+
for (const v of VARIANTS) {
|
|
245
|
+
map[`text-${v}`] = `color-${v}-700`;
|
|
246
|
+
map[`text-on-${v}`] = "color-white";
|
|
247
|
+
map[`text-on-warning`] = "color-warning-800";
|
|
248
|
+
}
|
|
249
|
+
if (opts.primary500) map["text-on-primary"] = accessibleTextOn(opts.primary500);
|
|
250
|
+
map["bg-default"] = "color-white";
|
|
251
|
+
map["bg-subtle"] = "color-neutral-50";
|
|
252
|
+
map["bg-surface"] = "color-neutral-100";
|
|
253
|
+
map["bg-overlay"] = "color-neutral-200";
|
|
254
|
+
map["bg-inverse"] = "color-neutral-900";
|
|
255
|
+
map["bg-neutral-subtle"] = "color-neutral-100";
|
|
256
|
+
for (const v of VARIANTS) {
|
|
257
|
+
map[`bg-${v}`] = `color-${v}-500`;
|
|
258
|
+
map[`bg-${v}-subtle`] = `color-${v}-100`;
|
|
259
|
+
}
|
|
260
|
+
map["border-default"] = "color-neutral-200";
|
|
261
|
+
map["border-strong"] = "color-neutral-400";
|
|
262
|
+
map["border-subtle"] = "color-neutral-100";
|
|
263
|
+
map["border-focus"] = "color-primary-200";
|
|
264
|
+
for (const v of VARIANTS) {
|
|
265
|
+
map[`border-${v}`] = `color-${v}-200`;
|
|
266
|
+
map[`border-${v}-strong`] = `color-${v}-500`;
|
|
267
|
+
}
|
|
268
|
+
for (const v of VARIANTS) {
|
|
269
|
+
map[`interactive-${v}-bg`] = `color-${v}-500`;
|
|
270
|
+
map[`interactive-${v}-bg-hover`] = `color-${v}-600`;
|
|
271
|
+
map[`interactive-${v}-bg-active`] = `color-${v}-700`;
|
|
272
|
+
}
|
|
273
|
+
return map;
|
|
274
|
+
};
|
|
275
|
+
var buildDarkSemanticTokens = (opts = {}) => {
|
|
276
|
+
const map = {};
|
|
277
|
+
map["text-default"] = "color-neutral-100";
|
|
278
|
+
map["text-subtle"] = "color-neutral-300";
|
|
279
|
+
map["text-muted"] = "color-neutral-500";
|
|
280
|
+
map["text-disabled"] = "color-neutral-600";
|
|
281
|
+
map["text-inverse"] = "color-neutral-900";
|
|
282
|
+
map["text-neutral"] = "color-neutral-200";
|
|
283
|
+
for (const v of VARIANTS) {
|
|
284
|
+
map[`text-${v}`] = `color-${v}-300`;
|
|
285
|
+
map[`text-on-${v}`] = "color-white";
|
|
286
|
+
}
|
|
287
|
+
if (opts.primary400) map["text-on-primary"] = accessibleTextOn(opts.primary400);
|
|
288
|
+
map["bg-default"] = "color-neutral-900";
|
|
289
|
+
map["bg-subtle"] = "color-neutral-800";
|
|
290
|
+
map["bg-surface"] = "color-neutral-800";
|
|
291
|
+
map["bg-overlay"] = "color-neutral-700";
|
|
292
|
+
map["bg-inverse"] = "color-neutral-50";
|
|
293
|
+
map["bg-neutral-subtle"] = "color-neutral-800";
|
|
294
|
+
for (const v of VARIANTS) {
|
|
295
|
+
map[`bg-${v}`] = `color-${v}-500`;
|
|
296
|
+
map[`bg-${v}-subtle`] = `color-${v}-900`;
|
|
297
|
+
}
|
|
298
|
+
map["border-default"] = "color-neutral-700";
|
|
299
|
+
map["border-strong"] = "color-neutral-500";
|
|
300
|
+
map["border-subtle"] = "color-neutral-800";
|
|
301
|
+
map["border-focus"] = "color-primary-400";
|
|
302
|
+
for (const v of VARIANTS) {
|
|
303
|
+
map[`border-${v}`] = `color-${v}-700`;
|
|
304
|
+
map[`border-${v}-strong`] = `color-${v}-500`;
|
|
305
|
+
}
|
|
306
|
+
for (const v of VARIANTS) {
|
|
307
|
+
map[`interactive-${v}-bg`] = `color-${v}-400`;
|
|
308
|
+
map[`interactive-${v}-bg-hover`] = `color-${v}-300`;
|
|
309
|
+
map[`interactive-${v}-bg-active`] = `color-${v}-500`;
|
|
310
|
+
}
|
|
311
|
+
return map;
|
|
312
|
+
};
|
|
313
|
+
var buildSemanticTokens = (mode = "light", opts = {}) => mode === "dark" ? buildDarkSemanticTokens(opts) : buildLightSemanticTokens(opts);
|
|
314
|
+
|
|
315
|
+
// tokens.config.json
|
|
316
|
+
var tokens_config_default = {
|
|
317
|
+
color: {
|
|
318
|
+
primary: "#FF8200",
|
|
319
|
+
secondary: "#5252c5",
|
|
320
|
+
success: "#22c55e",
|
|
321
|
+
warning: "#eeb308",
|
|
322
|
+
error: "#ef4444",
|
|
323
|
+
info: "#0ea5e9"
|
|
324
|
+
},
|
|
325
|
+
border: {
|
|
326
|
+
radius: "2px"
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/configure/configure.ts
|
|
331
|
+
var DEFAULTS = {
|
|
332
|
+
colors: tokens_config_default.color,
|
|
333
|
+
font: { family: DEFAULT_FONT_FAMILY },
|
|
334
|
+
border: { radius: DEFAULT_BORDER_RADIUS }
|
|
335
|
+
};
|
|
336
|
+
var toInternalConfig = (userConfig) => ({
|
|
337
|
+
color: { ...DEFAULTS.colors, ...userConfig.colors }
|
|
338
|
+
});
|
|
339
|
+
var createStyleSheet = (userConfig = {}) => {
|
|
340
|
+
const cfg = toInternalConfig(userConfig);
|
|
341
|
+
let tokens = buildTokens(cfg);
|
|
342
|
+
if (userConfig.font?.family) {
|
|
343
|
+
tokens = { ...tokens, typography: { fontFamily: userConfig.font.family } };
|
|
344
|
+
}
|
|
345
|
+
if (userConfig.border?.radius) {
|
|
346
|
+
tokens = { ...tokens, border: { ...tokens.border, radius: userConfig.border.radius } };
|
|
347
|
+
}
|
|
348
|
+
const primitives = tokensToCss(tokens);
|
|
349
|
+
const theme = userConfig.theme ?? "light";
|
|
350
|
+
const primary = tokens.color.primary;
|
|
351
|
+
const semanticOpts = { primary500: primary["500"], primary400: primary["400"] };
|
|
352
|
+
if (theme === "dark") return primitives + "\n" + semanticTokensToCss(buildSemanticTokens("dark", semanticOpts));
|
|
353
|
+
if (theme === "light") return primitives + "\n" + semanticTokensToCss(buildSemanticTokens("light", semanticOpts));
|
|
354
|
+
return primitives + "\n" + semanticTokensToCss(buildSemanticTokens("light", semanticOpts)) + "\n" + semanticTokensToCss(buildSemanticTokens("dark", semanticOpts), "@media (prefers-color-scheme: dark)") + "\n" + semanticTokensToCss(buildSemanticTokens("dark", semanticOpts), '[data-theme="dark"]') + "\n" + semanticTokensToCss(buildSemanticTokens("light", semanticOpts), '[data-theme="light"]');
|
|
355
|
+
};
|
|
356
|
+
var configure = (userConfig = {}) => {
|
|
357
|
+
const root = document.documentElement;
|
|
358
|
+
root.removeAttribute("data-lt-ready");
|
|
359
|
+
let style = document.getElementById("lt-tokens");
|
|
360
|
+
if (!style) {
|
|
361
|
+
style = document.createElement("style");
|
|
362
|
+
style.id = "lt-tokens";
|
|
363
|
+
document.head.prepend(style);
|
|
364
|
+
}
|
|
365
|
+
style.textContent = "html[data-lt]:not([data-lt-ready]){visibility:hidden}\n" + createStyleSheet(userConfig);
|
|
366
|
+
requestAnimationFrame(() => root.setAttribute("data-lt-ready", ""));
|
|
367
|
+
};
|
|
368
|
+
export {
|
|
369
|
+
configure,
|
|
370
|
+
createStyleSheet
|
|
371
|
+
};
|