@orsetra/shared-ui 1.2.2 → 1.2.4
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.
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ArrowLeft } from "lucide-react"
|
|
4
|
+
import Link from "next/link"
|
|
5
|
+
import type { ReactNode, ElementType } from "react"
|
|
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"
|
|
11
|
+
|
|
12
|
+
export interface DetailPageHeaderTab {
|
|
13
|
+
id: string
|
|
14
|
+
label: string
|
|
15
|
+
icon?: ElementType
|
|
16
|
+
href: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DetailPageHeaderProps {
|
|
20
|
+
/** Back breadcrumb link destination */
|
|
21
|
+
backHref: string
|
|
22
|
+
/** Back breadcrumb label */
|
|
23
|
+
backLabel: string
|
|
24
|
+
/** Optional icon rendered in a blue square container */
|
|
25
|
+
icon?: ReactNode
|
|
26
|
+
/** Page title – truncated to one line */
|
|
27
|
+
title: string
|
|
28
|
+
/** Optional subtitle – truncated to one line */
|
|
29
|
+
description?: string
|
|
30
|
+
/** Slot for badges, status buttons, action buttons placed to the right */
|
|
31
|
+
actions?: ReactNode
|
|
32
|
+
/** Optional tab navigation rendered at the bottom of the header */
|
|
33
|
+
tabBar?: DetailPageHeaderTab[]
|
|
34
|
+
/** Alignment of the tab bar – defaults to "left" */
|
|
35
|
+
tabBarPosition?: "left" | "right"
|
|
36
|
+
/** Currently active tab id */
|
|
37
|
+
activeTab?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function DetailPageHeader({
|
|
41
|
+
backHref,
|
|
42
|
+
backLabel,
|
|
43
|
+
icon,
|
|
44
|
+
title,
|
|
45
|
+
description,
|
|
46
|
+
actions,
|
|
47
|
+
tabBar,
|
|
48
|
+
tabBarPosition = "left",
|
|
49
|
+
activeTab,
|
|
50
|
+
}: DetailPageHeaderProps) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="bg-white border-b border-ibm-gray-20">
|
|
53
|
+
<div className="px-4 sm:px-6 pt-4 pb-0">
|
|
54
|
+
|
|
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>
|
|
63
|
+
|
|
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>
|
|
71
|
+
)}
|
|
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>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
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
|
+
>
|
|
99
|
+
{tabBar.map((tab) => {
|
|
100
|
+
const Icon = tab.icon
|
|
101
|
+
const isActive = activeTab === tab.id
|
|
102
|
+
return (
|
|
103
|
+
<Link
|
|
104
|
+
key={tab.id}
|
|
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
|
+
}`}
|
|
109
|
+
>
|
|
110
|
+
{Icon && <Icon className="h-4 w-4" />}
|
|
111
|
+
{tab.label}
|
|
112
|
+
</Link>
|
|
113
|
+
)
|
|
114
|
+
})}
|
|
115
|
+
</nav>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
package/components/ui/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { UserMenu, type UserMenuItem, type UserMenuConfig } from './user-menu'
|
|
|
9
9
|
export { Input } from './input'
|
|
10
10
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './tooltip'
|
|
11
11
|
export { PageHeader } from './page-header'
|
|
12
|
+
export { DetailPageHeader, type DetailPageHeaderProps, type DetailPageHeaderTab } from './detail-page-header'
|
|
12
13
|
export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, type ChartConfig } from './chart'
|
|
13
14
|
export { SearchInput } from './search-input'
|
|
14
15
|
export { AssetsHeader } from './assets-header'
|
package/lib/interceptors.ts
CHANGED
|
@@ -14,19 +14,6 @@ export const interceptors: Interceptors = {
|
|
|
14
14
|
case 401:
|
|
15
15
|
window.location.href = '/login';
|
|
16
16
|
break;
|
|
17
|
-
|
|
18
|
-
case 403:
|
|
19
|
-
window.location.href = '/access-denied';
|
|
20
|
-
break;
|
|
21
|
-
|
|
22
|
-
case 404:
|
|
23
|
-
window.location.href = '/not-found';
|
|
24
|
-
break;
|
|
25
|
-
|
|
26
|
-
case 500:
|
|
27
|
-
default:
|
|
28
|
-
window.location.href = '/error';
|
|
29
|
-
break;
|
|
30
17
|
}
|
|
31
18
|
}
|
|
32
19
|
}
|