xertica-ui 1.0.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/App.tsx +182 -0
- package/README.md +330 -0
- package/assets/xertica-logo.svg +38 -0
- package/assets/xertica-x-logo.svg +21 -0
- package/bin/cli.ts +193 -0
- package/components/AssistenteXertica.tsx +2003 -0
- package/components/AudioPlayer.tsx +203 -0
- package/components/CodeBlock.tsx +242 -0
- package/components/DocumentEditor.tsx +504 -0
- package/components/ForgotPasswordPage.tsx +170 -0
- package/components/FormattedDocument.tsx +87 -0
- package/components/HomeContent.tsx +123 -0
- package/components/HomePage.tsx +70 -0
- package/components/LanguageSelector.tsx +54 -0
- package/components/LoginPage.tsx +199 -0
- package/components/MarkdownMessage.tsx +62 -0
- package/components/ModernChatInput.tsx +502 -0
- package/components/PodcastPlayer.tsx +409 -0
- package/components/ResetPasswordPage.tsx +234 -0
- package/components/Sidebar.tsx +489 -0
- package/components/TemplateContent.tsx +629 -0
- package/components/TemplatePage.tsx +70 -0
- package/components/ThemeToggle.tsx +65 -0
- package/components/VerifyEmailPage.tsx +187 -0
- package/components/XerticaLogo.tsx +69 -0
- package/components/XerticaOrbe.tsx +1339 -0
- package/components/XerticaXLogo.tsx +53 -0
- package/components/examples/DrawingMapExample.tsx +530 -0
- package/components/examples/FilterableMapExample.tsx +380 -0
- package/components/examples/LocationPickerExample.tsx +330 -0
- package/components/examples/MapExamples.tsx +280 -0
- package/components/examples/MapShowcase.tsx +446 -0
- package/components/examples/RouteMapExamples.tsx +329 -0
- package/components/examples/SimpleFilterableMap.tsx +192 -0
- package/components/examples/index.ts +52 -0
- package/components/figma/ImageWithFallback.tsx +27 -0
- package/components/index.ts +44 -0
- package/components/media/AudioPlayer.tsx +278 -0
- package/components/media/FloatingMediaWrapper.tsx +166 -0
- package/components/media/VideoPlayer.tsx +285 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +159 -0
- package/components/ui/alert.tsx +91 -0
- package/components/ui/aspect-ratio.tsx +11 -0
- package/components/ui/avatar.tsx +65 -0
- package/components/ui/badge.tsx +55 -0
- package/components/ui/breadcrumb.tsx +109 -0
- package/components/ui/button.tsx +78 -0
- package/components/ui/calendar.tsx +235 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +353 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/collapsible.tsx +33 -0
- package/components/ui/command.tsx +177 -0
- package/components/ui/context-menu.tsx +252 -0
- package/components/ui/dialog.tsx +138 -0
- package/components/ui/drawer.tsx +134 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/empty.tsx +90 -0
- package/components/ui/file-upload.tsx +152 -0
- package/components/ui/form.tsx +195 -0
- package/components/ui/google-maps-loader.tsx +379 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/index.ts +242 -0
- package/components/ui/input-otp.tsx +77 -0
- package/components/ui/input.tsx +38 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/map-config.ts +12 -0
- package/components/ui/map-layers.tsx +129 -0
- package/components/ui/map.exports.ts +31 -0
- package/components/ui/map.tsx +412 -0
- package/components/ui/menubar.tsx +276 -0
- package/components/ui/navigation-menu.tsx +162 -0
- package/components/ui/notification-badge.tsx +61 -0
- package/components/ui/page-header.tsx +229 -0
- package/components/ui/pagination.tsx +127 -0
- package/components/ui/popover.tsx +48 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/radio-group.tsx +56 -0
- package/components/ui/rating.tsx +102 -0
- package/components/ui/resizable.tsx +405 -0
- package/components/ui/route-map.tsx +246 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/search.tsx +70 -0
- package/components/ui/select.tsx +176 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/sidebar.tsx +726 -0
- package/components/ui/simple-map.tsx +92 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/slider.tsx +58 -0
- package/components/ui/sonner.tsx +77 -0
- package/components/ui/stats-card.tsx +84 -0
- package/components/ui/stepper.tsx +126 -0
- package/components/ui/switch.tsx +34 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +66 -0
- package/components/ui/textarea.tsx +26 -0
- package/components/ui/timeline.tsx +140 -0
- package/components/ui/toggle-group.tsx +71 -0
- package/components/ui/toggle.tsx +46 -0
- package/components/ui/tooltip.tsx +61 -0
- package/components/ui/tree-view.tsx +123 -0
- package/components/ui/use-mobile.ts +24 -0
- package/components/ui/utils.ts +6 -0
- package/components/ui/xertica-assistant.tsx +1420 -0
- package/contexts/ApiKeyContext.tsx +123 -0
- package/contexts/AssistenteContext.tsx +118 -0
- package/contexts/BrandColorsContext.tsx +551 -0
- package/contexts/LanguageContext.tsx +36 -0
- package/contexts/ThemeContext.tsx +85 -0
- package/dist/cli.js +20922 -0
- package/eslint.config.js +41 -0
- package/guidelines/Guidelines.md +61 -0
- package/hooks/useTheme.ts +4 -0
- package/imports/Podcast.tsx +389 -0
- package/imports/XerticaAi.tsx +46 -0
- package/imports/XerticaX.tsx +20 -0
- package/imports/svg-aueiaqngck.ts +11 -0
- package/imports/svg-v9krss1ozd.ts +16 -0
- package/imports/svg-vhrdofe3qe.ts +5 -0
- package/index.css +4448 -0
- package/index.html +14 -0
- package/main.tsx +10 -0
- package/package.json +119 -0
- package/postcss.config.js +6 -0
- package/routes.tsx +33 -0
- package/styles/globals.css +15 -0
- package/styles/xertica/app-overrides/chat.css +61 -0
- package/styles/xertica/app-overrides/scrollbar.css +33 -0
- package/styles/xertica/base.css +70 -0
- package/styles/xertica/integrations/google-maps.css +76 -0
- package/styles/xertica/integrations/sonner.css +73 -0
- package/styles/xertica/theme-map.css +88 -0
- package/styles/xertica/tokens.css +190 -0
- package/tsconfig.json +31 -0
- package/tsconfig.node.json +10 -0
- package/utils/gemini.ts +140 -0
- package/vite-env.d.ts +12 -0
- package/vite.config.ts +36 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Menu,
|
|
4
|
+
ArrowLeft,
|
|
5
|
+
LogOut,
|
|
6
|
+
Settings,
|
|
7
|
+
MoreHorizontal,
|
|
8
|
+
ChevronRight,
|
|
9
|
+
} from "lucide-react";
|
|
10
|
+
import { routes } from "../routes";
|
|
11
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
12
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
|
13
|
+
import { Tooltip, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
|
|
14
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
15
|
+
import { cn } from "./ui/utils";
|
|
16
|
+
import { XerticaLogo } from "./XerticaLogo";
|
|
17
|
+
import { XerticaXLogo } from "./XerticaXLogo";
|
|
18
|
+
|
|
19
|
+
// TooltipContent customizado para a Sidebar com seta branca
|
|
20
|
+
function SidebarTooltipContent({
|
|
21
|
+
className,
|
|
22
|
+
sideOffset = 0,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
26
|
+
return (
|
|
27
|
+
<TooltipPrimitive.Content
|
|
28
|
+
data-slot="tooltip-content"
|
|
29
|
+
sideOffset={sideOffset}
|
|
30
|
+
className={cn(
|
|
31
|
+
"bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
<TooltipPrimitive.Arrow
|
|
38
|
+
className="fill-popover z-50 drop-shadow-sm"
|
|
39
|
+
width={8}
|
|
40
|
+
height={4}
|
|
41
|
+
/>
|
|
42
|
+
</TooltipPrimitive.Content>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface SubMenuItem {
|
|
47
|
+
id: string;
|
|
48
|
+
label: string;
|
|
49
|
+
path: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface NavigationItem {
|
|
53
|
+
path: string;
|
|
54
|
+
label: string;
|
|
55
|
+
icon: any;
|
|
56
|
+
active: boolean;
|
|
57
|
+
subItems?: SubMenuItem[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SidebarProps {
|
|
61
|
+
expanded: boolean;
|
|
62
|
+
onToggle: () => void;
|
|
63
|
+
user: { email: string } | null;
|
|
64
|
+
onLogout: () => void;
|
|
65
|
+
location: { pathname: string };
|
|
66
|
+
navigate: (path: string) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function Sidebar({
|
|
70
|
+
expanded,
|
|
71
|
+
onToggle,
|
|
72
|
+
user,
|
|
73
|
+
onLogout,
|
|
74
|
+
location,
|
|
75
|
+
navigate,
|
|
76
|
+
}: SidebarProps) {
|
|
77
|
+
const navRef = useRef<HTMLDivElement>(null);
|
|
78
|
+
const [hasOverflow, setHasOverflow] = useState(false);
|
|
79
|
+
const [visibleItems, setVisibleItems] = useState<NavigationItem[]>([]);
|
|
80
|
+
const [overflowItems, setOverflowItems] = useState<NavigationItem[]>([]);
|
|
81
|
+
const [openSubmenu, setOpenSubmenu] = useState<string | null>(null);
|
|
82
|
+
|
|
83
|
+
// Tradução direta para português
|
|
84
|
+
const labelTranslations: Record<string, string> = {
|
|
85
|
+
home: "Início",
|
|
86
|
+
dashboard: "Dashboard",
|
|
87
|
+
components: "Componentes",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Exemplo de subitens - você pode passar isso como prop também
|
|
91
|
+
const subItemsExample: Record<string, SubMenuItem[]> = {
|
|
92
|
+
"/components": [
|
|
93
|
+
{ id: "buttons", label: "Botões", path: "/components/buttons" },
|
|
94
|
+
{ id: "forms", label: "Formulários", path: "/components/forms" },
|
|
95
|
+
{ id: "tables", label: "Tabelas", path: "/components/tables" },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const navigationItems: NavigationItem[] = routes.map((route) => ({
|
|
100
|
+
...route,
|
|
101
|
+
label:
|
|
102
|
+
labelTranslations[route.label.toLowerCase()] ||
|
|
103
|
+
route.label,
|
|
104
|
+
active: location.pathname === route.path || location.pathname.startsWith(route.path + "/"),
|
|
105
|
+
subItems: subItemsExample[route.path],
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
const isSettingsActive = location.pathname === "/settings";
|
|
109
|
+
|
|
110
|
+
// Detectar overflow vertical
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const checkOverflow = () => {
|
|
113
|
+
if (!navRef.current) return;
|
|
114
|
+
|
|
115
|
+
const navHeight = navRef.current.clientHeight;
|
|
116
|
+
const itemHeight = 44; // h-10 + gap
|
|
117
|
+
const maxVisibleItems = Math.floor(navHeight / itemHeight);
|
|
118
|
+
|
|
119
|
+
if (navigationItems.length > maxVisibleItems) {
|
|
120
|
+
setHasOverflow(true);
|
|
121
|
+
setVisibleItems(navigationItems.slice(0, maxVisibleItems - 1));
|
|
122
|
+
setOverflowItems(navigationItems.slice(maxVisibleItems - 1));
|
|
123
|
+
} else {
|
|
124
|
+
setHasOverflow(false);
|
|
125
|
+
setVisibleItems(navigationItems);
|
|
126
|
+
setOverflowItems([]);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
checkOverflow();
|
|
131
|
+
window.addEventListener("resize", checkOverflow);
|
|
132
|
+
return () => window.removeEventListener("resize", checkOverflow);
|
|
133
|
+
}, [navigationItems.length]);
|
|
134
|
+
|
|
135
|
+
const handleNavigate = (path: string) => {
|
|
136
|
+
navigate(path);
|
|
137
|
+
setOpenSubmenu(null);
|
|
138
|
+
// Fecha o menu no mobile após navegar
|
|
139
|
+
if (window.innerWidth < 768) {
|
|
140
|
+
onToggle();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const renderMenuItem = (item: NavigationItem, showEllipsis: boolean = false) => {
|
|
145
|
+
const Icon = item.icon;
|
|
146
|
+
const hasSubItems = item.subItems && item.subItems.length > 0;
|
|
147
|
+
|
|
148
|
+
const buttonContent = (
|
|
149
|
+
<button
|
|
150
|
+
onClick={hasSubItems ? undefined : () => handleNavigate(item.path)}
|
|
151
|
+
className={
|
|
152
|
+
expanded
|
|
153
|
+
? item.active
|
|
154
|
+
? "w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 bg-sidebar-foreground/15 text-sidebar-foreground shadow-sm"
|
|
155
|
+
: "w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
156
|
+
: "w-full h-10 flex items-center justify-center px-0 rounded-[var(--radius-button)] transition-all duration-200 " +
|
|
157
|
+
(item.active
|
|
158
|
+
? "bg-sidebar-foreground/15 text-sidebar-foreground shadow-sm"
|
|
159
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground")
|
|
160
|
+
}
|
|
161
|
+
>
|
|
162
|
+
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
163
|
+
{expanded && (
|
|
164
|
+
<>
|
|
165
|
+
<span className="truncate text-sidebar-foreground flex-1 text-left">
|
|
166
|
+
{item.label}
|
|
167
|
+
</span>
|
|
168
|
+
{hasSubItems && (
|
|
169
|
+
<MoreHorizontal className="w-4 h-4 flex-shrink-0 text-sidebar-foreground/60" />
|
|
170
|
+
)}
|
|
171
|
+
</>
|
|
172
|
+
)}
|
|
173
|
+
</button>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (hasSubItems || showEllipsis) {
|
|
177
|
+
return (
|
|
178
|
+
<Popover
|
|
179
|
+
key={item.path}
|
|
180
|
+
open={openSubmenu === item.path}
|
|
181
|
+
onOpenChange={(open) => setOpenSubmenu(open ? item.path : null)}
|
|
182
|
+
>
|
|
183
|
+
<PopoverTrigger asChild>
|
|
184
|
+
{!expanded ? (
|
|
185
|
+
<Tooltip>
|
|
186
|
+
<TooltipTrigger asChild>
|
|
187
|
+
{buttonContent}
|
|
188
|
+
</TooltipTrigger>
|
|
189
|
+
<SidebarTooltipContent side="right" sideOffset={0}>
|
|
190
|
+
<p>{item.label}</p>
|
|
191
|
+
</SidebarTooltipContent>
|
|
192
|
+
</Tooltip>
|
|
193
|
+
) : (
|
|
194
|
+
buttonContent
|
|
195
|
+
)}
|
|
196
|
+
</PopoverTrigger>
|
|
197
|
+
<PopoverContent
|
|
198
|
+
side="right"
|
|
199
|
+
align="start"
|
|
200
|
+
className="w-56 p-2 bg-popover border border-border rounded-[var(--radius-card)] shadow-lg"
|
|
201
|
+
sideOffset={8}
|
|
202
|
+
>
|
|
203
|
+
<div className="space-y-1">
|
|
204
|
+
{/* Item principal clicável */}
|
|
205
|
+
{!showEllipsis && (
|
|
206
|
+
<button
|
|
207
|
+
onClick={() => handleNavigate(item.path)}
|
|
208
|
+
className="w-full h-9 flex items-center gap-2 px-3 rounded-[var(--radius-button)] transition-all duration-200 text-popover-foreground/80 hover:bg-accent hover:text-accent-foreground text-left"
|
|
209
|
+
>
|
|
210
|
+
<span className="truncate">{item.label}</span>
|
|
211
|
+
</button>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{/* Subitens */}
|
|
215
|
+
{hasSubItems && item.subItems && (
|
|
216
|
+
<>
|
|
217
|
+
{!showEllipsis && (
|
|
218
|
+
<div className="h-px bg-border my-1" />
|
|
219
|
+
)}
|
|
220
|
+
{item.subItems.map((subItem) => (
|
|
221
|
+
<button
|
|
222
|
+
key={subItem.id}
|
|
223
|
+
onClick={() => handleNavigate(subItem.path)}
|
|
224
|
+
className="w-full h-9 flex items-center gap-2 px-3 pl-5 rounded-[var(--radius-button)] transition-all duration-200 text-popover-foreground/70 hover:bg-accent hover:text-accent-foreground text-left"
|
|
225
|
+
>
|
|
226
|
+
<ChevronRight className="w-3 h-3 flex-shrink-0" />
|
|
227
|
+
<span className="truncate text-sm">{subItem.label}</span>
|
|
228
|
+
</button>
|
|
229
|
+
))}
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</PopoverContent>
|
|
234
|
+
</Popover>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Item normal sem subitens
|
|
239
|
+
const simpleButton = (
|
|
240
|
+
<button
|
|
241
|
+
key={item.path}
|
|
242
|
+
onClick={() => handleNavigate(item.path)}
|
|
243
|
+
className={
|
|
244
|
+
expanded
|
|
245
|
+
? item.active
|
|
246
|
+
? "w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 bg-sidebar-foreground/15 text-sidebar-foreground shadow-sm"
|
|
247
|
+
: "w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
248
|
+
: "w-full h-10 flex items-center justify-center px-0 rounded-[var(--radius-button)] transition-all duration-200 " +
|
|
249
|
+
(item.active
|
|
250
|
+
? "bg-sidebar-foreground/15 text-sidebar-foreground shadow-sm"
|
|
251
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground")
|
|
252
|
+
}
|
|
253
|
+
>
|
|
254
|
+
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
255
|
+
{expanded && (
|
|
256
|
+
<span className="truncate text-sidebar-foreground">
|
|
257
|
+
{item.label}
|
|
258
|
+
</span>
|
|
259
|
+
)}
|
|
260
|
+
</button>
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// Adiciona tooltip quando colapsado
|
|
264
|
+
if (!expanded) {
|
|
265
|
+
return (
|
|
266
|
+
<Tooltip key={item.path}>
|
|
267
|
+
<TooltipTrigger asChild>
|
|
268
|
+
{simpleButton}
|
|
269
|
+
</TooltipTrigger>
|
|
270
|
+
<SidebarTooltipContent side="right" sideOffset={0}>
|
|
271
|
+
<p>{item.label}</p>
|
|
272
|
+
</SidebarTooltipContent>
|
|
273
|
+
</Tooltip>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return simpleButton;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<TooltipProvider delayDuration={300}>
|
|
282
|
+
{/* Sidebar */}
|
|
283
|
+
<div
|
|
284
|
+
className={`bg-sidebar text-sidebar-foreground transition-all duration-300 ease-in-out flex flex-col z-50 ${
|
|
285
|
+
expanded
|
|
286
|
+
? "fixed inset-0 md:fixed md:inset-y-0 md:left-0 md:w-64"
|
|
287
|
+
: "fixed inset-y-0 left-0 w-20 -translate-x-full md:translate-x-0"
|
|
288
|
+
}`}
|
|
289
|
+
>
|
|
290
|
+
{/* Botão Toggle Menu */}
|
|
291
|
+
<div className="p-[14px] pt-[13px] pr-[14px] pb-[12px] pl-[14px]">
|
|
292
|
+
<button
|
|
293
|
+
onClick={onToggle}
|
|
294
|
+
className="w-full h-10 flex items-center gap-3 px-3 justify-center rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
295
|
+
>
|
|
296
|
+
{expanded ? (
|
|
297
|
+
<ArrowLeft className="w-5 h-5" />
|
|
298
|
+
) : (
|
|
299
|
+
<Menu className="w-5 h-5" />
|
|
300
|
+
)}
|
|
301
|
+
</button>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
{/* Logo */}
|
|
305
|
+
<div className="px-4 py-4">
|
|
306
|
+
<div
|
|
307
|
+
className={`flex items-center h-10 ${expanded ? "justify-center" : "justify-center"}`}
|
|
308
|
+
>
|
|
309
|
+
<div className="flex items-center justify-center flex-shrink-0">
|
|
310
|
+
{expanded ? (
|
|
311
|
+
<XerticaLogo
|
|
312
|
+
className="h-5 w-auto"
|
|
313
|
+
variant="white"
|
|
314
|
+
/>
|
|
315
|
+
) : (
|
|
316
|
+
<XerticaXLogo
|
|
317
|
+
className="h-5 w-auto"
|
|
318
|
+
variant="white"
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
{/* Navegação */}
|
|
326
|
+
<nav className="flex-1 px-4 py-4 overflow-hidden" ref={navRef}>
|
|
327
|
+
<div className="space-y-1">
|
|
328
|
+
{/* Itens visíveis */}
|
|
329
|
+
{(hasOverflow ? visibleItems : navigationItems).map((item) =>
|
|
330
|
+
renderMenuItem(item)
|
|
331
|
+
)}
|
|
332
|
+
|
|
333
|
+
{/* Botão de overflow (ellipsis) */}
|
|
334
|
+
{hasOverflow && (
|
|
335
|
+
<Popover>
|
|
336
|
+
<PopoverTrigger asChild>
|
|
337
|
+
<button
|
|
338
|
+
className={
|
|
339
|
+
expanded
|
|
340
|
+
? "w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
341
|
+
: "w-full h-10 flex items-center justify-center px-0 rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
342
|
+
}
|
|
343
|
+
>
|
|
344
|
+
<MoreHorizontal className="w-5 h-5 flex-shrink-0" />
|
|
345
|
+
{expanded && (
|
|
346
|
+
<span className="truncate text-sidebar-foreground">
|
|
347
|
+
Mais opções
|
|
348
|
+
</span>
|
|
349
|
+
)}
|
|
350
|
+
</button>
|
|
351
|
+
</PopoverTrigger>
|
|
352
|
+
<PopoverContent
|
|
353
|
+
side="right"
|
|
354
|
+
align="start"
|
|
355
|
+
className="w-56 p-2 bg-popover border border-border rounded-[var(--radius-card)] shadow-lg"
|
|
356
|
+
sideOffset={8}
|
|
357
|
+
>
|
|
358
|
+
<div className="space-y-1">
|
|
359
|
+
{overflowItems.map((item) => {
|
|
360
|
+
const Icon = item.icon;
|
|
361
|
+
return (
|
|
362
|
+
<button
|
|
363
|
+
key={item.path}
|
|
364
|
+
onClick={() => handleNavigate(item.path)}
|
|
365
|
+
className="w-full h-9 flex items-center gap-2 px-3 rounded-[var(--radius-button)] transition-all duration-200 text-popover-foreground/80 hover:bg-accent hover:text-accent-foreground text-left"
|
|
366
|
+
>
|
|
367
|
+
<Icon className="w-4 h-4 flex-shrink-0" />
|
|
368
|
+
<span className="truncate">{item.label}</span>
|
|
369
|
+
</button>
|
|
370
|
+
);
|
|
371
|
+
})}
|
|
372
|
+
</div>
|
|
373
|
+
</PopoverContent>
|
|
374
|
+
</Popover>
|
|
375
|
+
)}
|
|
376
|
+
</div>
|
|
377
|
+
</nav>
|
|
378
|
+
|
|
379
|
+
{/* Footer da sidebar - Usuário */}
|
|
380
|
+
<div className="p-4 space-y-2">
|
|
381
|
+
{/* Avatar do usuário */}
|
|
382
|
+
{!expanded ? (
|
|
383
|
+
<Tooltip>
|
|
384
|
+
<TooltipTrigger asChild>
|
|
385
|
+
<button
|
|
386
|
+
className="w-full h-10 flex items-center justify-center px-0 rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
387
|
+
>
|
|
388
|
+
<Avatar className="w-7 h-7 flex-shrink-0">
|
|
389
|
+
<AvatarFallback className="bg-sidebar-foreground/15 text-sidebar-foreground text-xs">
|
|
390
|
+
A
|
|
391
|
+
</AvatarFallback>
|
|
392
|
+
</Avatar>
|
|
393
|
+
</button>
|
|
394
|
+
</TooltipTrigger>
|
|
395
|
+
<SidebarTooltipContent side="right" sideOffset={0}>
|
|
396
|
+
<p>Ariel</p>
|
|
397
|
+
</SidebarTooltipContent>
|
|
398
|
+
</Tooltip>
|
|
399
|
+
) : (
|
|
400
|
+
<button
|
|
401
|
+
className="w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
402
|
+
>
|
|
403
|
+
<Avatar className="w-7 h-7 flex-shrink-0">
|
|
404
|
+
<AvatarFallback className="bg-sidebar-foreground/15 text-sidebar-foreground text-xs">
|
|
405
|
+
A
|
|
406
|
+
</AvatarFallback>
|
|
407
|
+
</Avatar>
|
|
408
|
+
<span className="text-sidebar-foreground truncate">
|
|
409
|
+
Ariel
|
|
410
|
+
</span>
|
|
411
|
+
</button>
|
|
412
|
+
)}
|
|
413
|
+
|
|
414
|
+
{/* Botão Configurações */}
|
|
415
|
+
{!expanded ? (
|
|
416
|
+
<Tooltip>
|
|
417
|
+
<TooltipTrigger asChild>
|
|
418
|
+
<button
|
|
419
|
+
onClick={() => {
|
|
420
|
+
navigate("/settings");
|
|
421
|
+
if (window.innerWidth < 768) {
|
|
422
|
+
onToggle();
|
|
423
|
+
}
|
|
424
|
+
}}
|
|
425
|
+
className={"w-full h-10 flex items-center justify-center px-0 rounded-[var(--radius-button)] transition-all duration-200 " +
|
|
426
|
+
(isSettingsActive
|
|
427
|
+
? "bg-sidebar-foreground/15 text-sidebar-foreground shadow-sm"
|
|
428
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground")
|
|
429
|
+
}
|
|
430
|
+
>
|
|
431
|
+
<Settings className="w-5 h-5 flex-shrink-0" />
|
|
432
|
+
</button>
|
|
433
|
+
</TooltipTrigger>
|
|
434
|
+
<SidebarTooltipContent side="right" sideOffset={0}>
|
|
435
|
+
<p>Configurações</p>
|
|
436
|
+
</SidebarTooltipContent>
|
|
437
|
+
</Tooltip>
|
|
438
|
+
) : (
|
|
439
|
+
<button
|
|
440
|
+
onClick={() => {
|
|
441
|
+
navigate("/settings");
|
|
442
|
+
if (window.innerWidth < 768) {
|
|
443
|
+
onToggle();
|
|
444
|
+
}
|
|
445
|
+
}}
|
|
446
|
+
className={
|
|
447
|
+
isSettingsActive
|
|
448
|
+
? "w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 bg-sidebar-foreground/15 text-sidebar-foreground shadow-sm"
|
|
449
|
+
: "w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
450
|
+
}
|
|
451
|
+
>
|
|
452
|
+
<Settings className="w-5 h-5 flex-shrink-0" />
|
|
453
|
+
<span className="truncate text-sidebar-foreground">
|
|
454
|
+
Configurações
|
|
455
|
+
</span>
|
|
456
|
+
</button>
|
|
457
|
+
)}
|
|
458
|
+
|
|
459
|
+
{/* Botão Sair */}
|
|
460
|
+
{!expanded ? (
|
|
461
|
+
<Tooltip>
|
|
462
|
+
<TooltipTrigger asChild>
|
|
463
|
+
<button
|
|
464
|
+
onClick={onLogout}
|
|
465
|
+
className="w-full h-10 flex items-center justify-center px-0 rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
466
|
+
>
|
|
467
|
+
<LogOut className="w-5 h-5 flex-shrink-0" />
|
|
468
|
+
</button>
|
|
469
|
+
</TooltipTrigger>
|
|
470
|
+
<SidebarTooltipContent side="right" sideOffset={0}>
|
|
471
|
+
<p>Sair</p>
|
|
472
|
+
</SidebarTooltipContent>
|
|
473
|
+
</Tooltip>
|
|
474
|
+
) : (
|
|
475
|
+
<button
|
|
476
|
+
onClick={onLogout}
|
|
477
|
+
className="w-full h-10 flex items-center gap-3 px-3 justify-start rounded-[var(--radius-button)] transition-all duration-200 text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground"
|
|
478
|
+
>
|
|
479
|
+
<LogOut className="w-5 h-5 flex-shrink-0" />
|
|
480
|
+
<span className="truncate text-sidebar-foreground">
|
|
481
|
+
Sair
|
|
482
|
+
</span>
|
|
483
|
+
</button>
|
|
484
|
+
)}
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
487
|
+
</TooltipProvider>
|
|
488
|
+
);
|
|
489
|
+
}
|