This commit is contained in:
2026-01-19 09:23:57 +00:00
parent 5e2f117c61
commit dd2e246f2f
2 changed files with 1 additions and 263 deletions

View File

@@ -3,7 +3,7 @@ import { toast } from 'sonner'
import AppDialogs from '@/components/app/AppDialogs'
import AppMainPanel from '@/components/app/AppMainPanel'
import { NavigationMenu } from '@/components/organisms/NavigationMenu'
import { NavigationMenu } from '@/lib/json-ui/json-components'
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'
import appStrings from '@/data/app-shortcuts.json'
import useAppNavigation from '@/hooks/use-app-navigation'

View File

@@ -1,262 +0,0 @@
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Sidebar, SidebarContent, SidebarHeader } from '@/components/ui/sidebar'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'
import { CaretDoubleDown, CaretDoubleUp, CaretDown } from '@phosphor-icons/react'
import { CollapsibleTrigger } from '@/components/ui/collapsible'
import { Badge, Flex, Text, IconWrapper } from '@/components/atoms'
import { navigationGroups, NavigationItemData } from '@/lib/navigation-config'
import { FeatureToggles } from '@/types/project'
import { useRoutePreload } from '@/hooks/use-route-preload'
import navigationMenuCopy from '@/data/navigation-menu.json'
interface NavigationMenuProps {
activeTab: string
onTabChange: (tab: string) => void
featureToggles: FeatureToggles
errorCount?: number
}
interface NavigationMenuControlsProps {
onExpandAll: () => void
onCollapseAll: () => void
}
interface NavigationMenuGroupListProps {
activeTab: string
expandedGroups: Set<string>
featureToggles: FeatureToggles
errorCount: number
onToggleGroup: (groupId: string) => void
onItemClick: (value: string) => void
onItemHover: (value: string) => void
onItemLeave: (value: string) => void
}
function NavigationMenuControls({
onExpandAll,
onCollapseAll,
}: NavigationMenuControlsProps) {
return (
<div className="flex gap-2 mt-4">
<Button
variant="outline"
size="sm"
onClick={onExpandAll}
className="flex-1"
>
<CaretDoubleDown size={16} className="mr-2" />
{navigationMenuCopy.labels.expandAll}
</Button>
<Button
variant="outline"
size="sm"
onClick={onCollapseAll}
className="flex-1"
>
<CaretDoubleUp size={16} className="mr-2" />
{navigationMenuCopy.labels.collapseAll}
</Button>
</div>
)
}
function NavigationMenuHeader({
onExpandAll,
onCollapseAll,
}: NavigationMenuControlsProps) {
return (
<SidebarHeader className="px-4 py-4 border-b">
<h2 className="text-lg font-semibold">{navigationMenuCopy.labels.title}</h2>
<NavigationMenuControls
onExpandAll={onExpandAll}
onCollapseAll={onCollapseAll}
/>
</SidebarHeader>
)
}
function NavigationMenuGroupList({
activeTab,
expandedGroups,
featureToggles,
errorCount,
onToggleGroup,
onItemClick,
onItemHover,
onItemLeave,
}: NavigationMenuGroupListProps) {
const isItemVisible = (item: NavigationItemData) => {
if (!item.featureKey) return true
return featureToggles[item.featureKey]
}
const getVisibleItemsCount = (groupId: string) => {
const group = navigationGroups.find((g) => g.id === groupId)
if (!group) return 0
return group.items.filter(isItemVisible).length
}
const getItemBadge = (item: NavigationItemData) => {
if (item.id === 'errors') return errorCount
return item.badge
}
return (
<div className="space-y-2 py-4">
{navigationGroups.map((group) => {
const visibleItemsCount = getVisibleItemsCount(group.id)
if (visibleItemsCount === 0) return null
const isExpanded = expandedGroups.has(group.id)
return (
<Collapsible
key={group.id}
open={isExpanded}
onOpenChange={() => onToggleGroup(group.id)}
>
{/* NavigationGroupHeader - inlined */}
<CollapsibleTrigger className="w-full flex items-center gap-2 px-2 py-2 rounded-lg hover:bg-muted transition-colors group">
<CaretDown
size={16}
weight="bold"
className={`text-muted-foreground transition-transform ${
isExpanded ? 'rotate-0' : '-rotate-90'
}`}
/>
<h3 className="flex-1 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{group.label}
</h3>
<span className="text-xs text-muted-foreground">{visibleItemsCount}</span>
</CollapsibleTrigger>
<CollapsibleContent className="mt-1">
<div className="space-y-1 pl-2">
{group.items.map((item) => {
if (!isItemVisible(item)) return null
const isActive = activeTab === item.value
const badge = getItemBadge(item)
return (
<div
key={item.id}
onMouseEnter={() => onItemHover(item.value)}
onMouseLeave={() => onItemLeave(item.value)}
>
{/* NavigationItem - inlined */}
<button
onClick={() => onItemClick(item.value)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
isActive
? 'bg-primary text-primary-foreground'
: 'hover:bg-muted text-foreground'
}`}
>
<IconWrapper
icon={item.icon}
size="md"
variant={isActive ? 'default' : 'muted'}
/>
<Text className="flex-1 text-left font-medium" variant="small">
{item.label}
</Text>
{badge !== undefined && badge > 0 && (
<Badge
variant={isActive ? 'secondary' : 'destructive'}
className="ml-auto"
>
{badge}
</Badge>
)}
</button>
</div>
)
})}
</div>
</CollapsibleContent>
</Collapsible>
)
})}
</div>
)
}
export function NavigationMenu({
activeTab,
onTabChange,
featureToggles,
errorCount = 0,
}: NavigationMenuProps) {
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
new Set(['overview', 'development', 'automation', 'design', 'backend', 'testing', 'tools'])
)
const { preloadRoute, cancelPreload } = useRoutePreload({ delay: 100 })
const handleItemClick = (value: string) => {
onTabChange(value)
}
const handleItemHover = (value: string) => {
console.log(`[NAV] 🖱️ Hover detected on: ${value}`)
preloadRoute(value)
}
const handleItemLeave = (value: string) => {
console.log(`[NAV] 👋 Hover left: ${value}`)
cancelPreload(value)
}
const toggleGroup = (groupId: string) => {
setExpandedGroups((prev) => {
const newSet = new Set(prev)
if (newSet.has(groupId)) {
newSet.delete(groupId)
} else {
newSet.add(groupId)
}
return newSet
})
}
const handleExpandAll = () => {
const allGroupIds = navigationGroups
.filter((group) =>
group.items.some((item) => {
if (!item.featureKey) return true
return featureToggles[item.featureKey]
})
)
.map((group) => group.id)
setExpandedGroups(new Set(allGroupIds))
}
const handleCollapseAll = () => {
setExpandedGroups(new Set())
}
return (
<Sidebar side="left" collapsible="offcanvas">
<NavigationMenuHeader
onExpandAll={handleExpandAll}
onCollapseAll={handleCollapseAll}
/>
<SidebarContent>
<ScrollArea className="h-full px-4">
<NavigationMenuGroupList
activeTab={activeTab}
expandedGroups={expandedGroups}
featureToggles={featureToggles}
errorCount={errorCount}
onToggleGroup={toggleGroup}
onItemClick={handleItemClick}
onItemHover={handleItemHover}
onItemLeave={handleItemLeave}
/>
</ScrollArea>
</SidebarContent>
</Sidebar>
)
}