@orsetra/shared-ui 1.10.2 → 1.10.3
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.
- package/components/layout/layout-container.tsx +1 -0
- package/components/layout/sidebar/data.tsx +2 -0
- package/components/layout/sidebar/main-sidebar-flyout.tsx +41 -2
- package/components/layout/sidebar/main-sidebar.tsx +14 -4
- package/components/layout/sidebar/sidebar.tsx +48 -13
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ export interface SubMenuItem {
|
|
|
54
54
|
href: string
|
|
55
55
|
icon: LucideIcon
|
|
56
56
|
applied?: boolean
|
|
57
|
+
target?: string
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export interface SidebarMenus {
|
|
@@ -86,6 +87,7 @@ export interface ApiSubMenuItem {
|
|
|
86
87
|
description?: string
|
|
87
88
|
href: string
|
|
88
89
|
icon: string
|
|
90
|
+
target?: string
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
export interface ApiSubMenu {
|
|
@@ -79,6 +79,8 @@ export function MainSidebarFlyout({
|
|
|
79
79
|
<a
|
|
80
80
|
key={sub.id}
|
|
81
81
|
href={sub.href}
|
|
82
|
+
target={sub.target}
|
|
83
|
+
rel={sub.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
82
84
|
className={cn(
|
|
83
85
|
"flex items-start transition-colors border-l-4",
|
|
84
86
|
"px-2 lg:px-3 py-2 gap-x-3",
|
|
@@ -99,10 +101,10 @@ export function MainSidebarFlyout({
|
|
|
99
101
|
</>
|
|
100
102
|
) : hasQuickAccess ? (
|
|
101
103
|
/* ── Quick access: pages recently visited ────────────────────────── */
|
|
102
|
-
<div className="flex flex-col h-full
|
|
104
|
+
<div className="flex flex-col h-full px-6 py-5 gap-5 overflow-y-auto">
|
|
103
105
|
<div className="w-full">
|
|
104
106
|
<p className="px-2 pb-1 text-[10px] font-semibold text-text-secondary uppercase tracking-widest">
|
|
105
|
-
|
|
107
|
+
Quick Access
|
|
106
108
|
</p>
|
|
107
109
|
<nav className="space-y-0.5">
|
|
108
110
|
{quickAccessItems.map((qa) => {
|
|
@@ -126,6 +128,43 @@ export function MainSidebarFlyout({
|
|
|
126
128
|
})}
|
|
127
129
|
</nav>
|
|
128
130
|
</div>
|
|
131
|
+
|
|
132
|
+
{defaultLinks.length > 0 && (
|
|
133
|
+
<div className="w-full border-t border-ui-border pt-4">
|
|
134
|
+
<p className="px-2 pb-1 text-[10px] font-semibold text-text-secondary uppercase tracking-widest">
|
|
135
|
+
Resources
|
|
136
|
+
</p>
|
|
137
|
+
<nav className="space-y-0.5">
|
|
138
|
+
{defaultLinks.map((link, i) => {
|
|
139
|
+
const isExternal = link.href.startsWith("http://") || link.href.startsWith("https://")
|
|
140
|
+
const LinkIcon: LucideIcon = !link.icon
|
|
141
|
+
? ExternalLink
|
|
142
|
+
: typeof link.icon === "string"
|
|
143
|
+
? resolveIcon(link.icon)
|
|
144
|
+
: link.icon
|
|
145
|
+
return (
|
|
146
|
+
<a
|
|
147
|
+
key={i}
|
|
148
|
+
href={link.href}
|
|
149
|
+
{...(isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
|
150
|
+
className="flex items-start gap-x-3 px-2 py-2 border-l-4 border-transparent hover:bg-ui-background hover:border-interactive transition-colors"
|
|
151
|
+
>
|
|
152
|
+
<LinkIcon className="h-4 w-4 flex-shrink-0 mt-0.5 text-text-secondary" />
|
|
153
|
+
<div className="min-w-0 flex-1">
|
|
154
|
+
<p className="text-sm font-medium text-text-primary leading-snug flex items-center gap-1.5">
|
|
155
|
+
{link.label}
|
|
156
|
+
{isExternal && <ExternalLink className="h-3 w-3 text-text-secondary flex-shrink-0" />}
|
|
157
|
+
</p>
|
|
158
|
+
{link.description && (
|
|
159
|
+
<p className="text-xs text-text-secondary leading-relaxed mt-0.5 line-clamp-2">{link.description}</p>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
</a>
|
|
163
|
+
)
|
|
164
|
+
})}
|
|
165
|
+
</nav>
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
129
168
|
</div>
|
|
130
169
|
) : (
|
|
131
170
|
/* ── Default state: centré verticalement ────────────────────────── */
|
|
@@ -142,7 +142,7 @@ export function MainSidebar({
|
|
|
142
142
|
try {
|
|
143
143
|
const resolved = applyTemplateVars(href)
|
|
144
144
|
if (resolved.startsWith('http://') || resolved.startsWith('https://')) {
|
|
145
|
-
return
|
|
145
|
+
return resolved // keep full URL for external links
|
|
146
146
|
}
|
|
147
147
|
const cleanHref = resolved.replace(/^\//, '')
|
|
148
148
|
const cleanBase = (main_base_url.startsWith('http://') || main_base_url.startsWith('https://'))
|
|
@@ -165,9 +165,17 @@ export function MainSidebar({
|
|
|
165
165
|
icon: l.icon,
|
|
166
166
|
}))
|
|
167
167
|
|
|
168
|
-
const handleSubMenuClick = (e: React.MouseEvent, menuId: string, href: string) => {
|
|
168
|
+
const handleSubMenuClick = (e: React.MouseEvent, menuId: string, href: string, target?: string) => {
|
|
169
169
|
e.preventDefault()
|
|
170
|
-
|
|
170
|
+
const resolved = buildSubItemHref(menuId, href)
|
|
171
|
+
if (target === "_blank") {
|
|
172
|
+
window.open(resolved, "_blank", "noopener,noreferrer")
|
|
173
|
+
} else {
|
|
174
|
+
const path = resolved.startsWith('http://') || resolved.startsWith('https://')
|
|
175
|
+
? safeUrlPathname(resolved)
|
|
176
|
+
: resolved
|
|
177
|
+
window.location.href = path
|
|
178
|
+
}
|
|
171
179
|
setExpandedMenu(null)
|
|
172
180
|
}
|
|
173
181
|
|
|
@@ -284,7 +292,9 @@ export function MainSidebar({
|
|
|
284
292
|
<a
|
|
285
293
|
key={subItem.id}
|
|
286
294
|
href={buildSubItemHref(item.id, subItem.href)}
|
|
287
|
-
|
|
295
|
+
target={subItem.target}
|
|
296
|
+
rel={subItem.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
297
|
+
onClick={(e) => handleSubMenuClick(e, item.id, subItem.href, subItem.target)}
|
|
288
298
|
className="flex items-center gap-2 lg:gap-3 px-2 lg:px-3 py-1.5 lg: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"
|
|
289
299
|
>
|
|
290
300
|
<subItem.icon className="h-4 w-4 flex-shrink-0 text-text-secondary" />
|
|
@@ -325,25 +325,40 @@ function Sidebar({ currentMenu, sidebarMenus = {}, main_base_url = "", sectionLa
|
|
|
325
325
|
|
|
326
326
|
const handleClick = (e: React.MouseEvent) => {
|
|
327
327
|
const href = item.href
|
|
328
|
+
if (item.target === "_blank") {
|
|
329
|
+
e.preventDefault()
|
|
330
|
+
try {
|
|
331
|
+
let resolved = href
|
|
332
|
+
if (typeof window !== "undefined") {
|
|
333
|
+
const projRaw = localStorage.getItem("current-business-unit")
|
|
334
|
+
const cp = typeof projRaw === "string" ? (JSON.parse(projRaw)?.state?.currentProject ?? "") : ""
|
|
335
|
+
if (cp) resolved = resolved.replace(/\$\{currentProject\}/g, cp)
|
|
336
|
+
const envRaw = localStorage.getItem("current-environment")
|
|
337
|
+
const ce = typeof envRaw === "string" ? (JSON.parse(envRaw)?.state?.currentEnv ?? "") : ""
|
|
338
|
+
if (ce) resolved = resolved.replace(/\$\{currentEnv\}/g, ce)
|
|
339
|
+
}
|
|
340
|
+
window.open(resolved, "_blank", "noopener,noreferrer")
|
|
341
|
+
} catch {
|
|
342
|
+
window.open(href, "_blank", "noopener,noreferrer")
|
|
343
|
+
}
|
|
344
|
+
return
|
|
345
|
+
}
|
|
328
346
|
if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
329
347
|
e.preventDefault()
|
|
330
348
|
window.location.href = new URL(href).pathname
|
|
331
349
|
}
|
|
332
350
|
}
|
|
333
351
|
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
: "text-text-secondary hover:bg-ui-background hover:text-text-primary border-transparent"
|
|
345
|
-
)}
|
|
346
|
-
>
|
|
352
|
+
const linkCls = cn(
|
|
353
|
+
"flex items-center transition-colors border-l-4",
|
|
354
|
+
isCollapsed ? "justify-center p-2" : "px-2 lg:px-3 py-1.5 lg:py-2 gap-x-3 text-sm",
|
|
355
|
+
isActive
|
|
356
|
+
? "bg-interactive/10 text-interactive font-medium border-interactive"
|
|
357
|
+
: "text-text-secondary hover:bg-ui-background hover:text-text-primary border-transparent"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
const linkContent = (
|
|
361
|
+
<>
|
|
347
362
|
<item.icon
|
|
348
363
|
className="h-4 w-4 flex-shrink-0"
|
|
349
364
|
style={{ color: isActive ? '#0f62fe' : '#4589ff' }}
|
|
@@ -351,6 +366,26 @@ function Sidebar({ currentMenu, sidebarMenus = {}, main_base_url = "", sectionLa
|
|
|
351
366
|
{!isCollapsed && (
|
|
352
367
|
<span className="flex-1 truncate">{item.name}</span>
|
|
353
368
|
)}
|
|
369
|
+
</>
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
const linkEl = item.target === "_blank" ? (
|
|
373
|
+
<a
|
|
374
|
+
key={item.id}
|
|
375
|
+
href={item.href}
|
|
376
|
+
onClick={handleClick}
|
|
377
|
+
className={linkCls}
|
|
378
|
+
>
|
|
379
|
+
{linkContent}
|
|
380
|
+
</a>
|
|
381
|
+
) : (
|
|
382
|
+
<Link
|
|
383
|
+
key={item.id}
|
|
384
|
+
href={item.href}
|
|
385
|
+
onClick={handleClick}
|
|
386
|
+
className={linkCls}
|
|
387
|
+
>
|
|
388
|
+
{linkContent}
|
|
354
389
|
</Link>
|
|
355
390
|
)
|
|
356
391
|
|