xertica-ui 2.1.2 → 2.1.4
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/CHANGELOG.md +46 -0
- package/README.md +1 -1
- package/bin/cli.ts +1 -1
- package/bin/generate-tokens.ts +13 -7
- package/components/assistant/xertica-assistant/index.ts +2 -0
- package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +97 -0
- package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -0
- package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -0
- package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +86 -0
- package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +77 -0
- package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +573 -0
- package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +65 -0
- package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -0
- package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +98 -0
- package/components/assistant/xertica-assistant/parts/index.ts +16 -0
- package/components/assistant/xertica-assistant/types.ts +139 -0
- package/components/assistant/xertica-assistant/use-assistant.ts +559 -0
- package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +200 -0
- package/components/assistant/xertica-assistant/xertica-assistant.tsx +198 -1460
- package/components/brand/theme-toggle/ThemeToggle.tsx +8 -27
- package/components/hooks/index.ts +3 -0
- package/components/hooks/use-layout-shortcuts.ts +46 -0
- package/components/layout/sidebar/index.ts +2 -0
- package/components/layout/sidebar/sidebar.stories.tsx +160 -8
- package/components/layout/sidebar/sidebar.tsx +606 -497
- package/components/layout/sidebar/use-sidebar.ts +104 -0
- package/components/media/audio-player/AudioPlayer.tsx +131 -206
- package/components/media/audio-player/use-audio-player.ts +298 -0
- package/components/pages/home-page/HomePage.tsx +1 -1
- package/components/pages/template-content/TemplateContent.tsx +5 -5
- package/components/pages/template-page/TemplatePage.tsx +5 -5
- package/components/shared/CustomTooltipContent.tsx +52 -0
- package/components/shared/layout-constants.ts +1 -1
- package/components/ui/chart/chart.stories.tsx +966 -7
- package/components/ui/chart/chart.tsx +918 -45
- package/components/ui/file-upload/file-upload.stories.tsx +100 -0
- package/components/ui/file-upload/file-upload.tsx +14 -74
- package/components/ui/file-upload/index.ts +1 -0
- package/components/ui/file-upload/use-file-upload.ts +181 -0
- package/components/ui/pagination/index.ts +2 -0
- package/components/ui/pagination/pagination.stories.tsx +94 -0
- package/components/ui/pagination/use-pagination.ts +194 -0
- package/components/ui/rich-text-editor/index.ts +2 -0
- package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +129 -1
- package/components/ui/rich-text-editor/rich-text-editor.tsx +86 -305
- package/components/ui/rich-text-editor/use-rich-text-editor.ts +439 -0
- package/components/ui/stepper/index.ts +3 -1
- package/components/ui/stepper/stepper.stories.tsx +116 -0
- package/components/ui/stepper/stepper.tsx +4 -4
- package/components/ui/stepper/use-stepper.ts +137 -0
- package/components/ui/tree-view/index.ts +4 -1
- package/components/ui/tree-view/tree-view.stories.tsx +110 -4
- package/components/ui/tree-view/tree-view.tsx +17 -125
- package/components/ui/tree-view/use-tree-view.ts +229 -0
- package/contexts/AssistenteContext.tsx +17 -54
- package/contexts/BrandColorsContext.tsx +6 -17
- package/contexts/LayoutContext.tsx +5 -31
- package/dist/AssistantChart-BAudAfne.cjs +3591 -0
- package/dist/AssistantChart-BP8upjMk.js +3565 -0
- package/dist/AudioPlayer-1ypwE2Wh.cjs +936 -0
- package/dist/AudioPlayer-DuKXrCfy.js +937 -0
- package/dist/CustomTooltipContent-DHjkY0ww.js +40 -0
- package/dist/CustomTooltipContent-c_K-DWRr.cjs +56 -0
- package/dist/LanguageContext-BwhwC3G2.js +657 -0
- package/dist/LanguageContext-DvUt5jBg.cjs +656 -0
- package/dist/LayoutContext-BDmcZfMH.cjs +84 -0
- package/dist/LayoutContext-dbQvdC4O.js +85 -0
- package/dist/ThemeContext-RTy1m2Uq.js +82 -0
- package/dist/ThemeContext-bSzuOit2.cjs +81 -0
- package/dist/VerifyEmailPage-C_ihbcth.js +2828 -0
- package/dist/VerifyEmailPage-Dt7zgA4w.cjs +2827 -0
- package/dist/XerticaProvider-CW9hpCdF.cjs +39 -0
- package/dist/XerticaProvider-siSt9uG2.js +40 -0
- package/dist/XerticaXLogo-D8jf0SNv.cjs +214 -0
- package/dist/XerticaXLogo-fAJMy3H4.js +215 -0
- package/dist/assistant.cjs.js +2 -1
- package/dist/assistant.es.js +3 -2
- package/dist/brand.cjs.js +2 -2
- package/dist/brand.es.js +2 -2
- package/dist/cli.js +14 -8
- package/dist/components/assistant/xertica-assistant/index.d.ts +2 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantCollapsedView.d.ts +13 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantConversationList.d.ts +16 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.d.ts +17 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.d.ts +19 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantHeader.d.ts +11 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantMessageBubble.d.ts +29 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantTabBar.d.ts +13 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.d.ts +4 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.d.ts +17 -0
- package/dist/components/assistant/xertica-assistant/parts/index.d.ts +16 -0
- package/dist/components/assistant/xertica-assistant/types.d.ts +106 -0
- package/dist/components/assistant/xertica-assistant/use-assistant.d.ts +125 -0
- package/dist/components/assistant/xertica-assistant/xertica-assistant.d.ts +8 -97
- package/dist/components/hooks/index.d.ts +3 -0
- package/dist/components/hooks/use-layout-shortcuts.d.ts +22 -0
- package/dist/components/layout/sidebar/index.d.ts +2 -0
- package/dist/components/layout/sidebar/sidebar.d.ts +80 -0
- package/dist/components/layout/sidebar/use-sidebar.d.ts +22 -0
- package/dist/components/media/audio-player/AudioPlayer.d.ts +4 -1
- package/dist/components/media/audio-player/use-audio-player.d.ts +72 -0
- package/dist/components/shared/CustomTooltipContent.d.ts +20 -0
- package/dist/components/shared/layout-constants.d.ts +1 -1
- package/dist/components/ui/alert/alert.d.ts +1 -1
- package/dist/components/ui/badge/badge.d.ts +1 -1
- package/dist/components/ui/button/button.d.ts +2 -2
- package/dist/components/ui/chart/chart.d.ts +162 -5
- package/dist/components/ui/file-upload/file-upload.d.ts +2 -0
- package/dist/components/ui/file-upload/index.d.ts +1 -0
- package/dist/components/ui/file-upload/use-file-upload.d.ts +49 -0
- package/dist/components/ui/pagination/index.d.ts +2 -0
- package/dist/components/ui/pagination/use-pagination.d.ts +78 -0
- package/dist/components/ui/rich-text-editor/index.d.ts +2 -0
- package/dist/components/ui/rich-text-editor/use-rich-text-editor.d.ts +107 -0
- package/dist/components/ui/stepper/index.d.ts +3 -1
- package/dist/components/ui/stepper/stepper.d.ts +2 -2
- package/dist/components/ui/stepper/use-stepper.d.ts +60 -0
- package/dist/components/ui/tree-view/index.d.ts +4 -1
- package/dist/components/ui/tree-view/tree-view.d.ts +4 -6
- package/dist/components/ui/tree-view/use-tree-view.d.ts +60 -0
- package/dist/contexts/AssistenteContext.d.ts +10 -49
- package/dist/hooks.cjs.js +30 -10
- package/dist/hooks.es.js +25 -4
- package/dist/index.cjs.js +20 -9
- package/dist/index.es.js +38 -27
- package/dist/layout.cjs.js +82 -1
- package/dist/layout.es.js +83 -2
- package/dist/media.cjs.js +1 -1
- package/dist/media.es.js +1 -1
- package/dist/pages.cjs.js +1 -1
- package/dist/pages.es.js +1 -1
- package/dist/rich-text-editor-BmsjY03B.js +2949 -0
- package/dist/rich-text-editor-GS2kpTAK.cjs +2966 -0
- package/dist/sidebar-CVUGHOS_.cjs +756 -0
- package/dist/sidebar-CmvwjnVb.js +757 -0
- package/dist/ui.cjs.js +12 -2
- package/dist/ui.es.js +24 -14
- package/dist/use-audio-player-Bkh23vQ3.js +177 -0
- package/dist/use-audio-player-Dn1NR9xN.cjs +176 -0
- package/dist/utils/color-utils.d.ts +51 -0
- package/dist/xertica-assistant-BMqdyRVi.js +2082 -0
- package/dist/xertica-assistant-Bj3vBCq_.cjs +2081 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/ai-usage.md +28 -10
- package/docs/architecture-improvements.md +463 -0
- package/docs/architecture.md +77 -1
- package/docs/components/assistant-chart.md +1 -1
- package/docs/components/assistant.md +159 -0
- package/docs/components/audio-player.md +46 -0
- package/docs/components/branding.md +251 -0
- package/docs/components/chart.md +354 -39
- package/docs/components/code-block.md +108 -0
- package/docs/components/file-upload.md +119 -2
- package/docs/components/formatted-document.md +113 -0
- package/docs/components/hooks.md +430 -0
- package/docs/components/image-with-fallback.md +106 -0
- package/docs/components/map-layers.md +140 -0
- package/docs/components/modern-chat-input.md +163 -0
- package/docs/components/pages.md +351 -0
- package/docs/components/pagination.md +187 -0
- package/docs/components/rich-text-editor.md +164 -0
- package/docs/components/sidebar.md +153 -4
- package/docs/components/stepper.md +157 -12
- package/docs/components/tree-view.md +164 -6
- package/docs/doc-audit.md +223 -0
- package/docs/getting-started.md +155 -1
- package/docs/guidelines.md +14 -8
- package/docs/layout.md +2 -2
- package/docs/llms.md +29 -9
- package/docs/patterns/detail-page.md +276 -0
- package/docs/patterns/settings.md +346 -0
- package/docs/patterns/wizard.md +217 -0
- package/guidelines/Guidelines.md +5 -3
- package/llms.txt +1 -1
- package/package.json +10 -10
- package/styles/xertica/tokens.css +41 -12
- package/templates/CLAUDE.md +16 -6
- package/templates/guidelines/Guidelines.md +16 -4
- package/templates/package.json +3 -3
- package/templates/src/styles/xertica/tokens.css +39 -10
- package/utils/color-utils.ts +72 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import {
|
|
4
4
|
Play,
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
RotateCcw,
|
|
12
12
|
Gauge,
|
|
13
13
|
Info,
|
|
14
|
-
RefreshCw,
|
|
15
14
|
Download,
|
|
16
15
|
X,
|
|
17
16
|
ExternalLink,
|
|
@@ -33,10 +32,10 @@ import {
|
|
|
33
32
|
DropdownMenuItem,
|
|
34
33
|
DropdownMenuTrigger,
|
|
35
34
|
DropdownMenuSeparator,
|
|
36
|
-
} from
|
|
35
|
+
} from '../../ui/dropdown-menu';
|
|
37
36
|
import { FloatingMediaWrapper } from '../FloatingMediaWrapper';
|
|
38
37
|
import { cn } from '../../shared/utils';
|
|
39
|
-
import {
|
|
38
|
+
import { useAudioPlayer } from './use-audio-player';
|
|
40
39
|
|
|
41
40
|
export interface AudioPlayerProps {
|
|
42
41
|
/** Source URL of the audio file */
|
|
@@ -73,10 +72,13 @@ export interface AudioPlayerProps {
|
|
|
73
72
|
|
|
74
73
|
/**
|
|
75
74
|
* Unified Audio Player component.
|
|
75
|
+
*
|
|
76
|
+
* All state and logic is managed by the `useAudioPlayer` headless hook.
|
|
77
|
+
* This component is responsible only for rendering.
|
|
76
78
|
*/
|
|
77
79
|
export function AudioPlayer({
|
|
78
80
|
src,
|
|
79
|
-
title =
|
|
81
|
+
title = 'Áudio',
|
|
80
82
|
artist,
|
|
81
83
|
subtitle,
|
|
82
84
|
autoPlay = false,
|
|
@@ -88,191 +90,102 @@ export function AudioPlayer({
|
|
|
88
90
|
currentTime: initialTime = 0,
|
|
89
91
|
colorVariant = 'default',
|
|
90
92
|
enableAutoFloat = true,
|
|
91
|
-
onCloseFloating
|
|
93
|
+
onCloseFloating,
|
|
92
94
|
}: AudioPlayerProps) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
const audio = audioRef.current;
|
|
138
|
-
if (!audio) return;
|
|
139
|
-
audio.volume = volume;
|
|
140
|
-
audio.muted = isMuted;
|
|
141
|
-
audio.playbackRate = playbackSpeed;
|
|
142
|
-
}, [volume, isMuted, playbackSpeed, src]);
|
|
143
|
-
|
|
144
|
-
// Auto-float on scroll logic (only for 'card' variant)
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
if (variant !== 'card' || !enableAutoFloatLocal) return;
|
|
147
|
-
|
|
148
|
-
const container = containerRef.current;
|
|
149
|
-
if (!container) return;
|
|
150
|
-
|
|
151
|
-
if (typeof IntersectionObserver === 'undefined') return;
|
|
152
|
-
|
|
153
|
-
const observer = new IntersectionObserver(
|
|
154
|
-
([entry]) => {
|
|
155
|
-
if (isPlaying && !entry.isIntersecting && !isFloating) {
|
|
156
|
-
if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
|
|
157
|
-
setIsFloating(true);
|
|
158
|
-
}
|
|
159
|
-
else if (entry.isIntersecting && isFloating && !isManualFloating) {
|
|
160
|
-
if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
|
|
161
|
-
handleSetFloating(false);
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
{ threshold: 0.2 }
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
observer.observe(container);
|
|
168
|
-
return () => observer.disconnect();
|
|
169
|
-
}, [isPlaying, isFloating, isManualFloating, variant, enableAutoFloatLocal]);
|
|
170
|
-
|
|
171
|
-
// Restore state when switching modes (remounting audio element)
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
const audio = audioRef.current;
|
|
174
|
-
if (audio) {
|
|
175
|
-
if (Math.abs(audio.currentTime - currentTime) > 0.5) {
|
|
176
|
-
audio.currentTime = currentTime;
|
|
177
|
-
}
|
|
178
|
-
if (isPlaying) {
|
|
179
|
-
audio.play().catch(err => console.log("Playback interrupted:", err));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}, [isFloating, variant]);
|
|
183
|
-
|
|
184
|
-
const handleSetFloating = (floating: boolean) => {
|
|
185
|
-
if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
|
|
186
|
-
setIsFloating(floating);
|
|
187
|
-
if (!floating) {
|
|
188
|
-
setIsManualFloating(false);
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const togglePlay = () => {
|
|
193
|
-
if (audioRef.current) {
|
|
194
|
-
if (isPlaying) {
|
|
195
|
-
audioRef.current.pause();
|
|
196
|
-
} else {
|
|
197
|
-
audioRef.current.play().catch(err => console.log("Playback error:", err));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const toggleMute = () => {
|
|
203
|
-
setIsMuted(!isMuted);
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const handleSeek = (value: number[]) => {
|
|
207
|
-
if (audioRef.current) {
|
|
208
|
-
audioRef.current.currentTime = value[0];
|
|
209
|
-
setCurrentTime(value[0]);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const handleVolumeChange = (value: number[]) => {
|
|
214
|
-
const newVolume = value[0];
|
|
215
|
-
setVolume(newVolume);
|
|
216
|
-
setIsMuted(newVolume === 0);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const formatTime = (time: number) => {
|
|
220
|
-
if (isNaN(time) || time === Infinity || !isFinite(time)) return "0:00";
|
|
221
|
-
const minutes = Math.floor(time / 60);
|
|
222
|
-
const seconds = Math.floor(time % 60);
|
|
223
|
-
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
|
224
|
-
};
|
|
225
|
-
|
|
95
|
+
const {
|
|
96
|
+
audioRef,
|
|
97
|
+
containerRef,
|
|
98
|
+
isPlaying,
|
|
99
|
+
currentTime,
|
|
100
|
+
duration,
|
|
101
|
+
volume,
|
|
102
|
+
isMuted,
|
|
103
|
+
playbackSpeed,
|
|
104
|
+
isFloating,
|
|
105
|
+
isManualFloating,
|
|
106
|
+
isVisible,
|
|
107
|
+
isMobile,
|
|
108
|
+
enableAutoFloatLocal,
|
|
109
|
+
sidebarExpanded,
|
|
110
|
+
sidebarWidth,
|
|
111
|
+
assistenteExpanded,
|
|
112
|
+
togglePlay,
|
|
113
|
+
toggleMute,
|
|
114
|
+
handleSeek,
|
|
115
|
+
handleVolumeChange,
|
|
116
|
+
handleSetFloating,
|
|
117
|
+
handleEnableManualFloat,
|
|
118
|
+
setPlaybackSpeed,
|
|
119
|
+
resetAudio,
|
|
120
|
+
formatTime,
|
|
121
|
+
onPlay,
|
|
122
|
+
onPause,
|
|
123
|
+
onEnded,
|
|
124
|
+
onTimeUpdate,
|
|
125
|
+
onLoadedMetadata,
|
|
126
|
+
} = useAudioPlayer({
|
|
127
|
+
src,
|
|
128
|
+
autoPlay,
|
|
129
|
+
initialTime,
|
|
130
|
+
initialDuration,
|
|
131
|
+
variant,
|
|
132
|
+
isOpen,
|
|
133
|
+
enableAutoFloat,
|
|
134
|
+
onCloseFloating,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ── Audio element (hidden) ──────────────────────────────────────────────────
|
|
226
138
|
const audioElement = (
|
|
227
139
|
<audio
|
|
228
140
|
ref={audioRef}
|
|
229
141
|
src={src}
|
|
230
142
|
autoPlay={autoPlay}
|
|
231
143
|
className="hidden"
|
|
232
|
-
onTimeUpdate={
|
|
233
|
-
onLoadedMetadata={
|
|
234
|
-
onPlay={
|
|
235
|
-
onPause={
|
|
236
|
-
onEnded={
|
|
144
|
+
onTimeUpdate={onTimeUpdate}
|
|
145
|
+
onLoadedMetadata={onLoadedMetadata}
|
|
146
|
+
onPlay={onPlay}
|
|
147
|
+
onPause={onPause}
|
|
148
|
+
onEnded={onEnded}
|
|
237
149
|
/>
|
|
238
150
|
);
|
|
239
151
|
|
|
152
|
+
// ── Bar variant — desktop layout ────────────────────────────────────────────
|
|
240
153
|
const desktopLayout = (
|
|
241
|
-
<div className={cn(
|
|
154
|
+
<div className={cn('px-6 flex items-center justify-between gap-4', colorVariant === 'primary' ? 'h-[80px] gap-6' : 'h-[80px]')}>
|
|
242
155
|
{/* Left: Info */}
|
|
243
|
-
<div className={cn(
|
|
156
|
+
<div className={cn('flex flex-col min-w-0 shrink-0 relative z-10', colorVariant === 'primary' ? 'w-[280px]' : 'w-[280px]')}>
|
|
244
157
|
<div className="flex items-center gap-2 mb-0.5">
|
|
245
158
|
{colorVariant === 'primary' ? (
|
|
246
159
|
<div className="w-2 h-2 rounded-full bg-[var(--chart-4)] animate-pulse shrink-0" />
|
|
247
160
|
) : (
|
|
248
161
|
<Radio className="w-3 h-3 text-[var(--chart-4)] animate-pulse shrink-0" />
|
|
249
162
|
)}
|
|
250
|
-
<h4 className={cn(
|
|
163
|
+
<h4 className={cn('truncate', colorVariant === 'primary' ? 'font-semibold text-base tracking-tight' : 'font-medium text-base')} title={title}>
|
|
251
164
|
{title}
|
|
252
165
|
</h4>
|
|
253
166
|
</div>
|
|
254
|
-
<div className={cn(
|
|
167
|
+
<div className={cn('flex items-center gap-1 text-xs text-muted-foreground', colorVariant === 'primary' && 'opacity-80')}>
|
|
255
168
|
<span className="truncate">{subtitle || artist}</span>
|
|
256
|
-
<ExternalLink className={cn(
|
|
169
|
+
<ExternalLink className={cn('w-3 h-3 cursor-pointer hover:text-foreground ml-1 transition-opacity', colorVariant === 'primary' ? 'opacity-60 hover:opacity-100' : 'opacity-70 hover:opacity-100')} />
|
|
257
170
|
</div>
|
|
258
171
|
</div>
|
|
259
172
|
|
|
260
173
|
{/* Center: Controls */}
|
|
261
|
-
<div className={cn(
|
|
174
|
+
<div className={cn('flex-1 flex items-center justify-center gap-4 min-w-0 max-w-xl relative z-10', colorVariant === 'primary' && 'gap-4')}>
|
|
262
175
|
<button
|
|
263
176
|
onClick={togglePlay}
|
|
264
|
-
aria-label={isPlaying ?
|
|
265
|
-
className={cn(
|
|
177
|
+
aria-label={isPlaying ? 'Pausar' : 'Reproduzir'}
|
|
178
|
+
className={cn('transition-all focus:outline-none shrink-0', colorVariant === 'primary' ? 'hover:scale-110' : 'text-muted-foreground hover:text-foreground hover:scale-110 active:scale-95 transform duration-100')}
|
|
266
179
|
>
|
|
267
180
|
{isPlaying ? (
|
|
268
|
-
<PauseCircle className={cn(colorVariant === 'primary' ?
|
|
181
|
+
<PauseCircle className={cn(colorVariant === 'primary' ? 'w-12 h-12' : 'w-10 h-10')} strokeWidth={colorVariant === 'primary' ? 1 : 1.5} />
|
|
269
182
|
) : (
|
|
270
|
-
<PlayCircle className={cn(colorVariant === 'primary' ?
|
|
183
|
+
<PlayCircle className={cn(colorVariant === 'primary' ? 'w-12 h-12' : 'w-10 h-10')} strokeWidth={colorVariant === 'primary' ? 1 : 1.5} />
|
|
271
184
|
)}
|
|
272
185
|
</button>
|
|
273
186
|
|
|
274
187
|
<div className="flex-1 flex items-center gap-3 max-w-lg">
|
|
275
|
-
<span className={cn(
|
|
188
|
+
<span className={cn('text-xs font-mono shrink-0 w-10 text-right', colorVariant === 'primary' ? 'text-[11px] w-12 opacity-80' : 'text-muted-foreground')}>
|
|
276
189
|
{formatTime(currentTime)}
|
|
277
190
|
</span>
|
|
278
191
|
<Slider
|
|
@@ -281,7 +194,7 @@ export function AudioPlayer({
|
|
|
281
194
|
onValueChange={handleSeek}
|
|
282
195
|
className="cursor-pointer"
|
|
283
196
|
/>
|
|
284
|
-
<span className={cn(
|
|
197
|
+
<span className={cn('text-xs font-mono shrink-0 w-10', colorVariant === 'primary' ? 'text-[11px] w-12 opacity-80' : 'text-muted-foreground')}>
|
|
285
198
|
{formatTime(duration)}
|
|
286
199
|
</span>
|
|
287
200
|
</div>
|
|
@@ -293,7 +206,7 @@ export function AudioPlayer({
|
|
|
293
206
|
<button
|
|
294
207
|
onClick={toggleMute}
|
|
295
208
|
className="text-muted-foreground hover:text-foreground"
|
|
296
|
-
aria-label={isMuted || volume === 0 ?
|
|
209
|
+
aria-label={isMuted || volume === 0 ? 'Ativar som' : 'Desativar som'}
|
|
297
210
|
>
|
|
298
211
|
{isMuted || volume === 0 ? <VolumeX className="w-5 h-5" /> : <Volume2 className="w-5 h-5" />}
|
|
299
212
|
</button>
|
|
@@ -310,7 +223,7 @@ export function AudioPlayer({
|
|
|
310
223
|
<TooltipProvider>
|
|
311
224
|
<Tooltip>
|
|
312
225
|
<TooltipTrigger asChild>
|
|
313
|
-
<Button variant="ghost" size="icon" className="rounded-full h-9 w-9" onClick={
|
|
226
|
+
<Button variant="ghost" size="icon" className="rounded-full h-9 w-9" onClick={resetAudio} aria-label="Recomecar audio">
|
|
314
227
|
<RotateCcw className="w-4 h-4" />
|
|
315
228
|
</Button>
|
|
316
229
|
</TooltipTrigger>
|
|
@@ -342,7 +255,7 @@ export function AudioPlayer({
|
|
|
342
255
|
<DropdownMenuItem
|
|
343
256
|
key={speed}
|
|
344
257
|
onClick={() => setPlaybackSpeed(speed)}
|
|
345
|
-
className={cn(
|
|
258
|
+
className={cn('cursor-pointer', playbackSpeed === speed && 'bg-accent font-bold')}
|
|
346
259
|
>
|
|
347
260
|
{speed}x
|
|
348
261
|
</DropdownMenuItem>
|
|
@@ -384,7 +297,7 @@ export function AudioPlayer({
|
|
|
384
297
|
</div>
|
|
385
298
|
|
|
386
299
|
<div className="md:hidden">
|
|
387
|
-
<DropdownMenuItem onClick={
|
|
300
|
+
<DropdownMenuItem onClick={resetAudio} className="cursor-pointer py-2.5">
|
|
388
301
|
<RotateCcw className="w-4 h-4 mr-3 text-muted-foreground" /> Recomeçar
|
|
389
302
|
</DropdownMenuItem>
|
|
390
303
|
<DropdownMenuItem className="cursor-pointer py-2.5">
|
|
@@ -405,8 +318,8 @@ export function AudioPlayer({
|
|
|
405
318
|
key={speed}
|
|
406
319
|
onClick={() => setPlaybackSpeed(speed)}
|
|
407
320
|
className={cn(
|
|
408
|
-
|
|
409
|
-
playbackSpeed === speed ?
|
|
321
|
+
'px-2 py-0.5 text-[10px] rounded border transition-colors',
|
|
322
|
+
playbackSpeed === speed ? 'bg-primary text-primary-foreground border-primary' : 'bg-transparent border-border hover:bg-accent'
|
|
410
323
|
)}
|
|
411
324
|
>
|
|
412
325
|
{speed}x
|
|
@@ -433,8 +346,9 @@ export function AudioPlayer({
|
|
|
433
346
|
</div>
|
|
434
347
|
);
|
|
435
348
|
|
|
349
|
+
// ── Bar variant — mobile layout ─────────────────────────────────────────────
|
|
436
350
|
const mobileLayout = (
|
|
437
|
-
<div className={cn(
|
|
351
|
+
<div className={cn('flex flex-col py-3 px-4 gap-3', colorVariant === 'primary' && 'py-4 px-5 gap-4')}>
|
|
438
352
|
<div className="flex items-start justify-between gap-3">
|
|
439
353
|
<div className="flex flex-col min-w-0 flex-1">
|
|
440
354
|
<div className="flex items-center gap-2 mb-1">
|
|
@@ -443,9 +357,9 @@ export function AudioPlayer({
|
|
|
443
357
|
) : (
|
|
444
358
|
<Radio className="w-3 h-3 text-[var(--chart-4)] animate-pulse shrink-0" />
|
|
445
359
|
)}
|
|
446
|
-
<h4 className={cn(
|
|
360
|
+
<h4 className={cn('truncate', colorVariant === 'primary' ? 'font-semibold text-sm tracking-tight' : 'font-medium text-sm')}>{title}</h4>
|
|
447
361
|
</div>
|
|
448
|
-
<div className={cn(
|
|
362
|
+
<div className={cn('flex items-center gap-1.5 text-xs text-muted-foreground', colorVariant === 'primary' && 'opacity-70')}>
|
|
449
363
|
<span className="truncate">{subtitle || artist}</span>
|
|
450
364
|
</div>
|
|
451
365
|
</div>
|
|
@@ -455,20 +369,20 @@ export function AudioPlayer({
|
|
|
455
369
|
</div>
|
|
456
370
|
|
|
457
371
|
<div className="flex items-center gap-3">
|
|
458
|
-
<button onClick={togglePlay} aria-label={isPlaying ?
|
|
372
|
+
<button onClick={togglePlay} aria-label={isPlaying ? 'Pausar' : 'Reproduzir'} className="shrink-0 transition-transform active:scale-95">
|
|
459
373
|
{isPlaying ? (
|
|
460
|
-
<PauseCircle className={cn(colorVariant === 'primary' ?
|
|
374
|
+
<PauseCircle className={cn(colorVariant === 'primary' ? 'w-12 h-12' : 'w-10 h-10')} strokeWidth={colorVariant === 'primary' ? 1 : 1.5} />
|
|
461
375
|
) : (
|
|
462
|
-
<PlayCircle className={cn(colorVariant === 'primary' ?
|
|
376
|
+
<PlayCircle className={cn(colorVariant === 'primary' ? 'w-12 h-12' : 'w-10 h-10')} strokeWidth={colorVariant === 'primary' ? 1 : 1.5} />
|
|
463
377
|
)}
|
|
464
378
|
</button>
|
|
465
379
|
|
|
466
380
|
<div className="flex-1 flex items-center gap-2">
|
|
467
|
-
<Slider
|
|
468
|
-
value={[currentTime]}
|
|
469
|
-
max={duration || 100}
|
|
470
|
-
onValueChange={handleSeek}
|
|
471
|
-
className="cursor-pointer"
|
|
381
|
+
<Slider
|
|
382
|
+
value={[currentTime]}
|
|
383
|
+
max={duration || 100}
|
|
384
|
+
onValueChange={handleSeek}
|
|
385
|
+
className="cursor-pointer"
|
|
472
386
|
aria-label="Progresso da reprodução"
|
|
473
387
|
/>
|
|
474
388
|
</div>
|
|
@@ -480,33 +394,34 @@ export function AudioPlayer({
|
|
|
480
394
|
</Button>
|
|
481
395
|
</DropdownMenuTrigger>
|
|
482
396
|
<DropdownMenuContent align="end" className="w-56 p-2 z-[1001]">
|
|
483
|
-
<DropdownMenuItem onClick={
|
|
484
|
-
<DropdownMenuItem>Velocidade (
|
|
397
|
+
<DropdownMenuItem onClick={resetAudio}>Recomeçar</DropdownMenuItem>
|
|
398
|
+
<DropdownMenuItem>Velocidade ({playbackSpeed}x)</DropdownMenuItem>
|
|
485
399
|
</DropdownMenuContent>
|
|
486
400
|
</DropdownMenu>
|
|
487
401
|
</div>
|
|
488
402
|
</div>
|
|
489
403
|
);
|
|
490
404
|
|
|
405
|
+
// ── Card variant — compact layout ───────────────────────────────────────────
|
|
491
406
|
const desktopLayoutCompact = (
|
|
492
|
-
<div className={cn(
|
|
407
|
+
<div className={cn('flex items-center gap-3 w-full', isFloating ? 'px-3 py-1 text-foreground' : 'px-4 py-3')}>
|
|
493
408
|
<Button
|
|
494
409
|
onClick={togglePlay}
|
|
495
410
|
size="icon"
|
|
496
411
|
variant="outline"
|
|
497
412
|
className={cn(
|
|
498
|
-
|
|
499
|
-
colorVariant === 'primary' &&
|
|
500
|
-
isFloating ?
|
|
413
|
+
'shrink-0 rounded-full border-border text-foreground hover:bg-accent transition-colors',
|
|
414
|
+
colorVariant === 'primary' && 'border-primary-foreground/20 hover:bg-primary-foreground/10 text-primary-foreground',
|
|
415
|
+
isFloating ? 'h-8 w-8' : 'h-10 w-10'
|
|
501
416
|
)}
|
|
502
|
-
aria-label={isPlaying ?
|
|
417
|
+
aria-label={isPlaying ? 'Pausar' : 'Reproduzir'}
|
|
503
418
|
>
|
|
504
|
-
{isPlaying ? <Pause className={cn(isFloating ?
|
|
419
|
+
{isPlaying ? <Pause className={cn(isFloating ? 'w-3 h-3' : 'w-4 h-4', 'fill-current')} /> : <Play className={cn(isFloating ? 'w-3 h-3' : 'w-4 h-4', 'fill-current ml-0.5')} />}
|
|
505
420
|
</Button>
|
|
506
421
|
<div className="flex-1 min-w-0 flex flex-col justify-center gap-1.5">
|
|
507
422
|
<div className="flex items-center justify-between text-xs leading-none">
|
|
508
|
-
<span className={cn(
|
|
509
|
-
<span className={cn(
|
|
423
|
+
<span className={cn('font-medium truncate', colorVariant === 'primary' ? 'text-primary-foreground' : 'text-foreground')}>{title}</span>
|
|
424
|
+
<span className={cn('font-medium tabular-nums text-[10px] sm:text-xs', colorVariant === 'primary' ? 'text-primary-foreground/70' : 'text-muted-foreground')}>
|
|
510
425
|
{formatTime(currentTime)} / {formatTime(duration)}
|
|
511
426
|
</span>
|
|
512
427
|
</div>
|
|
@@ -523,8 +438,17 @@ export function AudioPlayer({
|
|
|
523
438
|
<div className="flex items-center gap-1 shrink-0">
|
|
524
439
|
<DropdownMenu>
|
|
525
440
|
<DropdownMenuTrigger asChild>
|
|
526
|
-
<Button
|
|
527
|
-
|
|
441
|
+
<Button
|
|
442
|
+
variant="ghost"
|
|
443
|
+
size="icon"
|
|
444
|
+
className={cn(
|
|
445
|
+
'rounded-full',
|
|
446
|
+
colorVariant === 'primary' ? 'text-primary-foreground/80 hover:text-primary-foreground hover:bg-primary-foreground/10' : 'text-muted-foreground hover:text-foreground',
|
|
447
|
+
isFloating ? 'h-7 w-7' : 'h-8 w-8'
|
|
448
|
+
)}
|
|
449
|
+
aria-label="Mais opções"
|
|
450
|
+
>
|
|
451
|
+
<MoreVertical className={isFloating ? 'w-4 h-4' : 'w-5 h-5'} />
|
|
528
452
|
</Button>
|
|
529
453
|
</DropdownMenuTrigger>
|
|
530
454
|
<DropdownMenuContent align="end" className="w-56 p-2 z-[1001]">
|
|
@@ -556,8 +480,8 @@ export function AudioPlayer({
|
|
|
556
480
|
key={speed}
|
|
557
481
|
onClick={() => setPlaybackSpeed(speed)}
|
|
558
482
|
className={cn(
|
|
559
|
-
|
|
560
|
-
playbackSpeed === speed ?
|
|
483
|
+
'px-2 py-1 text-[10px] rounded border transition-colors',
|
|
484
|
+
playbackSpeed === speed ? 'bg-primary text-primary-foreground border-primary' : 'bg-transparent border-border hover:bg-accent'
|
|
561
485
|
)}
|
|
562
486
|
>
|
|
563
487
|
{speed}x
|
|
@@ -568,7 +492,7 @@ export function AudioPlayer({
|
|
|
568
492
|
|
|
569
493
|
<DropdownMenuSeparator className="my-2" />
|
|
570
494
|
|
|
571
|
-
<DropdownMenuItem onClick={
|
|
495
|
+
<DropdownMenuItem onClick={resetAudio} className="cursor-pointer py-2.5">
|
|
572
496
|
<RotateCcw className="w-4 h-4 mr-3 text-muted-foreground" />
|
|
573
497
|
<span className="text-sm">Recomeçar áudio</span>
|
|
574
498
|
</DropdownMenuItem>
|
|
@@ -584,12 +508,11 @@ export function AudioPlayer({
|
|
|
584
508
|
<Button
|
|
585
509
|
variant="ghost"
|
|
586
510
|
size="icon"
|
|
587
|
-
className={cn(
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}}
|
|
511
|
+
className={cn(
|
|
512
|
+
'h-8 w-8',
|
|
513
|
+
colorVariant === 'primary' ? 'text-primary-foreground/80 hover:text-primary-foreground hover:bg-primary-foreground/10' : 'text-muted-foreground hover:text-foreground'
|
|
514
|
+
)}
|
|
515
|
+
onClick={handleEnableManualFloat}
|
|
593
516
|
title="Modo Flutuante"
|
|
594
517
|
aria-label="Abrir modo flutuante"
|
|
595
518
|
>
|
|
@@ -600,22 +523,24 @@ export function AudioPlayer({
|
|
|
600
523
|
</div>
|
|
601
524
|
);
|
|
602
525
|
|
|
526
|
+
// ── Early return ────────────────────────────────────────────────────────────
|
|
603
527
|
if (!isVisible) return null;
|
|
604
528
|
|
|
529
|
+
// ── Bar variant — portal render ─────────────────────────────────────────────
|
|
605
530
|
if (variant === 'bar' && isVisible && typeof document !== 'undefined') {
|
|
606
531
|
return createPortal(
|
|
607
532
|
<div
|
|
608
533
|
className={cn(
|
|
609
|
-
|
|
534
|
+
'fixed bottom-0 z-[100] border-t transition-all duration-300 ease-in-out',
|
|
610
535
|
colorVariant === 'primary'
|
|
611
|
-
?
|
|
612
|
-
:
|
|
613
|
-
isOpen ?
|
|
536
|
+
? 'bg-primary/95 text-primary-foreground border-primary/20 backdrop-blur-sm [&_.text-muted-foreground]:text-primary-foreground/70 [&_.text-foreground]:text-primary-foreground [&_button]:text-primary-foreground [&_[data-slot=slider-track]]:bg-primary-foreground/30 [&_[data-slot=slider-range]]:bg-primary-foreground [&_[data-slot=slider-thumb]]:border-primary-foreground [&_[data-slot=slider-thumb]]:bg-primary-foreground'
|
|
537
|
+
: 'bg-card/95 text-card-foreground border-border backdrop-blur-md shadow-[var(--elevation-sm)]',
|
|
538
|
+
isOpen ? 'translate-y-0 opacity-100' : 'translate-y-full opacity-0',
|
|
614
539
|
className
|
|
615
540
|
)}
|
|
616
541
|
style={{
|
|
617
542
|
left: isMobile ? 0 : (sidebarExpanded ? sidebarWidth : 80),
|
|
618
|
-
right: isMobile ? 0 : (assistenteExpanded ? 420 : 80)
|
|
543
|
+
right: isMobile ? 0 : (assistenteExpanded ? 420 : 80),
|
|
619
544
|
}}
|
|
620
545
|
>
|
|
621
546
|
{audioElement}
|
|
@@ -625,6 +550,7 @@ export function AudioPlayer({
|
|
|
625
550
|
);
|
|
626
551
|
}
|
|
627
552
|
|
|
553
|
+
// ── Card variant — floating wrapper render ──────────────────────────────────
|
|
628
554
|
return (
|
|
629
555
|
<div ref={containerRef} className={className}>
|
|
630
556
|
{audioElement}
|
|
@@ -633,8 +559,7 @@ export function AudioPlayer({
|
|
|
633
559
|
setIsFloating={handleSetFloating}
|
|
634
560
|
onClose={() => handleSetFloating(false)}
|
|
635
561
|
onCloseMedia={() => {
|
|
636
|
-
|
|
637
|
-
setEnableAutoFloatLocal(false);
|
|
562
|
+
handleSetFloating(false);
|
|
638
563
|
}}
|
|
639
564
|
title="Reproduzindo Mídia"
|
|
640
565
|
aspectRatio={320 / 110}
|
|
@@ -646,13 +571,13 @@ export function AudioPlayer({
|
|
|
646
571
|
enablePadding
|
|
647
572
|
>
|
|
648
573
|
<div className={cn(
|
|
649
|
-
|
|
574
|
+
'w-full overflow-hidden flex flex-col justify-center',
|
|
650
575
|
colorVariant === 'primary'
|
|
651
|
-
?
|
|
652
|
-
:
|
|
576
|
+
? 'bg-primary text-primary-foreground [&_[data-slot=slider-track]]:bg-white/20 [&_[data-slot=slider-range]]:bg-white [&_[data-slot=slider-thumb]]:border-white [&_[data-slot=slider-thumb]]:bg-white'
|
|
577
|
+
: 'bg-card text-card-foreground',
|
|
653
578
|
isFloating
|
|
654
|
-
?
|
|
655
|
-
:
|
|
579
|
+
? 'h-full bg-transparent'
|
|
580
|
+
: 'border rounded-md shadow-sm'
|
|
656
581
|
)}>
|
|
657
582
|
{desktopLayoutCompact}
|
|
658
583
|
</div>
|