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