@orsetra/shared-ui 1.0.13 → 1.0.15
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.
- package/components/layout/index.ts +2 -1
- package/components/layout/layout-container.tsx +5 -5
- package/components/layout/sidebar/data.tsx +27 -55
- package/components/layout/sidebar/main-sidebar.tsx +8 -6
- package/components/layout/sidebar/sidebar.tsx +9 -13
- package/components/ui/editable-title.tsx +13 -4
- package/package.json +24 -24
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Layout components exports
|
|
2
2
|
export { MainSidebar, type SidebarMode } from './sidebar/main-sidebar'
|
|
3
|
+
export { menuItems, menuLabels, type MainMenuItem, type SubMenuItem, type SidebarMenus } from './sidebar/data'
|
|
3
4
|
export {
|
|
4
5
|
Sidebar,
|
|
5
6
|
SidebarContent,
|
|
@@ -28,4 +29,4 @@ export {
|
|
|
28
29
|
} from './sidebar/sidebar'
|
|
29
30
|
// Skeleton is exported from ../ui to avoid duplicate exports
|
|
30
31
|
export { RootLayoutWrapper, ibmPlexSans, ibmPlexMono } from './root-layout-wrapper'
|
|
31
|
-
export { LayoutContainer
|
|
32
|
+
export { LayoutContainer } from './layout-container'
|
|
@@ -2,16 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react"
|
|
4
4
|
import { usePathname } from "next/navigation"
|
|
5
|
-
import { MainSidebar,
|
|
5
|
+
import { MainSidebar, type SidebarMode } from "./sidebar/main-sidebar"
|
|
6
|
+
import { Sidebar, SidebarProvider, useSidebar } from "./sidebar/sidebar"
|
|
7
|
+
import { type SidebarMenus } from "./sidebar/data"
|
|
6
8
|
import { UserMenu, Button } from "../ui"
|
|
7
9
|
import { getMenuFromPath } from "../../lib/menu-utils"
|
|
8
10
|
import { useIsMobile } from "../../hooks/use-mobile"
|
|
9
11
|
import { Menu } from "lucide-react"
|
|
10
12
|
|
|
11
|
-
export interface SidebarMenus {
|
|
12
|
-
[key: string]: any[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
13
|
interface LayoutContainerProps {
|
|
16
14
|
children: React.ReactNode
|
|
17
15
|
sidebarMenus: SidebarMenus
|
|
@@ -64,6 +62,7 @@ function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expand
|
|
|
64
62
|
<Sidebar
|
|
65
63
|
currentMenu={currentMenu}
|
|
66
64
|
onMainMenuToggle={handleMainSidebarToggle}
|
|
65
|
+
sidebarMenus={sidebarMenus}
|
|
67
66
|
/>
|
|
68
67
|
)}
|
|
69
68
|
|
|
@@ -75,6 +74,7 @@ function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expand
|
|
|
75
74
|
currentMenu={currentMenu}
|
|
76
75
|
onSecondarySidebarOpen={handleSecondarySidebarOpen}
|
|
77
76
|
mode={isHidden ? "expanded" : mode}
|
|
77
|
+
sidebarMenus={sidebarMenus}
|
|
78
78
|
/>
|
|
79
79
|
|
|
80
80
|
<div className="flex-1 flex flex-col min-w-0">
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
import { type LucideIcon } from "lucide-react"
|
|
3
4
|
import {
|
|
4
|
-
Network,
|
|
5
|
-
Server,
|
|
6
|
-
Box,
|
|
7
5
|
Shield,
|
|
8
|
-
CloudCog,
|
|
9
|
-
Globe,
|
|
10
|
-
Database,
|
|
11
|
-
Library,
|
|
12
6
|
Package,
|
|
13
|
-
Users,
|
|
14
|
-
FileText,
|
|
15
|
-
Key,
|
|
16
7
|
Settings,
|
|
17
8
|
Rocket,
|
|
18
9
|
} from "lucide-react"
|
|
19
10
|
|
|
20
|
-
|
|
11
|
+
// Types pour les menus
|
|
12
|
+
export interface MainMenuItem {
|
|
13
|
+
id: string
|
|
14
|
+
label: string
|
|
15
|
+
icon: React.ReactNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SubMenuItem {
|
|
19
|
+
id: string
|
|
20
|
+
name: string
|
|
21
|
+
href: string
|
|
22
|
+
icon: LucideIcon
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SidebarMenus {
|
|
26
|
+
[key: string]: SubMenuItem[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Menu principal (centralisé) - Structure commune à toutes les apps
|
|
30
|
+
export const menuItems: MainMenuItem[] = [
|
|
21
31
|
{
|
|
22
32
|
id: "assets",
|
|
23
33
|
label: "Assets",
|
|
@@ -40,49 +50,11 @@ export const menuItems = [
|
|
|
40
50
|
}
|
|
41
51
|
]
|
|
42
52
|
|
|
43
|
-
//
|
|
44
|
-
export const
|
|
45
|
-
"assets":
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"applications": [
|
|
50
|
-
{ id: "applications", name: "Applications", href: "/applications", icon: Package },
|
|
51
|
-
{ id: "vpc", name: "Réseaux", href: "/vpcs", icon: Network },
|
|
52
|
-
{ id: "secrets", name: "Secrets", href: "/secrets", icon: Key },
|
|
53
|
-
{ id: "environments", name: "Environnements", href: "/environments", icon: Globe }
|
|
54
|
-
],
|
|
55
|
-
"api-manager": [
|
|
56
|
-
{ id: "apis", name: "APIs", href: "/api-manager/apis", icon: Database },
|
|
57
|
-
{ id: "gateway", name: "API Gateway", href: "/api-manager/gateway", icon: Globe }
|
|
58
|
-
],
|
|
59
|
-
"access-manager": [
|
|
60
|
-
{ id: "users", name: "Utilisateurs", href: "/access-manager/users", icon: Users },
|
|
61
|
-
{ id: "groups", name: "Groupes", href: "/access-manager/groups", icon: Users },
|
|
62
|
-
{ id: "roles", name: "Rôles IAM", href: "/access-manager/roles", icon: Shield },
|
|
63
|
-
{ id: "policies", name: "Politiques", href: "/access-manager/policies", icon: FileText },
|
|
64
|
-
{ id: "resource-servers", name: "Serveurs OAuth", href: "/access-manager/resource-servers", icon: Server },
|
|
65
|
-
{ id: "identity-providers", name: "Fournisseurs d'identité", href: "/access-manager/identity-providers", icon: Key }
|
|
66
|
-
]
|
|
53
|
+
// Labels des menus principaux pour l'affichage
|
|
54
|
+
export const menuLabels: Record<string, string> = {
|
|
55
|
+
"assets": "Assets",
|
|
56
|
+
"applications": "Applications",
|
|
57
|
+
"api-manager": "API Manager",
|
|
58
|
+
"access-manager": "Access Manager"
|
|
67
59
|
}
|
|
68
60
|
|
|
69
|
-
// Menu par défaut quand aucun menu principal n'est sélectionné
|
|
70
|
-
export const defaultNavigation = [
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
name: "Applications",
|
|
74
|
-
href: "/applications",
|
|
75
|
-
icon: Box,
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: "API Manager",
|
|
79
|
-
href: "/api-manager",
|
|
80
|
-
icon: CloudCog,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: "Assets",
|
|
84
|
-
href: "/assets",
|
|
85
|
-
icon: Server,
|
|
86
|
-
},
|
|
87
|
-
]
|
|
88
|
-
|
|
@@ -6,7 +6,7 @@ import { cn } from "../../../lib/utils"
|
|
|
6
6
|
import { useRouter } from "next/navigation"
|
|
7
7
|
import { Button } from "../../ui/button"
|
|
8
8
|
import { Logo } from "../../ui/logo"
|
|
9
|
-
import { menuItems,
|
|
9
|
+
import { menuItems, type SidebarMenus } from "./data"
|
|
10
10
|
import { X, Menu } from "lucide-react"
|
|
11
11
|
import Link from "next/link"
|
|
12
12
|
|
|
@@ -19,6 +19,7 @@ interface MainSidebarProps {
|
|
|
19
19
|
currentMenu?: string
|
|
20
20
|
mode?: SidebarMode
|
|
21
21
|
onSecondarySidebarOpen?: () => void
|
|
22
|
+
sidebarMenus?: SidebarMenus
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function MainSidebar({
|
|
@@ -27,7 +28,8 @@ export function MainSidebar({
|
|
|
27
28
|
onMenuSelect,
|
|
28
29
|
currentMenu,
|
|
29
30
|
mode = 'expanded',
|
|
30
|
-
onSecondarySidebarOpen
|
|
31
|
+
onSecondarySidebarOpen,
|
|
32
|
+
sidebarMenus = {}
|
|
31
33
|
}: MainSidebarProps) {
|
|
32
34
|
const isMinimized = mode === 'minimized'
|
|
33
35
|
const router = useRouter()
|
|
@@ -41,8 +43,8 @@ export function MainSidebar({
|
|
|
41
43
|
onSecondarySidebarOpen()
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
// Rediriger vers le premier sous-menu
|
|
45
|
-
const subMenus =
|
|
46
|
+
// Rediriger vers le premier sous-menu (utilise les menus fournis par l'app)
|
|
47
|
+
const subMenus = sidebarMenus[menuId]
|
|
46
48
|
if (subMenus && subMenus.length > 0) {
|
|
47
49
|
router.push(subMenus[0].href)
|
|
48
50
|
}
|
|
@@ -133,13 +135,13 @@ export function MainSidebar({
|
|
|
133
135
|
</button>
|
|
134
136
|
|
|
135
137
|
{/* Submenu au survol en mode minimized */}
|
|
136
|
-
{isMinimized && hoveredMenu === item.id &&
|
|
138
|
+
{isMinimized && hoveredMenu === item.id && sidebarMenus[item.id] && sidebarMenus[item.id].length > 0 && (
|
|
137
139
|
<div className="absolute left-full top-0 ml-2 bg-white border border-ui-border rounded-lg shadow-xl z-50 min-w-[200px] py-2">
|
|
138
140
|
<div className="px-4 py-2 border-b border-ui-border">
|
|
139
141
|
<h3 className="text-sm font-semibold text-text-primary">{item.label}</h3>
|
|
140
142
|
</div>
|
|
141
143
|
<div className="py-1">
|
|
142
|
-
{
|
|
144
|
+
{sidebarMenus[item.id].map((subItem) => (
|
|
143
145
|
<Link
|
|
144
146
|
key={subItem.id}
|
|
145
147
|
href={subItem.href}
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
Menu
|
|
27
27
|
} from "lucide-react"
|
|
28
28
|
import { Logo } from "../../ui/logo"
|
|
29
|
-
import {
|
|
29
|
+
import { menuLabels, type SidebarMenus, type SubMenuItem } from "./data"
|
|
30
30
|
import { Skeleton } from "../skeleton"
|
|
31
31
|
|
|
32
32
|
const SIDEBAR_COOKIE_NAME = "sidebar:state"
|
|
@@ -169,18 +169,19 @@ SidebarProvider.displayName = "SidebarProvider"
|
|
|
169
169
|
interface SidebarProps {
|
|
170
170
|
currentMenu?: string
|
|
171
171
|
onMainMenuToggle?: () => void
|
|
172
|
+
sidebarMenus?: SidebarMenus
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
|
|
175
176
|
|
|
176
|
-
function Sidebar({ currentMenu, onMainMenuToggle }: SidebarProps = {}) {
|
|
177
|
+
function Sidebar({ currentMenu, onMainMenuToggle, sidebarMenus = {} }: SidebarProps = {}) {
|
|
177
178
|
const pathname = usePathname()
|
|
178
179
|
const { state } = useSidebar()
|
|
179
180
|
|
|
180
|
-
// Déterminer quelle navigation afficher
|
|
181
|
-
const currentNavigation = currentMenu &&
|
|
182
|
-
?
|
|
183
|
-
:
|
|
181
|
+
// Déterminer quelle navigation afficher à partir des menus fournis par l'app
|
|
182
|
+
const currentNavigation: SubMenuItem[] = currentMenu && sidebarMenus[currentMenu]
|
|
183
|
+
? sidebarMenus[currentMenu]
|
|
184
|
+
: []
|
|
184
185
|
|
|
185
186
|
return (
|
|
186
187
|
<div className="h-screen sticky top-0 flex flex-col bg-white border-r border-ui-border min-w-[var(--sidebar-width-icon)] transition-[width] duration-200"
|
|
@@ -202,20 +203,15 @@ function Sidebar({ currentMenu, onMainMenuToggle }: SidebarProps = {}) {
|
|
|
202
203
|
</div>
|
|
203
204
|
|
|
204
205
|
{/* En-tête du menu principal sélectionné */}
|
|
205
|
-
{currentMenu && (
|
|
206
|
+
{currentMenu && menuLabels[currentMenu] && (
|
|
206
207
|
<div className="px-4 py-3 border-b border-ui-border">
|
|
207
208
|
<div className="flex items-center gap-2">
|
|
208
209
|
{currentMenu === "assets" && <Rocket className="h-4 w-4 text-interactive" />}
|
|
209
|
-
{currentMenu === "projects" && <Rocket className="h-4 w-4 text-interactive" />}
|
|
210
210
|
{currentMenu === "applications" && <Package className="h-4 w-4 text-interactive" />}
|
|
211
211
|
{currentMenu === "api-manager" && <Settings className="h-4 w-4 text-interactive" />}
|
|
212
212
|
{currentMenu === "access-manager" && <Shield className="h-4 w-4 text-interactive" />}
|
|
213
213
|
<h2 className="text-sm font-semibold text-text-primary">
|
|
214
|
-
{currentMenu
|
|
215
|
-
{currentMenu === "projects" && "Projects"}
|
|
216
|
-
{currentMenu === "applications" && "Applications"}
|
|
217
|
-
{currentMenu === "api-manager" && "API Manager"}
|
|
218
|
-
{currentMenu === "access-manager" && "Access Manager"}
|
|
214
|
+
{menuLabels[currentMenu]}
|
|
219
215
|
</h2>
|
|
220
216
|
</div>
|
|
221
217
|
</div>
|
|
@@ -7,11 +7,12 @@ import { Button } from "./button"
|
|
|
7
7
|
|
|
8
8
|
interface EditableTitleProps {
|
|
9
9
|
value: string
|
|
10
|
-
onSave
|
|
10
|
+
onSave?: (newValue: string) => void
|
|
11
11
|
className?: string
|
|
12
|
+
editable?: boolean
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export function EditableTitle({ value, onSave, className = "" }: EditableTitleProps) {
|
|
15
|
+
export function EditableTitle({ value, onSave, className = "", editable = true }: EditableTitleProps) {
|
|
15
16
|
const [isEditing, setIsEditing] = useState(false)
|
|
16
17
|
const [isHovered, setIsHovered] = useState(false)
|
|
17
18
|
const [editValue, setEditValue] = useState(value)
|
|
@@ -25,7 +26,7 @@ export function EditableTitle({ value, onSave, className = "" }: EditableTitlePr
|
|
|
25
26
|
}, [isEditing])
|
|
26
27
|
|
|
27
28
|
const handleSave = () => {
|
|
28
|
-
if (editValue.trim() && editValue !== value) {
|
|
29
|
+
if (editValue.trim() && editValue !== value && onSave) {
|
|
29
30
|
onSave(editValue.trim())
|
|
30
31
|
}
|
|
31
32
|
setIsEditing(false)
|
|
@@ -56,7 +57,7 @@ export function EditableTitle({ value, onSave, className = "" }: EditableTitlePr
|
|
|
56
57
|
/>
|
|
57
58
|
<Button
|
|
58
59
|
size="sm"
|
|
59
|
-
variant="
|
|
60
|
+
variant="primary"
|
|
60
61
|
onClick={handleSave}
|
|
61
62
|
className="h-8 w-8 p-0"
|
|
62
63
|
>
|
|
@@ -74,6 +75,14 @@ export function EditableTitle({ value, onSave, className = "" }: EditableTitlePr
|
|
|
74
75
|
)
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
if (!editable) {
|
|
79
|
+
return (
|
|
80
|
+
<div className={`flex items-center gap-2 ${className}`}>
|
|
81
|
+
<h2 className="text-base font-semibold text-text-primary">{value}</h2>
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
77
86
|
return (
|
|
78
87
|
<div
|
|
79
88
|
className={`flex items-center gap-2 group cursor-pointer ${className}`}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orsetra/shared-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Shared UI components for Orsetra platform",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -35,30 +35,12 @@
|
|
|
35
35
|
"tailwind"
|
|
36
36
|
],
|
|
37
37
|
"peerDependencies": {
|
|
38
|
+
"next": "^14.0.0 || ^15.0.0",
|
|
38
39
|
"react": "^18.0.0 || ^19.0.0",
|
|
39
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
40
|
-
"next": "^14.0.0 || ^15.0.0"
|
|
40
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"react-avatar": "^5.0.3",
|
|
44
|
-
"react-easy-crop": "^5.0.8",
|
|
45
|
-
"react-hook-form": "^7.54.0",
|
|
46
|
-
"react-resizable-panels": "^2.1.7",
|
|
47
43
|
"@hookform/resolvers": "^3.9.1",
|
|
48
|
-
"zod": "^3.24.1",
|
|
49
|
-
"clsx": "^2.1.1",
|
|
50
|
-
"tailwind-merge": "^2.5.5",
|
|
51
|
-
"class-variance-authority": "^0.7.1",
|
|
52
|
-
"lucide-react": "^0.454.0",
|
|
53
|
-
"react-day-picker": "8.10.1",
|
|
54
|
-
"embla-carousel-react": "8.5.1",
|
|
55
|
-
"cmdk": "1.0.4",
|
|
56
|
-
"recharts": "^2.15.0",
|
|
57
|
-
"date-fns": "4.1.0",
|
|
58
|
-
"input-otp": "1.4.1",
|
|
59
|
-
"vaul": "^1.1.1",
|
|
60
|
-
"next-themes": "^0.4.4",
|
|
61
|
-
"sonner": "^1.7.1",
|
|
62
44
|
"@radix-ui/react-accordion": "1.2.2",
|
|
63
45
|
"@radix-ui/react-alert-dialog": "1.1.4",
|
|
64
46
|
"@radix-ui/react-aspect-ratio": "1.1.1",
|
|
@@ -85,11 +67,29 @@
|
|
|
85
67
|
"@radix-ui/react-toast": "1.2.4",
|
|
86
68
|
"@radix-ui/react-toggle": "1.1.1",
|
|
87
69
|
"@radix-ui/react-toggle-group": "1.1.1",
|
|
88
|
-
"@radix-ui/react-tooltip": "1.1.6"
|
|
70
|
+
"@radix-ui/react-tooltip": "1.1.6",
|
|
71
|
+
"class-variance-authority": "^0.7.1",
|
|
72
|
+
"clsx": "^2.1.1",
|
|
73
|
+
"cmdk": "1.0.4",
|
|
74
|
+
"date-fns": "4.1.0",
|
|
75
|
+
"embla-carousel-react": "8.5.1",
|
|
76
|
+
"input-otp": "1.4.1",
|
|
77
|
+
"lucide-react": "^0.454.0",
|
|
78
|
+
"next-themes": "^0.4.4",
|
|
79
|
+
"react-avatar": "^5.0.3",
|
|
80
|
+
"react-day-picker": "8.10.1",
|
|
81
|
+
"react-easy-crop": "^5.0.8",
|
|
82
|
+
"react-hook-form": "^7.54.0",
|
|
83
|
+
"react-resizable-panels": "^2.1.7",
|
|
84
|
+
"recharts": "^2.15.0",
|
|
85
|
+
"sonner": "^1.7.1",
|
|
86
|
+
"tailwind-merge": "^2.5.5",
|
|
87
|
+
"vaul": "^1.1.1",
|
|
88
|
+
"zod": "^3.24.1"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@types/react": "^19",
|
|
92
|
-
"next": "^
|
|
92
|
+
"next": "^16.0.7",
|
|
93
93
|
"typescript": "^5"
|
|
94
94
|
}
|
|
95
|
-
}
|
|
95
|
+
}
|