@djangocfg/ui-nextjs 2.1.65 → 2.1.67
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/package.json +13 -8
- package/src/blocks/SplitHero/SplitHeroMedia.tsx +2 -1
- package/src/stores/index.ts +8 -0
- package/src/stores/mediaCache.ts +464 -0
- package/src/tools/AudioPlayer/@refactoring/00-PLAN.md +148 -0
- package/src/tools/AudioPlayer/@refactoring/01-TYPES.md +301 -0
- package/src/tools/AudioPlayer/@refactoring/02-HOOKS.md +281 -0
- package/src/tools/AudioPlayer/@refactoring/03-CONTEXT.md +328 -0
- package/src/tools/AudioPlayer/@refactoring/04-COMPONENTS.md +251 -0
- package/src/tools/AudioPlayer/@refactoring/05-EFFECTS.md +427 -0
- package/src/tools/AudioPlayer/@refactoring/06-UTILS-AND-INDEX.md +193 -0
- package/src/tools/AudioPlayer/@refactoring/07-EXECUTION-CHECKLIST.md +146 -0
- package/src/tools/AudioPlayer/README.md +325 -0
- package/src/tools/AudioPlayer/components/AudioEqualizer.tsx +200 -0
- package/src/tools/AudioPlayer/components/AudioPlayer.tsx +231 -0
- package/src/tools/AudioPlayer/components/AudioShortcutsPopover.tsx +99 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +147 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
- package/src/tools/AudioPlayer/components/SimpleAudioPlayer.tsx +280 -0
- package/src/tools/AudioPlayer/components/VisualizationToggle.tsx +64 -0
- package/src/tools/AudioPlayer/components/index.ts +21 -0
- package/src/tools/AudioPlayer/context/AudioProvider.tsx +292 -0
- package/src/tools/AudioPlayer/context/index.ts +11 -0
- package/src/tools/AudioPlayer/context/selectors.ts +96 -0
- package/src/tools/AudioPlayer/effects/index.ts +412 -0
- package/src/tools/AudioPlayer/hooks/index.ts +29 -0
- package/src/tools/AudioPlayer/hooks/useAudioAnalysis.ts +110 -0
- package/src/tools/AudioPlayer/hooks/useAudioHotkeys.ts +149 -0
- package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +106 -0
- package/src/tools/AudioPlayer/hooks/useVisualization.tsx +201 -0
- package/src/tools/AudioPlayer/index.ts +139 -0
- package/src/tools/AudioPlayer/types/audio.ts +107 -0
- package/src/tools/AudioPlayer/types/components.ts +98 -0
- package/src/tools/AudioPlayer/types/effects.ts +73 -0
- package/src/tools/AudioPlayer/types/index.ts +35 -0
- package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
- package/src/tools/AudioPlayer/utils/index.ts +5 -0
- package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
- package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
- package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
- package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
- package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
- package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
- package/src/tools/ImageViewer/README.md +174 -0
- package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +150 -0
- package/src/tools/ImageViewer/components/ImageViewer.tsx +235 -0
- package/src/tools/ImageViewer/components/index.ts +7 -0
- package/src/tools/ImageViewer/hooks/index.ts +9 -0
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +153 -0
- package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
- package/src/tools/ImageViewer/index.ts +60 -0
- package/src/tools/ImageViewer/types.ts +75 -0
- package/src/tools/ImageViewer/utils/constants.ts +59 -0
- package/src/tools/ImageViewer/utils/index.ts +16 -0
- package/src/tools/ImageViewer/utils/lqip.ts +47 -0
- package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
- package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
- package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
- package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
- package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
- package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
- package/src/tools/VideoPlayer/README.md +212 -187
- package/src/tools/VideoPlayer/{VideoControls.tsx → components/VideoControls.tsx} +8 -9
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +174 -0
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
- package/src/tools/VideoPlayer/components/index.ts +14 -0
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
- package/src/tools/VideoPlayer/context/index.ts +8 -0
- package/src/tools/VideoPlayer/hooks/index.ts +9 -0
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +109 -0
- package/src/tools/VideoPlayer/index.ts +70 -9
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +206 -0
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +401 -0
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +332 -0
- package/src/tools/VideoPlayer/providers/index.ts +8 -0
- package/src/tools/VideoPlayer/types/index.ts +38 -0
- package/src/tools/VideoPlayer/types/player.ts +116 -0
- package/src/tools/VideoPlayer/types/provider.ts +93 -0
- package/src/tools/VideoPlayer/types/sources.ts +97 -0
- package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
- package/src/tools/VideoPlayer/utils/index.ts +11 -0
- package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
- package/src/tools/index.ts +92 -4
- package/src/tools/VideoPlayer/NativePlayer.tsx +0 -141
- package/src/tools/VideoPlayer/VideoPlayer.tsx +0 -231
- package/src/tools/VideoPlayer/types.ts +0 -118
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SpotlightEffect - Rotating spotlight with conic gradients
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { cn } from '@djangocfg/ui-nextjs';
|
|
8
|
+
import type { calculateSpotlight } from '../../../effects';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// TYPES
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
interface SpotlightEffectProps {
|
|
15
|
+
data: ReturnType<typeof calculateSpotlight>;
|
|
16
|
+
colors: string[];
|
|
17
|
+
blur: string;
|
|
18
|
+
isPlaying: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// COMPONENT
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export function SpotlightEffect({ data, colors, blur, isPlaying }: SpotlightEffectProps) {
|
|
26
|
+
const inset = 'inset' in data ? data.inset : 12;
|
|
27
|
+
const pulseInset = 'pulseInset' in data ? data.pulseInset : 24;
|
|
28
|
+
const ringOpacity = 'ringOpacity' in data ? data.ringOpacity : 0.3;
|
|
29
|
+
const ringScale = 'ringScale' in data ? data.ringScale : 1;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
{/* Rotating conic gradient - reactive size */}
|
|
34
|
+
<div
|
|
35
|
+
className={cn('absolute rounded-xl -z-10', blur)}
|
|
36
|
+
style={{
|
|
37
|
+
inset: `-${inset}px`,
|
|
38
|
+
background: `conic-gradient(
|
|
39
|
+
from ${data.rotation}deg,
|
|
40
|
+
hsl(${colors[0]} / ${data.colors[0]?.opacity || 0.5}),
|
|
41
|
+
hsl(${colors[1] || colors[0]} / ${data.colors[1]?.opacity || 0.7}),
|
|
42
|
+
hsl(${colors[2] || colors[0]} / ${data.colors[2]?.opacity || 0.5}),
|
|
43
|
+
hsl(${colors[0]} / ${data.colors[1]?.opacity || 0.7}),
|
|
44
|
+
hsl(${colors[0]} / ${data.colors[0]?.opacity || 0.5})
|
|
45
|
+
)`,
|
|
46
|
+
opacity: isPlaying ? 1 : 0,
|
|
47
|
+
transition: 'all 0.08s ease-out',
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
{/* Inner border */}
|
|
52
|
+
<div
|
|
53
|
+
className="absolute -inset-1 rounded-lg bg-background -z-10"
|
|
54
|
+
style={{ opacity: isPlaying ? 1 : 0, transition: 'opacity 0.1s' }}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
{/* Bass pulse glow - reactive size */}
|
|
58
|
+
<div
|
|
59
|
+
className={cn('absolute rounded-2xl -z-10', blur)}
|
|
60
|
+
style={{
|
|
61
|
+
inset: `-${pulseInset}px`,
|
|
62
|
+
background: `radial-gradient(circle, hsl(${colors[0]} / 0.7) 0%, hsl(${colors[0]} / 0.3) 50%, transparent 70%)`,
|
|
63
|
+
opacity: isPlaying ? data.pulseOpacity : 0,
|
|
64
|
+
transform: `scale(${data.pulseScale})`,
|
|
65
|
+
transition: 'all 0.08s ease-out',
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
{/* Outer ring glow */}
|
|
70
|
+
<div
|
|
71
|
+
className="absolute rounded-3xl -z-10 blur-2xl"
|
|
72
|
+
style={{
|
|
73
|
+
inset: `-${pulseInset + 30}px`,
|
|
74
|
+
background: `radial-gradient(circle, hsl(${colors[1] || colors[0]} / 0.4) 0%, transparent 60%)`,
|
|
75
|
+
opacity: isPlaying ? ringOpacity : 0,
|
|
76
|
+
transform: `scale(${ringScale})`,
|
|
77
|
+
transition: 'all 0.08s ease-out',
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReactiveCover effects - Audio-reactive visual effects
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { GlowEffect, type GlowEffectData } from './GlowEffect';
|
|
6
|
+
export { OrbsEffect } from './OrbsEffect';
|
|
7
|
+
export { SpotlightEffect } from './SpotlightEffect';
|
|
8
|
+
export { MeshEffect } from './MeshEffect';
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SimpleAudioPlayer - Easy-to-use audio player wrapper
|
|
5
|
+
*
|
|
6
|
+
* Combines AudioProvider + AudioPlayer + optional reactive cover
|
|
7
|
+
* in a single component with sensible defaults.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Minimal usage
|
|
11
|
+
* <SimpleAudioPlayer src="https://example.com/audio.mp3" />
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // With cover art
|
|
15
|
+
* <SimpleAudioPlayer
|
|
16
|
+
* src={audioUrl}
|
|
17
|
+
* title="Track Title"
|
|
18
|
+
* artist="Artist Name"
|
|
19
|
+
* coverArt="/path/to/cover.jpg"
|
|
20
|
+
* />
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Full customization
|
|
24
|
+
* <SimpleAudioPlayer
|
|
25
|
+
* src={audioUrl}
|
|
26
|
+
* title="Track Title"
|
|
27
|
+
* coverArt={coverUrl}
|
|
28
|
+
* showWaveform
|
|
29
|
+
* showEqualizer={false}
|
|
30
|
+
* reactiveCover
|
|
31
|
+
* variant="spotlight"
|
|
32
|
+
* autoPlay={false}
|
|
33
|
+
* />
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { useRef, type ReactNode } from 'react';
|
|
37
|
+
import { Music } from 'lucide-react';
|
|
38
|
+
import { cn } from '@djangocfg/ui-core';
|
|
39
|
+
|
|
40
|
+
import { AudioProvider } from '../context';
|
|
41
|
+
import { AudioPlayer } from './AudioPlayer';
|
|
42
|
+
import { AudioReactiveCover } from './ReactiveCover';
|
|
43
|
+
import { VisualizationProvider, useVisualization } from '../hooks';
|
|
44
|
+
import type { WaveformOptions, EqualizerOptions } from '../types';
|
|
45
|
+
import type { EffectIntensity, EffectColorScheme } from '../effects';
|
|
46
|
+
import type { VisualizationVariant } from '../hooks';
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// TYPES
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
export interface SimpleAudioPlayerProps {
|
|
53
|
+
/** Audio source URL */
|
|
54
|
+
src: string;
|
|
55
|
+
|
|
56
|
+
/** Track title */
|
|
57
|
+
title?: string;
|
|
58
|
+
|
|
59
|
+
/** Artist name */
|
|
60
|
+
artist?: string;
|
|
61
|
+
|
|
62
|
+
/** Cover art URL or ReactNode */
|
|
63
|
+
coverArt?: string | ReactNode;
|
|
64
|
+
|
|
65
|
+
/** Cover art size */
|
|
66
|
+
coverSize?: 'sm' | 'md' | 'lg';
|
|
67
|
+
|
|
68
|
+
/** Show waveform visualization */
|
|
69
|
+
showWaveform?: boolean;
|
|
70
|
+
|
|
71
|
+
/** Show equalizer bars */
|
|
72
|
+
showEqualizer?: boolean;
|
|
73
|
+
|
|
74
|
+
/** Show timer */
|
|
75
|
+
showTimer?: boolean;
|
|
76
|
+
|
|
77
|
+
/** Show volume control */
|
|
78
|
+
showVolume?: boolean;
|
|
79
|
+
|
|
80
|
+
/** Show loop/repeat button */
|
|
81
|
+
showLoop?: boolean;
|
|
82
|
+
|
|
83
|
+
/** Enable audio-reactive cover effects */
|
|
84
|
+
reactiveCover?: boolean;
|
|
85
|
+
|
|
86
|
+
/** Reactive effect variant */
|
|
87
|
+
variant?: VisualizationVariant;
|
|
88
|
+
|
|
89
|
+
/** Reactive effect intensity */
|
|
90
|
+
intensity?: EffectIntensity;
|
|
91
|
+
|
|
92
|
+
/** Reactive effect color scheme */
|
|
93
|
+
colorScheme?: EffectColorScheme;
|
|
94
|
+
|
|
95
|
+
/** Auto-play on load */
|
|
96
|
+
autoPlay?: boolean;
|
|
97
|
+
|
|
98
|
+
/** Waveform customization */
|
|
99
|
+
waveformOptions?: WaveformOptions;
|
|
100
|
+
|
|
101
|
+
/** Equalizer customization */
|
|
102
|
+
equalizerOptions?: EqualizerOptions;
|
|
103
|
+
|
|
104
|
+
/** Layout direction */
|
|
105
|
+
layout?: 'vertical' | 'horizontal';
|
|
106
|
+
|
|
107
|
+
/** Additional class name */
|
|
108
|
+
className?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// CONSTANTS
|
|
113
|
+
// =============================================================================
|
|
114
|
+
|
|
115
|
+
const COVER_SIZES = {
|
|
116
|
+
sm: 'w-24 h-24',
|
|
117
|
+
md: 'w-32 h-32',
|
|
118
|
+
lg: 'w-48 h-48',
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// =============================================================================
|
|
122
|
+
// COMPONENT
|
|
123
|
+
// =============================================================================
|
|
124
|
+
|
|
125
|
+
export function SimpleAudioPlayer(props: SimpleAudioPlayerProps) {
|
|
126
|
+
return (
|
|
127
|
+
<VisualizationProvider>
|
|
128
|
+
<SimpleAudioPlayerContent {...props} />
|
|
129
|
+
</VisualizationProvider>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function SimpleAudioPlayerContent({
|
|
134
|
+
src,
|
|
135
|
+
title,
|
|
136
|
+
artist,
|
|
137
|
+
coverArt,
|
|
138
|
+
coverSize = 'md',
|
|
139
|
+
showWaveform = true,
|
|
140
|
+
showEqualizer = false,
|
|
141
|
+
showTimer = true,
|
|
142
|
+
showVolume = true,
|
|
143
|
+
showLoop = true,
|
|
144
|
+
reactiveCover = true,
|
|
145
|
+
variant,
|
|
146
|
+
intensity,
|
|
147
|
+
colorScheme,
|
|
148
|
+
autoPlay = false,
|
|
149
|
+
waveformOptions,
|
|
150
|
+
equalizerOptions,
|
|
151
|
+
layout = 'vertical',
|
|
152
|
+
className,
|
|
153
|
+
}: SimpleAudioPlayerProps) {
|
|
154
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
155
|
+
const { settings: vizSettings, nextVariant } = useVisualization();
|
|
156
|
+
|
|
157
|
+
// Determine effective variant (from props or localStorage settings)
|
|
158
|
+
const effectiveVariant = variant ?? (vizSettings.variant !== 'none' ? vizSettings.variant : 'spotlight');
|
|
159
|
+
const effectiveIntensity = intensity ?? vizSettings.intensity;
|
|
160
|
+
const effectiveColorScheme = colorScheme ?? vizSettings.colorScheme;
|
|
161
|
+
|
|
162
|
+
// Show reactive cover if enabled and variant is not 'none'
|
|
163
|
+
const showReactiveCover = reactiveCover && effectiveVariant !== 'none';
|
|
164
|
+
|
|
165
|
+
// Render cover art content
|
|
166
|
+
const renderCoverContent = () => {
|
|
167
|
+
if (typeof coverArt === 'string') {
|
|
168
|
+
return (
|
|
169
|
+
<img
|
|
170
|
+
src={coverArt}
|
|
171
|
+
alt={title || 'Album cover'}
|
|
172
|
+
className="w-full h-full object-cover"
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (coverArt) {
|
|
178
|
+
return coverArt;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Default placeholder
|
|
182
|
+
return (
|
|
183
|
+
<div className="w-full h-full bg-muted/30 flex items-center justify-center">
|
|
184
|
+
<Music className="w-1/3 h-1/3 text-muted-foreground/50" />
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const isHorizontal = layout === 'horizontal';
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<AudioProvider
|
|
193
|
+
source={{ uri: src }}
|
|
194
|
+
containerRef={containerRef}
|
|
195
|
+
autoPlay={autoPlay}
|
|
196
|
+
waveformOptions={waveformOptions}
|
|
197
|
+
>
|
|
198
|
+
<div
|
|
199
|
+
className={cn(
|
|
200
|
+
'flex gap-4',
|
|
201
|
+
isHorizontal ? 'flex-row items-center' : 'flex-col items-center',
|
|
202
|
+
className
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
205
|
+
{/* Cover Art */}
|
|
206
|
+
{(coverArt || reactiveCover) && (
|
|
207
|
+
<div className="flex flex-col items-center gap-2 shrink-0">
|
|
208
|
+
{showReactiveCover ? (
|
|
209
|
+
<AudioReactiveCover
|
|
210
|
+
size={coverSize}
|
|
211
|
+
variant={effectiveVariant as 'glow' | 'orbs' | 'spotlight' | 'mesh'}
|
|
212
|
+
intensity={effectiveIntensity}
|
|
213
|
+
colorScheme={effectiveColorScheme}
|
|
214
|
+
onClick={nextVariant}
|
|
215
|
+
>
|
|
216
|
+
<div className={cn('rounded-lg overflow-hidden', COVER_SIZES[coverSize])}>
|
|
217
|
+
{renderCoverContent()}
|
|
218
|
+
</div>
|
|
219
|
+
</AudioReactiveCover>
|
|
220
|
+
) : (
|
|
221
|
+
<div
|
|
222
|
+
className={cn(
|
|
223
|
+
'rounded-lg overflow-hidden shadow-lg cursor-pointer',
|
|
224
|
+
COVER_SIZES[coverSize]
|
|
225
|
+
)}
|
|
226
|
+
onClick={nextVariant}
|
|
227
|
+
role="button"
|
|
228
|
+
tabIndex={0}
|
|
229
|
+
onKeyDown={(e) => e.key === 'Enter' && nextVariant()}
|
|
230
|
+
>
|
|
231
|
+
{renderCoverContent()}
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
|
|
235
|
+
{/* Effect indicator */}
|
|
236
|
+
{reactiveCover && (
|
|
237
|
+
<span className="text-[10px] uppercase tracking-wider text-muted-foreground/50 select-none">
|
|
238
|
+
{vizSettings.variant === 'none' ? 'off' : vizSettings.variant}
|
|
239
|
+
</span>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
|
|
244
|
+
{/* Track Info + Player */}
|
|
245
|
+
<div className={cn('flex flex-col gap-3', isHorizontal ? 'flex-1 min-w-0' : 'w-full max-w-md')}>
|
|
246
|
+
{/* Track Info */}
|
|
247
|
+
{(title || artist) && (
|
|
248
|
+
<div className={cn('text-center', isHorizontal && 'text-left')}>
|
|
249
|
+
{title && (
|
|
250
|
+
<h3 className="text-base font-medium text-foreground truncate">
|
|
251
|
+
{title}
|
|
252
|
+
</h3>
|
|
253
|
+
)}
|
|
254
|
+
{artist && (
|
|
255
|
+
<p className="text-sm text-muted-foreground truncate">
|
|
256
|
+
{artist}
|
|
257
|
+
</p>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{/* Audio Player */}
|
|
263
|
+
<AudioPlayer
|
|
264
|
+
ref={containerRef}
|
|
265
|
+
showControls
|
|
266
|
+
showWaveform={showWaveform}
|
|
267
|
+
showEqualizer={showEqualizer}
|
|
268
|
+
showTimer={showTimer}
|
|
269
|
+
showVolume={showVolume}
|
|
270
|
+
showLoop={showLoop}
|
|
271
|
+
equalizerOptions={equalizerOptions}
|
|
272
|
+
className="border-0 bg-transparent"
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</AudioProvider>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export default SimpleAudioPlayer;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VisualizationToggle - Simple toggle for audio visualization settings
|
|
5
|
+
*
|
|
6
|
+
* Click: cycle through variants
|
|
7
|
+
* Long press / right click: opens menu (future)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Sparkles } from 'lucide-react';
|
|
11
|
+
import { Button, cn } from '@djangocfg/ui-nextjs';
|
|
12
|
+
import { useVisualization, VARIANT_INFO } from '../hooks';
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// TYPES
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
export interface VisualizationToggleProps {
|
|
19
|
+
/** Compact mode (icon only) */
|
|
20
|
+
compact?: boolean;
|
|
21
|
+
/** Additional class name */
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// COMPONENT
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
export function VisualizationToggle({
|
|
30
|
+
compact = false,
|
|
31
|
+
className,
|
|
32
|
+
}: VisualizationToggleProps) {
|
|
33
|
+
const { settings, nextVariant } = useVisualization();
|
|
34
|
+
|
|
35
|
+
const currentInfo = VARIANT_INFO[settings.variant];
|
|
36
|
+
const isEnabled = settings.variant !== 'none';
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Button
|
|
40
|
+
variant={isEnabled ? 'secondary' : 'ghost'}
|
|
41
|
+
size={compact ? 'icon' : 'sm'}
|
|
42
|
+
className={cn(
|
|
43
|
+
'transition-all',
|
|
44
|
+
isEnabled && 'bg-primary/10 text-primary hover:bg-primary/20',
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
onClick={nextVariant}
|
|
48
|
+
title={`Visualization: ${currentInfo.label} (click to change)`}
|
|
49
|
+
>
|
|
50
|
+
<Sparkles
|
|
51
|
+
className={cn(
|
|
52
|
+
'h-4 w-4',
|
|
53
|
+
isEnabled && 'text-primary',
|
|
54
|
+
!compact && 'mr-1.5'
|
|
55
|
+
)}
|
|
56
|
+
/>
|
|
57
|
+
{!compact && (
|
|
58
|
+
<span className="text-xs">{currentInfo.label}</span>
|
|
59
|
+
)}
|
|
60
|
+
</Button>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default VisualizationToggle;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPlayer components - Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Main components
|
|
6
|
+
export { AudioPlayer } from './AudioPlayer';
|
|
7
|
+
export { SimpleAudioPlayer, type SimpleAudioPlayerProps } from './SimpleAudioPlayer';
|
|
8
|
+
export { AudioEqualizer } from './AudioEqualizer';
|
|
9
|
+
export { AudioShortcutsPopover } from './AudioShortcutsPopover';
|
|
10
|
+
export { VisualizationToggle, type VisualizationToggleProps } from './VisualizationToggle';
|
|
11
|
+
|
|
12
|
+
// ReactiveCover
|
|
13
|
+
export {
|
|
14
|
+
AudioReactiveCover,
|
|
15
|
+
type AudioReactiveCoverProps,
|
|
16
|
+
GlowEffect,
|
|
17
|
+
OrbsEffect,
|
|
18
|
+
SpotlightEffect,
|
|
19
|
+
MeshEffect,
|
|
20
|
+
type GlowEffectData,
|
|
21
|
+
} from './ReactiveCover';
|