@snapdragonsnursery/react-components 1.7.0 → 1.9.0
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/package.json +7 -1
- package/src/EmployeeSearchPage.test.jsx +6 -1
- package/src/components/app-sidebar.jsx +205 -0
- package/src/components/nav-main.jsx +60 -0
- package/src/components/nav-projects.jsx +76 -0
- package/src/components/nav-user.jsx +104 -0
- package/src/components/room-switcher.jsx +116 -0
- package/src/components/site-switcher.jsx +116 -0
- package/src/components/team-switcher.jsx +83 -0
- package/src/components/theme-mode-toggle.jsx +62 -0
- package/src/components/ui/avatar.jsx +47 -0
- package/src/components/ui/breadcrumb.jsx +112 -0
- package/src/components/ui/collapsible.jsx +23 -0
- package/src/components/ui/dropdown-menu.jsx +221 -0
- package/src/components/ui/separator.jsx +25 -0
- package/src/components/ui/sheet.jsx +140 -0
- package/src/components/ui/sidebar.jsx +681 -0
- package/src/components/ui/skeleton.jsx +15 -0
- package/src/components/ui/tooltip.jsx +53 -0
- package/src/hooks/use-current-site.js +61 -0
- package/src/hooks/use-mobile.js +19 -0
- package/src/index.css +44 -0
- package/src/index.d.ts +67 -0
- package/src/index.js +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snapdragonsnursery/react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -40,8 +40,14 @@
|
|
|
40
40
|
"@headlessui/react": "^2.2.4",
|
|
41
41
|
"@heroicons/react": "^2.2.0",
|
|
42
42
|
"@popperjs/core": "^2.11.8",
|
|
43
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
44
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
45
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
46
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
43
47
|
"@radix-ui/react-popover": "^1.1.14",
|
|
48
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
44
49
|
"@radix-ui/react-slot": "^1.2.3",
|
|
50
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
45
51
|
"@tanstack/react-table": "^8.21.3",
|
|
46
52
|
"class-variance-authority": "^0.7.1",
|
|
47
53
|
"clsx": "^2.1.1",
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
AudioWaveform,
|
|
6
|
+
BookOpen,
|
|
7
|
+
Bot,
|
|
8
|
+
Command,
|
|
9
|
+
Frame,
|
|
10
|
+
GalleryVerticalEnd,
|
|
11
|
+
Map,
|
|
12
|
+
PieChart,
|
|
13
|
+
Settings2,
|
|
14
|
+
SquareTerminal,
|
|
15
|
+
} from "lucide-react"
|
|
16
|
+
|
|
17
|
+
import { NavMain } from "./nav-main"
|
|
18
|
+
import { NavProjects } from "./nav-projects"
|
|
19
|
+
import { NavUser } from "./nav-user"
|
|
20
|
+
import { TeamSwitcher } from "./team-switcher"
|
|
21
|
+
import { SiteSwitcher } from "./site-switcher"
|
|
22
|
+
import { RoomSwitcher } from "./room-switcher"
|
|
23
|
+
import { ThemeModeToggle } from "./theme-mode-toggle"
|
|
24
|
+
import {
|
|
25
|
+
Sidebar,
|
|
26
|
+
SidebarContent,
|
|
27
|
+
SidebarFooter,
|
|
28
|
+
SidebarHeader,
|
|
29
|
+
SidebarRail,
|
|
30
|
+
SidebarTrigger,
|
|
31
|
+
} from "./ui/sidebar"
|
|
32
|
+
|
|
33
|
+
// This is sample data.
|
|
34
|
+
const data = {
|
|
35
|
+
user: {
|
|
36
|
+
name: "shadcn",
|
|
37
|
+
email: "m@example.com",
|
|
38
|
+
avatar: "/avatars/shadcn.jpg",
|
|
39
|
+
},
|
|
40
|
+
teams: [
|
|
41
|
+
{
|
|
42
|
+
name: "Acme Inc",
|
|
43
|
+
logo: GalleryVerticalEnd,
|
|
44
|
+
plan: "Enterprise",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "Acme Corp.",
|
|
48
|
+
logo: AudioWaveform,
|
|
49
|
+
plan: "Startup",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "Evil Corp.",
|
|
53
|
+
logo: Command,
|
|
54
|
+
plan: "Free",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
navMain: [
|
|
58
|
+
{
|
|
59
|
+
title: "Playground",
|
|
60
|
+
url: "#",
|
|
61
|
+
icon: SquareTerminal,
|
|
62
|
+
isActive: true,
|
|
63
|
+
items: [
|
|
64
|
+
{
|
|
65
|
+
title: "History",
|
|
66
|
+
url: "#",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
title: "Starred",
|
|
70
|
+
url: "#",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: "Settings",
|
|
74
|
+
url: "#",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: "Models",
|
|
80
|
+
url: "#",
|
|
81
|
+
icon: Bot,
|
|
82
|
+
items: [
|
|
83
|
+
{
|
|
84
|
+
title: "Genesis",
|
|
85
|
+
url: "#",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: "Explorer",
|
|
89
|
+
url: "#",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
title: "Quantum",
|
|
93
|
+
url: "#",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
title: "Documentation",
|
|
99
|
+
url: "#",
|
|
100
|
+
icon: BookOpen,
|
|
101
|
+
items: [
|
|
102
|
+
{
|
|
103
|
+
title: "Introduction",
|
|
104
|
+
url: "#",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
title: "Get Started",
|
|
108
|
+
url: "#",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
title: "Tutorials",
|
|
112
|
+
url: "#",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
title: "Changelog",
|
|
116
|
+
url: "#",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
title: "Settings",
|
|
122
|
+
url: "#",
|
|
123
|
+
icon: Settings2,
|
|
124
|
+
items: [
|
|
125
|
+
{
|
|
126
|
+
title: "General",
|
|
127
|
+
url: "#",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
title: "Team",
|
|
131
|
+
url: "#",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
title: "Billing",
|
|
135
|
+
url: "#",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
title: "Limits",
|
|
139
|
+
url: "#",
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
projects: [
|
|
145
|
+
{
|
|
146
|
+
name: "Design Engineering",
|
|
147
|
+
url: "#",
|
|
148
|
+
icon: Frame,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "Sales & Marketing",
|
|
152
|
+
url: "#",
|
|
153
|
+
icon: PieChart,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "Travel",
|
|
157
|
+
url: "#",
|
|
158
|
+
icon: Map,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function AppSidebar({
|
|
164
|
+
sites,
|
|
165
|
+
activeSiteId,
|
|
166
|
+
onSiteChange,
|
|
167
|
+
sitesLoading,
|
|
168
|
+
rooms,
|
|
169
|
+
activeRoomId,
|
|
170
|
+
onRoomChange,
|
|
171
|
+
roomsLoading,
|
|
172
|
+
roomBaseColor,
|
|
173
|
+
navItems,
|
|
174
|
+
projects,
|
|
175
|
+
user,
|
|
176
|
+
...props
|
|
177
|
+
}) {
|
|
178
|
+
return (
|
|
179
|
+
<Sidebar collapsible="icon" {...props}>
|
|
180
|
+
<SidebarHeader>
|
|
181
|
+
{sites?.length || sitesLoading ? (
|
|
182
|
+
<SiteSwitcher items={sites} activeId={activeSiteId} onChange={onSiteChange} isLoading={sitesLoading} />
|
|
183
|
+
) : null}
|
|
184
|
+
{rooms?.length || roomsLoading ? (
|
|
185
|
+
<RoomSwitcher items={rooms} activeId={activeRoomId} onChange={onRoomChange} isLoading={roomsLoading} baseColor={roomBaseColor} />
|
|
186
|
+
) : null}
|
|
187
|
+
{!sites?.length && !rooms?.length ? (
|
|
188
|
+
<TeamSwitcher teams={data.teams} />
|
|
189
|
+
) : null}
|
|
190
|
+
</SidebarHeader>
|
|
191
|
+
<SidebarContent>
|
|
192
|
+
<NavMain items={navItems ?? data.navMain} />
|
|
193
|
+
<NavProjects projects={projects ?? data.projects} />
|
|
194
|
+
</SidebarContent>
|
|
195
|
+
<SidebarFooter>
|
|
196
|
+
<div className="flex items-center justify-end gap-1 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:items-end group-data-[collapsible=icon]:gap-2">
|
|
197
|
+
<ThemeModeToggle />
|
|
198
|
+
<SidebarTrigger aria-label="Toggle sidebar" />
|
|
199
|
+
</div>
|
|
200
|
+
<NavUser user={user ?? data.user} />
|
|
201
|
+
</SidebarFooter>
|
|
202
|
+
<SidebarRail />
|
|
203
|
+
</Sidebar>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ChevronRight } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Collapsible,
|
|
5
|
+
CollapsibleContent,
|
|
6
|
+
CollapsibleTrigger,
|
|
7
|
+
} from "./ui/collapsible"
|
|
8
|
+
import {
|
|
9
|
+
SidebarGroup,
|
|
10
|
+
SidebarGroupLabel,
|
|
11
|
+
SidebarMenu,
|
|
12
|
+
SidebarMenuButton,
|
|
13
|
+
SidebarMenuItem,
|
|
14
|
+
SidebarMenuSub,
|
|
15
|
+
SidebarMenuSubButton,
|
|
16
|
+
SidebarMenuSubItem,
|
|
17
|
+
} from "./ui/sidebar"
|
|
18
|
+
|
|
19
|
+
export function NavMain({
|
|
20
|
+
items
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<SidebarGroup>
|
|
24
|
+
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
|
25
|
+
<SidebarMenu>
|
|
26
|
+
{items.map((item) => (
|
|
27
|
+
<Collapsible
|
|
28
|
+
key={item.title}
|
|
29
|
+
asChild
|
|
30
|
+
defaultOpen={item.isActive}
|
|
31
|
+
className="group/collapsible">
|
|
32
|
+
<SidebarMenuItem>
|
|
33
|
+
<CollapsibleTrigger asChild>
|
|
34
|
+
<SidebarMenuButton tooltip={item.title}>
|
|
35
|
+
{item.icon && <item.icon />}
|
|
36
|
+
<span>{item.title}</span>
|
|
37
|
+
<ChevronRight
|
|
38
|
+
className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
|
39
|
+
</SidebarMenuButton>
|
|
40
|
+
</CollapsibleTrigger>
|
|
41
|
+
<CollapsibleContent>
|
|
42
|
+
<SidebarMenuSub>
|
|
43
|
+
{item.items?.map((subItem) => (
|
|
44
|
+
<SidebarMenuSubItem key={subItem.title}>
|
|
45
|
+
<SidebarMenuSubButton asChild>
|
|
46
|
+
<a href={subItem.url}>
|
|
47
|
+
<span>{subItem.title}</span>
|
|
48
|
+
</a>
|
|
49
|
+
</SidebarMenuSubButton>
|
|
50
|
+
</SidebarMenuSubItem>
|
|
51
|
+
))}
|
|
52
|
+
</SidebarMenuSub>
|
|
53
|
+
</CollapsibleContent>
|
|
54
|
+
</SidebarMenuItem>
|
|
55
|
+
</Collapsible>
|
|
56
|
+
))}
|
|
57
|
+
</SidebarMenu>
|
|
58
|
+
</SidebarGroup>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Folder, Forward, MoreHorizontal, Trash2 } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuSeparator,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from "./ui/dropdown-menu"
|
|
12
|
+
import {
|
|
13
|
+
SidebarGroup,
|
|
14
|
+
SidebarGroupLabel,
|
|
15
|
+
SidebarMenu,
|
|
16
|
+
SidebarMenuAction,
|
|
17
|
+
SidebarMenuButton,
|
|
18
|
+
SidebarMenuItem,
|
|
19
|
+
useSidebar,
|
|
20
|
+
} from "./ui/sidebar"
|
|
21
|
+
|
|
22
|
+
export function NavProjects({
|
|
23
|
+
projects
|
|
24
|
+
}) {
|
|
25
|
+
const { isMobile } = useSidebar()
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
|
29
|
+
<SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
30
|
+
<SidebarMenu>
|
|
31
|
+
{projects.map((item) => (
|
|
32
|
+
<SidebarMenuItem key={item.name}>
|
|
33
|
+
<SidebarMenuButton asChild>
|
|
34
|
+
<a href={item.url}>
|
|
35
|
+
<item.icon />
|
|
36
|
+
<span>{item.name}</span>
|
|
37
|
+
</a>
|
|
38
|
+
</SidebarMenuButton>
|
|
39
|
+
<DropdownMenu>
|
|
40
|
+
<DropdownMenuTrigger asChild>
|
|
41
|
+
<SidebarMenuAction showOnHover>
|
|
42
|
+
<MoreHorizontal />
|
|
43
|
+
<span className="sr-only">More</span>
|
|
44
|
+
</SidebarMenuAction>
|
|
45
|
+
</DropdownMenuTrigger>
|
|
46
|
+
<DropdownMenuContent
|
|
47
|
+
className="w-48 rounded-lg"
|
|
48
|
+
side={isMobile ? "bottom" : "right"}
|
|
49
|
+
align={isMobile ? "end" : "start"}>
|
|
50
|
+
<DropdownMenuItem>
|
|
51
|
+
<Folder className="text-muted-foreground" />
|
|
52
|
+
<span>View Project</span>
|
|
53
|
+
</DropdownMenuItem>
|
|
54
|
+
<DropdownMenuItem>
|
|
55
|
+
<Forward className="text-muted-foreground" />
|
|
56
|
+
<span>Share Project</span>
|
|
57
|
+
</DropdownMenuItem>
|
|
58
|
+
<DropdownMenuSeparator />
|
|
59
|
+
<DropdownMenuItem>
|
|
60
|
+
<Trash2 className="text-muted-foreground" />
|
|
61
|
+
<span>Delete Project</span>
|
|
62
|
+
</DropdownMenuItem>
|
|
63
|
+
</DropdownMenuContent>
|
|
64
|
+
</DropdownMenu>
|
|
65
|
+
</SidebarMenuItem>
|
|
66
|
+
))}
|
|
67
|
+
<SidebarMenuItem>
|
|
68
|
+
<SidebarMenuButton className="text-sidebar-foreground/70">
|
|
69
|
+
<MoreHorizontal className="text-sidebar-foreground/70" />
|
|
70
|
+
<span>More</span>
|
|
71
|
+
</SidebarMenuButton>
|
|
72
|
+
</SidebarMenuItem>
|
|
73
|
+
</SidebarMenu>
|
|
74
|
+
</SidebarGroup>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadgeCheck,
|
|
3
|
+
Bell,
|
|
4
|
+
ChevronsUpDown,
|
|
5
|
+
CreditCard,
|
|
6
|
+
LogOut,
|
|
7
|
+
Sparkles,
|
|
8
|
+
} from "lucide-react"
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Avatar,
|
|
12
|
+
AvatarFallback,
|
|
13
|
+
AvatarImage,
|
|
14
|
+
} from "./ui/avatar"
|
|
15
|
+
import {
|
|
16
|
+
DropdownMenu,
|
|
17
|
+
DropdownMenuContent,
|
|
18
|
+
DropdownMenuGroup,
|
|
19
|
+
DropdownMenuItem,
|
|
20
|
+
DropdownMenuLabel,
|
|
21
|
+
DropdownMenuSeparator,
|
|
22
|
+
DropdownMenuTrigger,
|
|
23
|
+
} from "./ui/dropdown-menu"
|
|
24
|
+
import {
|
|
25
|
+
SidebarMenu,
|
|
26
|
+
SidebarMenuButton,
|
|
27
|
+
SidebarMenuItem,
|
|
28
|
+
useSidebar,
|
|
29
|
+
} from "./ui/sidebar"
|
|
30
|
+
|
|
31
|
+
export function NavUser({
|
|
32
|
+
user
|
|
33
|
+
}) {
|
|
34
|
+
const { isMobile } = useSidebar()
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<SidebarMenu>
|
|
38
|
+
<SidebarMenuItem>
|
|
39
|
+
<DropdownMenu>
|
|
40
|
+
<DropdownMenuTrigger asChild>
|
|
41
|
+
<SidebarMenuButton
|
|
42
|
+
size="lg"
|
|
43
|
+
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
|
44
|
+
<Avatar className="h-8 w-8 rounded-lg">
|
|
45
|
+
<AvatarImage src={user.avatar} alt={user.name} />
|
|
46
|
+
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
47
|
+
</Avatar>
|
|
48
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
49
|
+
<span className="truncate font-semibold">{user.name}</span>
|
|
50
|
+
<span className="truncate text-xs">{user.email}</span>
|
|
51
|
+
</div>
|
|
52
|
+
<ChevronsUpDown className="ml-auto size-4" />
|
|
53
|
+
</SidebarMenuButton>
|
|
54
|
+
</DropdownMenuTrigger>
|
|
55
|
+
<DropdownMenuContent
|
|
56
|
+
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
|
57
|
+
side={isMobile ? "bottom" : "right"}
|
|
58
|
+
align="end"
|
|
59
|
+
sideOffset={4}>
|
|
60
|
+
<DropdownMenuLabel className="p-0 font-normal">
|
|
61
|
+
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
62
|
+
<Avatar className="h-8 w-8 rounded-lg">
|
|
63
|
+
<AvatarImage src={user.avatar} alt={user.name} />
|
|
64
|
+
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
65
|
+
</Avatar>
|
|
66
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
67
|
+
<span className="truncate font-semibold">{user.name}</span>
|
|
68
|
+
<span className="truncate text-xs">{user.email}</span>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</DropdownMenuLabel>
|
|
72
|
+
<DropdownMenuSeparator />
|
|
73
|
+
<DropdownMenuGroup>
|
|
74
|
+
<DropdownMenuItem>
|
|
75
|
+
<Sparkles />
|
|
76
|
+
Upgrade to Pro
|
|
77
|
+
</DropdownMenuItem>
|
|
78
|
+
</DropdownMenuGroup>
|
|
79
|
+
<DropdownMenuSeparator />
|
|
80
|
+
<DropdownMenuGroup>
|
|
81
|
+
<DropdownMenuItem>
|
|
82
|
+
<BadgeCheck />
|
|
83
|
+
Account
|
|
84
|
+
</DropdownMenuItem>
|
|
85
|
+
<DropdownMenuItem>
|
|
86
|
+
<CreditCard />
|
|
87
|
+
Billing
|
|
88
|
+
</DropdownMenuItem>
|
|
89
|
+
<DropdownMenuItem>
|
|
90
|
+
<Bell />
|
|
91
|
+
Notifications
|
|
92
|
+
</DropdownMenuItem>
|
|
93
|
+
</DropdownMenuGroup>
|
|
94
|
+
<DropdownMenuSeparator />
|
|
95
|
+
<DropdownMenuItem>
|
|
96
|
+
<LogOut />
|
|
97
|
+
Log out
|
|
98
|
+
</DropdownMenuItem>
|
|
99
|
+
</DropdownMenuContent>
|
|
100
|
+
</DropdownMenu>
|
|
101
|
+
</SidebarMenuItem>
|
|
102
|
+
</SidebarMenu>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ChevronsUpDown, DoorOpen } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuLabel,
|
|
11
|
+
DropdownMenuSeparator,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
} from "./ui/dropdown-menu"
|
|
14
|
+
import {
|
|
15
|
+
SidebarMenu,
|
|
16
|
+
SidebarMenuButton,
|
|
17
|
+
SidebarMenuItem,
|
|
18
|
+
useSidebar,
|
|
19
|
+
} from "./ui/sidebar"
|
|
20
|
+
|
|
21
|
+
// RoomSwitcher: shows a list of rooms with custom icons; no "Add" entry.
|
|
22
|
+
// Props:
|
|
23
|
+
// - items: Array<{ id?: string, name: string, icon?: React.ComponentType<any> }>
|
|
24
|
+
// - activeId?: string
|
|
25
|
+
// - onChange?: (item) => void
|
|
26
|
+
// - label?: string (default: "Rooms")
|
|
27
|
+
export function RoomSwitcher({ items = [], activeId, onChange, label = "Rooms", isLoading = false, baseColor }) {
|
|
28
|
+
const { isMobile } = useSidebar()
|
|
29
|
+
const initial = React.useMemo(() => {
|
|
30
|
+
if (!items?.length) return null
|
|
31
|
+
return activeId
|
|
32
|
+
? items.find((i) => i.id === activeId) ?? items[0]
|
|
33
|
+
: items[0]
|
|
34
|
+
}, [items, activeId])
|
|
35
|
+
const [active, setActive] = React.useState(initial)
|
|
36
|
+
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
if (!items?.length) return
|
|
39
|
+
if (activeId) {
|
|
40
|
+
const next = items.find((i) => i.id === activeId)
|
|
41
|
+
if (next && next !== active) setActive(next)
|
|
42
|
+
}
|
|
43
|
+
}, [activeId, items])
|
|
44
|
+
|
|
45
|
+
if (isLoading) {
|
|
46
|
+
return (
|
|
47
|
+
<SidebarMenu>
|
|
48
|
+
<SidebarMenuItem>
|
|
49
|
+
<SidebarMenuButton size="lg" className="opacity-60">
|
|
50
|
+
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary/60 text-sidebar-primary-foreground" />
|
|
51
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
52
|
+
<span className="truncate font-semibold">Loading…</span>
|
|
53
|
+
<span className="truncate text-xs">{label}</span>
|
|
54
|
+
</div>
|
|
55
|
+
</SidebarMenuButton>
|
|
56
|
+
</SidebarMenuItem>
|
|
57
|
+
</SidebarMenu>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!active || !items?.length) return null
|
|
62
|
+
|
|
63
|
+
const ActiveIcon = active.icon || DoorOpen
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<SidebarMenu>
|
|
67
|
+
<SidebarMenuItem>
|
|
68
|
+
<DropdownMenu>
|
|
69
|
+
<DropdownMenuTrigger asChild>
|
|
70
|
+
<SidebarMenuButton
|
|
71
|
+
size="lg"
|
|
72
|
+
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
|
73
|
+
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground" style={baseColor ? { backgroundColor: baseColor } : undefined}>
|
|
74
|
+
<ActiveIcon className="size-4" />
|
|
75
|
+
</div>
|
|
76
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
77
|
+
<span className="truncate font-semibold">{active.name}</span>
|
|
78
|
+
<span className="truncate text-xs">{label}</span>
|
|
79
|
+
</div>
|
|
80
|
+
<ChevronsUpDown className="ml-auto" />
|
|
81
|
+
</SidebarMenuButton>
|
|
82
|
+
</DropdownMenuTrigger>
|
|
83
|
+
<DropdownMenuContent
|
|
84
|
+
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
|
85
|
+
align="start"
|
|
86
|
+
side={isMobile ? "bottom" : "right"}
|
|
87
|
+
sideOffset={4}>
|
|
88
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
|
89
|
+
{label}
|
|
90
|
+
</DropdownMenuLabel>
|
|
91
|
+
{items.map((room) => {
|
|
92
|
+
const Icon = room.icon || DoorOpen
|
|
93
|
+
return (
|
|
94
|
+
<DropdownMenuItem
|
|
95
|
+
key={room.id ?? room.name}
|
|
96
|
+
onClick={() => {
|
|
97
|
+
setActive(room)
|
|
98
|
+
onChange?.(room)
|
|
99
|
+
}}
|
|
100
|
+
className="gap-2 p-2">
|
|
101
|
+
<div className="flex size-6 items-center justify-center rounded-sm border">
|
|
102
|
+
<Icon className="size-4 shrink-0" />
|
|
103
|
+
</div>
|
|
104
|
+
{room.name}
|
|
105
|
+
</DropdownMenuItem>
|
|
106
|
+
)
|
|
107
|
+
})}
|
|
108
|
+
<DropdownMenuSeparator />
|
|
109
|
+
</DropdownMenuContent>
|
|
110
|
+
</DropdownMenu>
|
|
111
|
+
</SidebarMenuItem>
|
|
112
|
+
</SidebarMenu>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default RoomSwitcher
|