@edc-motor/ui 0.5.3 → 0.5.5
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 +1 -1
- package/scss/components/_app-right-sidebar.scss +5 -1
- package/scss/components/_base-pagination.scss +11 -5
- package/scss/components/_multi-select.scss +9 -0
- package/scss/components/_palette-color-picker.scss +16 -2
- package/src/components/AppRightSidebar.vue +22 -4
- package/src/components/MultiSelect.vue +20 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edc-motor/ui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "EdC Motor — componentes públicos Vue 3 + tokens SCSS para webs de juegos de mesa (paquete fuente: lo compila el consumidor con Vite)",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
bottom: 0;
|
|
27
27
|
z-index: 40; // por debajo de la cabecera fija del cascarón (50)
|
|
28
28
|
// En viewports mínimos deja sitio al asa (el telón cubre el resto).
|
|
29
|
-
|
|
29
|
+
// OJO: 100% y no 100vw — el ancho porcentual de un fixed resuelve contra
|
|
30
|
+
// el viewport REAL (sin la barra de scroll del navegador); con 100vw la
|
|
31
|
+
// scrollbar, cuando aparece, robaba su ancho y el asa/cierre quedaba
|
|
32
|
+
// fuera de pantalla en el tamaño más estrecho.
|
|
33
|
+
width: min(var(--app-right-sidebar-width), 100% - 36px);
|
|
30
34
|
transform: translateX(100%);
|
|
31
35
|
transition: transform 0.25s ease;
|
|
32
36
|
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// Números de página clicables; el actual, marcado con aria-current.
|
|
40
|
+
// Los que NO son el actual van en caja como prev/next (fondo semiopaco +
|
|
41
|
+
// borde visibles ya en reposo); al hover, acento sutil: tinte de acento
|
|
42
|
+
// al 12% de fondo y el número en color de acento. El actual, como
|
|
43
|
+
// siempre: relleno de acento con texto de contraste.
|
|
40
44
|
&__number {
|
|
41
45
|
display: inline-flex;
|
|
42
46
|
align-items: center;
|
|
@@ -44,17 +48,19 @@
|
|
|
44
48
|
min-width: 32px;
|
|
45
49
|
height: 32px;
|
|
46
50
|
padding: 0 $space-1;
|
|
47
|
-
background:
|
|
48
|
-
border: 1px solid
|
|
51
|
+
background: color-mix(in srgb, $surface 65%, transparent);
|
|
52
|
+
border: 1px solid $border;
|
|
49
53
|
border-radius: $radius-sm;
|
|
50
54
|
color: $text-2;
|
|
51
55
|
font-size: $fs-13;
|
|
56
|
+
// Más peso sin tocar el tamaño de la caja (32px se quedan como están).
|
|
57
|
+
font-weight: $k-fw-semibold;
|
|
52
58
|
font-variant-numeric: tabular-nums;
|
|
53
|
-
transition: color 0.15s ease, border-color 0.15s ease;
|
|
59
|
+
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
|
|
54
60
|
|
|
55
61
|
&:hover {
|
|
56
|
-
color: $
|
|
57
|
-
|
|
62
|
+
color: $accent-500;
|
|
63
|
+
background: color-mix(in srgb, $accent-500 12%, transparent);
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
&[aria-current="page"] {
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
padding-block: 4px;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Variante compacta (prop compactTrigger): alto FIJO del select simple y
|
|
21
|
+
// resumen en una línea (el .base-select__value de arriba ya elipsa); las
|
|
22
|
+
// elegidas se pintan fuera (chips del consumidor), no dentro del trigger.
|
|
23
|
+
&--compact .base-select__trigger {
|
|
24
|
+
height: $input-height;
|
|
25
|
+
min-height: 0;
|
|
26
|
+
padding-block: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
&__tags {
|
|
21
30
|
display: flex;
|
|
22
31
|
flex-wrap: wrap;
|
|
@@ -32,9 +32,23 @@
|
|
|
32
32
|
background: var(--swatch-color);
|
|
33
33
|
color: #fff;
|
|
34
34
|
cursor: pointer;
|
|
35
|
-
transition:
|
|
35
|
+
transition: box-shadow 0.15s, border-color 0.15s;
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// Realce al hover: SOLO halo (sombra), nunca transform — el
|
|
38
|
+
// scale(1.06) de antes rasterizaba el swatch (y sus 2px de borde) en
|
|
39
|
+
// fracciones de píxel durante la transición: al pasear el ratón por
|
|
40
|
+
// el formulario del modal de bloque la fila entera «bailaba» y los
|
|
41
|
+
// bordes finos vecinos parpadeaban (peor aún con zoom/DPI
|
|
42
|
+
// fraccionario). El halo se pinta fuera de la caja y no toca ni
|
|
43
|
+
// layout ni raster de los vecinos.
|
|
44
|
+
// (el seleccionado conserva su anillo: el halo solo aplica sin marcar)
|
|
45
|
+
&:hover:not(#{&}--selected) {
|
|
46
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--swatch-color) 40%, transparent);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&--custom-idle:hover:not(#{&}--selected) {
|
|
50
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, #3b82f6 40%, transparent);
|
|
51
|
+
}
|
|
38
52
|
|
|
39
53
|
&--selected { box-shadow: 0 0 0 2px $surface, 0 0 0 4px var(--swatch-color); }
|
|
40
54
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, onBeforeUnmount, onMounted } from 'vue'
|
|
2
|
+
import { computed, onBeforeUnmount, onMounted, watch } from 'vue'
|
|
3
3
|
import { Funnel, X } from '@lucide/vue'
|
|
4
4
|
import { useAppRightSidebar } from '../composables/useAppRightSidebar'
|
|
5
5
|
|
|
@@ -17,7 +17,11 @@ import { useAppRightSidebar } from '../composables/useAppRightSidebar'
|
|
|
17
17
|
// .app-footer sobre la clase `--docked`; la cabecera no lo necesita: la
|
|
18
18
|
// barra no la tapa); por debajo de OVERLAY_BREAKPOINT es un drawer
|
|
19
19
|
// superpuesto con telón (también bajo la cabecera, que sigue usable), click
|
|
20
|
-
// fuera y Escape.
|
|
20
|
+
// fuera y Escape. Con `overlayAlways` el modo drawer vale para TODAS las
|
|
21
|
+
// anchuras: la barra se superpone siempre al contenido (nunca le roba ancho
|
|
22
|
+
// al main) y se abre/cierra igual que el drawer estrecho — para juegos que
|
|
23
|
+
// prefieren no hacer hueco ni en escritorio. Se abre y se cierra con el ASA
|
|
24
|
+
// anclada a la propia barra
|
|
21
25
|
// (Funnel cerrada / X abierta), visible solo si la vista registró
|
|
22
26
|
// contenido; el cascarón puede ajustar su altura con
|
|
23
27
|
// --app-right-sidebar-handle-top (relativa al techo de la barra).
|
|
@@ -32,8 +36,19 @@ const props = withDefaults(
|
|
|
32
36
|
closeLabel?: string
|
|
33
37
|
/** Título por defecto si la vista registra sin título. */
|
|
34
38
|
fallbackTitle?: string
|
|
39
|
+
/**
|
|
40
|
+
* Drawer superpuesto en TODAS las anchuras: nunca "atraca" (docked) ni
|
|
41
|
+
* le hace hueco el cascarón — siempre telón + superposición, como en
|
|
42
|
+
* estrecho. Por defecto, solo por debajo del corte de 900px.
|
|
43
|
+
*/
|
|
44
|
+
overlayAlways?: boolean
|
|
35
45
|
}>(),
|
|
36
|
-
{
|
|
46
|
+
{
|
|
47
|
+
openLabel: 'Abrir el panel',
|
|
48
|
+
closeLabel: 'Cerrar el panel',
|
|
49
|
+
fallbackTitle: 'Filtros',
|
|
50
|
+
overlayAlways: false,
|
|
51
|
+
},
|
|
37
52
|
)
|
|
38
53
|
|
|
39
54
|
const { hasContent, collapsed, mobileOpen, overlay, title, isOpen, toggle, closeMobile } =
|
|
@@ -44,10 +59,13 @@ const { hasContent, collapsed, mobileOpen, overlay, title, isOpen, toggle, close
|
|
|
44
59
|
const OVERLAY_BREAKPOINT = 900
|
|
45
60
|
|
|
46
61
|
function checkOverlay() {
|
|
47
|
-
overlay.value = window.innerWidth < OVERLAY_BREAKPOINT
|
|
62
|
+
overlay.value = props.overlayAlways || window.innerWidth < OVERLAY_BREAKPOINT
|
|
48
63
|
if (!overlay.value) mobileOpen.value = false
|
|
49
64
|
}
|
|
50
65
|
|
|
66
|
+
// Si el consumidor cambia el modo en caliente, el estado se recalcula.
|
|
67
|
+
watch(() => props.overlayAlways, checkOverlay)
|
|
68
|
+
|
|
51
69
|
// En el drawer, Escape cierra (el click fuera lo cubre el telón).
|
|
52
70
|
function onKeydown(e: KeyboardEvent) {
|
|
53
71
|
if (e.key === 'Escape' && overlay.value && mobileOpen.value) closeMobile()
|
|
@@ -13,6 +13,11 @@ import type { SelectOption } from './BaseSelect.vue'
|
|
|
13
13
|
// BaseSelect). El trigger pinta las elegidas como etiquetas con aspa: se
|
|
14
14
|
// quitan desde ahí sin abrir el panel (también con Backspace); así no
|
|
15
15
|
// hace falta ningún texto "N seleccionadas" que traducir.
|
|
16
|
+
// Con `compactTrigger` (opt-in) el trigger NO crece: mantiene el alto fijo
|
|
17
|
+
// del select simple y, en vez de etiquetas, pinta un resumen en una línea
|
|
18
|
+
// (las elegidas separadas por comas, con elipsis); quitar una elegida
|
|
19
|
+
// queda para fuera (el consumidor pinta sus propios chips) o para el
|
|
20
|
+
// propio panel/Backspace. Sin la prop, todo sigue como siempre.
|
|
16
21
|
const props = withDefaults(
|
|
17
22
|
defineProps<{
|
|
18
23
|
modelValue?: (string | number)[]
|
|
@@ -20,12 +25,17 @@ const props = withDefaults(
|
|
|
20
25
|
options: SelectOption[]
|
|
21
26
|
/** Texto en reposo (sin nada marcado), p. ej. "Todas". */
|
|
22
27
|
placeholder?: string
|
|
28
|
+
/**
|
|
29
|
+
* Trigger compacto de alto fijo: resumen en una línea (labels con
|
|
30
|
+
* comas y elipsis) en lugar de etiquetas con aspa dentro del trigger.
|
|
31
|
+
*/
|
|
32
|
+
compactTrigger?: boolean
|
|
23
33
|
error?: string
|
|
24
34
|
hint?: string
|
|
25
35
|
disabled?: boolean
|
|
26
36
|
id?: string
|
|
27
37
|
}>(),
|
|
28
|
-
{ modelValue: () => [], disabled: false },
|
|
38
|
+
{ modelValue: () => [], disabled: false, compactTrigger: false },
|
|
29
39
|
)
|
|
30
40
|
|
|
31
41
|
const emit = defineEmits<{ 'update:modelValue': [value: string[]] }>()
|
|
@@ -44,6 +54,9 @@ const selectedValues = computed(() => new Set(props.modelValue.map((v) => String
|
|
|
44
54
|
const isSelected = (option: SelectOption) => selectedValues.value.has(String(option.value))
|
|
45
55
|
const selectedOptions = computed(() => props.options.filter((o) => isSelected(o)))
|
|
46
56
|
|
|
57
|
+
/** Resumen de una línea del trigger compacto: labels separadas por comas. */
|
|
58
|
+
const summary = computed(() => selectedOptions.value.map((o) => o.label).join(', '))
|
|
59
|
+
|
|
47
60
|
/** Quita una elegida desde su etiqueta del trigger, sin tocar el panel. */
|
|
48
61
|
function removeOption(option: SelectOption) {
|
|
49
62
|
if (props.disabled) return
|
|
@@ -166,7 +179,7 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onOutside))
|
|
|
166
179
|
<div
|
|
167
180
|
ref="root"
|
|
168
181
|
class="form-field__select-wrapper base-select multi-select"
|
|
169
|
-
:class="{ 'is-open': open }"
|
|
182
|
+
:class="{ 'is-open': open, 'multi-select--compact': compactTrigger }"
|
|
170
183
|
>
|
|
171
184
|
<button
|
|
172
185
|
:id="selectId"
|
|
@@ -180,9 +193,13 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onOutside))
|
|
|
180
193
|
@click="toggle"
|
|
181
194
|
@keydown="onKeydown"
|
|
182
195
|
>
|
|
196
|
+
<!-- Trigger compacto (opt-in): resumen en una línea, sin etiquetas -->
|
|
197
|
+
<span v-if="compactTrigger && selectedOptions.length" class="base-select__value">
|
|
198
|
+
{{ summary }}
|
|
199
|
+
</span>
|
|
183
200
|
<!-- Elegidas como etiquetas con aspa (el aspa NO abre el panel);
|
|
184
201
|
span con role=button porque un button no puede anidar otro -->
|
|
185
|
-
<span v-if="selectedOptions.length" class="multi-select__tags">
|
|
202
|
+
<span v-else-if="selectedOptions.length" class="multi-select__tags">
|
|
186
203
|
<span v-for="option in selectedOptions" :key="option.value" class="multi-select__tag">
|
|
187
204
|
{{ option.label }}
|
|
188
205
|
<span
|