@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.
- package/README.md +30 -1
- package/package.json +3 -3
- package/src/docs.js +2 -2
- package/src/generate-entry.js +26 -5
- package/src/index.js +2 -1
- package/src/prerender.js +18 -2
- package/src/schema.js +78 -11
- package/src/site/config.js +2 -1
- package/src/site/content-collector.js +265 -20
- package/src/site/plugin.js +14 -4
- package/src/theme/css-generator.js +341 -0
- package/src/theme/index.js +65 -0
- package/src/theme/processor.js +422 -0
- package/src/theme/shade-generator.js +666 -0
|
@@ -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
|
+
}
|