@orsetra/shared-ui 1.0.0 → 1.0.2
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 +31 -0
- package/components/layout/layout-container.tsx +85 -0
- package/components/layout/root-layout-wrapper.tsx +33 -0
- package/components/layout/sidebar/data.tsx +88 -0
- package/components/layout/sidebar/main-sidebar.tsx +177 -0
- package/components/layout/sidebar/sidebar.tsx +750 -0
- package/components/layout/skeleton.tsx +15 -0
- package/components/ui/accordion.tsx +58 -0
- package/components/ui/alert-dialog.tsx +133 -0
- package/components/ui/alert.tsx +59 -0
- package/components/ui/aspect-ratio.tsx +7 -0
- package/components/ui/assets-header.tsx +50 -0
- package/components/ui/avatar.tsx +50 -0
- package/components/ui/badge.tsx +54 -0
- package/components/ui/breadcrumb.tsx +115 -0
- package/components/ui/button.tsx +83 -0
- package/components/ui/calendar.tsx +66 -0
- package/components/ui/card.tsx +79 -0
- package/components/ui/carousel.tsx +262 -0
- package/components/ui/certificate-editor.tsx +445 -0
- package/components/ui/chart.tsx +365 -0
- package/components/ui/checkbox.tsx +30 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +153 -0
- package/components/ui/context-menu.tsx +200 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/drawer.tsx +118 -0
- package/components/ui/dropdown-menu.tsx +200 -0
- package/components/ui/environment-settings.tsx +173 -0
- package/components/ui/environment-variables-config.tsx +175 -0
- package/components/ui/file-import.tsx +177 -0
- package/components/ui/form.tsx +178 -0
- package/components/ui/hover-card.tsx +29 -0
- package/components/ui/index.ts +54 -0
- package/components/ui/input-otp.tsx +71 -0
- package/components/ui/input.tsx +23 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/logo.tsx +17 -0
- package/components/ui/menubar.tsx +236 -0
- package/components/ui/navigation-menu.tsx +128 -0
- package/components/ui/page-header.tsx +35 -0
- package/components/ui/pagination.tsx +112 -0
- package/components/ui/popover.tsx +31 -0
- package/components/ui/process-status.tsx +98 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/radio-group.tsx +44 -0
- package/components/ui/resizable.tsx +45 -0
- package/components/ui/resource-settings.tsx +227 -0
- package/components/ui/scroll-area.tsx +48 -0
- package/components/ui/search-input.tsx +26 -0
- package/components/ui/secret-explorer.tsx +274 -0
- package/components/ui/secret-properties-editor.tsx +642 -0
- package/components/ui/select.tsx +162 -0
- package/components/ui/selected-asset.tsx +120 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/sheet.tsx +140 -0
- package/components/ui/skeleton.tsx +15 -0
- package/components/ui/slider.tsx +28 -0
- package/components/ui/sonner.tsx +31 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +117 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/textarea.tsx +22 -0
- package/components/ui/toast.tsx +131 -0
- package/components/ui/toaster.tsx +35 -0
- package/components/ui/toggle-group.tsx +61 -0
- package/components/ui/toggle.tsx +45 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/ui/user-menu.tsx +86 -0
- package/hooks/index.ts +5 -0
- package/hooks/use-auth.ts +10 -0
- package/hooks/use-mobile.ts +19 -0
- package/hooks/use-toast.ts +8 -0
- package/hooks/use-websocket.tsx +76 -0
- package/index.ts +11 -1
- package/lib/menu-utils.ts +48 -0
- package/package.json +36 -8
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Layout components exports
|
|
2
|
+
export { MainSidebar } from './sidebar/main-sidebar'
|
|
3
|
+
export {
|
|
4
|
+
Sidebar,
|
|
5
|
+
SidebarContent,
|
|
6
|
+
SidebarFooter,
|
|
7
|
+
SidebarGroup,
|
|
8
|
+
SidebarGroupAction,
|
|
9
|
+
SidebarGroupContent,
|
|
10
|
+
SidebarGroupLabel,
|
|
11
|
+
SidebarHeader,
|
|
12
|
+
SidebarInput,
|
|
13
|
+
SidebarInset,
|
|
14
|
+
SidebarMenu,
|
|
15
|
+
SidebarMenuAction,
|
|
16
|
+
SidebarMenuBadge,
|
|
17
|
+
SidebarMenuButton,
|
|
18
|
+
SidebarMenuItem,
|
|
19
|
+
SidebarMenuSkeleton,
|
|
20
|
+
SidebarMenuSub,
|
|
21
|
+
SidebarMenuSubButton,
|
|
22
|
+
SidebarMenuSubItem,
|
|
23
|
+
SidebarProvider,
|
|
24
|
+
SidebarRail,
|
|
25
|
+
SidebarSeparator,
|
|
26
|
+
SidebarTrigger,
|
|
27
|
+
useSidebar
|
|
28
|
+
} from './sidebar/sidebar'
|
|
29
|
+
// Skeleton is exported from ../ui to avoid duplicate exports
|
|
30
|
+
export { RootLayoutWrapper, ibmPlexSans, ibmPlexMono } from './root-layout-wrapper'
|
|
31
|
+
export { LayoutContainer, type SidebarMenus } from './layout-container'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react"
|
|
4
|
+
import { usePathname } from "next/navigation"
|
|
5
|
+
import { MainSidebar, Sidebar, SidebarProvider, useSidebar } from "./index"
|
|
6
|
+
import { UserMenu } from "../ui"
|
|
7
|
+
import { getMenuFromPath } from "../../lib/menu-utils"
|
|
8
|
+
|
|
9
|
+
export interface SidebarMenus {
|
|
10
|
+
[key: string]: any[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface LayoutContainerProps {
|
|
14
|
+
children: React.ReactNode
|
|
15
|
+
sidebarMenus: SidebarMenus
|
|
16
|
+
user?: { profile?: { email?: string; preferred_username?: string } } | null
|
|
17
|
+
onSignOut?: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function LayoutContent({ children, sidebarMenus, user, onSignOut }: LayoutContainerProps) {
|
|
21
|
+
const pathname = usePathname()
|
|
22
|
+
const { setOpen } = useSidebar()
|
|
23
|
+
const [isMainSidebarOpen, setIsMainSidebarOpen] = useState(false)
|
|
24
|
+
const [currentMenu, setCurrentMenu] = useState<string>("overview")
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const contextualMenu = getMenuFromPath(pathname)
|
|
28
|
+
setCurrentMenu(contextualMenu)
|
|
29
|
+
}, [pathname])
|
|
30
|
+
|
|
31
|
+
const handleMainSidebarToggle = () => {
|
|
32
|
+
setIsMainSidebarOpen(!isMainSidebarOpen)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleMenuSelect = (menu: string) => {
|
|
36
|
+
setCurrentMenu(menu)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const handleSecondarySidebarOpen = () => {
|
|
40
|
+
setOpen(true)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const module = pathname.split('/')[1] || 'overview'
|
|
44
|
+
const sidebarItems = sidebarMenus[module] || []
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="flex h-screen w-full bg-white">
|
|
48
|
+
<Sidebar
|
|
49
|
+
currentMenu={currentMenu}
|
|
50
|
+
onMainMenuToggle={handleMainSidebarToggle}
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
<MainSidebar
|
|
54
|
+
isOpen={isMainSidebarOpen}
|
|
55
|
+
onToggle={handleMainSidebarToggle}
|
|
56
|
+
onMenuSelect={handleMenuSelect}
|
|
57
|
+
currentMenu={currentMenu}
|
|
58
|
+
onSecondarySidebarOpen={handleSecondarySidebarOpen}
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<div className="flex-1 flex flex-col">
|
|
62
|
+
<header className="h-14 bg-white border-b border-ui-border flex-shrink-0">
|
|
63
|
+
<div className="h-full px-6 flex items-center justify-end">
|
|
64
|
+
<UserMenu
|
|
65
|
+
username={user?.profile?.email || user?.profile?.preferred_username}
|
|
66
|
+
onSignOut={onSignOut || (() => {})}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
</header>
|
|
70
|
+
|
|
71
|
+
<main className="flex-1 overflow-auto">
|
|
72
|
+
{children}
|
|
73
|
+
</main>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function LayoutContainer(props: LayoutContainerProps) {
|
|
80
|
+
return (
|
|
81
|
+
<SidebarProvider defaultOpen={true}>
|
|
82
|
+
<LayoutContent {...props} />
|
|
83
|
+
</SidebarProvider>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google"
|
|
4
|
+
|
|
5
|
+
const ibmPlexSans = IBM_Plex_Sans({
|
|
6
|
+
subsets: ["latin"],
|
|
7
|
+
weight: ["100", "200", "300", "400", "500", "600", "700"],
|
|
8
|
+
variable: "--font-ibm-plex-sans",
|
|
9
|
+
display: "swap",
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const ibmPlexMono = IBM_Plex_Mono({
|
|
13
|
+
subsets: ["latin"],
|
|
14
|
+
weight: ["100", "200", "300", "400", "500", "600", "700"],
|
|
15
|
+
variable: "--font-ibm-plex-mono",
|
|
16
|
+
display: "swap",
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
interface RootLayoutWrapperProps {
|
|
20
|
+
children: React.ReactNode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function RootLayoutWrapper({ children }: RootLayoutWrapperProps) {
|
|
24
|
+
return (
|
|
25
|
+
<html lang="en" className={`${ibmPlexSans.variable} ${ibmPlexMono.variable}`} suppressHydrationWarning>
|
|
26
|
+
<body className={`${ibmPlexSans.className} text-base`} suppressHydrationWarning>
|
|
27
|
+
{children}
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { ibmPlexSans, ibmPlexMono }
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Network,
|
|
5
|
+
Server,
|
|
6
|
+
Box,
|
|
7
|
+
Shield,
|
|
8
|
+
CloudCog,
|
|
9
|
+
Globe,
|
|
10
|
+
Database,
|
|
11
|
+
Library,
|
|
12
|
+
Package,
|
|
13
|
+
Users,
|
|
14
|
+
FileText,
|
|
15
|
+
Key,
|
|
16
|
+
Settings,
|
|
17
|
+
Rocket,
|
|
18
|
+
} from "lucide-react"
|
|
19
|
+
|
|
20
|
+
export const menuItems = [
|
|
21
|
+
{
|
|
22
|
+
id: "assets",
|
|
23
|
+
label: "Assets",
|
|
24
|
+
icon: <Rocket className="h-6 w-6" />
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "applications",
|
|
28
|
+
label: "Applications",
|
|
29
|
+
icon: <Package className="h-6 w-6" />
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "api-manager",
|
|
33
|
+
label: "API Manager",
|
|
34
|
+
icon: <Settings className="h-6 w-6" />
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "access-manager",
|
|
38
|
+
label: "Access Manager",
|
|
39
|
+
icon: <Shield className="h-6 w-6" />
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
// Mapping des menus principaux vers leurs sous-menus
|
|
44
|
+
export const menuSubItems = {
|
|
45
|
+
"assets": [
|
|
46
|
+
{ id: "library", name: "Bibliothèque", href: "/assets", icon: Library },
|
|
47
|
+
{ id: "projects", name: "Projets", href: "/projects", icon: Rocket },
|
|
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
|
+
]
|
|
67
|
+
}
|
|
68
|
+
|
|
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
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../../lib/utils"
|
|
5
|
+
|
|
6
|
+
import { useRouter } from "next/navigation"
|
|
7
|
+
import { Button } from "../../ui/button"
|
|
8
|
+
import { Logo } from "../../ui/logo"
|
|
9
|
+
import { menuItems, menuSubItems } from "./data"
|
|
10
|
+
import { X, Menu } from "lucide-react"
|
|
11
|
+
import Link from "next/link"
|
|
12
|
+
|
|
13
|
+
export type SidebarMode = 'expanded' | 'minimized'
|
|
14
|
+
|
|
15
|
+
interface MainSidebarProps {
|
|
16
|
+
isOpen: boolean
|
|
17
|
+
onToggle: () => void
|
|
18
|
+
onMenuSelect: (menu: string) => void
|
|
19
|
+
currentMenu?: string
|
|
20
|
+
mode?: SidebarMode
|
|
21
|
+
onSecondarySidebarOpen?: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function MainSidebar({
|
|
25
|
+
isOpen,
|
|
26
|
+
onToggle,
|
|
27
|
+
onMenuSelect,
|
|
28
|
+
currentMenu,
|
|
29
|
+
mode = 'expanded',
|
|
30
|
+
onSecondarySidebarOpen
|
|
31
|
+
}: MainSidebarProps) {
|
|
32
|
+
const isMinimized = mode === 'minimized'
|
|
33
|
+
const router = useRouter()
|
|
34
|
+
const [hoveredMenu, setHoveredMenu] = React.useState<string | null>(null)
|
|
35
|
+
|
|
36
|
+
const handleMenuClick = (menuId: string) => {
|
|
37
|
+
onMenuSelect(menuId)
|
|
38
|
+
|
|
39
|
+
// En mode minimized, ne pas ouvrir le sidebar secondaire
|
|
40
|
+
if (!isMinimized && onSecondarySidebarOpen) {
|
|
41
|
+
onSecondarySidebarOpen()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Rediriger vers le premier sous-menu
|
|
45
|
+
const subMenus = menuSubItems[menuId as keyof typeof menuSubItems]
|
|
46
|
+
if (subMenus && subMenus.length > 0) {
|
|
47
|
+
router.push(subMenus[0].href)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!isMinimized) {
|
|
51
|
+
onToggle() // Fermer le menu principal après sélection (seulement en mode expanded)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleSubMenuClick = (href: string) => {
|
|
56
|
+
router.push(href)
|
|
57
|
+
setHoveredMenu(null)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
{/* Overlay - seulement en mode expanded */}
|
|
63
|
+
{isOpen && !isMinimized && (
|
|
64
|
+
<div
|
|
65
|
+
className="fixed inset-0 bg-black/50 z-40"
|
|
66
|
+
onClick={onToggle}
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{/* Main Sidebar */}
|
|
71
|
+
<div className={cn(
|
|
72
|
+
"fixed left-0 top-0 h-full bg-white border-r border-ui-border z-50 transform transition-all duration-300 ease-in-out",
|
|
73
|
+
isMinimized ? "w-16" : "w-64 shadow-xl",
|
|
74
|
+
isMinimized
|
|
75
|
+
? "translate-x-0"
|
|
76
|
+
: (isOpen ? "translate-x-0" : "-translate-x-full")
|
|
77
|
+
)}>
|
|
78
|
+
{/* Header avec logo Orchestra */}
|
|
79
|
+
<div className={cn(
|
|
80
|
+
"flex items-center h-16 border-b border-ui-border bg-white",
|
|
81
|
+
isMinimized ? "justify-center px-2" : "justify-between px-4"
|
|
82
|
+
)}>
|
|
83
|
+
{!isMinimized && <Logo className="text-2xl font-semibold text-text-primary" />}
|
|
84
|
+
{isMinimized ? (
|
|
85
|
+
<Logo className="text-xl font-semibold text-text-primary" iconOnly />
|
|
86
|
+
) : (
|
|
87
|
+
<Button
|
|
88
|
+
variant="ghost"
|
|
89
|
+
size="sm"
|
|
90
|
+
onClick={onToggle}
|
|
91
|
+
className="h-8 w-8 p-0 hover:bg-ui-background text-text-secondary"
|
|
92
|
+
>
|
|
93
|
+
<X className="h-4 w-4" />
|
|
94
|
+
</Button>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Menu Items */}
|
|
99
|
+
<div className={cn(
|
|
100
|
+
"overflow-y-auto h-full",
|
|
101
|
+
isMinimized ? "p-2" : "p-3"
|
|
102
|
+
)}>
|
|
103
|
+
<nav className="space-y-2">
|
|
104
|
+
{menuItems.map((item) => (
|
|
105
|
+
<div
|
|
106
|
+
key={item.id}
|
|
107
|
+
className="relative"
|
|
108
|
+
onMouseEnter={() => isMinimized && setHoveredMenu(item.id)}
|
|
109
|
+
onMouseLeave={() => isMinimized && setHoveredMenu(null)}
|
|
110
|
+
>
|
|
111
|
+
<button
|
|
112
|
+
onClick={() => handleMenuClick(item.id)}
|
|
113
|
+
className={cn(
|
|
114
|
+
"w-full flex items-center text-left rounded-lg transition-all duration-200 font-medium",
|
|
115
|
+
isMinimized
|
|
116
|
+
? "justify-center p-3"
|
|
117
|
+
: "gap-3 px-3 py-3",
|
|
118
|
+
currentMenu === item.id
|
|
119
|
+
? "bg-interactive/10 text-interactive"
|
|
120
|
+
: "text-text-primary hover:bg-ui-background hover:text-text-primary"
|
|
121
|
+
)}
|
|
122
|
+
title={isMinimized ? item.label : undefined}
|
|
123
|
+
>
|
|
124
|
+
<div className={cn(
|
|
125
|
+
"flex items-center justify-center",
|
|
126
|
+
currentMenu === item.id
|
|
127
|
+
? "text-interactive"
|
|
128
|
+
: "text-text-secondary"
|
|
129
|
+
)}>
|
|
130
|
+
{item.icon}
|
|
131
|
+
</div>
|
|
132
|
+
{!isMinimized && <span className="text-base">{item.label}</span>}
|
|
133
|
+
</button>
|
|
134
|
+
|
|
135
|
+
{/* Submenu au survol en mode minimized */}
|
|
136
|
+
{isMinimized && hoveredMenu === item.id && menuSubItems[item.id as keyof typeof menuSubItems] && (
|
|
137
|
+
<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
|
+
<div className="px-4 py-2 border-b border-ui-border">
|
|
139
|
+
<h3 className="text-sm font-semibold text-text-primary">{item.label}</h3>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="py-1">
|
|
142
|
+
{menuSubItems[item.id as keyof typeof menuSubItems].map((subItem) => (
|
|
143
|
+
<Link
|
|
144
|
+
key={subItem.id}
|
|
145
|
+
href={subItem.href}
|
|
146
|
+
onClick={() => handleSubMenuClick(subItem.href)}
|
|
147
|
+
className="flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-ui-background hover:text-text-primary transition-colors"
|
|
148
|
+
>
|
|
149
|
+
<subItem.icon className="h-4 w-4" />
|
|
150
|
+
{subItem.name}
|
|
151
|
+
</Link>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</nav>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function MainSidebarToggle({ onClick }: { onClick: () => void }) {
|
|
166
|
+
return (
|
|
167
|
+
<Button
|
|
168
|
+
variant="ghost"
|
|
169
|
+
size="sm"
|
|
170
|
+
onClick={onClick}
|
|
171
|
+
className="h-8 w-8 p-0 hover:bg-ui-background text-text-secondary"
|
|
172
|
+
title="Ouvrir le menu principal"
|
|
173
|
+
>
|
|
174
|
+
<Menu className="h-4 w-4" />
|
|
175
|
+
</Button>
|
|
176
|
+
)
|
|
177
|
+
}
|