xertica-ui 1.2.4 → 1.2.6

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 (116) hide show
  1. package/components/CodeBlock.tsx +9 -9
  2. package/components/index.ts +2 -1
  3. package/components/media/AudioPlayer.tsx +79 -79
  4. package/components/media/VideoPlayer.tsx +36 -36
  5. package/components/ui/drawer.tsx +12 -0
  6. package/components/ui/index.ts +132 -132
  7. package/components/ui/input.tsx +1 -1
  8. package/components/ui/map.tsx +1 -1
  9. package/components/ui/page-header.tsx +42 -12
  10. package/components/ui/sheet.tsx +5 -4
  11. package/components/ui/tree-view.tsx +1 -1
  12. package/dist/components/AssistenteXertica.d.ts +8 -0
  13. package/dist/components/AudioPlayer.d.ts +12 -0
  14. package/dist/components/CodeBlock.d.ts +8 -0
  15. package/dist/components/DocumentEditor.d.ts +7 -0
  16. package/dist/components/ForgotPasswordPage.d.ts +1 -0
  17. package/dist/components/FormattedDocument.d.ts +7 -0
  18. package/dist/components/HomeContent.d.ts +8 -0
  19. package/dist/components/HomePage.d.ts +8 -0
  20. package/dist/components/LanguageSelector.d.ts +7 -0
  21. package/dist/components/LoginPage.d.ts +5 -0
  22. package/dist/components/MarkdownMessage.d.ts +6 -0
  23. package/dist/components/ModernChatInput.d.ts +14 -0
  24. package/dist/components/PodcastPlayer.d.ts +15 -0
  25. package/dist/components/ResetPasswordPage.d.ts +1 -0
  26. package/dist/components/Sidebar.d.ts +14 -0
  27. package/dist/components/TemplateContent.d.ts +8 -0
  28. package/dist/components/TemplatePage.d.ts +8 -0
  29. package/dist/components/ThemeToggle.d.ts +8 -0
  30. package/dist/components/VerifyEmailPage.d.ts +1 -0
  31. package/dist/components/XerticaLogo.d.ts +7 -0
  32. package/dist/components/XerticaOrbe.d.ts +6 -0
  33. package/dist/components/XerticaXLogo.d.ts +7 -0
  34. package/dist/components/figma/ImageWithFallback.d.ts +2 -0
  35. package/dist/components/index.d.ts +15 -0
  36. package/dist/components/media/AudioPlayer.d.ts +9 -0
  37. package/dist/components/media/FloatingMediaWrapper.d.ts +14 -0
  38. package/dist/components/media/VideoPlayer.d.ts +8 -0
  39. package/dist/components/ui/accordion.d.ts +7 -0
  40. package/dist/components/ui/alert-dialog.d.ts +14 -0
  41. package/dist/components/ui/alert.d.ts +9 -0
  42. package/dist/components/ui/aspect-ratio.d.ts +3 -0
  43. package/dist/components/ui/avatar.d.ts +8 -0
  44. package/dist/components/ui/badge.d.ts +9 -0
  45. package/dist/components/ui/breadcrumb.d.ts +11 -0
  46. package/dist/components/ui/button.d.ts +14 -0
  47. package/dist/components/ui/calendar.d.ts +8 -0
  48. package/dist/components/ui/card.d.ts +9 -0
  49. package/dist/components/ui/carousel.d.ts +19 -0
  50. package/dist/components/ui/chart.d.ts +40 -0
  51. package/dist/components/ui/checkbox.d.ts +4 -0
  52. package/dist/components/ui/collapsible.d.ts +5 -0
  53. package/dist/components/ui/command.d.ts +16 -0
  54. package/dist/components/ui/context-menu.d.ts +25 -0
  55. package/dist/components/ui/dialog.d.ts +15 -0
  56. package/dist/components/ui/drawer.d.ts +14 -0
  57. package/dist/components/ui/dropdown-menu.d.ts +25 -0
  58. package/dist/components/ui/empty.d.ts +8 -0
  59. package/dist/components/ui/file-upload.d.ts +10 -0
  60. package/dist/components/ui/form.d.ts +24 -0
  61. package/dist/components/ui/google-maps-loader.d.ts +30 -0
  62. package/dist/components/ui/hover-card.d.ts +6 -0
  63. package/dist/components/ui/index.d.ts +81 -0
  64. package/dist/components/ui/input-otp.d.ts +11 -0
  65. package/dist/components/ui/input.d.ts +6 -0
  66. package/dist/components/ui/label.d.ts +4 -0
  67. package/dist/components/ui/map-config.d.ts +12 -0
  68. package/dist/components/ui/map-layers.d.ts +48 -0
  69. package/dist/components/ui/map.d.ts +53 -0
  70. package/dist/components/ui/map.exports.d.ts +25 -0
  71. package/dist/components/ui/menubar.d.ts +26 -0
  72. package/dist/components/ui/navigation-menu.d.ts +14 -0
  73. package/dist/components/ui/notification-badge.d.ts +11 -0
  74. package/dist/components/ui/page-header.d.ts +58 -0
  75. package/dist/components/ui/pagination.d.ts +13 -0
  76. package/dist/components/ui/popover.d.ts +7 -0
  77. package/dist/components/ui/progress.d.ts +4 -0
  78. package/dist/components/ui/radio-group.d.ts +5 -0
  79. package/dist/components/ui/rating.d.ts +12 -0
  80. package/dist/components/ui/resizable.d.ts +27 -0
  81. package/dist/components/ui/route-map.d.ts +26 -0
  82. package/dist/components/ui/scroll-area.d.ts +5 -0
  83. package/dist/components/ui/search.d.ts +8 -0
  84. package/dist/components/ui/select.d.ts +15 -0
  85. package/dist/components/ui/separator.d.ts +4 -0
  86. package/dist/components/ui/sheet.d.ts +14 -0
  87. package/dist/components/ui/sidebar.d.ts +69 -0
  88. package/dist/components/ui/simple-map.d.ts +51 -0
  89. package/dist/components/ui/skeleton.d.ts +2 -0
  90. package/dist/components/ui/slider.d.ts +4 -0
  91. package/dist/components/ui/sonner.d.ts +23 -0
  92. package/dist/components/ui/stats-card.d.ts +14 -0
  93. package/dist/components/ui/stepper.d.ts +17 -0
  94. package/dist/components/ui/switch.d.ts +4 -0
  95. package/dist/components/ui/table.d.ts +10 -0
  96. package/dist/components/ui/tabs.d.ts +7 -0
  97. package/dist/components/ui/textarea.d.ts +3 -0
  98. package/dist/components/ui/timeline.d.ts +12 -0
  99. package/dist/components/ui/toggle-group.d.ts +12 -0
  100. package/dist/components/ui/toggle.d.ts +12 -0
  101. package/dist/components/ui/tooltip.d.ts +7 -0
  102. package/dist/components/ui/tree-view.d.ts +15 -0
  103. package/dist/components/ui/use-mobile.d.ts +2 -0
  104. package/dist/components/ui/utils.d.ts +2 -0
  105. package/dist/components/ui/xertica-assistant.d.ts +196 -0
  106. package/dist/contexts/ApiKeyContext.d.ts +15 -0
  107. package/dist/contexts/AssistenteContext.d.ts +71 -0
  108. package/dist/contexts/LanguageContext.d.ts +11 -0
  109. package/dist/contexts/ThemeContext.d.ts +13 -0
  110. package/dist/index.es.js +79983 -0
  111. package/dist/index.umd.js +80002 -0
  112. package/dist/routes.d.ts +10 -0
  113. package/dist/utils/gemini.d.ts +8 -0
  114. package/dist/xertica-ui.css +4470 -0
  115. package/package.json +18 -4
  116. package/vite.config.ts +14 -5
