xertica-ui 1.4.13 → 1.5.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/components/Sidebar.tsx +310 -57
- package/components/TemplateContent.tsx +130 -1
- package/dist/components/Sidebar.d.ts +38 -2
- package/dist/index.es.js +280 -38
- package/dist/index.umd.js +279 -37
- package/dist/xertica-ui.css +1 -1
- package/package.json +1 -1
- package/templates/package.json +1 -1
- package/templates/src/app/pages/Template/TemplateContent.tsx +139 -0
package/components/Sidebar.tsx
CHANGED
|
@@ -4,14 +4,28 @@ import {
|
|
|
4
4
|
ArrowLeft,
|
|
5
5
|
LogOut,
|
|
6
6
|
Settings,
|
|
7
|
-
|
|
7
|
+
MoreVertical,
|
|
8
8
|
ChevronRight,
|
|
9
|
+
Filter,
|
|
9
10
|
} from "lucide-react";
|
|
10
11
|
// routes import removed to make it reusable
|
|
11
12
|
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
12
13
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
|
13
14
|
import { Tooltip, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
|
|
14
15
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
16
|
+
import {
|
|
17
|
+
DropdownMenu,
|
|
18
|
+
DropdownMenuContent,
|
|
19
|
+
DropdownMenuItem,
|
|
20
|
+
DropdownMenuTrigger,
|
|
21
|
+
DropdownMenuSub,
|
|
22
|
+
DropdownMenuSubTrigger,
|
|
23
|
+
DropdownMenuSubContent,
|
|
24
|
+
DropdownMenuPortal,
|
|
25
|
+
} from "./ui/dropdown-menu";
|
|
26
|
+
import { ScrollArea } from "./ui/scroll-area";
|
|
27
|
+
import { Input } from "./ui/input";
|
|
28
|
+
import { Search as SearchIcon } from "lucide-react";
|
|
15
29
|
import { cn } from "./ui/utils";
|
|
16
30
|
import { XerticaLogo } from "./XerticaLogo";
|
|
17
31
|
import { XerticaXLogo } from "./XerticaXLogo";
|
|
@@ -45,17 +59,29 @@ function SidebarTooltipContent({
|
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
// Interface for Route Config (copied/imported type)
|
|
62
|
+
export interface ActionMenuItem {
|
|
63
|
+
label: string;
|
|
64
|
+
icon?: React.ComponentType<any>;
|
|
65
|
+
onClick?: (item: any) => void;
|
|
66
|
+
variant?: "default" | "destructive";
|
|
67
|
+
children?: ActionMenuItem[];
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
export interface RouteConfig {
|
|
49
71
|
path: string;
|
|
50
72
|
label: string;
|
|
51
73
|
icon: React.ComponentType<any>;
|
|
52
74
|
component?: React.ComponentType<any>;
|
|
75
|
+
actions?: ActionMenuItem[]; // Menus de hover (elipses)
|
|
76
|
+
description?: React.ReactNode; // Conteúdo auxiliar quando selecionado
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
interface SubMenuItem {
|
|
56
80
|
id: string;
|
|
57
81
|
label: string;
|
|
58
82
|
path: string;
|
|
83
|
+
icon?: React.ComponentType<any>;
|
|
84
|
+
actions?: ActionMenuItem[];
|
|
59
85
|
}
|
|
60
86
|
|
|
61
87
|
interface NavigationItem {
|
|
@@ -66,6 +92,33 @@ interface NavigationItem {
|
|
|
66
92
|
subItems?: SubMenuItem[];
|
|
67
93
|
}
|
|
68
94
|
|
|
95
|
+
export interface SidebarFilterConfig {
|
|
96
|
+
show: boolean;
|
|
97
|
+
content?: React.ReactNode;
|
|
98
|
+
icon?: React.ReactNode;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SidebarSearchConfig {
|
|
102
|
+
show: boolean;
|
|
103
|
+
placeholder?: string;
|
|
104
|
+
value?: string;
|
|
105
|
+
onChange?: (value: string) => void;
|
|
106
|
+
filter?: SidebarFilterConfig;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface SidebarFixedAreaConfig {
|
|
110
|
+
show: boolean;
|
|
111
|
+
content?: React.ReactNode;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface RouteGroup {
|
|
115
|
+
id: string;
|
|
116
|
+
label?: string; // Título do grupo
|
|
117
|
+
icon?: React.ComponentType<any>; // Ícone do grupo
|
|
118
|
+
items: RouteConfig[];
|
|
119
|
+
actions?: ActionMenuItem[]; // Menu contextual para o grupo inteiro
|
|
120
|
+
}
|
|
121
|
+
|
|
69
122
|
interface SidebarProps {
|
|
70
123
|
expanded: boolean;
|
|
71
124
|
onToggle: () => void;
|
|
@@ -73,9 +126,17 @@ interface SidebarProps {
|
|
|
73
126
|
onLogout: () => void;
|
|
74
127
|
location: { pathname: string };
|
|
75
128
|
navigate: (path: string) => void;
|
|
76
|
-
routes
|
|
129
|
+
routes?: RouteConfig[]; // tornado opcional devido ao assistant
|
|
77
130
|
logo?: React.ReactNode;
|
|
78
131
|
logoCollapsed?: React.ReactNode;
|
|
132
|
+
|
|
133
|
+
// Prop para definir qual comportamento renderizar
|
|
134
|
+
variant?: "default" | "assistant";
|
|
135
|
+
|
|
136
|
+
// Específicos do Assistant
|
|
137
|
+
fixedArea?: SidebarFixedAreaConfig;
|
|
138
|
+
search?: SidebarSearchConfig;
|
|
139
|
+
navigationGroups?: RouteGroup[];
|
|
79
140
|
}
|
|
80
141
|
|
|
81
142
|
export function Sidebar({
|
|
@@ -88,8 +149,13 @@ export function Sidebar({
|
|
|
88
149
|
routes,
|
|
89
150
|
logo,
|
|
90
151
|
logoCollapsed,
|
|
152
|
+
variant = "default",
|
|
153
|
+
fixedArea,
|
|
154
|
+
search,
|
|
155
|
+
navigationGroups = [],
|
|
91
156
|
}: SidebarProps) {
|
|
92
157
|
const navRef = useRef<HTMLDivElement>(null);
|
|
158
|
+
const [localActiveItem, setLocalActiveItem] = useState<string | null>(null);
|
|
93
159
|
const [hasOverflow, setHasOverflow] = useState(false);
|
|
94
160
|
const [visibleItems, setVisibleItems] = useState<NavigationItem[]>([]);
|
|
95
161
|
const [overflowItems, setOverflowItems] = useState<NavigationItem[]>([]);
|
|
@@ -111,7 +177,7 @@ export function Sidebar({
|
|
|
111
177
|
],
|
|
112
178
|
};
|
|
113
179
|
|
|
114
|
-
const navigationItems: NavigationItem[] = routes.map((route) => ({
|
|
180
|
+
const navigationItems: NavigationItem[] = (routes || []).map((route) => ({
|
|
115
181
|
...route,
|
|
116
182
|
label:
|
|
117
183
|
labelTranslations[route.label.toLowerCase()] ||
|
|
@@ -126,6 +192,7 @@ export function Sidebar({
|
|
|
126
192
|
useEffect(() => {
|
|
127
193
|
const checkOverflow = () => {
|
|
128
194
|
if (!navRef.current) return;
|
|
195
|
+
if (variant === "assistant") return;
|
|
129
196
|
|
|
130
197
|
const navHeight = navRef.current.clientHeight;
|
|
131
198
|
const itemHeight = 44; // h-10 + gap
|
|
@@ -148,6 +215,7 @@ export function Sidebar({
|
|
|
148
215
|
}, [navigationItems.length]);
|
|
149
216
|
|
|
150
217
|
const handleNavigate = (path: string) => {
|
|
218
|
+
setLocalActiveItem(path);
|
|
151
219
|
navigate(path);
|
|
152
220
|
setOpenSubmenu(null);
|
|
153
221
|
// Fecha o menu no mobile após navegar
|
|
@@ -181,7 +249,7 @@ export function Sidebar({
|
|
|
181
249
|
{item.label}
|
|
182
250
|
</span>
|
|
183
251
|
{hasSubItems && (
|
|
184
|
-
<
|
|
252
|
+
<MoreVertical className="w-4 h-4 flex-shrink-0 text-sidebar-foreground/60" />
|
|
185
253
|
)}
|
|
186
254
|
</>
|
|
187
255
|
)}
|
|
@@ -292,6 +360,132 @@ export function Sidebar({
|
|
|
292
360
|
return simpleButton;
|
|
293
361
|
};
|
|
294
362
|
|
|
363
|
+
const renderActionItems = (actions: ActionMenuItem[]) => {
|
|
364
|
+
return actions.map((action, idx) => {
|
|
365
|
+
const Icon = action.icon;
|
|
366
|
+
|
|
367
|
+
if (action.children && action.children.length > 0) {
|
|
368
|
+
return (
|
|
369
|
+
<DropdownMenuSub key={idx}>
|
|
370
|
+
<DropdownMenuSubTrigger>
|
|
371
|
+
{Icon && <Icon className="mr-2 h-4 w-4" />}
|
|
372
|
+
<span>{action.label}</span>
|
|
373
|
+
</DropdownMenuSubTrigger>
|
|
374
|
+
<DropdownMenuPortal>
|
|
375
|
+
<DropdownMenuSubContent className="w-48 bg-popover border-border">
|
|
376
|
+
{renderActionItems(action.children)}
|
|
377
|
+
</DropdownMenuSubContent>
|
|
378
|
+
</DropdownMenuPortal>
|
|
379
|
+
</DropdownMenuSub>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<DropdownMenuItem
|
|
385
|
+
key={idx}
|
|
386
|
+
className={cn(
|
|
387
|
+
"flex items-center gap-2",
|
|
388
|
+
action.variant === "destructive" ? "text-destructive focus:text-destructive" : ""
|
|
389
|
+
)}
|
|
390
|
+
onClick={(e) => {
|
|
391
|
+
e.stopPropagation();
|
|
392
|
+
action.onClick?.(null);
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
{Icon && <Icon className="h-4 w-4 flex-shrink-0" />}
|
|
396
|
+
<span>{action.label}</span>
|
|
397
|
+
</DropdownMenuItem>
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const renderAssistantActionMenu = (actions?: ActionMenuItem[], isHeader: boolean = false) => {
|
|
403
|
+
if (!actions || actions.length === 0) return null;
|
|
404
|
+
|
|
405
|
+
return (
|
|
406
|
+
<DropdownMenu>
|
|
407
|
+
<DropdownMenuTrigger asChild>
|
|
408
|
+
<Button
|
|
409
|
+
variant="ghost"
|
|
410
|
+
size="icon"
|
|
411
|
+
className={cn(
|
|
412
|
+
"h-8 w-8 text-sidebar-foreground/60 hover:bg-sidebar-foreground/20 hover:text-sidebar-foreground rounded-full transition-all",
|
|
413
|
+
!isHeader && "opacity-0 group-hover/item:opacity-100"
|
|
414
|
+
)}
|
|
415
|
+
>
|
|
416
|
+
<MoreVertical className="h-4 w-4" />
|
|
417
|
+
</Button>
|
|
418
|
+
</DropdownMenuTrigger>
|
|
419
|
+
<DropdownMenuContent align="end" className="w-48 bg-popover border-border p-1">
|
|
420
|
+
{renderActionItems(actions)}
|
|
421
|
+
</DropdownMenuContent>
|
|
422
|
+
</DropdownMenu>
|
|
423
|
+
);
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const renderAssistantGroup = (group: RouteGroup) => {
|
|
427
|
+
return (
|
|
428
|
+
<div key={group.id} className="py-2 group">
|
|
429
|
+
{(group.label || group.icon) && (
|
|
430
|
+
<div className="flex items-center justify-between px-3 mb-1">
|
|
431
|
+
<div className="flex items-center gap-2 text-sidebar-foreground/60 text-xs font-semibold uppercase tracking-wider">
|
|
432
|
+
{group.icon && <group.icon className="h-4 w-4" />}
|
|
433
|
+
{expanded && group.label && <span>{group.label}</span>}
|
|
434
|
+
</div>
|
|
435
|
+
{expanded && renderAssistantActionMenu(group.actions, true)}
|
|
436
|
+
</div>
|
|
437
|
+
)}
|
|
438
|
+
<div className="space-y-1">
|
|
439
|
+
{group.items.map(item => {
|
|
440
|
+
const isRouteActive = location.pathname === item.path || location.pathname.startsWith(item.path + "/");
|
|
441
|
+
const isActive = isRouteActive || localActiveItem === item.path;
|
|
442
|
+
const Icon = item.icon;
|
|
443
|
+
|
|
444
|
+
const simpleBtn = (
|
|
445
|
+
<div
|
|
446
|
+
className={expanded
|
|
447
|
+
? `group/item flex items-start justify-between px-3 min-h-[36px] py-2.5 rounded-[var(--radius-button)] cursor-pointer transition-all duration-200 ${isActive ? 'bg-sidebar-foreground/15 text-sidebar-foreground' : 'text-sidebar-foreground/80 hover:bg-sidebar-foreground/10 hover:text-sidebar-foreground'}`
|
|
448
|
+
: `group/item flex items-center justify-center h-10 px-0 rounded-[var(--radius-button)] cursor-pointer transition-all duration-200 ${isActive ? 'bg-sidebar-foreground/15 text-sidebar-foreground' : 'text-sidebar-foreground/80 hover:bg-sidebar-foreground/10 hover:text-sidebar-foreground'}`
|
|
449
|
+
}
|
|
450
|
+
onClick={() => handleNavigate(item.path)}
|
|
451
|
+
>
|
|
452
|
+
<div className="flex flex-col min-w-0 flex-1">
|
|
453
|
+
<div className="flex items-center gap-3 overflow-hidden h-5">
|
|
454
|
+
<Icon className="w-4 h-4 flex-shrink-0" />
|
|
455
|
+
{expanded && <span className="truncate text-sm font-medium leading-none">{item.label}</span>}
|
|
456
|
+
</div>
|
|
457
|
+
{expanded && isActive && item.description && (
|
|
458
|
+
<div className="ml-7 text-[11px] text-sidebar-foreground/60 mt-1.5 animate-in fade-in slide-in-from-top-1 duration-200">
|
|
459
|
+
{item.description}
|
|
460
|
+
</div>
|
|
461
|
+
)}
|
|
462
|
+
</div>
|
|
463
|
+
{expanded && (
|
|
464
|
+
<div className="h-5 flex items-center ml-1">
|
|
465
|
+
{renderAssistantActionMenu(item.actions)}
|
|
466
|
+
</div>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
return (
|
|
472
|
+
<div key={item.path}>
|
|
473
|
+
{!expanded ? (
|
|
474
|
+
<Tooltip>
|
|
475
|
+
<TooltipTrigger asChild>{simpleBtn}</TooltipTrigger>
|
|
476
|
+
<SidebarTooltipContent side="right" sideOffset={0}>
|
|
477
|
+
<p>{item.label}</p>
|
|
478
|
+
</SidebarTooltipContent>
|
|
479
|
+
</Tooltip>
|
|
480
|
+
) : simpleBtn}
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
})}
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
);
|
|
487
|
+
};
|
|
488
|
+
|
|
295
489
|
return (
|
|
296
490
|
<TooltipProvider delayDuration={300}>
|
|
297
491
|
{/* Sidebar */}
|
|
@@ -302,7 +496,7 @@ export function Sidebar({
|
|
|
302
496
|
}`}
|
|
303
497
|
>
|
|
304
498
|
{/* Botão Toggle Menu */}
|
|
305
|
-
<div className="p-[14px] pt-[13px] pr-[14px] pb-[12px] pl-[14px]">
|
|
499
|
+
<div className="flex-shrink-0 p-[14px] pt-[13px] pr-[14px] pb-[12px] pl-[14px]">
|
|
306
500
|
<button
|
|
307
501
|
onClick={onToggle}
|
|
308
502
|
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"
|
|
@@ -316,7 +510,7 @@ export function Sidebar({
|
|
|
316
510
|
</div>
|
|
317
511
|
|
|
318
512
|
{/* Logo */}
|
|
319
|
-
<div className="px-4 py-4">
|
|
513
|
+
<div className="flex-shrink-0 px-4 py-4">
|
|
320
514
|
<div
|
|
321
515
|
className={`flex items-center h-10 ${expanded ? "justify-center" : "justify-center"}`}
|
|
322
516
|
>
|
|
@@ -340,62 +534,121 @@ export function Sidebar({
|
|
|
340
534
|
</div>
|
|
341
535
|
</div>
|
|
342
536
|
|
|
343
|
-
{/*
|
|
344
|
-
|
|
345
|
-
<div className="space-y-
|
|
346
|
-
{
|
|
347
|
-
|
|
348
|
-
|
|
537
|
+
{/* Assistant-specific Header (Search/Fixed Area) - Always fixed when present */}
|
|
538
|
+
{variant === "assistant" && ((fixedArea && fixedArea.show) || (search && search.show)) && (
|
|
539
|
+
<div className="flex-shrink-0 px-4 pb-4 space-y-4 border-b border-sidebar-border/30 mb-2">
|
|
540
|
+
{fixedArea?.show && fixedArea.content && expanded && (
|
|
541
|
+
<div className="animate-in fade-in slide-in-from-top-1 duration-300">
|
|
542
|
+
{fixedArea.content}
|
|
543
|
+
</div>
|
|
349
544
|
)}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
})}
|
|
390
|
-
</div>
|
|
391
|
-
</PopoverContent>
|
|
392
|
-
</Popover>
|
|
545
|
+
{search?.show && expanded && (
|
|
546
|
+
<div className="flex items-center gap-2">
|
|
547
|
+
<div className="relative flex-1">
|
|
548
|
+
<SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-sidebar-foreground/50" />
|
|
549
|
+
<Input
|
|
550
|
+
type="text"
|
|
551
|
+
placeholder={search.placeholder || "Buscar..."}
|
|
552
|
+
value={search.value}
|
|
553
|
+
onChange={(e) => search.onChange?.(e.target.value)}
|
|
554
|
+
className="w-full h-9 bg-sidebar-foreground/10 border-sidebar-border text-sidebar-foreground placeholder:text-sidebar-foreground/50 pl-9 focus-visible:ring-1 focus-visible:ring-sidebar-foreground/30 focus-visible:ring-offset-0"
|
|
555
|
+
/>
|
|
556
|
+
</div>
|
|
557
|
+
{search.filter?.show && search.filter.content && (
|
|
558
|
+
<Popover>
|
|
559
|
+
<PopoverTrigger asChild>
|
|
560
|
+
<Button variant="ghost" size="icon" className="h-9 w-9 text-sidebar-foreground hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground">
|
|
561
|
+
{search.filter.icon || <Filter className="h-4 w-4" />}
|
|
562
|
+
</Button>
|
|
563
|
+
</PopoverTrigger>
|
|
564
|
+
<PopoverContent side="bottom" align="end" className="w-64 p-0 bg-popover border-border">
|
|
565
|
+
{search.filter.content}
|
|
566
|
+
</PopoverContent>
|
|
567
|
+
</Popover>
|
|
568
|
+
)}
|
|
569
|
+
</div>
|
|
570
|
+
)}
|
|
571
|
+
{(!expanded && (fixedArea?.show || search?.show)) && (
|
|
572
|
+
<div className="flex flex-col items-center gap-2">
|
|
573
|
+
{search?.show && (
|
|
574
|
+
<Tooltip>
|
|
575
|
+
<TooltipTrigger asChild>
|
|
576
|
+
<button className="h-10 w-10 flex items-center justify-center rounded-[var(--radius-button)] text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground">
|
|
577
|
+
<SearchIcon className="h-5 w-5" />
|
|
578
|
+
</button>
|
|
579
|
+
</TooltipTrigger>
|
|
580
|
+
<SidebarTooltipContent side="right">Buscar</SidebarTooltipContent>
|
|
581
|
+
</Tooltip>
|
|
582
|
+
)}
|
|
583
|
+
</div>
|
|
393
584
|
)}
|
|
394
585
|
</div>
|
|
395
|
-
|
|
586
|
+
)}
|
|
587
|
+
|
|
588
|
+
{/* Main Content Area - Scrollable */}
|
|
589
|
+
<div className="flex-1 min-h-0 overflow-hidden">
|
|
590
|
+
{variant === "default" ? (
|
|
591
|
+
<nav className="h-full px-4 py-4 overflow-hidden" ref={navRef}>
|
|
592
|
+
<div className="space-y-1">
|
|
593
|
+
{/* Itens visíveis */}
|
|
594
|
+
{(hasOverflow ? visibleItems : navigationItems).map((item) =>
|
|
595
|
+
renderMenuItem(item)
|
|
596
|
+
)}
|
|
597
|
+
|
|
598
|
+
{/* Botão de overflow (ellipsis) */}
|
|
599
|
+
{hasOverflow && (
|
|
600
|
+
<Popover>
|
|
601
|
+
<PopoverTrigger asChild>
|
|
602
|
+
<button
|
|
603
|
+
className={
|
|
604
|
+
expanded
|
|
605
|
+
? "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"
|
|
606
|
+
: "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"
|
|
607
|
+
}
|
|
608
|
+
>
|
|
609
|
+
<MoreVertical className="w-5 h-5 flex-shrink-0" />
|
|
610
|
+
{expanded && (
|
|
611
|
+
<span className="truncate text-sidebar-foreground">
|
|
612
|
+
Mais opções
|
|
613
|
+
</span>
|
|
614
|
+
)}
|
|
615
|
+
</button>
|
|
616
|
+
</PopoverTrigger>
|
|
617
|
+
<PopoverContent
|
|
618
|
+
side="right"
|
|
619
|
+
align="start"
|
|
620
|
+
className="w-56 p-2 bg-popover border border-border rounded-[var(--radius-card)] shadow-lg"
|
|
621
|
+
sideOffset={8}
|
|
622
|
+
>
|
|
623
|
+
<div className="space-y-1">
|
|
624
|
+
{overflowItems.map((item) => {
|
|
625
|
+
const Icon = item.icon;
|
|
626
|
+
return (
|
|
627
|
+
<button
|
|
628
|
+
key={item.path}
|
|
629
|
+
onClick={() => handleNavigate(item.path)}
|
|
630
|
+
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"
|
|
631
|
+
>
|
|
632
|
+
<Icon className="w-4 h-4 flex-shrink-0" />
|
|
633
|
+
<span className="truncate">{item.label}</span>
|
|
634
|
+
</button>
|
|
635
|
+
);
|
|
636
|
+
})}
|
|
637
|
+
</div>
|
|
638
|
+
</PopoverContent>
|
|
639
|
+
</Popover>
|
|
640
|
+
)}
|
|
641
|
+
</div>
|
|
642
|
+
</nav>
|
|
643
|
+
) : (
|
|
644
|
+
<ScrollArea className="h-full px-4">
|
|
645
|
+
{navigationGroups.map(group => renderAssistantGroup(group))}
|
|
646
|
+
</ScrollArea>
|
|
647
|
+
)}
|
|
648
|
+
</div>
|
|
396
649
|
|
|
397
650
|
{/* Footer da sidebar - Usuário */}
|
|
398
|
-
<div className="p-4 space-y-2">
|
|
651
|
+
<div className="flex-shrink-0 p-4 space-y-2">
|
|
399
652
|
{/* Avatar do usuário */}
|
|
400
653
|
{!expanded ? (
|
|
401
654
|
<Tooltip>
|
|
@@ -14,7 +14,7 @@ import { Textarea } from './ui/textarea';
|
|
|
14
14
|
import { Progress } from './ui/progress';
|
|
15
15
|
import { Separator } from './ui/separator';
|
|
16
16
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table';
|
|
17
|
-
import { Settings, User, Mail, Phone, Calendar, Search, Menu, ChevronRight, Home, Users } from 'lucide-react';
|
|
17
|
+
import { Settings, User, Mail, Phone, Calendar, Search, Menu, ChevronRight, Home, Users, Plus, Trash2, Archive, ArrowRightLeft, History, FileEdit, Filter, Clock, Map } from 'lucide-react';
|
|
18
18
|
import { Slider } from './ui/slider';
|
|
19
19
|
import { toast } from 'sonner';
|
|
20
20
|
import { ScrollArea } from './ui/scroll-area';
|
|
@@ -22,6 +22,7 @@ import { ThemeToggle } from './ThemeToggle';
|
|
|
22
22
|
import { LanguageSelector } from './LanguageSelector';
|
|
23
23
|
import { MapShowcase } from './examples/MapShowcase';
|
|
24
24
|
import { Header } from './Header';
|
|
25
|
+
import { Sidebar } from './Sidebar';
|
|
25
26
|
import {
|
|
26
27
|
Dialog,
|
|
27
28
|
DialogTrigger,
|
|
@@ -713,6 +714,134 @@ export function TemplateContent({ }: TemplateContentProps) {
|
|
|
713
714
|
|
|
714
715
|
<Separator className="my-8" />
|
|
715
716
|
|
|
717
|
+
{/* Sidebar Variations */}
|
|
718
|
+
<section>
|
|
719
|
+
<h3 className="mb-4">Sidebar Variations</h3>
|
|
720
|
+
<Card>
|
|
721
|
+
<CardHeader>
|
|
722
|
+
<CardTitle>Sidebar Assistant Mode vs Default</CardTitle>
|
|
723
|
+
<CardDescription>
|
|
724
|
+
A Sidebar suporta flexibilidade com a propriedade variant.
|
|
725
|
+
</CardDescription>
|
|
726
|
+
</CardHeader>
|
|
727
|
+
<CardContent>
|
|
728
|
+
<Tabs defaultValue="assistant" className="w-full">
|
|
729
|
+
<TabsList className="mb-4">
|
|
730
|
+
<TabsTrigger value="assistant">Modo Assistant</TabsTrigger>
|
|
731
|
+
<TabsTrigger value="default">Modo Padrão</TabsTrigger>
|
|
732
|
+
</TabsList>
|
|
733
|
+
|
|
734
|
+
<TabsContent value="assistant">
|
|
735
|
+
<div className="relative h-[600px] border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
|
|
736
|
+
<Sidebar
|
|
737
|
+
expanded={true}
|
|
738
|
+
onToggle={() => { }}
|
|
739
|
+
user={{ email: "admin@xertica.com" }}
|
|
740
|
+
onLogout={() => toast("Saiu")}
|
|
741
|
+
location={{ pathname: "/assistant/current" }}
|
|
742
|
+
navigate={() => { }}
|
|
743
|
+
variant="assistant"
|
|
744
|
+
search={{
|
|
745
|
+
show: true,
|
|
746
|
+
placeholder: "Pesquisar tópicos..."
|
|
747
|
+
}}
|
|
748
|
+
fixedArea={{
|
|
749
|
+
show: true,
|
|
750
|
+
content: (
|
|
751
|
+
<Button className="w-full bg-sidebar-primary hover:bg-sidebar-primary/90 text-sidebar-primary-foreground shadow-lg font-bold border-none transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98]">
|
|
752
|
+
<Plus className="w-4 h-4 mr-2" />
|
|
753
|
+
Nova Conversa
|
|
754
|
+
</Button>
|
|
755
|
+
)
|
|
756
|
+
}}
|
|
757
|
+
navigationGroups={[
|
|
758
|
+
{
|
|
759
|
+
id: "recent",
|
|
760
|
+
label: "Recentes",
|
|
761
|
+
icon: Clock,
|
|
762
|
+
items: [
|
|
763
|
+
{
|
|
764
|
+
path: "/assistant/refatoracao",
|
|
765
|
+
label: "Refatoração Sidebar",
|
|
766
|
+
icon: History,
|
|
767
|
+
description: "Ativa agora",
|
|
768
|
+
actions: [
|
|
769
|
+
{ label: "Renomear", icon: FileEdit, onClick: () => toast("Abrir renomear...") },
|
|
770
|
+
{
|
|
771
|
+
label: "Mover",
|
|
772
|
+
icon: ArrowRightLeft,
|
|
773
|
+
children: [
|
|
774
|
+
{ label: "Projetos Ativos", onClick: () => toast("Movido para Projetos Ativos") },
|
|
775
|
+
{ label: "Monitoramento", onClick: () => toast("Movido para Monitoramento") },
|
|
776
|
+
{ label: "Arquivo", onClick: () => toast("Movido para Arquivo") }
|
|
777
|
+
]
|
|
778
|
+
},
|
|
779
|
+
{ label: "Limpar", icon: Trash2, onClick: () => toast("Histórico Limpo!"), variant: "destructive" }
|
|
780
|
+
]
|
|
781
|
+
},
|
|
782
|
+
]
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
id: "projects",
|
|
786
|
+
label: "Monitoramento de Obras",
|
|
787
|
+
icon: Map,
|
|
788
|
+
actions: [
|
|
789
|
+
{ label: "Nova Categoria", icon: Plus, onClick: () => toast("Criar nova categoria...") },
|
|
790
|
+
{ label: "Arquivar Grupo", icon: Archive, onClick: () => toast("Arquivando grupo...") }
|
|
791
|
+
],
|
|
792
|
+
items: [
|
|
793
|
+
{
|
|
794
|
+
path: "/assistant/br163",
|
|
795
|
+
label: "Restauração BR-163",
|
|
796
|
+
icon: () => <div className="w-2 h-2 rounded-full bg-yellow-500" />,
|
|
797
|
+
description: (
|
|
798
|
+
<div className="space-y-1.5 min-w-[160px]">
|
|
799
|
+
<Progress value={67} className="h-1.5 bg-sidebar-foreground/10" />
|
|
800
|
+
<div className="flex justify-between items-center text-[10px] text-sidebar-foreground/60">
|
|
801
|
+
<span>Cuiabá, MT</span>
|
|
802
|
+
<span>67%</span>
|
|
803
|
+
</div>
|
|
804
|
+
</div>
|
|
805
|
+
)
|
|
806
|
+
}
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
]}
|
|
810
|
+
/>
|
|
811
|
+
<div className="absolute inset-y-0 right-0 left-64 p-8 flex items-center justify-center">
|
|
812
|
+
<p className="text-muted-foreground text-center">Conteúdo do Assistant Mode</p>
|
|
813
|
+
</div>
|
|
814
|
+
</div>
|
|
815
|
+
</TabsContent>
|
|
816
|
+
|
|
817
|
+
<TabsContent value="default">
|
|
818
|
+
<div className="relative h-[600px] border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
|
|
819
|
+
<Sidebar
|
|
820
|
+
expanded={true}
|
|
821
|
+
onToggle={() => { }}
|
|
822
|
+
user={{ email: "admin@xertica.com" }}
|
|
823
|
+
onLogout={() => toast("Saiu")}
|
|
824
|
+
location={{ pathname: "/home" }}
|
|
825
|
+
navigate={() => { }}
|
|
826
|
+
variant="default"
|
|
827
|
+
routes={[
|
|
828
|
+
{ path: "/home", label: "Início", icon: Home },
|
|
829
|
+
{ path: "/dashboard", label: "Dashboard", icon: Users },
|
|
830
|
+
{ path: "/settings", label: "Configurações", icon: Settings },
|
|
831
|
+
]}
|
|
832
|
+
/>
|
|
833
|
+
<div className="absolute inset-y-0 right-0 left-64 p-8 flex items-center justify-center">
|
|
834
|
+
<p className="text-muted-foreground text-center">Navegação Tradicional do Sistema</p>
|
|
835
|
+
</div>
|
|
836
|
+
</div>
|
|
837
|
+
</TabsContent>
|
|
838
|
+
</Tabs>
|
|
839
|
+
</CardContent>
|
|
840
|
+
</Card>
|
|
841
|
+
</section>
|
|
842
|
+
|
|
843
|
+
<Separator className="my-8" />
|
|
844
|
+
|
|
716
845
|
{/* Footer Note */}
|
|
717
846
|
<Card className="mt-8">
|
|
718
847
|
<CardHeader>
|