@uniweb/build 0.8.12 → 0.8.14

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.
@@ -1,400 +0,0 @@
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
- 'section': 'var(--neutral-50)',
20
- 'card': 'var(--neutral-100)',
21
- 'muted': 'var(--neutral-200)',
22
- 'body': 'var(--neutral-950)',
23
- 'heading': 'var(--neutral-900)',
24
- 'subtle': 'var(--neutral-600)',
25
- 'border': 'var(--neutral-200)',
26
- 'ring': 'var(--primary-500)',
27
- 'link': 'var(--primary-600)',
28
- 'link-hover': 'var(--primary-700)',
29
- 'primary': 'var(--primary-600)',
30
- 'primary-foreground': 'white',
31
- 'primary-hover': 'var(--primary-700)',
32
- 'primary-border': 'transparent',
33
- 'secondary': 'white',
34
- 'secondary-foreground': 'var(--neutral-900)',
35
- 'secondary-hover': 'var(--neutral-100)',
36
- 'secondary-border': 'var(--neutral-300)',
37
- 'success': '#16a34a',
38
- 'success-subtle': '#f0fdf4',
39
- 'warning': '#d97706',
40
- 'warning-subtle': '#fffbeb',
41
- 'error': '#dc2626',
42
- 'error-subtle': '#fef2f2',
43
- 'info': '#2563eb',
44
- 'info-subtle': '#eff6ff',
45
- },
46
- medium: {
47
- 'section': 'var(--neutral-100)',
48
- 'card': 'var(--neutral-200)',
49
- 'muted': 'var(--neutral-300)',
50
- 'body': 'var(--neutral-950)',
51
- 'heading': 'var(--neutral-900)',
52
- 'subtle': 'var(--neutral-700)',
53
- 'border': 'var(--neutral-300)',
54
- 'ring': 'var(--primary-500)',
55
- 'link': 'var(--primary-600)',
56
- 'link-hover': 'var(--primary-700)',
57
- 'primary': 'var(--primary-600)',
58
- 'primary-foreground': 'white',
59
- 'primary-hover': 'var(--primary-700)',
60
- 'primary-border': 'transparent',
61
- 'secondary': 'white',
62
- 'secondary-foreground': 'var(--neutral-900)',
63
- 'secondary-hover': 'var(--neutral-100)',
64
- 'secondary-border': 'var(--neutral-300)',
65
- 'success': '#16a34a',
66
- 'success-subtle': '#f0fdf4',
67
- 'warning': '#d97706',
68
- 'warning-subtle': '#fffbeb',
69
- 'error': '#dc2626',
70
- 'error-subtle': '#fef2f2',
71
- 'info': '#2563eb',
72
- 'info-subtle': '#eff6ff',
73
- },
74
- dark: {
75
- 'section': 'var(--neutral-900)',
76
- 'card': 'var(--neutral-800)',
77
- 'muted': 'var(--neutral-700)',
78
- 'body': 'var(--neutral-50)',
79
- 'heading': 'white',
80
- 'subtle': 'var(--neutral-400)',
81
- 'border': 'var(--neutral-700)',
82
- 'ring': 'var(--primary-500)',
83
- 'link': 'var(--primary-400)',
84
- 'link-hover': 'var(--primary-300)',
85
- 'primary': 'var(--primary-500)',
86
- 'primary-foreground': 'white',
87
- 'primary-hover': 'var(--primary-400)',
88
- 'primary-border': 'transparent',
89
- 'secondary': 'var(--neutral-800)',
90
- 'secondary-foreground': 'var(--neutral-100)',
91
- 'secondary-hover': 'var(--neutral-700)',
92
- 'secondary-border': 'var(--neutral-600)',
93
- 'success': '#4ade80',
94
- 'success-subtle': '#052e16',
95
- 'warning': '#fbbf24',
96
- 'warning-subtle': '#451a03',
97
- 'error': '#f87171',
98
- 'error-subtle': '#450a0a',
99
- 'info': '#60a5fa',
100
- 'info-subtle': '#172554',
101
- },
102
- }
103
-
104
- // Default color palette configuration
105
- const DEFAULT_COLORS = {
106
- primary: '#3b82f6', // Blue
107
- secondary: '#64748b', // Slate
108
- accent: '#8b5cf6', // Purple
109
- neutral: '#78716c', // Stone
110
- }
111
-
112
- // Shade levels for CSS variable generation
113
- const SHADE_LEVELS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
114
-
115
- /**
116
- * Generate CSS variable declarations from an object
117
- *
118
- * @param {Object} vars - Object with variable names as keys
119
- * @param {string} indent - Indentation string
120
- * @returns {string} CSS variable declarations
121
- */
122
- function generateVarDeclarations(vars, indent = ' ') {
123
- return Object.entries(vars)
124
- .map(([name, value]) => `${indent}--${name}: ${value};`)
125
- .join('\n')
126
- }
127
-
128
- /**
129
- * Generate color palette CSS variables
130
- *
131
- * @param {Object} palettes - Object with palette name → shades
132
- * @returns {string} CSS variable declarations for all palettes
133
- */
134
- function generatePaletteVars(palettes) {
135
- const lines = []
136
-
137
- for (const [name, shades] of Object.entries(palettes)) {
138
- for (const level of SHADE_LEVELS) {
139
- if (shades[level]) {
140
- lines.push(` --${name}-${level}: ${shades[level]};`)
141
- }
142
- }
143
- }
144
-
145
- return lines.join('\n')
146
- }
147
-
148
- /**
149
- * Generate context class CSS
150
- *
151
- * @param {string} context - Context name (light, medium, dark)
152
- * @param {Object} tokens - Token overrides
153
- * @returns {string} CSS for context class
154
- */
155
- function generateContextCSS(context, tokens = {}) {
156
- const defaultTokens = DEFAULT_CONTEXT_TOKENS[context] || DEFAULT_CONTEXT_TOKENS.light
157
- const mergedTokens = { ...defaultTokens, ...tokens }
158
-
159
- const vars = generateVarDeclarations(mergedTokens)
160
-
161
- return `.context-${context} {\n${vars}\n background-color: var(--section);\n}`
162
- }
163
-
164
- /**
165
- * Generate dark scheme CSS (for site-wide dark mode toggle)
166
- *
167
- * @param {Object} config - Appearance configuration
168
- * @returns {string} CSS for dark scheme support
169
- */
170
- function generateDarkSchemeCSS(config = {}) {
171
- const { respectSystemPreference = true } = config
172
-
173
- // Dark scheme tokens - similar to dark context but at root level
174
- const darkTokens = {
175
- 'section': 'var(--neutral-950)',
176
- 'card': 'var(--neutral-900)',
177
- 'muted': 'var(--neutral-800)',
178
- 'body': 'var(--neutral-50)',
179
- 'heading': 'white',
180
- 'subtle': 'var(--neutral-400)',
181
- 'border': 'var(--neutral-800)',
182
- 'ring': 'var(--primary-500)',
183
- 'link': 'var(--primary-400)',
184
- 'link-hover': 'var(--primary-300)',
185
- 'primary': 'var(--primary-500)',
186
- 'primary-foreground': 'white',
187
- 'primary-hover': 'var(--primary-400)',
188
- 'primary-border': 'transparent',
189
- 'secondary': 'var(--neutral-800)',
190
- 'secondary-foreground': 'var(--neutral-100)',
191
- 'secondary-hover': 'var(--neutral-700)',
192
- 'secondary-border': 'var(--neutral-600)',
193
- 'success': '#4ade80',
194
- 'success-subtle': '#052e16',
195
- 'warning': '#fbbf24',
196
- 'warning-subtle': '#451a03',
197
- 'error': '#f87171',
198
- 'error-subtle': '#450a0a',
199
- 'info': '#60a5fa',
200
- 'info-subtle': '#172554',
201
- }
202
-
203
- const vars = generateVarDeclarations(darkTokens)
204
-
205
- let css = `/* Dark scheme (user preference) */\n`
206
- css += `.scheme-dark {\n${vars}\n}\n`
207
-
208
- if (respectSystemPreference) {
209
- css += `\n@media (prefers-color-scheme: dark) {\n`
210
- css += ` :root:not(.scheme-light) {\n`
211
- for (const [name, value] of Object.entries(darkTokens)) {
212
- css += ` --${name}: ${value};\n`
213
- }
214
- css += ` }\n`
215
- css += `}\n`
216
- }
217
-
218
- return css
219
- }
220
-
221
- /**
222
- * Generate font CSS
223
- *
224
- * @param {Object} fonts - Font configuration
225
- * @returns {string} CSS for fonts
226
- */
227
- function generateFontCSS(fonts = {}) {
228
- const lines = []
229
-
230
- // Font imports
231
- if (fonts.import && Array.isArray(fonts.import)) {
232
- for (const font of fonts.import) {
233
- if (font.url) {
234
- lines.push(`@import url('${font.url}');`)
235
- }
236
- }
237
- if (lines.length > 0) {
238
- lines.push('') // Empty line after imports
239
- }
240
- }
241
-
242
- // Font family variables
243
- const fontVars = []
244
- if (fonts.body) {
245
- fontVars.push(` --font-body: ${fonts.body};`)
246
- }
247
- if (fonts.heading) {
248
- fontVars.push(` --font-heading: ${fonts.heading};`)
249
- }
250
- if (fonts.mono) {
251
- fontVars.push(` --font-mono: ${fonts.mono};`)
252
- }
253
-
254
- if (fontVars.length > 0) {
255
- lines.push(':root {')
256
- lines.push(...fontVars)
257
- lines.push('}')
258
- }
259
-
260
- return lines.join('\n')
261
- }
262
-
263
- /**
264
- * Generate foundation-specific CSS variables
265
- *
266
- * @param {Object} vars - Foundation variables from vars.js
267
- * @returns {string} CSS variable declarations
268
- */
269
- function generateFoundationVars(vars = {}) {
270
- if (!vars || Object.keys(vars).length === 0) {
271
- return ''
272
- }
273
-
274
- const declarations = []
275
-
276
- for (const [name, config] of Object.entries(vars)) {
277
- const value = typeof config === 'object' ? config.default : config
278
- if (value !== undefined) {
279
- declarations.push(` --${name}: ${value};`)
280
- }
281
- }
282
-
283
- if (declarations.length === 0) {
284
- return ''
285
- }
286
-
287
- return `:root {\n${declarations.join('\n')}\n}`
288
- }
289
-
290
- /**
291
- * Generate complete theme CSS
292
- *
293
- * @param {Object} config - Processed theme configuration
294
- * @param {Object} config.colors - Color palette configuration
295
- * @param {Object} config.contexts - Context token overrides
296
- * @param {Object} config.fonts - Font configuration
297
- * @param {Object} config.appearance - Appearance settings (dark mode, etc.)
298
- * @param {Object} config.foundationVars - Foundation-specific variables
299
- * @returns {string} Complete CSS string
300
- */
301
- export function generateThemeCSS(config = {}) {
302
- const {
303
- colors = DEFAULT_COLORS,
304
- contexts = {},
305
- fonts = {},
306
- appearance = {},
307
- foundationVars = {},
308
- } = config
309
-
310
- const sections = []
311
-
312
- // 1. Font imports and variables
313
- const fontCSS = generateFontCSS(fonts)
314
- if (fontCSS) {
315
- sections.push('/* Typography */\n' + fontCSS)
316
- }
317
-
318
- // 2. Color palettes
319
- const palettes = generatePalettes(colors)
320
- const paletteVars = generatePaletteVars(palettes)
321
- sections.push(`/* Color Palettes */\n:root {\n${paletteVars}\n}`)
322
-
323
- // 3. Default semantic tokens (applied to :root for global defaults)
324
- const defaultTokens = { ...DEFAULT_CONTEXT_TOKENS.light, ...(contexts.light || {}) }
325
- const defaultVars = generateVarDeclarations(defaultTokens)
326
- sections.push(`/* Default Semantic Tokens */\n:root {\n${defaultVars}\n}`)
327
-
328
- // 4. Context classes
329
- const contextCSS = [
330
- generateContextCSS('light', contexts.light),
331
- generateContextCSS('medium', contexts.medium),
332
- generateContextCSS('dark', contexts.dark),
333
- ]
334
- sections.push('/* Color Contexts */\n' + contextCSS.join('\n\n'))
335
-
336
- // 5. Foundation variables
337
- const foundationCSS = generateFoundationVars(foundationVars)
338
- if (foundationCSS) {
339
- sections.push('/* Foundation Variables */\n' + foundationCSS)
340
- }
341
-
342
- // 6. Dark scheme support (if enabled)
343
- if (appearance.allowToggle || appearance.schemes?.includes('dark')) {
344
- sections.push(generateDarkSchemeCSS(appearance))
345
- }
346
-
347
- // 7. Site background (if specified in theme.yml)
348
- if (config.background) {
349
- sections.push(`/* Site Background */\nbody {\n background: ${config.background};\n}`)
350
- }
351
-
352
- // 8. Inline text styles (if specified in theme.yml)
353
- if (config.inline && typeof config.inline === 'object') {
354
- const rules = Object.entries(config.inline)
355
- .filter(([, styles]) => styles && typeof styles === 'object')
356
- .map(([name, styles]) => {
357
- const declarations = Object.entries(styles)
358
- .map(([prop, value]) => ` ${prop}: ${value};`)
359
- .join('\n')
360
- return `span[${name}] {\n${declarations}\n}`
361
- })
362
- if (rules.length > 0) {
363
- sections.push('/* Inline Text Styles */\n' + rules.join('\n\n'))
364
- }
365
- }
366
-
367
- return sections.join('\n\n')
368
- }
369
-
370
- /**
371
- * Generate CSS for a single context (useful for testing)
372
- */
373
- export { generateContextCSS }
374
-
375
- /**
376
- * Generate palette CSS variables (useful for testing)
377
- */
378
- export { generatePaletteVars }
379
-
380
- /**
381
- * Get default context tokens
382
- */
383
- export function getDefaultContextTokens() {
384
- return JSON.parse(JSON.stringify(DEFAULT_CONTEXT_TOKENS))
385
- }
386
-
387
- /**
388
- * Get default colors
389
- */
390
- export function getDefaultColors() {
391
- return { ...DEFAULT_COLORS }
392
- }
393
-
394
- export default {
395
- generateThemeCSS,
396
- generateContextCSS,
397
- generatePaletteVars,
398
- getDefaultContextTokens,
399
- getDefaultColors,
400
- }