@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
- // This is the internal state of the sidebar.
76
- // We use openProp and setOpenProp for control from outside the component.
77
- // Initialize from defaultOpen (SSR-safe); sync from localStorage after mount.
78
- const [_open, _setOpen] = React.useState(defaultOpen)
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('http://') || href.startsWith('https://')) {
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={ item.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 (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orsetra/shared-ui",
3
- "version": "1.5.26",
3
+ "version": "1.5.28",
4
4
  "description": "Shared UI components for Orsetra platform",
5
5
  "main": "./index.ts",
6
6
  "types": "./index.ts",