@edc-motor/admin-kit 0.5.6 → 0.5.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edc-motor/admin-kit",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "description": "EdC Motor — kit del panel de administración: layout, gestores CRUD, imágenes, PDF, páginas, configuración, usuarios y copias (paquete fuente: lo compila el consumidor con Vite)",
5
5
  "license": "GPL-3.0-only",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@edc-motor/ui": "^0.5.6",
36
+ "@edc-motor/ui": "^0.5.8",
37
37
  "axios": "^1.7.0"
38
38
  },
39
39
  "peerDependencies": {
@@ -143,36 +143,10 @@
143
143
 
144
144
  &__flags { grid-area: flags; display: flex; gap: $space-1; }
145
145
 
146
- // Sección "General": ARRIBA del formulario (antes al fondo, como "Ajustes
147
- // comunes", y pasaba desapercibida), con su titulito discreto fila 1:
148
- // anchura + bloque padre; fila 2: los interruptores; fila 3: color de
149
- // fondo + alineación general del bloque. Los campos del TIPO van después.
150
- &__common {
151
- border-bottom: 1px dashed $border;
152
- padding-bottom: $space-4;
153
- display: grid;
154
- gap: $space-3;
155
-
156
- &-title {
157
- color: $text-2;
158
- font-size: $fs-13;
159
- }
160
- }
161
-
162
- // Cada fila de General: dos columnas iguales, apiladas en un modal
163
- // angosto (container query sobre el cuerpo del modal, no el viewport).
164
- &__common-row {
165
- display: grid;
166
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
167
- gap: $space-3;
168
- align-items: start;
169
-
170
- > .checkbox { display: flex; }
171
-
172
- @container modal-body (max-width: #{$bp-sm - 1px}) {
173
- grid-template-columns: 1fr;
174
- }
175
- }
146
+ // La sección "General" del modal de bloque y los campos del tipo van
147
+ // ahora en fieldsets del sistema compartido del ui (.form-fieldset con
148
+ // legend + filas .form-row): mismo lenguaje que el resto de formularios
149
+ // del admin. Aquí ya no queda CSS propio para ello.
176
150
 
177
151
  // Bloques anidados (parent_id, sin límite de niveles): sangría por
178
152
  // profundidad REAL — `--depth` lo pone el componente en línea.
@@ -190,88 +164,6 @@
190
164
 
191
165
  &__field-main { min-width: 0; }
192
166
 
193
- // Alineación junto a su campo: input flexible + select de ancho
194
- // contenido (~180px). En un modal angosto (container query sobre el
195
- // cuerpo del modal, no el viewport) se apilan, el select debajo.
196
- &__field--row {
197
- display: flex;
198
- // ARRIBA (no al fondo): con un textarea alto (p. ej. el subtítulo) el
199
- // select de alineación debe quedar a la altura de las etiquetas, no
200
- // descolgado al fondo de la fila.
201
- align-items: flex-start;
202
- gap: $space-3;
203
-
204
- .schema-fields__field-main { flex: 1 1 0; }
205
- }
206
-
207
- // Ancho justo para "Centrado"/"Izquierda" + etiqueta en es/en/eu sin
208
- // cortarse (180px sobraba de aire).
209
- &__field-align { flex: 0 0 140px; }
210
-
211
- // Fila declarada en el esquema (Field::row): el grupo baja por recursión
212
- // como un .schema-fields anidado — aquí solo se reparte en columnas
213
- // iguales MIENTRAS QUEPAN (auto-fit: con poco ancho van cayendo solas);
214
- // el checkbox se centra en vertical junto a sus compañeros de fila.
215
- &__field--pair > .schema-fields {
216
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
217
- gap: $space-3;
218
- align-items: start;
219
-
220
- > .schema-fields__field:has(> .schema-fields__field-main > .checkbox) {
221
- align-self: center;
222
- }
223
- }
224
-
225
- // Imagen a dos columnas: el input a la izquierda, sus ajustes (posición,
226
- // escalado, reparto de columnas) apilados a la derecha. `stretch` para que
227
- // la columna de la imagen llene el alto de la fila; el cuadro de arrastre
228
- // YA NO se estira con ella (0.4.28): conserva su tamaño natural (160×160)
229
- // y se centra en el espacio — el centrado vive en el componente
230
- // (_image-upload.scss de @edc-motor/ui), común a todos los inputs.
231
- &__field--image-group {
232
- display: grid;
233
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
234
- gap: $space-3;
235
- align-items: stretch;
236
-
237
- .schema-fields__field-main {
238
- display: flex;
239
- flex-direction: column;
240
- min-width: 0;
241
- }
242
-
243
- .schema-fields__image {
244
- display: flex;
245
- flex-direction: column;
246
- gap: $space-1;
247
- flex: 1;
248
- min-height: 0;
249
- }
250
-
251
- .image-upload {
252
- flex: 1;
253
- min-height: 0;
254
- }
255
- }
256
-
257
- &__field-image-settings {
258
- display: grid;
259
- gap: $space-3;
260
- }
261
-
262
- @container modal-body (max-width: #{$bp-sm - 1px}) {
263
- &__field--row {
264
- flex-direction: column;
265
- align-items: stretch;
266
- }
267
- &__field-align { flex-basis: auto; }
268
-
269
- &__field--image-group { grid-template-columns: 1fr; }
270
-
271
- // Las filas declaradas (Field::row) apilan en el modal angosto.
272
- &__field--pair > .schema-fields { grid-template-columns: 1fr; }
273
- }
274
-
275
167
  &__color,
276
168
  &__image {
277
169
  display: grid;
@@ -319,3 +211,17 @@
319
211
  font-weight: 600;
320
212
  }
321
213
  }
