All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m7s
Limpia la UI del selector de intensidad para una experiencia más minimalista: - Eliminado atributo title que mostraba tooltips con el valor - Eliminada sección completa de indicadores numéricos (min/valor/max) - El componente ahora solo muestra los iconos interactivos - Mantiene toda la funcionalidad (selección, hover, deselección) La interfaz es ahora más limpia y el usuario puede confiar en el feedback visual de los iconos activos para conocer el valor seleccionado.
258 lines
6.7 KiB
Vue
258 lines
6.7 KiB
Vue
<template>
|
|
<div class="selector-intensidad cata-fade-in" :style="customColorStyle">
|
|
<!-- Contenedor de iconos -->
|
|
<div class="iconos-container">
|
|
<button
|
|
v-for="valor in iconos"
|
|
:key="valor"
|
|
type="button"
|
|
class="icono-item"
|
|
:class="{
|
|
'icono-activo': modelValue !== null && valor <= modelValue,
|
|
'icono-hover': valorHover !== null && valor <= valorHover,
|
|
'icono-descriptiva': tipo === 'descriptiva',
|
|
'icono-afectiva': tipo === 'afectiva',
|
|
}"
|
|
:disabled="disabled"
|
|
@click="handleClick(valor)"
|
|
@mouseenter="valorHover = valor"
|
|
@mouseleave="valorHover = null"
|
|
>
|
|
<!-- Icono para descriptiva: círculo -->
|
|
<UIcon
|
|
v-if="tipo === 'descriptiva'"
|
|
:name="(modelValue !== null && valor <= modelValue) || (valorHover !== null && valor <= valorHover)
|
|
? 'i-lucide-circle-dot'
|
|
: 'i-lucide-circle'"
|
|
class="icono"
|
|
/>
|
|
|
|
<!-- Icono para afectiva: corazón -->
|
|
<UIcon
|
|
v-else
|
|
name="i-lucide-heart"
|
|
class="icono"
|
|
:class="{
|
|
'icono-filled': (modelValue !== null && valor <= modelValue) || (valorHover !== null && valor <= valorHover)
|
|
}"
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Descripción del tipo -->
|
|
<p v-if="showDescription" class="text-xs mt-1 cata-text opacity-75">
|
|
{{ tipoDescription }}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface SelectorIntensidadProps {
|
|
/** Tipo de intensidad: descriptiva (1-10) o afectiva (1-15) */
|
|
tipo: 'descriptiva' | 'afectiva'
|
|
/** Valor actual del selector */
|
|
modelValue: number | null
|
|
/** Etiqueta del selector */
|
|
label?: string
|
|
/** ID para el input (auto-generado si no se provee) */
|
|
id?: string
|
|
/** Deshabilitar el selector */
|
|
disabled?: boolean
|
|
/** Marcar como requerido */
|
|
required?: boolean
|
|
/** Mostrar descripción del tipo */
|
|
showDescription?: boolean
|
|
/** Color personalizado para el selector */
|
|
color?: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<SelectorIntensidadProps>(), {
|
|
disabled: false,
|
|
required: false,
|
|
showDescription: false,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: number | null]
|
|
}>()
|
|
|
|
// Estado para el hover
|
|
const valorHover = ref<number | null>(null)
|
|
|
|
// Configuración según el tipo
|
|
const min = computed(() => 1)
|
|
const max = computed(() => props.tipo === 'descriptiva' ? 10 : 15)
|
|
|
|
// Array de valores para los iconos
|
|
const iconos = computed(() => {
|
|
return Array.from({ length: max.value }, (_, i) => i + 1)
|
|
})
|
|
|
|
// Descripción del tipo de intensidad
|
|
const tipoDescription = computed(() => {
|
|
return props.tipo === 'descriptiva'
|
|
? 'Intensidad descriptiva: qué tan intensa es la característica (sin importar si es buena o mala)'
|
|
: 'Intensidad afectiva: qué tan buena o mala consideras esta característica'
|
|
})
|
|
|
|
// Manejar click en un icono
|
|
const handleClick = (valor: number) => {
|
|
if (props.disabled) return
|
|
|
|
// Si clickean el mismo valor, lo deseleccionan (vuelve a null)
|
|
if (props.modelValue === valor) {
|
|
emit('update:modelValue', null)
|
|
} else {
|
|
emit('update:modelValue', valor)
|
|
}
|
|
}
|
|
|
|
// Estilo dinámico para el color personalizado
|
|
const customColorStyle = computed(() => {
|
|
if (!props.color) return {}
|
|
return {
|
|
'--selector-custom-color': props.color,
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.selector-intensidad {
|
|
width: 100%;
|
|
}
|
|
|
|
.iconos-container {
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
padding: 0.5rem 0;
|
|
}
|
|
|
|
.icono-item {
|
|
background: transparent;
|
|
border: none;
|
|
padding: 0.25rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
border-radius: 0.25rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.icono-item:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.icono-item:not(:disabled):hover {
|
|
transform: scale(1.15);
|
|
}
|
|
|
|
.icono-item:not(:disabled):active {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
/* Iconos */
|
|
.icono {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
/* Iconos descriptiva (círculos) */
|
|
.icono-descriptiva .icono {
|
|
color: color-mix(in srgb, var(--cata-primary) 40%, transparent);
|
|
}
|
|
|
|
.icono-descriptiva.icono-activo .icono,
|
|
.icono-descriptiva.icono-hover .icono {
|
|
color: var(--cata-primary);
|
|
}
|
|
|
|
/* Iconos afectiva (corazones) */
|
|
.icono-afectiva .icono {
|
|
color: color-mix(in srgb, var(--cata-primary) 40%, transparent);
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.icono-afectiva.icono-activo .icono,
|
|
.icono-afectiva.icono-hover .icono {
|
|
color: var(--cata-primary);
|
|
}
|
|
|
|
/* Fill para corazones activos */
|
|
.icono-filled {
|
|
fill: currentColor;
|
|
}
|
|
|
|
/* Color personalizado */
|
|
.selector-intensidad[style*="--selector-custom-color"] .icono-descriptiva .icono {
|
|
color: color-mix(in srgb, var(--selector-custom-color) 40%, transparent);
|
|
}
|
|
|
|
.selector-intensidad[style*="--selector-custom-color"] .icono-descriptiva.icono-activo .icono,
|
|
.selector-intensidad[style*="--selector-custom-color"] .icono-descriptiva.icono-hover .icono {
|
|
color: var(--selector-custom-color);
|
|
}
|
|
|
|
.selector-intensidad[style*="--selector-custom-color"] .icono-afectiva .icono {
|
|
color: color-mix(in srgb, var(--selector-custom-color) 40%, transparent);
|
|
}
|
|
|
|
.selector-intensidad[style*="--selector-custom-color"] .icono-afectiva.icono-activo .icono,
|
|
.selector-intensidad[style*="--selector-custom-color"] .icono-afectiva.icono-hover .icono {
|
|
color: var(--selector-custom-color);
|
|
}
|
|
|
|
/* Modo oscuro */
|
|
.dark .icono-descriptiva .icono {
|
|
color: color-mix(in srgb, var(--cata-primary) 30%, transparent);
|
|
}
|
|
|
|
.dark .icono-descriptiva.icono-activo .icono,
|
|
.dark .icono-descriptiva.icono-hover .icono {
|
|
color: var(--cata-primary);
|
|
filter: drop-shadow(0 0 4px var(--cata-primary));
|
|
}
|
|
|
|
.dark .icono-afectiva .icono {
|
|
color: color-mix(in srgb, var(--cata-primary) 30%, transparent);
|
|
}
|
|
|
|
.dark .icono-afectiva.icono-activo .icono,
|
|
.dark .icono-afectiva.icono-hover .icono {
|
|
color: var(--cata-primary);
|
|
filter: drop-shadow(0 0 4px var(--cata-primary));
|
|
}
|
|
|
|
/* Color personalizado en modo oscuro */
|
|
.dark .selector-intensidad[style*="--selector-custom-color"] .icono-descriptiva.icono-activo .icono,
|
|
.dark .selector-intensidad[style*="--selector-custom-color"] .icono-descriptiva.icono-hover .icono {
|
|
filter: drop-shadow(0 0 4px var(--selector-custom-color));
|
|
}
|
|
|
|
.dark .selector-intensidad[style*="--selector-custom-color"] .icono-afectiva.icono-activo .icono,
|
|
.dark .selector-intensidad[style*="--selector-custom-color"] .icono-afectiva.icono-hover .icono {
|
|
filter: drop-shadow(0 0 4px var(--selector-custom-color));
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 640px) {
|
|
.icono {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
}
|
|
|
|
.iconos-container {
|
|
gap: 0.15rem;
|
|
}
|
|
}
|
|
|
|
/* Animación de fade in */
|
|
.selector-intensidad.cata-fade-in {
|
|
animation: cata-fade-in 0.3s ease-out;
|
|
}
|
|
</style>
|