@orsetra/shared-ui 1.3.7 → 1.3.8

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.
@@ -3,150 +3,119 @@
3
3
  import { ArrowLeft } from "lucide-react"
4
4
  import Link from "next/link"
5
5
  import type { ReactNode, ElementType } from "react"
6
- import { cn } from "../../lib/utils"
7
- import { Button } from "./button"
6
+
7
+ const TAB_ACTIVE =
8
+ "border-b-2 border-ibm-blue-60 text-ibm-blue-60 bg-ibm-blue-10/60"
9
+ const TAB_INACTIVE =
10
+ "border-b-2 border-transparent text-ibm-gray-60 hover:text-ibm-blue-60 hover:bg-ibm-blue-10/40"
8
11
 
9
12
  export interface DetailPageHeaderTab {
10
13
  id: string
11
14
  label: string
12
15
  icon?: ElementType
13
- /** URL-based navigation */
14
- href?: string
15
- /** State-based or programmatic navigation */
16
- action?: () => void
16
+ href: string
17
17
  }
18
18
 
19
19
  export interface DetailPageHeaderProps {
20
- /** If provided, renders a secondary Back button. Clicks navigate to backHref if set, otherwise history.back() */
21
- backText?: string
22
- /** Optional explicit back destination. If absent, the back button uses history.back() */
23
- backHref?: string
20
+ /** Back breadcrumb link destination */
21
+ backHref: string
22
+ /** Back breadcrumb label */
23
+ backLabel: string
24
24
  /** Optional icon rendered in a blue square container */
25
25
  icon?: ReactNode
26
- /** Page title */
26
+ /** Page title – truncated to one line */
27
27
  title: string
28
- /** Optional subtitle */
28
+ /** Optional subtitle – truncated to one line */
29
29
  description?: string
30
- /** Slot for badges or status nodes placed next to the title */
31
- statusNode?: ReactNode
32
- /** Slot for action buttons placed to the right, after the back button */
30
+ /** Slot for badges, status buttons, action buttons placed to the right */
33
31
  actions?: ReactNode
34
- /** Optional tab navigation */
32
+ /** Optional tab navigation rendered at the bottom of the header */
35
33
  tabBar?: DetailPageHeaderTab[]
36
34
  /** Alignment of the tab bar – defaults to "left" */
37
35
  tabBarPosition?: "left" | "right"
38
36
  /** Currently active tab id */
39
37
  activeTab?: string
40
- /** Extra classes on the root element – use to adjust spacing/height */
41
- className?: string
42
38
  }
43
39
 
