@orsetra/shared-ui 1.5.0 → 1.5.2
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.
|
@@ -34,41 +34,23 @@ export function MainSidebar({
|
|
|
34
34
|
mainMenuItems = [],
|
|
35
35
|
}: MainSidebarProps) {
|
|
36
36
|
const isMinimized = mode === 'minimized'
|
|
37
|
-
const [hoveredMenu, setHoveredMenu] = React.useState<string | null>(null)
|
|
38
|
-
const [flyoutPos, setFlyoutPos] = React.useState<{ top: number; left: number } | null>(null)
|
|
39
37
|
const [expandedMenu, setExpandedMenu] = React.useState<string | null>(null)
|
|
40
|
-
const closeTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
return () => { if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current) }
|
|
44
|
-
}, [])
|
|
45
|
-
|
|
46
|
-
// Reset expanded menu when sidebar closes
|
|
39
|
+
// Reset accordion when sidebar closes
|
|
47
40
|
React.useEffect(() => {
|
|
48
41
|
if (!isOpen) setExpandedMenu(null)
|
|
49
42
|
}, [isOpen])
|
|
50
43
|
|
|
51
|
-
const handleFlyoutMouseEnter = (menuId: string, rect?: DOMRect) => {
|
|
52
|
-
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current)
|
|
53
|
-
if (rect) setFlyoutPos({ top: rect.top, left: rect.right + 8 })
|
|
54
|
-
setHoveredMenu(menuId)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const handleFlyoutMouseLeave = () => {
|
|
58
|
-
closeTimeoutRef.current = setTimeout(() => setHoveredMenu(null), 150)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
44
|
const handleMenuClick = (menuId: string) => {
|
|
62
45
|
const hasSubMenu = (sidebarMenus[menuId]?.length ?? 0) > 0
|
|
63
46
|
|
|
64
|
-
//
|
|
65
|
-
if (!isMinimized &&
|
|
47
|
+
// Accordion: expand/collapse inline for any non-minimized mode
|
|
48
|
+
if (!isMinimized && hasSubMenu) {
|
|
66
49
|
setExpandedMenu((prev) => (prev === menuId ? null : menuId))
|
|
67
50
|
return
|
|
68
51
|
}
|
|
69
52
|
|
|
70
53
|
onMenuSelect(menuId)
|
|
71
|
-
|
|
72
54
|
if (!isMinimized && onSecondarySidebarOpen) {
|
|
73
55
|
onSecondarySidebarOpen()
|
|
74
56
|
}
|
|
@@ -98,7 +80,7 @@ export function MainSidebar({
|
|
|
98
80
|
const handleSubMenuClick = (e: React.MouseEvent, menuId: string, href: string) => {
|
|
99
81
|
e.preventDefault()
|
|
100
82
|
window.location.href = buildSubItemHref(menuId, href)
|
|
101
|
-
|
|
83
|
+
setExpandedMenu(null)
|
|
102
84
|
}
|
|
103
85
|
|
|
104
86
|
return (
|
|
@@ -111,14 +93,14 @@ export function MainSidebar({
|
|
|
111
93
|
)}
|
|
112
94
|
|
|
113
95
|
<div className={cn(
|
|
114
|
-
"fixed left-0 top-0 h-full bg-white border-r border-ui-border z-50 transform transition-all duration-300 ease-in-out",
|
|
96
|
+
"fixed left-0 top-0 h-full bg-white border-r border-ui-border z-50 transform transition-all duration-300 ease-in-out overflow-y-auto",
|
|
115
97
|
isMinimized ? "w-16" : "w-64 shadow-xl",
|
|
116
98
|
isMinimized
|
|
117
99
|
? "translate-x-0"
|
|
118
100
|
: (isOpen ? "translate-x-0" : "-translate-x-full")
|
|
119
101
|
)}>
|
|
120
102
|
<div className={cn(
|
|
121
|
-
"flex items-center h-16 border-b border-ui-border bg-white",
|
|
103
|
+
"flex items-center h-16 border-b border-ui-border bg-white sticky top-0 z-10",
|
|
122
104
|
isMinimized ? "justify-center px-2" : "justify-between px-4"
|
|
123
105
|
)}>
|
|
124
106
|
{!isMinimized && <Logo className="text-2xl font-semibold text-text-primary" href={main_base_url} />}
|
|
@@ -136,99 +118,62 @@ export function MainSidebar({
|
|
|
136
118
|
)}
|
|
137
119
|
</div>
|
|
138
120
|
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
121
|
+
<nav className={cn("space-y-1", isMinimized ? "p-2" : "p-3")}>
|
|
122
|
+
{mainMenuItems.map((item) => {
|
|
123
|
+
const Icon = item.icon
|
|
124
|
+
const hasSubMenu = (sidebarMenus[item.id]?.length ?? 0) > 0
|
|
125
|
+
const isActive = currentMenu === item.id
|
|
126
|
+
const isExpanded = expandedMenu === item.id
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div key={item.id}>
|
|
130
|
+
<button
|
|
131
|
+
onClick={() => handleMenuClick(item.id)}
|
|
132
|
+
className={cn(
|
|
133
|
+
"w-full flex items-center text-left transition-all duration-150 font-medium",
|
|
134
|
+
isMinimized ? "justify-center p-3" : "gap-3 px-3 py-2",
|
|
135
|
+
isActive
|
|
136
|
+
? "bg-interactive/10 text-interactive border-l-4 border-interactive"
|
|
137
|
+
: "text-text-primary hover:bg-ui-background border-l-4 border-transparent"
|
|
138
|
+
)}
|
|
139
|
+
title={isMinimized ? item.label : undefined}
|
|
154
140
|
>
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
isActive ? "text-interactive" : "text-text-secondary"
|
|
169
|
-
)} />
|
|
170
|
-
{!isMinimized && <span className="text-base flex-1">{item.label}</span>}
|
|
171
|
-
{!isMinimized && hasSubMenu && isOpen && (
|
|
172
|
-
expandedMenu === item.id
|
|
173
|
-
? <ChevronDown className="h-4 w-4 text-text-secondary flex-shrink-0" />
|
|
174
|
-
: <ChevronRight className="h-4 w-4 text-text-secondary flex-shrink-0" />
|
|
175
|
-
)}
|
|
176
|
-
</button>
|
|
177
|
-
|
|
178
|
-
{/* Inline accordion sub-items — shown on mobile (overlay open, not minimized) */}
|
|
179
|
-
{!isMinimized && isOpen && expandedMenu === item.id && hasSubMenu && (
|
|
180
|
-
<div className="pl-4 pb-1">
|
|
181
|
-
{sidebarMenus[item.id].map((subItem) => (
|
|
182
|
-
<a
|
|
183
|
-
key={subItem.id}
|
|
184
|
-
href={buildSubItemHref(item.id, subItem.href)}
|
|
185
|
-
onClick={(e) => handleSubMenuClick(e, item.id, subItem.href)}
|
|
186
|
-
className="flex items-center gap-3 px-3 py-2 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors no-underline border-l-2 border-ui-border hover:border-interactive"
|
|
187
|
-
>
|
|
188
|
-
<subItem.icon className="h-4 w-4 flex-shrink-0 text-text-secondary" />
|
|
189
|
-
{subItem.name}
|
|
190
|
-
</a>
|
|
191
|
-
))}
|
|
192
|
-
</div>
|
|
141
|
+
<Icon className={cn(
|
|
142
|
+
"h-5 w-5 flex-shrink-0",
|
|
143
|
+
isActive ? "text-interactive" : "text-text-secondary"
|
|
144
|
+
)} />
|
|
145
|
+
{!isMinimized && (
|
|
146
|
+
<>
|
|
147
|
+
<span className="text-base flex-1">{item.label}</span>
|
|
148
|
+
{hasSubMenu && (
|
|
149
|
+
isExpanded
|
|
150
|
+
? <ChevronDown className="h-4 w-4 text-text-secondary flex-shrink-0" />
|
|
151
|
+
: <ChevronRight className="h-4 w-4 text-text-secondary flex-shrink-0" />
|
|
152
|
+
)}
|
|
153
|
+
</>
|
|
193
154
|
)}
|
|
194
|
-
</
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<div className="absolute -left-[5px] top-[14px] w-[10px] h-[10px] bg-white border-l border-t border-ui-border -rotate-45" />
|
|
213
|
-
<div className="px-4 py-2 border-b border-ui-border">
|
|
214
|
-
<p className="text-xs font-semibold text-text-primary uppercase tracking-wide">{activeItem.label}</p>
|
|
215
|
-
</div>
|
|
216
|
-
<div>
|
|
217
|
-
{sidebarMenus[hoveredMenu].map((subItem) => (
|
|
218
|
-
<a
|
|
219
|
-
key={subItem.id}
|
|
220
|
-
href={buildSubItemHref(hoveredMenu, subItem.href)}
|
|
221
|
-
onClick={(e) => handleSubMenuClick(e, hoveredMenu, subItem.href)}
|
|
222
|
-
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 border-l-4 border-transparent hover:border-interactive"
|
|
223
|
-
>
|
|
224
|
-
<subItem.icon className="h-4 w-4 flex-shrink-0 text-text-secondary" />
|
|
225
|
-
{subItem.name}
|
|
226
|
-
</a>
|
|
227
|
-
))}
|
|
155
|
+
</button>
|
|
156
|
+
|
|
157
|
+
{/* Inline accordion sub-items */}
|
|
158
|
+
{!isMinimized && isExpanded && hasSubMenu && (
|
|
159
|
+
<div className="pl-4 pb-1">
|
|
160
|
+
{sidebarMenus[item.id].map((subItem) => (
|
|
161
|
+
<a
|
|
162
|
+
key={subItem.id}
|
|
163
|
+
href={buildSubItemHref(item.id, subItem.href)}
|
|
164
|
+
onClick={(e) => handleSubMenuClick(e, item.id, subItem.href)}
|
|
165
|
+
className="flex items-center gap-3 px-3 py-2 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors no-underline border-l-2 border-ui-border hover:border-interactive"
|
|
166
|
+
>
|
|
167
|
+
<subItem.icon className="h-4 w-4 flex-shrink-0 text-text-secondary" />
|
|
168
|
+
{subItem.name}
|
|
169
|
+
</a>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
228
173
|
</div>
|
|
229
|
-
|
|
230
|
-
)
|
|
231
|
-
|
|
174
|
+
)
|
|
175
|
+
})}
|
|
176
|
+
</nav>
|
|
232
177
|
</div>
|
|
233
178
|
</>
|
|
234
179
|
)
|
|
@@ -19,9 +19,9 @@ export interface DetailPageHeaderTab {
|
|
|
19
19
|
|
|
20
20
|
export interface DetailPageHeaderProps {
|
|
21
21
|
/** Back breadcrumb link destination */
|
|
22
|
-
backHref
|
|
22
|
+
backHref?: string
|
|
23
23
|
/** Back breadcrumb label */
|
|
24
|
-
backLabel
|
|
24
|
+
backLabel?: string
|
|
25
25
|
/** Optional icon rendered in a blue square container */
|
|
26
26
|
icon?: ReactNode
|
|
27
27
|
/** Page title – truncated to one line */
|
|
@@ -57,13 +57,15 @@ export function DetailPageHeader({
|
|
|
57
57
|
<div className="px-4 sm:px-2 pt-1 pb-0">
|
|
58
58
|
|
|
59
59
|
{/* Breadcrumb */}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
{backHref && backLabel && (
|
|
61
|
+
<Link
|
|
62
|
+
href={backHref}
|
|
63
|
+
className="inline-flex items-center gap-1.5 text-xs text-ibm-gray-50 hover:text-ibm-blue-60 transition-colors mb-3"
|
|
64
|
+
>
|
|
65
|
+
<ArrowLeft className="h-3 w-3" />
|
|
66
|
+
{backLabel}
|
|
67
|
+
</Link>
|
|
68
|
+
)}
|
|
67
69
|
|
|
68
70
|
{/* Title row */}
|
|
69
71
|
<div className="flex items-center justify-between gap-4 pb-4">
|