@orsetra/shared-ui 1.1.27 → 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
|
|
|
@@ -79,12 +81,6 @@ export function MainSidebar({
|
|
|
79
81
|
|
|
80
82
|
return (
|
|
81
83
|
<>
|
|
82
|
-
{isOpen && !isMinimized && !hoveredMenu && (
|
|
83
|
-
<div
|
|
84
|
-
className="fixed inset-0 bg-black/50 z-40"
|
|
85
|
-
onClick={onToggle}
|
|
86
|
-
/>
|
|
87
|
-
)}
|
|
88
84
|
|
|
89
85
|
<div className={cn(
|
|
90
86
|
"fixed left-0 top-0 h-full bg-white border-r border-ui-border z-50 transform transition-all duration-300 ease-in-out",
|
|
@@ -124,7 +120,7 @@ export function MainSidebar({
|
|
|
124
120
|
<div
|
|
125
121
|
key={item.id}
|
|
126
122
|
className="relative"
|
|
127
|
-
onMouseEnter={() => hasSubMenu && handleFlyoutMouseEnter(item.id)}
|
|
123
|
+
onMouseEnter={(e) => hasSubMenu && handleFlyoutMouseEnter(item.id, e.currentTarget.getBoundingClientRect())}
|
|
128
124
|
onMouseLeave={() => hasSubMenu && handleFlyoutMouseLeave()}
|
|
129
125
|
>
|
|
130
126
|
<button
|
|
@@ -146,38 +142,43 @@ export function MainSidebar({
|
|
|
146
142
|
)} />
|
|
147
143
|
{!isMinimized && <span className="text-base">{item.label}</span>}
|
|
148
144
|
</button>
|
|
149
|
-
|
|
150
|
-
{hoveredMenu === item.id && hasSubMenu && (
|
|
151
|
-
<div
|
|
152
|
-
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"
|
|
153
|
-
onMouseEnter={() => handleFlyoutMouseEnter(item.id)}
|
|
154
|
-
onMouseLeave={handleFlyoutMouseLeave}
|
|
155
|
-
>
|
|
156
|
-
{/* Arrow pointing left toward hovered item */}
|
|
157
|
-
<div className="absolute -left-[5px] top-[18px] w-[10px] h-[10px] bg-white border-l border-t border-ui-border -rotate-45" />
|
|
158
|
-
<div className="px-4 py-2 border-b border-ui-border">
|
|
159
|
-
<h3 className="text-sm font-semibold text-text-primary">{item.label}</h3>
|
|
160
|
-
</div>
|
|
161
|
-
<div className="py-1">
|
|
162
|
-
{sidebarMenus[item.id].map((subItem) => (
|
|
163
|
-
<a
|
|
164
|
-
key={subItem.id}
|
|
165
|
-
href={subItem.href}
|
|
166
|
-
onClick={(e) => handleSubMenuClick(e, subItem.href)}
|
|
167
|
-
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"
|
|
168
|
-
>
|
|
169
|
-
<subItem.icon className="h-4 w-4" />
|
|
170
|
-
{subItem.name}
|
|
171
|
-
</a>
|
|
172
|
-
))}
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
)}
|
|
176
145
|
</div>
|
|
177
146
|
)
|
|
178
147
|
})}
|
|
179
148
|
</nav>
|
|
180
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
|
+
})()}
|
|
181
182
|
</div>
|
|
182
183
|
</>
|
|
183
184
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orsetra/shared-ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.29",
|
|
4
4
|
"description": "Shared UI components for Orsetra platform",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -106,4 +106,4 @@
|
|
|
106
106
|
"next": "^16.0.7",
|
|
107
107
|
"typescript": "^5"
|
|
108
108
|
}
|
|
109
|
-
}
|
|
109
|
+
}
|