@clhaas/palette-kit 0.1.4 → 0.1.6
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 +38 -1
- package/dist/createTheme.d.ts +4 -0
- package/dist/createTheme.js +51 -2
- package/dist/exporters/toCssVars.d.ts +1 -0
- package/dist/exporters/toCssVars.js +30 -6
- package/dist/exporters/toReactNative.d.ts +41 -17
- package/dist/exporters/toReactNative.js +9 -2
- package/dist/exporters/toTailwind.d.ts +1 -0
- package/dist/exporters/toTailwind.js +23 -2
- package/dist/exporters/toTs.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/overlays/generateOverlayScale.d.ts +2 -0
- package/dist/overlays/generateOverlayScale.js +34 -0
- package/dist/text/generateTextScale.d.ts +8 -0
- package/dist/text/generateTextScale.js +18 -0
- package/dist/types.d.ts +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ pnpm add @clhaas/palette-kit
|
|
|
22
22
|
|
|
23
23
|
- 12-step scale (light/dark) from a seed.
|
|
24
24
|
- Semantic tokens for UI (`radix-like-ui` preset).
|
|
25
|
-
- Alpha
|
|
25
|
+
- Alpha scales per palette slot (chromatic alpha).
|
|
26
|
+
- Overlay scales (black/white alpha).
|
|
27
|
+
- Deterministic text scales for light/dark backgrounds.
|
|
26
28
|
- Exporters for TS, JSON, CSS vars, Tailwind, and React Native.
|
|
27
29
|
- Auto anchor selection per mode (light/dark), overridable via `anchorStep`.
|
|
28
30
|
- Basic contrast and gamut diagnostics.
|
|
@@ -41,6 +43,10 @@ const theme = createTheme({
|
|
|
41
43
|
danger: { source: "seed", value: "#ef4444" },
|
|
42
44
|
},
|
|
43
45
|
tokens: { preset: "radix-like-ui" },
|
|
46
|
+
text: {
|
|
47
|
+
darkBase: "#1C1C1E",
|
|
48
|
+
lightBase: "#F5F5F7",
|
|
49
|
+
},
|
|
44
50
|
p3: true,
|
|
45
51
|
});
|
|
46
52
|
```
|
|
@@ -95,6 +101,10 @@ export default {
|
|
|
95
101
|
},
|
|
96
102
|
tokens: { preset: "radix-like-ui" },
|
|
97
103
|
alpha: { enabled: true },
|
|
104
|
+
text: {
|
|
105
|
+
darkBase: "#1C1C1E",
|
|
106
|
+
lightBase: "#F5F5F7",
|
|
107
|
+
},
|
|
98
108
|
p3: true,
|
|
99
109
|
};
|
|
100
110
|
```
|
|
@@ -124,6 +134,31 @@ const tokens: ThemeTokenMap = theme.tokens.light;
|
|
|
124
134
|
const tokenName: ThemeTokenName = "bg.app";
|
|
125
135
|
```
|
|
126
136
|
|
|
137
|
+
## Alpha, overlay, and text examples
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const accentAlpha = theme.alpha?.accent.light[5];
|
|
141
|
+
const overlay = theme.overlay.black[9];
|
|
142
|
+
const textOnLight = theme.tokens.light["text.dark.primary"];
|
|
143
|
+
const textOnDark = theme.tokens.dark["text.light.primary"];
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Migration note (alpha)
|
|
147
|
+
|
|
148
|
+
Alpha scales are now generated per palette slot. If you previously used:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
theme.alpha?.light[5];
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Update to:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
theme.alpha?.accent.light[5];
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
CSS variables were also updated from `--pk-alpha-<step>` to `--pk-alpha-<slot>-<step>`.
|
|
161
|
+
|
|
127
162
|
## React Native + Expo
|
|
128
163
|
|
|
129
164
|
Use the React Native exporter and `useColorScheme()`:
|
|
@@ -164,6 +199,8 @@ Note: React Native does not support `color(display-p3 ...)` strings as drop-in c
|
|
|
164
199
|
- `docs/tokens.md`
|
|
165
200
|
- `docs/contrast.md`
|
|
166
201
|
- `docs/alpha.md`
|
|
202
|
+
- `docs/text.md`
|
|
203
|
+
- `docs/overlays.md`
|
|
167
204
|
- `docs/Why.md`
|
|
168
205
|
- `docs/spec-implementation.md`
|
|
169
206
|
- `docs/plan-tests.md`
|
package/dist/createTheme.d.ts
CHANGED
package/dist/createTheme.js
CHANGED
|
@@ -3,6 +3,8 @@ import { onSolidTextTokens } from "./contrast/onSolid.js";
|
|
|
3
3
|
import { adjustTextColor } from "./contrast/solveText.js";
|
|
4
4
|
import { analyzeTheme } from "./diagnostics/analyzeTheme.js";
|
|
5
5
|
import { generateScale } from "./generateScale.js";
|
|
6
|
+
import { generateOverlayScale } from "./overlays/generateOverlayScale.js";
|
|
7
|
+
import { generateTextScale } from "./text/generateTextScale.js";
|
|
6
8
|
import { buildPresetTokens } from "./tokens/presetRadixLikeUi.js";
|
|
7
9
|
export function createTheme(options) {
|
|
8
10
|
const includeP3 = options.p3 ?? false;
|
|
@@ -64,6 +66,48 @@ export function createTheme(options) {
|
|
|
64
66
|
tokens.dark["onSolid.primary"] = darkOnSolid.primary;
|
|
65
67
|
tokens.dark["onSolid.secondary"] = darkOnSolid.secondary;
|
|
66
68
|
tokens.dark["onSolid.disabled"] = darkOnSolid.disabled;
|
|
69
|
+
const textScale = generateTextScale({
|
|
70
|
+
darkBase: options.text?.darkBase,
|
|
71
|
+
lightBase: options.text?.lightBase,
|
|
72
|
+
});
|
|
73
|
+
const darkTextSteps = {
|
|
74
|
+
primary: 12,
|
|
75
|
+
secondary: 10,
|
|
76
|
+
tertiary: 9,
|
|
77
|
+
disabled: 8,
|
|
78
|
+
};
|
|
79
|
+
const lightTextSteps = {
|
|
80
|
+
primary: 1,
|
|
81
|
+
secondary: 3,
|
|
82
|
+
tertiary: 4,
|
|
83
|
+
disabled: 5,
|
|
84
|
+
};
|
|
85
|
+
for (const [step, value] of Object.entries(textScale.dark)) {
|
|
86
|
+
tokens.light[`text.dark.${step}`] = value;
|
|
87
|
+
tokens.dark[`text.dark.${step}`] = value;
|
|
88
|
+
}
|
|
89
|
+
for (const [step, value] of Object.entries(textScale.light)) {
|
|
90
|
+
tokens.light[`text.light.${step}`] = value;
|
|
91
|
+
tokens.dark[`text.light.${step}`] = value;
|
|
92
|
+
}
|
|
93
|
+
for (const [key, step] of Object.entries(darkTextSteps)) {
|
|
94
|
+
const token = `text.dark.${key}`;
|
|
95
|
+
tokens.light[token] = textScale.dark[step];
|
|
96
|
+
tokens.dark[token] = textScale.dark[step];
|
|
97
|
+
}
|
|
98
|
+
for (const [key, step] of Object.entries(lightTextSteps)) {
|
|
99
|
+
const token = `text.light.${key}`;
|
|
100
|
+
tokens.light[token] = textScale.light[step];
|
|
101
|
+
tokens.dark[token] = textScale.light[step];
|
|
102
|
+
}
|
|
103
|
+
tokens.light["text.primary"] = textScale.dark[darkTextSteps.primary];
|
|
104
|
+
tokens.light["text.secondary"] = textScale.dark[darkTextSteps.secondary];
|
|
105
|
+
tokens.light["text.tertiary"] = textScale.dark[darkTextSteps.tertiary];
|
|
106
|
+
tokens.light["text.disabled"] = textScale.dark[darkTextSteps.disabled];
|
|
107
|
+
tokens.dark["text.primary"] = textScale.light[lightTextSteps.primary];
|
|
108
|
+
tokens.dark["text.secondary"] = textScale.light[lightTextSteps.secondary];
|
|
109
|
+
tokens.dark["text.tertiary"] = textScale.light[lightTextSteps.tertiary];
|
|
110
|
+
tokens.dark["text.disabled"] = textScale.light[lightTextSteps.disabled];
|
|
67
111
|
if (options.tokens?.overrides?.light) {
|
|
68
112
|
Object.assign(tokens.light, options.tokens.overrides.light);
|
|
69
113
|
}
|
|
@@ -76,13 +120,18 @@ export function createTheme(options) {
|
|
|
76
120
|
light: options.alpha?.background?.light ?? "#ffffff",
|
|
77
121
|
dark: options.alpha?.background?.dark ?? "#111111",
|
|
78
122
|
};
|
|
79
|
-
alpha =
|
|
123
|
+
alpha = Object.fromEntries(Object.entries(scales).map(([slot, scale]) => [
|
|
124
|
+
slot,
|
|
125
|
+
generateAlphaScale(scale.light[9], background),
|
|
126
|
+
]));
|
|
80
127
|
}
|
|
81
|
-
const
|
|
128
|
+
const overlay = generateOverlayScale();
|
|
129
|
+
const diagnostics = analyzeTheme({ scales, tokens, alpha, overlay });
|
|
82
130
|
return {
|
|
83
131
|
scales,
|
|
84
132
|
tokens,
|
|
85
133
|
alpha,
|
|
134
|
+
overlay,
|
|
86
135
|
diagnostics,
|
|
87
136
|
};
|
|
88
137
|
}
|
|
@@ -7,8 +7,11 @@ function tokenToCssVar(token, prefix) {
|
|
|
7
7
|
function scaleToCssVar(slot, step, prefix) {
|
|
8
8
|
return `--${prefix}-scale-${slot}-${step}`;
|
|
9
9
|
}
|
|
10
|
-
function alphaToCssVar(step, prefix) {
|
|
11
|
-
return `--${prefix}-alpha-${step}`;
|
|
10
|
+
function alphaToCssVar(slot, step, prefix) {
|
|
11
|
+
return `--${prefix}-alpha-${slot}-${step}`;
|
|
12
|
+
}
|
|
13
|
+
function overlayToCssVar(color, step, prefix) {
|
|
14
|
+
return `--${prefix}-overlay-${color}-${step}`;
|
|
12
15
|
}
|
|
13
16
|
function hexToP3(value) {
|
|
14
17
|
return new Color(value).to("p3").toString({ format: "color" });
|
|
@@ -18,6 +21,7 @@ export function toCssVars(theme, options) {
|
|
|
18
21
|
const includeTokens = options?.includeTokens ?? true;
|
|
19
22
|
const includeScales = options?.includeScales ?? true;
|
|
20
23
|
const includeAlpha = options?.includeAlpha ?? true;
|
|
24
|
+
const includeOverlays = options?.includeOverlays ?? true;
|
|
21
25
|
const includeP3 = options?.includeP3 ?? false;
|
|
22
26
|
const lightSelector = options?.lightSelector ?? ":root";
|
|
23
27
|
const darkSelector = options?.darkSelector ?? ".dark";
|
|
@@ -38,8 +42,18 @@ export function toCssVars(theme, options) {
|
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
if (includeAlpha && theme.alpha) {
|
|
41
|
-
for (const [
|
|
42
|
-
|
|
45
|
+
for (const [slot, scale] of Object.entries(theme.alpha)) {
|
|
46
|
+
for (const [step, value] of Object.entries(scale[mode])) {
|
|
47
|
+
lines.push(` ${alphaToCssVar(slot, Number(step), prefix)}: ${value};`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (includeOverlays) {
|
|
52
|
+
for (const [step, value] of Object.entries(theme.overlay.black)) {
|
|
53
|
+
lines.push(` ${overlayToCssVar("black", Number(step), prefix)}: ${value};`);
|
|
54
|
+
}
|
|
55
|
+
for (const [step, value] of Object.entries(theme.overlay.white)) {
|
|
56
|
+
lines.push(` ${overlayToCssVar("white", Number(step), prefix)}: ${value};`);
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
lines.push("}");
|
|
@@ -69,8 +83,18 @@ export function toCssVars(theme, options) {
|
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
if (includeAlpha && theme.alpha) {
|
|
72
|
-
for (const [
|
|
73
|
-
|
|
86
|
+
for (const [slot, scale] of Object.entries(theme.alpha)) {
|
|
87
|
+
for (const [step, value] of Object.entries(scale[mode])) {
|
|
88
|
+
lines.push(` ${alphaToCssVar(slot, Number(step), prefix)}: ${hexToP3(value)};`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (includeOverlays) {
|
|
93
|
+
for (const [step, value] of Object.entries(theme.overlay.black)) {
|
|
94
|
+
lines.push(` ${overlayToCssVar("black", Number(step), prefix)}: ${hexToP3(value)};`);
|
|
95
|
+
}
|
|
96
|
+
for (const [step, value] of Object.entries(theme.overlay.white)) {
|
|
97
|
+
lines.push(` ${overlayToCssVar("white", Number(step), prefix)}: ${hexToP3(value)};`);
|
|
74
98
|
}
|
|
75
99
|
}
|
|
76
100
|
lines.push(" }");
|
|
@@ -3,28 +3,52 @@ type ReactNativeOptions = {
|
|
|
3
3
|
includeTokens?: boolean;
|
|
4
4
|
includeScales?: boolean;
|
|
5
5
|
includeAlpha?: boolean;
|
|
6
|
+
includeOverlays?: boolean;
|
|
6
7
|
includeP3?: boolean;
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
type Mode = "light" | "dark";
|
|
10
|
+
type ModeTokens<T extends Theme, O extends ReactNativeOptions, M extends Mode> = O extends {
|
|
11
|
+
includeTokens: false;
|
|
12
|
+
} ? Record<string, never> : T["tokens"][M];
|
|
13
|
+
type ModeScales<T extends Theme, O extends ReactNativeOptions, M extends Mode> = O extends {
|
|
14
|
+
includeScales: false;
|
|
15
|
+
} ? Record<string, never> : {
|
|
16
|
+
[K in keyof T["scales"]]: T["scales"][K][M];
|
|
17
|
+
};
|
|
18
|
+
type ThemeAlpha<T extends Theme> = T["alpha"] extends undefined ? undefined : T["alpha"];
|
|
19
|
+
type ModeAlpha<T extends Theme, O extends ReactNativeOptions, M extends Mode> = O extends {
|
|
20
|
+
includeAlpha: false;
|
|
21
|
+
} ? undefined : ThemeAlpha<T> extends undefined ? undefined : ThemeAlpha<T> extends Record<string, {
|
|
22
|
+
light: infer L;
|
|
23
|
+
dark: infer D;
|
|
24
|
+
}> ? M extends "light" ? Record<string, L> : Record<string, D> : undefined;
|
|
25
|
+
type ModeOverlays<T extends Theme, O extends ReactNativeOptions> = O extends {
|
|
26
|
+
includeOverlays: false;
|
|
27
|
+
} ? undefined : T["overlay"];
|
|
28
|
+
type ModeP3<T extends Theme, O extends ReactNativeOptions, M extends Mode> = O extends {
|
|
29
|
+
includeP3: true;
|
|
30
|
+
} ? Partial<{
|
|
31
|
+
[K in keyof T["scales"]]: T["scales"][K] extends {
|
|
32
|
+
p3?: {
|
|
33
|
+
light: infer L;
|
|
34
|
+
dark: infer D;
|
|
13
35
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
} ? M extends "light" ? L : D : never;
|
|
37
|
+
}> : undefined;
|
|
38
|
+
type ReactNativeTheme<T extends Theme, O extends ReactNativeOptions> = {
|
|
39
|
+
overlay: ModeOverlays<T, O>;
|
|
40
|
+
light: {
|
|
41
|
+
tokens: ModeTokens<T, O, "light">;
|
|
42
|
+
scales: ModeScales<T, O, "light">;
|
|
43
|
+
alpha: ModeAlpha<T, O, "light">;
|
|
44
|
+
p3: ModeP3<T, O, "light">;
|
|
18
45
|
};
|
|
19
46
|
dark: {
|
|
20
|
-
tokens:
|
|
21
|
-
scales:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
alpha: Record<import("../types.js").Step, `#${string}`> | undefined;
|
|
25
|
-
p3: {
|
|
26
|
-
[k: string]: Record<import("../types.js").Step, string> | undefined;
|
|
27
|
-
} | undefined;
|
|
47
|
+
tokens: ModeTokens<T, O, "dark">;
|
|
48
|
+
scales: ModeScales<T, O, "dark">;
|
|
49
|
+
alpha: ModeAlpha<T, O, "dark">;
|
|
50
|
+
p3: ModeP3<T, O, "dark">;
|
|
28
51
|
};
|
|
29
52
|
};
|
|
53
|
+
export declare function toReactNative<T extends Theme, O extends ReactNativeOptions = ReactNativeOptions>(theme: T, options?: O): ReactNativeTheme<T, O>;
|
|
30
54
|
export {};
|
|
@@ -2,6 +2,7 @@ export function toReactNative(theme, options) {
|
|
|
2
2
|
const includeTokens = options?.includeTokens ?? true;
|
|
3
3
|
const includeScales = options?.includeScales ?? true;
|
|
4
4
|
const includeAlpha = options?.includeAlpha ?? true;
|
|
5
|
+
const includeOverlays = options?.includeOverlays ?? true;
|
|
5
6
|
const includeP3 = options?.includeP3 ?? false;
|
|
6
7
|
function buildMode(mode) {
|
|
7
8
|
const scales = includeScales
|
|
@@ -12,14 +13,20 @@ export function toReactNative(theme, options) {
|
|
|
12
13
|
.filter(([, scale]) => scale.p3?.[mode])
|
|
13
14
|
.map(([slot, scale]) => [slot, scale.p3?.[mode]]))
|
|
14
15
|
: undefined;
|
|
16
|
+
const alpha = includeAlpha
|
|
17
|
+
? (theme.alpha
|
|
18
|
+
? Object.fromEntries(Object.entries(theme.alpha).map(([slot, scale]) => [slot, scale[mode]]))
|
|
19
|
+
: undefined)
|
|
20
|
+
: undefined;
|
|
15
21
|
return {
|
|
16
|
-
tokens: includeTokens ? theme.tokens[mode] : {},
|
|
22
|
+
tokens: (includeTokens ? theme.tokens[mode] : {}),
|
|
17
23
|
scales,
|
|
18
|
-
alpha
|
|
24
|
+
alpha,
|
|
19
25
|
p3,
|
|
20
26
|
};
|
|
21
27
|
}
|
|
22
28
|
return {
|
|
29
|
+
overlay: (includeOverlays ? theme.overlay : undefined),
|
|
23
30
|
light: buildMode("light"),
|
|
24
31
|
dark: buildMode("dark"),
|
|
25
32
|
};
|
|
@@ -36,8 +36,25 @@ function alphaToNested(alpha, mode) {
|
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
38
|
const output = {};
|
|
39
|
-
for (const [
|
|
40
|
-
|
|
39
|
+
for (const [slot, scale] of Object.entries(alpha)) {
|
|
40
|
+
const stepMap = {};
|
|
41
|
+
for (const [step, value] of Object.entries(scale[mode])) {
|
|
42
|
+
stepMap[String(step)] = value;
|
|
43
|
+
}
|
|
44
|
+
output[slot] = stepMap;
|
|
45
|
+
}
|
|
46
|
+
return output;
|
|
47
|
+
}
|
|
48
|
+
function overlaysToNested(overlays) {
|
|
49
|
+
const output = {
|
|
50
|
+
black: {},
|
|
51
|
+
white: {},
|
|
52
|
+
};
|
|
53
|
+
for (const [step, value] of Object.entries(overlays.black)) {
|
|
54
|
+
output.black[String(step)] = value;
|
|
55
|
+
}
|
|
56
|
+
for (const [step, value] of Object.entries(overlays.white)) {
|
|
57
|
+
output.white[String(step)] = value;
|
|
41
58
|
}
|
|
42
59
|
return output;
|
|
43
60
|
}
|
|
@@ -46,6 +63,7 @@ export function toTailwind(theme, options) {
|
|
|
46
63
|
const includeTokens = options?.includeTokens ?? true;
|
|
47
64
|
const includeScales = options?.includeScales ?? false;
|
|
48
65
|
const includeAlpha = options?.includeAlpha ?? false;
|
|
66
|
+
const includeOverlays = options?.includeOverlays ?? false;
|
|
49
67
|
const includeP3 = options?.includeP3 ?? false;
|
|
50
68
|
function buildModeTokens(modeKey, useP3) {
|
|
51
69
|
const colors = {};
|
|
@@ -58,6 +76,9 @@ export function toTailwind(theme, options) {
|
|
|
58
76
|
if (includeAlpha && theme.alpha) {
|
|
59
77
|
colors.alpha = alphaToNested(theme.alpha, modeKey);
|
|
60
78
|
}
|
|
79
|
+
if (includeOverlays) {
|
|
80
|
+
colors.overlay = overlaysToNested(theme.overlay);
|
|
81
|
+
}
|
|
61
82
|
return colors;
|
|
62
83
|
}
|
|
63
84
|
const colors = {};
|
package/dist/exporters/toTs.js
CHANGED
|
@@ -32,6 +32,7 @@ function typeExports() {
|
|
|
32
32
|
'export type ThemeScaleName = keyof Theme["scales"];',
|
|
33
33
|
'export type ThemeTokenName = keyof Theme["tokens"]["light"];',
|
|
34
34
|
'export type ThemeTokenMap = Theme["tokens"]["light"];',
|
|
35
|
+
'export type ThemeOverlay = Theme["overlay"];',
|
|
35
36
|
"",
|
|
36
37
|
].join("\n");
|
|
37
38
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,4 +15,6 @@ export { toTailwind } from "./exporters/toTailwind.js";
|
|
|
15
15
|
export { toTs, toTsWithMode } from "./exporters/toTs.js";
|
|
16
16
|
export type { AnchorStepOption, AutoAnchorModeOptions, AutoAnchorOptions, GenerateScaleOptions, SeedNormalizeOptions, SeedNormalizeRange, } from "./generateScale.js";
|
|
17
17
|
export { generateScale } from "./generateScale.js";
|
|
18
|
-
export
|
|
18
|
+
export { generateOverlayScale } from "./overlays/generateOverlayScale.js";
|
|
19
|
+
export { generateTextScale } from "./text/generateTextScale.js";
|
|
20
|
+
export type { AlphaScale, AlphaScales, ColorHex, ColorSource, OklchColor, OverlayScale, RadixSeedName, Scale, ScaleColorMode, ScaleDiagnostics, Step, TemplateId, TextScale, Theme, ThemeColorMode, ThemeDiagnostics, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -13,3 +13,5 @@ export { toReactNative } from "./exporters/toReactNative.js";
|
|
|
13
13
|
export { toTailwind } from "./exporters/toTailwind.js";
|
|
14
14
|
export { toTs, toTsWithMode } from "./exporters/toTs.js";
|
|
15
15
|
export { generateScale } from "./generateScale.js";
|
|
16
|
+
export { generateOverlayScale } from "./overlays/generateOverlayScale.js";
|
|
17
|
+
export { generateTextScale } from "./text/generateTextScale.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Color from "colorjs.io";
|
|
2
|
+
const steps = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
3
|
+
const alphaCurve = {
|
|
4
|
+
1: 0.05,
|
|
5
|
+
2: 0.1,
|
|
6
|
+
3: 0.15,
|
|
7
|
+
4: 0.2,
|
|
8
|
+
5: 0.3,
|
|
9
|
+
6: 0.4,
|
|
10
|
+
7: 0.5,
|
|
11
|
+
8: 0.6,
|
|
12
|
+
9: 0.7,
|
|
13
|
+
10: 0.8,
|
|
14
|
+
11: 0.9,
|
|
15
|
+
12: 0.95,
|
|
16
|
+
};
|
|
17
|
+
function mixWithAlpha(foreground, alpha) {
|
|
18
|
+
const color = new Color(foreground).to("srgb");
|
|
19
|
+
const [r, g, b] = color.coords;
|
|
20
|
+
const hex = new Color({ space: "srgb", coords: [r, g, b], alpha }).toString({
|
|
21
|
+
format: "hex",
|
|
22
|
+
});
|
|
23
|
+
return hex;
|
|
24
|
+
}
|
|
25
|
+
export function generateOverlayScale() {
|
|
26
|
+
const black = {};
|
|
27
|
+
const white = {};
|
|
28
|
+
for (const step of steps) {
|
|
29
|
+
const alpha = alphaCurve[step];
|
|
30
|
+
black[step] = mixWithAlpha("#000000", alpha);
|
|
31
|
+
white[step] = mixWithAlpha("#ffffff", alpha);
|
|
32
|
+
}
|
|
33
|
+
return { black, white };
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { compressToSrgb, hexToOklch, oklchToHex } from "../engine/oklch.js";
|
|
2
|
+
const steps = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
3
|
+
export function generateTextScale(options) {
|
|
4
|
+
const darkBase = options?.darkBase ?? "#1C1C1E";
|
|
5
|
+
const lightBase = options?.lightBase ?? "#F5F5F7";
|
|
6
|
+
const deltaL = options?.deltaL ?? 0.055;
|
|
7
|
+
const darkBaseOklch = hexToOklch(darkBase);
|
|
8
|
+
const lightBaseOklch = hexToOklch(lightBase);
|
|
9
|
+
const dark = {};
|
|
10
|
+
const light = {};
|
|
11
|
+
for (const step of steps) {
|
|
12
|
+
const darkL = Math.min(darkBaseOklch.l + (12 - step) * deltaL, 0.92);
|
|
13
|
+
const lightL = Math.max(lightBaseOklch.l - (step - 1) * deltaL, 0.12);
|
|
14
|
+
dark[step] = oklchToHex(compressToSrgb({ ...darkBaseOklch, l: darkL }));
|
|
15
|
+
light[step] = oklchToHex(compressToSrgb({ ...lightBaseOklch, l: lightL }));
|
|
16
|
+
}
|
|
17
|
+
return { dark, light };
|
|
18
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -35,6 +35,15 @@ export type AlphaScale = {
|
|
|
35
35
|
light: Record<Step, ColorHex>;
|
|
36
36
|
dark: Record<Step, ColorHex>;
|
|
37
37
|
};
|
|
38
|
+
export type AlphaScales = Record<string, AlphaScale>;
|
|
39
|
+
export type OverlayScale = {
|
|
40
|
+
black: Record<Step, ColorHex>;
|
|
41
|
+
white: Record<Step, ColorHex>;
|
|
42
|
+
};
|
|
43
|
+
export type TextScale = {
|
|
44
|
+
dark: Record<Step, ColorHex>;
|
|
45
|
+
light: Record<Step, ColorHex>;
|
|
46
|
+
};
|
|
38
47
|
export type ThemeDiagnostics = {
|
|
39
48
|
contrast: Record<string, number>;
|
|
40
49
|
outOfGamutCount: number;
|
|
@@ -46,7 +55,8 @@ export type Theme = {
|
|
|
46
55
|
light: Record<string, ColorHex>;
|
|
47
56
|
dark: Record<string, ColorHex>;
|
|
48
57
|
};
|
|
49
|
-
alpha?:
|
|
58
|
+
alpha?: AlphaScales;
|
|
59
|
+
overlay: OverlayScale;
|
|
50
60
|
diagnostics?: ThemeDiagnostics;
|
|
51
61
|
};
|
|
52
62
|
export type ThemeColorMode = Omit<Theme, "scales"> & {
|