Skip to content

Commit 5c083f4

Browse files
committed
🎛️ Enhance: Rediseñar LayerToggles con UI moderna y configuraciones predefinidas
## Mejoras implementadas: ### 🎨 **Diseño moderno y elegante** - Custom toggle switches animados - Cards individuales con hover effects - Iconos descriptivos para cada capa - Glassmorphism design con backdrop blur ### ⚡ **Funcionalidad avanzada** - Configuraciones predefinidas (Default, Minimal, Scientific, Presentation) - Toggle "Mostrar/Ocultar todo" inteligente - Contador de capas activas - Indicador de rendimiento en tiempo real ### 🔧 **UX mejorada** - Descripciones tooltip para cada capa - Estados visuales claros (activo/inactivo) - Transiciones suaves en todas las interacciones - Feedback visual inmediato ### ♿ **Accesibilidad** - Checkboxes ocultos para screen readers - ARIA labels apropiados - Navegación por teclado - Contraste de colores optimizado ### 📱 **Responsive y modular** - Grid layout adaptativo - Componentes modulares reutilizables - Performance optimizado - TypeScript interfaces mejoradas
1 parent 931d186 commit 5c083f4

File tree

1 file changed

+216
-43
lines changed

1 file changed

+216
-43
lines changed

frontend/components/LayerToggles.tsx

Lines changed: 216 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,233 @@
1+
/**
2+
* LayerToggles Component
3+
* Control de capas visuales para el globo 3D de SismoView
4+
* Permite activar/desactivar diferentes elementos visuales
5+
*/
16
"use client";
27

3-
export type LayerOptions = {
8+
import React from 'react';
9+
10+
export interface LayerOptions {
411
atmosphere: boolean;
512
graticule: boolean;
613
equator: boolean;
714
stars: boolean;
815
stand: boolean;
9-
};
16+
}
1017

