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.
Files changed (141) hide show
  1. package/App.tsx +182 -0
  2. package/README.md +330 -0
  3. package/assets/xertica-logo.svg +38 -0
  4. package/assets/xertica-x-logo.svg +21 -0
  5. package/bin/cli.ts +193 -0
  6. package/components/AssistenteXertica.tsx +2003 -0
  7. package/components/AudioPlayer.tsx +203 -0
  8. package/components/CodeBlock.tsx +242 -0
  9. package/components/DocumentEditor.tsx +504 -0
  10. package/components/ForgotPasswordPage.tsx +170 -0
  11. package/components/FormattedDocument.tsx +87 -0
  12. package/components/HomeContent.tsx +123 -0
  13. package/components/HomePage.tsx +70 -0
  14. package/components/LanguageSelector.tsx +54 -0
  15. package/components/LoginPage.tsx +199 -0
  16. package/components/MarkdownMessage.tsx +62 -0
  17. package/components/ModernChatInput.tsx +502 -0
  18. package/components/PodcastPlayer.tsx +409 -0
  19. package/components/ResetPasswordPage.tsx +234 -0
  20. package/components/Sidebar.tsx +489 -0
  21. package/components/TemplateContent.tsx +629 -0
  22. package/components/TemplatePage.tsx +70 -0
  23. package/components/ThemeToggle.tsx +65 -0
  24. package/components/VerifyEmailPage.tsx +187 -0
  25. package/components/XerticaLogo.tsx +69 -0
  26. package/components/XerticaOrbe.tsx +1339 -0
  27. package/components/XerticaXLogo.tsx +53 -0
  28. package/components/examples/DrawingMapExample.tsx +530 -0
  29. package/components/examples/FilterableMapExample.tsx +380 -0
  30. package/components/examples/LocationPickerExample.tsx +330 -0
  31. package/components/examples/MapExamples.tsx +280 -0
  32. package/components/examples/MapShowcase.tsx +446 -0
  33. package/components/examples/RouteMapExamples.tsx +329 -0
  34. package/components/examples/SimpleFilterableMap.tsx +192 -0
  35. package/components/examples/index.ts +52 -0
  36. package/components/figma/ImageWithFallback.tsx +27 -0
  37. package/components/index.ts +44 -0
  38. package/components/media/AudioPlayer.tsx +278 -0
  39. package/components/media/FloatingMediaWrapper.tsx +166 -0
  40. package/components/media/VideoPlayer.tsx +285 -0
  41. package/components/ui/accordion.tsx +66 -0
  42. package/components/ui/alert-dialog.tsx +159 -0
  43. package/components/ui/alert.tsx +91 -0
  44. package/components/ui/aspect-ratio.tsx +11 -0
  45. package/components/ui/avatar.tsx +65 -0
  46. package/components/ui/badge.tsx +55 -0
  47. package/components/ui/breadcrumb.tsx +109 -0
  48. package/components/ui/button.tsx +78 -0
  49. package/components/ui/calendar.tsx +235 -0
  50. package/components/ui/card.tsx +92 -0
  51. package/components/ui/carousel.tsx +241 -0
  52. package/components/ui/chart.tsx +353 -0
  53. package/components/ui/checkbox.tsx +32 -0
  54. package/components/ui/collapsible.tsx +33 -0
  55. package/components/ui/command.tsx +177 -0
  56. package/components/ui/context-menu.tsx +252 -0
  57. package/components/ui/dialog.tsx +138 -0
  58. package/components/ui/drawer.tsx +134 -0
  59. package/components/ui/dropdown-menu.tsx +257 -0
  60. package/components/ui/empty.tsx +90 -0
  61. package/components/ui/file-upload.tsx +152 -0
  62. package/components/ui/form.tsx +195 -0
  63. package/components/ui/google-maps-loader.tsx +379 -0
  64. package/components/ui/hover-card.tsx +44 -0
  65. package/components/ui/index.ts +242 -0
  66. package/components/ui/input-otp.tsx +77 -0
  67. package/components/ui/input.tsx +38 -0
  68. package/components/ui/label.tsx +24 -0
  69. package/components/ui/map-config.ts +12 -0
  70. package/components/ui/map-layers.tsx +129 -0
  71. package/components/ui/map.exports.ts +31 -0
  72. package/components/ui/map.tsx +412 -0
  73. package/components/ui/menubar.tsx +276 -0
  74. package/components/ui/navigation-menu.tsx +162 -0
  75. package/components/ui/notification-badge.tsx +61 -0
  76. package/components/ui/page-header.tsx +229 -0
  77. package/components/ui/pagination.tsx +127 -0
  78. package/components/ui/popover.tsx +48 -0
  79. package/components/ui/progress.tsx +31 -0
  80. package/components/ui/radio-group.tsx +56 -0
  81. package/components/ui/rating.tsx +102 -0
  82. package/components/ui/resizable.tsx +405 -0
  83. package/components/ui/route-map.tsx +246 -0
  84. package/components/ui/scroll-area.tsx +58 -0
  85. package/components/ui/search.tsx +70 -0
  86. package/components/ui/select.tsx +176 -0
  87. package/components/ui/separator.tsx +28 -0
  88. package/components/ui/sheet.tsx +138 -0
  89. package/components/ui/sidebar.tsx +726 -0
  90. package/components/ui/simple-map.tsx +92 -0
  91. package/components/ui/skeleton.tsx +13 -0
  92. package/components/ui/slider.tsx +58 -0
  93. package/components/ui/sonner.tsx +77 -0
  94. package/components/ui/stats-card.tsx +84 -0
  95. package/components/ui/stepper.tsx +126 -0
  96. package/components/ui/switch.tsx +34 -0
  97. package/components/ui/table.tsx +116 -0
  98. package/components/ui/tabs.tsx +66 -0
  99. package/components/ui/textarea.tsx +26 -0
  100. package/components/ui/timeline.tsx +140 -0
  101. package/components/ui/toggle-group.tsx +71 -0
  102. package/components/ui/toggle.tsx +46 -0
  103. package/components/ui/tooltip.tsx +61 -0
  104. package/components/ui/tree-view.tsx +123 -0
  105. package/components/ui/use-mobile.ts +24 -0
  106. package/components/ui/utils.ts +6 -0
  107. package/components/ui/xertica-assistant.tsx +1420 -0
  108. package/contexts/ApiKeyContext.tsx +123 -0
  109. package/contexts/AssistenteContext.tsx +118 -0
  110. package/contexts/BrandColorsContext.tsx +551 -0
  111. package/contexts/LanguageContext.tsx +36 -0
  112. package/contexts/ThemeContext.tsx +85 -0
  113. package/dist/cli.js +20922 -0
  114. package/eslint.config.js +41 -0
  115. package/guidelines/Guidelines.md +61 -0
  116. package/hooks/useTheme.ts +4 -0
  117. package/imports/Podcast.tsx +389 -0
  118. package/imports/XerticaAi.tsx +46 -0
  119. package/imports/XerticaX.tsx +20 -0
  120. package/imports/svg-aueiaqngck.ts +11 -0
  121. package/imports/svg-v9krss1ozd.ts +16 -0
  122. package/imports/svg-vhrdofe3qe.ts +5 -0
  123. package/index.css +4448 -0
  124. package/index.html +14 -0
  125. package/main.tsx +10 -0
  126. package/package.json +119 -0
  127. package/postcss.config.js +6 -0
  128. package/routes.tsx +33 -0
  129. package/styles/globals.css +15 -0
  130. package/styles/xertica/app-overrides/chat.css +61 -0
  131. package/styles/xertica/app-overrides/scrollbar.css +33 -0
  132. package/styles/xertica/base.css +70 -0
  133. package/styles/xertica/integrations/google-maps.css +76 -0
  134. package/styles/xertica/integrations/sonner.css +73 -0
  135. package/styles/xertica/theme-map.css +88 -0
  136. package/styles/xertica/tokens.css +190 -0
  137. package/tsconfig.json +31 -0
  138. package/tsconfig.node.json +10 -0
  139. package/utils/gemini.ts +140 -0
  140. package/vite-env.d.ts +12 -0
  141. package/vite.config.ts +36 -0
