@uniweb/build 0.1.32 → 0.2.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.
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Theme CSS Generator
3
+ *
4
+ * Generates complete CSS for site theming including:
5
+ * - Color palettes as CSS custom properties
6
+ * - Context classes (light/medium/dark) with semantic tokens
7
+ * - Foundation-specific variables
8
+ * - Optional dark scheme support
9
+ *
10
+ * @module @uniweb/build/theme/css-generator
11
+ */
12
+
13
+ import { generatePalettes, formatOklch } from './shade-generator.js'
14
+
15
+ // Default semantic tokens for each context
16
+ // These map abstract concepts to specific palette values
17
+ const DEFAULT_CONTEXT_TOKENS = {
18
+ light: {
19
+ 'bg': 'var(--neutral-50)',
20
+ 'bg-subtle': 'var(--neutral-100)',
21
+ 'bg-muted': 'var(--neutral-200)',
22
+ 'text': 'var(--neutral-950)',
23
+ 'text-muted': 'var(--neutral-600)',
24
+ 'text-subtle': 'var(--neutral-500)',
25
+ 'heading': 'var(--neutral-900)',
26
+ 'link': 'var(--primary-600)',
27
+ 'link-hover': 'var(--primary-700)',
28
+ 'border': 'var(--neutral-200)',
29
+ 'border-muted': 'var(--neutral-100)',
30
+ 'ring': 'var(--primary-500)',
31
+ 'btn-primary-bg': 'var(--primary-600)',
32
+ 'btn-primary-text': 'white',
33
+ 'btn-primary-hover': 'var(--primary-700)',
34
+ 'btn-secondary-bg': 'var(--neutral-100)',
35
+ 'btn-secondary-text': 'var(--neutral-900)',
36
+ 'btn-secondary-hover': 'var(--neutral-200)',
37
+ },
38
+ medium: {
39
+ 'bg': 'var(--neutral-100)',
40
+ 'bg-subtle': 'var(--neutral-200)',
41
+ 'bg-muted': 'var(--neutral-300)',
42
+ 'text': 'var(--neutral-950)',
43
+ 'text-muted': 'var(--neutral-700)',
44
+ 'text-subtle': 'var(--neutral-600)',
45
+ 'heading': 'var(--neutral-900)',
46
+ 'link': 'var(--primary-600)',
47
+ 'link-hover': 'var(--primary-700)',
48
+ 'border': 'var(--neutral-300)',
49
+ 'border-muted': 'var(--neutral-200)',
50
+ 'ring': 'var(--primary-500)',
51
+ 'btn-primary-bg': 'var(--primary-600)',
52
+ 'btn-primary-text': 'white',
53
+ 'btn-primary-hover': 'var(--primary-700)',
54
+ 'btn-secondary-bg': 'var(--neutral-200)',
55
+ 'btn-secondary-text': 'var(--neutral-900)',
56
+ 'btn-secondary-hover': 'var(--neutral-300)',
57
+ },
58
+ dark: {
59
+ 'bg': 'var(--neutral-900)',
60
+ 'bg-subtle': 'var(--neutral-800)',
61
+ 'bg-muted': 'var(--neutral-700)',
62
+ 'text': 'var(--neutral-50)',
63
+ 'text-muted': 'var(--neutral-300)',
64
+ 'text-subtle': 'var(--neutral-400)',
65
+ 'heading': 'white',
66
+ 'link': 'var(--primary-400)',
67
+ 'link-hover': 'var(--primary-300)',
68
+ 'border': 'var(--neutral-700)',
69
+ 'border-muted': 'var(--neutral-800)',
70
+ 'ring': 'var(--primary-500)',
71
+ 'btn-primary-bg': 'var(--primary-500)',
72
+ 'btn-primary-text': 'white',
73
+ 'btn-primary-hover': 'var(--primary-400)',
74
+ 'btn-secondary-bg': 'var(--neutral-800)',
75
+ 'btn-secondary-text': 'var(--neutral-100)',
76
+ 'btn-secondary-hover': 'var(--neutral-700)',
77
+ },
78
+ }
79
+
80
+ // Default color palette configuration
81
+ const DEFAULT_COLORS = {
82
+ primary: '#3b82f6', // Blue
83
+ secondary: '#64748b', // Slate
84
+ accent: '#8b5cf6', // Purple
85
+ neutral: '#71717a', // Zinc
86
+ }
87
+
88
+ // Shade levels for CSS variable generation
89
+ const SHADE_LEVELS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
90
+
91
+ /**
92
+ * Generate CSS variable declarations from an object
93
+ *
94
+ * @param {Object} vars - Object with variable names as keys
95
+ * @param {string} indent - Indentation string
96
+ * @returns {string} CSS variable declarations
97
+ */
98
+ function generateVarDeclarations(vars, indent = ' ') {
99
+ return Object.entries(vars)
100
+ .map(([name, value]) => `${indent}--${name}: ${value};`)
101
+ .join('\n')
102
+ }
103
+
104
+ /**
105
+ * Generate color palette CSS variables
106
+ *
107
+ * @param {Object} palettes - Object with palette name → shades
108
+ * @returns {string} CSS variable declarations for all palettes
109
+ */
110
+ function generatePaletteVars(palettes) {
111
+ const lines = []
112
+
113
+ for (const [name, shades] of Object.entries(palettes)) {
114
+ for (const level of SHADE_LEVELS) {
115
+ if (shades[level]) {
116
+ lines.push(` --${name}-${level}: ${shades[level]};`)
117
+ }
118
+ }
119
+ }
120
+
121
+ return lines.join('\n')
122
+ }
123
+
124
+ /**
125
+ * Generate context class CSS
126
+ *
127
+ * @param {string} context - Context name (light, medium, dark)
128
+ * @param {Object} tokens - Token overrides
129
+ * @returns {string} CSS for context class
130
+ */
131
+ function generateContextCSS(context, tokens = {}) {
132
+ const defaultTokens = DEFAULT_CONTEXT_TOKENS[context] || DEFAULT_CONTEXT_TOKENS.light
133
+ const mergedTokens = { ...defaultTokens, ...tokens }
134
+
135
+ const vars = generateVarDeclarations(mergedTokens)
136
+
137
+ return `.context-${context} {\n${vars}\n}`
138
+ }
139
+
140
+ /**
141
+ * Generate dark scheme CSS (for site-wide dark mode toggle)
142
+ *
143
+ * @param {Object} config - Appearance configuration
144
+ * @returns {string} CSS for dark scheme support
145
+ */
146
+ function generateDarkSchemeCSS(config = {}) {
147
+ const { respectSystemPreference = true } = config
148
+
149
+ // Dark scheme tokens - similar to dark context but at root level
150
+ const darkTokens = {
151
+ 'bg': 'var(--neutral-950)',
152
+ 'bg-subtle': 'var(--neutral-900)',
153
+ 'bg-muted': 'var(--neutral-800)',
154
+ 'text': 'var(--neutral-50)',
155
+ 'text-muted': 'var(--neutral-300)',
156
+ 'text-subtle': 'var(--neutral-400)',
157
+ 'heading': 'white',
158
+ 'link': 'var(--primary-400)',
159
+ 'link-hover': 'var(--primary-300)',
160
+ 'border': 'var(--neutral-800)',
161
+ 'border-muted': 'var(--neutral-900)',
162
+ }
163
+
164
+ const vars = generateVarDeclarations(darkTokens)
165
+
166
+ let css = `/* Dark scheme (user preference) */\n`
167
+ css += `.scheme-dark {\n${vars}\n}\n`
168
+
169
+ if (respectSystemPreference) {
170
+ css += `\n@media (prefers-color-scheme: dark) {\n`
171
+ css += ` :root:not(.scheme-light) {\n`
172
+ for (const [name, value] of Object.entries(darkTokens)) {
173
+ css += ` --${name}: ${value};\n`
174
+ }
175
+ css += ` }\n`
176
+ css += `}\n`
177
+ }
178
+
179
+ return css
180
+ }
181
+
182
+ /**
183
+ * Generate font CSS
184
+ *
185
+ * @param {Object} fonts - Font configuration
186
+ * @returns {string} CSS for fonts
187
+ */
188
+ function generateFontCSS(fonts = {}) {
189
+ const lines = []
190
+
191
+ // Font imports
192
+ if (fonts.import && Array.isArray(fonts.import)) {
193
+ for (const font of fonts.import) {
194
+ if (font.url) {
195
+ lines.push(`@import url('${font.url}');`)
196
+ }
197
+ }
198
+ if (lines.length > 0) {
199
+ lines.push('') // Empty line after imports
200
+ }
201
+ }
202
+
203
+ // Font family variables
204
+ const fontVars = []
205
+ if (fonts.body) {
206
+ fontVars.push(` --font-body: ${fonts.body};`)
207
+ }
208
+ if (fonts.heading) {
209
+ fontVars.push(` --font-heading: ${fonts.heading};`)
210
+ }
211
+ if (fonts.mono) {
212
+ fontVars.push(` --font-mono: ${fonts.mono};`)
213
+ }
214
+
215
+ if (fontVars.length > 0) {
216
+ lines.push(':root {')
217
+ lines.push(...fontVars)
218
+ lines.push('}')
219
+ }
220
+
221
+ return lines.join('\n')
222
+ }
223
+
224
+ /**
225
+ * Generate foundation-specific CSS variables
226
+ *
227
+ * @param {Object} vars - Foundation variables from vars.js
228
+ * @returns {string} CSS variable declarations
229
+ */
230
+ function generateFoundationVars(vars = {}) {
231
+ if (!vars || Object.keys(vars).length === 0) {
232
+ return ''
233
+ }
234
+
235
+ const declarations = []
236
+
237
+ for (const [name, config] of Object.entries(vars)) {
238
+ const value = typeof config === 'object' ? config.default : config
239
+ if (value !== undefined) {
240
+ declarations.push(` --${name}: ${value};`)
241
+ }
242
+ }
243
+
244
+ if (declarations.length === 0) {
245
+ return ''
246
+ }
247
+
248
+ return `:root {\n${declarations.join('\n')}\n}`
249
+ }
250
+
251
+ /**
252
+ * Generate complete theme CSS
253
+ *
254
+ * @param {Object} config - Processed theme configuration
255
+ * @param {Object} config.colors - Color palette configuration
256
+ * @param {Object} config.contexts - Context token overrides
257
+ * @param {Object} config.fonts - Font configuration
258
+ * @param {Object} config.appearance - Appearance settings (dark mode, etc.)
259
+ * @param {Object} config.foundationVars - Foundation-specific variables
260
+ * @returns {string} Complete CSS string
261
+ */
262
+ export function generateThemeCSS(config = {}) {
263
+ const {
264
+ colors = DEFAULT_COLORS,
265
+ contexts = {},
266
+ fonts = {},
267
+ appearance = {},
268
+ foundationVars = {},
269
+ } = config
270
+
271
+ const sections = []
272
+
273
+ // 1. Font imports and variables
274
+ const fontCSS = generateFontCSS(fonts)
275
+ if (fontCSS) {
276
+ sections.push('/* Typography */\n' + fontCSS)
277
+ }
278
+
279
+ // 2. Color palettes
280
+ const palettes = generatePalettes(colors)
281
+ const paletteVars = generatePaletteVars(palettes)
282
+ sections.push(`/* Color Palettes */\n:root {\n${paletteVars}\n}`)
283
+
284
+ // 3. Default semantic tokens (applied to :root for global defaults)
285
+ const defaultTokens = { ...DEFAULT_CONTEXT_TOKENS.light, ...(contexts.light || {}) }
286
+ const defaultVars = generateVarDeclarations(defaultTokens)
287
+ sections.push(`/* Default Semantic Tokens */\n:root {\n${defaultVars}\n}`)
288
+
289
+ // 4. Context classes
290
+ const contextCSS = [
291
+ generateContextCSS('light', contexts.light),
292
+ generateContextCSS('medium', contexts.medium),
293
+ generateContextCSS('dark', contexts.dark),
294
+ ]
295
+ sections.push('/* Color Contexts */\n' + contextCSS.join('\n\n'))
296
+
297
+ // 5. Foundation variables
298
+ const foundationCSS = generateFoundationVars(foundationVars)
299
+ if (foundationCSS) {
300
+ sections.push('/* Foundation Variables */\n' + foundationCSS)
301
+ }
302
+
303
+ // 6. Dark scheme support (if enabled)
304
+ if (appearance.allowToggle || appearance.schemes?.includes('dark')) {
305
+ sections.push(generateDarkSchemeCSS(appearance))
306
+ }
307
+
308
+ return sections.join('\n\n')
309
+ }
310
+
311
+ /**
312
+ * Generate CSS for a single context (useful for testing)
313
+ */
314
+ export { generateContextCSS }
315
+
316
+ /**
317
+ * Generate palette CSS variables (useful for testing)
318
+ */
319
+ export { generatePaletteVars }
320
+
321
+ /**
322
+ * Get default context tokens
323
+ */
324
+ export function getDefaultContextTokens() {
325
+ return JSON.parse(JSON.stringify(DEFAULT_CONTEXT_TOKENS))
326
+ }
327
+
328
+ /**
329
+ * Get default colors
330
+ */
331
+ export function getDefaultColors() {
332
+ return { ...DEFAULT_COLORS }
333
+ }
334
+
335
+ export default {
336
+ generateThemeCSS,
337
+ generateContextCSS,
338
+ generatePaletteVars,
339
+ getDefaultContextTokens,
340
+ getDefaultColors,
341
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Theme Module
3
+ *
4
+ * Exports all theme-related utilities for the build process.
5
+ *
6
+ * @module @uniweb/build/theme
7
+ */
8
+
9
+ // Shade generation
10
+ export {
11
+ parseColor,
12
+ formatOklch,
13
+ formatHex,
14
+ generateShades,
15
+ generatePalettes,
16
+ isValidColor,
17
+ getShadeLevels,
18
+ } from './shade-generator.js'
19
+
20
+ // CSS generation
21
+ export {
22
+ generateThemeCSS,
23
+ generateContextCSS,
24
+ generatePaletteVars,
25
+ getDefaultContextTokens,
26
+ getDefaultColors,
27
+ } from './css-generator.js'
28
+
29
+ // Theme processing
30
+ export {
31
+ validateThemeConfig,
32
+ processTheme,
33
+ extractFoundationVars,
34
+ foundationHasVars,
35
+ } from './processor.js'
36
+
37
+ // Default export for convenience
38
+ import { processTheme } from './processor.js'
39
+ import { generateThemeCSS } from './css-generator.js'
40
+
41
+ /**
42
+ * Process theme configuration and generate CSS in one step
43
+ *
44
+ * @param {Object} themeYml - Raw theme.yml content
45
+ * @param {Object} options - Processing options
46
+ * @param {Object} options.foundationVars - Foundation variables
47
+ * @returns {{ css: string, config: Object, errors: string[], warnings: string[] }}
48
+ */
49
+ export function buildTheme(themeYml = {}, options = {}) {
50
+ const { config, errors, warnings } = processTheme(themeYml, options)
51
+ const css = generateThemeCSS(config)
52
+
53
+ return {
54
+ css,
55
+ config,
56
+ errors,
57
+ warnings,
58
+ }
59
+ }
60
+
61
+ export default {
62
+ buildTheme,
63
+ processTheme,
64
+ generateThemeCSS,
65
+ }