Add a ne model and card design across pages

This commit is contained in:
Urtzi Alfaro
2025-08-31 10:46:13 +02:00
parent ab21149acf
commit a8b73e22ea
14 changed files with 1865 additions and 820 deletions

View File

@@ -125,6 +125,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
const isAuthenticated = useIsAuthenticated();
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
const [hoveredItem, setHoveredItem] = useState<string | null>(null);
const sidebarRef = React.useRef<HTMLDivElement>(null);
// Get navigation routes from config and convert to navigation items - memoized
@@ -302,11 +303,59 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
};
}, [isOpen, onClose]);
// Render submenu overlay for collapsed sidebar
const renderSubmenuOverlay = (item: NavigationItem) => {
if (!item.children || item.children.length === 0) return null;
return (
<div className="fixed left-[var(--sidebar-collapsed-width)] top-0 z-[var(--z-popover)] min-w-[200px] bg-[var(--bg-primary)] border border-[var(--border-primary)] rounded-lg shadow-lg py-2">
<div className="px-3 py-2 border-b border-[var(--border-primary)]">
<span className="text-sm font-medium text-[var(--text-secondary)]">
{item.label}
</span>
</div>
<ul className="py-1">
{item.children.map(child => (
<li key={child.id}>
<button
onClick={() => handleItemClick(child)}
disabled={child.disabled}
className={clsx(
'w-full text-left px-3 py-2 text-sm transition-colors duration-200',
'hover:bg-[var(--bg-secondary)]',
location.pathname === child.path && 'bg-[var(--color-primary)]/10 text-[var(--color-primary)] border-r-2 border-[var(--color-primary)]',
child.disabled && 'opacity-50 cursor-not-allowed'
)}
>
<div className="flex items-center">
{child.icon && (
<child.icon className="w-4 h-4 mr-3 flex-shrink-0" />
)}
<span className="truncate">{child.label}</span>
{child.badge && (
<Badge
variant={child.badge.variant || 'default'}
size="sm"
className="ml-2 text-xs"
>
{child.badge.text}
</Badge>
)}
</div>
</button>
</li>
))}
</ul>
</div>
);
};
// Render navigation item
const renderItem = (item: NavigationItem, level = 0) => {
const isActive = location.pathname === item.path || location.pathname.startsWith(item.path + '/');
const isExpanded = expandedItems.has(item.id);
const hasChildren = item.children && item.children.length > 0;
const isHovered = hoveredItem === item.id;
const ItemIcon = item.icon;
const itemContent = (
@@ -317,17 +366,24 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
level > 0 && 'pl-6',
)}
>
{ItemIcon && (
<ItemIcon
className={clsx(
'flex-shrink-0 transition-colors duration-200',
isCollapsed ? 'w-5 h-5' : 'w-4 h-4 mr-3',
isActive
? 'text-[var(--color-primary)]'
: 'text-[var(--text-tertiary)] group-hover:text-[var(--text-primary)]'
)}
/>
)}
<div className="relative">
{ItemIcon && (
<ItemIcon
className={clsx(
'flex-shrink-0 transition-colors duration-200',
isCollapsed ? 'w-5 h-5' : 'w-4 h-4 mr-3',
isActive
? 'text-[var(--color-primary)]'
: 'text-[var(--text-tertiary)] group-hover:text-[var(--text-primary)]'
)}
/>
)}
{/* Submenu indicator for collapsed sidebar */}
{isCollapsed && hasChildren && level === 0 && (
<div className="absolute -bottom-1 -right-1 w-2 h-2 bg-[var(--color-primary)] rounded-full opacity-75" />
)}
</div>
{!ItemIcon && level > 0 && (
<Dot className={clsx(
@@ -374,24 +430,47 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
);
const button = (
<button
onClick={() => handleItemClick(item)}
disabled={item.disabled}
data-path={item.path}
className={clsx(
'w-full rounded-lg transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
isActive && 'bg-[var(--color-primary)]/10 border-l-2 border-[var(--color-primary)]',
!isActive && 'hover:bg-[var(--bg-secondary)]',
item.disabled && 'opacity-50 cursor-not-allowed',
isCollapsed && !hasChildren ? 'flex justify-center items-center p-2 mx-1' : 'p-3'
<div className="relative">
<button
onClick={() => handleItemClick(item)}
disabled={item.disabled}
data-path={item.path}
onMouseEnter={() => {
if (isCollapsed && hasChildren && level === 0) {
setHoveredItem(item.id);
}
}}
onMouseLeave={() => {
if (isCollapsed && hasChildren && level === 0) {
setHoveredItem(null);
}
}}
className={clsx(
'w-full rounded-lg transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
isActive && 'bg-[var(--color-primary)]/10 border-l-2 border-[var(--color-primary)]',
!isActive && 'hover:bg-[var(--bg-secondary)]',
item.disabled && 'opacity-50 cursor-not-allowed',
isCollapsed && !hasChildren ? 'flex justify-center items-center p-2 mx-1' : 'p-3'
)}
aria-expanded={hasChildren ? isExpanded : undefined}
aria-current={isActive ? 'page' : undefined}
title={isCollapsed ? item.label : undefined}
>
{itemContent}
</button>
{/* Submenu overlay for collapsed sidebar */}
{isCollapsed && hasChildren && level === 0 && isHovered && (
<div
className="absolute left-full top-0 ml-2 z-[var(--z-popover)]"
onMouseEnter={() => setHoveredItem(item.id)}
onMouseLeave={() => setHoveredItem(null)}
>
{renderSubmenuOverlay(item)}
</div>
)}
aria-expanded={hasChildren ? isExpanded : undefined}
aria-current={isActive ? 'page' : undefined}
title={isCollapsed ? item.label : undefined}
>
{itemContent}
</button>
</div>
);
return (