clampography 2.0.0-beta.1 → 2.0.0-beta.11
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 +2 -0
- package/package.json +1 -1
- package/src/base.css +583 -0
- package/src/base.js +181 -136
- package/src/convert.js +185 -0
- package/src/extra.js +152 -16
- package/src/index.js +121 -87
- package/src/theme-plugin.js +39 -25
- package/src/themes.js +56 -20
package/src/convert.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts base.css to base.js format with preserved comments
|
|
5
|
+
* https://claude.ai/chat/90dedd98-3e31-4831-a620-c35d6523b836
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
|
|
11
|
+
// Configuration
|
|
12
|
+
const INPUT_FILE = "base.css";
|
|
13
|
+
const OUTPUT_FILE = "base.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse CSS content into structured data
|
|
17
|
+
*/
|
|
18
|
+
function parseCSS(css) {
|
|
19
|
+
const result = {};
|
|
20
|
+
const lines = css.split("\n");
|
|
21
|
+
let currentContext = result;
|
|
22
|
+
let contextStack = [result];
|
|
23
|
+
let currentSelector = null;
|
|
24
|
+
let inAtLayer = false;
|
|
25
|
+
let buffer = "";
|
|
26
|
+
let commentBuffer = [];
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i].trim();
|
|
30
|
+
|
|
31
|
+
// Skip empty lines
|
|
32
|
+
if (!line) {
|
|
33
|
+
if (commentBuffer.length > 0) {
|
|
34
|
+
commentBuffer.push("");
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Capture comments
|
|
40
|
+
if (line.startsWith("/*")) {
|
|
41
|
+
const commentLines = [line];
|
|
42
|
+
while (i < lines.length && !lines[i].includes("*/")) {
|
|
43
|
+
i++;
|
|
44
|
+
commentLines.push(lines[i].trim());
|
|
45
|
+
}
|
|
46
|
+
commentBuffer.push(...commentLines);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle @layer
|
|
51
|
+
if (line.startsWith("@layer")) {
|
|
52
|
+
const match = line.match(/@layer\s+(\w+)/);
|
|
53
|
+
if (match) {
|
|
54
|
+
inAtLayer = true;
|
|
55
|
+
currentContext["@layer base"] = {};
|
|
56
|
+
contextStack.push(currentContext["@layer base"]);
|
|
57
|
+
currentContext = currentContext["@layer base"];
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle opening brace (new rule)
|
|
63
|
+
if (line.includes("{") && !line.includes("}")) {
|
|
64
|
+
const selector = line.replace("{", "").trim();
|
|
65
|
+
|
|
66
|
+
// Store comments before selector
|
|
67
|
+
if (commentBuffer.length > 0) {
|
|
68
|
+
currentContext[`__comment_${Object.keys(currentContext).length}`] =
|
|
69
|
+
commentBuffer.join("\n");
|
|
70
|
+
commentBuffer = [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
currentContext[selector] = {};
|
|
74
|
+
contextStack.push(currentContext[selector]);
|
|
75
|
+
currentContext = currentContext[selector];
|
|
76
|
+
currentSelector = selector;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle closing brace
|
|
81
|
+
if (line === "}") {
|
|
82
|
+
contextStack.pop();
|
|
83
|
+
currentContext = contextStack[contextStack.length - 1];
|
|
84
|
+
currentSelector = null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle properties
|
|
89
|
+
if (line.includes(":") && currentSelector) {
|
|
90
|
+
let [prop, ...valueParts] = line.split(":");
|
|
91
|
+
let value = valueParts.join(":").trim();
|
|
92
|
+
|
|
93
|
+
// Remove semicolon and inline comments
|
|
94
|
+
const inlineComment = value.match(/\/\*.*?\*\//);
|
|
95
|
+
if (inlineComment) {
|
|
96
|
+
value = value.replace(inlineComment[0], "").trim();
|
|
97
|
+
}
|
|
98
|
+
value = value.replace(";", "").trim();
|
|
99
|
+
|
|
100
|
+
prop = prop.trim();
|
|
101
|
+
|
|
102
|
+
// Store inline comment separately if exists
|
|
103
|
+
if (inlineComment && inlineComment[0]) {
|
|
104
|
+
currentContext[`__inline_comment_${prop}`] = inlineComment[0];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
currentContext[prop] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Convert parsed CSS to JS object string
|
|
116
|
+
*/
|
|
117
|
+
function toJSObject(obj, indent = 0) {
|
|
118
|
+
const spaces = " ".repeat(indent);
|
|
119
|
+
const lines = [];
|
|
120
|
+
|
|
121
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
122
|
+
// Handle comments
|
|
123
|
+
if (key.startsWith("__comment_")) {
|
|
124
|
+
const comment = value.split("\n").map((line) => `${spaces}${line}`).join(
|
|
125
|
+
"\n",
|
|
126
|
+
);
|
|
127
|
+
lines.push(comment);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (key.startsWith("__inline_comment_")) {
|
|
132
|
+
continue; // Skip inline comments in first pass
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
136
|
+
// It's a nested object (selector)
|
|
137
|
+
lines.push(`${spaces}"${key}": {`);
|
|
138
|
+
lines.push(toJSObject(value, indent + 1));
|
|
139
|
+
lines.push(`${spaces}},`);
|
|
140
|
+
} else {
|
|
141
|
+
// It's a property
|
|
142
|
+
const formattedValue = value.includes('"') || value.includes("'")
|
|
143
|
+
? `"${value.replace(/"/g, '\\"')}"`
|
|
144
|
+
: `"${value}"`;
|
|
145
|
+
|
|
146
|
+
// Check for inline comment
|
|
147
|
+
const inlineCommentKey = `__inline_comment_${key}`;
|
|
148
|
+
const inlineComment = obj[inlineCommentKey];
|
|
149
|
+
const comment = inlineComment ? ` ${inlineComment}` : "";
|
|
150
|
+
|
|
151
|
+
lines.push(`${spaces}"${key}": ${formattedValue},${comment}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Main conversion function
|
|
160
|
+
*/
|
|
161
|
+
function convertCSStoJS(cssContent) {
|
|
162
|
+
const parsed = parseCSS(cssContent);
|
|
163
|
+
const jsContent = `export default {\n${
|
|
164
|
+
toJSObject(parsed["@layer base"] || parsed, 1)
|
|
165
|
+
}\n};\n`;
|
|
166
|
+
return jsContent;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Main execution
|
|
170
|
+
try {
|
|
171
|
+
console.log(`📖 Reading ${INPUT_FILE}...`);
|
|
172
|
+
const cssContent = readFileSync(resolve(INPUT_FILE), "utf-8");
|
|
173
|
+
|
|
174
|
+
console.log("⚙️ Converting CSS to JS...");
|
|
175
|
+
const jsContent = convertCSStoJS(cssContent);
|
|
176
|
+
|
|
177
|
+
console.log(`💾 Writing ${OUTPUT_FILE}...`);
|
|
178
|
+
writeFileSync(resolve(OUTPUT_FILE), jsContent, "utf-8");
|
|
179
|
+
|
|
180
|
+
console.log("✅ Conversion complete!");
|
|
181
|
+
console.log(`📄 Output: ${OUTPUT_FILE}`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error("❌ Error:", error.message);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
package/src/extra.js
CHANGED
|
@@ -1,34 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extra opinionated styles and coloring.
|
|
3
|
+
* Applies colors to the structural base elements.
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
export default {
|
|
7
|
+
// --- Basic Coloring (Applying theme variables) ---
|
|
8
|
+
"body": {
|
|
9
|
+
"background-color": "oklch(var(--clampography-background))",
|
|
10
|
+
"color": "oklch(var(--clampography-text))",
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
":where(h1, h2, h3, h4, h5, h6)": {
|
|
14
|
+
"color": "oklch(var(--clampography-heading))",
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"a": {
|
|
18
|
+
"color": "oklch(var(--clampography-link))",
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Lists
|
|
22
|
+
"ul > li::before": {
|
|
23
|
+
"background-color": "oklch(var(--clampography-primary))", // Bullet points
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
"ol > li::before": {
|
|
27
|
+
"color": "oklch(var(--clampography-secondary))", // Numbers
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Inline Code
|
|
31
|
+
":where(code, kbd, samp)": {
|
|
32
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
33
|
+
"color": "oklch(var(--clampography-heading))",
|
|
34
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
35
|
+
"border-radius": "0.25rem",
|
|
36
|
+
"padding": "0.125rem var(--spacing-xs)",
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Keyboard input - vertical alignment
|
|
40
|
+
"kbd": {
|
|
41
|
+
transform: "translateY(-0.15em)",
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Preformatted Code Blocks
|
|
45
|
+
"pre": {
|
|
46
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
47
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
48
|
+
"border-radius": "0.375rem",
|
|
49
|
+
"padding": "1rem",
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Tables
|
|
53
|
+
"table": {
|
|
54
|
+
"padding": "var(--spacing-sm)",
|
|
55
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
"th": {
|
|
59
|
+
"color": "oklch(var(--clampography-heading))",
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
"th, td": {
|
|
63
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"thead th": {
|
|
67
|
+
"border-bottom-width": "2px",
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Zebra striping for table rows
|
|
71
|
+
"tbody tr:nth-child(even)": {
|
|
72
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Captions & Muted text
|
|
76
|
+
"caption, figcaption, .muted": {
|
|
77
|
+
"color": "oklch(var(--clampography-muted))",
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Horizontal Rule (Thematic)
|
|
2
81
|
"hr": {
|
|
3
82
|
"height": "1px",
|
|
4
83
|
"border-width": "0",
|
|
5
|
-
"margin-top": "3rem",
|
|
6
|
-
"margin-bottom": "3rem",
|
|
7
|
-
"background-color": "var(--clampography-
|
|
84
|
+
"margin-top": "3rem",
|
|
85
|
+
"margin-bottom": "3rem",
|
|
86
|
+
"background-color": "oklch(var(--clampography-border))",
|
|
8
87
|
},
|
|
9
88
|
|
|
89
|
+
// --- Opinionated Extras ---
|
|
90
|
+
|
|
91
|
+
// Styled Blockquote
|
|
10
92
|
"blockquote": {
|
|
11
93
|
"border-left-width": "4px",
|
|
12
|
-
"border-color": "var(--clampography-primary)",
|
|
13
|
-
"
|
|
14
|
-
"padding
|
|
15
|
-
"
|
|
16
|
-
"
|
|
94
|
+
"border-left-color": "oklch(var(--clampography-primary))",
|
|
95
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
96
|
+
"padding": "1rem",
|
|
97
|
+
"border-radius": "0.25rem",
|
|
98
|
+
"font-style": "italic",
|
|
99
|
+
"color": "oklch(var(--clampography-heading))",
|
|
17
100
|
},
|
|
18
101
|
|
|
102
|
+
// Styled Links (Enhanced)
|
|
19
103
|
"a": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"letter-spacing": "0.025em", /* tracking-wide */
|
|
104
|
+
"font-weight": "700",
|
|
105
|
+
"letter-spacing": "0.025em",
|
|
23
106
|
"text-decoration-line": "underline",
|
|
24
|
-
"text-decoration-thickness": "2px",
|
|
25
|
-
"text-underline-offset": "4px",
|
|
107
|
+
"text-decoration-thickness": "2px",
|
|
108
|
+
"text-underline-offset": "4px",
|
|
26
109
|
"transition-property": "color, text-decoration-color",
|
|
27
|
-
"transition-duration": "150ms",
|
|
110
|
+
"transition-duration": "150ms",
|
|
28
111
|
},
|
|
29
112
|
|
|
30
113
|
"a:hover": {
|
|
31
|
-
|
|
32
|
-
|
|
114
|
+
"text-decoration-color": "oklch(var(--clampography-primary))",
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Mark
|
|
118
|
+
"mark": {
|
|
119
|
+
"background-color": "oklch(var(--clampography-primary))",
|
|
120
|
+
"color": "oklch(var(--clampography-background))",
|
|
121
|
+
"padding": "0.125rem var(--spacing-xs)",
|
|
122
|
+
"border-radius": "0.25rem",
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Deleted Text
|
|
126
|
+
"del": {
|
|
127
|
+
"text-decoration-color": "oklch(var(--clampography-secondary))",
|
|
128
|
+
"text-decoration-thickness": "2px",
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Buttons - All types
|
|
132
|
+
":where(button, [type='button'], [type='reset'], [type='submit'])": {
|
|
133
|
+
"padding": "var(--spacing-xs) var(--spacing-sm)",
|
|
134
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
135
|
+
"border-radius": "0.375rem", // ← Rounded corners
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Inputs - All types
|
|
139
|
+
":where(input:not([type='checkbox'], [type='radio']), textarea, select)": {
|
|
140
|
+
"padding": "var(--spacing-xs) var(--spacing-sm)",
|
|
141
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
142
|
+
"border-radius": "0.375rem", // ← Rounded corners
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Fieldset
|
|
146
|
+
"fieldset": {
|
|
147
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
148
|
+
"border-radius": "0.375rem",
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
"legend": {
|
|
152
|
+
"color": "oklch(var(--clampography-heading))",
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Details
|
|
156
|
+
"details": {
|
|
157
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
158
|
+
"border-radius": "0.375rem",
|
|
159
|
+
"padding": "0.5rem",
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
"summary": {
|
|
163
|
+
"color": "oklch(var(--clampography-heading))",
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
"details[open] > summary": {
|
|
167
|
+
"border-bottom": "1px solid oklch(var(--clampography-border))",
|
|
168
|
+
"padding-bottom": "0.5rem",
|
|
33
169
|
},
|
|
34
170
|
};
|
package/src/index.js
CHANGED
|
@@ -3,99 +3,133 @@ import { themes as builtInThemes } from "./themes.js";
|
|
|
3
3
|
import baseStyles from "./base.js";
|
|
4
4
|
import extraStyles from "./extra.js";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Helper to resolve boolean options from CSS configuration.
|
|
8
|
+
* CSS values often come as strings ("true"/"false"), which are both truthy in JS.
|
|
9
|
+
*/
|
|
10
|
+
const resolveBool = (value, defaultValue) => {
|
|
11
|
+
if (value === "false" || value === false) return false;
|
|
12
|
+
if (value === "true" || value === true) return true;
|
|
13
|
+
return defaultValue;
|
|
14
|
+
};
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
17
|
* Main plugin function.
|
|
8
18
|
*/
|
|
9
|
-
export default plugin.withOptions(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
export default plugin.withOptions(
|
|
20
|
+
(options = {}) => {
|
|
21
|
+
return ({ addBase }) => {
|
|
22
|
+
// 1. Load Base and Extra styles
|
|
23
|
+
// We use the helper to correctly parse "false" string from CSS
|
|
24
|
+
const includeBase = resolveBool(options.base, true); // Default: true
|
|
25
|
+
const includeExtra = resolveBool(options.extra, false); // Default: false
|
|
26
|
+
|
|
27
|
+
includeBase && addBase(baseStyles);
|
|
28
|
+
includeExtra && addBase(extraStyles);
|
|
29
|
+
|
|
30
|
+
// 2. Parse themes configuration
|
|
31
|
+
let configThemes = options.themes;
|
|
32
|
+
let themesToInclude = [];
|
|
33
|
+
let defaultThemeName = null;
|
|
34
|
+
let prefersDarkTheme = false;
|
|
35
|
+
let rootSelector = options.root ?? ":root";
|
|
36
|
+
|
|
37
|
+
// Normalize input to an array of strings
|
|
38
|
+
let rawThemeList = [];
|
|
39
|
+
|
|
40
|
+
if (typeof configThemes === "string") {
|
|
41
|
+
if (["all", "true", "yes"].includes(configThemes.trim())) {
|
|
42
|
+
// Special case: themes: all
|
|
43
|
+
rawThemeList = Object.keys(builtInThemes);
|
|
44
|
+
} else if (["false", "none", "no"].includes(configThemes.trim())) {
|
|
45
|
+
// Explicitly disabled themes
|
|
46
|
+
rawThemeList = [];
|
|
47
|
+
} else {
|
|
48
|
+
rawThemeList = configThemes.split(",");
|
|
49
|
+
}
|
|
50
|
+
} else if (Array.isArray(configThemes)) {
|
|
51
|
+
rawThemeList = configThemes;
|
|
37
52
|
} else {
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} else if (Array.isArray(configThemes)) {
|
|
42
|
-
rawThemeList = configThemes;
|
|
43
|
-
} else {
|
|
44
|
-
// Default fallback if nothing provided
|
|
45
|
-
rawThemeList = ["light", "dark"];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// 3. Process the list and look for flags (--default, --prefersdark)
|
|
49
|
-
// If "all" was used, we don't look for flags (we use default light/dark logic) unless implemented otherwise.
|
|
50
|
-
// Here we focus on the explicit list.
|
|
51
|
-
|
|
52
|
-
rawThemeList.forEach((rawItem) => {
|
|
53
|
-
let themeName = rawItem.trim();
|
|
54
|
-
|
|
55
|
-
// Ignore empty entries
|
|
56
|
-
if (!themeName) return;
|
|
57
|
-
|
|
58
|
-
// Check for --default flag
|
|
59
|
-
if (themeName.includes("--default")) {
|
|
60
|
-
themeName = themeName.replace("--default", "").trim();
|
|
61
|
-
defaultThemeName = themeName;
|
|
53
|
+
// Default behavior: NO themes loaded automatically.
|
|
54
|
+
// User must specify themes to load them.
|
|
55
|
+
rawThemeList = [];
|
|
62
56
|
}
|
|
63
57
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
themeName =
|
|
67
|
-
|
|
58
|
+
// 3. Process the list and look for flags (--default, --prefersdark)
|
|
59
|
+
rawThemeList.forEach((rawItem) => {
|
|
60
|
+
let themeName = rawItem.trim();
|
|
61
|
+
|
|
62
|
+
// Ignore empty entries
|
|
63
|
+
if (!themeName) return;
|
|
64
|
+
|
|
65
|
+
// Check for --default flag
|
|
66
|
+
if (themeName.includes("--default")) {
|
|
67
|
+
themeName = themeName.replace("--default", "").trim();
|
|
68
|
+
defaultThemeName = themeName;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check for --prefersdark flag
|
|
72
|
+
if (themeName.toLowerCase().includes("--prefersdark")) {
|
|
73
|
+
themeName = themeName.replace(/--prefersdark/i, "").trim();
|
|
74
|
+
prefersDarkTheme = themeName;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if theme exists in the database
|
|
78
|
+
if (builtInThemes[themeName]) {
|
|
79
|
+
themesToInclude.push(themeName);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// If list is empty after filtering, stop here
|
|
84
|
+
if (
|
|
85
|
+
themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
|
|
86
|
+
) return;
|
|
87
|
+
|
|
88
|
+
// 4. Generate CSS
|
|
89
|
+
const themeStyles = {};
|
|
90
|
+
|
|
91
|
+
// A. Default theme (:root)
|
|
92
|
+
if (defaultThemeName && builtInThemes[defaultThemeName]) {
|
|
93
|
+
themeStyles[rootSelector] = builtInThemes[defaultThemeName];
|
|
68
94
|
}
|
|
69
95
|
|
|
70
|
-
//
|
|
71
|
-
if (builtInThemes[
|
|
72
|
-
|
|
96
|
+
// B. Theme for prefers-color-scheme: dark
|
|
97
|
+
if (prefersDarkTheme && builtInThemes[prefersDarkTheme]) {
|
|
98
|
+
themeStyles["@media (prefers-color-scheme: dark)"] = {
|
|
99
|
+
[rootSelector]: builtInThemes[prefersDarkTheme],
|
|
100
|
+
};
|
|
73
101
|
}
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
|
|
103
|
+
// C. Scoped styles [data-theme="..."]
|
|
104
|
+
themesToInclude.forEach((themeName) => {
|
|
105
|
+
themeStyles[`[data-theme="${themeName}"]`] = builtInThemes[themeName];
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
addBase(themeStyles);
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
// Theme extension - enables utilities like bg-surface, text-heading, etc.
|
|
112
|
+
(options = {}) => {
|
|
113
|
+
return {
|
|
114
|
+
theme: {
|
|
115
|
+
extend: {
|
|
116
|
+
colors: {
|
|
117
|
+
background: "oklch(var(--clampography-background) / <alpha-value>)",
|
|
118
|
+
border: "oklch(var(--clampography-border) / <alpha-value>)",
|
|
119
|
+
error: "oklch(var(--clampography-error) / <alpha-value>)",
|
|
120
|
+
heading: "oklch(var(--clampography-heading) / <alpha-value>)",
|
|
121
|
+
info: "oklch(var(--clampography-info) / <alpha-value>)",
|
|
122
|
+
link: "oklch(var(--clampography-link) / <alpha-value>)",
|
|
123
|
+
muted: "oklch(var(--clampography-muted) / <alpha-value>)",
|
|
124
|
+
primary: "oklch(var(--clampography-primary) / <alpha-value>)",
|
|
125
|
+
secondary: "oklch(var(--clampography-secondary) / <alpha-value>)",
|
|
126
|
+
success: "oklch(var(--clampography-success) / <alpha-value>)",
|
|
127
|
+
surface: "oklch(var(--clampography-surface) / <alpha-value>)",
|
|
128
|
+
text: "oklch(var(--clampography-text) / <alpha-value>)",
|
|
129
|
+
warning: "oklch(var(--clampography-warning) / <alpha-value>)",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
);
|