@evolution-soft/ui 1.0.0 → 1.0.1
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/cli/index.mjs +386 -0
- package/components/button-icon-lottie/index.tsx +46 -0
- package/components/fullscreen-mode/index.tsx +82 -0
- package/components/header/components/buttons.tsx +102 -0
- package/components/header/index.tsx +146 -0
- package/components/loading-default/index.tsx +90 -0
- package/components/lottie-icon/index.tsx +78 -0
- package/components/not-found-default/index.tsx +68 -0
- package/components/settings-modal/index.tsx +225 -0
- package/components/sidebar/index.tsx +645 -0
- package/components/subtitle/index.tsx +60 -0
- package/components/theme-transition/index.tsx +142 -0
- package/components/title/index.tsx +66 -0
- package/components/tooltip-indicator/index.tsx +30 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +157 -0
- package/components/ui/alert.tsx +66 -0
- package/components/ui/aspect-ratio.tsx +11 -0
- package/components/ui/avatar.tsx +53 -0
- package/components/ui/badge.tsx +46 -0
- package/components/ui/breadcrumb.tsx +109 -0
- package/components/ui/button.tsx +58 -0
- package/components/ui/calendar.tsx +78 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +360 -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 +135 -0
- package/components/ui/divisor.tsx +9 -0
- package/components/ui/drawer.tsx +132 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/emoji-picker.tsx +76 -0
- package/components/ui/form.tsx +168 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/input-mask.tsx +46 -0
- package/components/ui/input-otp.tsx +77 -0
- package/components/ui/input.tsx +61 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/menubar.tsx +276 -0
- package/components/ui/multiselect.tsx +105 -0
- package/components/ui/navigation-menu.tsx +168 -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 +45 -0
- package/components/ui/resizable.tsx +65 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/searchable-select.tsx +211 -0
- package/components/ui/select.tsx +189 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +139 -0
- package/components/ui/sidebar.tsx +727 -0
- package/components/ui/skeleton.tsx +144 -0
- package/components/ui/slider.tsx +63 -0
- package/components/ui/sonner.tsx +26 -0
- package/components/ui/switch.tsx +31 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +76 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/theme-toggle.tsx +89 -0
- package/components/ui/toggle-group.tsx +73 -0
- package/components/ui/toggle.tsx +47 -0
- package/components/ui/tooltip.tsx +61 -0
- package/components/ui/use-mobile.ts +21 -0
- package/components/ui/utils.ts +6 -0
- package/contexts/AnimationSettingsContext.tsx +85 -0
- package/contexts/AuthContext.tsx +80 -0
- package/contexts/ThemeContext.tsx +70 -0
- package/hooks/useAnimationSettings.ts +2 -0
- package/hooks/usePermissions.ts +4 -0
- package/lib/persistentFilters.ts +120 -0
- package/lib/utils.ts +2 -0
- package/package.json +11 -2
- package/stores/theme.ts +30 -0
- package/stores/useThemeStore.ts +32 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React, { type ForwardRefExoticComponent, type RefAttributes } from "react";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { Buttons } from "./components/buttons";
|
|
5
|
+
import { Separator } from "@/components/ui/separator";
|
|
6
|
+
import { Title, type TitleProps } from "../title";
|
|
7
|
+
import { Badge, badgeVariants } from "@/components/ui/badge";
|
|
8
|
+
import { Subtitle, type SubtitleProps } from "../subtitle";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Breadcrumb,
|
|
12
|
+
BreadcrumbList,
|
|
13
|
+
BreadcrumbItem,
|
|
14
|
+
BreadcrumbLink,
|
|
15
|
+
BreadcrumbPage,
|
|
16
|
+
BreadcrumbSeparator
|
|
17
|
+
} from "@/components/ui/breadcrumb";
|
|
18
|
+
|
|
19
|
+
import clsx from "clsx";
|
|
20
|
+
|
|
21
|
+
import type { IconType } from "react-icons";
|
|
22
|
+
import type { IconProps } from "phosphor-react";
|
|
23
|
+
import type { VariantProps } from "class-variance-authority";
|
|
24
|
+
|
|
25
|
+
interface BreadCrumbItem {
|
|
26
|
+
label: string;
|
|
27
|
+
href?: string;
|
|
28
|
+
isCurrentPage?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface BreadCrumbProps {
|
|
32
|
+
items: BreadCrumbItem[];
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ButtonProps = React.ComponentProps<typeof Button>;
|
|
37
|
+
|
|
38
|
+
interface HeaderProps {
|
|
39
|
+
title: TitleProps | any;
|
|
40
|
+
subtitle?: SubtitleProps | any;
|
|
41
|
+
icon?: React.ReactNode | ForwardRefExoticComponent<IconProps & RefAttributes<SVGSVGElement>> | IconType;
|
|
42
|
+
lefticon?: React.ReactNode;
|
|
43
|
+
button?: ButtonProps;
|
|
44
|
+
secondButton?: ButtonProps;
|
|
45
|
+
thirdButton?: ButtonProps;
|
|
46
|
+
fourButton?: ButtonProps;
|
|
47
|
+
customItem?: React.ReactNode;
|
|
48
|
+
breadCrumbs?: BreadCrumbProps;
|
|
49
|
+
upperTitle?: boolean;
|
|
50
|
+
badge?: {
|
|
51
|
+
text: string;
|
|
52
|
+
variant?: VariantProps<typeof badgeVariants>['variant'];
|
|
53
|
+
className?: string;
|
|
54
|
+
};
|
|
55
|
+
showSeparator?: boolean;
|
|
56
|
+
headerClassName?: string;
|
|
57
|
+
titleContainerClassName?: string;
|
|
58
|
+
}
|
|
59
|
+
function HeaderBreadcrumb({ items, className }: BreadCrumbProps) {
|
|
60
|
+
return (
|
|
61
|
+
<Breadcrumb className={className}>
|
|
62
|
+
<BreadcrumbList>
|
|
63
|
+
{items.map((item, index) => (
|
|
64
|
+
<React.Fragment key={index}>
|
|
65
|
+
<BreadcrumbItem>
|
|
66
|
+
{item.isCurrentPage ? (
|
|
67
|
+
<BreadcrumbPage>{item.label}</BreadcrumbPage>
|
|
68
|
+
) : item.href ? (
|
|
69
|
+
<BreadcrumbLink href={item.href}>{item.label}</BreadcrumbLink>
|
|
70
|
+
) : (
|
|
71
|
+
<span>{item.label}</span>
|
|
72
|
+
)}
|
|
73
|
+
</BreadcrumbItem>
|
|
74
|
+
{index < items.length - 1 && <BreadcrumbSeparator />}
|
|
75
|
+
</React.Fragment>
|
|
76
|
+
))}
|
|
77
|
+
</BreadcrumbList>
|
|
78
|
+
</Breadcrumb>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function Header(props: HeaderProps) {
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
<div className={clsx(
|
|
86
|
+
"flex w-full gap-4 items-center justify-center flex-col md:justify-between md:flex-row",
|
|
87
|
+
props.headerClassName
|
|
88
|
+
)}>
|
|
89
|
+
<div
|
|
90
|
+
className={clsx(
|
|
91
|
+
"flex gap-4 flex-col sm:flex-row items-center text-gray-700 dark:text-gray-300",
|
|
92
|
+
{ "justify-between w-full": props.lefticon },
|
|
93
|
+
props.titleContainerClassName
|
|
94
|
+
)}
|
|
95
|
+
>
|
|
96
|
+
{props.icon && <>{props.icon}</>}
|
|
97
|
+
<div className="flex gap-4 flex-col sm:flex-row items-center text-gray-700 dark:text-gray-300">
|
|
98
|
+
<div className="flex flex-col items-center text-center md:text-left md:items-start">
|
|
99
|
+
{props.upperTitle && (
|
|
100
|
+
<div className="flex items-center gap-2 mb-1">
|
|
101
|
+
<Title
|
|
102
|
+
{...props.title}
|
|
103
|
+
className={`line-clamp-1 ${props.title.className} `}
|
|
104
|
+
/>
|
|
105
|
+
{props.badge && (
|
|
106
|
+
<Badge variant={props.badge.variant} className={props.badge.className}>
|
|
107
|
+
{props.badge.text}
|
|
108
|
+
</Badge>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
{props.breadCrumbs && (
|
|
113
|
+
<HeaderBreadcrumb {...props.breadCrumbs} />
|
|
114
|
+
)}
|
|
115
|
+
{props.subtitle && <Subtitle {...props.subtitle} />}
|
|
116
|
+
{!props.upperTitle && (
|
|
117
|
+
<div className="flex items-center gap-2">
|
|
118
|
+
<Title
|
|
119
|
+
{...props.title}
|
|
120
|
+
className={`line-clamp-1 ${props.title.className} `}
|
|
121
|
+
/>
|
|
122
|
+
{props.badge && (
|
|
123
|
+
<Badge variant={props.badge.variant} className={props.badge.className}>
|
|
124
|
+
{props.badge.text}
|
|
125
|
+
</Badge>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
{props.lefticon && <>{props.lefticon}</>}
|
|
132
|
+
</div>
|
|
133
|
+
<Buttons
|
|
134
|
+
customItem={props.customItem}
|
|
135
|
+
button={props.button}
|
|
136
|
+
secondButton={props.secondButton}
|
|
137
|
+
thirdButton={props.thirdButton}
|
|
138
|
+
fourButton={props.fourButton}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
{props.showSeparator && (
|
|
142
|
+
<Separator className="my-4" />
|
|
143
|
+
)}
|
|
144
|
+
</>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Activity } from 'react';
|
|
2
|
+
|
|
3
|
+
interface LoadingDefaultProps {
|
|
4
|
+
loading: boolean;
|
|
5
|
+
error?: boolean;
|
|
6
|
+
text?: string;
|
|
7
|
+
minHeight?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
rows?: number;
|
|
10
|
+
showAvatar?: boolean;
|
|
11
|
+
showHeader?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function LoadingDefault({
|
|
15
|
+
loading,
|
|
16
|
+
error = false,
|
|
17
|
+
text = "Carregando...",
|
|
18
|
+
minHeight = "350px",
|
|
19
|
+
className = "",
|
|
20
|
+
rows = 3,
|
|
21
|
+
showAvatar = true,
|
|
22
|
+
showHeader = true
|
|
23
|
+
}: LoadingDefaultProps) {
|
|
24
|
+
return (
|
|
25
|
+
<Activity mode={loading && !error ? 'visible' : 'hidden'}>
|
|
26
|
+
<div className={`w-full ${className}`}>
|
|
27
|
+
<div
|
|
28
|
+
className='w-full bg-card border border-border rounded-lg p-6 space-y-4'
|
|
29
|
+
style={{ minHeight }}
|
|
30
|
+
>
|
|
31
|
+
{/* Header Skeleton */}
|
|
32
|
+
{showHeader && (
|
|
33
|
+
<div className="flex items-center space-x-4">
|
|
34
|
+
{showAvatar && (
|
|
35
|
+
<div className="h-12 w-12 bg-muted rounded-full animate-pulse" />
|
|
36
|
+
)}
|
|
37
|
+
<div className="space-y-2 flex-1">
|
|
38
|
+
<div className="h-4 bg-muted rounded animate-pulse w-1/4" />
|
|
39
|
+
<div className="h-3 bg-muted rounded animate-pulse w-1/3" />
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
|
|
44
|
+
{/* Content Skeleton */}
|
|
45
|
+
<div className="space-y-3">
|
|
46
|
+
{Array.from({ length: rows }).map((_, index) => (
|
|
47
|
+
<div key={index} className="space-y-2">
|
|
48
|
+
<div
|
|
49
|
+
className="h-4 bg-muted rounded animate-pulse"
|
|
50
|
+
style={{
|
|
51
|
+
width: `${Math.random() * 40 + 60}%`,
|
|
52
|
+
animationDelay: `${index * 0.1}s`
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
{index < rows - 1 && (
|
|
56
|
+
<div
|
|
57
|
+
className="h-3 bg-muted/70 rounded animate-pulse"
|
|
58
|
+
style={{
|
|
59
|
+
width: `${Math.random() * 30 + 40}%`,
|
|
60
|
+
animationDelay: `${index * 0.15 + 0.05}s`
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Footer Skeleton */}
|
|
69
|
+
<div className="flex justify-between items-center pt-4">
|
|
70
|
+
<div className="flex space-x-2">
|
|
71
|
+
<div className="h-8 w-16 bg-muted rounded animate-pulse" />
|
|
72
|
+
<div className="h-8 w-16 bg-muted rounded animate-pulse" />
|
|
73
|
+
</div>
|
|
74
|
+
<div className="h-8 w-20 bg-muted rounded animate-pulse" />
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Loading indicator com texto */}
|
|
78
|
+
<div className="flex items-center justify-center gap-3 pt-2">
|
|
79
|
+
<div className="flex space-x-1">
|
|
80
|
+
<div className="w-2 h-2 bg-primary rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
|
81
|
+
<div className="w-2 h-2 bg-primary rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
|
82
|
+
<div className="w-2 h-2 bg-primary rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
|
83
|
+
</div>
|
|
84
|
+
<span className="text-muted-foreground text-sm font-medium">{text}</span>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</Activity>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
import Lottie from "lottie-react";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
interface LottieIconProps {
|
|
6
|
+
lightAnimationData: any;
|
|
7
|
+
darkAnimationData: any;
|
|
8
|
+
className?: string;
|
|
9
|
+
isActive?: boolean;
|
|
10
|
+
shouldPlay?: boolean;
|
|
11
|
+
isDark?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const LottieIcon = ({ lightAnimationData, darkAnimationData, className, isActive, shouldPlay = false }: LottieIconProps) => {
|
|
15
|
+
const lottieRef = useRef<any>(null);
|
|
16
|
+
const [key, setKey] = useState(0);
|
|
17
|
+
const [animationsEnabled, setAnimationsEnabled] = useState(true);
|
|
18
|
+
|
|
19
|
+
const [isDark, setIsDark] = useState(false);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const checkDarkMode = () => {
|
|
23
|
+
setIsDark(document.documentElement.classList.contains('dark'));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
checkDarkMode();
|
|
27
|
+
|
|
28
|
+
const observer = new MutationObserver(checkDarkMode);
|
|
29
|
+
observer.observe(document.documentElement, {
|
|
30
|
+
attributes: true,
|
|
31
|
+
attributeFilter: ['class']
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return () => observer.disconnect();
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const animationData = isDark ? darkAnimationData : lightAnimationData;
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const checkAnimationSettings = () => {
|
|
41
|
+
const saved = localStorage.getItem('lottie-animations-enabled');
|
|
42
|
+
setAnimationsEnabled(saved !== null ? JSON.parse(saved) : true);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
checkAnimationSettings();
|
|
46
|
+
|
|
47
|
+
const handleSettingsChange = (event: CustomEvent) => {
|
|
48
|
+
setAnimationsEnabled(event.detail.enabled);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
window.addEventListener('lottie-settings-changed', handleSettingsChange as EventListener);
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
window.removeEventListener('lottie-settings-changed', handleSettingsChange as EventListener);
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (animationsEnabled && (shouldPlay || isActive)) {
|
|
60
|
+
setKey(prev => prev + 1);
|
|
61
|
+
} else if (lottieRef.current) {
|
|
62
|
+
lottieRef.current.stop();
|
|
63
|
+
}
|
|
64
|
+
}, [shouldPlay, isActive, isDark, animationsEnabled]);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className={className}>
|
|
68
|
+
<Lottie
|
|
69
|
+
key={`${key}-${isDark ? 'dark' : 'light'}`}
|
|
70
|
+
lottieRef={lottieRef}
|
|
71
|
+
animationData={animationData}
|
|
72
|
+
loop={false}
|
|
73
|
+
autoplay={animationsEnabled && (shouldPlay || isActive)}
|
|
74
|
+
className="w-full h-full"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Activity } from 'react';
|
|
2
|
+
import { ArrowLeft } from 'lucide-react';
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
|
|
5
|
+
import Lottie from 'lottie-react';
|
|
6
|
+
import notFoundData from '@/public/animations/404-error-data.json';
|
|
7
|
+
|
|
8
|
+
interface NotFoundDefaultProps {
|
|
9
|
+
loading: boolean;
|
|
10
|
+
error: boolean;
|
|
11
|
+
title?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
backButtonText?: string;
|
|
14
|
+
onBack?: () => void;
|
|
15
|
+
minHeight?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
animationSize?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function NotFoundDefault({
|
|
21
|
+
loading,
|
|
22
|
+
error,
|
|
23
|
+
title = "Ops! Algo deu errado",
|
|
24
|
+
message = "Não foi possível carregar os dados solicitados",
|
|
25
|
+
backButtonText = "Voltar",
|
|
26
|
+
onBack,
|
|
27
|
+
minHeight = "350px",
|
|
28
|
+
className = "",
|
|
29
|
+
animationSize = "w-[100px] md:w-[280px]"
|
|
30
|
+
}: NotFoundDefaultProps) {
|
|
31
|
+
return (
|
|
32
|
+
<Activity mode={!loading && error ? 'visible' : 'hidden'}>
|
|
33
|
+
<div className={`flex items-center justify-center bg-card w-full rounded-2xl ${className}`}>
|
|
34
|
+
<div
|
|
35
|
+
className='flex flex-col w-full items-center gap-4 bg-card rounded-2xl shadow-lg px-6 py-8 border'
|
|
36
|
+
style={{ minHeight }}
|
|
37
|
+
>
|
|
38
|
+
<Lottie
|
|
39
|
+
animationData={notFoundData}
|
|
40
|
+
className={animationSize}
|
|
41
|
+
/>
|
|
42
|
+
<div className='flex flex-col gap-3 justify-center items-center text-center'>
|
|
43
|
+
<div className='space-y-2'>
|
|
44
|
+
<h3 className='text-xl font-semibold text-gray-900 dark:text-gray-200'>
|
|
45
|
+
{title}
|
|
46
|
+
</h3>
|
|
47
|
+
<p className='text-gray-600 dark:text-gray-400 text-sm max-w-xs'>
|
|
48
|
+
{message}
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
{onBack && (
|
|
52
|
+
<div className='flex flex-col sm:flex-row gap-3 justify-center items-center'>
|
|
53
|
+
<Button
|
|
54
|
+
variant="outline"
|
|
55
|
+
onClick={onBack}
|
|
56
|
+
className='flex items-center gap-2 px-4 py-2 border-gray-300 hover:bg-gray-50 transition-colors'
|
|
57
|
+
>
|
|
58
|
+
<ArrowLeft className="h-4 w-4" />
|
|
59
|
+
{backButtonText}
|
|
60
|
+
</Button>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</Activity>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Settings } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { Switch } from '@/components/ui/switch';
|
|
7
|
+
import { Label } from '@/components/ui/label';
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
DialogTrigger,
|
|
15
|
+
} from '@/components/ui/dialog';
|
|
16
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
17
|
+
|
|
18
|
+
import Lottie from 'lottie-react';
|
|
19
|
+
|
|
20
|
+
import configAnimated from "@/public/animations/config-animated.json";
|
|
21
|
+
import { PersistentFilters } from '@/lib/persistentFilters';
|
|
22
|
+
import { useAnimationSettings } from '@/hooks/useAnimationSettings';
|
|
23
|
+
|
|
24
|
+
interface SettingsModalProps {
|
|
25
|
+
collapsed?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function SettingsModal({ collapsed = false }: SettingsModalProps) {
|
|
29
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
30
|
+
const [dynamicSidebarEnabled, setDynamicSidebarEnabled] = useState(false);
|
|
31
|
+
const [persistentFiltersEnabled, setPersistentFiltersEnabled] = useState(true);
|
|
32
|
+
|
|
33
|
+
// Usar o contexto para as configurações de animação
|
|
34
|
+
const {
|
|
35
|
+
lottieAnimationsEnabled,
|
|
36
|
+
otherLottieAnimationsEnabled,
|
|
37
|
+
setLottieAnimationsEnabled,
|
|
38
|
+
setOtherLottieAnimationsEnabled
|
|
39
|
+
} = useAnimationSettings();
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
// As configurações de animação agora são gerenciadas pelo contexto
|
|
43
|
+
// Apenas carregamos as outras configurações
|
|
44
|
+
const savedDynamicSidebar = localStorage.getItem('dynamic-sidebar-enabled');
|
|
45
|
+
if (savedDynamicSidebar !== null) {
|
|
46
|
+
setDynamicSidebarEnabled(JSON.parse(savedDynamicSidebar));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const savedPersistentFilters = localStorage.getItem('persistent-filters-enabled');
|
|
50
|
+
if (savedPersistentFilters !== null) {
|
|
51
|
+
setPersistentFiltersEnabled(JSON.parse(savedPersistentFilters));
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
// As funções de toggle agora usam o contexto diretamente
|
|
56
|
+
const handleAnimationToggle = (enabled: boolean) => {
|
|
57
|
+
setLottieAnimationsEnabled(enabled);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleOtherAnimationToggle = (enabled: boolean) => {
|
|
61
|
+
setOtherLottieAnimationsEnabled(enabled);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleDynamicSidebarToggle = (enabled: boolean) => {
|
|
65
|
+
setDynamicSidebarEnabled(enabled);
|
|
66
|
+
localStorage.setItem('dynamic-sidebar-enabled', JSON.stringify(enabled));
|
|
67
|
+
|
|
68
|
+
window.dispatchEvent(new CustomEvent('dynamic-sidebar-settings-changed', {
|
|
69
|
+
detail: { enabled }
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
window.dispatchEvent(new CustomEvent('reset-dynamic-sidebar-state'));
|
|
73
|
+
|
|
74
|
+
if (enabled) {
|
|
75
|
+
window.dispatchEvent(new CustomEvent('auto-collapse-sidebar'));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handlePersistentFiltersToggle = (enabled: boolean) => {
|
|
80
|
+
setPersistentFiltersEnabled(enabled);
|
|
81
|
+
localStorage.setItem('persistent-filters-enabled', JSON.stringify(enabled));
|
|
82
|
+
|
|
83
|
+
window.dispatchEvent(new CustomEvent('persistent-filters-settings-changed', {
|
|
84
|
+
detail: { enabled }
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
if (!enabled) {
|
|
88
|
+
PersistentFilters.clearAllFilters();
|
|
89
|
+
window.dispatchEvent(new CustomEvent('clear-saved-filters'));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
95
|
+
<DialogTrigger asChild>
|
|
96
|
+
<Button
|
|
97
|
+
variant="outline"
|
|
98
|
+
size="icon"
|
|
99
|
+
className="h-6 w-6 shrink-0 rounded-full hover:bg-accent"
|
|
100
|
+
title="Configurações"
|
|
101
|
+
>
|
|
102
|
+
<Settings className="h-3 w-3" />
|
|
103
|
+
</Button>
|
|
104
|
+
</DialogTrigger>
|
|
105
|
+
<DialogContent className="sm:max-w-106.25 md:max-w-150 lg:max-w-3xl flex">
|
|
106
|
+
<div>
|
|
107
|
+
<DialogHeader>
|
|
108
|
+
<DialogTitle className="flex items-center gap-2">
|
|
109
|
+
<Settings className="h-5 w-5" />
|
|
110
|
+
Configurações
|
|
111
|
+
</DialogTitle>
|
|
112
|
+
<DialogDescription>
|
|
113
|
+
Personalize a experiência do sistema de acordo com suas preferências.
|
|
114
|
+
</DialogDescription>
|
|
115
|
+
</DialogHeader>
|
|
116
|
+
|
|
117
|
+
<div className="space-y-4">
|
|
118
|
+
<Card className='max-h-125 overflow-y-scroll gap-0'>
|
|
119
|
+
<CardHeader className='pb-3 pt-4 dark:bg-slate-800 bg-blue-50 rounded-t-lg'>
|
|
120
|
+
<CardTitle className="text-base font-semibold">Geral</CardTitle>
|
|
121
|
+
<CardDescription className="text-sm">
|
|
122
|
+
Configurações gerais do sistema
|
|
123
|
+
</CardDescription>
|
|
124
|
+
</CardHeader>
|
|
125
|
+
|
|
126
|
+
<CardContent className='pt-4'>
|
|
127
|
+
<div className="space-y-5">
|
|
128
|
+
|
|
129
|
+
<div className="flex items-center justify-between gap-2">
|
|
130
|
+
<div className="space-y-1">
|
|
131
|
+
<Label htmlFor="persistent-filters" className="text-sm font-medium">
|
|
132
|
+
Salvar Filtros
|
|
133
|
+
</Label>
|
|
134
|
+
<p className="text-xs text-muted-foreground">
|
|
135
|
+
Manter filtros salvos entre sessões (busca, paginação, etc.)
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
<Switch
|
|
139
|
+
id="persistent-filters"
|
|
140
|
+
checked={persistentFiltersEnabled}
|
|
141
|
+
onCheckedChange={handlePersistentFiltersToggle}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div className="flex items-center justify-between gap-2 pb-4">
|
|
146
|
+
<div className="space-y-1">
|
|
147
|
+
<Label htmlFor="dynamic-sidebar" className="text-sm font-medium">
|
|
148
|
+
Sidebar Dinâmica (Desktop)
|
|
149
|
+
</Label>
|
|
150
|
+
<p className="text-xs text-muted-foreground">
|
|
151
|
+
Expandir sidebar automaticamente ao passar o mouse (apenas desktop)
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
<Switch
|
|
155
|
+
id="dynamic-sidebar"
|
|
156
|
+
checked={dynamicSidebarEnabled}
|
|
157
|
+
onCheckedChange={handleDynamicSidebarToggle}
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
</div>
|
|
162
|
+
</CardContent>
|
|
163
|
+
|
|
164
|
+
<CardHeader className='pb-2 pt-2 dark:bg-slate-800 bg-blue-50 '>
|
|
165
|
+
<CardTitle className="text-base font-semibold">Interface</CardTitle>
|
|
166
|
+
<CardDescription className="text-sm">
|
|
167
|
+
Configurações relacionadas à interface do usuário
|
|
168
|
+
</CardDescription>
|
|
169
|
+
</CardHeader>
|
|
170
|
+
|
|
171
|
+
<CardContent className='pt-4'>
|
|
172
|
+
<div className="space-y-5">
|
|
173
|
+
<div className="flex items-center justify-between gap-2">
|
|
174
|
+
<div className="space-y-1">
|
|
175
|
+
<Label htmlFor="lottie-animations" className="text-sm font-medium">
|
|
176
|
+
Animações dos Ícones
|
|
177
|
+
</Label>
|
|
178
|
+
<p className="text-xs text-muted-foreground">
|
|
179
|
+
Ativar/desativar animações Lottie nos ícones do sistema
|
|
180
|
+
</p>
|
|
181
|
+
</div>
|
|
182
|
+
<Switch
|
|
183
|
+
id="lottie-animations"
|
|
184
|
+
checked={lottieAnimationsEnabled}
|
|
185
|
+
onCheckedChange={handleAnimationToggle}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div className="flex items-center justify-between gap-2">
|
|
190
|
+
<div className="space-y-1">
|
|
191
|
+
<Label htmlFor="other-lottie-animations" className="text-sm font-medium">
|
|
192
|
+
Outras Animações
|
|
193
|
+
</Label>
|
|
194
|
+
<p className="text-xs text-muted-foreground">
|
|
195
|
+
Ativar/desativar animações Lottie decorativas (páginas, modal)
|
|
196
|
+
</p>
|
|
197
|
+
</div>
|
|
198
|
+
<Switch
|
|
199
|
+
id="other-lottie-animations"
|
|
200
|
+
checked={otherLottieAnimationsEnabled}
|
|
201
|
+
onCheckedChange={handleOtherAnimationToggle}
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
</div>
|
|
206
|
+
</CardContent>
|
|
207
|
+
</Card>
|
|
208
|
+
|
|
209
|
+
<div className="text-xs text-muted-foreground text-center">
|
|
210
|
+
As configurações são salvas automaticamente no seu navegador.
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div className='hidden md:flex justify-center items-center'>
|
|
215
|
+
<Lottie
|
|
216
|
+
animationData={configAnimated}
|
|
217
|
+
loop={otherLottieAnimationsEnabled}
|
|
218
|
+
autoplay={otherLottieAnimationsEnabled}
|
|
219
|
+
className='max-w-125'
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
</DialogContent>
|
|
223
|
+
</Dialog>
|
|
224
|
+
);
|
|
225
|
+
}
|