@orsetra/shared-ui 1.1.28 → 1.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -35,14 +35,16 @@ export function MainSidebar({
|
|
|
35
35
|
}: MainSidebarProps) {
|
|
36
36
|
const isMinimized = mode === 'minimized'
|
|
37
37
|
const [hoveredMenu, setHoveredMenu] = React.useState<string | null>(null)
|
|
38
|
+
const [flyoutPos, setFlyoutPos] = React.useState<{ top: number; left: number } | null>(null)
|
|
38
39
|
const closeTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
39
40
|
|
|
40
41
|
React.useEffect(() => {
|
|
41
42
|
return () => { if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current) }
|
|
42
43
|
}, [])
|
|
43
44
|
|
|
44
|
-
const handleFlyoutMouseEnter = (menuId: string) => {
|
|
45
|
+
const handleFlyoutMouseEnter = (menuId: string, rect?: DOMRect) => {
|
|
45
46
|
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current)
|
|
47
|
+
if (rect) setFlyoutPos({ top: rect.top, left: rect.right + 8 })
|
|
46
48
|
setHoveredMenu(menuId)
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -118,7 +120,7 @@ export function MainSidebar({
|
|
|
118
120
|
<div
|
|
119
121
|
key={item.id}
|
|
120
122
|
className="relative"
|
|
121
|
-
onMouseEnter={() => hasSubMenu && handleFlyoutMouseEnter(item.id)}
|
|
123
|
+
onMouseEnter={(e) => hasSubMenu && handleFlyoutMouseEnter(item.id, e.currentTarget.getBoundingClientRect())}
|
|
122
124
|
onMouseLeave={() => hasSubMenu && handleFlyoutMouseLeave()}
|
|
123
125
|
>
|
|
124
126
|
<button
|
|
@@ -140,38 +142,43 @@ export function MainSidebar({
|
|
|
140
142
|
)} />
|
|
141
143
|
{!isMinimized && <span className="text-base">{item.label}</span>}
|
|
142
144
|
</button>
|
|
143
|
-
|
|
144
|
-
{hoveredMenu === item.id && hasSubMenu && (
|
|
145
|
-
<div
|
|
146
|
-
className="absolute left-full top-0 ml-2 bg-white border border-ui-border rounded-lg shadow-xl z-50 min-w-[200px] py-2"
|
|
147
|
-
onMouseEnter={() => handleFlyoutMouseEnter(item.id)}
|
|
148
|
-
onMouseLeave={handleFlyoutMouseLeave}
|
|
149
|
-
>
|
|
150
|
-
{/* Arrow pointing left toward hovered item */}
|
|
151
|
-
<div className="absolute -left-[5px] top-[18px] w-[10px] h-[10px] bg-white border-l border-t border-ui-border -rotate-45" />
|
|
152
|
-
<div className="px-4 py-2 border-b border-ui-border">
|
|
153
|
-
<h3 className="text-sm font-semibold text-text-primary">{item.label}</h3>
|
|
154
|
-
</div>
|
|
155
|
-
<div className="py-1">
|
|
156
|
-
{sidebarMenus[item.id].map((subItem) => (
|
|
157
|
-
<a
|
|
158
|
-
key={subItem.id}
|
|
159
|
-
href={subItem.href}
|
|
160
|
-
onClick={(e) => handleSubMenuClick(e, subItem.href)}
|
|
161
|
-
className="flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors no-underline"
|
|
162
|
-
>
|
|
163
|
-
<subItem.icon className="h-4 w-4" />
|
|
164
|
-
{subItem.name}
|
|
165
|
-
</a>
|
|
166
|
-
))}
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
)}
|
|
170
145
|
</div>
|
|
171
146
|
)
|
|
172
147
|
})}
|
|
173
148
|
</nav>
|
|
174
149
|
</div>
|
|
150
|
+
|
|
151
|
+
{/* Flyout en position fixed : échappe overflow-y-auto et tout contexte d'empilement */}
|
|
152
|
+
{hoveredMenu && flyoutPos && sidebarMenus[hoveredMenu]?.length > 0 && (() => {
|
|
153
|
+
const activeItem = mainMenuItems.find(i => i.id === hoveredMenu)
|
|
154
|
+
if (!activeItem) return null
|
|
155
|
+
return (
|
|
156
|
+
<div
|
|
157
|
+
className="fixed bg-white border border-ui-border rounded-lg shadow-xl z-[9999] min-w-[200px] py-2"
|
|
158
|
+
style={{ top: flyoutPos.top, left: flyoutPos.left }}
|
|
159
|
+
onMouseEnter={() => handleFlyoutMouseEnter(hoveredMenu)}
|
|
160
|
+
onMouseLeave={handleFlyoutMouseLeave}
|
|
161
|
+
>
|
|
162
|
+
<div className="absolute -left-[5px] top-[18px] w-[10px] h-[10px] bg-white border-l border-t border-ui-border -rotate-45" />
|
|
163
|
+
<div className="px-4 py-2 border-b border-ui-border">
|
|
164
|
+
<h3 className="text-sm font-semibold text-text-primary">{activeItem.label}</h3>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="py-1">
|
|
167
|
+
{sidebarMenus[hoveredMenu].map((subItem) => (
|
|
168
|
+
<a
|
|
169
|
+
key={subItem.id}
|
|
170
|
+
href={subItem.href}
|
|
171
|
+
onClick={(e) => handleSubMenuClick(e, subItem.href)}
|
|
172
|
+
className="flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors no-underline"
|
|
173
|
+
>
|
|
174
|
+
<subItem.icon className="h-4 w-4" />
|
|
175
|
+
{subItem.name}
|
|
176
|
+
</a>
|
|
177
|
+
))}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
})()}
|
|
175
182
|
</div>
|
|
176
183
|
</>
|
|
177
184
|
)
|