diff --git a/.Jules/palette.md b/.Jules/palette.md index ee2d8f0..7cb7097 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -39,6 +39,10 @@ ## 2025-05-27 - [Unified Loading States and Derived ARIA Labels] **Learning:** Centralizing loading logic within the `CustomButton` component (using `isPending` and `isPendingText` props) ensures consistent visual feedback and simplifies form components. Furthermore, automatically deriving `aria-label` from `tooltip` for icon-only buttons provides a robust accessibility fallback without requiring repetitive developer effort. **Action:** Prefer integrated loading states in core button components and use tooltips to automatically populate ARIA labels for icon-only interactive elements. + +## 2025-06-17 - [Dynamic Theme Feedback and Functional Branding] +**Learning:** Converting static branding elements into functional navigation links (e.g., logo to dashboard) fulfills standard user expectations. Furthermore, synchronizing the theme toggle's icon and ARIA labels with the active theme via React state, combined with subtle CSS transitions (scale and rotation), creates a more accessible and "delightful" interaction that feels responsive to the user's current environment. +**Action:** Always ensure branding logos are functional links and use dynamic state for theme-related UI elements to provide clear, accessible feedback. ## 2025-02-03 - [Unified Loading Feedback in Buttons and Components] **Learning:** Moving loading logic (spinners, text, disabled state) into base components like `CustomButton` reduces boilerplate and ensures consistent UX across the app. In Spanish interfaces, using semantic icons like `check_circle` within a `valign-wrapper` in toasts provides clear, professional feedback for critical actions like saving. **Action:** Abstract loading states into reusable UI components and always include visual markers (icons) for status updates. diff --git a/src/v2/components/V2Navi.jsx b/src/v2/components/V2Navi.jsx index b60f372..438e470 100644 --- a/src/v2/components/V2Navi.jsx +++ b/src/v2/components/V2Navi.jsx @@ -1,7 +1,9 @@ -import { NavLink } from 'react-router-dom'; +import { useState } from 'react'; +import { NavLink, Link } from 'react-router-dom'; import '../styles/v2-theme.css'; const V2Navi = () => { + const [theme, setTheme] = useState(document.documentElement.getAttribute('theme') || 'light'); const navItems = [ { label: "Inicio", icon: "home", path: "/v2/dashboard" }, { label: "Práctica", icon: "medical_services", path: "/v2/practica" }, @@ -20,11 +22,23 @@ const V2Navi = () => { { label: "Perfil", icon: "person", path: "/v2/perfil" }, ]; + const toggleTheme = () => { + const newTheme = theme === 'dark' ? 'light' : 'dark'; + document.documentElement.setAttribute('theme', newTheme); + localStorage.setItem('theme', newTheme); + setTheme(newTheme); + }; + return ( diff --git a/src/v2/components/V2Navi.test.jsx b/src/v2/components/V2Navi.test.jsx index 2d58a5f..57fa7d3 100644 --- a/src/v2/components/V2Navi.test.jsx +++ b/src/v2/components/V2Navi.test.jsx @@ -46,14 +46,29 @@ describe('V2Navi', () => { ); - const themeButton = screen.getByLabelText('Cambiar tema'); + const themeButton = screen.getByLabelText('Cambiar a modo oscuro'); fireEvent.click(themeButton); expect(setAttributeSpy).toHaveBeenCalledWith('theme', 'dark'); expect(setItemSpy).toHaveBeenCalledWith('theme', 'dark'); + // Icon and label should update + expect(screen.getByLabelText('Cambiar a modo claro')).toBeDefined(); + expect(screen.getByText('light_mode')).toBeDefined(); + setAttributeSpy.mockRestore(); getItemSpy.mockRestore(); setItemSpy.mockRestore(); }); + + it('renders logo as a link to dashboard', () => { + render( + + + + ); + + const logoLink = screen.getByLabelText('Ir al inicio'); + expect(logoLink.getAttribute('href')).toBe('/v2/dashboard'); + }); }); diff --git a/src/v2/styles/v2-theme.css b/src/v2/styles/v2-theme.css index 3669c70..24e6917 100644 --- a/src/v2/styles/v2-theme.css +++ b/src/v2/styles/v2-theme.css @@ -267,6 +267,23 @@ background: none; cursor: pointer; padding: 8px; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.v2-theme-toggle:hover { + transform: scale(1.1); +} + +.v2-theme-toggle:active { + transform: scale(0.95); +} + +.v2-theme-toggle i { + transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.v2-theme-toggle:active i { + transform: rotate(45deg); } .v2-content-wrapper {