44
40
  export function DetailPageHeader({
45
- backText,
46
41
  backHref,
42
+ backLabel,
47
43
  icon,
48
44
  title,
49
45
  description,
50
- statusNode,
51
46
  actions,
52
47
  tabBar,
53
48
  tabBarPosition = "left",
54
49
  activeTab,
55
- className,
56
50
  }: DetailPageHeaderProps) {
57
51
  return (
58
- <div className={cn("mb-3", className)}>
52
+ <div className="bg-white border-b border-ibm-gray-20">
53
+ <div className="px-4 sm:px-6 pt-4 pb-0">
59
54
 
60
- {/* Title row */}
61
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
62
- <div className="flex items-center gap-3 min-w-0">
63
- {icon && (
64
- <div className="h-9 w-9 rounded bg-ibm-blue-10 flex items-center justify-center flex-shrink-0">
65
- {icon}
66
- </div>
67
- )}
68
- <div className="min-w-0">
69
- <div className="flex items-center gap-3">
70
- <h1 className="text-xl sm:text-2xl font-semibold text-text-primary truncate">{title}</h1>
71
- {statusNode && <div className="flex-shrink-0">{statusNode}</div>}
72
- </div>
73
- {description && (
74
- <p className="text-sm text-text-secondary line-clamp-2 mt-0.5">{description}</p>
75
- )}
76
- </div>
77
- </div>
55
+ {/* Breadcrumb */}
56
+ <Link
57
+ href={backHref}
58
+ className="inline-flex items-center gap-1.5 text-xs text-ibm-gray-50 hover:text-ibm-blue-60 transition-colors mb-3"
59
+ >
60
+ <ArrowLeft className="h-3 w-3" />
61
+ {backLabel}
62
+ </Link>
78
63
 
79
- {(backText || actions) && (
80
- <div className="flex items-center gap-2 shrink-0">
81
- {backText && (
82
- backHref ? (
83
- <Link href={backHref}>
84
- <Button
85
- variant="secondary"
86
- className="h-9"
87
- leftIcon={<ArrowLeft className="h-4 w-4" />}
88
- >
89
- {backText}
90
- </Button>
91
- </Link>
92
- ) : (
93
- <Button
94
- variant="secondary"
95
- className="h-9"
96
- leftIcon={<ArrowLeft className="h-4 w-4" />}
97
- onClick={() => window.history.back()}
98
- >
99
- {backText}
100
- </Button>
101
- )
64
+ {/* Title row */}
65
+ <div className="flex items-center justify-between gap-4 pb-4">
66
+ <div className="flex items-center gap-3 min-w-0">
67
+ {icon && (
68
+ <div className="h-9 w-9 rounded bg-ibm-blue-10 flex items-center justify-center flex-shrink-0">
69
+ {icon}
70
+ </div>
102
71
  )}
103
- {actions}
72
+ <div className="min-w-0">
73
+ <h1 className="text-lg font-semibold text-ibm-gray-100 leading-tight truncate">
74
+ {title}
75
+ </h1>
76
+ {description && (
77
+ <p className="text-xs text-ibm-gray-50 mt-0.5 truncate">
78
+ {description}
79
+ </p>
80
+ )}
81
+ </div>
104
82
  </div>
105
- )}
106
- </div>
107
83
 
108
- {/* Tab bar */}
109
- {tabBar && tabBar.length > 0 && (
110
- <div className={cn("mt-3 flex", tabBarPosition === "right" ? "justify-end" : "justify-start")}>
111
- <div className="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
84
+ {actions && (
85
+ <div className="flex items-center gap-2 shrink-0">
86
+ {actions}
87
+ </div>
88
+ )}
89
+ </div>
90
+
91
+ {/* Tab bar */}
92
+ {tabBar && tabBar.length > 0 && (
93
+ <nav
94
+ className={`flex gap-0 overflow-x-auto -mx-4 sm:-mx-6 px-4 sm:px-6 ${
95
+ tabBarPosition === "right" ? "justify-end" : "justify-start"
96
+ }`}
97
+ aria-label="Tabs"
98
+ >
112
99
  {tabBar.map((tab) => {
113
100
  const Icon = tab.icon
114
101
  const isActive = activeTab === tab.id
115
- const triggerClass = cn(
116
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
117
- isActive
118
- ? "bg-white text-ibm-gray-100 shadow-sm"
119
- : "hover:text-ibm-gray-100"
120
- )
121
-
122
- if (tab.action) {
123
- return (
124
- <button
125
- key={tab.id}
126
- type="button"
127
- onClick={tab.action}
128
- className={triggerClass}
129
- >
130
- {Icon && <Icon className="h-4 w-4" />}
131
- {tab.label}
132
- </button>
133
- )
134
- }
135
-
136
102
  return (
137
103
  <Link
138
104
  key={tab.id}
139
- href={tab.href ?? "#"}
140
- className={triggerClass}
105
+ href={tab.href}
106
+ className={`inline-flex items-center gap-2 px-5 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${
107
+ isActive ? TAB_ACTIVE : TAB_INACTIVE
108
+ }`}
141
109
  >
142
110
  {Icon && <Icon className="h-4 w-4" />}
143
111
  {tab.label}
144
112
  </Link>
145
113
  )
146
114
  })}
147
- </div>
148
- </div>
149
- )}
115
+ </nav>
116
+ )}
117
+
118
+ </div>
150
119
  </div>
151
120
  )
152
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orsetra/shared-ui",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "Shared UI components for Orsetra platform",
5
5
  "main": "./index.ts",
6
6
  "types": "./index.ts",