@arun-dev/tokens 0.1.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 +98 -0
- package/dist/createBrand.cjs +210 -0
- package/dist/createBrand.d.cts +74 -0
- package/dist/createBrand.d.ts +74 -0
- package/dist/createBrand.js +183 -0
- package/package.json +70 -0
- package/src/base.css +10 -0
- package/src/brands/default/palette.css +28 -0
- package/src/brands/default/semantic.css +162 -0
- package/src/brands/default.css +5 -0
- package/src/createBrand.ts +293 -0
- package/src/fonts/inter-latin-wght-italic.woff2 +0 -0
- package/src/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/src/primitives/breakpoints.css +9 -0
- package/src/primitives/elevation.css +10 -0
- package/src/primitives/fonts.css +25 -0
- package/src/primitives/motion.css +10 -0
- package/src/primitives/radius.css +9 -0
- package/src/primitives/shadow.css +12 -0
- package/src/primitives/spacing.css +29 -0
- package/src/primitives/typography.css +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arun Tripathi
|
|
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,98 @@
|
|
|
1
|
+
# @arun-dev/tokens
|
|
2
|
+
|
|
3
|
+
Pure-CSS design tokens with white-label brand generation. Zero runtime dependencies — CSS ships
|
|
4
|
+
as-is, plus a small build-time `createBrand()` utility for generating custom brand stylesheets.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @arun-dev/tokens
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Token layers
|
|
13
|
+
|
|
14
|
+
Tokens are layered — never skip a layer:
|
|
15
|
+
|
|
16
|
+
1. **Primitives** — raw scales with no color: typography, spacing, radius, shadow, motion,
|
|
17
|
+
elevation, breakpoints
|
|
18
|
+
2. **Palette** — brand colors (`--color-brand-50…950`) and neutrals (`--color-neutral-0…950`)
|
|
19
|
+
3. **Semantic** — intent-named tokens consumed by components (`--color-bg-primary`,
|
|
20
|
+
`--color-text-accent`, `--color-border-default`, …)
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Everything at once (primitives + default brand)
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
@import '@arun-dev/tokens/base';
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### À la carte
|
|
31
|
+
|
|
32
|
+
```css
|
|
33
|
+
@import '@arun-dev/tokens/primitives/typography';
|
|
34
|
+
@import '@arun-dev/tokens/primitives/spacing';
|
|
35
|
+
@import '@arun-dev/tokens/primitives/radius';
|
|
36
|
+
@import '@arun-dev/tokens/primitives/shadow';
|
|
37
|
+
@import '@arun-dev/tokens/primitives/motion';
|
|
38
|
+
@import '@arun-dev/tokens/primitives/elevation';
|
|
39
|
+
@import '@arun-dev/tokens/primitives/breakpoints';
|
|
40
|
+
@import '@arun-dev/tokens/brands/default'; /* palette + semantic */
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Fonts (opt-in)
|
|
44
|
+
|
|
45
|
+
`base` deliberately does not load fonts. To use the bundled Inter variable font:
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
@import '@arun-dev/tokens/primitives/fonts';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Theming
|
|
52
|
+
|
|
53
|
+
Semantic tokens cover light and dark out of the box: system preference via
|
|
54
|
+
`@media (prefers-color-scheme: dark)`, with explicit overrides via `data-theme="dark"` /
|
|
55
|
+
`data-theme="light"` on the root element.
|
|
56
|
+
|
|
57
|
+
## Custom brands with `createBrand()`
|
|
58
|
+
|
|
59
|
+
`createBrand()` is a pure, build-time function that returns a complete brand CSS string (palette +
|
|
60
|
+
neutrals + semantic light/dark tokens). Write its output to a file and load it instead of the
|
|
61
|
+
default brand.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { writeFileSync } from 'node:fs';
|
|
65
|
+
import { createBrand } from '@arun-dev/tokens/createBrand';
|
|
66
|
+
|
|
67
|
+
// From a single seed color (generates an 11-step palette)
|
|
68
|
+
writeFileSync('styles/acme.css', createBrand({ name: 'acme', seed: '#0ea5e9' }));
|
|
69
|
+
|
|
70
|
+
// Or from an explicit 11-step palette (50–950)
|
|
71
|
+
writeFileSync(
|
|
72
|
+
'styles/acme.css',
|
|
73
|
+
createBrand({ name: 'acme', palette: { 50: '#f0f9ff' /* … */, 950: '#082f49' } }),
|
|
74
|
+
);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## `BrandSemanticContract`
|
|
78
|
+
|
|
79
|
+
The TypeScript type `BrandSemanticContract` (exported from `./createBrand`) lists every semantic
|
|
80
|
+
CSS variable that `@arun-dev/ui` components require. Consumers may skip `createBrand()` entirely
|
|
81
|
+
and hand-write a brand stylesheet — the only requirement is that all contract variables are
|
|
82
|
+
defined before components render.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import type { BrandSemanticContract } from '@arun-dev/tokens/createBrand';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Exports
|
|
89
|
+
|
|
90
|
+
| Specifier | Content |
|
|
91
|
+
| ------------------------------------------ | --------------------------------------------------- |
|
|
92
|
+
| `@arun-dev/tokens/base` | All primitives + default brand |
|
|
93
|
+
| `@arun-dev/tokens/brands/default` | Default brand (palette+semantic) |
|
|
94
|
+
| `@arun-dev/tokens/brands/default/palette` | Default palette only |
|
|
95
|
+
| `@arun-dev/tokens/brands/default/semantic` | Default semantic layer only |
|
|
96
|
+
| `@arun-dev/tokens/primitives/*` | Individual primitive scales |
|
|
97
|
+
| `@arun-dev/tokens/primitives/fonts` | Inter variable font (`@font-face`) |
|
|
98
|
+
| `@arun-dev/tokens/createBrand` | `createBrand()`, `generatePaletteFromSeed()`, types |
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/createBrand.ts
|
|
21
|
+
var createBrand_exports = {};
|
|
22
|
+
__export(createBrand_exports, {
|
|
23
|
+
DEFAULT_NEUTRAL: () => DEFAULT_NEUTRAL,
|
|
24
|
+
createBrand: () => createBrand,
|
|
25
|
+
generatePaletteFromSeed: () => generatePaletteFromSeed
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(createBrand_exports);
|
|
28
|
+
var DEFAULT_NEUTRAL = {
|
|
29
|
+
0: "#ffffff",
|
|
30
|
+
50: "#f8fafc",
|
|
31
|
+
100: "#f1f5f9",
|
|
32
|
+
200: "#e2e8f0",
|
|
33
|
+
300: "#cbd5e1",
|
|
34
|
+
400: "#96a5ba",
|
|
35
|
+
500: "#64748b",
|
|
36
|
+
600: "#475569",
|
|
37
|
+
700: "#334155",
|
|
38
|
+
800: "#1e293b",
|
|
39
|
+
900: "#0f172a",
|
|
40
|
+
950: "#020617"
|
|
41
|
+
};
|
|
42
|
+
function hexToRgb(hex) {
|
|
43
|
+
const h = hex.replace("#", "");
|
|
44
|
+
return [
|
|
45
|
+
parseInt(h.slice(0, 2), 16) / 255,
|
|
46
|
+
parseInt(h.slice(2, 4), 16) / 255,
|
|
47
|
+
parseInt(h.slice(4, 6), 16) / 255
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function rgbToHsl(r, g, b) {
|
|
51
|
+
const max = Math.max(r, g, b);
|
|
52
|
+
const min = Math.min(r, g, b);
|
|
53
|
+
const l = (max + min) / 2;
|
|
54
|
+
if (max === min) return [0, 0, l * 100];
|
|
55
|
+
const d = max - min;
|
|
56
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
57
|
+
let h;
|
|
58
|
+
switch (max) {
|
|
59
|
+
case r:
|
|
60
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
61
|
+
break;
|
|
62
|
+
case g:
|
|
63
|
+
h = ((b - r) / d + 2) / 6;
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
h = ((r - g) / d + 4) / 6;
|
|
67
|
+
}
|
|
68
|
+
return [h * 360, s * 100, l * 100];
|
|
69
|
+
}
|
|
70
|
+
function hslToHex(h, s, l) {
|
|
71
|
+
const sl = s / 100;
|
|
72
|
+
const ll = l / 100;
|
|
73
|
+
const a = sl * Math.min(ll, 1 - ll);
|
|
74
|
+
const f = (n) => {
|
|
75
|
+
const k = (n + h / 30) % 12;
|
|
76
|
+
const color = ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
77
|
+
return Math.round(255 * color).toString(16).padStart(2, "0");
|
|
78
|
+
};
|
|
79
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
80
|
+
}
|
|
81
|
+
var SHADE_LIGHTNESS = {
|
|
82
|
+
50: 97,
|
|
83
|
+
100: 93,
|
|
84
|
+
200: 86,
|
|
85
|
+
300: 75,
|
|
86
|
+
400: 64,
|
|
87
|
+
500: 53,
|
|
88
|
+
600: 44,
|
|
89
|
+
700: 36,
|
|
90
|
+
800: 28,
|
|
91
|
+
900: 20,
|
|
92
|
+
950: 14
|
|
93
|
+
};
|
|
94
|
+
function generatePaletteFromSeed(seed) {
|
|
95
|
+
const [r, g, b] = hexToRgb(seed);
|
|
96
|
+
const [h, s] = rgbToHsl(r, g, b);
|
|
97
|
+
const entries = Object.entries(SHADE_LIGHTNESS).map(
|
|
98
|
+
([shade, targetL]) => {
|
|
99
|
+
const adjustedS = targetL > 80 ? s * 0.55 : targetL < 25 ? s * 0.75 : s;
|
|
100
|
+
return [Number(shade), hslToHex(h, Math.min(adjustedS, 100), targetL)];
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
return Object.fromEntries(entries);
|
|
104
|
+
}
|
|
105
|
+
var STATUS_LIGHT = {
|
|
106
|
+
"--color-status-success": "#15803d",
|
|
107
|
+
"--color-status-success-bg": "#f0fdf4",
|
|
108
|
+
"--color-status-error": "#b91c1c",
|
|
109
|
+
"--color-status-error-bg": "#fef2f2",
|
|
110
|
+
"--color-status-warning": "#b45309",
|
|
111
|
+
"--color-status-warning-bg": "#fffbeb"
|
|
112
|
+
};
|
|
113
|
+
var STATUS_DARK = {
|
|
114
|
+
"--color-status-success": "#4ade80",
|
|
115
|
+
"--color-status-success-bg": "#052e16",
|
|
116
|
+
"--color-status-error": "#f87171",
|
|
117
|
+
"--color-status-error-bg": "#450a0a",
|
|
118
|
+
"--color-status-warning": "#fbbf24",
|
|
119
|
+
"--color-status-warning-bg": "#451a03"
|
|
120
|
+
};
|
|
121
|
+
function deriveLight() {
|
|
122
|
+
return {
|
|
123
|
+
"--color-bg-primary": "var(--color-neutral-0)",
|
|
124
|
+
"--color-bg-secondary": "var(--color-neutral-50)",
|
|
125
|
+
"--color-bg-surface": "var(--color-neutral-100)",
|
|
126
|
+
"--color-bg-accent": "var(--color-brand-50)",
|
|
127
|
+
"--color-bg-inverse": "var(--color-neutral-900)",
|
|
128
|
+
"--color-text-primary": "var(--color-neutral-900)",
|
|
129
|
+
"--color-text-secondary": "var(--color-neutral-700)",
|
|
130
|
+
"--color-text-muted": "var(--color-neutral-600)",
|
|
131
|
+
"--color-text-accent": "var(--color-brand-700)",
|
|
132
|
+
"--color-text-inverse": "var(--color-neutral-50)",
|
|
133
|
+
"--color-text-on-accent": "var(--color-neutral-0)",
|
|
134
|
+
"--color-border-default": "var(--color-neutral-200)",
|
|
135
|
+
"--color-border-subtle": "var(--color-neutral-100)",
|
|
136
|
+
"--color-border-strong": "var(--color-neutral-300)",
|
|
137
|
+
"--color-border-accent": "var(--color-brand-300)",
|
|
138
|
+
...STATUS_LIGHT,
|
|
139
|
+
"--color-status-info": "var(--color-brand-500)",
|
|
140
|
+
"--color-status-info-bg": "var(--color-brand-50)"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function deriveDark() {
|
|
144
|
+
return {
|
|
145
|
+
"--color-bg-primary": "var(--color-neutral-900)",
|
|
146
|
+
"--color-bg-secondary": "var(--color-neutral-800)",
|
|
147
|
+
"--color-bg-surface": "var(--color-neutral-700)",
|
|
148
|
+
"--color-bg-accent": "var(--color-brand-950)",
|
|
149
|
+
"--color-bg-inverse": "var(--color-neutral-50)",
|
|
150
|
+
"--color-text-primary": "var(--color-neutral-100)",
|
|
151
|
+
"--color-text-secondary": "var(--color-neutral-300)",
|
|
152
|
+
"--color-text-muted": "var(--color-neutral-400)",
|
|
153
|
+
"--color-text-accent": "var(--color-brand-300)",
|
|
154
|
+
"--color-text-inverse": "var(--color-neutral-900)",
|
|
155
|
+
"--color-text-on-accent": "var(--color-neutral-0)",
|
|
156
|
+
"--color-border-default": "var(--color-neutral-700)",
|
|
157
|
+
"--color-border-subtle": "var(--color-neutral-800)",
|
|
158
|
+
"--color-border-strong": "var(--color-neutral-600)",
|
|
159
|
+
"--color-border-accent": "var(--color-brand-700)",
|
|
160
|
+
...STATUS_DARK,
|
|
161
|
+
"--color-status-info": "var(--color-brand-300)",
|
|
162
|
+
"--color-status-info-bg": "var(--color-brand-950)"
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function toVars(tokens, indent = " ") {
|
|
166
|
+
return Object.entries(tokens).map(([k, v]) => `${indent}${k}: ${v};`).join("\n");
|
|
167
|
+
}
|
|
168
|
+
function paletteVars(palette) {
|
|
169
|
+
return Object.keys(SHADE_LIGHTNESS).map((shade) => ` --color-brand-${shade}: ${palette[shade]};`).join("\n");
|
|
170
|
+
}
|
|
171
|
+
function neutralVars(neutral) {
|
|
172
|
+
return [0, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950].map((shade) => ` --color-neutral-${shade}: ${neutral[shade]};`).join("\n");
|
|
173
|
+
}
|
|
174
|
+
function createBrand(input) {
|
|
175
|
+
const { name, neutral = DEFAULT_NEUTRAL } = input;
|
|
176
|
+
const palette = "seed" in input ? generatePaletteFromSeed(input.seed) : input.palette;
|
|
177
|
+
const light = deriveLight();
|
|
178
|
+
const dark = deriveDark();
|
|
179
|
+
return `/* ${name} brand \u2014 generated by createBrand (@arun-dev/tokens) */
|
|
180
|
+
/* Re-run createBrand to regenerate after palette changes. */
|
|
181
|
+
|
|
182
|
+
:root {
|
|
183
|
+
${paletteVars(palette)}
|
|
184
|
+
|
|
185
|
+
${neutralVars(neutral)}
|
|
186
|
+
|
|
187
|
+
${toVars(light)}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@media (prefers-color-scheme: dark) {
|
|
191
|
+
:root:not([data-theme='light']) {
|
|
192
|
+
${toVars(dark, " ")}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
[data-theme='dark'] {
|
|
197
|
+
${toVars(dark)}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
[data-theme='light'] {
|
|
201
|
+
${toVars(light)}
|
|
202
|
+
}
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
206
|
+
0 && (module.exports = {
|
|
207
|
+
DEFAULT_NEUTRAL,
|
|
208
|
+
createBrand,
|
|
209
|
+
generatePaletteFromSeed
|
|
210
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createBrand — build-time CSS generator for @arun-dev/tokens.
|
|
3
|
+
*
|
|
4
|
+
* Usage (full palette):
|
|
5
|
+
* import { createBrand } from '@arun-dev/tokens/createBrand';
|
|
6
|
+
* const css = createBrand({ name: 'acme', palette: { 50: '#...', ..., 950: '#...' } });
|
|
7
|
+
* writeFileSync('src/brands/acme.css', css);
|
|
8
|
+
*
|
|
9
|
+
* Usage (seed color — auto-generates palette):
|
|
10
|
+
* const css = createBrand({ name: 'acme', seed: '#7c3aed' });
|
|
11
|
+
*
|
|
12
|
+
* Consumer-level brand completion:
|
|
13
|
+
* A consumer may skip createBrand entirely and define semantic tokens directly in their globals.css.
|
|
14
|
+
* The only requirement: all semantic variables listed in BrandSemanticContract must be defined
|
|
15
|
+
* before @arun-dev/ui components are rendered.
|
|
16
|
+
*/
|
|
17
|
+
type Shade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
|
18
|
+
type NeutralShade = 0 | Shade;
|
|
19
|
+
type BrandPalette = Record<Shade, string>;
|
|
20
|
+
type NeutralPalette = Record<NeutralShade, string>;
|
|
21
|
+
type WithPalette = {
|
|
22
|
+
palette: BrandPalette;
|
|
23
|
+
};
|
|
24
|
+
type WithSeed = {
|
|
25
|
+
seed: string;
|
|
26
|
+
};
|
|
27
|
+
type CreateBrandInput = {
|
|
28
|
+
/** Brand identifier — used as a comment label in generated CSS */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Optional neutral/gray palette. Defaults to cool-gray (slate). */
|
|
31
|
+
neutral?: NeutralPalette;
|
|
32
|
+
} & (WithPalette | WithSeed);
|
|
33
|
+
declare const DEFAULT_NEUTRAL: NeutralPalette;
|
|
34
|
+
/**
|
|
35
|
+
* Generate an 11-step palette from a seed hex color.
|
|
36
|
+
* Uses HSL color space — perceptually approximate but dependency-free.
|
|
37
|
+
*/
|
|
38
|
+
declare function generatePaletteFromSeed(seed: string): BrandPalette;
|
|
39
|
+
/**
|
|
40
|
+
* BrandSemanticContract — the full set of CSS variables @arun-dev/ui expects.
|
|
41
|
+
* Consumers who skip createBrand must define all of these in their own CSS.
|
|
42
|
+
*/
|
|
43
|
+
type BrandSemanticContract = {
|
|
44
|
+
'--color-bg-primary': string;
|
|
45
|
+
'--color-bg-secondary': string;
|
|
46
|
+
'--color-bg-surface': string;
|
|
47
|
+
'--color-bg-accent': string;
|
|
48
|
+
'--color-bg-inverse': string;
|
|
49
|
+
'--color-text-primary': string;
|
|
50
|
+
'--color-text-secondary': string;
|
|
51
|
+
'--color-text-muted': string;
|
|
52
|
+
'--color-text-accent': string;
|
|
53
|
+
'--color-text-inverse': string;
|
|
54
|
+
'--color-text-on-accent': string;
|
|
55
|
+
'--color-border-default': string;
|
|
56
|
+
'--color-border-subtle': string;
|
|
57
|
+
'--color-border-strong': string;
|
|
58
|
+
'--color-border-accent': string;
|
|
59
|
+
'--color-status-success': string;
|
|
60
|
+
'--color-status-success-bg': string;
|
|
61
|
+
'--color-status-error': string;
|
|
62
|
+
'--color-status-error-bg': string;
|
|
63
|
+
'--color-status-warning': string;
|
|
64
|
+
'--color-status-warning-bg': string;
|
|
65
|
+
'--color-status-info': string;
|
|
66
|
+
'--color-status-info-bg': string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Generate a complete brand CSS string from a palette or seed color.
|
|
70
|
+
* Write the output to a .css file and import it in your globals.css.
|
|
71
|
+
*/
|
|
72
|
+
declare function createBrand(input: CreateBrandInput): string;
|
|
73
|
+
|
|
74
|
+
export { type BrandPalette, type BrandSemanticContract, type CreateBrandInput, DEFAULT_NEUTRAL, type NeutralPalette, createBrand, generatePaletteFromSeed };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createBrand — build-time CSS generator for @arun-dev/tokens.
|
|
3
|
+
*
|
|
4
|
+
* Usage (full palette):
|
|
5
|
+
* import { createBrand } from '@arun-dev/tokens/createBrand';
|
|
6
|
+
* const css = createBrand({ name: 'acme', palette: { 50: '#...', ..., 950: '#...' } });
|
|
7
|
+
* writeFileSync('src/brands/acme.css', css);
|
|
8
|
+
*
|
|
9
|
+
* Usage (seed color — auto-generates palette):
|
|
10
|
+
* const css = createBrand({ name: 'acme', seed: '#7c3aed' });
|
|
11
|
+
*
|
|
12
|
+
* Consumer-level brand completion:
|
|
13
|
+
* A consumer may skip createBrand entirely and define semantic tokens directly in their globals.css.
|
|
14
|
+
* The only requirement: all semantic variables listed in BrandSemanticContract must be defined
|
|
15
|
+
* before @arun-dev/ui components are rendered.
|
|
16
|
+
*/
|
|
17
|
+
type Shade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
|
18
|
+
type NeutralShade = 0 | Shade;
|
|
19
|
+
type BrandPalette = Record<Shade, string>;
|
|
20
|
+
type NeutralPalette = Record<NeutralShade, string>;
|
|
21
|
+
type WithPalette = {
|
|
22
|
+
palette: BrandPalette;
|
|
23
|
+
};
|
|
24
|
+
type WithSeed = {
|
|
25
|
+
seed: string;
|
|
26
|
+
};
|
|
27
|
+
type CreateBrandInput = {
|
|
28
|
+
/** Brand identifier — used as a comment label in generated CSS */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Optional neutral/gray palette. Defaults to cool-gray (slate). */
|
|
31
|
+
neutral?: NeutralPalette;
|
|
32
|
+
} & (WithPalette | WithSeed);
|
|
33
|
+
declare const DEFAULT_NEUTRAL: NeutralPalette;
|
|
34
|
+
/**
|
|
35
|
+
* Generate an 11-step palette from a seed hex color.
|
|
36
|
+
* Uses HSL color space — perceptually approximate but dependency-free.
|
|
37
|
+
*/
|
|
38
|
+
declare function generatePaletteFromSeed(seed: string): BrandPalette;
|
|
39
|
+
/**
|
|
40
|
+
* BrandSemanticContract — the full set of CSS variables @arun-dev/ui expects.
|
|
41
|
+
* Consumers who skip createBrand must define all of these in their own CSS.
|
|
42
|
+
*/
|
|
43
|
+
type BrandSemanticContract = {
|
|
44
|
+
'--color-bg-primary': string;
|
|
45
|
+
'--color-bg-secondary': string;
|
|
46
|
+
'--color-bg-surface': string;
|
|
47
|
+
'--color-bg-accent': string;
|
|
48
|
+
'--color-bg-inverse': string;
|
|
49
|
+
'--color-text-primary': string;
|
|
50
|
+
'--color-text-secondary': string;
|
|
51
|
+
'--color-text-muted': string;
|
|
52
|
+
'--color-text-accent': string;
|
|
53
|
+
'--color-text-inverse': string;
|
|
54
|
+
'--color-text-on-accent': string;
|
|
55
|
+
'--color-border-default': string;
|
|
56
|
+
'--color-border-subtle': string;
|
|
57
|
+
'--color-border-strong': string;
|
|
58
|
+
'--color-border-accent': string;
|
|
59
|
+
'--color-status-success': string;
|
|
60
|
+
'--color-status-success-bg': string;
|
|
61
|
+
'--color-status-error': string;
|
|
62
|
+
'--color-status-error-bg': string;
|
|
63
|
+
'--color-status-warning': string;
|
|
64
|
+
'--color-status-warning-bg': string;
|
|
65
|
+
'--color-status-info': string;
|
|
66
|
+
'--color-status-info-bg': string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Generate a complete brand CSS string from a palette or seed color.
|
|
70
|
+
* Write the output to a .css file and import it in your globals.css.
|
|
71
|
+
*/
|
|
72
|
+
declare function createBrand(input: CreateBrandInput): string;
|
|
73
|
+
|
|
74
|
+
export { type BrandPalette, type BrandSemanticContract, type CreateBrandInput, DEFAULT_NEUTRAL, type NeutralPalette, createBrand, generatePaletteFromSeed };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/createBrand.ts
|
|
2
|
+
var DEFAULT_NEUTRAL = {
|
|
3
|
+
0: "#ffffff",
|
|
4
|
+
50: "#f8fafc",
|
|
5
|
+
100: "#f1f5f9",
|
|
6
|
+
200: "#e2e8f0",
|
|
7
|
+
300: "#cbd5e1",
|
|
8
|
+
400: "#96a5ba",
|
|
9
|
+
500: "#64748b",
|
|
10
|
+
600: "#475569",
|
|
11
|
+
700: "#334155",
|
|
12
|
+
800: "#1e293b",
|
|
13
|
+
900: "#0f172a",
|
|
14
|
+
950: "#020617"
|
|
15
|
+
};
|
|
16
|
+
function hexToRgb(hex) {
|
|
17
|
+
const h = hex.replace("#", "");
|
|
18
|
+
return [
|
|
19
|
+
parseInt(h.slice(0, 2), 16) / 255,
|
|
20
|
+
parseInt(h.slice(2, 4), 16) / 255,
|
|
21
|
+
parseInt(h.slice(4, 6), 16) / 255
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
function rgbToHsl(r, g, b) {
|
|
25
|
+
const max = Math.max(r, g, b);
|
|
26
|
+
const min = Math.min(r, g, b);
|
|
27
|
+
const l = (max + min) / 2;
|
|
28
|
+
if (max === min) return [0, 0, l * 100];
|
|
29
|
+
const d = max - min;
|
|
30
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
31
|
+
let h;
|
|
32
|
+
switch (max) {
|
|
33
|
+
case r:
|
|
34
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
35
|
+
break;
|
|
36
|
+
case g:
|
|
37
|
+
h = ((b - r) / d + 2) / 6;
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
h = ((r - g) / d + 4) / 6;
|
|
41
|
+
}
|
|
42
|
+
return [h * 360, s * 100, l * 100];
|
|
43
|
+
}
|
|
44
|
+
function hslToHex(h, s, l) {
|
|
45
|
+
const sl = s / 100;
|
|
46
|
+
const ll = l / 100;
|
|
47
|
+
const a = sl * Math.min(ll, 1 - ll);
|
|
48
|
+
const f = (n) => {
|
|
49
|
+
const k = (n + h / 30) % 12;
|
|
50
|
+
const color = ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
51
|
+
return Math.round(255 * color).toString(16).padStart(2, "0");
|
|
52
|
+
};
|
|
53
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
54
|
+
}
|
|
55
|
+
var SHADE_LIGHTNESS = {
|
|
56
|
+
50: 97,
|
|
57
|
+
100: 93,
|
|
58
|
+
200: 86,
|
|
59
|
+
300: 75,
|
|
60
|
+
400: 64,
|
|
61
|
+
500: 53,
|
|
62
|
+
600: 44,
|
|
63
|
+
700: 36,
|
|
64
|
+
800: 28,
|
|
65
|
+
900: 20,
|
|
66
|
+
950: 14
|
|
67
|
+
};
|
|
68
|
+
function generatePaletteFromSeed(seed) {
|
|
69
|
+
const [r, g, b] = hexToRgb(seed);
|
|
70
|
+
const [h, s] = rgbToHsl(r, g, b);
|
|
71
|
+
const entries = Object.entries(SHADE_LIGHTNESS).map(
|
|
72
|
+
([shade, targetL]) => {
|
|
73
|
+
const adjustedS = targetL > 80 ? s * 0.55 : targetL < 25 ? s * 0.75 : s;
|
|
74
|
+
return [Number(shade), hslToHex(h, Math.min(adjustedS, 100), targetL)];
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
return Object.fromEntries(entries);
|
|
78
|
+
}
|
|
79
|
+
var STATUS_LIGHT = {
|
|
80
|
+
"--color-status-success": "#15803d",
|
|
81
|
+
"--color-status-success-bg": "#f0fdf4",
|
|
82
|
+
"--color-status-error": "#b91c1c",
|
|
83
|
+
"--color-status-error-bg": "#fef2f2",
|
|
84
|
+
"--color-status-warning": "#b45309",
|
|
85
|
+
"--color-status-warning-bg": "#fffbeb"
|
|
86
|
+
};
|
|
87
|
+
var STATUS_DARK = {
|
|
88
|
+
"--color-status-success": "#4ade80",
|
|
89
|
+
"--color-status-success-bg": "#052e16",
|
|
90
|
+
"--color-status-error": "#f87171",
|
|
91
|
+
"--color-status-error-bg": "#450a0a",
|
|
92
|
+
"--color-status-warning": "#fbbf24",
|
|
93
|
+
"--color-status-warning-bg": "#451a03"
|
|
94
|
+
};
|
|
95
|
+
function deriveLight() {
|
|
96
|
+
return {
|
|
97
|
+
"--color-bg-primary": "var(--color-neutral-0)",
|
|
98
|
+
"--color-bg-secondary": "var(--color-neutral-50)",
|
|
99
|
+
"--color-bg-surface": "var(--color-neutral-100)",
|
|
100
|
+
"--color-bg-accent": "var(--color-brand-50)",
|
|
101
|
+
"--color-bg-inverse": "var(--color-neutral-900)",
|
|
102
|
+
"--color-text-primary": "var(--color-neutral-900)",
|
|
103
|
+
"--color-text-secondary": "var(--color-neutral-700)",
|
|
104
|
+
"--color-text-muted": "var(--color-neutral-600)",
|
|
105
|
+
"--color-text-accent": "var(--color-brand-700)",
|
|
106
|
+
"--color-text-inverse": "var(--color-neutral-50)",
|
|
107
|
+
"--color-text-on-accent": "var(--color-neutral-0)",
|
|
108
|
+
"--color-border-default": "var(--color-neutral-200)",
|
|
109
|
+
"--color-border-subtle": "var(--color-neutral-100)",
|
|
110
|
+
"--color-border-strong": "var(--color-neutral-300)",
|
|
111
|
+
"--color-border-accent": "var(--color-brand-300)",
|
|
112
|
+
...STATUS_LIGHT,
|
|
113
|
+
"--color-status-info": "var(--color-brand-500)",
|
|
114
|
+
"--color-status-info-bg": "var(--color-brand-50)"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function deriveDark() {
|
|
118
|
+
return {
|
|
119
|
+
"--color-bg-primary": "var(--color-neutral-900)",
|
|
120
|
+
"--color-bg-secondary": "var(--color-neutral-800)",
|
|
121
|
+
"--color-bg-surface": "var(--color-neutral-700)",
|
|
122
|
+
"--color-bg-accent": "var(--color-brand-950)",
|
|
123
|
+
"--color-bg-inverse": "var(--color-neutral-50)",
|
|
124
|
+
"--color-text-primary": "var(--color-neutral-100)",
|
|
125
|
+
"--color-text-secondary": "var(--color-neutral-300)",
|
|
126
|
+
"--color-text-muted": "var(--color-neutral-400)",
|
|
127
|
+
"--color-text-accent": "var(--color-brand-300)",
|
|
128
|
+
"--color-text-inverse": "var(--color-neutral-900)",
|
|
129
|
+
"--color-text-on-accent": "var(--color-neutral-0)",
|
|
130
|
+
"--color-border-default": "var(--color-neutral-700)",
|
|
131
|
+
"--color-border-subtle": "var(--color-neutral-800)",
|
|
132
|
+
"--color-border-strong": "var(--color-neutral-600)",
|
|
133
|
+
"--color-border-accent": "var(--color-brand-700)",
|
|
134
|
+
...STATUS_DARK,
|
|
135
|
+
"--color-status-info": "var(--color-brand-300)",
|
|
136
|
+
"--color-status-info-bg": "var(--color-brand-950)"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function toVars(tokens, indent = " ") {
|
|
140
|
+
return Object.entries(tokens).map(([k, v]) => `${indent}${k}: ${v};`).join("\n");
|
|
141
|
+
}
|
|
142
|
+
function paletteVars(palette) {
|
|
143
|
+
return Object.keys(SHADE_LIGHTNESS).map((shade) => ` --color-brand-${shade}: ${palette[shade]};`).join("\n");
|
|
144
|
+
}
|
|
145
|
+
function neutralVars(neutral) {
|
|
146
|
+
return [0, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950].map((shade) => ` --color-neutral-${shade}: ${neutral[shade]};`).join("\n");
|
|
147
|
+
}
|
|
148
|
+
function createBrand(input) {
|
|
149
|
+
const { name, neutral = DEFAULT_NEUTRAL } = input;
|
|
150
|
+
const palette = "seed" in input ? generatePaletteFromSeed(input.seed) : input.palette;
|
|
151
|
+
const light = deriveLight();
|
|
152
|
+
const dark = deriveDark();
|
|
153
|
+
return `/* ${name} brand \u2014 generated by createBrand (@arun-dev/tokens) */
|
|
154
|
+
/* Re-run createBrand to regenerate after palette changes. */
|
|
155
|
+
|
|
156
|
+
:root {
|
|
157
|
+
${paletteVars(palette)}
|
|
158
|
+
|
|
159
|
+
${neutralVars(neutral)}
|
|
160
|
+
|
|
161
|
+
${toVars(light)}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@media (prefers-color-scheme: dark) {
|
|
165
|
+
:root:not([data-theme='light']) {
|
|
166
|
+
${toVars(dark, " ")}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
[data-theme='dark'] {
|
|
171
|
+
${toVars(dark)}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
[data-theme='light'] {
|
|
175
|
+
${toVars(light)}
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
DEFAULT_NEUTRAL,
|
|
181
|
+
createBrand,
|
|
182
|
+
generatePaletteFromSeed
|
|
183
|
+
};
|