214
+
215
+ // Encaje de SchemaFields con las filas del sistema compartido (.form-row,
216
+ // @edc-motor/ui). Un .schema-fields que llega como HIJO directo de una fila
217
+ // (la recursión de las filas declaradas Field::row, o los mini-SchemaFields
218
+ // de la sección General de PageBlocks) se DISUELVE (display: contents): sus
219
+ // campos pasan a ser celdas directas de la fila — sin envoltorios que
220
+ // rompan el grid ni re-aniden gaps.
221
+ .form-row > .schema-fields { display: contents; }
222
+
223
+ // El checkbox de un campo del esquema se centra en vertical junto a sus
224
+ // compañeros de fila (que llevan etiqueta encima y quedarían descolgados).
225
+ .form-row .schema-fields__field:has(> .schema-fields__field-main > .checkbox) {
226
+ align-self: center;
227
+ }
@@ -660,13 +660,14 @@ defineExpose({ reload: load })
660
660
  >
661
661
  <template v-if="modalType">
662
662
  <!-- General ARRIBA del todo (antes iba al fondo, como "Ajustes
663
- comunes", y pasaba desapercibida): anchura + bloque padre;
664
- los interruptores; color de fondo + alineación general del
665
- bloque. Los campos del tipo van DESPUÉS. -->
666
- <div class="page-blocks__common">
667
- <span class="page-blocks__common-title">{{ L.common }}</span>
668
-
669
- <div class="page-blocks__common-row">
663
+ comunes", y pasaba desapercibida), como fieldset del sistema
664
+ compartido (mismo lenguaje que el resto de formularios; el
665
+ primero no lleva raya): anchura + bloque padre; los
666
+ interruptores; color de fondo + alineación general. -->
667
+ <fieldset class="form-fieldset">
668
+ <legend>{{ L.common }}</legend>
669
+
670
+ <div class="form-row">
670
671
  <SchemaFields
671
672
  v-model="form"
672
673
  :fields="generalWidthFields"
@@ -682,12 +683,12 @@ defineExpose({ reload: load })
682
683
  />
683
684
  </div>
684
685
 
685
- <div class="page-blocks__common-row">
686
+ <div class="form-row">
686
687
  <BaseCheckbox v-model="formPrintable" :label="L.printable" />
687
688
  <BaseCheckbox v-model="formIndexable" :label="L.indexable" />
688
689
  </div>
689
690
 
690
- <div class="page-blocks__common-row">
691
+ <div class="form-row">
691
692
  <SchemaFields
692
693
  v-model="form"
693
694
  :fields="generalBackgroundFields"
@@ -703,17 +704,22 @@ defineExpose({ reload: load })
703
704
  :translate="translate"
704
705
  />
705
706
  </div>
706
- </div>
707
-
708
- <SchemaFields
709
- v-model="form"
710
- :fields="typeFieldsWithAligns"
711
- :locales="locales"
712
- :api="api"
713
- :icons="icons"
714
- :rich-labels="richLabels"
715
- :translate="translate"
716
- />
707
+ </fieldset>
708
+
709
+ <!-- Los campos del TIPO, en su propio fieldset (la raya dashed
710
+ separa General del contenido, como antes). -->
711
+ <fieldset class="form-fieldset">
712
+ <legend>{{ L.panelContent }}</legend>
713
+ <SchemaFields
714
+ v-model="form"
715
+ :fields="typeFieldsWithAligns"
716
+ :locales="locales"
717
+ :api="api"
718
+ :icons="icons"
719
+ :rich-labels="richLabels"
720
+ :translate="translate"
721
+ />
722
+ </fieldset>
717
723
  </template>
718
724
  </EditModal>
719
725
 
@@ -15,6 +15,7 @@ import {
15
15
  RichTextInput,
16
16
  TranslatableImage,
17
17
  TranslatableInput,
18
+ type ColorPreset,
18
19
  type RichIcon,
19
20
  type RichTextLabels,
20
21
  type SelectOption,
@@ -67,24 +68,30 @@ const emit = defineEmits<{ 'update:modelValue': [value: Record<string, unknown>]
67
68
 
68
69
  // Convenciones genéricas de agrupado (doc 03 ampliado):
69
70
  //
71
+ // El layout se pinta con las filas del sistema compartido del ui
72
+ // (.form-row y variantes: columnas FIJAS + cortes de container query sobre
73
+ // modal-body; nada de auto-fit):
74
+ //
70
75
  // 1) Alineaciones junto a su campo: un select `<base>_align` cuyo campo
71
76
  // `<base>` (o `<base>_text`, caso de `button_align`/`button_text`) esté
72
- // en la MISMA lista se pinta junto a él, en una fila de dos columnas
73
- // (PageBlocks inyecta title_align/subtitle_align —comunes del motor—
74
- // junto a title/subtitle antes de pasar la lista aquí). El `align`
75
- // general del bloque no tiene campo objetivo ("align" no acaba en
76
- // "_align" con un prefijo no vacío) y se queda fuera de esta pareja.
77
- // 2) Imagen a dos columnas: el campo `image` con alguno de sus ajustes
78
- // (`image_position`, `image_fit`, `image_columns`) en la misma lista se
79
- // agrupa con ellos columna 1 la imagen, columna 2 los ajustes
80
- // apilados.
77
+ // en la MISMA lista se pinta junto a él en una .form-row--wide-left
78
+ // el campo (input/textarea/wysiwyg) ancho a la izquierda, el select
79
+ // estrecho a la derecha (PageBlocks inyecta title_align/subtitle_align
80
+ // —comunes del motor— junto a title/subtitle antes de pasar la lista
81
+ // aquí). El `align` general del bloque no tiene campo objetivo ("align"
82
+ // no acaba en "_align" con un prefijo no vacío) y queda fuera.
83
+ // 2) Imagen a dos columnas (.form-row--media): el campo `image` con alguno
84
+ // de sus ajustes (`image_position`, `image_fit`, `image_columns`) en la
85
+ // misma lista se agrupa con ellos — la celda izquierda (el input) llena
86
+ // todo el alto de la fila; la derecha es la pila de ajustes.
81
87
  // 3) Filas DECLARADAS en el esquema (Field::row('nombre')): los campos que
82
- // comparten `row` van juntos en una fila de columnas iguales mientras
83
- // quepan (auto-fit; en un modal angosto apilan). Se pintan por RECURSIÓN
84
- // sobre este mismo componente (los compañeros pierden su `row` al bajar,
85
- // para no re-agrupar en bucle). La fila declarada es intención EXPLÍCITA
86
- // del esquema: gana a las convenciones 1 y 2 — un campo con `row` queda
87
- // fuera del emparejado implícito (tanto de origen como de destino).
88
+ // comparten `row` van juntos en una .form-row (--3 si son 3+; en un
89
+ // modal angosto apilan). Se pintan por RECURSIÓN sobre este mismo
90
+ // componente (los compañeros pierden su `row` al bajar, para no
91
+ // re-agrupar en bucle; el .schema-fields anidado se disuelve por CSS).
92
+ // La fila declarada es intención EXPLÍCITA del esquema: gana a las
93
+ // convenciones 1 y 2 un campo con `row` queda fuera del emparejado
94
+ // implícito (tanto de origen como de destino).
88
95
  const IMAGE_SETTING_KEYS = ['image_position', 'image_fit', 'image_columns']
89
96
 
90
97
  interface LayoutItem {
@@ -196,6 +203,16 @@ function selectOptions(field: FieldSchema): SelectOption[] {
196
203
  }))
197
204
  }
198
205
 
206
+ /** Presets DINÁMICOS de un campo color (sus options, valores token:* — hoy
207
+ * solo el `background` común de los bloques los declara): mismas etiquetas
208
+ * y mismo camino i18n que las opciones de un select. */
209
+ function colorPresets(field: FieldSchema): ColorPreset[] {
210
+ return Object.entries(field.options ?? {}).map(([value, text]) => ({
211
+ value,
212
+ label: props.translate?.(`blockOptions.${field.key}.${value}`, text) ?? text,
213
+ }))
214
+ }
215
+
199
216
  // --- Anidados (group / repeater) ---
200
217
 
201
218
  function groupValue(field: FieldSchema): Record<string, unknown> {
@@ -243,9 +260,15 @@ function addLabel(): string {
243
260
  <template>
244
261
  <div class="schema-fields">
245
262
  <template v-for="{ field, align, imageSettings, rowFields } in layout" :key="field.key">
246
- <!-- Fila declarada (Field::row): los campos del grupo por recursión,
247
- en columnas iguales (los compañeros bajan sin `row`) -->
248
- <div v-if="rowFields" class="schema-fields__field schema-fields__field--pair">
263
+ <!-- Fila declarada (Field::row): los campos del grupo por recursión
264
+ sobre una .form-row del ui (columnas fijas: 2, o 3 si la fila trae
265
+ 3+ campos; el .schema-fields anidado se disuelve por CSS y los
266
+ compañeros bajan sin `row`) -->
267
+ <div
268
+ v-if="rowFields"
269
+ class="schema-fields__field form-row"
270
+ :class="{ 'form-row--3': rowFields.length >= 3 }"
271
+ >
249
272
  <SchemaFields
250
273
  :fields="rowFields"
251
274
  :model-value="modelValue"
@@ -257,14 +280,45 @@ function addLabel(): string {
257
280
  @update:model-value="(v) => emit('update:modelValue', v)"
258
281
  />
259
282
  </div>
260
- <div
261
- v-else
262
- class="schema-fields__field"
263
- :class="{
264
- 'schema-fields__field--row': !!align,
265
- 'schema-fields__field--image-group': !!imageSettings,
266
- }"
267
- >
283
+
284
+ <!-- Fila de imagen (convención 2), el caso especial del sistema: el
285
+ input de imagen es UNA sola celda a todo el alto (izquierda) y los
286
+ ajustes (posición, escalado, reparto de columnas) van apilados en
287
+ la columna derecha (.form-row__stack) -->
288
+ <div v-else-if="imageSettings" class="schema-fields__field form-row form-row--media">
289
+ <TranslatableImage
290
+ v-if="field.translatable"
291
+ :model-value="imageTranslations(field)"
292
+ :locales="locales"
293
+ :label="label(field)"
294
+ :required="field.required"
295
+ @update:model-value="(v) => set(field.key, v)"
296
+ />
297
+ <div v-else class="schema-fields__image">
298
+ <span class="form-field__label">{{ label(field) }}</span>
299
+ <ImageUpload
300
+ :model-value="imageFile(field)"
301
+ :current-url="imageCurrentUrl(field)"
302
+ @update:model-value="(f: File | null) => set(field.key, f)"
303
+ @remove="set(field.key, null)"
304
+ />
305
+ </div>
306
+ <div class="form-row__stack">
307
+ <BaseSelect
308
+ v-for="setting in imageSettings"
309
+ :key="setting.key"
310
+ :model-value="(modelValue[setting.key] as string) ?? (setting.default as string) ?? ''"
311
+ :label="label(setting)"
312
+ :options="selectOptions(setting)"
313
+ @update:model-value="(v) => set(setting.key, v)"
314
+ />
315
+ </div>
316
+ </div>
317
+
318
+ <!-- Campo suelto — o, con su select de alineación al lado (convención
319
+ 1), fila ancha-izquierda del ui: el campo (input/textarea/wysiwyg)
320
+ a la izquierda y el select pequeño a la derecha -->
321
+ <div v-else class="schema-fields__field" :class="{ 'form-row form-row--wide-left': !!align }">
268
322
  <div class="schema-fields__field-main">
269
323
  <!-- Traducibles: text / textarea / richtext van al TranslatableInput -->
270
324
  <TranslatableInput
@@ -336,6 +390,7 @@ function addLabel(): string {
336
390
  <span class="form-field__label">{{ label(field) }}</span>
337
391
  <PaletteColorPicker
338
392
  :model-value="(modelValue[field.key] as string) ?? null"
393
+ :presets="colorPresets(field)"
339
394
  @update:model-value="(v) => set(field.key, v)"
340
395
  />
341
396
  </div>
@@ -433,28 +488,14 @@ function addLabel(): string {
433
488
  </div>
434
489
 
435
490
  <!-- Alineación del campo (title_align/subtitle_align/author_align/…),
436
- en la misma fila que su campo (columna de ancho contenido). -->
491
+ en la misma fila que su campo (columna derecha, estrecha). -->
437
492
  <BaseSelect
438
493
  v-if="align"
439
- class="schema-fields__field-align"
440
494
  :model-value="(modelValue[align.key] as string) ?? (align.default as string) ?? ''"
441
495
  :label="label(align)"
442
496
  :options="selectOptions(align)"
443
497
  @update:model-value="(v) => set(align!.key, v)"
444
498
  />
445
-
446
- <!-- Ajustes de la imagen (image_position/image_fit/image_columns),
447
- apilados en la segunda columna junto al input de imagen. -->
448
- <div v-if="imageSettings" class="schema-fields__field-image-settings">
449
- <BaseSelect
450
- v-for="setting in imageSettings"
451
- :key="setting.key"
452
- :model-value="(modelValue[setting.key] as string) ?? (setting.default as string) ?? ''"
453
- :label="label(setting)"
454
- :options="selectOptions(setting)"
455
- @update:model-value="(v) => set(setting.key, v)"
456
- />
457
- </div>
458
499
  </div>
459
500
  </template>
460
501
  </div>