@orsetra/shared-ui 1.0.54 → 1.0.61
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.
|
@@ -30,3 +30,4 @@ export {
|
|
|
30
30
|
// Skeleton is exported from ../ui to avoid duplicate exports
|
|
31
31
|
export { RootLayoutWrapper, ibmPlexSans, ibmPlexMono } from './root-layout-wrapper'
|
|
32
32
|
export { LayoutContainer } from './layout-container'
|
|
33
|
+
export { PageWithSidePanel } from './page-with-side-panel'
|
|
@@ -82,7 +82,7 @@ function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expand
|
|
|
82
82
|
/>
|
|
83
83
|
|
|
84
84
|
<div className="flex-1 flex flex-col min-w-0">
|
|
85
|
-
<header className="h-14 bg-
|
|
85
|
+
<header className="h-14 bg-gray-50 flex-shrink-0">
|
|
86
86
|
<div className="h-full px-4 md:px-6 flex items-center justify-between">
|
|
87
87
|
{/* Menu button - shown when sidebar is hidden (including on mobile) */}
|
|
88
88
|
{isHidden ? (
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useState } from "react"
|
|
4
|
+
import { X, ChevronLeft } from "lucide-react"
|
|
5
|
+
import { Button } from "../ui/button"
|
|
6
|
+
|
|
7
|
+
interface PageWithSidePanelProps {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
sidePanel?: ReactNode
|
|
10
|
+
sidePanelHeader?: ReactNode
|
|
11
|
+
sidePanelWidth?: "sm" | "md" | "lg"
|
|
12
|
+
closable?: boolean
|
|
13
|
+
defaultOpen?: boolean
|
|
14
|
+
showBorder?: boolean
|
|
15
|
+
onClose?: () => void
|
|
16
|
+
onOpen?: () => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PANEL_WIDTHS = {
|
|
20
|
+
sm: "w-64", // 256px
|
|
21
|
+
md: "w-72", // 288px
|
|
22
|
+
lg: "w-80", // 320px
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PANEL_PADDING = {
|
|
26
|
+
sm: "lg:pr-64",
|
|
27
|
+
md: "lg:pr-72",
|
|
28
|
+
lg: "lg:pr-80",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function PageWithSidePanel({
|
|
32
|
+
children,
|
|
33
|
+
sidePanel,
|
|
34
|
+
sidePanelHeader,
|
|
35
|
+
sidePanelWidth = "md",
|
|
36
|
+
closable = false,
|
|
37
|
+
defaultOpen = true,
|
|
38
|
+
showBorder = true,
|
|
39
|
+
onClose,
|
|
40
|
+
onOpen
|
|
41
|
+
}: PageWithSidePanelProps) {
|
|
42
|
+
const [isOpen, setIsOpen] = useState(defaultOpen)
|
|
43
|
+
const panelWidthClass = PANEL_WIDTHS[sidePanelWidth]
|
|
44
|
+
const panelPaddingClass = PANEL_PADDING[sidePanelWidth]
|
|
45
|
+
|
|
46
|
+
const handleClose = () => {
|
|
47
|
+
setIsOpen(false)
|
|
48
|
+
onClose?.()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const handleOpen = () => {
|
|
52
|
+
setIsOpen(true)
|
|
53
|
+
onOpen?.()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="min-h-screen bg-white overflow-hidden">
|
|
58
|
+
{/* Main content */}
|
|
59
|
+
<div className={`w-full px-6 py-8 transition-all duration-300 ${sidePanel && isOpen ? panelPaddingClass : ''}`}>
|
|
60
|
+
{children}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{/* Side panel - fixed */}
|
|
64
|
+
{sidePanel && (
|
|
65
|
+
<>
|
|
66
|
+
<div
|
|
67
|
+
className={`hidden lg:block ${panelWidthClass} flex-shrink-0 fixed top-14 bottom-0 right-0 bg-white flex flex-col ${showBorder ? 'border-l border-ibm-gray-40' : ''} transition-transform duration-300 ease-in-out ${
|
|
68
|
+
isOpen ? 'translate-x-0' : 'translate-x-full'
|
|
69
|
+
}`}
|
|
70
|
+
>
|
|
71
|
+
{/* Header */}
|
|
72
|
+
{(sidePanelHeader || closable) && (
|
|
73
|
+
<div className="flex items-center justify-between p-4 border-b border-ibm-gray-20 flex-shrink-0">
|
|
74
|
+
<div className="flex-1">
|
|
75
|
+
{sidePanelHeader}
|
|
76
|
+
</div>
|
|
77
|
+
{closable && (
|
|
78
|
+
<Button
|
|
79
|
+
variant="ghost"
|
|
80
|
+
size="xs"
|
|
81
|
+
onClick={handleClose}
|
|
82
|
+
className="h-8 w-8 p-0 ml-2"
|
|
83
|
+
>
|
|
84
|
+
<X className="h-4 w-4" />
|
|
85
|
+
</Button>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{/* Content */}
|
|
91
|
+
<div className="flex-1 overflow-y-auto">
|
|
92
|
+
{sidePanel}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{/* Toggle button - shown when panel is closed */}
|
|
97
|
+
{closable && !isOpen && (
|
|
98
|
+
<button
|
|
99
|
+
onClick={handleOpen}
|
|
100
|
+
className="hidden lg:flex fixed top-1/2 right-0 -translate-y-1/2 items-center justify-center w-8 h-16 bg-white border border-ibm-gray-40 border-r-0 rounded-l-lg shadow-md hover:bg-ibm-gray-10 transition-colors duration-200"
|
|
101
|
+
aria-label="Open side panel"
|
|
102
|
+
>
|
|
103
|
+
<ChevronLeft className="h-5 w-5 text-ibm-gray-70" />
|
|
104
|
+
</button>
|
|
105
|
+
)}
|
|
106
|
+
</>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
@@ -66,12 +66,9 @@ export function MainSidebar({
|
|
|
66
66
|
|
|
67
67
|
const handleSubMenuClick = (e: React.MouseEvent, href: string) => {
|
|
68
68
|
e.preventDefault()
|
|
69
|
-
console.log('handleSubMenuClick - href:', href)
|
|
70
69
|
const path = href.startsWith('http://') || href.startsWith('https://')
|
|
71
70
|
? new URL(href).pathname
|
|
72
71
|
: href
|
|
73
|
-
console.log('handleSubMenuClick - path:', path)
|
|
74
|
-
console.log('Redirecting to:', path)
|
|
75
72
|
window.location.href = path
|
|
76
73
|
setHoveredMenu(null)
|
|
77
74
|
}
|
|
@@ -86,14 +83,14 @@ export function MainSidebar({
|
|
|
86
83
|
)}
|
|
87
84
|
|
|
88
85
|
<div className={cn(
|
|
89
|
-
"fixed left-0 top-0 h-full bg-
|
|
86
|
+
"fixed left-0 top-0 h-full bg-gray-50 z-50 transform transition-all duration-300 ease-in-out",
|
|
90
87
|
isMinimized ? "w-16" : "w-64 shadow-xl",
|
|
91
88
|
isMinimized
|
|
92
89
|
? "translate-x-0"
|
|
93
90
|
: (isOpen ? "translate-x-0" : "-translate-x-full")
|
|
94
91
|
)}>
|
|
95
92
|
<div className={cn(
|
|
96
|
-
"flex items-center h-16
|
|
93
|
+
"flex items-center h-16 bg-gray-50",
|
|
97
94
|
isMinimized ? "justify-center px-2" : "justify-between px-4"
|
|
98
95
|
)}>
|
|
99
96
|
{!isMinimized && <Logo className="text-2xl font-semibold text-text-primary" />}
|
|
@@ -148,8 +145,8 @@ export function MainSidebar({
|
|
|
148
145
|
</button>
|
|
149
146
|
|
|
150
147
|
{isMinimized && hoveredMenu === item.id && sidebarMenus[item.id] && sidebarMenus[item.id].length > 0 && (
|
|
151
|
-
<div className="absolute left-full top-0 ml-2 bg-
|
|
152
|
-
<div className="px-4 py-2
|
|
148
|
+
<div className="absolute left-full top-0 ml-2 bg-gray-50 rounded-lg shadow-xl z-50 min-w-[200px] py-2">
|
|
149
|
+
<div className="px-4 py-2 bg-gray-50">
|
|
153
150
|
<h3 className="text-sm font-semibold text-text-primary">{item.label}</h3>
|
|
154
151
|
</div>
|
|
155
152
|
<div className="py-1">
|
|
@@ -204,7 +204,7 @@ function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {}, main_base_u
|
|
|
204
204
|
|| []
|
|
205
205
|
|
|
206
206
|
return (
|
|
207
|
-
<div className="h-screen sticky top-0 flex flex-col bg-
|
|
207
|
+
<div className="h-screen sticky top-0 flex flex-col bg-gray-50 min-w-[var(--sidebar-width-icon)] transition-[width] duration-200"
|
|
208
208
|
style={{ width: state === "expanded" ? "var(--sidebar-width)" : "var(--sidebar-width-icon)" }}>
|
|
209
209
|
{/* Logo avec bouton de menu principal */}
|
|
210
210
|
<div className="h-14 flex items-center justify-between px-4 border-b border-ui-border">
|
|
@@ -518,7 +518,6 @@ const SidebarGroupAction = React.forwardRef<
|
|
|
518
518
|
data-sidebar="group-action"
|
|
519
519
|
className={cn(
|
|
520
520
|
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
521
|
-
// Increases the hit area of the button on mobile.
|
|
522
521
|
"after:absolute after:-inset-2 after:md:hidden",
|
|
523
522
|
"group-data-[collapsible=icon]:hidden",
|
|
524
523
|
className
|
|
@@ -664,7 +663,6 @@ const SidebarMenuAction = React.forwardRef<
|
|
|
664
663
|
data-sidebar="menu-action"
|
|
665
664
|
className={cn(
|
|
666
665
|
"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
|
667
|
-
// Increases the hit area of the button on mobile.
|
|
668
666
|
"after:absolute after:-inset-2 after:md:hidden",
|
|
669
667
|
"peer-data-[size=sm]/menu-button:top-1",
|
|
670
668
|
"peer-data-[size=default]/menu-button:top-1.5",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orsetra/shared-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.61",
|
|
4
4
|
"description": "Shared UI components for Orsetra platform",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -93,4 +93,4 @@
|
|
|
93
93
|
"next": "^16.0.7",
|
|
94
94
|
"typescript": "^5"
|
|
95
95
|
}
|
|
96
|
-
}
|
|
96
|
+
}
|