clampography 2.0.0-beta.9 → 2.0.1
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 +20 -20
- package/README.md +71 -44
- package/css/base.css +501 -0
- package/css/base.min.css +1 -0
- package/css/clampography.css +1050 -0
- package/css/clampography.min.css +1 -0
- package/css/extra.css +189 -0
- package/css/extra.min.css +1 -0
- package/css/figma-tokens.json +110 -0
- package/css/forms.css +244 -0
- package/css/forms.min.css +1 -0
- package/css/kbd.css +28 -0
- package/css/kbd.min.css +1 -0
- package/css/theme.css +73 -0
- package/css/theme.min.css +1 -0
- package/package.json +42 -22
- package/src/base.js +626 -460
- package/src/convert.js +285 -0
- package/src/export-figma.js +89 -0
- package/src/export-types.js +39 -0
- package/src/extra.js +256 -137
- package/src/forms.js +298 -0
- package/src/index.js +248 -90
- package/src/kbd.js +88 -0
- package/src/print.js +92 -0
- package/src/theme-plugin.js +68 -14
- package/src/theme.js +34 -0
- package/src/themes.js +43 -48
- package/src/types/index.d.ts +24 -0
- package/src/types/theme-plugin.d.ts +32 -0
- package/src/types/themes.d.ts +28 -0
- package/src/types/vars.d.ts +68 -0
- package/src/base.css +0 -535
package/src/forms.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
export default (options = {}) => {
|
|
2
|
+
const root = options.root || ":root";
|
|
3
|
+
|
|
4
|
+
// Helper to scope selectors safely
|
|
5
|
+
const scope = (selector) => {
|
|
6
|
+
const parts = [];
|
|
7
|
+
let current = "";
|
|
8
|
+
let depth = 0;
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < selector.length; i++) {
|
|
11
|
+
const char = selector[i];
|
|
12
|
+
if (char === "(") depth++;
|
|
13
|
+
if (char === ")") depth--;
|
|
14
|
+
if (char === "," && depth === 0) {
|
|
15
|
+
parts.push(current.trim());
|
|
16
|
+
current = "";
|
|
17
|
+
} else {
|
|
18
|
+
current += char;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
parts.push(current.trim());
|
|
22
|
+
|
|
23
|
+
return parts
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((part) => {
|
|
26
|
+
if (part === ":root" || part === "body") return root;
|
|
27
|
+
return `${root} ${part}`;
|
|
28
|
+
})
|
|
29
|
+
.join(", ");
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
// ── Buttons ──────────────────────────────────────────────────────────────
|
|
34
|
+
[scope(":where(button, [type='button'], [type='reset'], [type='submit'])")]: {
|
|
35
|
+
"display": "inline-flex",
|
|
36
|
+
"align-items": "center",
|
|
37
|
+
"justify-content": "center",
|
|
38
|
+
"gap": "0.375em",
|
|
39
|
+
"padding": "var(--clampography-spacing-xs) var(--clampography-spacing-sm)",
|
|
40
|
+
"background-color": "var(--clampography-surface)",
|
|
41
|
+
"color": "var(--clampography-text)",
|
|
42
|
+
"border": "1px solid var(--clampography-border)",
|
|
43
|
+
"border-radius": "0.375rem",
|
|
44
|
+
"font-weight": "500",
|
|
45
|
+
"white-space": "nowrap",
|
|
46
|
+
"transition-property": "background-color, border-color, color, box-shadow",
|
|
47
|
+
"transition-duration": "150ms",
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
[scope(":where(button, [type='button'], [type='reset'], [type='submit']):hover")]: {
|
|
51
|
+
"background-color": "var(--clampography-background)",
|
|
52
|
+
"border-color": "var(--clampography-primary)",
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
[scope(":where(button, [type='button'], [type='submit']).primary, [type='submit']")]: {
|
|
56
|
+
"background-color": "var(--clampography-primary)",
|
|
57
|
+
"color": "var(--clampography-background)",
|
|
58
|
+
"border-color": "var(--clampography-primary)",
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
[scope(":where(button, [type='button'], [type='submit']).primary:hover, [type='submit']:hover")]: {
|
|
62
|
+
"filter": "brightness(1.1)",
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// ── Text Inputs & Textarea ────────────────────────────────────────────────
|
|
66
|
+
[scope(":where(input:not([type='checkbox'], [type='radio'], [type='range'], [type='color']), textarea, select)")]: {
|
|
67
|
+
"display": "block",
|
|
68
|
+
"width": "100%",
|
|
69
|
+
"padding": "var(--clampography-spacing-xs) var(--clampography-spacing-sm)",
|
|
70
|
+
"background-color": "var(--clampography-background)",
|
|
71
|
+
"color": "var(--clampography-text)",
|
|
72
|
+
"border": "1px solid var(--clampography-border)",
|
|
73
|
+
"border-radius": "0.375rem",
|
|
74
|
+
"transition-property": "border-color, box-shadow",
|
|
75
|
+
"transition-duration": "150ms",
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
[scope(":where(input:not([type='checkbox'], [type='radio'], [type='range'], [type='color']), textarea, select):focus")]: {
|
|
79
|
+
"outline": "none",
|
|
80
|
+
"border-color": "var(--clampography-primary)",
|
|
81
|
+
"box-shadow": "0 0 0 3px color-mix(in oklab, var(--clampography-primary) 20%, transparent)",
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
[scope(":where(input, textarea, select):disabled")]: {
|
|
85
|
+
"opacity": "0.5",
|
|
86
|
+
"cursor": "not-allowed",
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
[scope(":where(input, textarea, select)[readonly]")]: {
|
|
90
|
+
"background-color": "color-mix(in oklab, var(--clampography-surface) 50%, transparent)",
|
|
91
|
+
"cursor": "default",
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
[scope(":where(input, textarea, select):user-invalid")]: {
|
|
95
|
+
"border-color": "var(--clampography-error)",
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
[scope(":where(input, textarea, select):user-invalid:focus")]: {
|
|
99
|
+
"box-shadow": "0 0 0 3px color-mix(in oklab, var(--clampography-error) 20%, transparent)",
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
[scope("[type='search']::-webkit-search-cancel-button, [type='search']::-webkit-search-decoration")]: {
|
|
103
|
+
"-webkit-appearance": "none",
|
|
104
|
+
"appearance": "none",
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
[scope("[type='number']::-webkit-inner-spin-button, [type='number']::-webkit-outer-spin-button")]: {
|
|
108
|
+
"height": "auto",
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
[scope(":where(input, textarea, select)::placeholder")]: {
|
|
112
|
+
"color": "var(--clampography-muted)",
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// ── Select ────────────────────────────────────────────────────────────────
|
|
116
|
+
[scope("select")]: {
|
|
117
|
+
"appearance": "none",
|
|
118
|
+
"background-image": `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E")`,
|
|
119
|
+
"background-position": "inline-end 0.5rem center",
|
|
120
|
+
"background-repeat": "no-repeat",
|
|
121
|
+
"background-size": "1.5em 1.5em",
|
|
122
|
+
"padding-inline-end": "2.5rem",
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// ── File Input ────────────────────────────────────────────────────────────
|
|
126
|
+
[scope("[type='file']")]: {
|
|
127
|
+
"padding": "0",
|
|
128
|
+
"background-color": "transparent",
|
|
129
|
+
"border": "none",
|
|
130
|
+
"cursor": "pointer",
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
[scope("[type='file']::file-selector-button")]: {
|
|
134
|
+
"display": "inline-flex",
|
|
135
|
+
"align-items": "center",
|
|
136
|
+
"padding": "var(--clampography-spacing-xs) var(--clampography-spacing-sm)",
|
|
137
|
+
"margin-inline-end": "var(--clampography-spacing-sm)",
|
|
138
|
+
"background-color": "var(--clampography-surface)",
|
|
139
|
+
"color": "var(--clampography-text)",
|
|
140
|
+
"border": "1px solid var(--clampography-border)",
|
|
141
|
+
"border-radius": "0.375rem",
|
|
142
|
+
"font-family": "inherit",
|
|
143
|
+
"font-size": "inherit",
|
|
144
|
+
"cursor": "pointer",
|
|
145
|
+
"transition-property": "background-color, border-color",
|
|
146
|
+
"transition-duration": "150ms",
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
[scope("[type='file']:hover::file-selector-button")]: {
|
|
150
|
+
"background-color": "var(--clampography-background)",
|
|
151
|
+
"border-color": "var(--clampography-primary)",
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// ── Checkbox & Radio ──────────────────────────────────────────────────────
|
|
155
|
+
[scope("[type='checkbox'], [type='radio']")]: {
|
|
156
|
+
"width": "1em",
|
|
157
|
+
"height": "1em",
|
|
158
|
+
"accent-color": "var(--clampography-primary)",
|
|
159
|
+
"vertical-align": "middle",
|
|
160
|
+
"cursor": "pointer",
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
[scope("[type='checkbox']:focus-visible, [type='radio']:focus-visible")]: {
|
|
164
|
+
"outline": "2px solid var(--clampography-primary)",
|
|
165
|
+
"outline-offset": "2px",
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// ── Range ────────────────────────────────────────────────────────────────
|
|
169
|
+
[scope("[type='range']")]: {
|
|
170
|
+
"accent-color": "var(--clampography-primary)",
|
|
171
|
+
"width": "100%",
|
|
172
|
+
"cursor": "pointer",
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// ── Color Picker ──────────────────────────────────────────────────────────
|
|
176
|
+
[scope("[type='color']")]: {
|
|
177
|
+
"padding": "0.125rem",
|
|
178
|
+
"width": "2.5rem",
|
|
179
|
+
"height": "2.5rem",
|
|
180
|
+
"border": "1px solid var(--clampography-border)",
|
|
181
|
+
"border-radius": "0.375rem",
|
|
182
|
+
"background-color": "var(--clampography-background)",
|
|
183
|
+
"cursor": "pointer",
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// ── Fieldset & Legend ────────────────────────────────────────────────────
|
|
187
|
+
[scope("fieldset")]: {
|
|
188
|
+
"border": "1px solid var(--clampography-border)",
|
|
189
|
+
"border-radius": "0.5rem",
|
|
190
|
+
"background-color": "var(--clampography-surface)",
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
[scope("legend")]: {
|
|
194
|
+
"color": "var(--clampography-heading)",
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// ── Label ────────────────────────────────────────────────────────────────
|
|
198
|
+
[scope("label")]: {
|
|
199
|
+
"color": "var(--clampography-text)",
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// ── Output ───────────────────────────────────────────────────────────────
|
|
203
|
+
[scope("output")]: {
|
|
204
|
+
"color": "var(--clampography-primary)",
|
|
205
|
+
"font-weight": "600",
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// ── Progress ──────────────────────────────────────────────────────────────
|
|
209
|
+
[scope("progress")]: {
|
|
210
|
+
"-webkit-appearance": "none",
|
|
211
|
+
"appearance": "none",
|
|
212
|
+
"width": "100%",
|
|
213
|
+
"height": "1em",
|
|
214
|
+
"background": "transparent",
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// WebKit progress track
|
|
218
|
+
[scope("progress::-webkit-progress-bar")]: {
|
|
219
|
+
"background": "color-mix(in oklab, var(--clampography-text) 20%, transparent)",
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// WebKit progress value
|
|
223
|
+
[scope("progress::-webkit-progress-value")]: {
|
|
224
|
+
"background": "var(--clampography-success)",
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// Firefox progress value
|
|
228
|
+
[scope("progress::-moz-progress-bar")]: {
|
|
229
|
+
"background": "var(--clampography-success)",
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// ── Meter ─────────────────────────────────────────────────────────────────
|
|
233
|
+
// Custom styling for <meter> (accent-color does not work on meter)
|
|
234
|
+
[scope("meter")]: {
|
|
235
|
+
"-webkit-appearance": "none",
|
|
236
|
+
"appearance": "none",
|
|
237
|
+
"width": "100%",
|
|
238
|
+
"height": "1em",
|
|
239
|
+
"background": "transparent",
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
// Firefox track (restored via Firefox-only feature query)
|
|
243
|
+
// @supports (-moz-appearance: none) is ignored by all WebKit/Blink browsers
|
|
244
|
+
"@supports (-moz-appearance: none)": {
|
|
245
|
+
[scope("progress")]: {
|
|
246
|
+
"background": "color-mix(in oklab, var(--clampography-text) 20%, transparent)",
|
|
247
|
+
},
|
|
248
|
+
[scope("meter")]: {
|
|
249
|
+
"background": "color-mix(in oklab, var(--clampography-text) 20%, transparent)",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// Re-establish height context for WebKit shadow DOM
|
|
254
|
+
// appearance:none breaks Chrome's flex layout; inner elements can't resolve height:100%
|
|
255
|
+
// without a concrete parent height set here.
|
|
256
|
+
// display:flex + align-items:stretch forces the child bar to fill the full height
|
|
257
|
+
// without top-anchoring it the way display:block would.
|
|
258
|
+
[scope("meter::-webkit-meter-inner-element")]: {
|
|
259
|
+
"display": "flex",
|
|
260
|
+
"align-items": "stretch",
|
|
261
|
+
"height": "1em",
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// WebKit inner track
|
|
265
|
+
[scope("meter::-webkit-meter-bar")]: {
|
|
266
|
+
"background": "color-mix(in oklab, var(--clampography-text) 20%, transparent)",
|
|
267
|
+
"height": "100%",
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// 1. Optimum (Success)
|
|
271
|
+
[scope("meter::-webkit-meter-optimum-value")]: {
|
|
272
|
+
"background": "var(--clampography-success)",
|
|
273
|
+
"height": "100%",
|
|
274
|
+
},
|
|
275
|
+
[scope("meter:-moz-meter-optimum::-moz-meter-bar")]: {
|
|
276
|
+
"background": "var(--clampography-success)",
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// 2. Sub-optimum (Warning)
|
|
280
|
+
[scope("meter::-webkit-meter-suboptimum-value")]: {
|
|
281
|
+
"background": "var(--clampography-warning)",
|
|
282
|
+
"height": "100%",
|
|
283
|
+
},
|
|
284
|
+
[scope("meter:-moz-meter-sub-optimum::-moz-meter-bar")]: {
|
|
285
|
+
"background": "var(--clampography-warning)",
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// 3. Even less good (Error)
|
|
289
|
+
[scope("meter::-webkit-meter-even-less-good-value")]: {
|
|
290
|
+
"background": "var(--clampography-error)",
|
|
291
|
+
"height": "100%",
|
|
292
|
+
},
|
|
293
|
+
[scope("meter:-moz-meter-sub-sub-optimum::-moz-meter-bar")]: {
|
|
294
|
+
"background": "var(--clampography-error)",
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
};
|
|
298
|
+
};
|
package/src/index.js
CHANGED
|
@@ -2,108 +2,266 @@ import plugin from "tailwindcss/plugin";
|
|
|
2
2
|
import { themes as builtInThemes } from "./themes.js";
|
|
3
3
|
import baseStyles from "./base.js";
|
|
4
4
|
import extraStyles from "./extra.js";
|
|
5
|
+
import formsStyles from "./forms.js";
|
|
6
|
+
import kbdStyles from "./kbd.js";
|
|
7
|
+
import printStyles from "./print.js";
|
|
8
|
+
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Helper to resolve boolean options from CSS configuration.
|
|
8
13
|
* CSS values often come as strings ("true"/"false"), which are both truthy in JS.
|
|
9
14
|
*/
|
|
10
15
|
const resolveBool = (value, defaultValue) => {
|
|
11
|
-
if (
|
|
12
|
-
|
|
16
|
+
if (
|
|
17
|
+
value === "false" || value === false || value === "no" || value === "none"
|
|
18
|
+
) return false;
|
|
19
|
+
if (value === "true" || value === true || value === "yes") return true;
|
|
13
20
|
return defaultValue;
|
|
14
21
|
};
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Main plugin function.
|
|
18
25
|
*/
|
|
19
|
-
export default plugin.withOptions(
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
26
|
+
export default plugin.withOptions(
|
|
27
|
+
(() => {
|
|
28
|
+
let firstRun = true; // Track first run for logging
|
|
29
|
+
|
|
30
|
+
return (options = {}) => {
|
|
31
|
+
return ({ addBase }) => {
|
|
32
|
+
// Extract logs option (default: true)
|
|
33
|
+
const showLogs = resolveBool(options.logs, true);
|
|
34
|
+
|
|
35
|
+
// Show startup log only once
|
|
36
|
+
if (showLogs && firstRun) {
|
|
37
|
+
console.log(`🍀 Clampography loaded successfully`);
|
|
38
|
+
firstRun = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 1. Load Base and Extra styles
|
|
42
|
+
// We use the helper to correctly parse "false" string from CSS
|
|
43
|
+
const includeBase = resolveBool(options.base, true); // Default: true
|
|
44
|
+
const includeExtra = resolveBool(options.extra, false); // Default: false
|
|
45
|
+
const includeForms = resolveBool(options.forms, false); // Default: false
|
|
46
|
+
const includeKbd = resolveBool(options.kbd, false); // Default: false
|
|
47
|
+
const includePrint = resolveBool(options.print, false); // Default: false
|
|
48
|
+
|
|
49
|
+
// Extract fluid bounds for clampography math engine
|
|
50
|
+
const fluidMin = parseInt(options["fluid-min"] || options.fluidMin || "320");
|
|
51
|
+
const fluidMax = parseInt(options["fluid-max"] || options.fluidMax || "1280");
|
|
52
|
+
|
|
53
|
+
// Extract scaleMode: 'viewport' (default, uses vw) or 'container' (uses cqi)
|
|
54
|
+
const scaleMode = options["scale-mode"] || options.scaleMode || "viewport";
|
|
55
|
+
|
|
56
|
+
// Extract typography scope option (default: global)
|
|
57
|
+
const typography = options.typography || "global";
|
|
58
|
+
|
|
59
|
+
// Pass options to the style functions to enable scoping
|
|
60
|
+
includeBase && addBase(baseStyles({ ...options, fluidMin, fluidMax, scaleMode, typography }));
|
|
61
|
+
includeExtra && addBase(extraStyles({ ...options, typography }));
|
|
62
|
+
includeForms && addBase(formsStyles({ ...options, typography }));
|
|
63
|
+
includeKbd && addBase(kbdStyles({ ...options, typography }));
|
|
64
|
+
includePrint && addBase(printStyles({ ...options, typography }));
|
|
65
|
+
|
|
66
|
+
// 2. Parse themes configuration
|
|
67
|
+
let configThemes = options.themes;
|
|
68
|
+
let themesToInclude = [];
|
|
69
|
+
let defaultThemeName = null;
|
|
70
|
+
let prefersDarkTheme = false;
|
|
71
|
+
let rootSelector = options.root || ":root";
|
|
72
|
+
let isAllThemes = false; // Track if user specified "all"
|
|
73
|
+
|
|
74
|
+
// Normalize input to an array of strings
|
|
75
|
+
let rawThemeList = [];
|
|
76
|
+
if (typeof configThemes === "string") {
|
|
77
|
+
if (["all", "true", "yes"].includes(configThemes.trim())) {
|
|
78
|
+
// Special case: themes: all
|
|
79
|
+
isAllThemes = true;
|
|
80
|
+
rawThemeList = Object.keys(builtInThemes);
|
|
81
|
+
} else if (["false", "none", "no"].includes(configThemes.trim())) {
|
|
82
|
+
// Explicitly disabled themes
|
|
83
|
+
rawThemeList = [];
|
|
84
|
+
} else {
|
|
85
|
+
rawThemeList = configThemes.split(",");
|
|
86
|
+
}
|
|
87
|
+
} else if (Array.isArray(configThemes)) {
|
|
88
|
+
rawThemeList = configThemes;
|
|
89
|
+
} else {
|
|
90
|
+
// Default behavior: NO themes loaded automatically.
|
|
91
|
+
// User must specify themes to load them.
|
|
92
|
+
rawThemeList = [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 3. Process the list and look for flags (--default, --prefersdark)
|
|
96
|
+
rawThemeList.forEach((rawItem) => {
|
|
97
|
+
let themeName = rawItem.trim();
|
|
98
|
+
|
|
99
|
+
// Ignore empty entries
|
|
100
|
+
if (!themeName) return;
|
|
101
|
+
|
|
102
|
+
// Check for --default flag
|
|
103
|
+
if (themeName.includes("--default")) {
|
|
104
|
+
themeName = themeName.replace("--default", "").trim();
|
|
105
|
+
defaultThemeName = themeName;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for --prefersdark flag
|
|
109
|
+
if (themeName.toLowerCase().includes("--prefersdark")) {
|
|
110
|
+
themeName = themeName.replace(/--prefersdark/i, "").trim();
|
|
111
|
+
prefersDarkTheme = themeName;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if theme exists in the database
|
|
115
|
+
if (builtInThemes[themeName]) {
|
|
116
|
+
themesToInclude.push(themeName);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// 4. Auto-configure defaults for "themes: all"
|
|
121
|
+
if (isAllThemes) {
|
|
122
|
+
if (!defaultThemeName) {
|
|
123
|
+
if (themesToInclude.includes("light")) {
|
|
124
|
+
defaultThemeName = "light";
|
|
125
|
+
} else if (themesToInclude.length > 0) {
|
|
126
|
+
defaultThemeName = themesToInclude[0];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!prefersDarkTheme && themesToInclude.includes("dark")) {
|
|
130
|
+
prefersDarkTheme = "dark";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// LOGGING: Built-in themes summary
|
|
135
|
+
if (showLogs) {
|
|
136
|
+
const explicitlyDisabled = ["false", "no", "none"].includes(
|
|
137
|
+
String(options.themes).trim(),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (themesToInclude.length > 0) {
|
|
141
|
+
const themesList = themesToInclude.map((theme) => {
|
|
142
|
+
const flags = [];
|
|
143
|
+
if (theme === defaultThemeName) flags.push("default");
|
|
144
|
+
if (theme === prefersDarkTheme) flags.push("prefersdark");
|
|
145
|
+
|
|
146
|
+
const flagStr = flags.length > 0 ? ` (${flags.join(", ")})` : "";
|
|
147
|
+
return `${theme}${flagStr}`;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
console.log(
|
|
151
|
+
`🍀 Clampography: Loaded ${themesToInclude.length} built-in themes: ${
|
|
152
|
+
themesList.join(", ")
|
|
153
|
+
}`,
|
|
154
|
+
);
|
|
155
|
+
} else if (!explicitlyDisabled) {
|
|
156
|
+
console.info("ℹ️ Clampography: No built-in themes loaded.");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Final check before generating CSS
|
|
161
|
+
if (
|
|
162
|
+
themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
|
|
163
|
+
) return;
|
|
164
|
+
|
|
165
|
+
// 5. Generate CSS
|
|
166
|
+
const themeStyles = {};
|
|
167
|
+
|
|
168
|
+
// A. Default theme - uses :where() for lower specificity
|
|
169
|
+
// Helper to combine root and data-theme
|
|
170
|
+
// If root is ":root", we use "html[data-theme...]" for backwards compatibility.
|
|
171
|
+
// If root is custom (e.g. "#morda"), we generate "#morda[data-theme...]"
|
|
172
|
+
const getThemeSelector = (themeName) => {
|
|
173
|
+
if (rootSelector === ":root") {
|
|
174
|
+
return `html[data-theme="${themeName}"], [data-theme="${themeName}"]`;
|
|
175
|
+
}
|
|
176
|
+
// For custom root, attach data-theme directly to it
|
|
177
|
+
// AND allow a nested data-theme inside it (optional, but good for nesting)
|
|
178
|
+
return `${rootSelector}[data-theme="${themeName}"], ${rootSelector} [data-theme="${themeName}"]`;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// A. Default theme
|
|
182
|
+
if (defaultThemeName && builtInThemes[defaultThemeName]) {
|
|
183
|
+
// Default variables applied to the root element itself without data-theme attribute
|
|
184
|
+
// uses :where() for lower specificity so it can be overridden
|
|
185
|
+
const defaultSelector = `:where(${rootSelector})`;
|
|
186
|
+
|
|
187
|
+
// Also apply if explicitly selected
|
|
188
|
+
const explicitSelector = getThemeSelector(defaultThemeName);
|
|
189
|
+
|
|
190
|
+
themeStyles[`${defaultSelector}, ${explicitSelector}`] =
|
|
191
|
+
builtInThemes[defaultThemeName];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// B. Theme for prefers-color-scheme: dark
|
|
195
|
+
if (prefersDarkTheme && builtInThemes[prefersDarkTheme]) {
|
|
196
|
+
themeStyles["@media (prefers-color-scheme: dark)"] = {
|
|
197
|
+
[rootSelector]: builtInThemes[prefersDarkTheme],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// C. All themes available via [data-theme] attribute
|
|
202
|
+
themesToInclude.forEach((themeName) => {
|
|
203
|
+
if (themeName === defaultThemeName) return;
|
|
204
|
+
const selector = getThemeSelector(themeName);
|
|
205
|
+
themeStyles[selector] = builtInThemes[themeName];
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// D. Override media query with data-theme selectors
|
|
209
|
+
if (prefersDarkTheme) {
|
|
210
|
+
themeStyles["@media (prefers-color-scheme: dark)"] = {
|
|
211
|
+
...themeStyles["@media (prefers-color-scheme: dark)"],
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
themesToInclude.forEach((themeName) => {
|
|
215
|
+
if (themeName === prefersDarkTheme) return;
|
|
216
|
+
const selector = getThemeSelector(themeName);
|
|
217
|
+
|
|
218
|
+
if (!themeStyles["@media (prefers-color-scheme: dark)"][selector]) {
|
|
219
|
+
themeStyles["@media (prefers-color-scheme: dark)"][selector] =
|
|
220
|
+
builtInThemes[themeName];
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
addBase(themeStyles);
|
|
99
226
|
};
|
|
100
|
-
}
|
|
227
|
+
};
|
|
228
|
+
})(),
|
|
229
|
+
// Theme extension - enables utilities like bg-surface, text-heading, etc.
|
|
230
|
+
(options = {}) => {
|
|
231
|
+
// ✅ Extract prefix option (default: "clampography")
|
|
232
|
+
// This prefix is ONLY used for Tailwind utility classes (e.g., bg-clampography-primary)
|
|
233
|
+
// CSS variables remain unchanged (always --clampography-*)
|
|
234
|
+
const prefixEnabled = resolveBool(options.prefix, true);
|
|
235
|
+
const prefix = prefixEnabled
|
|
236
|
+
? (typeof options.prefix === "string" &&
|
|
237
|
+
!["false", "no", "none", "true"].includes(options.prefix)
|
|
238
|
+
? options.prefix
|
|
239
|
+
: "clampography")
|
|
240
|
+
: "";
|
|
101
241
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
themeStyles[`[data-theme="${themeName}"]`] = builtInThemes[themeName];
|
|
105
|
-
});
|
|
242
|
+
// Helper to add prefix with separator
|
|
243
|
+
const addPrefix = (name) => prefix ? `${prefix}-${name}` : name;
|
|
106
244
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
245
|
+
return {
|
|
246
|
+
theme: {
|
|
247
|
+
extend: {
|
|
248
|
+
colors: {
|
|
249
|
+
[addPrefix("background")]: "var(--clampography-background)",
|
|
250
|
+
[addPrefix("border")]: "var(--clampography-border)",
|
|
251
|
+
[addPrefix("error")]: "var(--clampography-error)",
|
|
252
|
+
[addPrefix("heading")]: "var(--clampography-heading)",
|
|
253
|
+
[addPrefix("info")]: "var(--clampography-info)",
|
|
254
|
+
[addPrefix("link")]: "var(--clampography-link)",
|
|
255
|
+
[addPrefix("muted")]: "var(--clampography-muted)",
|
|
256
|
+
[addPrefix("primary")]: "var(--clampography-primary)",
|
|
257
|
+
[addPrefix("secondary")]: "var(--clampography-secondary)",
|
|
258
|
+
[addPrefix("success")]: "var(--clampography-success)",
|
|
259
|
+
[addPrefix("surface")]: "var(--clampography-surface)",
|
|
260
|
+
[addPrefix("text")]: "var(--clampography-text)",
|
|
261
|
+
[addPrefix("warning")]: "var(--clampography-warning)",
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
);
|