@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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
|
|
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
|
|
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=
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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={
|
|
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
|
-
</
|
|
148
|
-
|
|
149
|
-
|
|
115
|
+
</nav>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
</div>
|
|
150
119
|
</div>
|
|
151
120
|
)
|
|
152
121
|
}
|