feat(ux-ui): update layout, providers and config for UX remediation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a3b61b8ae4
commit
6568b9bfed
@ -16,36 +16,73 @@ import {
|
||||
ChevronDown,
|
||||
LogOut,
|
||||
Users2,
|
||||
Search,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@utils/cn';
|
||||
import { useUIStore } from '@stores/useUIStore';
|
||||
import { useAuthStore } from '@stores/useAuthStore';
|
||||
import { useIsMobile } from '@hooks/useMediaQuery';
|
||||
import { useFilteredNavigation, type NavigationItem } from '@hooks/useFilteredNavigation';
|
||||
import { ThemeToggle } from '@components/atoms/ThemeToggle';
|
||||
import { CommandPaletteWithRouter, useCommandPalette } from '@components/organisms/CommandPalette';
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const navigation = [
|
||||
/**
|
||||
* Search button component that opens the command palette
|
||||
*/
|
||||
function SearchButton() {
|
||||
const { open } = useCommandPalette();
|
||||
const isMac = typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={open}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg px-3 py-1.5',
|
||||
'border border-gray-200 bg-gray-50 text-gray-500',
|
||||
'hover:bg-gray-100 hover:text-gray-700',
|
||||
'dark:border-gray-600 dark:bg-gray-700 dark:text-gray-400',
|
||||
'dark:hover:bg-gray-600 dark:hover:text-gray-300',
|
||||
'transition-colors duration-150'
|
||||
)}
|
||||
aria-label="Buscar"
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
<span className="hidden text-sm sm:inline">Buscar...</span>
|
||||
<kbd className="hidden rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px] font-medium text-gray-500 sm:inline dark:border-gray-500 dark:bg-gray-600 dark:text-gray-400">
|
||||
{isMac ? '⌘' : 'Ctrl'}+K
|
||||
</kbd>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const navigation: NavigationItem[] = [
|
||||
{ name: 'Dashboard', href: '/dashboard', icon: Home },
|
||||
{ name: 'Usuarios', href: '/users', icon: Users },
|
||||
{ name: 'Empresas', href: '/companies', icon: Building2 },
|
||||
{ name: 'Partners', href: '/partners', icon: Users2 },
|
||||
{ name: 'Inventario', href: '/inventory', icon: Package },
|
||||
{ name: 'Ventas', href: '/sales', icon: ShoppingCart },
|
||||
{ name: 'Compras', href: '/purchases', icon: ShoppingCart },
|
||||
{ name: 'Finanzas', href: '/financial', icon: Receipt },
|
||||
{ name: 'Proyectos', href: '/projects', icon: FolderKanban },
|
||||
{ name: 'CRM', href: '/crm', icon: UserCircle },
|
||||
{ name: 'RRHH', href: '/hr', icon: Users },
|
||||
{ name: 'Configuración', href: '/settings', icon: Settings },
|
||||
{ name: 'Usuarios', href: '/users', icon: Users, module: 'users' },
|
||||
{ name: 'Empresas', href: '/companies', icon: Building2, module: 'companies' },
|
||||
{ name: 'Partners', href: '/partners', icon: Users2, module: 'partners' },
|
||||
{ name: 'Inventario', href: '/inventory', icon: Package, module: 'inventory' },
|
||||
{ name: 'Ventas', href: '/sales', icon: ShoppingCart, module: 'sales' },
|
||||
{ name: 'Compras', href: '/purchases', icon: ShoppingCart, module: 'purchases' },
|
||||
{ name: 'Finanzas', href: '/financial', icon: Receipt, module: 'finance' },
|
||||
{ name: 'Proyectos', href: '/projects', icon: FolderKanban, module: 'projects' },
|
||||
{ name: 'CRM', href: '/crm', icon: UserCircle, module: 'crm' },
|
||||
{ name: 'RRHH', href: '/hr', icon: Users, module: 'hr' },
|
||||
{ name: 'Configuración', href: '/settings', icon: Settings, roles: ['admin', 'super_admin'] },
|
||||
];
|
||||
|
||||
export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
/**
|
||||
* Internal layout component (used inside CommandPaletteWithRouter)
|
||||
*/
|
||||
function DashboardLayoutInner({ children }: DashboardLayoutProps) {
|
||||
const location = useLocation();
|
||||
const isMobile = useIsMobile();
|
||||
const { sidebarOpen, sidebarCollapsed, toggleSidebar, setSidebarOpen, setIsMobile } = useUIStore();
|
||||
const { user, logout } = useAuthStore();
|
||||
const { items: filteredNavigation, isLoading: isNavigationLoading } = useFilteredNavigation(navigation);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMobile(isMobile);
|
||||
@ -59,11 +96,11 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
}, [location.pathname, isMobile, setSidebarOpen]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{/* Mobile sidebar backdrop */}
|
||||
{isMobile && sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-gray-600/75"
|
||||
className="fixed inset-0 z-40 bg-gray-600/75 dark:bg-gray-900/80"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
@ -71,7 +108,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={cn(
|
||||
'fixed inset-y-0 left-0 z-50 flex flex-col bg-white shadow-lg transition-all duration-300',
|
||||
'fixed inset-y-0 left-0 z-50 flex flex-col bg-white shadow-lg transition-all duration-300 dark:bg-gray-800 dark:shadow-gray-900/50',
|
||||
isMobile
|
||||
? sidebarOpen
|
||||
? 'translate-x-0'
|
||||
@ -82,17 +119,17 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
)}
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="flex h-16 items-center justify-between border-b px-4">
|
||||
<div className="flex h-16 items-center justify-between border-b px-4 dark:border-gray-700">
|
||||
{(!sidebarCollapsed || isMobile) && (
|
||||
<Link to="/dashboard" className="flex items-center">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary-600">
|
||||
<span className="text-lg font-bold text-white">E</span>
|
||||
</div>
|
||||
<span className="ml-2 text-lg font-bold text-gray-900">ERP</span>
|
||||
<span className="ml-2 text-lg font-bold text-gray-900 dark:text-white">ERP</span>
|
||||
</Link>
|
||||
)}
|
||||
{isMobile && (
|
||||
<button onClick={() => setSidebarOpen(false)} className="p-2">
|
||||
<button onClick={() => setSidebarOpen(false)} className="p-2 dark:text-gray-400" aria-label="Cerrar menú">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
@ -100,8 +137,14 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 space-y-1 overflow-y-auto p-2">
|
||||
{navigation.map((item) => {
|
||||
{isNavigationLoading ? (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<div className="h-5 w-5 animate-spin rounded-full border-2 border-primary-600 border-t-transparent" />
|
||||
</div>
|
||||
) : (
|
||||
filteredNavigation.map((item) => {
|
||||
const isActive = location.pathname.startsWith(item.href);
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
@ -109,36 +152,38 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
className={cn(
|
||||
'flex items-center rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-primary-50 text-primary-700'
|
||||
: 'text-gray-700 hover:bg-gray-100'
|
||||
? 'bg-primary-50 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400'
|
||||
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'
|
||||
)}
|
||||
>
|
||||
<item.icon className={cn('h-5 w-5 flex-shrink-0', isActive ? 'text-primary-600' : 'text-gray-400')} />
|
||||
{Icon && <Icon className={cn('h-5 w-5 flex-shrink-0', isActive ? 'text-primary-600 dark:text-primary-400' : 'text-gray-400 dark:text-gray-500')} />}
|
||||
{(!sidebarCollapsed || isMobile) && (
|
||||
<span className="ml-3">{item.name}</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* User menu */}
|
||||
<div className="border-t p-4">
|
||||
<div className="border-t p-4 dark:border-gray-700">
|
||||
{(!sidebarCollapsed || isMobile) ? (
|
||||
<div className="flex items-center">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary-100 text-primary-700">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400">
|
||||
{user?.firstName?.[0]}{user?.lastName?.[0]}
|
||||
</div>
|
||||
<div className="ml-3 flex-1 overflow-hidden">
|
||||
<p className="truncate text-sm font-medium text-gray-900">
|
||||
<p className="truncate text-sm font-medium text-gray-900 dark:text-white">
|
||||
{user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
<p className="truncate text-xs text-gray-500">{user?.email}</p>
|
||||
<p className="truncate text-xs text-gray-500 dark:text-gray-400">{user?.email}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="p-2 text-gray-400 hover:text-gray-600"
|
||||
className="p-2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
|
||||
title="Cerrar sesión"
|
||||
aria-label="Cerrar sesión"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
</button>
|
||||
@ -146,8 +191,9 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
) : (
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex w-full items-center justify-center p-2 text-gray-400 hover:text-gray-600"
|
||||
className="flex w-full items-center justify-center p-2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
|
||||
title="Cerrar sesión"
|
||||
aria-label="Cerrar sesión"
|
||||
>
|
||||
<LogOut className="h-5 w-5" />
|
||||
</button>
|
||||
@ -163,23 +209,28 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
)}
|
||||
>
|
||||
{/* Top bar */}
|
||||
<header className="sticky top-0 z-30 flex h-16 items-center justify-between border-b bg-white px-4 shadow-sm">
|
||||
<header className="sticky top-0 z-30 flex h-16 items-center justify-between border-b bg-white px-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="rounded-lg p-2 hover:bg-gray-100"
|
||||
className="rounded-lg p-2 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
aria-label="Abrir menú"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
<Menu className="h-5 w-5 dark:text-gray-400" />
|
||||
</button>
|
||||
<SearchButton />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<button className="relative rounded-lg p-2 hover:bg-gray-100">
|
||||
<Bell className="h-5 w-5 text-gray-500" />
|
||||
<ThemeToggle />
|
||||
<button className="relative rounded-lg p-2 hover:bg-gray-100 dark:hover:bg-gray-700" aria-label="Notificaciones">
|
||||
<Bell className="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
||||
<span className="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-danger-500 text-xs text-white">
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary-100 text-sm font-medium text-primary-700">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary-100 text-sm font-medium text-primary-700 dark:bg-primary-900/30 dark:text-primary-400">
|
||||
{user?.firstName?.[0]}{user?.lastName?.[0]}
|
||||
</div>
|
||||
<ChevronDown className="h-4 w-4 text-gray-400" />
|
||||
@ -193,3 +244,15 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard Layout with Command Palette support
|
||||
* Wraps the layout with CommandPaletteWithRouter for Cmd+K / Ctrl+K navigation
|
||||
*/
|
||||
export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
return (
|
||||
<CommandPaletteWithRouter>
|
||||
<DashboardLayoutInner>{children}</DashboardLayoutInner>
|
||||
</CommandPaletteWithRouter>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { ToastContainer } from '@components/organisms/Toast';
|
||||
import { ThemeProvider } from '@shared/providers/ThemeProvider';
|
||||
|
||||
interface AppProvidersProps {
|
||||
children: ReactNode;
|
||||
@ -7,9 +8,9 @@ interface AppProvidersProps {
|
||||
|
||||
export function AppProviders({ children }: AppProvidersProps) {
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
</>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
362
src/index.css
362
src/index.css
@ -2,39 +2,240 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* =============================================================================
|
||||
CSS VARIABLES - Design Tokens para Runtime Theming
|
||||
============================================================================= */
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
/* =========================================================================
|
||||
RGB VALUES FOR TAILWIND ALPHA SUPPORT
|
||||
Format: R G B (space-separated for rgb(var(--color) / alpha) syntax)
|
||||
========================================================================= */
|
||||
|
||||
/* Primary Palette - ISEM Blue (RGB values) */
|
||||
--color-primary-50: 230 243 255;
|
||||
--color-primary-100: 204 231 255;
|
||||
--color-primary-200: 153 207 255;
|
||||
--color-primary-300: 102 183 255;
|
||||
--color-primary-400: 51 159 255;
|
||||
--color-primary-500: 0 97 168;
|
||||
--color-primary-600: 0 77 134;
|
||||
--color-primary-700: 0 58 101;
|
||||
--color-primary-800: 0 38 67;
|
||||
--color-primary-900: 0 19 34;
|
||||
|
||||
/* Secondary Palette - ISEM Green (RGB values) */
|
||||
--color-secondary-50: 230 255 245;
|
||||
--color-secondary-100: 204 255 235;
|
||||
--color-secondary-200: 153 255 214;
|
||||
--color-secondary-300: 102 255 194;
|
||||
--color-secondary-400: 51 255 173;
|
||||
--color-secondary-500: 0 168 104;
|
||||
--color-secondary-600: 0 134 83;
|
||||
--color-secondary-700: 0 101 63;
|
||||
--color-secondary-800: 0 67 42;
|
||||
--color-secondary-900: 0 34 21;
|
||||
|
||||
/* Success Palette (RGB values) */
|
||||
--color-success-50: 209 231 221;
|
||||
--color-success-100: 209 231 221;
|
||||
--color-success-500: 25 135 84;
|
||||
--color-success-600: 21 115 71;
|
||||
--color-success-700: 15 81 50;
|
||||
|
||||
/* Warning Palette (RGB values) */
|
||||
--color-warning-50: 255 243 205;
|
||||
--color-warning-100: 255 243 205;
|
||||
--color-warning-500: 255 193 7;
|
||||
--color-warning-600: 224 168 0;
|
||||
--color-warning-700: 102 77 3;
|
||||
|
||||
/* Danger Palette (RGB values) */
|
||||
--color-danger-50: 248 215 218;
|
||||
--color-danger-100: 248 215 218;
|
||||
--color-danger-500: 220 53 69;
|
||||
--color-danger-600: 187 45 59;
|
||||
--color-danger-700: 132 32 41;
|
||||
|
||||
/* Info Palette (RGB values) */
|
||||
--color-info-50: 207 244 252;
|
||||
--color-info-100: 207 244 252;
|
||||
--color-info-500: 13 202 240;
|
||||
--color-info-600: 10 162 192;
|
||||
--color-info-700: 5 81 96;
|
||||
|
||||
/* Background Colors (RGB values) - Light Theme */
|
||||
--color-background: 255 255 255;
|
||||
--color-background-subtle: 248 249 251;
|
||||
--color-background-muted: 241 243 245;
|
||||
--color-background-emphasis: 233 236 239;
|
||||
|
||||
/* Foreground Colors (RGB values) - Light Theme */
|
||||
--color-foreground: 33 37 41;
|
||||
--color-foreground-muted: 108 117 125;
|
||||
--color-foreground-subtle: 173 181 189;
|
||||
|
||||
/* Border Colors (RGB values) - Light Theme */
|
||||
--color-border: 222 226 230;
|
||||
--color-border-subtle: 233 236 239;
|
||||
--color-border-emphasis: 206 212 218;
|
||||
|
||||
/* Surface Colors (RGB values) - Light Theme */
|
||||
--color-surface: 249 250 251;
|
||||
--color-surface-hover: 243 244 246;
|
||||
--color-surface-card: 255 255 255;
|
||||
--color-surface-popover: 255 255 255;
|
||||
--color-surface-modal: 255 255 255;
|
||||
--color-surface-dropdown: 255 255 255;
|
||||
|
||||
/* =========================================================================
|
||||
LEGACY HEX VALUES (Backward Compatibility)
|
||||
Keep these for direct CSS usage and gradual migration
|
||||
========================================================================= */
|
||||
|
||||
/* Colores de Marca ISEM (HEX) */
|
||||
--color-brand-primary: #0061A8;
|
||||
--color-brand-secondary: #00A868;
|
||||
|
||||
/* Colores Semanticos (HEX) */
|
||||
--color-success-hex: #198754;
|
||||
--color-success-light-hex: #D1E7DD;
|
||||
--color-success-dark-hex: #0F5132;
|
||||
--color-warning-hex: #FFC107;
|
||||
--color-warning-light-hex: #FFF3CD;
|
||||
--color-warning-dark-hex: #664D03;
|
||||
--color-danger-hex: #DC3545;
|
||||
--color-danger-light-hex: #F8D7DA;
|
||||
--color-danger-dark-hex: #842029;
|
||||
--color-info-hex: #0DCAF0;
|
||||
--color-info-light-hex: #CFF4FC;
|
||||
--color-info-dark-hex: #055160;
|
||||
|
||||
/* Semantic Aliases (HEX for legacy components) */
|
||||
--color-success: #198754;
|
||||
--color-success-light: #D1E7DD;
|
||||
--color-success-dark: #0F5132;
|
||||
--color-warning: #FFC107;
|
||||
--color-warning-light: #FFF3CD;
|
||||
--color-warning-dark: #664D03;
|
||||
--color-danger: #DC3545;
|
||||
--color-danger-light: #F8D7DA;
|
||||
--color-danger-dark: #842029;
|
||||
--color-info: #0DCAF0;
|
||||
--color-info-light: #CFF4FC;
|
||||
--color-info-dark: #055160;
|
||||
|
||||
/* Primary/Secondary HEX aliases */
|
||||
--color-primary-hex: var(--color-brand-primary);
|
||||
--color-secondary-hex: var(--color-brand-secondary);
|
||||
|
||||
/* Sombras Tema Claro */
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-default: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
|
||||
/* Tipografia */
|
||||
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', Consolas, Monaco, monospace;
|
||||
|
||||
/* Animaciones */
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 200ms;
|
||||
--duration-slow: 300ms;
|
||||
--easing-default: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
/* Bordes */
|
||||
--radius-sm: 0.125rem;
|
||||
--radius-default: 0.25rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
--radius-full: 9999px;
|
||||
}
|
||||
|
||||
/* Tema Oscuro */
|
||||
.dark {
|
||||
/* Background Colors (RGB values) - Dark Theme */
|
||||
--color-background: 27 30 35;
|
||||
--color-background-subtle: 33 37 41;
|
||||
--color-background-muted: 45 49 57;
|
||||
--color-background-emphasis: 52 58 64;
|
||||
|
||||
/* Foreground Colors (RGB values) - Dark Theme */
|
||||
--color-foreground: 236 236 236;
|
||||
--color-foreground-muted: 160 160 160;
|
||||
--color-foreground-subtle: 108 117 125;
|
||||
|
||||
/* Border Colors (RGB values) - Dark Theme */
|
||||
--color-border: 73 80 87;
|
||||
--color-border-subtle: 52 58 64;
|
||||
--color-border-emphasis: 108 117 125;
|
||||
|
||||
/* Surface Colors (RGB values) - Dark Theme */
|
||||
--color-surface: 31 41 55;
|
||||
--color-surface-hover: 55 65 81;
|
||||
--color-surface-card: 45 49 57;
|
||||
--color-surface-popover: 52 58 64;
|
||||
--color-surface-modal: 45 49 57;
|
||||
--color-surface-dropdown: 52 58 64;
|
||||
|
||||
/* Sombras Tema Oscuro */
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
|
||||
--shadow-default: 0 1px 3px 0 rgb(0 0 0 / 0.4), 0 1px 2px -1px rgb(0 0 0 / 0.4);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4);
|
||||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.4);
|
||||
}
|
||||
|
||||
html {
|
||||
@apply antialiased;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-gray-50 text-gray-900;
|
||||
background-color: rgb(var(--color-background));
|
||||
color: rgb(var(--color-foreground));
|
||||
}
|
||||
|
||||
* {
|
||||
@apply border-gray-200;
|
||||
border-color: rgb(var(--color-border));
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
COMPONENTES BASE
|
||||
============================================================================= */
|
||||
|
||||
@layer components {
|
||||
/* Botones */
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50;
|
||||
transition-duration: var(--duration-fast);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500;
|
||||
@apply bg-primary-500 text-white hover:bg-primary-600 focus-visible:ring-primary-500;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-secondary-100 text-secondary-900 hover:bg-secondary-200 focus-visible:ring-secondary-500;
|
||||
@apply bg-secondary-500 text-white hover:bg-secondary-600 focus-visible:ring-secondary-500;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
@apply border border-gray-300 bg-white hover:bg-gray-50 focus-visible:ring-gray-500;
|
||||
@apply border border-border-emphasis bg-transparent hover:bg-background-muted focus-visible:ring-primary-500;
|
||||
color: rgb(var(--color-foreground));
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
@apply bg-transparent hover:bg-background-muted focus-visible:ring-primary-500;
|
||||
color: rgb(var(--color-foreground));
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
@apply bg-danger-600 text-white hover:bg-danger-700 focus-visible:ring-danger-500;
|
||||
@apply bg-danger-500 text-white hover:bg-danger-600 focus-visible:ring-danger-500;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@ -49,31 +250,125 @@
|
||||
@apply h-12 px-6 text-lg;
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
.input {
|
||||
@apply block w-full rounded-md border border-gray-300 px-3 py-2 text-sm placeholder-gray-400 shadow-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500;
|
||||
@apply block w-full rounded-md border px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50;
|
||||
background-color: rgb(var(--color-background));
|
||||
border-color: rgb(var(--color-border));
|
||||
color: rgb(var(--color-foreground));
|
||||
transition-duration: var(--duration-fast);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: rgb(var(--color-foreground-subtle));
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
border-color: rgb(var(--color-primary-500));
|
||||
--tw-ring-color: rgb(var(--color-primary-500));
|
||||
}
|
||||
|
||||
.input-error {
|
||||
@apply border-danger-500 focus:border-danger-500 focus:ring-danger-500;
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
.label {
|
||||
@apply block text-sm font-medium text-gray-700;
|
||||
@apply block text-sm font-medium;
|
||||
color: rgb(var(--color-foreground));
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
@apply rounded-lg border bg-white shadow-sm;
|
||||
@apply rounded-lg border;
|
||||
background-color: rgb(var(--color-surface-card));
|
||||
border-color: rgb(var(--color-border));
|
||||
box-shadow: var(--shadow-default);
|
||||
}
|
||||
|
||||
/* Links */
|
||||
.link {
|
||||
@apply text-primary-600 hover:text-primary-700 hover:underline;
|
||||
@apply hover:underline;
|
||||
color: rgb(var(--color-primary-500));
|
||||
transition-duration: var(--duration-fast);
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: rgb(var(--color-primary-600));
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge {
|
||||
@apply inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
@apply bg-primary-100 text-primary-700;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
@apply bg-secondary-100 text-secondary-700;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background-color: var(--color-warning-light);
|
||||
color: var(--color-warning-dark);
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background-color: var(--color-danger-light);
|
||||
color: var(--color-danger-dark);
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background-color: var(--color-info-light);
|
||||
color: var(--color-info-dark);
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert {
|
||||
@apply rounded-lg border p-4;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: var(--color-success-light);
|
||||
border-color: var(--color-success);
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--color-warning-light);
|
||||
border-color: var(--color-warning);
|
||||
color: var(--color-warning-dark);
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: var(--color-danger-light);
|
||||
border-color: var(--color-danger);
|
||||
color: var(--color-danger-dark);
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: var(--color-info-light);
|
||||
border-color: var(--color-info);
|
||||
color: var(--color-info-dark);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
UTILIDADES
|
||||
============================================================================= */
|
||||
|
||||
@layer utilities {
|
||||
/* Scrollbar personalizado */
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: theme('colors.gray.300') transparent;
|
||||
scrollbar-color: rgb(var(--color-border)) transparent;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
@ -86,7 +381,50 @@
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background-color: theme('colors.gray.300');
|
||||
background-color: rgb(var(--color-border));
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Focus visible mejorado */
|
||||
.focus-ring {
|
||||
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
|
||||
--tw-ring-color: rgb(var(--color-primary-500));
|
||||
}
|
||||
|
||||
/* Texto truncado */
|
||||
.truncate-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.truncate-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Transiciones suaves */
|
||||
.transition-theme {
|
||||
transition-property: background-color, border-color, color, fill, stroke;
|
||||
transition-duration: var(--duration-normal);
|
||||
transition-timing-function: var(--easing-default);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
MULTI-TENANT THEMING (Placeholder para override por tenant)
|
||||
============================================================================= */
|
||||
|
||||
/*
|
||||
* Los tenants pueden sobreescribir estas variables via JS:
|
||||
* document.documentElement.style.setProperty('--color-brand-primary', '#FF0000');
|
||||
*
|
||||
* O cargar un CSS adicional con:
|
||||
* [data-tenant="empresa-abc"] {
|
||||
* --color-brand-primary: #123456;
|
||||
* --color-brand-secondary: #654321;
|
||||
* }
|
||||
*/
|
||||
|
||||
@ -1,78 +1,234 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
// Helper function to create CSS variable color with alpha support
|
||||
const withAlpha = (variableName) => `rgb(var(${variableName}) / <alpha-value>)`
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// =================================================================
|
||||
// CSS VARIABLE COLORS (Dynamic Theming Support)
|
||||
// These reference CSS variables defined in index.css
|
||||
// Supports alpha via: bg-primary-500/50, text-foreground/80, etc.
|
||||
// =================================================================
|
||||
|
||||
// Primary Palette - Uses CSS Variables
|
||||
primary: {
|
||||
50: '#f0f9ff',
|
||||
100: '#e0f2fe',
|
||||
200: '#bae6fd',
|
||||
300: '#7dd3fc',
|
||||
400: '#38bdf8',
|
||||
500: '#0ea5e9',
|
||||
600: '#0284c7',
|
||||
700: '#0369a1',
|
||||
800: '#075985',
|
||||
900: '#0c4a6e',
|
||||
950: '#082f49',
|
||||
50: withAlpha('--color-primary-50'),
|
||||
100: withAlpha('--color-primary-100'),
|
||||
200: withAlpha('--color-primary-200'),
|
||||
300: withAlpha('--color-primary-300'),
|
||||
400: withAlpha('--color-primary-400'),
|
||||
500: withAlpha('--color-primary-500'),
|
||||
600: withAlpha('--color-primary-600'),
|
||||
700: withAlpha('--color-primary-700'),
|
||||
800: withAlpha('--color-primary-800'),
|
||||
900: withAlpha('--color-primary-900'),
|
||||
DEFAULT: withAlpha('--color-primary-500'),
|
||||
},
|
||||
// Secondary Palette - Uses CSS Variables
|
||||
secondary: {
|
||||
50: '#f8fafc',
|
||||
100: '#f1f5f9',
|
||||
200: '#e2e8f0',
|
||||
300: '#cbd5e1',
|
||||
400: '#94a3b8',
|
||||
500: '#64748b',
|
||||
600: '#475569',
|
||||
700: '#334155',
|
||||
800: '#1e293b',
|
||||
900: '#0f172a',
|
||||
950: '#020617',
|
||||
50: withAlpha('--color-secondary-50'),
|
||||
100: withAlpha('--color-secondary-100'),
|
||||
200: withAlpha('--color-secondary-200'),
|
||||
300: withAlpha('--color-secondary-300'),
|
||||
400: withAlpha('--color-secondary-400'),
|
||||
500: withAlpha('--color-secondary-500'),
|
||||
600: withAlpha('--color-secondary-600'),
|
||||
700: withAlpha('--color-secondary-700'),
|
||||
800: withAlpha('--color-secondary-800'),
|
||||
900: withAlpha('--color-secondary-900'),
|
||||
DEFAULT: withAlpha('--color-secondary-500'),
|
||||
},
|
||||
// Semantic Colors - Uses CSS Variables
|
||||
success: {
|
||||
50: '#f0fdf4',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
700: '#15803d',
|
||||
50: withAlpha('--color-success-50'),
|
||||
100: withAlpha('--color-success-100'),
|
||||
500: withAlpha('--color-success-500'),
|
||||
600: withAlpha('--color-success-600'),
|
||||
700: withAlpha('--color-success-700'),
|
||||
DEFAULT: withAlpha('--color-success-500'),
|
||||
light: withAlpha('--color-success-50'),
|
||||
dark: withAlpha('--color-success-700'),
|
||||
},
|
||||
warning: {
|
||||
50: '#fffbeb',
|
||||
500: '#f59e0b',
|
||||
600: '#d97706',
|
||||
700: '#b45309',
|
||||
50: withAlpha('--color-warning-50'),
|
||||
100: withAlpha('--color-warning-100'),
|
||||
500: withAlpha('--color-warning-500'),
|
||||
600: withAlpha('--color-warning-600'),
|
||||
700: withAlpha('--color-warning-700'),
|
||||
DEFAULT: withAlpha('--color-warning-500'),
|
||||
light: withAlpha('--color-warning-50'),
|
||||
dark: withAlpha('--color-warning-700'),
|
||||
},
|
||||
danger: {
|
||||
50: '#fef2f2',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
700: '#b91c1c',
|
||||
50: withAlpha('--color-danger-50'),
|
||||
100: withAlpha('--color-danger-100'),
|
||||
500: withAlpha('--color-danger-500'),
|
||||
600: withAlpha('--color-danger-600'),
|
||||
700: withAlpha('--color-danger-700'),
|
||||
DEFAULT: withAlpha('--color-danger-500'),
|
||||
light: withAlpha('--color-danger-50'),
|
||||
dark: withAlpha('--color-danger-700'),
|
||||
},
|
||||
info: {
|
||||
50: withAlpha('--color-info-50'),
|
||||
100: withAlpha('--color-info-100'),
|
||||
500: withAlpha('--color-info-500'),
|
||||
600: withAlpha('--color-info-600'),
|
||||
700: withAlpha('--color-info-700'),
|
||||
DEFAULT: withAlpha('--color-info-500'),
|
||||
light: withAlpha('--color-info-50'),
|
||||
dark: withAlpha('--color-info-700'),
|
||||
},
|
||||
// Neutral Colors - Uses CSS Variables (auto dark/light)
|
||||
background: {
|
||||
DEFAULT: withAlpha('--color-background'),
|
||||
subtle: withAlpha('--color-background-subtle'),
|
||||
muted: withAlpha('--color-background-muted'),
|
||||
emphasis: withAlpha('--color-background-emphasis'),
|
||||
},
|
||||
foreground: {
|
||||
DEFAULT: withAlpha('--color-foreground'),
|
||||
muted: withAlpha('--color-foreground-muted'),
|
||||
subtle: withAlpha('--color-foreground-subtle'),
|
||||
},
|
||||
border: {
|
||||
DEFAULT: withAlpha('--color-border'),
|
||||
subtle: withAlpha('--color-border-subtle'),
|
||||
emphasis: withAlpha('--color-border-emphasis'),
|
||||
},
|
||||
surface: {
|
||||
DEFAULT: withAlpha('--color-surface'),
|
||||
hover: withAlpha('--color-surface-hover'),
|
||||
card: withAlpha('--color-surface-card'),
|
||||
popover: withAlpha('--color-surface-popover'),
|
||||
modal: withAlpha('--color-surface-modal'),
|
||||
dropdown: withAlpha('--color-surface-dropdown'),
|
||||
},
|
||||
|
||||
// =================================================================
|
||||
// STATIC HEX COLORS (Backward Compatibility)
|
||||
// Keep these for legacy code - prefix with 'static-'
|
||||
// =================================================================
|
||||
'static-primary': {
|
||||
50: '#E6F3FF',
|
||||
100: '#CCE7FF',
|
||||
200: '#99CFFF',
|
||||
300: '#66B7FF',
|
||||
400: '#339FFF',
|
||||
500: '#0061A8',
|
||||
600: '#004D86',
|
||||
700: '#003A65',
|
||||
800: '#002643',
|
||||
900: '#001322',
|
||||
},
|
||||
'static-secondary': {
|
||||
50: '#E6FFF5',
|
||||
100: '#CCFFEB',
|
||||
200: '#99FFD6',
|
||||
300: '#66FFC2',
|
||||
400: '#33FFAD',
|
||||
500: '#00A868',
|
||||
600: '#008653',
|
||||
700: '#00653F',
|
||||
800: '#00432A',
|
||||
900: '#002215',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'Fira Code', 'Consolas', 'Monaco', 'monospace'],
|
||||
},
|
||||
fontSize: {
|
||||
xs: ['0.75rem', { lineHeight: '1rem' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.25rem' }],
|
||||
base: ['1rem', { lineHeight: '1.5rem' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
||||
xl: ['1.25rem', { lineHeight: '1.75rem' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '2rem' }],
|
||||
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
|
||||
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
|
||||
},
|
||||
spacing: {
|
||||
'18': '4.5rem',
|
||||
'88': '22rem',
|
||||
},
|
||||
borderRadius: {
|
||||
sm: '0.125rem',
|
||||
DEFAULT: '0.25rem',
|
||||
md: '0.375rem',
|
||||
lg: '0.5rem',
|
||||
xl: '0.75rem',
|
||||
'2xl': '1rem',
|
||||
'3xl': '1.5rem',
|
||||
},
|
||||
boxShadow: {
|
||||
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
|
||||
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
|
||||
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
|
||||
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
|
||||
},
|
||||
zIndex: {
|
||||
dropdown: '1000',
|
||||
sticky: '1020',
|
||||
fixed: '1030',
|
||||
modalBackdrop: '1040',
|
||||
modal: '1050',
|
||||
popover: '1060',
|
||||
tooltip: '1070',
|
||||
toast: '1080',
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.2s ease-out',
|
||||
'fade-out': 'fadeOut 0.2s ease-in',
|
||||
'slide-in': 'slideIn 0.2s ease-out',
|
||||
'slide-out': 'slideOut 0.2s ease-in',
|
||||
'spin-slow': 'spin 2s linear infinite',
|
||||
'pulse-slow': 'pulse 3s ease-in-out infinite',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
fadeOut: {
|
||||
'0%': { opacity: '1' },
|
||||
'100%': { opacity: '0' },
|
||||
},
|
||||
slideIn: {
|
||||
'0%': { transform: 'translateY(-10px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||
},
|
||||
slideOut: {
|
||||
'0%': { transform: 'translateY(0)', opacity: '1' },
|
||||
'100%': { transform: 'translateY(-10px)', opacity: '0' },
|
||||
},
|
||||
},
|
||||
transitionDuration: {
|
||||
'0': '0ms',
|
||||
'75': '75ms',
|
||||
'100': '100ms',
|
||||
'150': '150ms',
|
||||
'200': '200ms',
|
||||
'300': '300ms',
|
||||
'500': '500ms',
|
||||
'700': '700ms',
|
||||
'1000': '1000ms',
|
||||
},
|
||||
transitionTimingFunction: {
|
||||
'ease-in': 'cubic-bezier(0.4, 0, 1, 1)',
|
||||
'ease-out': 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
'ease-in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user