Add a ne model and card design across pages
This commit is contained in:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user