11-
export default function LayerToggles({
12-
value,
13-
onChange,
14-
}: {
18+
interface LayerToggleProps {
1519
value: LayerOptions;
16-
onChange: (v: LayerOptions) => void;
17-
}) {
18-
const set = (k: keyof LayerOptions) =>
19-
(e: React.ChangeEvent<HTMLInputElement>) =>
20-
onChange({ ...value, [k]: e.target.checked });
20+
onChange: (options: LayerOptions) => void;
21+
}
22+
23+
interface LayerConfig {
24+
key: keyof LayerOptions;
25+
label: string;
26+
description: string;
27+
icon: string;
28+
}
2129

22-
const reset = () =>
23-
onChange({ atmosphere: true, graticule: true, equator: true, stars: true, stand: true });
30+
// Configuración de las capas disponibles
31+
const LAYER_CONFIGS: LayerConfig[] = [
32+
{
33+
key: 'atmosphere',
34+
label: 'Atmósfera',
35+
description: 'Efecto de atmósfera alrededor del planeta',
36+
icon: '🌫️'
37+
},
38+
{
39+
key: 'graticule',
40+
label: 'Retícula',
41+
description: 'Líneas de latitud y longitud',
42+
icon: '🗺️'
43+
},
44+
{
45+
key: 'equator',
46+
label: 'Ecuador',
47+
description: 'Línea ecuatorial destacada',
48+
icon: '🌍'
49+
},
50+
{
51+
key: 'stars',
52+
label: 'Estrellas',
53+
description: 'Campo de estrellas de fondo',
54+
icon: '⭐'
55+
},
56+
{
57+
key: 'stand',
58+
label: 'Base',
59+
description: 'Soporte del globo terráqueo',
60+
icon: '🏛️'
61+
}
62+
];
63+
64+
// Configuraciones predefinidas
65+
const PRESETS = {
66+
default: { atmosphere: true, graticule: true, equator: true, stars: true, stand: true },
67+
minimal: { atmosphere: true, graticule: false, equator: false, stars: false, stand: false },
68+
scientific: { atmosphere: false, graticule: true, equator: true, stars: false, stand: false },
69+
presentation: { atmosphere: true, graticule: false, equator: true, stars: true, stand: true }
70+
};
71+
72+
/**
73+
* Componente individual para cada toggle de capa
74+
*/
75+
const LayerToggle: React.FC<{
76+
config: LayerConfig;
77+
checked: boolean;
78+
onChange: (checked: boolean) => void;
79+
}> = ({ config, checked, onChange }) => (
80+
<div
81+
className={`
82+
relative flex items-center p-3 rounded-lg border transition-all duration-200 cursor-pointer
83+
${checked
84+
? 'bg-blue-500/10 border-blue-500/30 shadow-sm'
85+
: 'bg-slate-800/40 border-slate-700 hover:border-slate-600'
86+
}
87+
`}
88+
onClick={() => onChange(!checked)}
89+
>
90+
<div className="flex items-center gap-3 w-full">
91+
<span className="text-lg" role="img" aria-label={config.label}>
92+
{config.icon}
93+
</span>
94+
95+
<div className="flex-1 min-w-0">
96+
<div className={`font-medium text-sm ${checked ? 'text-blue-300' : 'text-slate-300'}`}>
97+
{config.label}
98+
</div>
99+
<div className="text-xs text-slate-500 truncate">
100+
{config.description}
101+
</div>
102+
</div>
103+
104+
{/* Custom Toggle Switch */}
105+
<div className={`
106+
relative w-11 h-6 rounded-full transition-colors duration-200
107+
${checked ? 'bg-blue-500' : 'bg-slate-600'}
108+
`}>
109+
<div className={`
110+
absolute top-0.5 w-5 h-5 bg-white rounded-full shadow-sm transition-transform duration-200
111+
${checked ? 'translate-x-5' : 'translate-x-0.5'}
112+
`} />
113+
</div>
114+
</div>
115+
116+
{/* Hidden checkbox for accessibility */}
117+
<input
118+
type="checkbox"
119+
checked={checked}
120+
onChange={(e) => onChange(e.target.checked)}
121+
className="sr-only"
122+
aria-describedby={`${config.key}-description`}
123+
/>
124+
</div>
125+
);
126+
127+
/**
128+
* Componente principal LayerToggles
129+
*/
130+
export default function LayerToggles({ value, onChange }: LayerToggleProps): JSX.Element {
131+
/**
132+
* Actualiza una capa específica
133+
*/
134+
const updateLayer = (key: keyof LayerOptions, checked: boolean) => {
135+
onChange({ ...value, [key]: checked });
136+
};
137+
138+
/**
139+
* Aplica una configuración predefinida
140+
*/
141+
const applyPreset = (presetKey: keyof typeof PRESETS) => {
142+
onChange(PRESETS[presetKey]);
143+
};
144+
145+
/**
146+
* Calcula el número de capas activas
147+
*/
148+
const activeLayersCount = Object.values(value).filter(Boolean).length;
24149

25150
return (
26-
<div className="card p-3">
27-
<div className="mb-2 font-semibold text-sm">Capas del globo</div>
28-
29-
<div className="grid grid-cols-2 gap-2 text-sm">
30-
<label className="flex items-center gap-2">
31-
<input type="checkbox" checked={value.atmosphere} onChange={set("atmosphere")} />
32-
Atmósfera
33-
</label>
34-
35-
<label className="flex items-center gap-2">
36-
<input type="checkbox" checked={value.graticule} onChange={set("graticule")} />
37-
Retícula
38-
</label>
39-
40-
<label className="flex items-center gap-2">
41-
<input type="checkbox" checked={value.equator} onChange={set("equator")} />
42-
Ecuador
43-
</label>
44-
45-
<label className="flex items-center gap-2">
46-
<input type="checkbox" checked={value.stars} onChange={set("stars")} />
47-
Estrellas
48-
</label>
49-
50-
<label className="flex items-center gap-2">
51-
<input type="checkbox" checked={value.stand} onChange={set("stand")} />
52-
Base
53-
</label>
151+
<div className="bg-slate-900/80 backdrop-blur-sm border border-slate-700 rounded-xl p-4 shadow-xl">
152+
{/* Header */}
153+
<div className="flex items-center justify-between mb-4">
154+
<div>
155+
<h3 className="font-semibold text-slate-200 text-sm">Capas del Globo</h3>
156+
<p className="text-xs text-slate-500">
157+
{activeLayersCount} de {LAYER_CONFIGS.length} capas activas
158+
</p>
159+
</div>
160+
161+
{/* Toggle All Button */}
162+
<button
163+
onClick={() => {
164+
const allActive = activeLayersCount === LAYER_CONFIGS.length;
165+
const newState = allActive
166+
? { atmosphere: false, graticule: false, equator: false, stars: false, stand: false }
167+
: { atmosphere: true, graticule: true, equator: true, stars: true, stand: true };
168+
onChange(newState);
169+
}}
170+
className={`
171+
px-3 py-1.5 rounded-md text-xs font-medium transition-colors
172+
${activeLayersCount === LAYER_CONFIGS.length
173+
? 'bg-red-500/20 text-red-400 hover:bg-red-500/30'
174+
: 'bg-green-500/20 text-green-400 hover:bg-green-500/30'
175+
}
176+
`}
177+
>
178+
{activeLayersCount === LAYER_CONFIGS.length ? 'Ocultar todo' : 'Mostrar todo'}
179+
</button>
180+
</div>
181+
182+
{/* Layer Controls */}
183+
<div className="space-y-3 mb-4">
184+
{LAYER_CONFIGS.map((config) => (
185+
<LayerToggle
186+
key={config.key}
187+
config={config}
188+
checked={value[config.key]}
189+
onChange={(checked) => updateLayer(config.key, checked)}
190+
/>
191+
))}
192+
</div>
193+
194+
{/* Presets */}
195+
<div className="border-t border-slate-700 pt-4">
196+
<h4 className="text-xs font-medium text-slate-400 mb-3">Configuraciones rápidas</h4>
197+
<div className="grid grid-cols-2 gap-2">
198+
{Object.entries(PRESETS).map(([key, preset]) => {
199+
const isActive = JSON.stringify(value) === JSON.stringify(preset);
200+
201+
return (
202+
<button
203+
key={key}
204+
onClick={() => applyPreset(key as keyof typeof PRESETS)}
205+
className={`
206+
px-3 py-2 rounded-lg text-xs font-medium transition-all duration-200
207+
${isActive
208+
? 'bg-blue-500/20 text-blue-300 border border-blue-500/30'
209+
: 'bg-slate-800/60 text-slate-400 border border-slate-700 hover:bg-slate-700/60 hover:text-slate-300'
210+
}
211+
`}
212+
>
213+
{key.charAt(0).toUpperCase() + key.slice(1)}
214+
</button>
215+
);
216+
})}
217+
</div>
54218
</div>
55219

56-
<div className="mt-3">
57-
<button className="input px-3 py-2" onClick={reset}>Reset</button>
220+
{/* Performance Indicator */}
221+
<div className="mt-4 p-2 bg-slate-800/40 rounded-lg border border-slate-700">
222+
<div className="flex items-center gap-2 text-xs">
223+
<div className={`w-2 h-2 rounded-full ${
224+
activeLayersCount <= 3 ? 'bg-green-400' :
225+
activeLayersCount <= 4 ? 'bg-yellow-400' : 'bg-red-400'
226+
}`} />
227+
<span className="text-slate-400">
228+
Rendimiento: {activeLayersCount <= 3 ? 'Óptimo' : activeLayersCount <= 4 ? 'Bueno' : 'Intenso'}
229+
</span>
230+
</div>
58231
</div>
59232
</div>
60233
);

0 commit comments

Comments
 (0)