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
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useIsMobile } from '../../shared/use-mobile';
|
|
3
|
+
import { useOptionalLayout } from '../../../contexts/LayoutContext';
|
|
4
|
+
|
|
5
|
+
export interface UseAudioPlayerProps {
|
|
6
|
+
/** Source URL of the audio file */
|
|
7
|
+
src?: string;
|
|
8
|
+
/** Whether to play automatically on mount */
|
|
9
|
+
autoPlay?: boolean;
|
|
10
|
+
/** Initial playback time in seconds */
|
|
11
|
+
initialTime?: number;
|
|
12
|
+
/** Total duration in seconds (optional — loaded from metadata if not provided) */
|
|
13
|
+
initialDuration?: number;
|
|
14
|
+
/** Display variant: 'card' (standard floating) or 'bar' (global fixed bar) */
|
|
15
|
+
variant?: 'card' | 'bar';
|
|
16
|
+
/** Whether the player is currently visible and open (only for 'bar' variant) */
|
|
17
|
+
isOpen?: boolean;
|
|
18
|
+
/** Whether the player should automatically float when scrolled out of view */
|
|
19
|
+
enableAutoFloat?: boolean;
|
|
20
|
+
/** Callback fired when user manually closes the floating player */
|
|
21
|
+
onCloseFloating?: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseAudioPlayerReturn {
|
|
25
|
+
// ── Refs ──────────────────────────────────────────────────────────────────
|
|
26
|
+
audioRef: React.RefObject<HTMLAudioElement>;
|
|
27
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
28
|
+
|
|
29
|
+
// ── Playback state ────────────────────────────────────────────────────────
|
|
30
|
+
isPlaying: boolean;
|
|
31
|
+
currentTime: number;
|
|
32
|
+
duration: number;
|
|
33
|
+
volume: number;
|
|
34
|
+
isMuted: boolean;
|
|
35
|
+
playbackSpeed: number;
|
|
36
|
+
|
|
37
|
+
// ── UI state ──────────────────────────────────────────────────────────────
|
|
38
|
+
isFloating: boolean;
|
|
39
|
+
isManualFloating: boolean;
|
|
40
|
+
isVisible: boolean;
|
|
41
|
+
isMobile: boolean;
|
|
42
|
+
enableAutoFloatLocal: boolean;
|
|
43
|
+
|
|
44
|
+
// ── Layout context (for 'bar' variant positioning) ────────────────────────
|
|
45
|
+
sidebarExpanded: boolean;
|
|
46
|
+
sidebarWidth: number;
|
|
47
|
+
assistenteExpanded: boolean;
|
|
48
|
+
|
|
49
|
+
// ── Handlers ──────────────────────────────────────────────────────────────
|
|
50
|
+
togglePlay: () => void;
|
|
51
|
+
toggleMute: () => void;
|
|
52
|
+
handleSeek: (value: number[]) => void;
|
|
53
|
+
handleVolumeChange: (value: number[]) => void;
|
|
54
|
+
handleSetFloating: (floating: boolean) => void;
|
|
55
|
+
handleEnableManualFloat: () => void;
|
|
56
|
+
setPlaybackSpeed: (speed: number) => void;
|
|
57
|
+
resetAudio: () => void;
|
|
58
|
+
|
|
59
|
+
// ── Audio element event handlers (for use in <audio> JSX) ─────────────────
|
|
60
|
+
onPlay: () => void;
|
|
61
|
+
onPause: () => void;
|
|
62
|
+
onEnded: () => void;
|
|
63
|
+
onTimeUpdate: (e: React.SyntheticEvent<HTMLAudioElement>) => void;
|
|
64
|
+
onLoadedMetadata: (e: React.SyntheticEvent<HTMLAudioElement>) => void;
|
|
65
|
+
|
|
66
|
+
// ── Utilities ─────────────────────────────────────────────────────────────
|
|
67
|
+
formatTime: (time: number) => string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* `useAudioPlayer` — Headless hook for the AudioPlayer component.
|
|
72
|
+
*
|
|
73
|
+
* Encapsulates all state management and side effects for audio playback,
|
|
74
|
+
* including auto-float on scroll, mobile detection, and layout context integration.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* import { useAudioPlayer } from './use-audio-player';
|
|
79
|
+
*
|
|
80
|
+
* function MyCustomPlayer({ src }: { src: string }) {
|
|
81
|
+
* const { isPlaying, togglePlay, formatTime, currentTime, duration } = useAudioPlayer({ src });
|
|
82
|
+
* return (
|
|
83
|
+
* <div>
|
|
84
|
+
* <button onClick={togglePlay}>{isPlaying ? 'Pause' : 'Play'}</button>
|
|
85
|
+
* <span>{formatTime(currentTime)} / {formatTime(duration)}</span>
|
|
86
|
+
* </div>
|
|
87
|
+
* );
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function useAudioPlayer({
|
|
92
|
+
src,
|
|
93
|
+
autoPlay = false,
|
|
94
|
+
initialTime = 0,
|
|
95
|
+
initialDuration,
|
|
96
|
+
variant = 'card',
|
|
97
|
+
isOpen = true,
|
|
98
|
+
enableAutoFloat = true,
|
|
99
|
+
onCloseFloating,
|
|
100
|
+
}: UseAudioPlayerProps = {}): UseAudioPlayerReturn {
|
|
101
|
+
const audioRef = useRef<HTMLAudioElement>(null);
|
|
102
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
103
|
+
|
|
104
|
+
// Layout context for 'bar' variant positioning
|
|
105
|
+
const layout = useOptionalLayout();
|
|
106
|
+
const sidebarExpanded = layout?.sidebarExpanded ?? false;
|
|
107
|
+
const sidebarWidth = layout?.sidebarWidth ?? 256;
|
|
108
|
+
const assistenteExpanded = layout?.assistenteExpanded ?? false;
|
|
109
|
+
|
|
110
|
+
// Mobile detection via shared hook (replaces inline window.innerWidth < 768)
|
|
111
|
+
const isMobile = useIsMobile();
|
|
112
|
+
|
|
113
|
+
// ── Playback state ──────────────────────────────────────────────────────────
|
|
114
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
115
|
+
const [volume, setVolume] = useState(0.8);
|
|
116
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
117
|
+
const [currentTime, setCurrentTime] = useState(initialTime);
|
|
118
|
+
const [duration, setDuration] = useState(initialDuration || 0);
|
|
119
|
+
const [playbackSpeed, setPlaybackSpeedState] = useState(1);
|
|
120
|
+
|
|
121
|
+
// ── Float state ─────────────────────────────────────────────────────────────
|
|
122
|
+
const [isFloating, setIsFloating] = useState(false);
|
|
123
|
+
const [isManualFloating, setIsManualFloating] = useState(false);
|
|
124
|
+
const [enableAutoFloatLocal, setEnableAutoFloatLocal] = useState(enableAutoFloat);
|
|
125
|
+
|
|
126
|
+
// ── Visibility state ────────────────────────────────────────────────────────
|
|
127
|
+
const [isVisible, setIsVisible] = useState(variant === 'card');
|
|
128
|
+
|
|
129
|
+
// ── Effects ─────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
// Handle visibility for 'bar' variant
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (variant === 'bar') {
|
|
134
|
+
if (isOpen) {
|
|
135
|
+
setIsVisible(true);
|
|
136
|
+
} else {
|
|
137
|
+
const timer = setTimeout(() => setIsVisible(false), 300);
|
|
138
|
+
return () => clearTimeout(timer);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
setIsVisible(true);
|
|
142
|
+
}
|
|
143
|
+
}, [isOpen, variant]);
|
|
144
|
+
|
|
145
|
+
// Sync audio ref with state updates (volume, mute, speed)
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
const audio = audioRef.current;
|
|
148
|
+
if (!audio) return;
|
|
149
|
+
audio.volume = volume;
|
|
150
|
+
audio.muted = isMuted;
|
|
151
|
+
audio.playbackRate = playbackSpeed;
|
|
152
|
+
}, [volume, isMuted, playbackSpeed, src]);
|
|
153
|
+
|
|
154
|
+
// Auto-float on scroll logic (only for 'card' variant)
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (variant !== 'card' || !enableAutoFloatLocal) return;
|
|
157
|
+
const container = containerRef.current;
|
|
158
|
+
if (!container) return;
|
|
159
|
+
if (typeof IntersectionObserver === 'undefined') return;
|
|
160
|
+
|
|
161
|
+
const observer = new IntersectionObserver(
|
|
162
|
+
([entry]) => {
|
|
163
|
+
if (isPlaying && !entry.isIntersecting && !isFloating) {
|
|
164
|
+
if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
|
|
165
|
+
setIsFloating(true);
|
|
166
|
+
} else if (entry.isIntersecting && isFloating && !isManualFloating) {
|
|
167
|
+
if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
|
|
168
|
+
setIsFloating(false);
|
|
169
|
+
setIsManualFloating(false);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{ threshold: 0.2 }
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
observer.observe(container);
|
|
176
|
+
return () => observer.disconnect();
|
|
177
|
+
}, [isPlaying, isFloating, isManualFloating, variant, enableAutoFloatLocal]);
|
|
178
|
+
|
|
179
|
+
// Restore state when switching modes (remounting audio element)
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const audio = audioRef.current;
|
|
182
|
+
if (audio) {
|
|
183
|
+
if (Math.abs(audio.currentTime - currentTime) > 0.5) {
|
|
184
|
+
audio.currentTime = currentTime;
|
|
185
|
+
}
|
|
186
|
+
if (isPlaying) {
|
|
187
|
+
audio.play().catch(err => console.log('Playback interrupted:', err));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
191
|
+
}, [isFloating, variant]);
|
|
192
|
+
|
|
193
|
+
// ── Handlers ────────────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
const handleSetFloating = useCallback((floating: boolean) => {
|
|
196
|
+
if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
|
|
197
|
+
setIsFloating(floating);
|
|
198
|
+
if (!floating) {
|
|
199
|
+
setIsManualFloating(false);
|
|
200
|
+
if (onCloseFloating) onCloseFloating();
|
|
201
|
+
}
|
|
202
|
+
}, [onCloseFloating]);
|
|
203
|
+
|
|
204
|
+
const togglePlay = useCallback(() => {
|
|
205
|
+
if (audioRef.current) {
|
|
206
|
+
if (isPlaying) {
|
|
207
|
+
audioRef.current.pause();
|
|
208
|
+
} else {
|
|
209
|
+
audioRef.current.play().catch(err => console.log('Playback error:', err));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}, [isPlaying]);
|
|
213
|
+
|
|
214
|
+
const toggleMute = useCallback(() => {
|
|
215
|
+
setIsMuted(prev => !prev);
|
|
216
|
+
}, []);
|
|
217
|
+
|
|
218
|
+
const handleSeek = useCallback((value: number[]) => {
|
|
219
|
+
if (audioRef.current) {
|
|
220
|
+
audioRef.current.currentTime = value[0];
|
|
221
|
+
setCurrentTime(value[0]);
|
|
222
|
+
}
|
|
223
|
+
}, []);
|
|
224
|
+
|
|
225
|
+
const handleVolumeChange = useCallback((value: number[]) => {
|
|
226
|
+
const newVolume = value[0];
|
|
227
|
+
setVolume(newVolume);
|
|
228
|
+
setIsMuted(newVolume === 0);
|
|
229
|
+
}, []);
|
|
230
|
+
|
|
231
|
+
const handleEnableManualFloat = useCallback(() => {
|
|
232
|
+
setEnableAutoFloatLocal(true);
|
|
233
|
+
setIsManualFloating(true);
|
|
234
|
+
if (audioRef.current) setCurrentTime(audioRef.current.currentTime);
|
|
235
|
+
setIsFloating(true);
|
|
236
|
+
}, []);
|
|
237
|
+
|
|
238
|
+
const setPlaybackSpeed = useCallback((speed: number) => {
|
|
239
|
+
setPlaybackSpeedState(speed);
|
|
240
|
+
}, []);
|
|
241
|
+
|
|
242
|
+
const resetAudio = useCallback(() => {
|
|
243
|
+
if (audioRef.current) {
|
|
244
|
+
audioRef.current.currentTime = 0;
|
|
245
|
+
}
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
248
|
+
const formatTime = useCallback((time: number): string => {
|
|
249
|
+
if (isNaN(time) || time === Infinity || !isFinite(time)) return '0:00';
|
|
250
|
+
const minutes = Math.floor(time / 60);
|
|
251
|
+
const seconds = Math.floor(time % 60);
|
|
252
|
+
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
|
253
|
+
}, []);
|
|
254
|
+
|
|
255
|
+
// ── Audio element event handlers ─────────────────────────────────────────────
|
|
256
|
+
const onPlay = useCallback(() => setIsPlaying(true), []);
|
|
257
|
+
const onPause = useCallback(() => setIsPlaying(false), []);
|
|
258
|
+
const onEnded = useCallback(() => setIsPlaying(false), []);
|
|
259
|
+
const onTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLAudioElement>) => {
|
|
260
|
+
setCurrentTime(e.currentTarget.currentTime);
|
|
261
|
+
}, []);
|
|
262
|
+
const onLoadedMetadata = useCallback((e: React.SyntheticEvent<HTMLAudioElement>) => {
|
|
263
|
+
setDuration(e.currentTarget.duration);
|
|
264
|
+
}, []);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
audioRef,
|
|
268
|
+
containerRef,
|
|
269
|
+
isPlaying,
|
|
270
|
+
currentTime,
|
|
271
|
+
duration,
|
|
272
|
+
volume,
|
|
273
|
+
isMuted,
|
|
274
|
+
playbackSpeed,
|
|
275
|
+
isFloating,
|
|
276
|
+
isManualFloating,
|
|
277
|
+
isVisible,
|
|
278
|
+
isMobile,
|
|
279
|
+
enableAutoFloatLocal,
|
|
280
|
+
sidebarExpanded,
|
|
281
|
+
sidebarWidth,
|
|
282
|
+
assistenteExpanded,
|
|
283
|
+
togglePlay,
|
|
284
|
+
toggleMute,
|
|
285
|
+
handleSeek,
|
|
286
|
+
handleVolumeChange,
|
|
287
|
+
handleSetFloating,
|
|
288
|
+
handleEnableManualFloat,
|
|
289
|
+
setPlaybackSpeed,
|
|
290
|
+
resetAudio,
|
|
291
|
+
onPlay,
|
|
292
|
+
onPause,
|
|
293
|
+
onEnded,
|
|
294
|
+
onTimeUpdate,
|
|
295
|
+
onLoadedMetadata,
|
|
296
|
+
formatTime,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
@@ -48,7 +48,7 @@ export function HomePage({ user, onLogout }: HomePageProps) {
|
|
|
48
48
|
const [localSidebarExpanded, setLocalSidebarExpanded] = React.useState(false);
|
|
49
49
|
const [localAssistantExpanded, setLocalAssistantExpanded] = React.useState(false);
|
|
50
50
|
const sidebarExpanded = layout?.sidebarExpanded ?? localSidebarExpanded;
|
|
51
|
-
const sidebarWidth = layout?.sidebarWidth ??
|
|
51
|
+
const sidebarWidth = layout?.sidebarWidth ?? 280;
|
|
52
52
|
const assistenteExpanded = layout?.assistenteExpanded ?? localAssistantExpanded;
|
|
53
53
|
const toggleSidebar = layout?.toggleSidebar ?? (() => setLocalSidebarExpanded((value) => !value));
|
|
54
54
|
const toggleAssistente = layout?.toggleAssistente ?? (() => setLocalAssistantExpanded((value) => !value));
|
|
@@ -72,7 +72,7 @@ interface TemplateContentProps {
|
|
|
72
72
|
export function TemplateContent({ user, onLogout, onSettings }: TemplateContentProps) {
|
|
73
73
|
const layout = useOptionalLayout();
|
|
74
74
|
const [localSidebarExpanded, setLocalSidebarExpanded] = useState(false);
|
|
75
|
-
const [localSidebarWidth, setLocalSidebarWidth] = useState(
|
|
75
|
+
const [localSidebarWidth, setLocalSidebarWidth] = useState(280);
|
|
76
76
|
const sidebarExpanded = layout?.sidebarExpanded ?? localSidebarExpanded;
|
|
77
77
|
const sidebarWidth = layout?.sidebarWidth ?? localSidebarWidth;
|
|
78
78
|
const setSidebarWidth = layout?.setSidebarWidth ?? setLocalSidebarWidth;
|
|
@@ -118,7 +118,7 @@ export function TemplateContent({ user, onLogout, onSettings }: TemplateContentP
|
|
|
118
118
|
<div className="p-2 sm:p-4 md:p-6">
|
|
119
119
|
<div className="max-w-6xl mx-auto space-y-8">
|
|
120
120
|
{/* Page header */}
|
|
121
|
-
<PageHeader
|
|
121
|
+
<PageHeader
|
|
122
122
|
title="Página de Template"
|
|
123
123
|
subtitle="Template completo com componentes do Design System"
|
|
124
124
|
/>
|
|
@@ -413,10 +413,10 @@ export function TemplateContent({ user, onLogout, onSettings }: TemplateContentP
|
|
|
413
413
|
<div className="mb-4">
|
|
414
414
|
<div className="relative">
|
|
415
415
|
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
|
416
|
-
<Input
|
|
417
|
-
placeholder="Buscar usuários..."
|
|
416
|
+
<Input
|
|
417
|
+
placeholder="Buscar usuários..."
|
|
418
418
|
aria-label="Buscar usuários"
|
|
419
|
-
className="pl-10"
|
|
419
|
+
className="pl-10"
|
|
420
420
|
/>
|
|
421
421
|
</div>
|
|
422
422
|
</div>
|
|
@@ -32,7 +32,7 @@ export function TemplatePage({ user, onLogout }: TemplatePageProps) {
|
|
|
32
32
|
const [localSidebarExpanded, setLocalSidebarExpanded] = React.useState(false);
|
|
33
33
|
const [localAssistantExpanded, setLocalAssistantExpanded] = React.useState(false);
|
|
34
34
|
const sidebarExpanded = layout?.sidebarExpanded ?? localSidebarExpanded;
|
|
35
|
-
const sidebarWidth = layout?.sidebarWidth ??
|
|
35
|
+
const sidebarWidth = layout?.sidebarWidth ?? 280;
|
|
36
36
|
const assistenteExpanded = layout?.assistenteExpanded ?? localAssistantExpanded;
|
|
37
37
|
const toggleSidebar = layout?.toggleSidebar ?? (() => setLocalSidebarExpanded((value) => !value));
|
|
38
38
|
const toggleAssistente = layout?.toggleAssistente ?? (() => setLocalAssistantExpanded((value) => !value));
|
|
@@ -56,10 +56,10 @@ export function TemplatePage({ user, onLogout }: TemplatePageProps) {
|
|
|
56
56
|
navigate={navigate}
|
|
57
57
|
routes={routes}
|
|
58
58
|
/>
|
|
59
|
-
<TemplateContent
|
|
60
|
-
user={user}
|
|
61
|
-
onLogout={onLogout}
|
|
62
|
-
onSettings={() => navigate('/settings')}
|
|
59
|
+
<TemplateContent
|
|
60
|
+
user={user}
|
|
61
|
+
onLogout={onLogout}
|
|
62
|
+
onSettings={() => navigate('/settings')}
|
|
63
63
|
/>
|
|
64
64
|
<XerticaAssistant
|
|
65
65
|
isExpanded={assistenteExpanded}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
3
|
+
import { cn } from './utils';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CustomTooltipContent — Tooltip com portal e seta estilo sidebar/assistente.
|
|
7
|
+
*
|
|
8
|
+
* Substitui os componentes `SidebarTooltipContent` e `AssistantTooltipContent`
|
|
9
|
+
* que eram definidos inline em `sidebar.tsx` e `xertica-assistant.tsx`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { Tooltip, TooltipTrigger } from '../ui/tooltip';
|
|
14
|
+
* import { CustomTooltipContent } from '../shared/CustomTooltipContent';
|
|
15
|
+
*
|
|
16
|
+
* <Tooltip>
|
|
17
|
+
* <TooltipTrigger asChild><Button>Hover me</Button></TooltipTrigger>
|
|
18
|
+
* <CustomTooltipContent side="right">Minha dica</CustomTooltipContent>
|
|
19
|
+
* </Tooltip>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function CustomTooltipContent({
|
|
23
|
+
className,
|
|
24
|
+
sideOffset = 0,
|
|
25
|
+
children,
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
28
|
+
return (
|
|
29
|
+
<TooltipPrimitive.Portal>
|
|
30
|
+
<TooltipPrimitive.Content
|
|
31
|
+
data-slot="tooltip-content"
|
|
32
|
+
sideOffset={sideOffset}
|
|
33
|
+
className={cn(
|
|
34
|
+
'bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95',
|
|
35
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
36
|
+
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
|
|
37
|
+
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
38
|
+
'z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<TooltipPrimitive.Arrow
|
|
45
|
+
className="fill-popover z-50 drop-shadow-sm"
|
|
46
|
+
width={8}
|
|
47
|
+
height={4}
|
|
48
|
+
/>
|
|
49
|
+
</TooltipPrimitive.Content>
|
|
50
|
+
</TooltipPrimitive.Portal>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Width of the sidebar when expanded (Desktop) */
|
|
2
|
-
export const SIDEBAR_EXPANDED_WIDTH =
|
|
2
|
+
export const SIDEBAR_EXPANDED_WIDTH = 280;
|
|
3
3
|
/** Width of the sidebar when collapsed (Desktop) */
|
|
4
4
|
export const SIDEBAR_COLLAPSED_WIDTH = 80;
|
|
5
5
|
/** Width of the assistant panel when expanded (Desktop) */
|