holygrail5 1.0.2
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 +631 -0
- package/config.json +365 -0
- package/generator.js +58 -0
- package/package.json +48 -0
- package/src/cli-variables.js +147 -0
- package/src/config.js +62 -0
- package/src/dev.js +47 -0
- package/src/guide.js +1798 -0
- package/src/parser.js +624 -0
- package/src/utils.js +74 -0
- package/src/variables-manager.js +248 -0
- package/src/watch.js +88 -0
package/src/parser.js
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
// Parseador JSON a CSS
|
|
2
|
+
// Convierte la configuración JSON en CSS con variables compartidas y clases responsive
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { toKebabCase, pxToRem, getFontFamilyName } = require('./utils');
|
|
7
|
+
const { BREAKPOINTS } = require('./config');
|
|
8
|
+
|
|
9
|
+
// Lista de propiedades de tipografía que se procesan
|
|
10
|
+
const PROPERTIES = ['fontFamily', 'fontWeight', 'fontSize', 'lineHeight', 'letterSpacing', 'textTransform'];
|
|
11
|
+
|
|
12
|
+
// Genera el nombre de variable CSS para una fuente
|
|
13
|
+
// Usa el nombre de la fuente (como "primary" o "secondary") para crear la variable
|
|
14
|
+
function getFontFamilyVarName(fontFamilyName, prefix, category) {
|
|
15
|
+
return `--${prefix}-${category}-font-family-${fontFamilyName}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Genera el nombre de variable CSS para line-height
|
|
19
|
+
// Convierte el valor numérico a un formato válido para nombres de variables
|
|
20
|
+
function getLineHeightVarName(lineHeightValue, prefix, category) {
|
|
21
|
+
return `--${prefix}-${category}-line-height-${lineHeightValue.toString().replace('.', '-')}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Genera el nombre de variable CSS compartida para cualquier propiedad
|
|
25
|
+
// Convierte el nombre de la propiedad a kebab-case y el valor a un formato válido
|
|
26
|
+
// Para fontSize y letterSpacing, limpia las unidades (px, rem) y puntos
|
|
27
|
+
function getSharedVarName(prop, value, prefix, category) {
|
|
28
|
+
const propName = toKebabCase(prop);
|
|
29
|
+
let name = value.toString();
|
|
30
|
+
|
|
31
|
+
if (prop === 'fontSize') {
|
|
32
|
+
name = name.endsWith('px') ? name.replace('px', '').replace('.', '-') : name.replace(/rem/g, '').replace('.', '-');
|
|
33
|
+
} else if (prop === 'letterSpacing') {
|
|
34
|
+
name = name.replace(/rem/g, '').replace('.', '-');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return `--${prefix}-${category}-${propName}-${name}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Carga las variables CSS históricas guardadas previamente
|
|
41
|
+
// Esto asegura que las variables nunca se eliminen aunque se borren las clases que las usaban
|
|
42
|
+
function loadHistoricalVariables(historicalVarsPath) {
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(historicalVarsPath)) {
|
|
45
|
+
const content = fs.readFileSync(historicalVarsPath, 'utf8');
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// Si no existe o hay error, devuelve objeto vacío
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
fontFamilyVars: {},
|
|
53
|
+
lineHeightVars: {},
|
|
54
|
+
fontWeightVars: {},
|
|
55
|
+
letterSpacingVars: {},
|
|
56
|
+
textTransformVars: {},
|
|
57
|
+
fontSizeVars: {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Guarda las variables CSS actuales para mantenerlas en el futuro
|
|
62
|
+
// Esto asegura que las variables nunca se eliminen aunque se borren las clases
|
|
63
|
+
function saveHistoricalVariables(variables, historicalVarsPath) {
|
|
64
|
+
try {
|
|
65
|
+
const dir = path.dirname(historicalVarsPath);
|
|
66
|
+
if (!fs.existsSync(dir)) {
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Convertir Maps a objetos para guardar en JSON
|
|
71
|
+
const varsToSave = {
|
|
72
|
+
fontFamilyVars: {},
|
|
73
|
+
lineHeightVars: {},
|
|
74
|
+
fontWeightVars: {},
|
|
75
|
+
letterSpacingVars: {},
|
|
76
|
+
textTransformVars: {},
|
|
77
|
+
fontSizeVars: {}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Convertir cada Map a objeto
|
|
81
|
+
variables.fontFamilyVars.forEach((value, key) => {
|
|
82
|
+
varsToSave.fontFamilyVars[key] = value;
|
|
83
|
+
});
|
|
84
|
+
variables.lineHeightVars.forEach((value, key) => {
|
|
85
|
+
varsToSave.lineHeightVars[key] = value;
|
|
86
|
+
});
|
|
87
|
+
variables.fontWeightVars.forEach((value, key) => {
|
|
88
|
+
varsToSave.fontWeightVars[key] = value;
|
|
89
|
+
});
|
|
90
|
+
variables.letterSpacingVars.forEach((value, key) => {
|
|
91
|
+
varsToSave.letterSpacingVars[key] = value;
|
|
92
|
+
});
|
|
93
|
+
variables.textTransformVars.forEach((value, key) => {
|
|
94
|
+
varsToSave.textTransformVars[key] = value;
|
|
95
|
+
});
|
|
96
|
+
variables.fontSizeVars.forEach((value, key) => {
|
|
97
|
+
varsToSave.fontSizeVars[key] = value;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
fs.writeFileSync(historicalVarsPath, JSON.stringify(varsToSave, null, 2), 'utf8');
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.warn('⚠️ No se pudo guardar las variables históricas:', error.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Construye un mapa de todas las variables CSS compartidas y sus valores
|
|
107
|
+
// Recorre todas las clases y agrupa valores únicos para crear variables compartidas
|
|
108
|
+
// Esto evita duplicar variables cuando varias clases usan el mismo valor
|
|
109
|
+
// También carga variables históricas para que nunca se eliminen
|
|
110
|
+
function buildValueMap(classes, fontFamilyMap, prefix, category, historicalVarsPath = null) {
|
|
111
|
+
// Cargar variables históricas si existe el archivo
|
|
112
|
+
const historicalVarsPathDefault = historicalVarsPath || path.join(__dirname, '..', '.data', '.historical-variables.json');
|
|
113
|
+
const historicalVars = loadHistoricalVariables(historicalVarsPathDefault);
|
|
114
|
+
|
|
115
|
+
// Inicializar Maps con variables históricas
|
|
116
|
+
const fontFamilyVars = new Map(Object.entries(historicalVars.fontFamilyVars || {}));
|
|
117
|
+
const lineHeightVars = new Map(Object.entries(historicalVars.lineHeightVars || {}));
|
|
118
|
+
const fontWeightVars = new Map(Object.entries(historicalVars.fontWeightVars || {}));
|
|
119
|
+
const letterSpacingVars = new Map(Object.entries(historicalVars.letterSpacingVars || {}));
|
|
120
|
+
const textTransformVars = new Map(Object.entries(historicalVars.textTransformVars || {}));
|
|
121
|
+
const fontSizeVars = new Map(Object.entries(historicalVars.fontSizeVars || {}));
|
|
122
|
+
|
|
123
|
+
const valueMap = {};
|
|
124
|
+
|
|
125
|
+
Object.entries(classes).forEach(([className, cls]) => {
|
|
126
|
+
valueMap[className] = {};
|
|
127
|
+
|
|
128
|
+
// Procesa la fuente de la clase
|
|
129
|
+
// Si la fuente ya existe en el mapa, reutiliza la variable
|
|
130
|
+
if (cls.fontFamily !== undefined) {
|
|
131
|
+
const fontFamilyName = getFontFamilyName(cls.fontFamily, fontFamilyMap);
|
|
132
|
+
const varName = getFontFamilyVarName(fontFamilyName, prefix, category);
|
|
133
|
+
if (!fontFamilyVars.has(cls.fontFamily)) {
|
|
134
|
+
fontFamilyVars.set(cls.fontFamily, { varName, value: cls.fontFamily, name: fontFamilyName });
|
|
135
|
+
}
|
|
136
|
+
valueMap[className].fontFamily = {
|
|
137
|
+
varName: fontFamilyVars.get(cls.fontFamily).varName,
|
|
138
|
+
value: cls.fontFamily
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Procesa propiedades base que se comparten entre breakpoints
|
|
143
|
+
// Estas propiedades no cambian entre mobile y desktop
|
|
144
|
+
['fontWeight', 'letterSpacing', 'textTransform'].forEach(prop => {
|
|
145
|
+
if (cls[prop] !== undefined) {
|
|
146
|
+
const varName = getSharedVarName(prop, cls[prop], prefix, category);
|
|
147
|
+
const varMap = prop === 'fontWeight' ? fontWeightVars : prop === 'letterSpacing' ? letterSpacingVars : textTransformVars;
|
|
148
|
+
|
|
149
|
+
// Solo crea la variable si no existe ya
|
|
150
|
+
if (!varMap.has(cls[prop])) {
|
|
151
|
+
varMap.set(cls[prop], { varName, value: cls[prop] });
|
|
152
|
+
}
|
|
153
|
+
valueMap[className][prop] = {
|
|
154
|
+
varName: varMap.get(cls[prop]).varName,
|
|
155
|
+
value: cls[prop]
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Procesa propiedades que cambian según el breakpoint
|
|
161
|
+
// fontSize y lineHeight pueden ser diferentes en mobile y desktop
|
|
162
|
+
BREAKPOINTS.forEach(bp => {
|
|
163
|
+
if (cls[bp]) {
|
|
164
|
+
if (cls[bp].fontSize !== undefined) {
|
|
165
|
+
const fontSizeValue = cls[bp].fontSize;
|
|
166
|
+
const fontSizeRem = pxToRem(fontSizeValue);
|
|
167
|
+
const varName = getSharedVarName('fontSize', fontSizeValue, prefix, category);
|
|
168
|
+
|
|
169
|
+
// Solo crea la variable si no existe ya
|
|
170
|
+
if (!fontSizeVars.has(fontSizeValue)) {
|
|
171
|
+
fontSizeVars.set(fontSizeValue, { varName, value: fontSizeRem });
|
|
172
|
+
}
|
|
173
|
+
if (!valueMap[className].fontSize) valueMap[className].fontSize = {};
|
|
174
|
+
valueMap[className].fontSize[bp] = {
|
|
175
|
+
varName: fontSizeVars.get(fontSizeValue).varName,
|
|
176
|
+
value: fontSizeRem
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (cls[bp].lineHeight !== undefined) {
|
|
181
|
+
const lineHeightValue = cls[bp].lineHeight;
|
|
182
|
+
const varName = getLineHeightVarName(lineHeightValue, prefix, category);
|
|
183
|
+
|
|
184
|
+
// Solo crea la variable si no existe ya
|
|
185
|
+
if (!lineHeightVars.has(lineHeightValue)) {
|
|
186
|
+
lineHeightVars.set(lineHeightValue, { varName, value: lineHeightValue });
|
|
187
|
+
}
|
|
188
|
+
if (!valueMap[className].lineHeight) valueMap[className].lineHeight = {};
|
|
189
|
+
valueMap[className].lineHeight[bp] = {
|
|
190
|
+
varName: lineHeightVars.get(lineHeightValue).varName,
|
|
191
|
+
value: lineHeightValue
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Guardar las variables actuales (incluyendo las históricas) para la próxima vez
|
|
199
|
+
const allVariables = {
|
|
200
|
+
fontFamilyVars,
|
|
201
|
+
lineHeightVars,
|
|
202
|
+
fontWeightVars,
|
|
203
|
+
letterSpacingVars,
|
|
204
|
+
textTransformVars,
|
|
205
|
+
fontSizeVars
|
|
206
|
+
};
|
|
207
|
+
saveHistoricalVariables(allVariables, historicalVarsPathDefault);
|
|
208
|
+
|
|
209
|
+
return { valueMap, fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Genera variables CSS para spacing (padding y margin)
|
|
213
|
+
// Crea variables como --hg-spacing-4, --hg-spacing-8, etc.
|
|
214
|
+
function generateSpacingVariables(spacingMap, prefix, baseFontSize = 16) {
|
|
215
|
+
if (!spacingMap || typeof spacingMap !== 'object') {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const variables = [];
|
|
220
|
+
|
|
221
|
+
Object.entries(spacingMap).forEach(([key, value]) => {
|
|
222
|
+
// Si el valor termina en %, no lo convierte a rem
|
|
223
|
+
const finalValue = value.endsWith('%') ? value : pxToRem(value, baseFontSize);
|
|
224
|
+
const varName = `--${prefix}-spacing-${key}`;
|
|
225
|
+
variables.push({ varName, value: finalValue, key });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return variables;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Genera variables CSS para colores
|
|
232
|
+
// Crea variables como --hg-color-white, --hg-color-black, etc.
|
|
233
|
+
function generateColorVariables(colorsMap, prefix) {
|
|
234
|
+
if (!colorsMap || typeof colorsMap !== 'object') {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const variables = [];
|
|
239
|
+
|
|
240
|
+
Object.entries(colorsMap).forEach(([key, value]) => {
|
|
241
|
+
const varName = `--${prefix}-color-${key}`;
|
|
242
|
+
variables.push({ varName, value, key });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return variables;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Genera todas las variables CSS en el bloque :root
|
|
249
|
+
// Recorre todos los mapas de variables y las convierte en declaraciones CSS
|
|
250
|
+
function generateRootVariables(fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars, spacingVars = [], colorVars = []) {
|
|
251
|
+
const variables = [];
|
|
252
|
+
const allMaps = [
|
|
253
|
+
{ map: fontFamilyVars, name: 'font-family' },
|
|
254
|
+
{ map: lineHeightVars, name: 'line-height' },
|
|
255
|
+
{ map: fontWeightVars, name: 'font-weight' },
|
|
256
|
+
{ map: letterSpacingVars, name: 'letter-spacing' },
|
|
257
|
+
{ map: textTransformVars, name: 'text-transform' },
|
|
258
|
+
{ map: fontSizeVars, name: 'font-size' }
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
allMaps.forEach(({ map }) => {
|
|
262
|
+
Array.from(map.values()).forEach(item => {
|
|
263
|
+
variables.push(` ${item.varName}: ${item.value};`);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Agregar variables de spacing
|
|
268
|
+
spacingVars.forEach(item => {
|
|
269
|
+
variables.push(` ${item.varName}: ${item.value};`);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Agregar variables de colores
|
|
273
|
+
colorVars.forEach(item => {
|
|
274
|
+
variables.push(` ${item.varName}: ${item.value};`);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return variables.join('\n');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Obtiene las propiedades finales de una clase para un breakpoint específico
|
|
281
|
+
// Combina las propiedades base (que no cambian) con las del breakpoint (que sí cambian)
|
|
282
|
+
function getFinalProps(cls, breakpoint, baseFontSize) {
|
|
283
|
+
const props = {};
|
|
284
|
+
|
|
285
|
+
// Propiedades base que se aplican a todos los breakpoints
|
|
286
|
+
['fontFamily', 'fontWeight', 'letterSpacing', 'textTransform'].forEach(prop => {
|
|
287
|
+
if (cls[prop] !== undefined) props[prop] = cls[prop];
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Propiedades específicas del breakpoint (fontSize y lineHeight)
|
|
291
|
+
if (cls[breakpoint]) {
|
|
292
|
+
if (cls[breakpoint].fontSize !== undefined) props.fontSize = pxToRem(cls[breakpoint].fontSize, baseFontSize);
|
|
293
|
+
if (cls[breakpoint].lineHeight !== undefined) props.lineHeight = cls[breakpoint].lineHeight;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return props;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Genera el CSS para una clase específica en un breakpoint
|
|
300
|
+
// Crea las propiedades CSS usando las variables compartidas
|
|
301
|
+
function generateClassCSS(className, cls, breakpointName, valueMap, prefix, category, baseFontSize, fontFamilyMap) {
|
|
302
|
+
const props = getFinalProps(cls, breakpointName, baseFontSize);
|
|
303
|
+
const cssProps = [];
|
|
304
|
+
|
|
305
|
+
PROPERTIES.forEach(prop => {
|
|
306
|
+
if (props[prop] === undefined) return;
|
|
307
|
+
|
|
308
|
+
// Obtiene el nombre de la variable CSS según el tipo de propiedad
|
|
309
|
+
let varName;
|
|
310
|
+
if (prop === 'fontFamily') {
|
|
311
|
+
const fontFamilyName = getFontFamilyName(props[prop], fontFamilyMap);
|
|
312
|
+
varName = getFontFamilyVarName(fontFamilyName, prefix, category);
|
|
313
|
+
} else if (prop === 'lineHeight') {
|
|
314
|
+
varName = getLineHeightVarName(props[prop], prefix, category);
|
|
315
|
+
} else if (prop === 'fontSize') {
|
|
316
|
+
// Para fontSize, usa el valor original en px para generar el nombre de variable
|
|
317
|
+
const originalValue = cls[breakpointName]?.fontSize ?? cls.fontSize;
|
|
318
|
+
varName = getSharedVarName(prop, originalValue, prefix, category);
|
|
319
|
+
} else {
|
|
320
|
+
varName = getSharedVarName(prop, props[prop], prefix, category);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
cssProps.push(` ${toKebabCase(prop)}: var(${varName});`);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return cssProps.length ? ` .${className} {\n${cssProps.join('\n')}\n }` : '';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Genera una media query con todas las clases para un breakpoint
|
|
330
|
+
// Convierte el breakpoint a rem y genera el CSS de todas las clases
|
|
331
|
+
function generateMediaQuery(breakpointName, minWidth, classes, valueMap, prefix, category, baseFontSize, fontFamilyMap) {
|
|
332
|
+
const minWidthRem = pxToRem(minWidth, baseFontSize);
|
|
333
|
+
|
|
334
|
+
const cssClasses = Object.entries(classes)
|
|
335
|
+
.map(([className, cls]) => generateClassCSS(className, cls, breakpointName, valueMap, prefix, category, baseFontSize, fontFamilyMap))
|
|
336
|
+
.filter(Boolean);
|
|
337
|
+
|
|
338
|
+
return `@media (min-width: ${minWidthRem}) {\n\n${cssClasses.join('\n\n')}\n\n}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Genera un bloque de tipografías para un breakpoint específico con comentario descriptivo
|
|
342
|
+
// Incluye un comentario que indica qué breakpoint es y el min-width
|
|
343
|
+
function generateTypographyBlock(breakpointName, minWidth, classes, valueMap, prefix, category, baseFontSize, fontFamilyMap) {
|
|
344
|
+
const minWidthRem = pxToRem(minWidth, baseFontSize);
|
|
345
|
+
const breakpointLabel = breakpointName === 'mobile' ? 'Mobile' : 'Desktop';
|
|
346
|
+
|
|
347
|
+
const cssClasses = Object.entries(classes)
|
|
348
|
+
.map(([className, cls]) => generateClassCSS(className, cls, breakpointName, valueMap, prefix, category, baseFontSize, fontFamilyMap))
|
|
349
|
+
.filter(Boolean);
|
|
350
|
+
|
|
351
|
+
return `/* Tipografías - ${breakpointLabel} (min-width: ${minWidthRem}) */
|
|
352
|
+
@media (min-width: ${minWidthRem}) {
|
|
353
|
+
|
|
354
|
+
${cssClasses.join('\n\n')}
|
|
355
|
+
|
|
356
|
+
}`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Genera el reset CSS mínimo necesario para que todo funcione correctamente
|
|
360
|
+
// Establece el tamaño de fuente base del HTML para que los rem funcionen
|
|
361
|
+
function generateResetCSS(baseFontSize = 16) {
|
|
362
|
+
return `/* Reset CSS Mínimo */
|
|
363
|
+
html {
|
|
364
|
+
box-sizing: border-box;
|
|
365
|
+
font-size: ${baseFontSize}px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
369
|
+
html {
|
|
370
|
+
interpolate-size: allow-keywords;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
*,
|
|
375
|
+
*:before,
|
|
376
|
+
*:after {
|
|
377
|
+
box-sizing: inherit;
|
|
378
|
+
-webkit-text-size-adjust: 100%;
|
|
379
|
+
-moz-tab-size: 4;
|
|
380
|
+
tab-size: 4;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
* {
|
|
384
|
+
margin: 0;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
html,
|
|
388
|
+
body {
|
|
389
|
+
margin: 0;
|
|
390
|
+
padding: 0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
body {
|
|
394
|
+
line-height: calc(1em + 0.5rem);
|
|
395
|
+
-webkit-font-smoothing: antialiased;
|
|
396
|
+
min-height: 100vh;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
ol,
|
|
400
|
+
ul,
|
|
401
|
+
dl {
|
|
402
|
+
list-style: none;
|
|
403
|
+
padding-inline-start: unset;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
img, picture, video, canvas, svg {
|
|
407
|
+
display: inline-block;
|
|
408
|
+
max-width: 100%;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
input, button, textarea, select {
|
|
412
|
+
font: inherit;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
p, h1, h2, h3, h4, h5, h6 {
|
|
416
|
+
overflow-wrap: break-word;
|
|
417
|
+
}
|
|
418
|
+
`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Genera helpers de padding y margin basados en el spacingMap del config.json
|
|
422
|
+
// Crea clases como p-4, pr-4, pl-4, pb-4, pt-4 para padding
|
|
423
|
+
// y m-4, mr-4, ml-4, mb-4, mt-4 para margin
|
|
424
|
+
// También genera versiones con prefijo md: para el breakpoint desktop
|
|
425
|
+
// Usa las variables CSS definidas en :root
|
|
426
|
+
function generateSpacingHelpers(spacingMap, prefix, desktopBreakpoint, baseFontSize = 16, spacingImportant = []) {
|
|
427
|
+
if (!spacingMap || typeof spacingMap !== 'object') {
|
|
428
|
+
return '';
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const helpers = [];
|
|
432
|
+
const desktopHelpers = [];
|
|
433
|
+
const desktopBreakpointRem = pxToRem(desktopBreakpoint, baseFontSize);
|
|
434
|
+
|
|
435
|
+
// Generar helpers para cada valor en spacingMap
|
|
436
|
+
Object.entries(spacingMap).forEach(([key, value]) => {
|
|
437
|
+
const varName = `--${prefix}-spacing-${key}`;
|
|
438
|
+
|
|
439
|
+
// Padding helpers base (mobile) usando variables CSS con propiedades lógicas para RTL
|
|
440
|
+
helpers.push(` .p-${key} { padding: var(${varName}); }`);
|
|
441
|
+
helpers.push(` .pr-${key} { padding-inline-end: var(${varName}); }`);
|
|
442
|
+
helpers.push(` .pl-${key} { padding-inline-start: var(${varName}); }`);
|
|
443
|
+
helpers.push(` .pb-${key} { padding-bottom: var(${varName}); }`);
|
|
444
|
+
helpers.push(` .pt-${key} { padding-top: var(${varName}); }`);
|
|
445
|
+
|
|
446
|
+
// Margin helpers base (mobile) usando variables CSS con propiedades lógicas para RTL
|
|
447
|
+
helpers.push(` .m-${key} { margin: var(${varName}); }`);
|
|
448
|
+
helpers.push(` .mr-${key} { margin-inline-end: var(${varName}); }`);
|
|
449
|
+
helpers.push(` .ml-${key} { margin-inline-start: var(${varName}); }`);
|
|
450
|
+
helpers.push(` .mb-${key} { margin-bottom: var(${varName}); }`);
|
|
451
|
+
helpers.push(` .mt-${key} { margin-top: var(${varName}); }`);
|
|
452
|
+
|
|
453
|
+
// Padding helpers con prefijo md: (desktop) usando variables CSS con propiedades lógicas para RTL
|
|
454
|
+
desktopHelpers.push(` .md\\:p-${key} { padding: var(${varName}); }`);
|
|
455
|
+
desktopHelpers.push(` .md\\:pr-${key} { padding-inline-end: var(${varName}); }`);
|
|
456
|
+
desktopHelpers.push(` .md\\:pl-${key} { padding-inline-start: var(${varName}); }`);
|
|
457
|
+
desktopHelpers.push(` .md\\:pb-${key} { padding-bottom: var(${varName}); }`);
|
|
458
|
+
desktopHelpers.push(` .md\\:pt-${key} { padding-top: var(${varName}); }`);
|
|
459
|
+
|
|
460
|
+
// Margin helpers con prefijo md: (desktop) usando variables CSS con propiedades lógicas para RTL
|
|
461
|
+
desktopHelpers.push(` .md\\:m-${key} { margin: var(${varName}); }`);
|
|
462
|
+
desktopHelpers.push(` .md\\:mr-${key} { margin-inline-end: var(${varName}); }`);
|
|
463
|
+
desktopHelpers.push(` .md\\:ml-${key} { margin-inline-start: var(${varName}); }`);
|
|
464
|
+
desktopHelpers.push(` .md\\:mb-${key} { margin-bottom: var(${varName}); }`);
|
|
465
|
+
desktopHelpers.push(` .md\\:mt-${key} { margin-top: var(${varName}); }`);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Generar helpers con !important para los valores especificados en spacingImportant
|
|
469
|
+
if (spacingImportant && Array.isArray(spacingImportant)) {
|
|
470
|
+
spacingImportant.forEach(key => {
|
|
471
|
+
if (spacingMap[key]) {
|
|
472
|
+
const varName = `--${prefix}-spacing-${key}`;
|
|
473
|
+
|
|
474
|
+
// Padding helpers con !important (mobile)
|
|
475
|
+
helpers.push(` .p-${key}\\! { padding: var(${varName}) !important; }`);
|
|
476
|
+
helpers.push(` .pr-${key}\\! { padding-inline-end: var(${varName}) !important; }`);
|
|
477
|
+
helpers.push(` .pl-${key}\\! { padding-inline-start: var(${varName}) !important; }`);
|
|
478
|
+
helpers.push(` .pb-${key}\\! { padding-bottom: var(${varName}) !important; }`);
|
|
479
|
+
helpers.push(` .pt-${key}\\! { padding-top: var(${varName}) !important; }`);
|
|
480
|
+
|
|
481
|
+
// Margin helpers con !important (mobile)
|
|
482
|
+
helpers.push(` .m-${key}\\! { margin: var(${varName}) !important; }`);
|
|
483
|
+
helpers.push(` .mr-${key}\\! { margin-inline-end: var(${varName}) !important; }`);
|
|
484
|
+
helpers.push(` .ml-${key}\\! { margin-inline-start: var(${varName}) !important; }`);
|
|
485
|
+
helpers.push(` .mb-${key}\\! { margin-bottom: var(${varName}) !important; }`);
|
|
486
|
+
helpers.push(` .mt-${key}\\! { margin-top: var(${varName}) !important; }`);
|
|
487
|
+
|
|
488
|
+
// Padding helpers con !important y prefijo md: (desktop)
|
|
489
|
+
desktopHelpers.push(` .md\\:p-${key}\\! { padding: var(${varName}) !important; }`);
|
|
490
|
+
desktopHelpers.push(` .md\\:pr-${key}\\! { padding-inline-end: var(${varName}) !important; }`);
|
|
491
|
+
desktopHelpers.push(` .md\\:pl-${key}\\! { padding-inline-start: var(${varName}) !important; }`);
|
|
492
|
+
desktopHelpers.push(` .md\\:pb-${key}\\! { padding-bottom: var(${varName}) !important; }`);
|
|
493
|
+
desktopHelpers.push(` .md\\:pt-${key}\\! { padding-top: var(${varName}) !important; }`);
|
|
494
|
+
|
|
495
|
+
// Margin helpers con !important y prefijo md: (desktop)
|
|
496
|
+
desktopHelpers.push(` .md\\:m-${key}\\! { margin: var(${varName}) !important; }`);
|
|
497
|
+
desktopHelpers.push(` .md\\:mr-${key}\\! { margin-inline-end: var(${varName}) !important; }`);
|
|
498
|
+
desktopHelpers.push(` .md\\:ml-${key}\\! { margin-inline-start: var(${varName}) !important; }`);
|
|
499
|
+
desktopHelpers.push(` .md\\:mb-${key}\\! { margin-bottom: var(${varName}) !important; }`);
|
|
500
|
+
desktopHelpers.push(` .md\\:mt-${key}\\! { margin-top: var(${varName}) !important; }`);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (helpers.length === 0) {
|
|
506
|
+
return '';
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Generar bloque base (mobile) y bloque con media query (desktop)
|
|
510
|
+
const baseHelpers = `/* Helpers de Spacing (Padding y Margin) - Mobile */
|
|
511
|
+
${helpers.join('\n')}
|
|
512
|
+
|
|
513
|
+
/* Helpers de Spacing (Padding y Margin) - Desktop (md:) */
|
|
514
|
+
@media (min-width: ${desktopBreakpointRem}) {
|
|
515
|
+
${desktopHelpers.join('\n')}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
`;
|
|
519
|
+
|
|
520
|
+
return baseHelpers;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function generateLayoutHelpers(helpers, spacingMap, prefix, desktopBreakpoint, baseFontSize = 16) {
|
|
524
|
+
if (!helpers || typeof helpers !== 'object') {
|
|
525
|
+
return '';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let css = '\n/* Layout Helpers */\n';
|
|
529
|
+
|
|
530
|
+
Object.entries(helpers).forEach(([helperName, config]) => {
|
|
531
|
+
const { property, class: className, responsive, values, useSpacing } = config;
|
|
532
|
+
|
|
533
|
+
if (useSpacing && spacingMap) {
|
|
534
|
+
Object.entries(spacingMap).forEach(([key, value]) => {
|
|
535
|
+
const finalValue = value.endsWith('%') ? value : pxToRem(value, baseFontSize);
|
|
536
|
+
css += `.${prefix}-${className}-${key} { ${property}: ${finalValue}; }\n`;
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
if (responsive) {
|
|
540
|
+
css += `\n@media (min-width: ${desktopBreakpoint}) {\n`;
|
|
541
|
+
Object.entries(spacingMap).forEach(([key, value]) => {
|
|
542
|
+
const finalValue = value.endsWith('%') ? value : pxToRem(value, baseFontSize);
|
|
543
|
+
css += ` .md\\:${prefix}-${className}-${key} { ${property}: ${finalValue}; }\n`;
|
|
544
|
+
});
|
|
545
|
+
css += '}\n';
|
|
546
|
+
}
|
|
547
|
+
} else if (values) {
|
|
548
|
+
if (Array.isArray(values)) {
|
|
549
|
+
values.forEach(value => {
|
|
550
|
+
css += `.${prefix}-${className}-${value} { ${property}: ${value}; }\n`;
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
if (responsive) {
|
|
554
|
+
css += `\n@media (min-width: ${desktopBreakpoint}) {\n`;
|
|
555
|
+
values.forEach(value => {
|
|
556
|
+
css += ` .md\\:${prefix}-${className}-${value} { ${property}: ${value}; }\n`;
|
|
557
|
+
});
|
|
558
|
+
css += '}\n';
|
|
559
|
+
}
|
|
560
|
+
} else if (typeof values === 'object') {
|
|
561
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
562
|
+
css += `.${prefix}-${className}-${key} { ${property}: ${value}; }\n`;
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
if (responsive) {
|
|
566
|
+
css += `\n@media (min-width: ${desktopBreakpoint}) {\n`;
|
|
567
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
568
|
+
css += ` .md\\:${prefix}-${className}-${key} { ${property}: ${value}; }\n`;
|
|
569
|
+
});
|
|
570
|
+
css += '}\n';
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
css += '\n';
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
return css;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Función principal que genera todo el CSS desde la configuración JSON
|
|
582
|
+
// Organiza el CSS en bloques separados: reset, variables, tipografías mobile y desktop
|
|
583
|
+
function generateCSS(configData) {
|
|
584
|
+
const prefix = configData.prefix || 'hg';
|
|
585
|
+
const category = configData.category || 'typo';
|
|
586
|
+
const baseFontSize = configData.baseFontSize || 16;
|
|
587
|
+
|
|
588
|
+
// Construye el mapa de variables compartidas
|
|
589
|
+
const { valueMap, fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars } =
|
|
590
|
+
buildValueMap(configData.classes, configData.fontFamilyMap, prefix, category);
|
|
591
|
+
|
|
592
|
+
// Genera variables de spacing
|
|
593
|
+
const spacingVars = generateSpacingVariables(configData.spacingMap, prefix, baseFontSize);
|
|
594
|
+
|
|
595
|
+
// Genera variables de colores
|
|
596
|
+
const colorVars = generateColorVariables(configData.colors, prefix);
|
|
597
|
+
|
|
598
|
+
// Genera cada bloque del CSS
|
|
599
|
+
const resetCSS = generateResetCSS(baseFontSize);
|
|
600
|
+
const rootVars = generateRootVariables(fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars, spacingVars, colorVars);
|
|
601
|
+
const spacingHelpers = generateSpacingHelpers(configData.spacingMap, prefix, configData.breakpoints.desktop, baseFontSize, configData.spacingImportant);
|
|
602
|
+
const layoutHelpers = configData.helpers ? generateLayoutHelpers(configData.helpers, configData.spacingMap, prefix, configData.breakpoints.desktop, baseFontSize) : '';
|
|
603
|
+
const mobileTypography = generateTypographyBlock('mobile', configData.breakpoints.mobile, configData.classes, valueMap, prefix, category, baseFontSize, configData.fontFamilyMap);
|
|
604
|
+
const desktopTypography = generateTypographyBlock('desktop', configData.breakpoints.desktop, configData.classes, valueMap, prefix, category, baseFontSize, configData.fontFamilyMap);
|
|
605
|
+
|
|
606
|
+
// Combina todos los bloques en el orden correcto
|
|
607
|
+
return `${resetCSS}/* Variables CSS Compartidas */
|
|
608
|
+
:root {
|
|
609
|
+
${rootVars}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
${spacingHelpers}${layoutHelpers}${mobileTypography}
|
|
613
|
+
|
|
614
|
+
${desktopTypography}`;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
module.exports = {
|
|
618
|
+
generateCSS,
|
|
619
|
+
buildValueMap,
|
|
620
|
+
generateResetCSS,
|
|
621
|
+
generateSpacingVariables,
|
|
622
|
+
generateColorVariables
|
|
623
|
+
};
|
|
624
|
+
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Utilidades compartidas
|
|
2
|
+
// Funciones auxiliares utilizadas por el parseador y el generador de guía
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// Convierte nombres de propiedades de JavaScript (camelCase) a formato CSS (kebab-case)
|
|
8
|
+
// Por ejemplo, "fontSize" se convierte en "font-size" para usarlo en CSS
|
|
9
|
+
function toKebabCase(str) {
|
|
10
|
+
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Convierte valores en píxeles a unidades rem para hacer el CSS responsive
|
|
14
|
+
|
|
15
|
+
function pxToRem(value, baseFontSize = 16) {
|
|
16
|
+
if (typeof value === 'string' && value.endsWith('px')) {
|
|
17
|
+
const pxValue = parseFloat(value);
|
|
18
|
+
const remValue = pxValue / baseFontSize;
|
|
19
|
+
return `${parseFloat(remValue.toFixed(4))}rem`;
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Convierte valores rem de vuelta a píxeles para mostrarlos en la guía HTML
|
|
25
|
+
// Esto ayuda a los desarrolladores a entender mejor los valores, ya que
|
|
26
|
+
// muchos están más familiarizados con píxeles que con rem
|
|
27
|
+
function remToPx(remValue, baseFontSize = 16) {
|
|
28
|
+
const remMatch = remValue.toString().match(/^([\d.]+)rem$/);
|
|
29
|
+
return remMatch ? `${parseFloat(remMatch[1]) * baseFontSize}px` : '-';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Busca el nombre de una fuente en el mapa de fuentes configurado
|
|
33
|
+
// Si la fuente está en el mapa (como "primary" o "secondary"), devuelve ese nombre
|
|
34
|
+
// Si no está, genera un nombre automático desde el valor de la fuente
|
|
35
|
+
// Esto se usa para crear variables CSS compartidas con nombres consistentes
|
|
36
|
+
function getFontFamilyName(fontFamilyValue, fontFamilyMap) {
|
|
37
|
+
// Primero busca en el mapa de fuentes si existe
|
|
38
|
+
if (fontFamilyMap) {
|
|
39
|
+
for (const [name, value] of Object.entries(fontFamilyMap)) {
|
|
40
|
+
if (value === fontFamilyValue) return name;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Si no está en el mapa, genera un nombre automático limpiando el valor
|
|
44
|
+
// Elimina comillas, espacios y toma solo el primer nombre de la lista de fuentes
|
|
45
|
+
return fontFamilyValue.replace(/["']/g, '').replace(/\s+/g, '-').toLowerCase().split(',')[0].trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Escribe un archivo en el sistema de archivos con validación y mensajes de confirmación
|
|
49
|
+
// Crea el directorio si no existe y verifica que el archivo se escribió correctamente
|
|
50
|
+
function writeFile(filePath, content, description) {
|
|
51
|
+
try {
|
|
52
|
+
const dir = path.dirname(filePath);
|
|
53
|
+
if (!fs.existsSync(dir)) {
|
|
54
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
// Forzar escritura del archivo para asegurar que se actualice
|
|
57
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
58
|
+
// Verificar que el archivo se escribió correctamente
|
|
59
|
+
const stats = fs.statSync(filePath);
|
|
60
|
+
console.log(`✅ ${description} generado exitosamente en ${filePath} (${stats.size} bytes)`);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`❌ Error al escribir ${description} en ${filePath}:`, error.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
toKebabCase,
|
|
69
|
+
pxToRem,
|
|
70
|
+
remToPx,
|
|
71
|
+
getFontFamilyName,
|
|
72
|
+
writeFile
|
|
73
|
+
};
|
|
74
|
+
|