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,285 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { Play, Pause, Volume2, VolumeX, Maximize, Settings, PictureInPicture } from 'lucide-react';
|
|
3
|
+
import { Slider } from '../ui/slider';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { FloatingMediaWrapper } from './FloatingMediaWrapper';
|
|
6
|
+
import { cn } from '../ui/utils';
|
|
7
|
+
|
|
8
|
+
export interface VideoPlayerProps {
|
|
9
|
+
src: string;
|
|
10
|
+
poster?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
autoPlay?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function VideoPlayer({
|
|
17
|
+
src,
|
|
18
|
+
poster,
|
|
19
|
+
title = "Untitled Video",
|
|
20
|
+
autoPlay = false,
|
|
21
|
+
className
|
|
22
|
+
}: VideoPlayerProps) {
|
|
23
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
24
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
26
|
+
const [progress, setProgress] = useState(0);
|
|
27
|
+
const [volume, setVolume] = useState(1);
|
|
28
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
29
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
30
|
+
const [duration, setDuration] = useState(0);
|
|
31
|
+
const [isFloating, setIsFloating] = useState(false);
|
|
32
|
+
const [isManualFloating, setIsManualFloating] = useState(false);
|
|
33
|
+
const [showControls, setShowControls] = useState(true);
|
|
34
|
+
|
|
35
|
+
let controlsTimeout: NodeJS.Timeout;
|
|
36
|
+
|
|
37
|
+
// Wrapper for setIsFloating to handle scroll-on-restore and sync state
|
|
38
|
+
const handleSetFloating = (floating: boolean) => {
|
|
39
|
+
if (videoRef.current) {
|
|
40
|
+
setCurrentTime(videoRef.current.currentTime);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setIsFloating(floating);
|
|
44
|
+
|
|
45
|
+
if (!floating) {
|
|
46
|
+
setIsManualFloating(false);
|
|
47
|
+
if (containerRef.current) {
|
|
48
|
+
containerRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Restore state when switching modes (remounting video element)
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const video = videoRef.current;
|
|
56
|
+
if (video) {
|
|
57
|
+
if (Math.abs(video.currentTime - currentTime) > 0.5) {
|
|
58
|
+
video.currentTime = currentTime;
|
|
59
|
+
}
|
|
60
|
+
if (isPlaying) {
|
|
61
|
+
const playPromise = video.play();
|
|
62
|
+
if (playPromise !== undefined) {
|
|
63
|
+
playPromise.catch(error => {
|
|
64
|
+
console.log("Playback interrupted during switch:", error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, [isFloating]);
|
|
70
|
+
|
|
71
|
+
// Auto-float on scroll logic
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const container = containerRef.current;
|
|
74
|
+
if (!container) return;
|
|
75
|
+
|
|
76
|
+
const observer = new IntersectionObserver(
|
|
77
|
+
([entry]) => {
|
|
78
|
+
if (isPlaying && !entry.isIntersecting && !isFloating) {
|
|
79
|
+
if (videoRef.current) setCurrentTime(videoRef.current.currentTime);
|
|
80
|
+
setIsFloating(true);
|
|
81
|
+
} else if (entry.isIntersecting && isFloating && !isManualFloating) {
|
|
82
|
+
if (videoRef.current) setCurrentTime(videoRef.current.currentTime);
|
|
83
|
+
handleSetFloating(false);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{ threshold: 0.2 }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
observer.observe(container);
|
|
90
|
+
return () => observer.disconnect();
|
|
91
|
+
}, [isPlaying, isFloating, isManualFloating]);
|
|
92
|
+
|
|
93
|
+
// Video Event Listeners
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const video = videoRef.current;
|
|
96
|
+
if (!video) return;
|
|
97
|
+
|
|
98
|
+
const updateTime = () => {
|
|
99
|
+
setCurrentTime(video.currentTime);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const updateDuration = () => setDuration(video.duration);
|
|
103
|
+
const onPlay = () => setIsPlaying(true);
|
|
104
|
+
const onPause = () => setIsPlaying(false);
|
|
105
|
+
|
|
106
|
+
video.addEventListener('timeupdate', updateTime);
|
|
107
|
+
video.addEventListener('loadedmetadata', updateDuration);
|
|
108
|
+
video.addEventListener('play', onPlay);
|
|
109
|
+
video.addEventListener('pause', onPause);
|
|
110
|
+
|
|
111
|
+
// Initial volume set
|
|
112
|
+
video.volume = volume;
|
|
113
|
+
video.muted = isMuted;
|
|
114
|
+
|
|
115
|
+
return () => {
|
|
116
|
+
video.removeEventListener('timeupdate', updateTime);
|
|
117
|
+
video.removeEventListener('loadedmetadata', updateDuration);
|
|
118
|
+
video.removeEventListener('play', onPlay);
|
|
119
|
+
video.removeEventListener('pause', onPause);
|
|
120
|
+
};
|
|
121
|
+
}, [isFloating]); // Re-attach when element changes
|
|
122
|
+
|
|
123
|
+
const togglePlay = () => {
|
|
124
|
+
if (videoRef.current) {
|
|
125
|
+
if (isPlaying) {
|
|
126
|
+
videoRef.current.pause();
|
|
127
|
+
} else {
|
|
128
|
+
videoRef.current.play();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const handleSeek = (value: number[]) => {
|
|
134
|
+
if (videoRef.current) {
|
|
135
|
+
videoRef.current.currentTime = value[0];
|
|
136
|
+
setCurrentTime(value[0]);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleVolumeChange = (value: number[]) => {
|
|
141
|
+
const newVolume = value[0];
|
|
142
|
+
if (videoRef.current) {
|
|
143
|
+
videoRef.current.volume = newVolume;
|
|
144
|
+
setVolume(newVolume);
|
|
145
|
+
setIsMuted(newVolume === 0);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const toggleMute = () => {
|
|
150
|
+
if (videoRef.current) {
|
|
151
|
+
const newMutedState = !isMuted;
|
|
152
|
+
videoRef.current.muted = newMutedState;
|
|
153
|
+
setIsMuted(newMutedState);
|
|
154
|
+
if (newMutedState) {
|
|
155
|
+
setVolume(0);
|
|
156
|
+
} else {
|
|
157
|
+
setVolume(1);
|
|
158
|
+
videoRef.current.volume = 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleMouseMove = () => {
|
|
164
|
+
setShowControls(true);
|
|
165
|
+
clearTimeout(controlsTimeout);
|
|
166
|
+
controlsTimeout = setTimeout(() => {
|
|
167
|
+
if (isPlaying) setShowControls(false);
|
|
168
|
+
}, 2500);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const formatTime = (time: number) => {
|
|
172
|
+
const minutes = Math.floor(time / 60);
|
|
173
|
+
const seconds = Math.floor(time % 60);
|
|
174
|
+
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div ref={containerRef} className={className}>
|
|
179
|
+
<FloatingMediaWrapper
|
|
180
|
+
isFloating={isFloating}
|
|
181
|
+
setIsFloating={handleSetFloating}
|
|
182
|
+
onCloseMedia={() => {
|
|
183
|
+
setIsPlaying(false);
|
|
184
|
+
setIsFloating(false);
|
|
185
|
+
if (videoRef.current) videoRef.current.pause();
|
|
186
|
+
}}
|
|
187
|
+
title={title}
|
|
188
|
+
aspectRatio={16/9}
|
|
189
|
+
className="w-full h-full"
|
|
190
|
+
>
|
|
191
|
+
<div
|
|
192
|
+
className="relative w-full h-full bg-black group overflow-hidden"
|
|
193
|
+
onMouseMove={handleMouseMove}
|
|
194
|
+
onMouseLeave={() => isPlaying && setShowControls(false)}
|
|
195
|
+
>
|
|
196
|
+
<video
|
|
197
|
+
ref={videoRef}
|
|
198
|
+
src={src}
|
|
199
|
+
poster={poster}
|
|
200
|
+
className="w-full h-full object-contain"
|
|
201
|
+
onClick={togglePlay}
|
|
202
|
+
autoPlay={autoPlay}
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
{!isPlaying && (
|
|
206
|
+
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
207
|
+
<div className="w-16 h-16 rounded-full bg-black/40 backdrop-blur-sm flex items-center justify-center border border-white/20 shadow-2xl">
|
|
208
|
+
<Play className="w-8 h-8 text-white ml-1" fill="white" />
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
<div
|
|
214
|
+
className={cn(
|
|
215
|
+
"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent p-4 transition-opacity duration-300",
|
|
216
|
+
showControls ? "opacity-100" : "opacity-0"
|
|
217
|
+
)}
|
|
218
|
+
>
|
|
219
|
+
<div className="mb-4 group/slider">
|
|
220
|
+
<Slider
|
|
221
|
+
value={[currentTime]}
|
|
222
|
+
max={duration || 100}
|
|
223
|
+
step={1}
|
|
224
|
+
onValueChange={handleSeek}
|
|
225
|
+
className="cursor-pointer"
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<div className="flex items-center justify-between">
|
|
230
|
+
<div className="flex items-center gap-4">
|
|
231
|
+
<Button
|
|
232
|
+
variant="ghost"
|
|
233
|
+
size="icon"
|
|
234
|
+
className="text-white hover:bg-white/20 h-8 w-8"
|
|
235
|
+
onClick={togglePlay}
|
|
236
|
+
>
|
|
237
|
+
{isPlaying ? <Pause className="w-5 h-5" fill="currentColor" /> : <Play className="w-5 h-5" fill="currentColor" />}
|
|
238
|
+
</Button>
|
|
239
|
+
|
|
240
|
+
<div className="flex items-center gap-2 group/vol">
|
|
241
|
+
<Button
|
|
242
|
+
variant="ghost"
|
|
243
|
+
size="icon"
|
|
244
|
+
className="text-white hover:bg-white/20 h-8 w-8"
|
|
245
|
+
onClick={toggleMute}
|
|
246
|
+
>
|
|
247
|
+
{isMuted || volume === 0 ? <VolumeX className="w-5 h-5" /> : <Volume2 className="w-5 h-5" />}
|
|
248
|
+
</Button>
|
|
249
|
+
<div className="w-0 overflow-hidden group-hover/vol:w-20 transition-all duration-300">
|
|
250
|
+
<Slider
|
|
251
|
+
value={[isMuted ? 0 : volume]}
|
|
252
|
+
max={1}
|
|
253
|
+
step={0.01}
|
|
254
|
+
onValueChange={handleVolumeChange}
|
|
255
|
+
className="w-20"
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className="text-white/90 text-xs font-medium tabular-nums select-none">
|
|
261
|
+
{formatTime(currentTime)} / {formatTime(duration)}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div className="flex items-center gap-2">
|
|
266
|
+
<Button
|
|
267
|
+
variant="ghost"
|
|
268
|
+
size="icon"
|
|
269
|
+
className="text-white hover:bg-white/20 h-8 w-8"
|
|
270
|
+
onClick={() => {
|
|
271
|
+
if (!isFloating) setIsManualFloating(true);
|
|
272
|
+
handleSetFloating(!isFloating);
|
|
273
|
+
}}
|
|
274
|
+
title={isFloating ? "Restaurar" : "Pop-out Player"}
|
|
275
|
+
>
|
|
276
|
+
{isFloating ? <Maximize className="w-4 h-4" /> : <PictureInPicture className="w-4 h-4" />}
|
|
277
|
+
</Button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</FloatingMediaWrapper>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
5
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "./utils";
|
|
8
|
+
|
|
9
|
+
function Accordion({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
12
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AccordionItem({
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
19
|
+
return (
|
|
20
|
+
<AccordionPrimitive.Item
|
|
21
|
+
data-slot="accordion-item"
|
|
22
|
+
className={cn("border-b last:border-b-0", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AccordionTrigger({
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
33
|
+
return (
|
|
34
|
+
<AccordionPrimitive.Header className="flex">
|
|
35
|
+
<AccordionPrimitive.Trigger
|
|
36
|
+
data-slot="accordion-trigger"
|
|
37
|
+
className={cn(
|
|
38
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
45
|
+
</AccordionPrimitive.Trigger>
|
|
46
|
+
</AccordionPrimitive.Header>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AccordionContent({
|
|
51
|
+
className,
|
|
52
|
+
children,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
55
|
+
return (
|
|
56
|
+
<AccordionPrimitive.Content
|
|
57
|
+
data-slot="accordion-content"
|
|
58
|
+
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
62
|
+
</AccordionPrimitive.Content>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
import { buttonVariants } from "./button";
|
|
8
|
+
|
|
9
|
+
function AlertDialog({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
12
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AlertDialogTrigger({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
18
|
+
return (
|
|
19
|
+
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function AlertDialogPortal({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
26
|
+
return (
|
|
27
|
+
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const AlertDialogOverlay = React.forwardRef<
|
|
32
|
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
33
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
34
|
+
>(({ className, ...props }, ref) => {
|
|
35
|
+
return (
|
|
36
|
+
<AlertDialogPrimitive.Overlay
|
|
37
|
+
ref={ref}
|
|
38
|
+
data-slot="alert-dialog-overlay"
|
|
39
|
+
className={cn(
|
|
40
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
48
|
+
|
|
49
|
+
function AlertDialogContent({
|
|
50
|
+
className,
|
|
51
|
+
...props
|
|
52
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
|
53
|
+
return (
|
|
54
|
+
<AlertDialogPortal>
|
|
55
|
+
<AlertDialogOverlay />
|
|
56
|
+
<AlertDialogPrimitive.Content
|
|
57
|
+
data-slot="alert-dialog-content"
|
|
58
|
+
className={cn(
|
|
59
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
</AlertDialogPortal>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function AlertDialogHeader({
|
|
69
|
+
className,
|
|
70
|
+
...props
|
|
71
|
+
}: React.ComponentProps<"div">) {
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
data-slot="alert-dialog-header"
|
|
75
|
+
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function AlertDialogFooter({
|
|
82
|
+
className,
|
|
83
|
+
...props
|
|
84
|
+
}: React.ComponentProps<"div">) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
data-slot="alert-dialog-footer"
|
|
88
|
+
className={cn(
|
|
89
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function AlertDialogTitle({
|
|
98
|
+
className,
|
|
99
|
+
...props
|
|
100
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
101
|
+
return (
|
|
102
|
+
<AlertDialogPrimitive.Title
|
|
103
|
+
data-slot="alert-dialog-title"
|
|
104
|
+
className={cn("text-lg font-semibold", className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function AlertDialogDescription({
|
|
111
|
+
className,
|
|
112
|
+
...props
|
|
113
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
114
|
+
return (
|
|
115
|
+
<AlertDialogPrimitive.Description
|
|
116
|
+
data-slot="alert-dialog-description"
|
|
117
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function AlertDialogAction({
|
|
124
|
+
className,
|
|
125
|
+
...props
|
|
126
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
|
|
127
|
+
return (
|
|
128
|
+
<AlertDialogPrimitive.Action
|
|
129
|
+
className={cn(buttonVariants(), className)}
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function AlertDialogCancel({
|
|
136
|
+
className,
|
|
137
|
+
...props
|
|
138
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
|
139
|
+
return (
|
|
140
|
+
<AlertDialogPrimitive.Cancel
|
|
141
|
+
className={cn(buttonVariants({ variant: "outline" }), className)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
AlertDialog,
|
|
149
|
+
AlertDialogPortal,
|
|
150
|
+
AlertDialogOverlay,
|
|
151
|
+
AlertDialogTrigger,
|
|
152
|
+
AlertDialogContent,
|
|
153
|
+
AlertDialogHeader,
|
|
154
|
+
AlertDialogFooter,
|
|
155
|
+
AlertDialogTitle,
|
|
156
|
+
AlertDialogDescription,
|
|
157
|
+
AlertDialogAction,
|
|
158
|
+
AlertDialogCancel,
|
|
159
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { CheckCircle, Info, AlertTriangle, XCircle } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "./utils";
|
|
6
|
+
|
|
7
|
+
const alertVariants = cva(
|
|
8
|
+
"relative w-full rounded-[var(--radius)] border-l-4 px-4 py-3 grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-5 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-muted/50 border-l-border text-foreground [&>svg]:text-muted-foreground",
|
|
13
|
+
success: "bg-[color:var(--success)]/10 dark:bg-[color:var(--success)]/20 border-l-[color:var(--success)] text-foreground [&>svg]:text-[color:var(--success)]",
|
|
14
|
+
info: "bg-[color:var(--info)]/10 dark:bg-[color:var(--info)]/20 border-l-[color:var(--info)] text-foreground [&>svg]:text-[color:var(--info)]",
|
|
15
|
+
warning: "bg-[color:var(--warning)]/10 dark:bg-[color:var(--warning)]/20 border-l-[color:var(--warning)] text-foreground [&>svg]:text-[color:var(--warning)]",
|
|
16
|
+
error: "bg-[color:var(--destructive)]/10 dark:bg-[color:var(--destructive)]/20 border-l-[color:var(--destructive)] text-foreground [&>svg]:text-[color:var(--destructive)]",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "default",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const alertIcons = {
|
|
26
|
+
default: Info,
|
|
27
|
+
success: CheckCircle,
|
|
28
|
+
info: Info,
|
|
29
|
+
warning: AlertTriangle,
|
|
30
|
+
error: XCircle,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
type AlertVariantType = keyof typeof alertIcons;
|
|
34
|
+
|
|
35
|
+
function Alert({
|
|
36
|
+
className,
|
|
37
|
+
variant,
|
|
38
|
+
children,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
41
|
+
// Determinar a variante válida com fallback seguro
|
|
42
|
+
const alertVariant: AlertVariantType =
|
|
43
|
+
variant && variant in alertIcons
|
|
44
|
+
? (variant as AlertVariantType)
|
|
45
|
+
: "default";
|
|
46
|
+
|
|
47
|
+
const Icon = alertIcons[alertVariant];
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
data-slot="alert"
|
|
52
|
+
role="alert"
|
|
53
|
+
className={cn(alertVariants({ variant }), className)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
<Icon className="flex-shrink-0" />
|
|
57
|
+
<div className="flex-1">{children}</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
data-slot="alert-title"
|
|
66
|
+
className={cn(
|
|
67
|
+
"font-medium mb-1 leading-tight",
|
|
68
|
+
className,
|
|
69
|
+
)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function AlertDescription({
|
|
76
|
+
className,
|
|
77
|
+
...props
|
|
78
|
+
}: React.ComponentProps<"div">) {
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
data-slot="alert-description"
|
|
82
|
+
className={cn(
|
|
83
|
+
"leading-relaxed",
|
|
84
|
+
className,
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { Alert, AlertTitle, AlertDescription };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
|
|
4
|
+
|
|
5
|
+
function AspectRatio({
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
|
|
8
|
+
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { AspectRatio };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
function Avatar({
|
|
9
|
+
className,
|
|
10
|
+
size = "md",
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
13
|
+
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
14
|
+
}) {
|
|
15
|
+
const sizeClasses = {
|
|
16
|
+
xs: "size-6",
|
|
17
|
+
sm: "size-8",
|
|
18
|
+
md: "size-10",
|
|
19
|
+
lg: "size-12",
|
|
20
|
+
xl: "size-16",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<AvatarPrimitive.Root
|
|
25
|
+
data-slot="avatar"
|
|
26
|
+
className={cn(
|
|
27
|
+
"relative flex shrink-0 overflow-hidden rounded-full",
|
|
28
|
+
sizeClasses[size],
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AvatarImage({
|
|
37
|
+
className,
|
|
38
|
+
...props
|
|
39
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
40
|
+
return (
|
|
41
|
+
<AvatarPrimitive.Image
|
|
42
|
+
data-slot="avatar-image"
|
|
43
|
+
className={cn("aspect-square size-full", className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function AvatarFallback({
|
|
50
|
+
className,
|
|
51
|
+
...props
|
|
52
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
53
|
+
return (
|
|
54
|
+
<AvatarPrimitive.Fallback
|
|
55
|
+
data-slot="avatar-fallback"
|
|
56
|
+
className={cn(
|
|
57
|
+
"bg-muted flex size-full items-center justify-center rounded-full",
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { Avatar, AvatarImage, AvatarFallback };
|