@@ -137,11 +137,11 @@ interface CodeBlockProps {
137
137
  showLineNumbers?: boolean;
138
138
  }
139
139
 
140
- export const CodeBlock = ({
141
- code,
142
- language = 'tsx',
140
+ export const CodeBlock = ({
141
+ code,
142
+ language = 'tsx',
143
143
  filename,
144
- showLineNumbers = false
144
+ showLineNumbers = false
145
145
  }: CodeBlockProps) => {
146
146
  const [copied, setCopied] = useState(false);
147
147
 
@@ -161,7 +161,7 @@ export const CodeBlock = ({
161
161
  } else {
162
162
  throw new Error('Clipboard API not available');
163
163
  }
164
-
164
+
165
165
  setCopied(true);
166
166
  setTimeout(() => setCopied(false), 2000);
167
167
  } catch (err) {
@@ -175,10 +175,10 @@ export const CodeBlock = ({
175
175
  document.body.appendChild(textArea);
176
176
  textArea.focus();
177
177
  textArea.select();
178
-
178
+
179
179
  const successful = document.execCommand('copy');
180
180
  document.body.removeChild(textArea);
181
-
181
+
182
182
  if (successful) {
183
183
  setCopied(true);
184
184
  setTimeout(() => setCopied(false), 2000);
@@ -202,7 +202,7 @@ export const CodeBlock = ({
202
202
  <span className="text-xs text-muted-foreground uppercase">{language}</span>
203
203
  </div>
204
204
  )}
205
-
205
+
206
206
  <div className="relative">
207
207
  <Button
208
208
  variant="ghost"
@@ -220,7 +220,7 @@ export const CodeBlock = ({
220
220
  <div className="overflow-x-auto w-full max-w-full">
221
221
  <SyntaxHighlighter
222
222
  language={language}
223
- style={elegantTheme}
223
+ style={elegantTheme as any}
224
224
  showLineNumbers={showLineNumbers}
225
225
  wrapLines={true}
226
226
  customStyle={{
@@ -1,9 +1,10 @@
1
+ import '../index.css';
1
2
  // ============================================================================
2
3
  // Xertica Assistant Exports
3
4
  // ============================================================================
4
5
 
5
6
  export { XerticaAssistant } from './ui/xertica-assistant';
6
- export type {
7
+ export type {
7
8
  XerticaAssistantProps,
8
9
  Message,
9
10
  Conversation,
@@ -14,12 +14,12 @@ export interface AudioPlayerProps {
14
14
  className?: string;
15
15
  }
16
16
 
17
- export function AudioPlayer({
18
- src,
17
+ export function AudioPlayer({
18
+ src,
19
19
  title = "Audio",
20
20
  artist,
21
21
  autoPlay = false,
22
- className
22
+ className
23
23
  }: AudioPlayerProps) {
24
24
  const audioRef = useRef<HTMLAudioElement>(null);
25
25
  const containerRef = useRef<HTMLDivElement>(null);
@@ -38,9 +38,9 @@ export function AudioPlayer({
38
38
  if (audioRef.current) {
39
39
  setCurrentTime(audioRef.current.currentTime);
40
40
  }
41
-
41
+
42
42
  setIsFloating(floating);
43
-
43
+
44
44
  if (!floating) {
45
45
  setIsManualFloating(false);
46
46
  if (containerRef.current) {
@@ -55,21 +55,21 @@ export function AudioPlayer({
55
55
  if (audio) {
56
56
  // If we are switching modes, restore time and play state
57
57
  if (Math.abs(audio.currentTime - currentTime) > 0.5) {
58
- audio.currentTime = currentTime;
58
+ audio.currentTime = currentTime;
59
59
  }
60
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
- }
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
68
  }
69
69
  }
70
70
  // We depend on isFloating to trigger this check when the DOM node is recreated
71
71
  // But we also need to attach listeners to the new node
72
- }, [isFloating]);
72
+ }, [isFloating]);
73
73
 
74
74
  // Auto-float on scroll logic
75
75
  useEffect(() => {
@@ -79,17 +79,17 @@ export function AudioPlayer({
79
79
  const observer = new IntersectionObserver(
80
80
  ([entry]) => {
81
81
  if (isPlaying && !entry.isIntersecting && !isFloating) {
82
- // Sync time before floating
83
- if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
84
- setIsFloating(true);
82
+ // Sync time before floating
83
+ if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
84
+ setIsFloating(true);
85
85
  }
86
86
  else if (entry.isIntersecting && isFloating && !isManualFloating) {
87
- // Sync time before docking
88
- if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
89
- handleSetFloating(false);
87
+ // Sync time before docking
88
+ if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
89
+ handleSetFloating(false);
90
90
  }
91
91
  },
92
- { threshold: 0.2 }
92
+ { threshold: 0.2 }
93
93
  );
94
94
 
95
95
  observer.observe(container);
@@ -161,7 +161,7 @@ export function AudioPlayer({
161
161
 
162
162
  const PlayerControls = ({ isCompact = false }) => (
163
163
  <div className={cn("flex items-center gap-3 w-full", isCompact ? "px-3 py-1" : "px-4 py-3")}>
164
- <Button
164
+ <Button
165
165
  onClick={togglePlay}
166
166
  size="icon"
167
167
  variant="outline"
@@ -174,70 +174,70 @@ export function AudioPlayer({
174
174
  </Button>
175
175
 
176
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>
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>}
185
181
  </div>
186
-
187
- <Slider
188
- value={[currentTime]}
189
- max={duration || 100}
190
- step={1}
191
- onValueChange={handleSeek}
192
- className="w-full"
193
- />
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
194
  </div>
195
195
 
196
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 && (
197
+ {!isCompact && (
198
+ <div className="hidden sm:flex items-center gap-2 group/volume mr-2">
228
199
  <Button
229
200
  variant="ghost"
230
201
  size="icon"
231
202
  className="h-8 w-8 text-muted-foreground hover:text-foreground"
232
203
  onClick={() => {
233
- setIsManualFloating(true);
234
- handleSetFloating(true);
204
+ const newMuted = !isMuted;
205
+ setIsMuted(newMuted);
206
+ if (audioRef.current) audioRef.current.muted = newMuted;
235
207
  }}
236
- title="Modo Flutuante"
237
208
  >
238
- <Maximize2 className="w-4 h-4" />
209
+ {isMuted ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}
239
210
  </Button>
240
- )}
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
241
  </div>
242
242
  </div>
243
243
  );
@@ -247,21 +247,21 @@ export function AudioPlayer({
247
247
  <FloatingMediaWrapper
248
248
  isFloating={isFloating}
249
249
  setIsFloating={handleSetFloating}
250
- onCloseMedia={() => {
250
+ onClose={() => {
251
251
  setIsPlaying(false);
252
- setIsFloating(false);
252
+ setIsFloating(false);
253
253
  if (audioRef.current) audioRef.current.pause();
254
254
  }}
255
255
  title={title}
256
- aspectRatio={320/110}
256
+ aspectRatio={320 / 110}
257
257
  minHeight={110}
258
258
  minWidth={320}
259
259
  className="w-full"
260
260
  >
261
261
  <div className={cn(
262
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"
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
265
  : "border rounded-md shadow-sm"
266
266
  )}>
267
267
  <audio
@@ -13,12 +13,12 @@ export interface VideoPlayerProps {
13
13
  className?: string;
14
14
  }
15
15
 
16
- export function VideoPlayer({
17
- src,
18
- poster,
16
+ export function VideoPlayer({
17
+ src,
18
+ poster,
19
19
  title = "Untitled Video",
20
20
  autoPlay = false,
21
- className
21
+ className
22
22
  }: VideoPlayerProps) {
23
23
  const videoRef = useRef<HTMLVideoElement>(null);
24
24
  const containerRef = useRef<HTMLDivElement>(null);
@@ -31,7 +31,7 @@ export function VideoPlayer({
31
31
  const [isFloating, setIsFloating] = useState(false);
32
32
  const [isManualFloating, setIsManualFloating] = useState(false);
33
33
  const [showControls, setShowControls] = useState(true);
34
-
34
+
35
35
  let controlsTimeout: NodeJS.Timeout;
36
36
 
37
37
  // Wrapper for setIsFloating to handle scroll-on-restore and sync state
@@ -39,9 +39,9 @@ export function VideoPlayer({
39
39
  if (videoRef.current) {
40
40
  setCurrentTime(videoRef.current.currentTime);
41
41
  }
42
-
42
+
43
43
  setIsFloating(floating);
44
-
44
+
45
45
  if (!floating) {
46
46
  setIsManualFloating(false);
47
47
  if (containerRef.current) {
@@ -55,15 +55,15 @@ export function VideoPlayer({
55
55
  const video = videoRef.current;
56
56
  if (video) {
57
57
  if (Math.abs(video.currentTime - currentTime) > 0.5) {
58
- video.currentTime = currentTime;
58
+ video.currentTime = currentTime;
59
59
  }
60
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
- }
61
+ const playPromise = video.play();
62
+ if (playPromise !== undefined) {
63
+ playPromise.catch(error => {
64
+ console.log("Playback interrupted during switch:", error);
65
+ });
66
+ }
67
67
  }
68
68
  }
69
69
  }, [isFloating]);
@@ -76,11 +76,11 @@ export function VideoPlayer({
76
76
  const observer = new IntersectionObserver(
77
77
  ([entry]) => {
78
78
  if (isPlaying && !entry.isIntersecting && !isFloating) {
79
- if (videoRef.current) setCurrentTime(videoRef.current.currentTime);
80
- setIsFloating(true);
79
+ if (videoRef.current) setCurrentTime(videoRef.current.currentTime);
80
+ setIsFloating(true);
81
81
  } else if (entry.isIntersecting && isFloating && !isManualFloating) {
82
- if (videoRef.current) setCurrentTime(videoRef.current.currentTime);
83
- handleSetFloating(false);
82
+ if (videoRef.current) setCurrentTime(videoRef.current.currentTime);
83
+ handleSetFloating(false);
84
84
  }
85
85
  },
86
86
  { threshold: 0.2 }
@@ -179,16 +179,16 @@ export function VideoPlayer({
179
179
  <FloatingMediaWrapper
180
180
  isFloating={isFloating}
181
181
  setIsFloating={handleSetFloating}
182
- onCloseMedia={() => {
182
+ onClose={() => {
183
183
  setIsPlaying(false);
184
- setIsFloating(false);
184
+ setIsFloating(false);
185
185
  if (videoRef.current) videoRef.current.pause();
186
186
  }}
187
187
  title={title}
188
- aspectRatio={16/9}
188
+ aspectRatio={16 / 9}
189
189
  className="w-full h-full"
190
190
  >
191
- <div
191
+ <div
192
192
  className="relative w-full h-full bg-black group overflow-hidden"
193
193
  onMouseMove={handleMouseMove}
194
194
  onMouseLeave={() => isPlaying && setShowControls(false)}
@@ -205,12 +205,12 @@ export function VideoPlayer({
205
205
  {!isPlaying && (
206
206
  <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
207
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" />
208
+ <Play className="w-8 h-8 text-white ml-1" fill="white" />
209
209
  </div>
210
210
  </div>
211
211
  )}
212
212
 
213
- <div
213
+ <div
214
214
  className={cn(
215
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
216
  showControls ? "opacity-100" : "opacity-0"
@@ -263,18 +263,18 @@ export function VideoPlayer({
263
263
  </div>
264
264
 
265
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>
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
278
  </div>
279
279
  </div>
280
280
  </div>
@@ -117,6 +117,17 @@ function DrawerDescription({
117
117
  className={cn("text-muted-foreground text-sm", className)}
118
118
  {...props}
119
119
  />
120
+
121
+ );
122
+ }
123
+
124
+ function DrawerHandle({ className, ...props }: React.ComponentProps<"div">) {
125
+ return (
126
+ <div
127
+ data-slot="drawer-handle"
128
+ className={cn("bg-muted mx-auto mt-4 h-2 w-[100px] shrink-0 rounded-full", className)}
129
+ {...props}
130
+ />
120
131
  );
121
132
  }
122
133
 
@@ -131,4 +142,5 @@ export {
131
142
  DrawerFooter,
132
143
  DrawerTitle,
133
144
  DrawerDescription,
145
+ DrawerHandle,
134
146
  };