@@ -0,0 +1,278 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { Play, Pause, Volume2, VolumeX, Maximize2 } 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 AudioPlayerProps {
9
+ src: string;
10
+ title?: string;
11
+ artist?: string;
12
+ coverArt?: string;
13
+ autoPlay?: boolean;
14
+ className?: string;
15
+ }
16
+
17
+ export function AudioPlayer({
18
+ src,
19
+ title = "Audio",
20
+ artist,
21
+ autoPlay = false,
22
+ className
23
+ }: AudioPlayerProps) {
24
+ const audioRef = useRef<HTMLAudioElement>(null);
25
+ const containerRef = useRef<HTMLDivElement>(null);
26
+ const [isPlaying, setIsPlaying] = useState(false);
27
+ const [progress, setProgress] = useState(0);
28
+ const [volume, setVolume] = useState(1);
29
+ const [isMuted, setIsMuted] = useState(false);
30
+ const [currentTime, setCurrentTime] = useState(0);
31
+ const [duration, setDuration] = useState(0);
32
+ const [isFloating, setIsFloating] = useState(false);
33
+ const [isManualFloating, setIsManualFloating] = useState(false);
34
+
35
+ // Wrapper for setIsFloating to handle scroll-on-restore and sync state
36
+ const handleSetFloating = (floating: boolean) => {
37
+ // Capture precise time before switching
38
+ if (audioRef.current) {
39
+ setCurrentTime(audioRef.current.currentTime);
40
+ }
41
+
42
+ setIsFloating(floating);
43
+
44
+ if (!floating) {
45
+ setIsManualFloating(false);
46
+ if (containerRef.current) {
47
+ containerRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
48
+ }
49
+ }
50
+ };
51
+
52
+ // Restore state when switching modes (remounting audio element)
53
+ useEffect(() => {
54
+ const audio = audioRef.current;
55
+ if (audio) {
56
+ // If we are switching modes, restore time and play state
57
+ if (Math.abs(audio.currentTime - currentTime) > 0.5) {
58
+ audio.currentTime = currentTime;
59
+ }
60
+ if (isPlaying) {
61
+ const playPromise = audio.play();
62
+ if (playPromise !== undefined) {
63
+ playPromise.catch(error => {
64
+ // Auto-play might be blocked or interrupted
65
+ console.log("Playback interrupted during switch:", error);
66
+ });
67
+ }
68
+ }
69
+ }
70
+ // We depend on isFloating to trigger this check when the DOM node is recreated
71
+ // But we also need to attach listeners to the new node
72
+ }, [isFloating]);
73
+
74
+ // Auto-float on scroll logic
75
+ useEffect(() => {
76
+ const container = containerRef.current;
77
+ if (!container) return;
78
+
79
+ const observer = new IntersectionObserver(
80
+ ([entry]) => {
81
+ if (isPlaying && !entry.isIntersecting && !isFloating) {
82
+ // Sync time before floating
83
+ if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
84
+ setIsFloating(true);
85
+ }
86
+ else if (entry.isIntersecting && isFloating && !isManualFloating) {
87
+ // Sync time before docking
88
+ if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
89
+ handleSetFloating(false);
90
+ }
91
+ },
92
+ { threshold: 0.2 }
93
+ );
94
+
95
+ observer.observe(container);
96
+ return () => observer.disconnect();
97
+ }, [isPlaying, isFloating, isManualFloating]);
98
+
99
+ // Main Audio Event Listeners
100
+ // We need to re-run this effect when isFloating changes because audioRef.current changes
101
+ useEffect(() => {
102
+ const audio = audioRef.current;
103
+ if (!audio) return;
104
+
105
+ const updateTime = () => {
106
+ setCurrentTime(audio.currentTime);
107
+ };
108
+
109
+ const updateDuration = () => setDuration(audio.duration);
110
+ const onPlay = () => setIsPlaying(true);
111
+ const onPause = () => setIsPlaying(false);
112
+
113
+ audio.addEventListener('timeupdate', updateTime);
114
+ audio.addEventListener('loadedmetadata', updateDuration);
115
+ audio.addEventListener('play', onPlay);
116
+ audio.addEventListener('pause', onPause);
117
+
118
+ // Initial volume set
119
+ audio.volume = volume;
120
+ audio.muted = isMuted;
121
+
122
+ return () => {
123
+ audio.removeEventListener('timeupdate', updateTime);
124
+ audio.removeEventListener('loadedmetadata', updateDuration);
125
+ audio.removeEventListener('play', onPlay);
126
+ audio.removeEventListener('pause', onPause);
127
+ };
128
+ }, [isFloating]); // Depend on isFloating to re-attach listeners to new element
129
+
130
+ const togglePlay = () => {
131
+ if (audioRef.current) {
132
+ if (isPlaying) {
133
+ audioRef.current.pause();
134
+ } else {
135
+ audioRef.current.play();
136
+ }
137
+ }
138
+ };
139
+
140
+ const handleSeek = (value: number[]) => {
141
+ if (audioRef.current) {
142
+ audioRef.current.currentTime = value[0];
143
+ setCurrentTime(value[0]);
144
+ }
145
+ };
146
+
147
+ const handleVolumeChange = (value: number[]) => {
148
+ const newVolume = value[0];
149
+ if (audioRef.current) {
150
+ audioRef.current.volume = newVolume;
151
+ setVolume(newVolume);
152
+ setIsMuted(newVolume === 0);
153
+ }
154
+ };
155
+
156
+ const formatTime = (time: number) => {
157
+ const minutes = Math.floor(time / 60);
158
+ const seconds = Math.floor(time % 60);
159
+ return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
160
+ };
161
+
162
+ const PlayerControls = ({ isCompact = false }) => (
163
+ <div className={cn("flex items-center gap-3 w-full", isCompact ? "px-3 py-1" : "px-4 py-3")}>
164
+ <Button
165
+ onClick={togglePlay}
166
+ size="icon"
167
+ variant="outline"
168
+ className={cn(
169
+ "shrink-0 rounded-full border-primary/20 text-primary hover:bg-primary/5 hover:text-primary hover:border-primary/50 transition-colors",
170
+ isCompact ? "h-8 w-8" : "h-10 w-10"
171
+ )}
172
+ >
173
+ {isPlaying ? <Pause className="w-4 h-4 fill-current" /> : <Play className="w-4 h-4 fill-current ml-0.5" />}
174
+ </Button>
175
+
176
+ <div className="flex-1 min-w-0 flex flex-col justify-center gap-1.5">
177
+ <div className="flex items-center justify-between text-xs leading-none">
178
+ <div className="flex items-center gap-2 truncate">
179
+ <span className="font-medium text-foreground truncate">{title}</span>
180
+ {artist && !isCompact && <span className="text-muted-foreground hidden sm:inline-block border-l pl-2 border-border truncate">{artist}</span>}
181
+ </div>
182
+ <div className="text-muted-foreground font-medium tabular-nums shrink-0 text-[10px] sm:text-xs">
183
+ {formatTime(currentTime)} / {formatTime(duration || 0)}
184
+ </div>
185
+ </div>
186
+
187
+ <Slider
188
+ value={[currentTime]}
189
+ max={duration || 100}
190
+ step={1}
191
+ onValueChange={handleSeek}
192
+ className="w-full"
193
+ />
194
+ </div>
195
+
196
+ <div className="flex items-center gap-1 shrink-0">
197
+ {!isCompact && (
198
+ <div className="hidden sm:flex items-center gap-2 group/volume mr-2">
199
+ <Button
200
+ variant="ghost"
201
+ size="icon"
202
+ className="h-8 w-8 text-muted-foreground hover:text-foreground"
203
+ onClick={() => {
204
+ const newMuted = !isMuted;
205
+ setIsMuted(newMuted);
206
+ if (audioRef.current) audioRef.current.muted = newMuted;
207
+ }}
208
+ >
209
+ {isMuted ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}
210
+ </Button>
211
+ <div className="w-0 overflow-hidden group-hover/volume:w-20 transition-all duration-300">
212
+ <Slider
213
+ value={[isMuted ? 0 : volume]}
214
+ max={1}
215
+ step={0.01}
216
+ onValueChange={handleVolumeChange}
217
+ className="w-20"
218
+ />
219
+ </div>
220
+ </div>
221
+ )}
222
+
223
+ {!isCompact && (
224
+ <div className="w-px h-6 bg-border mx-1 hidden sm:block" />
225
+ )}
226
+
227
+ {!isCompact && (
228
+ <Button
229
+ variant="ghost"
230
+ size="icon"
231
+ className="h-8 w-8 text-muted-foreground hover:text-foreground"
232
+ onClick={() => {
233
+ setIsManualFloating(true);
234
+ handleSetFloating(true);
235
+ }}
236
+ title="Modo Flutuante"
237
+ >
238
+ <Maximize2 className="w-4 h-4" />
239
+ </Button>
240
+ )}
241
+ </div>
242
+ </div>
243
+ );
244
+
245
+ return (
246
+ <div ref={containerRef} className={className}>
247
+ <FloatingMediaWrapper
248
+ isFloating={isFloating}
249
+ setIsFloating={handleSetFloating}
250
+ onCloseMedia={() => {
251
+ setIsPlaying(false);
252
+ setIsFloating(false);
253
+ if (audioRef.current) audioRef.current.pause();
254
+ }}
255
+ title={title}
256
+ aspectRatio={320/110}
257
+ minHeight={110}
258
+ minWidth={320}
259
+ className="w-full"
260
+ >
261
+ <div className={cn(
262
+ "bg-card w-full overflow-hidden flex flex-col justify-center",
263
+ isFloating
264
+ ? "h-full bg-transparent text-primary-foreground [&_.text-muted-foreground]:text-primary-foreground/80 [&_.text-foreground]:text-primary-foreground [&_.text-primary]:text-primary-foreground [&_.border-primary\\/20]:border-primary-foreground/30 [&_.hover\\:bg-primary\\/5]:hover:bg-primary-foreground/10 [&_[data-slot=slider-track]]:bg-primary-foreground/20 [&_[data-slot=slider-range]]:bg-primary-foreground [&_[data-slot=slider-thumb]]:border-primary-foreground"
265
+ : "border rounded-md shadow-sm"
266
+ )}>
267
+ <audio
268
+ ref={audioRef}
269
+ src={src}
270
+ autoPlay={autoPlay}
271
+ className="hidden"
272
+ />
273
+ <PlayerControls isCompact={isFloating} />
274
+ </div>
275
+ </FloatingMediaWrapper>
276
+ </div>
277
+ );
278
+ }
@@ -0,0 +1,166 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { motion } from 'framer-motion';
4
+ import { Resizable } from 're-resizable';
5
+ import { X, GripHorizontal, Maximize2, Minimize2 } from 'lucide-react';
6
+ import { Button } from '../ui/button';
7
+ import { cn } from '../ui/utils';
8
+
9
+ interface FloatingMediaWrapperProps {
10
+ children: React.ReactNode;
11
+ isFloating: boolean;
12
+ setIsFloating: (floating: boolean) => void;
13
+ title?: string;
14
+ onClose?: () => void;
15
+ aspectRatio?: number;
16
+ className?: string;
17
+ minWidth?: number;
18
+ minHeight?: number;
19
+ }
20
+
21
+ export function FloatingMediaWrapper({
22
+ children,
23
+ isFloating,
24
+ setIsFloating,
25
+ title,
26
+ onClose,
27
+ aspectRatio = 16 / 9,
28
+ className,
29
+ minWidth = 320,
30
+ minHeight = 180,
31
+ }: FloatingMediaWrapperProps) {
32
+ const [isMounted, setIsMounted] = useState(false);
33
+
34
+ useEffect(() => {
35
+ setIsMounted(true);
36
+ return () => setIsMounted(false);
37
+ }, []);
38
+
39
+ // Placeholder when content is floating
40
+ if (isFloating) {
41
+ return (
42
+ <>
43
+ <div
44
+ className={cn(
45
+ "w-full rounded-[var(--radius-card)] bg-muted/20 border border-dashed border-muted-foreground/20 flex items-center justify-center flex-col gap-2 p-8 text-muted-foreground transition-all",
46
+ className
47
+ )}
48
+ style={{ aspectRatio }}
49
+ >
50
+ <Maximize2 className="w-8 h-8 opacity-50" />
51
+ <p className="text-sm font-medium">Reproduzindo em janela flutuante</p>
52
+ <Button variant="outline" size="sm" onClick={() => setIsFloating(false)}>
53
+ Restaurar para a página
54
+ </Button>
55
+ </div>
56
+ {isMounted && createPortal(
57
+ <FloatingContainer
58
+ onClose={() => setIsFloating(false)}
59
+ onCloseMedia={onClose}
60
+ title={title}
61
+ aspectRatio={aspectRatio}
62
+ minWidth={minWidth}
63
+ minHeight={minHeight}
64
+ >
65
+ {children}
66
+ </FloatingContainer>,
67
+ document.body
68
+ )}
69
+ </>
70
+ );
71
+ }
72
+
73
+ return (
74
+ <div className={cn("relative w-full overflow-hidden rounded-[var(--radius-card)]", className)}>
75
+ {children}
76
+ </div>
77
+ );
78
+ }
79
+
80
+ interface FloatingContainerProps {
81
+ children: React.ReactNode;
82
+ onClose: () => void;
83
+ onCloseMedia?: () => void;
84
+ title?: string;
85
+ aspectRatio: number;
86
+ minWidth: number;
87
+ minHeight: number;
88
+ }
89
+
90
+ function FloatingContainer({
91
+ children,
92
+ onClose,
93
+ onCloseMedia,
94
+ title,
95
+ aspectRatio,
96
+ minWidth,
97
+ minHeight
98
+ }: FloatingContainerProps) {
99
+ // Initial position: bottom right with some margin
100
+ const initialX = typeof window !== 'undefined' ? window.innerWidth - minWidth - 24 : 0;
101
+ const initialY = typeof window !== 'undefined' ? window.innerHeight - minHeight - 24 : 0;
102
+
103
+ return (
104
+ <div className="fixed inset-0 pointer-events-none z-50 overflow-hidden">
105
+ <motion.div
106
+ drag
107
+ dragMomentum={false}
108
+ dragElastic={0.05}
109
+ initial={{ x: initialX, y: initialY, scale: 0.9, opacity: 0 }}
110
+ animate={{ scale: 1, opacity: 1 }}
111
+ exit={{ scale: 0.9, opacity: 0 }}
112
+ className="pointer-events-auto absolute"
113
+ >
114
+ <Resizable
115
+ defaultSize={{
116
+ width: minWidth,
117
+ height: minWidth / aspectRatio,
118
+ }}
119
+ minWidth={minWidth}
120
+ minHeight={minHeight}
121
+ lockAspectRatio={aspectRatio}
122
+ enable={{
123
+ top: true, right: true, bottom: true, left: true,
124
+ topRight: true, bottomRight: true, bottomLeft: true, topLeft: true
125
+ }}
126
+ className="shadow-2xl rounded-[var(--radius-card)] overflow-hidden border border-primary/20 bg-primary/80 backdrop-blur-md text-primary-foreground"
127
+ >
128
+ <div className="flex flex-col h-full w-full">
129
+ {/* Drag Handle & Header */}
130
+ <div className="h-8 bg-primary-foreground/10 backdrop-blur-sm border-b border-primary-foreground/10 flex items-center justify-between px-2 cursor-move active:cursor-grabbing group z-50">
131
+ <div className="flex items-center gap-2 text-xs font-medium text-primary-foreground/90 truncate flex-1">
132
+ <GripHorizontal className="w-4 h-4" />
133
+ <span className="truncate max-w-[150px]">{title || 'Media Player'}</span>
134
+ </div>
135
+ <div className="flex items-center gap-1">
136
+ <Button
137
+ variant="ghost"
138
+ size="icon"
139
+ className="h-6 w-6 text-primary-foreground/80 hover:bg-primary-foreground/20 hover:text-primary-foreground"
140
+ onClick={onClose}
141
+ title="Restaurar"
142
+ >
143
+ <Minimize2 className="w-3 h-3" />
144
+ </Button>
145
+ <Button
146
+ variant="ghost"
147
+ size="icon"
148
+ className="h-6 w-6 text-primary-foreground/80 hover:bg-destructive/20 hover:text-destructive"
149
+ onClick={onCloseMedia || onClose}
150
+ title="Fechar"
151
+ >
152
+ <X className="w-3 h-3" />
153
+ </Button>
154
+ </div>
155
+ </div>
156
+
157
+ {/* Content */}
158
+ <div className="flex-1 relative bg-black overflow-hidden group">
159
+ {children}
160
+ </div>
161
+ </div>
162
+ </Resizable>
163
+ </motion.div>
164
+ </div>
165
+ );
166
+ }