@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/src/preset.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { contrastRatio, formatOklch, oklchToLinearRgb, type Oklch } from "./colour";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deriving a theme preset from a single colour.
|
|
5
|
+
*
|
|
6
|
+
* A preset in this system reassigns four tokens per mode and inherits
|
|
7
|
+
* everything else, so building one is not a palette exercise — it is picking a
|
|
8
|
+
* primary and then answering three questions the shipped presets already
|
|
9
|
+
* answer: what it looks like pressed, what it looks like in dark mode, and what
|
|
10
|
+
* text can be read on it.
|
|
11
|
+
*
|
|
12
|
+
* The deltas below are read off the presets that ship. They are not arbitrary:
|
|
13
|
+
* every one of those passes the contrast audit in both modes, so starting from
|
|
14
|
+
* the same relationships means a derived preset starts somewhere that works.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Lightness step from the base colour to its hover state, in light mode. */
|
|
18
|
+
const LIGHT_HOVER_DELTA = -0.045;
|
|
19
|
+
/** And to its active state, which is a press and reads as further down. */
|
|
20
|
+
const LIGHT_ACTIVE_DELTA = -0.083;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Dark mode raises lightness and drops chroma.
|
|
24
|
+
*
|
|
25
|
+
* A colour that reads as saturated on white reads as glaring on near-black, and
|
|
26
|
+
* one dark enough to sit on white disappears into the background.
|
|
27
|
+
*/
|
|
28
|
+
const DARK_LIGHTNESS_DELTA = 0.115;
|
|
29
|
+
const DARK_CHROMA_DELTA = -0.015;
|
|
30
|
+
const DARK_HOVER_DELTA = 0.045;
|
|
31
|
+
const DARK_ACTIVE_DELTA = -0.045;
|
|
32
|
+
|
|
33
|
+
/** The near-white the shipped presets use for text on a saturated colour. */
|
|
34
|
+
const LIGHT_FOREGROUND: Oklch = { l: 0.985, c: 0.002, h: 265 };
|
|
35
|
+
|
|
36
|
+
/** WCAG 2.2 AA for normal text; a button label is normal text. */
|
|
37
|
+
export const TEXT_MINIMUM = 4.5;
|
|
38
|
+
|
|
39
|
+
function clampLightness(value: number): number {
|
|
40
|
+
return Math.min(0.99, Math.max(0.01, value));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ratio(a: Oklch, b: Oklch): number {
|
|
44
|
+
return contrastRatio(oklchToLinearRgb(a.l, a.c, a.h), oklchToLinearRgb(b.l, b.c, b.h));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Text for a saturated background: near-white, or a dark tint of its own hue.
|
|
49
|
+
*
|
|
50
|
+
* Whichever reads better, rather than always white. A light primary — amber,
|
|
51
|
+
* lime — cannot carry white text at 4.5:1 no matter how it is nudged, and the
|
|
52
|
+
* shipped `amber` preset is dark-on-light for exactly this reason.
|
|
53
|
+
*/
|
|
54
|
+
export function foregroundFor(background: Oklch): Oklch {
|
|
55
|
+
const dark: Oklch = { l: 0.155, c: 0.03, h: background.h };
|
|
56
|
+
return ratio(LIGHT_FOREGROUND, background) >= ratio(dark, background)
|
|
57
|
+
? LIGHT_FOREGROUND
|
|
58
|
+
: dark;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PresetMode {
|
|
62
|
+
primary: Oklch;
|
|
63
|
+
primaryHover: Oklch;
|
|
64
|
+
primaryActive: Oklch;
|
|
65
|
+
primaryForeground: Oklch;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DerivedPreset {
|
|
69
|
+
light: PresetMode;
|
|
70
|
+
dark: PresetMode;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DeriveOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Lightness of the dark-mode primary.
|
|
76
|
+
*
|
|
77
|
+
* Overridable because it is the one derived value with no single right
|
|
78
|
+
* answer: how bright a brand reads on near-black is a judgement about the
|
|
79
|
+
* brand, not about contrast.
|
|
80
|
+
*/
|
|
81
|
+
darkLightness?: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function derivePreset(input: Oklch, options: DeriveOptions = {}): DerivedPreset {
|
|
85
|
+
// Clamped on the way in as well as on the way out. Every value this returns
|
|
86
|
+
// is one it is responsible for, including the one it was handed: a preset
|
|
87
|
+
// built on pure black is not a preset anyone can use, and passing it through
|
|
88
|
+
// untouched would mean the only unusable value in the output is the one that
|
|
89
|
+
// was never checked.
|
|
90
|
+
const primary: Oklch = { ...input, l: clampLightness(input.l), c: Math.max(0, input.c) };
|
|
91
|
+
|
|
92
|
+
const light: PresetMode = {
|
|
93
|
+
primary,
|
|
94
|
+
primaryHover: { ...primary, l: clampLightness(primary.l + LIGHT_HOVER_DELTA) },
|
|
95
|
+
primaryActive: { ...primary, l: clampLightness(primary.l + LIGHT_ACTIVE_DELTA) },
|
|
96
|
+
primaryForeground: foregroundFor(primary),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const darkPrimary: Oklch = {
|
|
100
|
+
l: clampLightness(options.darkLightness ?? primary.l + DARK_LIGHTNESS_DELTA),
|
|
101
|
+
c: Math.max(0, primary.c + DARK_CHROMA_DELTA),
|
|
102
|
+
h: primary.h,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const dark: PresetMode = {
|
|
106
|
+
primary: darkPrimary,
|
|
107
|
+
primaryHover: { ...darkPrimary, l: clampLightness(darkPrimary.l + DARK_HOVER_DELTA) },
|
|
108
|
+
primaryActive: { ...darkPrimary, l: clampLightness(darkPrimary.l + DARK_ACTIVE_DELTA) },
|
|
109
|
+
primaryForeground: foregroundFor(darkPrimary),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return { light, dark };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ContrastCheck {
|
|
116
|
+
label: string;
|
|
117
|
+
ratio: number;
|
|
118
|
+
minimum: number;
|
|
119
|
+
passes: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The pairs a derived preset is responsible for.
|
|
124
|
+
*
|
|
125
|
+
* Only these four per mode: every other pair in the system is inherited from
|
|
126
|
+
* the base tokens, which the audit already covers. Reporting the inherited ones
|
|
127
|
+
* would be reporting on something the person cannot change from here.
|
|
128
|
+
*/
|
|
129
|
+
export function checkPreset(preset: DerivedPreset): ContrastCheck[] {
|
|
130
|
+
const checks: ContrastCheck[] = [];
|
|
131
|
+
|
|
132
|
+
for (const [mode, values] of [
|
|
133
|
+
["Light", preset.light],
|
|
134
|
+
["Dark", preset.dark],
|
|
135
|
+
] as const) {
|
|
136
|
+
for (const [state, background] of [
|
|
137
|
+
["primary", values.primary],
|
|
138
|
+
["primary-hover", values.primaryHover],
|
|
139
|
+
["primary-active", values.primaryActive],
|
|
140
|
+
] as const) {
|
|
141
|
+
checks.push({
|
|
142
|
+
label: `${mode}: primary-foreground on ${state}`,
|
|
143
|
+
ratio: ratio(values.primaryForeground, background),
|
|
144
|
+
minimum: TEXT_MINIMUM,
|
|
145
|
+
passes: ratio(values.primaryForeground, background) >= TEXT_MINIMUM,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return checks;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function block(selector: string, mode: PresetMode): string {
|
|
154
|
+
return [
|
|
155
|
+
`${selector} {`,
|
|
156
|
+
` --primary: ${formatOklch(mode.primary)};`,
|
|
157
|
+
` --primary-hover: ${formatOklch(mode.primaryHover)};`,
|
|
158
|
+
` --primary-active: ${formatOklch(mode.primaryActive)};`,
|
|
159
|
+
` --primary-foreground: ${formatOklch(mode.primaryForeground)};`,
|
|
160
|
+
`}`,
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The preset as a stylesheet, in the same shape as the ones that ship.
|
|
166
|
+
*
|
|
167
|
+
* Deliberately the same file format rather than a bespoke export: what comes
|
|
168
|
+
* out of here can be dropped into `packages/themes/src/presets/` unchanged, and
|
|
169
|
+
* is then covered by the same audit as everything else.
|
|
170
|
+
*/
|
|
171
|
+
export function formatPreset(name: string, preset: DerivedPreset): string {
|
|
172
|
+
return [
|
|
173
|
+
`/* Theme preset: ${name}.`,
|
|
174
|
+
` * Apply with data-theme="${name}" on the <html> element. Only the brand-carrying`,
|
|
175
|
+
` * tokens are reassigned; every neutral, radius and motion token is inherited. */`,
|
|
176
|
+
"",
|
|
177
|
+
block(`[data-theme="${name}"]`, preset.light),
|
|
178
|
+
"",
|
|
179
|
+
block(`.dark[data-theme="${name}"]`, preset.dark),
|
|
180
|
+
"",
|
|
181
|
+
].join("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** A name usable as a `data-theme` value. */
|
|
185
|
+
export function slugify(name: string): string {
|
|
186
|
+
const slug = name
|
|
187
|
+
.toLowerCase()
|
|
188
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
189
|
+
.replace(/^-+|-+$/g, "");
|
|
190
|
+
return slug.length > 0 ? slug : "custom";
|
|
191
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* Theme preset: blue.
|
|
2
|
+
* Apply with data-theme="blue" on the <html> element. Only the brand-carrying
|
|
3
|
+
* tokens are reassigned; every neutral, radius and motion token is inherited.
|
|
4
|
+
*
|
|
5
|
+
* From SmoothUI's Blue theme (MIT © 2024 Eduardo Calvo): brand
|
|
6
|
+
* oklch(0.67 0.17 257.78), brand-secondary oklch(0.59 0.21 258.02). As in every
|
|
7
|
+
* SmoothUI-derived preset, the brand is --primary and the secondary — the deep
|
|
8
|
+
* end of SmoothUI's gradients — is --primary-active.
|
|
9
|
+
*
|
|
10
|
+
* Hue 258 sits between `ocean` (232, a cyan-leaning blue) and the default
|
|
11
|
+
* (275, an indigo): this is the plain blue neither of them is. */
|
|
12
|
+
|
|
13
|
+
[data-theme="blue"] {
|
|
14
|
+
/* The brand is 2.90:1 under white text and 3.03:1 as text on white. 0.555 is
|
|
15
|
+
the lightest value that clears 4.5:1 for both; hue and chroma are kept.
|
|
16
|
+
The secondary's chroma is capped at 0.175, the most sRGB holds at 0.472. */
|
|
17
|
+
--primary: oklch(0.555 0.17 257.78);
|
|
18
|
+
--primary-hover: oklch(0.51 0.17 257.78);
|
|
19
|
+
--primary-active: oklch(0.472 0.175 258.02);
|
|
20
|
+
--primary-foreground: oklch(0.985 0.002 265);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dark[data-theme="blue"] {
|
|
24
|
+
/* SmoothUI's exact values: 6.5:1 under dark text on the brand, 4.6:1 on the
|
|
25
|
+
secondary. Hover is capped at the chroma sRGB holds at 0.715. */
|
|
26
|
+
--primary: oklch(0.67 0.17 257.78);
|
|
27
|
+
--primary-hover: oklch(0.715 0.145 257.78);
|
|
28
|
+
--primary-active: oklch(0.59 0.21 258.02);
|
|
29
|
+
--primary-foreground: oklch(0.155 0.03 257.78);
|
|
30
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* Theme preset: candy.
|
|
2
|
+
* Apply with data-theme="candy" on the <html> element. Only the brand-carrying
|
|
3
|
+
* tokens are reassigned; every neutral, radius and motion token is inherited.
|
|
4
|
+
*
|
|
5
|
+
* From SmoothUI's Candy theme (MIT © 2024 Eduardo Calvo). Candy is two colours,
|
|
6
|
+
* not one: a pink brand, oklch(0.72 0.2 352.53), and a deeper brand-secondary,
|
|
7
|
+
* oklch(0.66 0.21 354.31), that SmoothUI uses as the far end of its gradients.
|
|
8
|
+
* Dowel has one brand colour and no gradient token, so the pair is mapped onto
|
|
9
|
+
* the states it already has: the brand is --primary, and the secondary is
|
|
10
|
+
* --primary-active — pressing moves further down the same gradient. Hover
|
|
11
|
+
* follows the brand hue. */
|
|
12
|
+
|
|
13
|
+
[data-theme="candy"] {
|
|
14
|
+
/* SmoothUI's pink is 2.62:1 under white text and 2.74:1 as text on white —
|
|
15
|
+
short of 4.5:1 twice, and this token is the focus ring too. Lightness is
|
|
16
|
+
brought down to 0.58, the lightest value that clears 4.5:1 for both, with
|
|
17
|
+
hue and chroma untouched. The active state carries brand-secondary's hue
|
|
18
|
+
at the standard step down, its chroma capped at 0.2, the most sRGB holds
|
|
19
|
+
at 0.497. */
|
|
20
|
+
--primary: oklch(0.58 0.2 352.53);
|
|
21
|
+
--primary-hover: oklch(0.535 0.2 352.53);
|
|
22
|
+
--primary-active: oklch(0.497 0.2 354.31);
|
|
23
|
+
--primary-foreground: oklch(0.985 0.002 265);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dark[data-theme="candy"] {
|
|
27
|
+
/* SmoothUI's exact values. Its brand does not change between modes, and on
|
|
28
|
+
near-black both of them already carry dark text: 7.2:1 on the brand, 5.7:1
|
|
29
|
+
on the secondary. Hover is capped at chroma 0.16, the most sRGB holds at
|
|
30
|
+
that lightness. Near `rose`, but not the same colour: rose is a red at hue
|
|
31
|
+
17 and a dark primary of 0.645; candy is a cooler pink, 25° away, and far
|
|
32
|
+
brighter in dark mode. */
|
|
33
|
+
--primary: oklch(0.72 0.2 352.53);
|
|
34
|
+
--primary-hover: oklch(0.765 0.16 352.53);
|
|
35
|
+
--primary-active: oklch(0.66 0.21 354.31);
|
|
36
|
+
--primary-foreground: oklch(0.155 0.03 352.53);
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Theme preset: green.
|
|
2
|
+
* Apply with data-theme="green" on the <html> element. Only the brand-carrying
|
|
3
|
+
* tokens are reassigned; every neutral, radius and motion token is inherited.
|
|
4
|
+
*
|
|
5
|
+
* From SmoothUI's Green theme (MIT © 2024 Eduardo Calvo): brand
|
|
6
|
+
* oklch(0.7 0.15 162.48), brand-secondary oklch(0.6 0.13 163.23). As in every
|
|
7
|
+
* SmoothUI-derived preset, the brand is --primary and the secondary — the deep
|
|
8
|
+
* end of SmoothUI's gradients — is --primary-active.
|
|
9
|
+
*
|
|
10
|
+
* The closest of the six to an existing preset: `emerald` is at hue 162 too,
|
|
11
|
+
* and in light mode the two are nearly the same colour, because contrast
|
|
12
|
+
* pushes both to the same place. The difference is dark mode, where green is
|
|
13
|
+
* SmoothUI's bright mint at 0.7/0.15 and emerald a deeper 0.645/0.12. */
|
|
14
|
+
|
|
15
|
+
[data-theme="green"] {
|
|
16
|
+
/* The brand is 2.39:1 under white text and 2.50:1 as text on white. 0.54 is
|
|
17
|
+
the lightest value that clears 4.5:1 for both. Green is where sRGB is
|
|
18
|
+
narrowest at that lightness: it holds 0.115 chroma, not SmoothUI's 0.15,
|
|
19
|
+
so that is what is written — the value the screen can show. Hover and
|
|
20
|
+
the secondary are capped the same way, at 0.105 and 0.095. */
|
|
21
|
+
--primary: oklch(0.54 0.115 162.48);
|
|
22
|
+
--primary-hover: oklch(0.495 0.105 162.48);
|
|
23
|
+
--primary-active: oklch(0.457 0.095 163.23);
|
|
24
|
+
--primary-foreground: oklch(0.985 0.002 265);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.dark[data-theme="green"] {
|
|
28
|
+
/* SmoothUI's brand exactly: 7.8:1 under dark text. The secondary's chroma is
|
|
29
|
+
0.125, just inside sRGB where SmoothUI's 0.13 is not; 5.2:1. */
|
|
30
|
+
--primary: oklch(0.7 0.15 162.48);
|
|
31
|
+
--primary-hover: oklch(0.745 0.15 162.48);
|
|
32
|
+
--primary-active: oklch(0.6 0.125 163.23);
|
|
33
|
+
--primary-foreground: oklch(0.155 0.03 162.48);
|
|
34
|
+
}
|
package/src/presets/index.css
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* Theme preset: indigo.
|
|
2
|
+
* Apply with data-theme="indigo" on the <html> element. Only the brand-carrying
|
|
3
|
+
* tokens are reassigned; every neutral, radius and motion token is inherited.
|
|
4
|
+
*
|
|
5
|
+
* From SmoothUI's Indigo theme (MIT © 2024 Eduardo Calvo): brand
|
|
6
|
+
* oklch(0.65 0.22 300.21), brand-secondary oklch(0.54 0.23 286.53). As in every
|
|
7
|
+
* SmoothUI-derived preset, the brand is --primary and the secondary — the deep
|
|
8
|
+
* end of SmoothUI's gradients — is --primary-active.
|
|
9
|
+
*
|
|
10
|
+
* Next to `violet` (hue 302), the brand hue is almost the same: SmoothUI's
|
|
11
|
+
* "indigo" is a violet in OKLCH terms, and the indigo is its secondary, 14°
|
|
12
|
+
* bluer. What separates the two presets is that pressing indigo turns to that
|
|
13
|
+
* true indigo, and that its dark mode keeps the full 0.22 chroma where violet
|
|
14
|
+
* drops to 0.175. */
|
|
15
|
+
|
|
16
|
+
[data-theme="indigo"] {
|
|
17
|
+
/* The brand is 3.41:1 under white text and 3.56:1 as text on white. 0.58 is
|
|
18
|
+
the lightest value that clears 4.5:1 for both; hue and chroma are kept. */
|
|
19
|
+
--primary: oklch(0.58 0.22 300.21);
|
|
20
|
+
--primary-hover: oklch(0.535 0.22 300.21);
|
|
21
|
+
--primary-active: oklch(0.497 0.23 286.53);
|
|
22
|
+
--primary-foreground: oklch(0.985 0.002 265);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dark[data-theme="indigo"] {
|
|
26
|
+
/* The brand is SmoothUI's exact value: 5.5:1 under dark text. The secondary
|
|
27
|
+
is not. One foreground serves every state, so the pressed fill has to
|
|
28
|
+
carry the same dark text, and at 0.54 it reaches only 3.49:1. It is raised
|
|
29
|
+
to 0.605, the first lightness that clears 4.5:1, keeping its hue; its
|
|
30
|
+
chroma is capped at 0.225 and hover's at 0.185, the most sRGB holds at
|
|
31
|
+
those lightnesses. */
|
|
32
|
+
--primary: oklch(0.65 0.22 300.21);
|
|
33
|
+
--primary-hover: oklch(0.695 0.185 300.21);
|
|
34
|
+
--primary-active: oklch(0.605 0.225 286.53);
|
|
35
|
+
--primary-foreground: oklch(0.155 0.03 300.21);
|
|
36
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Theme preset: orange.
|
|
2
|
+
* Apply with data-theme="orange" on the <html> element. Only the brand-carrying
|
|
3
|
+
* tokens are reassigned; every neutral, radius and motion token is inherited.
|
|
4
|
+
*
|
|
5
|
+
* From SmoothUI's Orange theme (MIT © 2024 Eduardo Calvo): brand
|
|
6
|
+
* oklch(0.75 0.17 47.65), brand-secondary oklch(0.68 0.21 40.59). As in every
|
|
7
|
+
* SmoothUI-derived preset, the brand is --primary and the secondary — the deep
|
|
8
|
+
* end of SmoothUI's gradients — is --primary-active.
|
|
9
|
+
*
|
|
10
|
+
* Not `amber`: amber sits at hue 70 and in light mode is an ochre; orange is
|
|
11
|
+
* 22° redder, and in light mode reads as a burnt orange rather than a brown. */
|
|
12
|
+
|
|
13
|
+
[data-theme="orange"] {
|
|
14
|
+
/* The largest adjustment of the six. SmoothUI's orange is 2.28:1 under white
|
|
15
|
+
text and 2.38:1 as text on white; like amber, a bright orange cannot be
|
|
16
|
+
read on a light background at all. 0.565 is the lightest value that clears
|
|
17
|
+
4.5:1 for both, and at that lightness sRGB holds only 0.15 chroma, so that
|
|
18
|
+
is what is written — the value the screen can show. Hover and the
|
|
19
|
+
secondary are capped the same way, at 0.14 and 0.145. */
|
|
20
|
+
--primary: oklch(0.565 0.15 47.65);
|
|
21
|
+
--primary-hover: oklch(0.52 0.14 47.65);
|
|
22
|
+
--primary-active: oklch(0.482 0.145 40.59);
|
|
23
|
+
--primary-foreground: oklch(0.985 0.002 265);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dark[data-theme="orange"] {
|
|
27
|
+
/* SmoothUI's values, with the brand's chroma at 0.16: 0.17 is just outside
|
|
28
|
+
sRGB at this lightness. 8.4:1 under dark text on the brand, 6.3:1 on the
|
|
29
|
+
secondary. Hover is capped at the chroma sRGB holds at 0.795. */
|
|
30
|
+
--primary: oklch(0.75 0.16 47.65);
|
|
31
|
+
--primary-hover: oklch(0.795 0.125 47.65);
|
|
32
|
+
--primary-active: oklch(0.68 0.21 40.59);
|
|
33
|
+
--primary-foreground: oklch(0.155 0.03 47.65);
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* Theme preset: red.
|
|
2
|
+
* Apply with data-theme="red" on the <html> element. Only the brand-carrying
|
|
3
|
+
* tokens are reassigned; every neutral, radius and motion token is inherited.
|
|
4
|
+
*
|
|
5
|
+
* From SmoothUI's Red theme (MIT © 2024 Eduardo Calvo): brand
|
|
6
|
+
* oklch(0.67 0.21 24.28), brand-secondary oklch(0.62 0.25 28.23). As in every
|
|
7
|
+
* SmoothUI-derived preset, the brand is --primary and the secondary — the deep
|
|
8
|
+
* end of SmoothUI's gradients — is --primary-active.
|
|
9
|
+
*
|
|
10
|
+
* Near `rose`, but warmer: rose sits at hue 17 and leans pink; red is at 24
|
|
11
|
+
* and presses towards 28, a vermilion. In dark mode red is also brighter and
|
|
12
|
+
* more saturated — 0.67/0.21 against rose's 0.645/0.17. Worth knowing: the
|
|
13
|
+
* brand is now close to `--destructive`, so a primary and a destructive button
|
|
14
|
+
* side by side are told apart by their labels rather than their colour. */
|
|
15
|
+
|
|
16
|
+
[data-theme="red"] {
|
|
17
|
+
/* The brand is 3.16:1 under white text and 3.30:1 as text on white. 0.58 is
|
|
18
|
+
the lightest value that clears 4.5:1 for both; hue and chroma are kept.
|
|
19
|
+
The secondary's chroma is capped at 0.2, the most sRGB holds at 0.497. */
|
|
20
|
+
--primary: oklch(0.58 0.21 24.28);
|
|
21
|
+
--primary-hover: oklch(0.535 0.21 24.28);
|
|
22
|
+
--primary-active: oklch(0.497 0.2 28.23);
|
|
23
|
+
--primary-foreground: oklch(0.985 0.002 265);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dark[data-theme="red"] {
|
|
27
|
+
/* SmoothUI's exact values: 6.0:1 under dark text on the brand, 4.8:1 on the
|
|
28
|
+
secondary. Hover is capped at the chroma sRGB holds at 0.715. */
|
|
29
|
+
--primary: oklch(0.67 0.21 24.28);
|
|
30
|
+
--primary-hover: oklch(0.715 0.175 24.28);
|
|
31
|
+
--primary-active: oklch(0.62 0.25 28.23);
|
|
32
|
+
--primary-foreground: oklch(0.155 0.03 24.28);
|
|
33
|
+
}
|
package/src/tokens.css
CHANGED
|
@@ -140,6 +140,8 @@
|
|
|
140
140
|
|
|
141
141
|
--animate-accordion-open: accordion-open var(--duration-normal) var(--ease-out-quint);
|
|
142
142
|
--animate-accordion-close: accordion-close var(--duration-fast) var(--ease-in-quint);
|
|
143
|
+
--animate-collapse-down: collapse-down var(--duration-normal) var(--ease-out-quint);
|
|
144
|
+
--animate-collapse-up: collapse-up var(--duration-fast) var(--ease-in-quint);
|
|
143
145
|
|
|
144
146
|
@keyframes fade-in {
|
|
145
147
|
from {
|
|
@@ -242,6 +244,24 @@
|
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
|
|
247
|
+
@keyframes collapse-down {
|
|
248
|
+
from {
|
|
249
|
+
height: 0;
|
|
250
|
+
}
|
|
251
|
+
to {
|
|
252
|
+
height: var(--radix-collapsible-content-height);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@keyframes collapse-up {
|
|
257
|
+
from {
|
|
258
|
+
height: var(--radix-collapsible-content-height);
|
|
259
|
+
}
|
|
260
|
+
to {
|
|
261
|
+
height: 0;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
245
265
|
@keyframes spin {
|
|
246
266
|
to {
|
|
247
267
|
transform: rotate(360deg);
|