Fix frontend 3
This commit is contained in:
@@ -4,12 +4,6 @@ export { default as RegisterForm } from './RegisterForm';
|
||||
export { default as PasswordResetForm } from './PasswordResetForm';
|
||||
export { default as ProfileSettings } from './ProfileSettings';
|
||||
|
||||
// Export named exports as well
|
||||
export { LoginForm } from './LoginForm';
|
||||
export { RegisterForm } from './RegisterForm';
|
||||
export { PasswordResetForm } from './PasswordResetForm';
|
||||
export { ProfileSettings } from './ProfileSettings';
|
||||
|
||||
// Re-export types for convenience
|
||||
export type {
|
||||
LoginFormProps,
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -410,14 +410,14 @@ const DataTable = forwardRef<HTMLDivElement, DataTableProps>(({
|
||||
>
|
||||
{/* Loading overlay */}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 bg-white/80 backdrop-blur-sm z-10 flex items-center justify-center">
|
||||
<div className="absolute inset-0 bg-bg-primary/80 backdrop-blur-sm z-10 flex items-center justify-center">
|
||||
<LoadingSpinner size="lg" text="Cargando datos..." />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tabla */}
|
||||
<table
|
||||
className="w-full border-collapse"
|
||||
className="w-full border-collapse bg-bg-primary"
|
||||
role="table"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
@@ -501,10 +501,10 @@ const DataTable = forwardRef<HTMLDivElement, DataTableProps>(({
|
||||
</thead>
|
||||
|
||||
{/* Body */}
|
||||
<tbody>
|
||||
<tbody className="bg-bg-primary">
|
||||
{data.length === 0 && !isLoading ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length + (selection ? 1 : 0) + (showRowNumbers ? 1 : 0) + (expandable ? 1 : 0)} className="p-8">
|
||||
<td colSpan={columns.length + (selection ? 1 : 0) + (showRowNumbers ? 1 : 0) + (expandable ? 1 : 0)} className="p-8 bg-bg-primary">
|
||||
<EmptyState
|
||||
variant="no-data"
|
||||
size="sm"
|
||||
@@ -525,7 +525,7 @@ const DataTable = forwardRef<HTMLDivElement, DataTableProps>(({
|
||||
<React.Fragment key={rowId}>
|
||||
<tr
|
||||
className={clsx(
|
||||
'border-b border-border-primary hover:bg-bg-secondary transition-colors',
|
||||
'border-b border-border-primary hover:bg-bg-secondary transition-colors bg-bg-primary',
|
||||
{
|
||||
'bg-color-primary/5': isSelected,
|
||||
'cursor-pointer': onRowClick
|
||||
@@ -537,7 +537,7 @@ const DataTable = forwardRef<HTMLDivElement, DataTableProps>(({
|
||||
>
|
||||
{/* Checkbox de selección */}
|
||||
{selection && (
|
||||
<td className={clsx('border-r border-border-primary', densityClasses[density])}>
|
||||
<td className={clsx('border-r border-border-primary bg-bg-primary', densityClasses[density])}>
|
||||
<input
|
||||
type={selection.mode === 'single' ? 'radio' : 'checkbox'}
|
||||
checked={isSelected}
|
||||
@@ -551,14 +551,14 @@ const DataTable = forwardRef<HTMLDivElement, DataTableProps>(({
|
||||
|
||||
{/* Número de fila */}
|
||||
{showRowNumbers && (
|
||||
<td className={clsx('border-r border-border-primary text-text-tertiary font-mono', densityClasses[density])}>
|
||||
<td className={clsx('border-r border-border-primary text-text-tertiary font-mono bg-bg-primary', densityClasses[density])}>
|
||||
{rowIndex + 1}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{/* Botón de expansión */}
|
||||
{expandable && (
|
||||
<td className={clsx('border-r border-border-primary', densityClasses[density])}>
|
||||
<td className={clsx('border-r border-border-primary bg-bg-primary', densityClasses[density])}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
@@ -592,9 +592,10 @@ const DataTable = forwardRef<HTMLDivElement, DataTableProps>(({
|
||||
className={clsx(
|
||||
densityClasses[density],
|
||||
alignClass,
|
||||
'bg-bg-primary',
|
||||
{
|
||||
'hidden sm:table-cell': column.hideOnMobile,
|
||||
'sticky bg-inherit': column.sticky,
|
||||
'sticky bg-bg-primary': column.sticky,
|
||||
'left-0': column.sticky === 'left',
|
||||
'right-0': column.sticky === 'right'
|
||||
}
|
||||
@@ -616,7 +617,7 @@ const DataTable = forwardRef<HTMLDivElement, DataTableProps>(({
|
||||
<tr>
|
||||
<td
|
||||
colSpan={columns.length + (selection ? 1 : 0) + (showRowNumbers ? 1 : 0) + 1}
|
||||
className="p-0 border-b border-border-primary"
|
||||
className="p-0 border-b border-border-primary bg-bg-primary"
|
||||
>
|
||||
<div className="bg-bg-secondary p-4">
|
||||
{expandable.renderExpandedRow(row, rowIndex)}
|
||||
|
||||
Reference in New Issue
Block a user