Feat: Implementar configurador de tema personalizado
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 47s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 47s
- Crear formulario completo de configuración de colores - Agregar 8 campos editables: bg, surface, border, primary, primaryStrong, accent, text, textMuted - Implementar color pickers + inputs de texto hexadecimal - Agregar vista previa en tiempo real (toggle opcional) - Guardar configuración en localStorage - Aplicar cambios dinámicamente con CSS variables - Incluir botón para restaurar colores por defecto - Colores default: tema café actual de la aplicación
This commit is contained in:
@@ -1,8 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Configuración'
|
||||
})
|
||||
|
||||
// Colores por defecto (actuales del tema café)
|
||||
const defaultTheme = {
|
||||
bg: '#14100b',
|
||||
surface: '#1f180f',
|
||||
border: '#3a2a16',
|
||||
primary: '#e0c080',
|
||||
primaryStrong: '#c08040',
|
||||
accent: '#ffe0a0',
|
||||
text: '#fef9f0',
|
||||
textMuted: '#d8c7a6'
|
||||
}
|
||||
|
||||
// Estado del tema (inicializar con valores actuales del CSS)
|
||||
const theme = ref({ ...defaultTheme })
|
||||
|
||||
// Cargar tema guardado en localStorage
|
||||
onMounted(() => {
|
||||
const savedTheme = localStorage.getItem('custom-theme')
|
||||
if (savedTheme) {
|
||||
try {
|
||||
theme.value = JSON.parse(savedTheme)
|
||||
applyTheme(theme.value)
|
||||
} catch (e) {
|
||||
console.error('Error al cargar tema guardado', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Aplicar tema al documento
|
||||
const applyTheme = (themeColors: typeof defaultTheme) => {
|
||||
const root = document.documentElement
|
||||
root.style.setProperty('--brand-bg', themeColors.bg)
|
||||
root.style.setProperty('--brand-surface', themeColors.surface)
|
||||
root.style.setProperty('--brand-border', themeColors.border)
|
||||
root.style.setProperty('--brand-primary', themeColors.primary)
|
||||
root.style.setProperty('--brand-primary-strong', themeColors.primaryStrong)
|
||||
root.style.setProperty('--brand-accent', themeColors.accent)
|
||||
root.style.setProperty('--brand-text', themeColors.text)
|
||||
root.style.setProperty('--brand-text-muted', themeColors.textMuted)
|
||||
}
|
||||
|
||||
// Guardar tema
|
||||
const saveTheme = () => {
|
||||
localStorage.setItem('custom-theme', JSON.stringify(theme.value))
|
||||
applyTheme(theme.value)
|
||||
useToast().add({
|
||||
title: 'Tema guardado',
|
||||
description: 'Los cambios se aplicaron correctamente',
|
||||
color: 'green'
|
||||
})
|
||||
}
|
||||
|
||||
// Resetear al tema por defecto
|
||||
const resetTheme = () => {
|
||||
theme.value = { ...defaultTheme }
|
||||
localStorage.removeItem('custom-theme')
|
||||
applyTheme(theme.value)
|
||||
useToast().add({
|
||||
title: 'Tema reseteado',
|
||||
description: 'Se restauraron los colores por defecto',
|
||||
color: 'blue'
|
||||
})
|
||||
}
|
||||
|
||||
// Vista previa en tiempo real (opcional)
|
||||
const livePreview = ref(false)
|
||||
watch(() => theme.value, (newTheme) => {
|
||||
if (livePreview.value) {
|
||||
applyTheme(newTheme)
|
||||
}
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -15,70 +89,251 @@ definePageMeta({
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<div class="max-w-4xl mx-auto space-y-8">
|
||||
<!-- Coming Soon Banner -->
|
||||
<!-- Configuración de Tema -->
|
||||
<UCard>
|
||||
<div class="text-center py-12 space-y-6">
|
||||
<div class="flex justify-center">
|
||||
<div class="w-24 h-24 rounded-full bg-gray-50 dark:bg-gray-800/50 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-settings" class="size-12 text-gray-600 dark:text-gray-400" />
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-[var(--brand-primary)]/20 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-palette" class="size-5 text-[var(--brand-primary)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[var(--brand-text)]">
|
||||
Tema y Apariencia
|
||||
</h3>
|
||||
<p class="text-sm text-[var(--brand-text-muted)]">
|
||||
Personaliza los colores de la interfaz
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<UToggle v-model="livePreview" label="Vista previa en vivo" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Fondo Principal -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Fondo Principal
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.bg"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.bg"
|
||||
placeholder="#14100b"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Fondo Secundario (Surface)
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.surface"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.surface"
|
||||
placeholder="#1f180f"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||
Página en construcción
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400 mb-4">
|
||||
Estamos trabajando en esta funcionalidad
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-500 max-w-md mx-auto">
|
||||
Pronto podrás configurar tus preferencias de visualización, notificaciones, privacidad y más opciones del sistema.
|
||||
</p>
|
||||
<!-- Bordes y Primario -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Color de Bordes
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.border"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.border"
|
||||
placeholder="#3a2a16"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Color Primario
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.primary"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.primary"
|
||||
placeholder="#e0c080"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center gap-3">
|
||||
<!-- Primario Fuerte y Acento -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Color Primario Fuerte
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.primaryStrong"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.primaryStrong"
|
||||
placeholder="#c08040"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Color de Acento
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.accent"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.accent"
|
||||
placeholder="#ffe0a0"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Texto y Texto Muted -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Color de Texto
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.text"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.text"
|
||||
placeholder="#fef9f0"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
|
||||
Color de Texto Secundario
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="theme.textMuted"
|
||||
type="color"
|
||||
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
|
||||
/>
|
||||
<UInput
|
||||
v-model="theme.textMuted"
|
||||
placeholder="#d8c7a6"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Acciones -->
|
||||
<div class="flex justify-between items-center pt-4 border-t border-[var(--brand-border)]">
|
||||
<UButton
|
||||
to="/"
|
||||
color="primary"
|
||||
icon="i-lucide-home"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-rotate-ccw"
|
||||
@click="resetTheme"
|
||||
>
|
||||
Volver al inicio
|
||||
Restaurar por defecto
|
||||
</UButton>
|
||||
<UButton
|
||||
color="primary"
|
||||
icon="i-lucide-save"
|
||||
@click="saveTheme"
|
||||
>
|
||||
Guardar cambios
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Settings Preview -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-50 dark:bg-blue-950/30 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-palette" class="size-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">
|
||||
Apariencia
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Tema, colores y personalización visual
|
||||
</p>
|
||||
</UCard>
|
||||
<!-- Vista previa de colores -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-3">
|
||||
<UIcon name="i-lucide-eye" class="size-5 text-[var(--brand-primary)]" />
|
||||
<h3 class="font-semibold text-[var(--brand-text)]">
|
||||
Vista Previa
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="space-y-2">
|
||||
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.bg }" />
|
||||
<p class="text-xs text-center text-[var(--brand-text-muted)]">Fondo Principal</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.surface }" />
|
||||
<p class="text-xs text-center text-[var(--brand-text-muted)]">Surface</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.primary }" />
|
||||
<p class="text-xs text-center text-[var(--brand-text-muted)]">Primario</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.accent }" />
|
||||
<p class="text-xs text-center text-[var(--brand-text-muted)]">Acento</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Otras configuraciones (placeholder) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-amber-50 dark:bg-amber-950/30 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-bell" class="size-5 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">
|
||||
<h3 class="font-semibold text-[var(--brand-text)]">
|
||||
Notificaciones
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Gestión de alertas y comunicaciones
|
||||
<p class="text-sm text-[var(--brand-text-muted)]">
|
||||
Gestión de alertas y comunicaciones (Próximamente)
|
||||
</p>
|
||||
</UCard>
|
||||
|
||||
@@ -88,29 +343,13 @@ definePageMeta({
|
||||
<div class="w-10 h-10 rounded-lg bg-green-50 dark:bg-green-950/30 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-shield" class="size-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">
|
||||
<h3 class="font-semibold text-[var(--brand-text)]">
|
||||
Privacidad
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Control de datos y seguridad
|
||||
</p>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-50 dark:bg-purple-950/30 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-globe" class="size-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">
|
||||
Idioma y región
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Preferencias de localización
|
||||
<p class="text-sm text-[var(--brand-text-muted)]">
|
||||
Control de datos y seguridad (Próximamente)
|
||||
</p>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user