@orsetra/shared-ui 1.8.5 → 1.9.1

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.
@@ -6,6 +6,7 @@ import { MainSidebar, type SidebarMode } from "./sidebar/main-sidebar"
6
6
  import { Sidebar, SidebarProvider, useSidebar } from "./sidebar/sidebar"
7
7
  import { type SidebarMenus, type MainMenuItem, resolveIcon } from "./sidebar/data"
8
8
  import { Button } from "../ui"
9
+ import { Logo } from "../ui/logo"
9
10
  import { getMenuFromPath } from "../../lib/menu-utils"
10
11
  import { useIsMobile } from "../../hooks/use-mobile"
11
12
  import { Menu } from "lucide-react"
@@ -100,51 +101,54 @@ function LayoutContent({
100
101
  const handleSecondarySidebarOpen = () => setOpen(true)
101
102
 
102
103
  return (
103
- <div className="flex h-screen w-full bg-white overflow-hidden">
104
- {!isMinimized && !isHidden && (
105
- <Sidebar
104
+ <div className="flex flex-col h-screen w-full bg-white overflow-hidden">
105
+ <header className="h-14 bg-gray-50 border-b border-ui-border flex-shrink-0">
106
+ <div className="h-full flex items-center justify-between">
107
+ <div
108
+ className="h-full flex items-center justify-between flex-shrink-0 px-4 md:px-6"
109
+ style={{ width: 'var(--sidebar-width)' }}
110
+ >
111
+ <Logo href={main_base_url} />
112
+ <Button
113
+ variant="ghost"
114
+ size="sm"
115
+ onClick={handleMainSidebarToggle}
116
+ className="h-8 w-8 p-0 hover:bg-ui-background text-text-secondary"
117
+ title="Menu principal"
118
+ >
119
+ <Menu className="h-5 w-5" />
120
+ </Button>
121
+ </div>
122
+ <div className="flex-1 flex items-center justify-end px-4 md:px-6">
123
+ <UserPanel main_base_url={main_base_url} />
124
+ </div>
125
+ </div>
126
+ </header>
127
+
128
+ <div className="flex flex-1 min-h-0 overflow-hidden">
129
+ {!isMinimized && !isHidden && (
130
+ <Sidebar
131
+ currentMenu={currentMenu}
132
+ onMainMenuToggle={handleMainSidebarToggle}
133
+ sidebarMenus={effectiveSidebarMenus}
134
+ main_base_url={main_base_url}
135
+ sectionLabels={sectionLabels}
136
+ getCurrentMenuItem={getCurrentMenuItem}
137
+ mainMenuItems={mainMenuItems}
138
+ />
139
+ )}
140
+
141
+ <MainSidebar
142
+ main_base_url={main_base_url}
143
+ isOpen={isMainSidebarOpen}
144
+ onToggle={handleMainSidebarToggle}
145
+ onMenuSelect={handleMenuSelect}
106
146
  currentMenu={currentMenu}
107
- onMainMenuToggle={handleMainSidebarToggle}
147
+ onSecondarySidebarOpen={handleSecondarySidebarOpen}
148
+ mode={isHidden ? "expanded" : mode}
108
149
  sidebarMenus={effectiveSidebarMenus}
109
- main_base_url={main_base_url}
110
- sectionLabels={sectionLabels}
111
- getCurrentMenuItem={getCurrentMenuItem}
112
150
  mainMenuItems={mainMenuItems}
113
151
  />
114
- )}
115
-
116
- <MainSidebar
117
- main_base_url={main_base_url}
118
- isOpen={isMainSidebarOpen}
119
- onToggle={handleMainSidebarToggle}
120
- onMenuSelect={handleMenuSelect}
121
- currentMenu={currentMenu}
122
- onSecondarySidebarOpen={handleSecondarySidebarOpen}
123
- mode={isHidden ? "expanded" : mode}
124
- sidebarMenus={effectiveSidebarMenus}
125
- mainMenuItems={mainMenuItems}
126
- />
127
-
128
- <div className="flex-1 flex flex-col min-w-0">
129
- <header className="h-14 bg-gray-50 border-b border-ui-border flex-shrink-0">
130
- <div className="h-full px-4 md:px-6 flex items-center justify-between">
131
- {isHidden ? (
132
- <Button
133
- variant="ghost"
134
- size="sm"
135
- onClick={handleMainSidebarToggle}
136
- className="h-8 w-8 p-0 hover:bg-ui-background text-text-secondary"
137
- title="Ouvrir le menu"
138
- >
139
- <Menu className="h-5 w-5" />
140
- </Button>
141
- ) : (
142
- <div />
143
- )}
144
-
145
- <UserPanel main_base_url={main_base_url} />
146
- </div>
147
- </header>
148
152
 
149
153
  <main className="flex-1 bg-ibm-gray-10 min-h-0 overflow-auto">
150
154
  {children}
@@ -3,13 +3,24 @@
3
3
  import * as React from "react"
4
4
  import { cn } from "../../../lib/utils"
5
5
  import type { MainMenuItem, SubMenuItem } from "./data"
6
+ import { Home, ExternalLink, type LucideIcon } from "lucide-react"
7
+
8
+ export interface FlyoutDefaultLink {
9
+ label: string
10
+ href: string
11
+ description?: string
12
+ icon?: LucideIcon
13
+ }
6
14
 
7
15
  interface MainSidebarFlyoutProps {
8
- item: MainMenuItem
9
- /** Sub-items with hrefs already resolved by the parent */
10
- subItems: SubMenuItem[]
11
- onMouseEnter: () => void
12
- onMouseLeave: () => void
16
+ item: MainMenuItem | null
17
+ subItems: SubMenuItem[]
18
+ onMouseEnter: () => void
19
+ onMouseLeave: () => void
20
+ main_base_url?: string
21
+ defaultTitle?: string
22
+ defaultDescription?: string
23
+ defaultLinks?: FlyoutDefaultLink[]
13
24
  }
14
25
 
15
26
  export function MainSidebarFlyout({
@@ -17,8 +28,21 @@ export function MainSidebarFlyout({
17
28
  subItems,
18
29
  onMouseEnter,
19
30
  onMouseLeave,
31
+ main_base_url = "/",
32
+ defaultTitle = "Camel X Platform",
33
+ defaultDescription,
34
+ defaultLinks = [],
20
35
  }: MainSidebarFlyoutProps) {
21
- const Icon = item.icon
36
+ const homePath = React.useMemo(() => {
37
+ if (!main_base_url) return "/"
38
+ try {
39
+ return (main_base_url.startsWith("http://") || main_base_url.startsWith("https://"))
40
+ ? new URL(main_base_url).pathname
41
+ : main_base_url
42
+ } catch {
43
+ return "/"
44
+ }
45
+ }, [main_base_url])
22
46
 
23
47
  return (
24
48
  <div
@@ -26,46 +50,107 @@ export function MainSidebarFlyout({
26
50
  onMouseLeave={onMouseLeave}
27
51
  className="h-full flex flex-col flex-shrink-0 overflow-hidden bg-gray-50 border-r border-ui-border shadow-[4px_0_20px_-4px_rgba(0,0,0,0.10)] w-72 lg:w-80"
28
52
  >
29
- {/* ── Section header ───────────────────────────────────────────────────── */}
30
- <div className="flex items-center px-4 lg:px-5 pt-5 pb-1 flex-shrink-0 gap-2.5">
31
- <Icon className="h-5 w-5 flex-shrink-0" style={{ color: "#4589ff" }} />
32
- <h2 className="text-sm font-semibold text-text-primary tracking-wide truncate flex-1">
33
- {item.label}
34
- </h2>
35
- </div>
53
+ {item ? (
54
+ /* ── Hovered item: sub-menu list ─────────────────────────────────── */
55
+ <>
56
+ <div className="flex items-center px-4 lg:px-5 pt-5 pb-1 flex-shrink-0 gap-2.5">
57
+ <item.icon className="h-5 w-5 flex-shrink-0" style={{ color: "#4589ff" }} />
58
+ <h2 className="text-sm font-semibold text-text-primary tracking-wide truncate flex-1">
59
+ {item.label}
60
+ </h2>
61
+ </div>
36
62
 
37
- {/* ── Section description ───────────────────────────────────────────────── */}
38
- {item.description && (
39
- <p className="px-4 lg:px-5 pt-1 pb-4 text-xs text-text-secondary leading-relaxed">
40
- {item.description}
41
- </p>
42
- )}
63
+ {item.description && (
64
+ <p className="px-4 lg:px-5 pt-1 pb-4 text-xs text-text-secondary leading-relaxed">
65
+ {item.description}
66
+ </p>
67
+ )}
68
+
69
+ <nav className="flex-1 overflow-y-auto px-2 lg:px-3 py-2 space-y-0.5">
70
+ {subItems.map((sub) => {
71
+ const SubIcon = sub.icon
72
+ return (
73
+ <a
74
+ key={sub.id}
75
+ href={sub.href}
76
+ className={cn(
77
+ "flex items-start transition-colors border-l-4",
78
+ "px-2 lg:px-3 py-2 gap-x-3",
79
+ "hover:bg-ui-background border-transparent hover:border-interactive",
80
+ )}
81
+ >
82
+ <SubIcon className="h-4 w-4 flex-shrink-0 mt-0.5" style={{ color: "#4589ff" }} />
83
+ <div className="min-w-0 flex-1">
84
+ <p className="text-sm font-medium text-text-primary leading-snug truncate">{sub.name}</p>
85
+ {sub.description && (
86
+ <p className="text-xs text-text-secondary leading-relaxed mt-0.5 line-clamp-2">{sub.description}</p>
87
+ )}
88
+ </div>
89
+ </a>
90
+ )
91
+ })}
92
+ </nav>
93
+ </>
94
+ ) : (
95
+ /* ── Default state: welcome + CTA + doc links ───────────────────── */
96
+ <div className="flex flex-col h-full">
97
+ {/* Header */}
98
+ <div className="px-5 pt-6 pb-5 border-b border-ui-border flex-shrink-0">
99
+ <div className="flex items-center gap-2.5 mb-3">
100
+ <div className="h-8 w-8 rounded-md bg-interactive flex items-center justify-center flex-shrink-0">
101
+ <Home className="h-4 w-4 text-white" />
102
+ </div>
103
+ <span className="text-sm font-semibold text-text-primary truncate">{defaultTitle}</span>
104
+ </div>
105
+
106
+ {defaultDescription && (
107
+ <p className="text-xs text-text-secondary leading-relaxed mb-4">
108
+ {defaultDescription}
109
+ </p>
110
+ )}
43
111
 
44
- {/* ── Nav items ────────────────────────────────────────────────────────── */}
45
- <nav className="flex-1 overflow-y-auto px-2 lg:px-3 py-2 space-y-0.5">
46
- {subItems.map((sub) => {
47
- const SubIcon = sub.icon
48
- return (
49
112
  <a
50
- key={sub.id}
51
- href={sub.href}
52
- className={cn(
53
- "flex items-start transition-colors border-l-4",
54
- "px-2 lg:px-3 py-2 gap-x-3",
55
- "hover:bg-ui-background border-transparent hover:border-interactive",
56
- )}
113
+ href={homePath}
114
+ className="flex items-center justify-center gap-2 w-full px-3 py-2 bg-interactive hover:bg-interactive-hover text-white text-sm font-medium transition-colors"
57
115
  >
58
- <SubIcon className="h-4 w-4 flex-shrink-0 mt-0.5" style={{ color: "#4589ff" }} />
59
- <div className="min-w-0 flex-1">
60
- <p className="text-sm font-medium text-text-primary leading-snug truncate">{sub.name}</p>
61
- {sub.description && (
62
- <p className="text-xs text-text-secondary leading-relaxed mt-0.5 line-clamp-2">{sub.description}</p>
63
- )}
64
- </div>
116
+ <Home className="h-4 w-4 flex-shrink-0" />
117
+ Tableau de bord
65
118
  </a>
66
- )
67
- })}
68
- </nav>
119
+ </div>
120
+
121
+ {/* Doc / resource links */}
122
+ {defaultLinks.length > 0 && (
123
+ <nav className="flex-1 overflow-y-auto px-2 lg:px-3 py-3 space-y-0.5">
124
+ <p className="px-2 pb-1 text-[10px] font-semibold text-text-secondary uppercase tracking-widest">
125
+ Ressources
126
+ </p>
127
+ {defaultLinks.map((link, i) => {
128
+ const LinkIcon = link.icon ?? ExternalLink
129
+ const isExternal = link.href.startsWith("http://") || link.href.startsWith("https://")
130
+ return (
131
+ <a
132
+ key={i}
133
+ href={link.href}
134
+ {...(isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {})}
135
+ className="flex items-start gap-x-3 px-2 lg:px-3 py-2 border-l-4 border-transparent hover:bg-ui-background hover:border-interactive transition-colors"
136
+ >
137
+ <LinkIcon className="h-4 w-4 flex-shrink-0 mt-0.5 text-text-secondary" />
138
+ <div className="min-w-0 flex-1">
139
+ <p className="text-sm font-medium text-text-primary leading-snug flex items-center gap-1.5">
140
+ {link.label}
141
+ {isExternal && <ExternalLink className="h-3 w-3 text-text-secondary flex-shrink-0" />}
142
+ </p>
143
+ {link.description && (
144
+ <p className="text-xs text-text-secondary leading-relaxed mt-0.5 line-clamp-2">{link.description}</p>
145
+ )}
146
+ </div>
147
+ </a>
148
+ )
149
+ })}
150
+ </nav>
151
+ )}
152
+ </div>
153
+ )}
69
154
  </div>
70
155
  )
71
156
  }
@@ -7,7 +7,9 @@ import { Logo } from "../../ui/logo"
7
7
  import { type MainMenuItem, type SidebarMenus } from "./data"
8
8
  import { X, Menu, ChevronDown, ChevronRight } from "lucide-react"
9
9
  import { useIsMobile } from "../../../hooks/use-mobile"
10
- import { MainSidebarFlyout } from "./main-sidebar-flyout"
10
+ import { MainSidebarFlyout, type FlyoutDefaultLink } from "./main-sidebar-flyout"
11
+
12
+ export type { FlyoutDefaultLink }
11
13
 
12
14
  export type SidebarMode = 'expanded' | 'minimized' | 'hidden'
13
15
 
@@ -21,6 +23,9 @@ interface MainSidebarProps {
21
23
  onSecondarySidebarOpen?: () => void
22
24
  sidebarMenus?: SidebarMenus
23
25
  mainMenuItems?: MainMenuItem[]
26
+ flyoutDefaultTitle?: string
27
+ flyoutDefaultDescription?: string
28
+ flyoutDefaultLinks?: FlyoutDefaultLink[]
24
29
  }
25
30
 
26
31
  export function MainSidebar({
@@ -33,6 +38,9 @@ export function MainSidebar({
33
38
  onSecondarySidebarOpen,
34
39
  sidebarMenus = {},
35
40
  mainMenuItems = [],
41
+ flyoutDefaultTitle,
42
+ flyoutDefaultDescription,
43
+ flyoutDefaultLinks = [],
36
44
  }: MainSidebarProps) {
37
45
  const isMinimized = mode === 'minimized'
38
46
  const isMobile = useIsMobile()
@@ -66,6 +74,40 @@ export function MainSidebar({
66
74
  setHoveredItem(item)
67
75
  }
68
76
 
77
+ const applyTemplateVars = (href: string): string => {
78
+ // Guard: SSR has no window/localStorage
79
+ if (typeof window === "undefined") return href
80
+ try {
81
+ const projRaw = localStorage.getItem("current-business-unit")
82
+ const envRaw = localStorage.getItem("current-environment")
83
+ // Parse carefully: value may be null (not yet set), invalid JSON, or a non-object
84
+ const currentProject = typeof projRaw === "string"
85
+ ? (JSON.parse(projRaw)?.state?.currentProject ?? "")
86
+ : ""
87
+ const currentEnv = typeof envRaw === "string"
88
+ ? (JSON.parse(envRaw)?.state?.currentEnv ?? "")
89
+ : ""
90
+ // Only replace when the value is a non-empty string; if empty, leave the
91
+ // placeholder in the URL so the link is visibly broken rather than silently
92
+ // producing a protocol-relative URL (e.g. "//path") that navigates incorrectly.
93
+ let result = href
94
+ if (currentProject && typeof currentProject === "string") {
95
+ result = result.replace(/\$\{currentProject\}/g, currentProject)
96
+ }
97
+ if (currentEnv && typeof currentEnv === "string") {
98
+ result = result.replace(/\$\{currentEnv\}/g, currentEnv)
99
+ }
100
+ return result
101
+ } catch {
102
+ // localStorage blocked (Safari private mode), JSON parse error, etc. — return unchanged
103
+ return href
104
+ }
105
+ }
106
+
107
+ const safeUrlPathname = (url: string): string => {
108
+ try { return new URL(url).pathname } catch { return "/" }
109
+ }
110
+
69
111
  const handleMenuClick = (item: MainMenuItem) => {
70
112
  const menuId = item.id
71
113
  const hasSubMenu = (sidebarMenus[menuId]?.length ?? 0) > 0
@@ -81,24 +123,34 @@ export function MainSidebar({
81
123
  if (!isMinimized && onSecondarySidebarOpen) onSecondarySidebarOpen()
82
124
 
83
125
  if (item.href) {
84
- const path = item.href.startsWith('http://') || item.href.startsWith('https://')
85
- ? new URL(item.href).pathname
86
- : item.href
87
- window.location.href = path
126
+ try {
127
+ const resolved = applyTemplateVars(item.href)
128
+ const path = resolved.startsWith('http://') || resolved.startsWith('https://')
129
+ ? safeUrlPathname(resolved)
130
+ : resolved
131
+ window.location.href = path
132
+ } catch {
133
+ // navigation error — don't crash the sidebar
134
+ }
88
135
  }
89
136
 
90
137
  if (!isMinimized) onToggle()
91
138
  }
92
139
 
93
140
  const buildSubItemHref = (menuId: string, href: string): string => {
94
- if (href.startsWith('http://') || href.startsWith('https://')) {
95
- return new URL(href).pathname
141
+ try {
142
+ const resolved = applyTemplateVars(href)
143
+ if (resolved.startsWith('http://') || resolved.startsWith('https://')) {
144
+ return safeUrlPathname(resolved)
145
+ }
146
+ const cleanHref = resolved.replace(/^\//, '')
147
+ const cleanBase = (main_base_url.startsWith('http://') || main_base_url.startsWith('https://'))
148
+ ? safeUrlPathname(main_base_url).replace(/\/$/, '')
149
+ : (main_base_url ?? "").replace(/\/$/, '')
150
+ return `${cleanBase}/${menuId}/${cleanHref}`
151
+ } catch {
152
+ return "/"
96
153
  }
97
- const cleanHref = href.replace(/^\//, '')
98
- const cleanBase = (main_base_url.startsWith('http://') || main_base_url.startsWith('https://'))
99
- ? new URL(main_base_url).pathname.replace(/\/$/, '')
100
- : main_base_url.replace(/\/$/, '')
101
- return `${cleanBase}/${menuId}/${cleanHref}`
102
154
  }
103
155
 
104
156
  const handleSubMenuClick = (e: React.MouseEvent, menuId: string, href: string) => {
@@ -119,7 +171,8 @@ export function MainSidebar({
119
171
  The flyout is never "in front of" the nav; it's always to its right.
120
172
  */}
121
173
  <div className={cn(
122
- "fixed left-0 top-0 h-full z-50 flex",
174
+ "fixed left-0 z-50 flex",
175
+ isMinimized ? "top-14 h-[calc(100%-3.5rem)]" : "top-0 h-full",
123
176
  "transform transition-all duration-300 ease-in-out",
124
177
  isMinimized ? "translate-x-0" : (isOpen ? "translate-x-0" : "-translate-x-full"),
125
178
  )}>
@@ -131,27 +184,20 @@ export function MainSidebar({
131
184
  // Shadow only when flyout is not open (avoid double shadow)
132
185
  !hoveredItem && !isMinimized && "shadow-xl",
133
186
  )}>
134
- {/* Header */}
135
- <div className={cn(
136
- "flex items-center h-14 bg-white sticky top-0 z-10 flex-shrink-0",
137
- isMinimized ? "justify-center px-2" : "justify-between px-3 lg:px-4",
138
- )}>
139
- {isMinimized ? (
140
- <Logo className="text-xl font-semibold text-text-primary" iconOnly href={main_base_url} />
141
- ) : (
142
- <>
143
- <Logo className="text-2xl font-semibold text-text-primary" href={main_base_url} />
144
- <Button
145
- variant="ghost"
146
- size="sm"
147
- onClick={onToggle}
148
- className="h-8 w-8 p-0 hover:bg-ui-background text-text-secondary"
149
- >
150
- <X className="h-4 w-4" />
151
- </Button>
152
- </>
153
- )}
154
- </div>
187
+ {/* Header — only in overlay (non-minimized) mode; logo lives in the global header */}
188
+ {!isMinimized && (
189
+ <div className="flex items-center justify-between h-14 bg-white sticky top-0 z-10 flex-shrink-0 px-3 lg:px-4">
190
+ <Logo className="text-2xl font-semibold text-text-primary" href={main_base_url} />
191
+ <Button
192
+ variant="ghost"
193
+ size="sm"
194
+ onClick={onToggle}
195
+ className="h-8 w-8 p-0 hover:bg-ui-background text-text-secondary"
196
+ >
197
+ <X className="h-4 w-4" />
198
+ </Button>
199
+ </div>
200
+ )}
155
201
 
156
202
  {/* Nav items */}
157
203
  <nav className={cn("flex-1 space-y-0.5 lg:space-y-1", isMinimized ? "p-2" : "p-2 lg:p-3")}>
@@ -163,9 +209,14 @@ export function MainSidebar({
163
209
  const isFlyActive = hoveredItem?.id === item.id
164
210
 
165
211
  const itemHref = !hasSubMenu && item.href
166
- ? (item.href.startsWith('http://') || item.href.startsWith('https://')
167
- ? new URL(item.href).pathname
168
- : item.href)
212
+ ? (() => {
213
+ try {
214
+ const resolved = applyTemplateVars(item.href)
215
+ return resolved.startsWith('http://') || resolved.startsWith('https://')
216
+ ? safeUrlPathname(resolved)
217
+ : resolved
218
+ } catch { return item.href }
219
+ })()
169
220
  : undefined
170
221
 
171
222
  const itemCls = cn(
@@ -237,15 +288,23 @@ export function MainSidebar({
237
288
  </div>
238
289
 
239
290
  {/* ── Flyout — desktop only, flex child to the right of nav ─────────── */}
240
- {!isMobile && hoveredItem && (
291
+ {/* Shown when the sidebar is open (default state) or an item is hovered (sub-menu state) */}
292
+ {!isMobile && (hoveredItem || (isOpen && !isMinimized)) && (
241
293
  <MainSidebarFlyout
242
294
  item={hoveredItem}
243
- subItems={(sidebarMenus[hoveredItem.id] ?? []).map(sub => ({
244
- ...sub,
245
- href: buildSubItemHref(hoveredItem.id, sub.href),
246
- }))}
295
+ subItems={hoveredItem
296
+ ? (sidebarMenus[hoveredItem.id] ?? []).map(sub => ({
297
+ ...sub,
298
+ href: buildSubItemHref(hoveredItem.id, sub.href),
299
+ }))
300
+ : []
301
+ }
247
302
  onMouseEnter={cancelCloseTimer}
248
303
  onMouseLeave={startCloseTimer}
304
+ main_base_url={main_base_url}
305
+ defaultTitle={flyoutDefaultTitle}
306
+ defaultDescription={flyoutDefaultDescription}
307
+ defaultLinks={flyoutDefaultLinks}
249
308
  />
250
309
  )}
251
310
 
@@ -18,8 +18,6 @@ import {
18
18
  TooltipProvider,
19
19
  TooltipTrigger,
20
20
  } from "../../ui/tooltip"
21
- import { Menu } from "lucide-react"
22
- import { Logo } from "../../ui/logo"
23
21
  import { type SidebarMenus, type SubMenuItem, type MainMenuItem } from "./data"
24
22
  import { Skeleton } from "../skeleton"
25
23
 
@@ -232,7 +230,7 @@ interface SidebarProps {
232
230
 
233
231
 
234
232
 
235
- function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_url = "", sectionLabels = {}, getCurrentMenuItem, mainMenuItems = [] }: SidebarProps = {}) {
233
+ function Sidebar({ currentMenu, sidebarMenus = {}, main_base_url = "", sectionLabels = {}, getCurrentMenuItem, mainMenuItems = [] }: SidebarProps = {}) {
236
234
  const pathname = usePathname()
237
235
  const searchParams = useSearchParams()
238
236
  const router = useRouter()
@@ -259,36 +257,9 @@ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_u
259
257
 
260
258
  return (
261
259
  <div
262
- className="h-screen sticky top-0 flex flex-col bg-gray-50 min-w-[var(--sidebar-width-icon)] transition-[width] duration-200 overflow-visible"
260
+ className="h-full flex-shrink-0 flex flex-col bg-gray-50 min-w-[var(--sidebar-width-icon)] transition-[width] duration-200 overflow-visible"
263
261
  style={{ width: isCollapsed ? "var(--sidebar-width-icon)" : "var(--sidebar-width)" }}
264
262
  >
265
- {/* Header */}
266
- <div className={cn(
267
- "h-14 flex items-center flex-shrink-0",
268
- isCollapsed ? "justify-center px-2" : "justify-between px-4"
269
- )}>
270
- {isCollapsed ? (
271
- <button type="button" onClick={toggleSidebar} title="Expand sidebar" className="flex items-center justify-center">
272
- <Logo iconOnly />
273
- </button>
274
- ) : (
275
- <>
276
- <Logo />
277
- {onMainMenuToggle && (
278
- <Button
279
- variant="ghost"
280
- size="sm"
281
- onClick={onMainMenuToggle}
282
- className="h-8 w-8 p-0 hover:bg-ui-background rounded-lg"
283
- title="Ouvrir le menu principal"
284
- >
285
- <Menu className="h-4 w-4" />
286
- </Button>
287
- )}
288
- </>
289
- )}
290
- </div>
291
-
292
263
  {/* Section label — hidden when collapsed */}
293
264
  {!isCollapsed && (
294
265
  injectedItems === undefined ? (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orsetra/shared-ui",
3
- "version": "1.8.5",
3
+ "version": "1.9.1",
4
4
  "description": "Shared UI components for Orsetra platform",
5
5
  "main": "./index.ts",
6
6
  "types": "./index.ts",