@orsetra/shared-ui 1.5.1 → 1.5.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.
@@ -106,6 +106,7 @@ function LayoutContent({
106
106
  main_base_url={main_base_url}
107
107
  sectionLabels={sectionLabels}
108
108
  getCurrentMenuItem={getCurrentMenuItem}
109
+ mainMenuItems={mainMenuItems}
109
110
  />
110
111
  )}
111
112
 
@@ -20,7 +20,7 @@ import {
20
20
  } from "../../ui/tooltip"
21
21
  import { Menu } from "lucide-react"
22
22
  import { Logo } from "../../ui/logo"
23
- import { type SidebarMenus, type SubMenuItem } from "./data"
23
+ import { type SidebarMenus, type SubMenuItem, type MainMenuItem } from "./data"
24
24
  import { Skeleton } from "../skeleton"
25
25
 
26
26
  const SIDEBAR_STORAGE_KEY = "sidebar:state"
@@ -179,11 +179,12 @@ interface SidebarProps {
179
179
  main_base_url?: string
180
180
  sectionLabels?: Record<string, string>
181
181
  getCurrentMenuItem?: (pathname: string, searchParams: URLSearchParams) => string
182
+ mainMenuItems?: MainMenuItem[]
182
183
  }
183
184
 
184
185
 
185
186
 
186
- function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_url = "", sectionLabels = {}, getCurrentMenuItem }: SidebarProps = {}) {
187
+ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_url = "", sectionLabels = {}, getCurrentMenuItem, mainMenuItems = [] }: SidebarProps = {}) {
187
188
  const pathname = usePathname()
188
189
  const searchParams = useSearchParams()
189
190
  const { state, toggleSidebar } = useSidebar()
@@ -238,6 +239,37 @@ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_u
238
239
  "flex-1 overflow-y-auto",
239
240
  isCollapsed ? "px-1 py-3 space-y-0.5" : "px-3 py-4 space-y-1"
240
241
  )}>
242
+ {/* Main menu items — only shown in collapsed mode */}
243
+ {isCollapsed && mainMenuItems.length > 0 && (
244
+ <>
245
+ {mainMenuItems.map((item) => {
246
+ const href = `${main_base_url}/${item.id}`
247
+ const isActive = currentMenu === item.id
248
+ const linkEl = (
249
+ <Link
250
+ key={item.id}
251
+ href={href.startsWith('http') ? new URL(href).pathname : href}
252
+ className={cn(
253
+ "flex items-center justify-center p-2 transition-colors border-l-4",
254
+ isActive
255
+ ? "bg-interactive/10 text-interactive border-interactive"
256
+ : "text-text-secondary hover:bg-ui-background hover:text-text-primary border-transparent"
257
+ )}
258
+ >
259
+ <item.icon className={cn("h-4 w-4 flex-shrink-0", isActive ? "text-interactive" : "text-text-secondary")} />
260
+ </Link>
261
+ )
262
+ return (
263
+ <Tooltip key={item.id}>
264
+ <TooltipTrigger asChild>{linkEl}</TooltipTrigger>
265
+ <TooltipContent side="right" align="center">{item.label}</TooltipContent>
266
+ </Tooltip>
267
+ )
268
+ })}
269
+ <div className="border-t border-ui-border mx-1 my-1" />
270
+ </>
271
+ )}
272
+
241
273
  {currentNavigation.map((item) => {
242
274
  const activeItemId = getCurrentMenuItem ? getCurrentMenuItem(pathname, searchParams) : null
243
275
  const isActive = activeItemId
@@ -14,14 +14,17 @@ export interface DetailPageHeaderTab {
14
14
  id: string
15
15
  label: string
16
16
  icon?: ElementType
17
- href: string
17
+ /** Navigate to a URL when the tab is clicked */
18
+ href?: string
19
+ /** Call a callback instead of navigating (for state-based tab switching) */
20
+ action?: () => void
18
21
  }
19
22
 
20
23
  export interface DetailPageHeaderProps {
21
24
  /** Back breadcrumb link destination */
22
- backHref: string
25
+ backHref?: string
23
26
  /** Back breadcrumb label */
24
- backLabel: string
27
+ backLabel?: string
25
28
  /** Optional icon rendered in a blue square container */
26
29
  icon?: ReactNode
27
30
  /** Page title – truncated to one line */
@@ -57,13 +60,15 @@ export function DetailPageHeader({
57
60
  <div className="px-4 sm:px-2 pt-1 pb-0">
58
61
 
59
62
  {/* Breadcrumb */}
60
- <Link
61
- href={backHref}
62
- className="inline-flex items-center gap-1.5 text-xs text-ibm-gray-50 hover:text-ibm-blue-60 transition-colors mb-3"
63
- >
64
- <ArrowLeft className="h-3 w-3" />
65
- {backLabel}
66
- </Link>
63
+ {backHref && backLabel && (
64
+ <Link
65
+ href={backHref}
66
+ className="inline-flex items-center gap-1.5 text-xs text-ibm-gray-50 hover:text-ibm-blue-60 transition-colors mb-3"
67
+ >
68
+ <ArrowLeft className="h-3 w-3" />
69
+ {backLabel}
70
+ </Link>
71
+ )}
67
72
 
68
73
  {/* Title row */}
69
74
  <div className="flex items-center justify-between gap-4 pb-4">
@@ -102,16 +107,20 @@ export function DetailPageHeader({
102
107
  {tabBar?.map((tab) => {
103
108
  const Icon = tab.icon
104
109
  const isActive = activeTab === tab.id
110
+ const tabClassName = `inline-flex items-center gap-2 px-5 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${
111
+ isActive ? TAB_ACTIVE : TAB_INACTIVE
112
+ }`
113
+ const content = <>{Icon && <Icon className="h-4 w-4" />}{tab.label}</>
114
+ if (tab.action) {
115
+ return (
116
+ <button key={tab.id} type="button" onClick={tab.action} className={tabClassName}>
117
+ {content}
118
+ </button>
119
+ )
120
+ }
105
121
  return (
106
- <Link
107
- key={tab.id}
108
- href={tab.href}
109
- className={`inline-flex items-center gap-2 px-5 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${
110
- isActive ? TAB_ACTIVE : TAB_INACTIVE
111
- }`}
112
- >
113
- {Icon && <Icon className="h-4 w-4" />}
114
- {tab.label}
122
+ <Link key={tab.id} href={tab.href!} className={tabClassName}>
123
+ {content}
115
124
  </Link>
116
125
  )
117
126
  })}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orsetra/shared-ui",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Shared UI components for Orsetra platform",
5
5
  "main": "./index.ts",
6
6
  "types": "./index.ts",