Files
cataRio/nuxt4/app/components/cata/SliderIntensidad.vue
josedario87 9ffba43ab4
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
Feat: Implementar paleta de colores por categoría en sliders
- Agregar composable useCategoryColors con 8 colores coordinados (light/dark)
- Colores por categoría: Fragancia (lavanda), Aroma (verde menta), Sabor (rojo),
  Sabor Residual (naranja), Acidez (amarillo), Dulzor (rosa), Sensación en Boca (azul),
  Impresión Global (turquesa)
- Eliminar hints de texto de SliderIntensidad (showDescription = false por defecto)
- Aplicar colores dinámicos a headers y sliders de cada categoría
- Aumentar font-weight de headers a 700 (bold)
- Los colores se adaptan automáticamente al tema light/dark
2025-10-18 03:16:30 -06:00

193 lines
5.0 KiB
Vue

<template>
<div class="slider-intensidad cata-fade-in" :style="customColorStyle">
<!-- Label -->
<label
v-if="label"
:for="inputId"
class="block text-sm font-medium mb-2 cata-text"
>
{{ label }}
<span v-if="required" class="text-error">*</span>
</label>
<!-- Slider Container -->
<div class="relative">
<USlider
:id="inputId"
:model-value="modelValue ?? undefined"
:min="min"
:max="max"
:step="step"
:disabled="disabled"
:tooltip="tooltipConfig"
:ui="sliderUi"
@update:model-value="handleChange"
/>
<!-- Indicadores de rango -->
<div class="flex justify-between mt-2 text-xs cata-text opacity-60">
<span>{{ min }}</span>
<span v-if="modelValue !== null" class="font-semibold opacity-100">
{{ modelValue }}
</span>
<span>{{ max }}</span>
</div>
</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">
import type { TooltipProps } from '@nuxt/ui'
interface SliderIntensidadProps {
/** Tipo de intensidad: descriptiva (1-10) o afectiva (1-15) */
tipo: 'descriptiva' | 'afectiva'
/** Valor actual del slider */
modelValue: number | null
/** Etiqueta del slider */
label?: string
/** ID para el input (auto-generado si no se provee) */
id?: string
/** Deshabilitar el slider */
disabled?: boolean
/** Marcar como requerido */
required?: boolean
/** Mostrar descripción del tipo */
showDescription?: boolean
/** Color personalizado para el slider */
color?: string
}
const props = withDefaults(defineProps<SliderIntensidadProps>(), {
disabled: false,
required: false,
showDescription: false,
})
const emit = defineEmits<{
'update:modelValue': [value: number | null]
}>()
// ID único para el input
const inputId = computed(() => props.id || `slider-${Math.random().toString(36).substring(2, 9)}`)
// Configuración según el tipo
const min = computed(() => 1)
const max = computed(() => props.tipo === 'descriptiva' ? 10 : 15)
const step = computed(() => 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'
})
// Configuración del tooltip
const tooltipConfig = computed<boolean | TooltipProps>(() => ({
disableClosingTrigger: true,
text: props.modelValue !== null ? String(props.modelValue) : '',
}))
// Configuración UI personalizada del slider
const sliderUi = computed(() => ({
root: 'cata-slider-root',
track: `cata-slider-track ${props.color ? 'cata-slider-track-custom' : ''}`,
range: props.tipo === 'descriptiva'
? 'bg-primary/20 dark:bg-primary/30'
: 'bg-primary/40 dark:bg-primary/50',
thumb: `cata-slider-thumb ${props.color ? 'cata-slider-thumb-custom' : ''}`,
}))
// Manejar cambio de valor
const handleChange = (value: number | number[] | undefined) => {
if (value === undefined || value === null) {
emit('update:modelValue', null)
return
}
const newValue = Array.isArray(value) ? (value[0] ?? null) : value
emit('update:modelValue', newValue)
}
// Estilo dinámico para el color personalizado
const customColorStyle = computed(() => {
if (!props.color) return {}
return {
'--slider-custom-color': props.color,
}
})
</script>
<style scoped>
.slider-intensidad {
width: 100%;
}
/* Personalización adicional para el slider */
:deep(.cata-slider-root) {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
:deep(.cata-slider-track) {
height: 0.5rem;
border-radius: 9999px;
background: transparent;
border: var(--cata-border-width) solid;
border-color: color-mix(in srgb, var(--cata-primary) 50%, transparent);
}
:deep(.cata-slider-track-custom) {
border-color: color-mix(in srgb, var(--slider-custom-color) 50%, transparent);
}
:deep(.cata-slider-thumb) {
width: 1.25rem;
height: 1.25rem;
border-radius: 9999px;
background-color: var(--cata-bg);
border: 2px solid var(--cata-primary);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
:deep(.cata-slider-thumb-custom) {
border-color: var(--slider-custom-color);
}
:deep(.cata-slider-thumb:hover) {
transform: scale(1.1);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
}
:deep(.cata-slider-thumb:active) {
transform: scale(1.05);
}
.dark :deep(.cata-slider-thumb) {
box-shadow: 0 0 6px var(--cata-primary);
}
.dark :deep(.cata-slider-thumb-custom) {
box-shadow: 0 0 6px var(--slider-custom-color);
}
.dark :deep(.cata-slider-thumb:hover) {
box-shadow: 0 0 12px var(--cata-primary);
}
.dark :deep(.cata-slider-thumb-custom:hover) {
box-shadow: 0 0 12px var(--slider-custom-color);
}
/* Animación de fade in */
.slider-intensidad.cata-fade-in {
animation: cata-fade-in 0.3s ease-out;
}
</style>