@dowel-ui/themes 0.5.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/index.d.ts +211 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +510 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
- package/src/colour.ts +182 -0
- package/src/figma.ts +287 -0
- package/src/index.ts +49 -0
- package/src/preset.ts +191 -0
- package/src/presets/blue.css +30 -0
- package/src/presets/candy.css +37 -0
- package/src/presets/green.css +34 -0
- package/src/presets/index.css +6 -0
- package/src/presets/indigo.css +36 -0
- package/src/presets/orange.css +34 -0
- package/src/presets/red.css +33 -0
- package/src/tokens.css +20 -0
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ no JavaScript theme object — just CSS custom properties you can read and edit.
|
|
|
20
20
|
/* Brings Tailwind, the raw scales and the semantic layer. */
|
|
21
21
|
@import "@dowel-ui/themes/styles.css";
|
|
22
22
|
|
|
23
|
-
/* Optional — adds the
|
|
23
|
+
/* Optional — adds the thirteen presets. */
|
|
24
24
|
@import "@dowel-ui/themes/presets.css";
|
|
25
25
|
```
|
|
26
26
|
|
|
@@ -43,12 +43,17 @@ file.
|
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## Thirteen presets, all contrast-audited
|
|
47
47
|
|
|
48
|
-
Default · Ocean · Emerald · Violet · Rose · Amber · Monochrome
|
|
48
|
+
Default · Ocean · Emerald · Violet · Rose · Amber · Monochrome · Candy · Indigo ·
|
|
49
|
+
Blue · Red · Orange · Green
|
|
50
|
+
|
|
51
|
+
The last six derive from [SmoothUI](https://smoothui.dev)'s themes, adjusted
|
|
52
|
+
where SmoothUI's exact colour misses a contrast floor — each file says where and
|
|
53
|
+
by how much.
|
|
49
54
|
|
|
50
55
|
Every preset passes **WCAG AA contrast in light and dark**, checked by an audit
|
|
51
|
-
that evaluates
|
|
56
|
+
that evaluates 598 colour pairs across 26 schemes on every commit. When the
|
|
52
57
|
audit first ran it found 88 failures — including that the amber preset could not
|
|
53
58
|
carry dark text on its fill at any usable lightness, so amber became an ochre.
|
|
54
59
|
That is a real trade, made knowingly, rather than a swatch that looks nice and
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,219 @@
|
|
|
1
|
+
//#region src/colour.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* OKLCH → sRGB, and WCAG contrast.
|
|
4
|
+
*
|
|
5
|
+
* The design tokens are authored in OKLCH because its lightness is
|
|
6
|
+
* perceptually even. WCAG contrast, however, is defined on sRGB relative
|
|
7
|
+
* luminance — so checking the palette means actually converting it rather than
|
|
8
|
+
* eyeballing the lightness numbers, which are not the same thing.
|
|
9
|
+
*/
|
|
10
|
+
interface Rgb {
|
|
11
|
+
r: number;
|
|
12
|
+
g: number;
|
|
13
|
+
b: number;
|
|
14
|
+
}
|
|
15
|
+
/** Parses `oklch(L C H)` or `oklch(L C H / A)`. L may be a percentage. */
|
|
16
|
+
declare function parseOklch(value: string): {
|
|
17
|
+
l: number;
|
|
18
|
+
c: number;
|
|
19
|
+
h: number;
|
|
20
|
+
alpha: number;
|
|
21
|
+
} | undefined;
|
|
22
|
+
/** Linear-light sRGB, before gamma encoding. Luminance is defined on these. */
|
|
23
|
+
declare function oklchToLinearRgb(l: number, c: number, h: number): Rgb;
|
|
24
|
+
/** WCAG 2.x relative luminance. */
|
|
25
|
+
declare function luminance(rgb: Rgb): number;
|
|
26
|
+
/**
|
|
27
|
+
* Composites a translucent colour over an opaque one.
|
|
28
|
+
*
|
|
29
|
+
* Several tokens are alpha values over a surface — an overlay, a tinted alert
|
|
30
|
+
* background. Measuring them without compositing would report the contrast of a
|
|
31
|
+
* colour nobody ever sees.
|
|
32
|
+
*/
|
|
33
|
+
declare function composite(foreground: Rgb, alpha: number, background: Rgb): Rgb;
|
|
34
|
+
declare function contrastRatio(a: Rgb, b: Rgb): number;
|
|
35
|
+
/** `oklch(...)` string to linear RGB, composited over `over` if translucent. */
|
|
36
|
+
declare function resolveColour(value: string, over?: Rgb): Rgb | undefined;
|
|
37
|
+
/** Gamma-encoded sRGB, 0–255, from linear-light sRGB. */
|
|
38
|
+
declare function encodeSrgb(rgb: Rgb): {
|
|
39
|
+
r: number;
|
|
40
|
+
g: number;
|
|
41
|
+
b: number;
|
|
42
|
+
};
|
|
43
|
+
/** Linear-light sRGB from gamma-encoded channels, each 0–255. */
|
|
44
|
+
declare function decodeSrgb(r: number, g: number, b: number): Rgb;
|
|
45
|
+
interface Oklch {
|
|
46
|
+
l: number;
|
|
47
|
+
c: number;
|
|
48
|
+
h: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Linear-light sRGB to OKLCH.
|
|
52
|
+
*
|
|
53
|
+
* The inverse of `oklchToLinearRgb`, needed because people pick colours as hex
|
|
54
|
+
* and the tokens are authored in OKLCH. Round-tripping through this is lossy
|
|
55
|
+
* only where the input is outside the OKLCH gamut the tokens use, which a hex
|
|
56
|
+
* value from a colour input never is.
|
|
57
|
+
*/
|
|
58
|
+
declare function linearRgbToOklch(rgb: Rgb): Oklch;
|
|
59
|
+
/** `#rrggbb` (or `#rgb`) to OKLCH. Undefined for anything else. */
|
|
60
|
+
declare function hexToOklch(hex: string): Oklch | undefined;
|
|
61
|
+
declare function oklchToHex({ l, c, h }: Oklch): string;
|
|
62
|
+
/** An OKLCH triple as the tokens write it. */
|
|
63
|
+
declare function formatOklch({ l, c, h }: Oklch): string;
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/preset.d.ts
|
|
66
|
+
/** WCAG 2.2 AA for normal text; a button label is normal text. */
|
|
67
|
+
declare const TEXT_MINIMUM = 4.5;
|
|
68
|
+
/**
|
|
69
|
+
* Text for a saturated background: near-white, or a dark tint of its own hue.
|
|
70
|
+
*
|
|
71
|
+
* Whichever reads better, rather than always white. A light primary — amber,
|
|
72
|
+
* lime — cannot carry white text at 4.5:1 no matter how it is nudged, and the
|
|
73
|
+
* shipped `amber` preset is dark-on-light for exactly this reason.
|
|
74
|
+
*/
|
|
75
|
+
declare function foregroundFor(background: Oklch): Oklch;
|
|
76
|
+
interface PresetMode {
|
|
77
|
+
primary: Oklch;
|
|
78
|
+
primaryHover: Oklch;
|
|
79
|
+
primaryActive: Oklch;
|
|
80
|
+
primaryForeground: Oklch;
|
|
81
|
+
}
|
|
82
|
+
interface DerivedPreset {
|
|
83
|
+
light: PresetMode;
|
|
84
|
+
dark: PresetMode;
|
|
85
|
+
}
|
|
86
|
+
interface DeriveOptions {
|
|
87
|
+
/**
|
|
88
|
+
* Lightness of the dark-mode primary.
|
|
89
|
+
*
|
|
90
|
+
* Overridable because it is the one derived value with no single right
|
|
91
|
+
* answer: how bright a brand reads on near-black is a judgement about the
|
|
92
|
+
* brand, not about contrast.
|
|
93
|
+
*/
|
|
94
|
+
darkLightness?: number;
|
|
95
|
+
}
|
|
96
|
+
declare function derivePreset(input: Oklch, options?: DeriveOptions): DerivedPreset;
|
|
97
|
+
interface ContrastCheck {
|
|
98
|
+
label: string;
|
|
99
|
+
ratio: number;
|
|
100
|
+
minimum: number;
|
|
101
|
+
passes: boolean;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The pairs a derived preset is responsible for.
|
|
105
|
+
*
|
|
106
|
+
* Only these four per mode: every other pair in the system is inherited from
|
|
107
|
+
* the base tokens, which the audit already covers. Reporting the inherited ones
|
|
108
|
+
* would be reporting on something the person cannot change from here.
|
|
109
|
+
*/
|
|
110
|
+
declare function checkPreset(preset: DerivedPreset): ContrastCheck[];
|
|
111
|
+
/**
|
|
112
|
+
* The preset as a stylesheet, in the same shape as the ones that ship.
|
|
113
|
+
*
|
|
114
|
+
* Deliberately the same file format rather than a bespoke export: what comes
|
|
115
|
+
* out of here can be dropped into `packages/themes/src/presets/` unchanged, and
|
|
116
|
+
* is then covered by the same audit as everything else.
|
|
117
|
+
*/
|
|
118
|
+
declare function formatPreset(name: string, preset: DerivedPreset): string;
|
|
119
|
+
/** A name usable as a `data-theme` value. */
|
|
120
|
+
declare function slugify(name: string): string;
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/figma.d.ts
|
|
123
|
+
/**
|
|
124
|
+
* The tokens, in the shape a design tool reads.
|
|
125
|
+
*
|
|
126
|
+
* The CSS is the source of truth and stays that way — nothing here is a second
|
|
127
|
+
* palette to keep in step. This reads the same `tokens.css`, `base.css` and
|
|
128
|
+
* preset files the components consume, resolves every `var()` the way a
|
|
129
|
+
* browser would, and writes the result as W3C Design Tokens (DTCG): the format
|
|
130
|
+
* Tokens Studio for Figma imports directly, and the one every other design
|
|
131
|
+
* tool is converging on.
|
|
132
|
+
*
|
|
133
|
+
* Colours come out as sRGB hex. Figma has no OKLCH; converting here, with the
|
|
134
|
+
* same maths the contrast audit uses, means the swatch a designer sees is the
|
|
135
|
+
* colour a user gets — rather than whatever a tool makes of an `oklch()` string
|
|
136
|
+
* it cannot parse.
|
|
137
|
+
*/
|
|
138
|
+
/** A flat map of custom property name (without the leading `--`) to raw value. */
|
|
139
|
+
type Declarations = Record<string, string>;
|
|
140
|
+
/** A W3C Design Tokens document: nested groups whose leaves carry `$type` and `$value`. */
|
|
141
|
+
interface DesignToken {
|
|
142
|
+
$type: "color" | "dimension" | "fontFamily" | "number";
|
|
143
|
+
$value: string | number | string[];
|
|
144
|
+
$description?: string;
|
|
145
|
+
}
|
|
146
|
+
interface DesignTokenGroup {
|
|
147
|
+
[key: string]: DesignToken | DesignTokenGroup | string | undefined;
|
|
148
|
+
$description?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The declarations inside the first block whose selector is exactly `selector`.
|
|
152
|
+
*
|
|
153
|
+
* Exact, not substring: `.dark` must not match `.dark[data-theme="ocean"]`, and
|
|
154
|
+
* `:root` must not match `:root[dir="rtl"]`. Whitespace inside a value is
|
|
155
|
+
* collapsed, because a font stack written over four lines is one value.
|
|
156
|
+
*/
|
|
157
|
+
declare function parseTokenCss(css: string, selector: string): Declarations;
|
|
158
|
+
/**
|
|
159
|
+
* Resolves `var(--x)` references the way the cascade would.
|
|
160
|
+
*
|
|
161
|
+
* `scopes` are searched in order, so a mode's own declarations shadow the root's
|
|
162
|
+
* and the root's shadow the raw scale — which is exactly what `.dark { --x }`
|
|
163
|
+
* over `:root { --x }` over `@theme { --x }` means. A fallback inside the
|
|
164
|
+
* `var()` is used when nothing defines the name, and an unresolvable reference
|
|
165
|
+
* is left as written rather than silently dropped.
|
|
166
|
+
*/
|
|
167
|
+
declare function resolveReferences(value: string, scopes: Declarations[]): string;
|
|
168
|
+
/** `oklch(...)` to `#rrggbb`, or `#rrggbbaa` when it carries alpha. */
|
|
169
|
+
declare function cssColourToHex(value: string): string | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* A length in px, for the values the scale is written in.
|
|
172
|
+
*
|
|
173
|
+
* Handles the two shapes the tokens use: a plain `rem`/`px`, and the radius
|
|
174
|
+
* ladder's `calc(<rem> * var(--radius-scale, 1))`, which is resolved with the
|
|
175
|
+
* given scale so an exported theme carries the corner radius it was designed
|
|
176
|
+
* with rather than a formula Figma cannot evaluate.
|
|
177
|
+
*/
|
|
178
|
+
declare function cssLengthToPx(value: string, radiusScale?: number): number | undefined;
|
|
179
|
+
/** The four tokens a derived preset owns, as declarations, per mode. */
|
|
180
|
+
declare function presetDeclarations(preset: DerivedPreset): {
|
|
181
|
+
light: Declarations;
|
|
182
|
+
dark: Declarations;
|
|
183
|
+
};
|
|
184
|
+
interface DesignTokensInput {
|
|
185
|
+
/** Named in the document, e.g. "ocean" or a studio preset's slug. */
|
|
186
|
+
name: string;
|
|
187
|
+
/** The `@theme` block of tokens.css: the raw scales. */
|
|
188
|
+
scale: Declarations;
|
|
189
|
+
/** The `:root` block of base.css. */
|
|
190
|
+
light: Declarations;
|
|
191
|
+
/** The `.dark` block of base.css. */
|
|
192
|
+
dark: Declarations;
|
|
193
|
+
/** A preset's overrides, layered over `light` and `dark`. */
|
|
194
|
+
preset?: {
|
|
195
|
+
light: Declarations;
|
|
196
|
+
dark: Declarations;
|
|
197
|
+
};
|
|
198
|
+
/** The `--radius-scale` the theme was designed at. */
|
|
199
|
+
radiusScale?: number;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The whole theme, as a design-tokens document.
|
|
203
|
+
*
|
|
204
|
+
* Three sets: `core` (the raw scales, the same in both modes), `light` and
|
|
205
|
+
* `dark` (the semantic colours, resolved). A designer enables `core` plus one
|
|
206
|
+
* mode, which mirrors exactly how the CSS composes.
|
|
207
|
+
*/
|
|
208
|
+
declare function toDesignTokens(input: DesignTokensInput): DesignTokenGroup;
|
|
209
|
+
//#endregion
|
|
1
210
|
//#region src/index.d.ts
|
|
2
211
|
/**
|
|
3
212
|
* Typed surface of the theme layer. The CSS is the implementation; these
|
|
4
213
|
* constants exist so theme switchers, the docs playground and the future CLI
|
|
5
214
|
* all agree on the same vocabulary.
|
|
6
215
|
*/
|
|
7
|
-
declare const THEME_PRESETS: readonly ["default", "ocean", "emerald", "violet", "rose", "amber", "monochrome"];
|
|
216
|
+
declare const THEME_PRESETS: readonly ["default", "ocean", "emerald", "violet", "rose", "amber", "monochrome", "candy", "indigo", "blue", "red", "orange", "green"];
|
|
8
217
|
type ThemePreset = (typeof THEME_PRESETS)[number];
|
|
9
218
|
declare const COLOR_MODES: readonly ["light", "dark", "system"];
|
|
10
219
|
type ColorMode = (typeof COLOR_MODES)[number];
|
|
@@ -20,5 +229,5 @@ declare const RADIUS_SCALE_PROPERTY = "--radius-scale";
|
|
|
20
229
|
declare function isThemePreset(value: string): value is ThemePreset;
|
|
21
230
|
declare function isColorMode(value: string): value is ColorMode;
|
|
22
231
|
//#endregion
|
|
23
|
-
export { COLOR_MODES, ColorMode, DARK_CLASS, RADIUS_SCALE_PROPERTY, THEME_ATTRIBUTE, THEME_PRESETS, ThemePreset, isColorMode, isThemePreset };
|
|
232
|
+
export { COLOR_MODES, ColorMode, type ContrastCheck, DARK_CLASS, type Declarations, type DeriveOptions, type DerivedPreset, type DesignToken, type DesignTokenGroup, type DesignTokensInput, type Oklch, type PresetMode, RADIUS_SCALE_PROPERTY, type Rgb, TEXT_MINIMUM, THEME_ATTRIBUTE, THEME_PRESETS, ThemePreset, checkPreset, composite, contrastRatio, cssColourToHex, cssLengthToPx, decodeSrgb, derivePreset, encodeSrgb, foregroundFor, formatOklch, formatPreset, hexToOklch, isColorMode, isThemePreset, linearRgbToOklch, luminance, oklchToHex, oklchToLinearRgb, parseOklch, parseTokenCss, presetDeclarations, resolveColour, resolveReferences, slugify, toDesignTokens };
|
|
24
233
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/colour.ts","../src/preset.ts","../src/figma.ts","../src/index.ts"],"mappings":";;;;;;;;;UASiB;EACf;EACA;EACA;;;iBAIc,WACd;EACG;EAAW;EAAW;EAAW;;;iBAkBtB,iBAAiB,WAAW,WAAW,YAAY;;iBAqBnD,UAAU,KAAK;;;;;;;;iBAWf,UAAU,YAAY,KAAK,eAAe,YAAY,MAAM;iBAQ5D,cAAc,GAAG,KAAK,GAAG;;iBASzB,cAAc,eAAe,OAAO,MAAM;;iBAU1C,WAAW,KAAK;EAAQ;EAAW;EAAW;;;iBAW9C,WAAW,WAAW,WAAW,YAAY;UAS5C;EACf;EACA;EACA;;;;;;;;;;iBAWc,iBAAiB,KAAK,MAAM;;iBAqB5B,WAAW,cAAc;iBAqBzB,aAAa,GAAG,GAAG,KAAK;;iBAOxB,cAAc,GAAG,GAAG,KAAK;;;;cC9I5B;;;;;;;;iBAiBG,cAAc,YAAY,QAAQ;UAOjC;EACf,SAAS;EACT,cAAc;EACd,eAAe;EACf,mBAAmB;;UAGJ;EACf,OAAO;EACP,MAAM;;UAGS;;;;;;;;EAQf;;iBAGc,aAAa,OAAO,OAAO,UAAS,gBAAqB;UA+BxD;EACf;EACA;EACA;EACA;;;;;;;;;iBAUc,YAAY,QAAQ,gBAAgB;;;;;;;;iBA0CpC,aAAa,cAAc,QAAQ;;iBAcnC,QAAQ;;;;;;;;;;;;;;;;;;;KCpKZ,eAAe;;UAGV;EACf;EACA;EACA;;UAGe;GACd,cAAc,cAAc;EAC7B;;;;;;;;;iBAec,cAAc,aAAa,mBAAmB;;;;;;;;;;iBA6C9C,kBAAkB,eAAe,QAAQ;;iBAsBzC,eAAe;;;;;;;;;iBAoBf,cAAc,eAAe;;iBA0B7B,mBAAmB,QAAQ;EACzC,OAAO;EACP,MAAM;;UAWS;;EAEf;;EAEA,OAAO;;EAEP,OAAO;;EAEP,MAAM;;EAEN;IAAW,OAAO;IAAc,MAAM;;;EAEtC;;;;;;;;;iBAgFc,eAAe,OAAO,oBAAoB;;;;;;;;cCvN7C;KAgBD,sBAAsB;cAErB;KAED,oBAAoB;;cAGnB;;cAGA;;;;;cAMA;iBAEG,cAAc,gBAAgB,SAAS;iBAIvC,YAAY,gBAAgB,SAAS"}
|