holygrail5 1.0.19 → 1.0.21
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 +88 -18
- package/config.json +205 -77
- package/dist/assets/fonts/suisse-intl-light.woff +0 -0
- package/dist/assets/fonts/suisse-intl-light.woff2 +0 -0
- package/dist/assets/fonts/suisse-intl-medium.woff +0 -0
- package/dist/assets/fonts/suisse-intl-medium.woff2 +0 -0
- package/dist/assets/fonts/suisse-intl-regular.woff +0 -0
- package/dist/assets/fonts/suisse-intl-regular.woff2 +0 -0
- package/dist/assets/fonts/suisse-intl-semibold.woff +0 -0
- package/dist/assets/fonts/suisse-intl-semibold.woff2 +0 -0
- package/dist/assets/fonts/suisse-works-bold.woff +0 -0
- package/dist/assets/fonts/suisse-works-bold.woff2 +0 -0
- package/dist/assets/fonts/suisse-works-medium.woff +0 -0
- package/dist/assets/fonts/suisse-works-medium.woff2 +0 -0
- package/dist/assets/fonts/suisse-works-regular.woff +0 -0
- package/dist/assets/fonts/suisse-works-regular.woff2 +0 -0
- package/dist/componentes.html +429 -0
- package/dist/developer-guide.md +7 -7
- package/dist/guide-styles.css +197 -25
- package/dist/index.html +807 -689
- package/dist/output.css +217 -114
- package/dist/skills.html +14 -8
- package/dist/themes/dutti-demo.html +713 -19
- package/dist/themes/dutti.css +67 -16
- package/dist/themes/limited-demo.html +1121 -0
- package/dist/themes/limited.css +2493 -0
- package/package.json +1 -1
- package/src/.data/.previous-values.json +151 -84
- package/src/assets/fonts/suisse-intl-light.woff +0 -0
- package/src/assets/fonts/suisse-intl-light.woff2 +0 -0
- package/src/assets/fonts/suisse-intl-medium.woff +0 -0
- package/src/assets/fonts/suisse-intl-medium.woff2 +0 -0
- package/src/assets/fonts/suisse-intl-regular.woff +0 -0
- package/src/assets/fonts/suisse-intl-regular.woff2 +0 -0
- package/src/assets/fonts/suisse-intl-semibold.woff +0 -0
- package/src/assets/fonts/suisse-intl-semibold.woff2 +0 -0
- package/src/assets/fonts/suisse-works-bold.woff +0 -0
- package/src/assets/fonts/suisse-works-bold.woff2 +0 -0
- package/src/assets/fonts/suisse-works-medium.woff +0 -0
- package/src/assets/fonts/suisse-works-medium.woff2 +0 -0
- package/src/assets/fonts/suisse-works-regular.woff +0 -0
- package/src/assets/fonts/suisse-works-regular.woff2 +0 -0
- package/src/build/asset-manager.js +94 -3
- package/src/build/build-orchestrator.js +74 -12
- package/src/build/components-generator.js +584 -0
- package/src/build/skills-generator.js +43 -5
- package/src/build/theme-config-loader.js +58 -0
- package/src/build/theme-transformer.js +109 -16
- package/src/build/theme-vars-table-generator.js +349 -0
- package/src/build/typo-table-generator.js +154 -0
- package/src/docs-generator/guide-styles.css +197 -24
- package/src/docs-generator/html-generator.js +92 -246
- package/src/docs-generator/sections/colors-section.js +109 -0
- package/src/docs-generator/value-tracker.js +154 -0
- package/src/generators/typo-generator.js +2 -1
- package/src/generators/utils.js +90 -1
- package/src/skills.html +1 -0
- package/src/watch-config.js +99 -32
- package/themes/{dutti → _base}/_buttons.css +2 -2
- package/themes/{dutti → _base}/_checkboxes.css +1 -1
- package/themes/{dutti → _base}/_forms.css +1 -1
- package/themes/{dutti → _base}/_inputs.css +55 -10
- package/themes/{dutti → _base}/_labels.css +1 -1
- package/themes/dutti/README.md +67 -21
- package/themes/dutti/_variables.css +7 -1
- package/themes/dutti/demo.html +18 -14
- package/themes/dutti/theme.css +22 -44
- package/themes/dutti/theme.json +86 -0
- package/themes/limited/_variables.css +123 -0
- package/themes/limited/demo.html +296 -0
- package/themes/limited/theme.css +32 -0
- package/themes/limited/theme.json +105 -0
- /package/themes/{dutti → _base}/_containers.css +0 -0
- /package/themes/{dutti → _base}/_radios.css +0 -0
- /package/themes/{dutti → _base}/_switches.css +0 -0
- /package/themes/{dutti → _base}/components/_icons.css +0 -0
- /package/themes/{dutti → _base}/objects/_grid.css +0 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Components Page Generator
|
|
3
|
+
*
|
|
4
|
+
* Genera dist/componentes.html: una página que muestra todos los
|
|
5
|
+
* componentes base (themes/_base/) con preview vivo + el nombre de
|
|
6
|
+
* clase junto a cada variante.
|
|
7
|
+
*
|
|
8
|
+
* Se renderiza con el tema DUTTI como base genérica (tema neutro del
|
|
9
|
+
* framework). Sobre él se pueden aplicar otros temas en el futuro si
|
|
10
|
+
* añadimos un theme switcher. Por tanto la página enlaza:
|
|
11
|
+
* - dist/output.css → tokens --hg-* del framework
|
|
12
|
+
* - dist/themes/dutti.css → mapeo de variables + reglas de componente
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { resolveActiveThemes } = require('../generators/utils');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Nombre del tema que se usa como "base genérica" para renderizar la
|
|
21
|
+
* página. Si en algún momento se quiere cambiar, basta con modificar
|
|
22
|
+
* esta constante (o exponerla en config.json).
|
|
23
|
+
*/
|
|
24
|
+
const BASE_THEME = 'dutti';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Lista canónica de componentes mostrados en la página.
|
|
28
|
+
*/
|
|
29
|
+
const COMPONENT_SECTIONS = [
|
|
30
|
+
{
|
|
31
|
+
id: 'buttons',
|
|
32
|
+
title: 'Botones',
|
|
33
|
+
description:
|
|
34
|
+
'Variantes estándar del framework: <code>primary</code>, <code>secondary</code>, <code>tertiary</code>, <code>label-m</code>, <code>link</code> y <code>badge</code>. Clases en <code>themes/_base/_buttons.css</code>. Cada tema puede sobreescribirlas con su propia identidad visual.',
|
|
35
|
+
examples: [
|
|
36
|
+
{
|
|
37
|
+
subtitle: 'Variantes',
|
|
38
|
+
items: [
|
|
39
|
+
{ html: '<button class="btn btn-primary">Primary</button>', cls: '.btn .btn-primary' },
|
|
40
|
+
{ html: '<button class="btn btn-secondary">Secondary</button>', cls: '.btn .btn-secondary' },
|
|
41
|
+
{ html: '<button class="btn btn-tertiary">Tertiary</button>', cls: '.btn .btn-tertiary' },
|
|
42
|
+
{
|
|
43
|
+
html: '<button class="btn btn-tertiary hg-text-underline">Tertiary underline</button>',
|
|
44
|
+
cls: '.btn .btn-tertiary .hg-text-underline'
|
|
45
|
+
},
|
|
46
|
+
{ html: '<button class="btn btn-label-m">Label M</button>', cls: '.btn .btn-label-m' },
|
|
47
|
+
{ html: '<button class="btn btn-link">Link</button>', cls: '.btn .btn-link' },
|
|
48
|
+
{ html: '<button class="btn btn-badge">Badge</button>', cls: '.btn .btn-badge' },
|
|
49
|
+
{ html: '<button class="btn btn-primary" disabled>Disabled</button>', cls: '.btn[disabled]' }
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
subtitle: 'Tamaños',
|
|
54
|
+
items: [
|
|
55
|
+
{ html: '<button class="btn btn-primary btn-sm">Small</button>', cls: '.btn .btn-sm' },
|
|
56
|
+
{ html: '<button class="btn btn-primary btn-md">Medium</button>', cls: '.btn .btn-md' },
|
|
57
|
+
{ html: '<button class="btn btn-primary btn-lg">Large</button>', cls: '.btn .btn-lg' }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
subtitle: 'Ancho completo',
|
|
62
|
+
items: [
|
|
63
|
+
{
|
|
64
|
+
html: '<button class="btn btn-primary btn-md btn-full">Botón ancho completo</button>',
|
|
65
|
+
cls: '.btn .btn-full'
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'inputs',
|
|
73
|
+
title: 'Inputs',
|
|
74
|
+
description:
|
|
75
|
+
'Campos de formulario base con <strong>floating label</strong>: texto, email, password, textarea y select. Cada input vive dentro de <code>.form-input-label-2</code> para que el label se anime al enfocar o al contener valor. Clases en <code>themes/_base/_inputs.css</code>.',
|
|
76
|
+
examples: [
|
|
77
|
+
{
|
|
78
|
+
subtitle: 'Tipos',
|
|
79
|
+
items: [
|
|
80
|
+
{
|
|
81
|
+
html:
|
|
82
|
+
'<div class="form-input-label-2"><input type="text" id="cmp-input-text" class="input" placeholder=" " /><label for="cmp-input-text">Texto</label></div>',
|
|
83
|
+
cls: '.form-input-label-2 > .input'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
html:
|
|
87
|
+
'<div class="form-input-label-2"><input type="email" id="cmp-input-email" class="input" placeholder=" " /><label for="cmp-input-email">Email</label></div>',
|
|
88
|
+
cls: '.input (type=email)'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
html:
|
|
92
|
+
'<div class="form-input-label-2"><input type="password" id="cmp-input-password" class="input" placeholder=" " /><label for="cmp-input-password">Contraseña</label></div>',
|
|
93
|
+
cls: '.input (type=password)'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
html:
|
|
97
|
+
'<div class="form-input-label-2"><textarea id="cmp-input-textarea" class="input" placeholder=" " rows="3"></textarea><label for="cmp-input-textarea">Comentario</label></div>',
|
|
98
|
+
cls: '.input (textarea)'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
html:
|
|
102
|
+
'<div class="form-input-label-2"><select id="cmp-input-select" class="input"><option>Opción A</option><option>Opción B</option></select><label for="cmp-input-select">Selecciona</label></div>',
|
|
103
|
+
cls: '.input (select)'
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
subtitle: 'Estados',
|
|
109
|
+
items: [
|
|
110
|
+
{
|
|
111
|
+
html:
|
|
112
|
+
'<div class="form-input-label-2"><input type="text" id="cmp-input-error" class="input input-error" value="Valor inválido" placeholder=" " /><label for="cmp-input-error">Error</label></div><span class="helper-text helper-text-error">Este campo tiene un error</span>',
|
|
113
|
+
cls: '.input-error + .helper-text-error'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
html:
|
|
117
|
+
'<div class="form-input-label-2"><input type="text" id="cmp-input-success" class="input input-success" value="Valor válido" placeholder=" " /><label for="cmp-input-success">Success</label></div><span class="helper-text helper-text-success">Campo válido</span>',
|
|
118
|
+
cls: '.input-success + .helper-text-success'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
html:
|
|
122
|
+
'<div class="form-input-label-2"><input type="text" id="cmp-input-warning" class="input input-warning" value="Atención" placeholder=" " /><label for="cmp-input-warning">Warning</label></div><span class="helper-text helper-text-warning">Revisa este campo</span>',
|
|
123
|
+
cls: '.input-warning + .helper-text-warning'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
html:
|
|
127
|
+
'<div class="form-input-label-2"><input type="text" id="cmp-input-disabled" class="input" value="No editable" placeholder=" " disabled /><label for="cmp-input-disabled">Disabled</label></div>',
|
|
128
|
+
cls: '.input[disabled]'
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'labels',
|
|
136
|
+
title: 'Labels',
|
|
137
|
+
description:
|
|
138
|
+
'Etiquetas de formulario: base, obligatoria e inline. Clases en <code>themes/_base/_labels.css</code>.',
|
|
139
|
+
examples: [
|
|
140
|
+
{
|
|
141
|
+
subtitle: 'Variantes',
|
|
142
|
+
items: [
|
|
143
|
+
{ html: '<label class="label">Nombre</label>', cls: '.label' },
|
|
144
|
+
{ html: '<label class="label label-required">Email</label>', cls: '.label .label-required' },
|
|
145
|
+
{
|
|
146
|
+
html:
|
|
147
|
+
'<label class="label label-inline"><input type="checkbox" /> Acepto los términos</label>',
|
|
148
|
+
cls: '.label .label-inline'
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'checkboxes',
|
|
156
|
+
title: 'Checkboxes',
|
|
157
|
+
description:
|
|
158
|
+
'Checkbox con input nativo oculto y marca SVG inline dentro de <code>.checkbox-indicator</code>. El estado visible se controla 100% con CSS (sin JS). Clases en <code>themes/_base/_checkboxes.css</code>.',
|
|
159
|
+
examples: [
|
|
160
|
+
{
|
|
161
|
+
subtitle: 'Estados',
|
|
162
|
+
items: [
|
|
163
|
+
{
|
|
164
|
+
html:
|
|
165
|
+
'<label class="checkbox"><input type="checkbox" /><span class="checkbox-indicator"><svg class="cbox__icon" viewBox="0 0 10 8" width="10" height="8" aria-hidden="true" focusable="false"><path d="M9.05823.198273 9.69185.801721 3.5417 7.25937.308228 3.86422.941848 3.26077 3.5417 5.99062 9.05823.198273Z" fill="currentColor"/></svg></span><span class="checkbox-label">Sin marcar</span></label>',
|
|
166
|
+
cls: '.checkbox'
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
html:
|
|
170
|
+
'<label class="checkbox"><input type="checkbox" checked /><span class="checkbox-indicator"><svg class="cbox__icon" viewBox="0 0 10 8" width="10" height="8" aria-hidden="true" focusable="false"><path d="M9.05823.198273 9.69185.801721 3.5417 7.25937.308228 3.86422.941848 3.26077 3.5417 5.99062 9.05823.198273Z" fill="currentColor"/></svg></span><span class="checkbox-label">Marcado</span></label>',
|
|
171
|
+
cls: '.checkbox (checked)'
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
html:
|
|
175
|
+
'<label class="checkbox"><input type="checkbox" disabled /><span class="checkbox-indicator"><svg class="cbox__icon" viewBox="0 0 10 8" width="10" height="8" aria-hidden="true" focusable="false"><path d="M9.05823.198273 9.69185.801721 3.5417 7.25937.308228 3.86422.941848 3.26077 3.5417 5.99062 9.05823.198273Z" fill="currentColor"/></svg></span><span class="checkbox-label">Disabled</span></label>',
|
|
176
|
+
cls: '.checkbox[disabled]'
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'radios',
|
|
184
|
+
title: 'Radios',
|
|
185
|
+
description:
|
|
186
|
+
'Radio buttons con el patrón <code>.checkbox-radio</code>: el input nativo se oculta visualmente y el círculo se pinta con <code>label::before</code>. Clases en <code>themes/_base/_radios.css</code>.',
|
|
187
|
+
examples: [
|
|
188
|
+
{
|
|
189
|
+
subtitle: 'Grupo',
|
|
190
|
+
items: [
|
|
191
|
+
{
|
|
192
|
+
html:
|
|
193
|
+
'<div class="checkbox-radio"><input id="cmp-radio-1" name="cmp-radio" type="radio" value="A" /><label for="cmp-radio-1"><i class="ico-radio"></i><span class="title-m">Opción A</span></label></div>',
|
|
194
|
+
cls: '.checkbox-radio'
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
html:
|
|
198
|
+
'<div class="checkbox-radio"><input id="cmp-radio-2" name="cmp-radio" type="radio" value="B" checked /><label for="cmp-radio-2"><i class="ico-radio"></i><span class="title-m">Opción B (activa)</span></label></div>',
|
|
199
|
+
cls: '.checkbox-radio (checked)'
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
html:
|
|
203
|
+
'<div class="checkbox-radio"><input id="cmp-radio-3" name="cmp-radio-2" type="radio" value="C" disabled /><label for="cmp-radio-3"><i class="ico-radio"></i><span class="title-m">Disabled</span></label></div>',
|
|
204
|
+
cls: '.checkbox-radio[disabled]'
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'switches',
|
|
212
|
+
title: 'Switches',
|
|
213
|
+
description:
|
|
214
|
+
'Interruptores on/off con el patrón <code>.checkbox-item-2</code>: pista rectangular y un <code>.checkbox-circle</code> que se desplaza al marcar. Clases en <code>themes/_base/_switches.css</code>.',
|
|
215
|
+
examples: [
|
|
216
|
+
{
|
|
217
|
+
subtitle: 'Estados',
|
|
218
|
+
items: [
|
|
219
|
+
{
|
|
220
|
+
html:
|
|
221
|
+
'<div class="checkbox-item-2"><input id="cmp-switch-1" name="cmp-switch-1" type="checkbox" /><label for="cmp-switch-1"><div class="checkbox-circle"></div><span class="theta">Inactivo</span></label></div>',
|
|
222
|
+
cls: '.checkbox-item-2'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
html:
|
|
226
|
+
'<div class="checkbox-item-2"><input id="cmp-switch-2" name="cmp-switch-2" type="checkbox" checked /><label for="cmp-switch-2"><div class="checkbox-circle"></div><span class="theta">Activado</span></label></div>',
|
|
227
|
+
cls: '.checkbox-item-2 (checked)'
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
html:
|
|
231
|
+
'<div class="checkbox-item-2"><input id="cmp-switch-3" name="cmp-switch-3" type="checkbox" disabled /><label for="cmp-switch-3"><div class="checkbox-circle"></div><span class="theta">Disabled</span></label></div>',
|
|
232
|
+
cls: '.checkbox-item-2[disabled]'
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
html:
|
|
236
|
+
'<div class="checkbox-item-2 is-error"><input id="cmp-switch-4" name="cmp-switch-4" type="checkbox" /><label for="cmp-switch-4"><div class="checkbox-circle"></div><span class="theta">Error</span></label></div>',
|
|
237
|
+
cls: '.checkbox-item-2.is-error'
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: 'forms',
|
|
245
|
+
title: 'Formularios',
|
|
246
|
+
description:
|
|
247
|
+
'Composición de campos con label flotante + estado. <code>.form-group</code> apila verticalmente los campos; cada uno usa <code>.form-input-label-2</code> para el floating label y (opcionalmente) <code>.helper-text</code> para el mensaje de estado. Clases en <code>themes/_base/_forms.css</code>.',
|
|
248
|
+
examples: [
|
|
249
|
+
{
|
|
250
|
+
subtitle: 'Grupo de formulario',
|
|
251
|
+
items: [
|
|
252
|
+
{
|
|
253
|
+
html:
|
|
254
|
+
'<div class="form-group"><div class="form-input-label-2"><input type="email" id="cmp-form-email" class="input" placeholder=" " /><label for="cmp-form-email">Email</label></div></div>',
|
|
255
|
+
cls: '.form-group > .form-input-label-2'
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
html:
|
|
259
|
+
'<div class="form-group"><div class="form-input-label-2"><textarea id="cmp-form-msg" class="input" rows="3" placeholder=" "></textarea><label for="cmp-form-msg">Mensaje</label></div></div>',
|
|
260
|
+
cls: '.form-group (con textarea)'
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
html:
|
|
264
|
+
'<div class="form-group"><div class="form-input-label-2"><input type="text" id="cmp-form-err" class="input input-error" value="" placeholder=" " /><label for="cmp-form-err">Nombre</label></div><span class="helper-text helper-text-error">Este campo es obligatorio</span></div>',
|
|
265
|
+
cls: '.form-group (con helper-text)'
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'containers',
|
|
273
|
+
title: 'Containers',
|
|
274
|
+
description:
|
|
275
|
+
'Contenedores centrados con <code>max-width</code> responsivo y/o fijo. <code>.container</code> escala con los breakpoints; <code>.container-2</code> es más estrecho; las variantes <code>.container-640</code>, <code>.container-512</code> y <code>.container-360</code> fijan un ancho concreto. Clases en <code>themes/_base/_containers.css</code>.',
|
|
276
|
+
examples: [
|
|
277
|
+
{
|
|
278
|
+
subtitle: 'Responsivos',
|
|
279
|
+
items: [
|
|
280
|
+
{
|
|
281
|
+
html:
|
|
282
|
+
'<div class="container" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">.container</div>',
|
|
283
|
+
cls: '.container'
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
html:
|
|
287
|
+
'<div class="container-2" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">.container-2</div>',
|
|
288
|
+
cls: '.container-2'
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
subtitle: 'Anchos fijos',
|
|
294
|
+
items: [
|
|
295
|
+
{
|
|
296
|
+
html:
|
|
297
|
+
'<div class="container-640" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">.container-640</div>',
|
|
298
|
+
cls: '.container-640'
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
html:
|
|
302
|
+
'<div class="container-512" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">.container-512</div>',
|
|
303
|
+
cls: '.container-512'
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
html:
|
|
307
|
+
'<div class="container-360" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">.container-360</div>',
|
|
308
|
+
cls: '.container-360'
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: 'grid',
|
|
316
|
+
title: 'Grid',
|
|
317
|
+
description:
|
|
318
|
+
'Utilidades de CSS Grid inspiradas en Tailwind. El contenedor debe tener <code>display:grid</code> y usar <code>.hg-grid-cols-N</code> para definir N columnas; los hijos usan <code>.hg-col-span-N</code> para ocupar varias. Con el prefijo <code>md:</code> se activan a partir de 768 px. Clases en <code>themes/_base/objects/_grid.css</code>.',
|
|
319
|
+
examples: [
|
|
320
|
+
{
|
|
321
|
+
subtitle: 'Columnas iguales',
|
|
322
|
+
items: [
|
|
323
|
+
{
|
|
324
|
+
html:
|
|
325
|
+
'<div class="hg-grid-cols-3" style="display:grid; gap:var(--hg-spacing-8);"><div style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">1</div><div style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">2</div><div style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">3</div></div>',
|
|
326
|
+
cls: '.hg-grid-cols-3'
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
html:
|
|
330
|
+
'<div class="hg-grid-cols-4" style="display:grid; gap:var(--hg-spacing-8);"><div style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">1</div><div style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">2</div><div style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">3</div><div style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">4</div></div>',
|
|
331
|
+
cls: '.hg-grid-cols-4'
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
subtitle: 'Col-span',
|
|
337
|
+
items: [
|
|
338
|
+
{
|
|
339
|
+
html:
|
|
340
|
+
'<div class="hg-grid-cols-12" style="display:grid; gap:var(--hg-spacing-8);"><div class="hg-col-span-8" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">span 8</div><div class="hg-col-span-4" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">span 4</div></div>',
|
|
341
|
+
cls: '.hg-grid-cols-12 > .hg-col-span-{8,4}'
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
html:
|
|
345
|
+
'<div class="hg-grid-cols-12" style="display:grid; gap:var(--hg-spacing-8);"><div class="hg-col-span-6" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">span 6</div><div class="hg-col-span-6" style="background:var(--hg-color-light-grey); padding:var(--hg-spacing-16);">span 6</div></div>',
|
|
346
|
+
cls: '.hg-grid-cols-12 > .hg-col-span-6'
|
|
347
|
+
}
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
// Escape HTML para mostrar el nombre de clase dentro de <code>.
|
|
355
|
+
function escapeHtml(str) {
|
|
356
|
+
return String(str)
|
|
357
|
+
.replace(/&/g, '&')
|
|
358
|
+
.replace(/</g, '<')
|
|
359
|
+
.replace(/>/g, '>');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function renderSection(section) {
|
|
363
|
+
const blocks = section.examples
|
|
364
|
+
.map((group) => {
|
|
365
|
+
const items = group.items
|
|
366
|
+
.map(
|
|
367
|
+
(it) => `
|
|
368
|
+
<div class="demo-item">
|
|
369
|
+
<div class="cmp-preview">${it.html}</div>
|
|
370
|
+
<div class="demo-code">${escapeHtml(it.cls)}</div>
|
|
371
|
+
</div>`
|
|
372
|
+
)
|
|
373
|
+
.join('');
|
|
374
|
+
return `
|
|
375
|
+
<h3 class="demo-subtitle">${group.subtitle}</h3>
|
|
376
|
+
<div class="demo-grid">${items}
|
|
377
|
+
</div>`;
|
|
378
|
+
})
|
|
379
|
+
.join('');
|
|
380
|
+
|
|
381
|
+
return `
|
|
382
|
+
<section class="demo-section" id="${section.id}">
|
|
383
|
+
<h2 class="demo-title">${section.title}</h2>
|
|
384
|
+
<p class="cmp-desc">${section.description}</p>
|
|
385
|
+
${blocks}
|
|
386
|
+
</section>`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Construye el header + sidebar de la página Componentes, usando la
|
|
391
|
+
* misma estructura que las demos de tema (`buildHeaderAndSidebar` en
|
|
392
|
+
* theme-transformer.js). Enlaces relativos al mismo nivel que los
|
|
393
|
+
* theme demos para mantener coherencia.
|
|
394
|
+
*/
|
|
395
|
+
function buildHeaderAndSidebar(activeThemes) {
|
|
396
|
+
const themeLinks = (activeThemes || [])
|
|
397
|
+
.map((t) => ` <a href="themes/${t.name}-demo.html">${t.label}</a>`)
|
|
398
|
+
.join('\n');
|
|
399
|
+
|
|
400
|
+
const sidebarLinks = COMPONENT_SECTIONS.map(
|
|
401
|
+
(s) => ` <a href="#${s.id}" class="guide-menu-item">${s.title}</a>`
|
|
402
|
+
).join('\n');
|
|
403
|
+
|
|
404
|
+
return `
|
|
405
|
+
<div class="guide-header">
|
|
406
|
+
<a href="index.html" class="guide-logo" style="text-decoration:none;">HolyGrail5</a>
|
|
407
|
+
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
408
|
+
<nav class="guide-nav">
|
|
409
|
+
<a href="index.html">Guía</a>
|
|
410
|
+
<a href="componentes.html" class="active">Componentes</a>
|
|
411
|
+
${themeLinks}
|
|
412
|
+
<a href="skills.html">Skills</a>
|
|
413
|
+
</nav>
|
|
414
|
+
<button class="guide-header-button" onclick="toggleSidebar()">☰</button>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
<div class="guide-sidebar-overlay" onclick="toggleSidebar()"></div>
|
|
419
|
+
|
|
420
|
+
<aside class="guide-sidebar">
|
|
421
|
+
<div class="guide-sidebar-header">
|
|
422
|
+
<div>HolyGrail5</div>
|
|
423
|
+
<p class="guide-sidebar-subtitle">Componentes base</p>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<nav class="guide-sidebar-nav">
|
|
427
|
+
<p class="guide-sidebar-title">Componentes</p>
|
|
428
|
+
|
|
429
|
+
${sidebarLinks}
|
|
430
|
+
</nav>
|
|
431
|
+
</aside>
|
|
432
|
+
|
|
433
|
+
<script>
|
|
434
|
+
function toggleSidebar() {
|
|
435
|
+
const sidebar = document.querySelector('.guide-sidebar');
|
|
436
|
+
const overlay = document.querySelector('.guide-sidebar-overlay');
|
|
437
|
+
sidebar.classList.toggle('open');
|
|
438
|
+
overlay.classList.toggle('active');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function closeSidebar() {
|
|
442
|
+
const sidebar = document.querySelector('.guide-sidebar');
|
|
443
|
+
const overlay = document.querySelector('.guide-sidebar-overlay');
|
|
444
|
+
sidebar.classList.remove('open');
|
|
445
|
+
overlay.classList.remove('active');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
window.toggleSidebar = toggleSidebar;
|
|
449
|
+
window.closeSidebar = closeSidebar;
|
|
450
|
+
</script>`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Genera el HTML completo de dist/componentes.html.
|
|
455
|
+
*
|
|
456
|
+
* La página adopta la misma estructura que las demos de tema
|
|
457
|
+
* (`guide-header` + `guide-sidebar` + `demo-container.guide-main-content`)
|
|
458
|
+
* y reutiliza `guide-styles.css` para mantener un flow consistente
|
|
459
|
+
* con el resto del sitio.
|
|
460
|
+
*
|
|
461
|
+
* @param {string} projectRoot
|
|
462
|
+
* @param {Object} [configData] - Config ya cargado (para nav dinámica).
|
|
463
|
+
* @returns {string|null}
|
|
464
|
+
*/
|
|
465
|
+
function generateComponentsPage(projectRoot, configData = null) {
|
|
466
|
+
const baseDir = path.join(projectRoot, 'themes', '_base');
|
|
467
|
+
if (!fs.existsSync(baseDir)) {
|
|
468
|
+
console.warn('⚠️ No se encontró themes/_base/, omitiendo componentes.html');
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const activeThemes = configData ? resolveActiveThemes(configData) : [];
|
|
473
|
+
const sectionsHtml = COMPONENT_SECTIONS.map(renderSection).join('\n');
|
|
474
|
+
const headerAndSidebar = buildHeaderAndSidebar(activeThemes);
|
|
475
|
+
|
|
476
|
+
return `<!DOCTYPE html>
|
|
477
|
+
<html lang="es">
|
|
478
|
+
<head>
|
|
479
|
+
<meta charset="UTF-8">
|
|
480
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
481
|
+
<title>HolyGrail5 — Componentes base</title>
|
|
482
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
483
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
484
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Instrument+Sans:regular,100,500,600,700">
|
|
485
|
+
<!-- Framework base -->
|
|
486
|
+
<link rel="stylesheet" href="output.css">
|
|
487
|
+
<!-- Tema base genérico: ${BASE_THEME} (variables + componentes) -->
|
|
488
|
+
<link rel="stylesheet" href="themes/${BASE_THEME}.css">
|
|
489
|
+
<!-- Estilos compartidos de guía (header, sidebar, demo-*) -->
|
|
490
|
+
<link rel="stylesheet" href="guide-styles.css">
|
|
491
|
+
<style>
|
|
492
|
+
body {
|
|
493
|
+
font-family: 'Instrument Sans', sans-serif !important;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/* Descripción de cada sección (debajo del título) */
|
|
497
|
+
.cmp-desc {
|
|
498
|
+
font-size: 14px;
|
|
499
|
+
line-height: 1.6;
|
|
500
|
+
color: #555;
|
|
501
|
+
margin: 0 0 1.5rem;
|
|
502
|
+
max-width: 720px;
|
|
503
|
+
}
|
|
504
|
+
.cmp-desc code {
|
|
505
|
+
background: #f3f3f3;
|
|
506
|
+
padding: 2px 6px;
|
|
507
|
+
border-radius: 4px;
|
|
508
|
+
font-size: 0.88em;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* Preview de cada componente dentro de .demo-item */
|
|
512
|
+
.cmp-preview {
|
|
513
|
+
min-height: 48px;
|
|
514
|
+
display: flex;
|
|
515
|
+
align-items: center;
|
|
516
|
+
flex-wrap: wrap;
|
|
517
|
+
gap: 0.5rem;
|
|
518
|
+
margin-bottom: var(--hg-spacing-12);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* Los inputs con floating label necesitan respirar: el label flota
|
|
522
|
+
encima, el helper-text se apila debajo. */
|
|
523
|
+
#inputs .cmp-preview,
|
|
524
|
+
#forms .cmp-preview {
|
|
525
|
+
display: block;
|
|
526
|
+
}
|
|
527
|
+
#inputs .cmp-preview > .form-input-label-2,
|
|
528
|
+
#forms .cmp-preview > .form-group {
|
|
529
|
+
width: 100%;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* Containers y Grid son estructuras de layout: interesan como
|
|
533
|
+
bloques a 100% del item, no como chips alineados horizontalmente. */
|
|
534
|
+
#containers .demo-grid,
|
|
535
|
+
#grid .demo-grid {
|
|
536
|
+
grid-template-columns: 1fr;
|
|
537
|
+
}
|
|
538
|
+
#containers .cmp-preview,
|
|
539
|
+
#grid .cmp-preview {
|
|
540
|
+
display: block;
|
|
541
|
+
}
|
|
542
|
+
#containers .cmp-preview > [class^="container"],
|
|
543
|
+
#grid .cmp-preview > [class^="hg-grid-"] {
|
|
544
|
+
width: 100%;
|
|
545
|
+
max-width: 100%;
|
|
546
|
+
}
|
|
547
|
+
</style>
|
|
548
|
+
</head>
|
|
549
|
+
<body>
|
|
550
|
+
${headerAndSidebar}
|
|
551
|
+
|
|
552
|
+
<main class="demo-container guide-main-content">
|
|
553
|
+
<h2 class="demo-title">Componentes base</h2>
|
|
554
|
+
|
|
555
|
+
<div class="demo-section-2">
|
|
556
|
+
<h3>¿Qué es esta página?</h3>
|
|
557
|
+
<p class="mb-16">
|
|
558
|
+
Librería de componentes compartidos que viven en
|
|
559
|
+
<code>themes/_base/</code>. Se renderizan con el tema
|
|
560
|
+
<strong>${BASE_THEME[0].toUpperCase() + BASE_THEME.slice(1)}</strong>
|
|
561
|
+
como base genérica del framework; cualquier otro tema puede
|
|
562
|
+
aplicarse encima para redefinir la identidad visual sin tocar
|
|
563
|
+
el HTML.
|
|
564
|
+
</p>
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
${sectionsHtml}
|
|
568
|
+
</main>
|
|
569
|
+
</body>
|
|
570
|
+
</html>`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// CLI
|
|
574
|
+
if (require.main === module) {
|
|
575
|
+
const projectRoot = path.join(__dirname, '..', '..');
|
|
576
|
+
const html = generateComponentsPage(projectRoot);
|
|
577
|
+
if (html) {
|
|
578
|
+
const outputPath = path.join(projectRoot, 'dist', 'componentes.html');
|
|
579
|
+
fs.writeFileSync(outputPath, html, 'utf-8');
|
|
580
|
+
console.log('✅ componentes.html generado en dist/');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
module.exports = { generateComponentsPage };
|
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { resolveActiveThemes } = require('../generators/utils');
|
|
9
|
+
|
|
10
|
+
// Fallback usado cuando el llamador NO inyecta un config (p. ej. CLI
|
|
11
|
+
// standalone `node src/build/skills-generator.js`). Permite seguir
|
|
12
|
+
// generando skills.html con los temas históricos aunque falte el config
|
|
13
|
+
// activo. Si el BuildOrchestrator llama a `generateSkillsPage(root, config)`,
|
|
14
|
+
// este fallback queda ignorado y se usa la lista real de temas activos.
|
|
15
|
+
const FALLBACK_THEMES_IN_NAV = [
|
|
16
|
+
{ name: 'dutti', label: 'Tema Dutti' },
|
|
17
|
+
{ name: 'limited', label: 'Tema Limited' }
|
|
18
|
+
];
|
|
8
19
|
|
|
9
20
|
/**
|
|
10
21
|
* Parsea Markdown básico a HTML
|
|
@@ -204,8 +215,16 @@ function extractSections(md) {
|
|
|
204
215
|
|
|
205
216
|
/**
|
|
206
217
|
* Main generator
|
|
218
|
+
*
|
|
219
|
+
* @param {string} projectRoot - Raíz del proyecto (donde vive skills/).
|
|
220
|
+
* @param {Object} [configData] - config.json ya cargado. Si se pasa,
|
|
221
|
+
* los enlaces a demos en la nav de skills.html se construyen a partir
|
|
222
|
+
* de los temas realmente activos (`config.themes[]` o `config.theme`),
|
|
223
|
+
* evitando enlazar a ficheros `dutti-demo.html` / `limited-demo.html`
|
|
224
|
+
* que quizá no existan en dist/. Si se omite, se usa el fallback
|
|
225
|
+
* histórico con Dutti + Limited para no romper ejecuciones standalone.
|
|
207
226
|
*/
|
|
208
|
-
function generateSkillsPage(projectRoot) {
|
|
227
|
+
function generateSkillsPage(projectRoot, configData = null) {
|
|
209
228
|
const skillsDir = path.join(projectRoot, 'skills');
|
|
210
229
|
|
|
211
230
|
if (!fs.existsSync(skillsDir)) {
|
|
@@ -235,12 +254,26 @@ function generateSkillsPage(projectRoot) {
|
|
|
235
254
|
raw: md
|
|
236
255
|
};
|
|
237
256
|
|
|
257
|
+
// Resolver temas activos para la nav. Si el llamador no pasó un
|
|
258
|
+
// config, usamos el fallback estático.
|
|
259
|
+
const activeThemes = configData
|
|
260
|
+
? resolveActiveThemes(configData)
|
|
261
|
+
: FALLBACK_THEMES_IN_NAV;
|
|
262
|
+
|
|
238
263
|
// Generate HTML
|
|
239
|
-
const html = buildPage(devGuide);
|
|
264
|
+
const html = buildPage(devGuide, activeThemes);
|
|
240
265
|
return html;
|
|
241
266
|
}
|
|
242
267
|
|
|
243
|
-
function buildPage(skill) {
|
|
268
|
+
function buildPage(skill, activeThemes = FALLBACK_THEMES_IN_NAV) {
|
|
269
|
+
// Nav de temas construida dinámicamente a partir de la lista activa.
|
|
270
|
+
// Se respeta el orden definido en config.themes[].
|
|
271
|
+
const themeNavLinks = (activeThemes && activeThemes.length > 0
|
|
272
|
+
? activeThemes
|
|
273
|
+
: FALLBACK_THEMES_IN_NAV
|
|
274
|
+
)
|
|
275
|
+
.map(t => ` <a href="themes/${t.name}-demo.html">${t.label}</a>`)
|
|
276
|
+
.join('\n');
|
|
244
277
|
const toc = skill.sections.map(sec =>
|
|
245
278
|
`<a href="#${sec.id}" class="sk-toc-link">${sec.text}</a>`
|
|
246
279
|
).join('\n ');
|
|
@@ -281,7 +314,11 @@ function buildPage(skill) {
|
|
|
281
314
|
text-decoration: none; color: #000; width: max-content;
|
|
282
315
|
}
|
|
283
316
|
.guide-nav { display: flex; gap: 1.5rem; align-items: center; }
|
|
284
|
-
.guide-nav a {
|
|
317
|
+
.guide-nav a {
|
|
318
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
319
|
+
font-size: 13px; color: #666; text-decoration: none;
|
|
320
|
+
transition: color 0.2s; text-transform: uppercase; letter-spacing: 0.05em;
|
|
321
|
+
}
|
|
285
322
|
.guide-nav a:hover { color: #000; }
|
|
286
323
|
.guide-nav a.active { color: #000; font-weight: 600; }
|
|
287
324
|
|
|
@@ -386,7 +423,8 @@ function buildPage(skill) {
|
|
|
386
423
|
<a href="index.html" class="guide-logo" style="text-decoration:none;">HolyGrail5</a>
|
|
387
424
|
<nav class="guide-nav">
|
|
388
425
|
<a href="index.html">Guía</a>
|
|
389
|
-
<a href="
|
|
426
|
+
<a href="componentes.html">Componentes</a>
|
|
427
|
+
${themeNavLinks}
|
|
390
428
|
<a href="skills.html" class="active">Skills</a>
|
|
391
429
|
</nav>
|
|
392
430
|
</div>
|