Fix frontend 3

This commit is contained in:
Urtzi Alfaro
2025-08-28 23:40:44 +02:00
parent 2bbbf33d7b
commit 221781731c
11 changed files with 872 additions and 69 deletions

View File

@@ -124,7 +124,7 @@ export const AppShell = forwardRef<AppShellRef, AppShellProps>(({
isSidebarCollapsed,
}), [toggleSidebar, collapseSidebar, expandSidebar, isSidebarOpen, isSidebarCollapsed]);
// Handle responsive sidebar state
// Handle responsive sidebar state and prevent body scroll on mobile
React.useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 1024) {
@@ -133,9 +133,26 @@ export const AppShell = forwardRef<AppShellRef, AppShellProps>(({
}
};
// Prevent body scroll when mobile sidebar is open
if (isSidebarOpen && window.innerWidth < 1024) {
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
} else {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return () => {
window.removeEventListener('resize', handleResize);
// Cleanup on unmount
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
};
}, [isSidebarOpen]);
// Error boundary handling
React.useEffect(() => {
@@ -212,8 +229,9 @@ export const AppShell = forwardRef<AppShellRef, AppShellProps>(({
{/* Mobile overlay */}
{isSidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-[var(--z-modal-backdrop)] lg:hidden"
className="fixed inset-0 bg-black/50 z-[var(--z-modal-backdrop)] lg:hidden backdrop-blur-sm"
onClick={handleOverlayClick}
onTouchStart={handleOverlayClick}
aria-hidden="true"
/>
)}

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback, forwardRef } from 'react';
import { clsx } from 'clsx';
import { useNavigate } from 'react-router-dom';
import { useAuthUser, useIsAuthenticated, useAuthActions } from '../../../stores';
import { useTheme } from '../../../contexts/ThemeContext';
import { Button } from '../../ui';
@@ -95,6 +96,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
notificationCount = 0,
onNotificationClick,
}, ref) => {
const navigate = useNavigate();
const user = useAuthUser();
const isAuthenticated = useIsAuthenticated();
const { logout } = useAuthActions();
@@ -221,10 +223,10 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
variant="ghost"
size="sm"
onClick={onMenuClick}
className="lg:hidden w-10 h-10 p-0 flex items-center justify-center"
className="lg:hidden w-10 h-10 p-0 flex items-center justify-center hover:bg-[var(--bg-secondary)] active:scale-95 transition-all duration-150"
aria-label="Abrir menú de navegación"
>
<Menu className="h-5 w-5" />
<Menu className="h-5 w-5 text-[var(--text-primary)]" />
</Button>
{/* Logo */}
@@ -420,7 +422,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
<div className="py-1">
<button
onClick={() => {
// TODO: Navigate to profile
navigate('/app/settings/profile');
setIsUserMenuOpen(false);
}}
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"
@@ -430,7 +432,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
</button>
<button
onClick={() => {
// TODO: Navigate to settings
navigate('/app/settings');
setIsUserMenuOpen(false);
}}
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"

View File

@@ -256,7 +256,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
collapseItem,
}), [scrollToItem, expandItem, collapseItem]);
// Handle keyboard navigation
// Handle keyboard navigation and touch gestures
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen && onClose) {
@@ -264,8 +264,40 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
}
};
let touchStartX = 0;
let touchStartY = 0;
const handleTouchStart = (e: TouchEvent) => {
if (isOpen) {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}
};
const handleTouchMove = (e: TouchEvent) => {
if (!isOpen || !onClose) return;
const touchCurrentX = e.touches[0].clientX;
const touchCurrentY = e.touches[0].clientY;
const deltaX = touchStartX - touchCurrentX;
const deltaY = Math.abs(touchStartY - touchCurrentY);
// Only trigger swipe left to close if it's more horizontal than vertical
// and the swipe distance is significant
if (deltaX > 50 && deltaX > deltaY * 2) {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
document.addEventListener('touchstart', handleTouchStart, { passive: true });
document.addEventListener('touchmove', handleTouchMove, { passive: true });
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('touchstart', handleTouchStart);
document.removeEventListener('touchmove', handleTouchMove);
};
}, [isOpen, onClose]);
// Render navigation item
@@ -443,10 +475,11 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
{/* Mobile Drawer */}
<aside
className={clsx(
'fixed left-0 top-[var(--header-height)] bottom-0 w-[var(--sidebar-width)]',
'fixed inset-y-0 left-0 w-[var(--sidebar-width)] max-w-[85vw]',
'bg-[var(--bg-primary)] border-r border-[var(--border-primary)]',
'transition-transform duration-300 ease-in-out z-[var(--z-fixed)]',
'transition-transform duration-300 ease-in-out z-[var(--z-modal)]',
'lg:hidden flex flex-col',
'shadow-xl',
isOpen ? 'translate-x-0' : '-translate-x-full'
)}
aria-label="Navegación principal"
@@ -470,8 +503,8 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
</div>
{/* Navigation */}
<nav className="flex-1 p-4 overflow-y-auto">
<ul className="space-y-2">
<nav className="flex-1 p-4 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent">
<ul className="space-y-2 pb-4">
{visibleItems.map(item => renderItem(item))}
</ul>
</nav>