Design System: bootstrap tokens + paleta brand El Día #13
91
src/web/package-lock.json
generated
91
src/web/package-lock.json
generated
@@ -19,6 +19,7 @@
|
|||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-query": "^5.99.0",
|
"@tanstack/react-query": "^5.99.0",
|
||||||
"axios": "1.7",
|
"axios": "1.7",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -2723,6 +2724,96 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-tooltip": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.3",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-popper": "1.2.8",
|
||||||
|
"@radix-ui/react-portal": "1.1.9",
|
||||||
|
"@radix-ui/react-presence": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-query": "^5.99.0",
|
"@tanstack/react-query": "^5.99.0",
|
||||||
"axios": "1.7",
|
"axios": "1.7",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { Toaster } from 'sonner'
|
import { Toaster } from 'sonner'
|
||||||
|
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||||
import { AppRoutes } from './router'
|
import { AppRoutes } from './router'
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
@@ -13,10 +14,12 @@ const queryClient = new QueryClient({
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<TooltipProvider delayDuration={150}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
<Toaster richColors closeButton position="top-right" />
|
<Toaster richColors closeButton position="top-right" />
|
||||||
|
</TooltipProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
KeyRound,
|
KeyRound,
|
||||||
PanelLeftClose,
|
PanelLeftClose,
|
||||||
PanelLeftOpen,
|
PanelLeftOpen,
|
||||||
Newspaper,
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { useAuthStore } from '@/stores/authStore'
|
import { useAuthStore } from '@/stores/authStore'
|
||||||
import { useSidebar } from '@/hooks/useSidebar'
|
import { useSidebar } from '@/hooks/useSidebar'
|
||||||
|
|
||||||
@@ -65,18 +65,42 @@ export function SidebarNav({ forceExpanded = false }: SidebarNavProps) {
|
|||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-full flex-col bg-card text-card-foreground overflow-hidden transition-[width] duration-200 ease-out',
|
'flex h-full flex-col bg-card text-card-foreground transition-[width] duration-200 ease-out',
|
||||||
collapsed ? 'w-[68px]' : 'w-60',
|
collapsed ? 'w-[68px]' : 'w-60',
|
||||||
)}
|
)}
|
||||||
data-collapsed={collapsed}
|
data-collapsed={collapsed}
|
||||||
>
|
>
|
||||||
{/* Brand */}
|
{/* Brand + Toggle (top header) */}
|
||||||
<div className="flex h-14 items-center border-b border-border px-3 shrink-0">
|
<div
|
||||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-brand-500 to-violet-500 shadow-md shadow-brand-500/20 shrink-0">
|
className={cn(
|
||||||
<Newspaper className="h-4 w-4 text-white" strokeWidth={2.25} />
|
'flex h-14 items-center border-b border-border shrink-0',
|
||||||
</div>
|
collapsed ? 'justify-center px-2' : 'px-3 gap-2',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!forceExpanded && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggle}
|
||||||
|
aria-label={collapsed ? 'Expandir sidebar' : 'Colapsar sidebar'}
|
||||||
|
className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground shrink-0"
|
||||||
|
>
|
||||||
|
{collapsed ? (
|
||||||
|
<PanelLeftOpen className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<PanelLeftClose className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">
|
||||||
|
{collapsed ? 'Expandir' : 'Colapsar'}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<span className="ml-3 text-sm font-semibold tracking-tight text-foreground truncate">
|
<span className="text-sm font-semibold tracking-tight text-foreground truncate">
|
||||||
SIG-CM 2.0
|
SIG-CM 2.0
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -107,32 +131,6 @@ export function SidebarNav({ forceExpanded = false }: SidebarNavProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Collapse toggle — visible only on desktop (no en Sheet) */}
|
|
||||||
{!forceExpanded && (
|
|
||||||
<div className="border-t border-border p-2 shrink-0">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={toggle}
|
|
||||||
aria-label={collapsed ? 'Expandir sidebar' : 'Colapsar sidebar'}
|
|
||||||
className={cn(
|
|
||||||
'group/toggle relative w-full flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
|
|
||||||
'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
|
||||||
collapsed && 'justify-center',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{collapsed ? (
|
|
||||||
<PanelLeftOpen className="h-4 w-4 shrink-0" />
|
|
||||||
) : (
|
|
||||||
<PanelLeftClose className="h-4 w-4 shrink-0" />
|
|
||||||
)}
|
|
||||||
{!collapsed && <span>Colapsar</span>}
|
|
||||||
{collapsed && (
|
|
||||||
<SidebarTooltip>Expandir</SidebarTooltip>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</aside>
|
</aside>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -151,12 +149,13 @@ function NavRow({ item, collapsed, active }: NavRowProps) {
|
|||||||
const Icon = item.icon
|
const Icon = item.icon
|
||||||
|
|
||||||
const baseClasses = cn(
|
const baseClasses = cn(
|
||||||
'group/item relative flex items-center rounded-md text-sm transition-colors',
|
'relative flex items-center rounded-md text-sm transition-colors',
|
||||||
collapsed ? 'justify-center h-10 px-0' : 'gap-3 px-3 py-2',
|
collapsed ? 'justify-center h-10 w-10 mx-auto' : 'gap-3 px-3 py-2',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Disabled item
|
||||||
if (item.disabled) {
|
if (item.disabled) {
|
||||||
return (
|
const content = (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
baseClasses,
|
baseClasses,
|
||||||
@@ -173,12 +172,23 @@ function NavRow({ item, collapsed, active }: NavRowProps) {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{collapsed && <SidebarTooltip>{item.label} · Próximamente</SidebarTooltip>}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!collapsed) return content
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">
|
||||||
|
{item.label}{' '}
|
||||||
|
<span className="text-muted-foreground">· Próximamente</span>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// Active link
|
||||||
|
const link = (
|
||||||
<Link
|
<Link
|
||||||
to={item.href}
|
to={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -189,15 +199,22 @@ function NavRow({ item, collapsed, active }: NavRowProps) {
|
|||||||
)}
|
)}
|
||||||
aria-current={active ? 'page' : undefined}
|
aria-current={active ? 'page' : undefined}
|
||||||
>
|
>
|
||||||
{/* Active indicator bar (left edge) — sutil cuando expanded, dot cuando collapsed */}
|
{/* Active indicator bar (left edge) when expanded */}
|
||||||
{active && !collapsed && (
|
{active && !collapsed && (
|
||||||
<span className="absolute left-0 top-1/2 -translate-y-1/2 h-5 w-0.5 rounded-r-full bg-primary" />
|
<span className="absolute left-0 top-1/2 -translate-y-1/2 h-5 w-0.5 rounded-r-full bg-primary" />
|
||||||
)}
|
)}
|
||||||
<Icon className="h-4 w-4 shrink-0" />
|
<Icon className="h-4 w-4 shrink-0" />
|
||||||
{!collapsed && <span className="truncate">{item.label}</span>}
|
{!collapsed && <span className="truncate">{item.label}</span>}
|
||||||
{collapsed && <SidebarTooltip>{item.label}</SidebarTooltip>}
|
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!collapsed) return link
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>{link}</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">{item.label}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SectionLabel({
|
function SectionLabel({
|
||||||
@@ -208,9 +225,7 @@ function SectionLabel({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
return (
|
return <div className="my-2 mx-2 border-t border-border" aria-hidden="true" />
|
||||||
<div className="my-2 mx-2 border-t border-border" aria-hidden="true" />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="pt-3 pb-1 px-3">
|
<div className="pt-3 pb-1 px-3">
|
||||||
@@ -220,25 +235,3 @@ function SectionLabel({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom tooltip que aparece a la derecha del item cuando el sidebar está collapsed.
|
|
||||||
* Sin radix dep — pure CSS hover + group selector. Se posiciona absolute fuera del sidebar.
|
|
||||||
*/
|
|
||||||
function SidebarTooltip({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
role="tooltip"
|
|
||||||
className={cn(
|
|
||||||
'pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 z-50',
|
|
||||||
'whitespace-nowrap rounded-md border border-border bg-popover px-2.5 py-1.5',
|
|
||||||
'text-xs font-medium text-popover-foreground shadow-lg',
|
|
||||||
'opacity-0 -translate-x-1 transition-all duration-150',
|
|
||||||
'group-hover/item:opacity-100 group-hover/item:translate-x-0',
|
|
||||||
'group-hover/toggle:opacity-100 group-hover/toggle:translate-x-0',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
33
src/web/src/components/ui/tooltip.tsx
Normal file
33
src/web/src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 8, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
'z-50 overflow-hidden rounded-md border border-border bg-popover px-2.5 py-1.5 text-xs font-medium text-popover-foreground shadow-lg',
|
||||||
|
'data-[state=delayed-open]:animate-in data-[state=closed]:animate-out',
|
||||||
|
'data-[state=closed]:fade-out-0 data-[state=delayed-open]:fade-in-0',
|
||||||
|
'data-[state=closed]:zoom-out-95 data-[state=delayed-open]:zoom-in-95',
|
||||||
|
'data-[side=right]:slide-in-from-left-1 data-[side=left]:slide-in-from-right-1',
|
||||||
|
'data-[side=top]:slide-in-from-bottom-1 data-[side=bottom]:slide-in-from-top-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
))
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
Reference in New Issue
Block a user