@orsetra/shared-ui 1.5.26 → 1.5.28
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.
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import Link from "next/link"
|
|
5
|
+
import { type SubMenuItem } from "./data"
|
|
6
|
+
|
|
7
|
+
interface SidebarFlyoutProps {
|
|
8
|
+
label: string
|
|
9
|
+
subItems: SubMenuItem[]
|
|
10
|
+
onClose: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function SidebarFlyout({ label, subItems, onClose }: SidebarFlyoutProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className="absolute left-full top-0 ml-1 w-52 bg-white shadow-lg border border-ui-border z-50"
|
|
17
|
+
onMouseEnter={(e) => e.stopPropagation()}
|
|
18
|
+
onMouseLeave={onClose}
|
|
19
|
+
>
|
|
20
|
+
<div className="px-3 py-2 text-[11px] font-semibold text-text-secondary uppercase tracking-wide border-b border-ui-border">
|
|
21
|
+
{label}
|
|
22
|
+
</div>
|
|
23
|
+
{subItems.map((subItem) => (
|
|
24
|
+
<Link
|
|
25
|
+
key={subItem.id}
|
|
26
|
+
href={subItem.href}
|
|
27
|
+
onClick={(e) => {
|
|
28
|
+
if (subItem.href.startsWith("http://") || subItem.href.startsWith("https://")) {
|
|
29
|
+
e.preventDefault()
|
|
30
|
+
window.location.href = new URL(subItem.href).pathname
|
|
31
|
+
}
|
|
32
|
+
onClose()
|
|
33
|
+
}}
|
|
34
|
+
className="flex items-center gap-2.5 px-3 py-2.5 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors"
|
|
35
|
+
>
|
|
36
|
+
<subItem.icon className="h-4 w-4 flex-shrink-0" />
|
|
37
|
+
{subItem.name}
|
|
38
|
+
</Link>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -72,21 +72,19 @@ const SidebarProvider = React.forwardRef<
|
|
|
72
72
|
const isMobile = useIsMobile()
|
|
73
73
|
const [openMobile, setOpenMobile] = React.useState(false)
|
|
74
74
|
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
// Read from localStorage synchronously on first client render to avoid flash.
|
|
76
|
+
// Falls back to defaultOpen during SSR (typeof window === 'undefined').
|
|
77
|
+
const [_open, _setOpen] = React.useState(() => {
|
|
78
|
+
if (typeof window !== "undefined") {
|
|
79
|
+
try {
|
|
80
|
+
const stored = localStorage.getItem(SIDEBAR_STORAGE_KEY)
|
|
81
|
+
if (stored !== null) return stored === "true"
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
return defaultOpen
|
|
85
|
+
})
|
|
79
86
|
const open = openProp ?? _open
|
|
80
87
|
|
|
81
|
-
// Hydrate from localStorage on first client render (avoids SSR mismatch).
|
|
82
|
-
React.useEffect(() => {
|
|
83
|
-
try {
|
|
84
|
-
const stored = localStorage.getItem(SIDEBAR_STORAGE_KEY)
|
|
85
|
-
if (stored !== null) _setOpen(stored === "true")
|
|
86
|
-
} catch {}
|
|
87
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
88
|
-
}, [])
|
|
89
|
-
|
|
90
88
|
const setOpen = React.useCallback(
|
|
91
89
|
(value: boolean | ((value: boolean) => boolean)) => {
|
|
92
90
|
const openState = typeof value === "function" ? value(open) : value
|
|
@@ -155,6 +153,7 @@ const SidebarProvider = React.forwardRef<
|
|
|
155
153
|
...style,
|
|
156
154
|
} as React.CSSProperties
|
|
157
155
|
}
|
|
156
|
+
suppressHydrationWarning
|
|
158
157
|
className={cn(
|
|
159
158
|
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
|
|
160
159
|
className
|
|
@@ -194,10 +193,8 @@ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_u
|
|
|
194
193
|
|| []
|
|
195
194
|
|
|
196
195
|
const isCollapsed = state === "collapsed"
|
|
197
|
-
|
|
198
196
|
const filteredMainItems = React.useMemo(() => {
|
|
199
197
|
if (!isCollapsed || mainMenuItems.length === 0) return []
|
|
200
|
-
// Exclude items that match the current menu context OR appear in the secondary nav OR have no href
|
|
201
198
|
const navIds = new Set(currentNavigation.map((n) => n.id))
|
|
202
199
|
return mainMenuItems.filter((item) => item.id !== currentMenu && !navIds.has(item.id) && !!item.href)
|
|
203
200
|
}, [isCollapsed, mainMenuItems, currentNavigation, currentMenu])
|
|
@@ -301,22 +298,20 @@ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_u
|
|
|
301
298
|
<div className="border-t border-ui-border mx-1 my-2" />
|
|
302
299
|
{filteredMainItems.map((item) => {
|
|
303
300
|
const handleClick = (e: React.MouseEvent) => {
|
|
304
|
-
const href = item.href
|
|
305
|
-
if (href.startsWith(
|
|
301
|
+
const href = item.href ?? ""
|
|
302
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
306
303
|
e.preventDefault()
|
|
307
304
|
window.location.href = new URL(href).pathname
|
|
308
305
|
}
|
|
309
|
-
|
|
306
|
+
}
|
|
310
307
|
const linkEl = (
|
|
311
308
|
<Link
|
|
312
309
|
key={item.id}
|
|
313
|
-
href={
|
|
310
|
+
href={item.href ?? "#"}
|
|
311
|
+
onClick={handleClick}
|
|
314
312
|
className="flex items-center justify-center p-2 transition-colors border-l-4 border-transparent hover:bg-ui-background"
|
|
315
313
|
>
|
|
316
|
-
<item.icon
|
|
317
|
-
className="h-4 w-4 flex-shrink-0"
|
|
318
|
-
style={{ color: '#8d8d8d' }}
|
|
319
|
-
/>
|
|
314
|
+
<item.icon className="h-4 w-4 flex-shrink-0" style={{ color: "#8d8d8d" }} />
|
|
320
315
|
</Link>
|
|
321
316
|
)
|
|
322
317
|
return (
|