@djangocfg/ui-nextjs 2.1.65 → 2.1.66

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 (30) hide show
  1. package/package.json +9 -6
  2. package/src/blocks/SplitHero/SplitHeroMedia.tsx +2 -1
  3. package/src/tools/AudioPlayer/AudioEqualizer.tsx +235 -0
  4. package/src/tools/AudioPlayer/AudioPlayer.tsx +223 -0
  5. package/src/tools/AudioPlayer/AudioReactiveCover.tsx +389 -0
  6. package/src/tools/AudioPlayer/AudioShortcutsPopover.tsx +95 -0
  7. package/src/tools/AudioPlayer/README.md +301 -0
  8. package/src/tools/AudioPlayer/SimpleAudioPlayer.tsx +275 -0
  9. package/src/tools/AudioPlayer/VisualizationToggle.tsx +68 -0
  10. package/src/tools/AudioPlayer/context.tsx +426 -0
  11. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  12. package/src/tools/AudioPlayer/index.ts +84 -0
  13. package/src/tools/AudioPlayer/types.ts +162 -0
  14. package/src/tools/AudioPlayer/useAudioHotkeys.ts +142 -0
  15. package/src/tools/AudioPlayer/useAudioVisualization.tsx +195 -0
  16. package/src/tools/ImageViewer/ImageViewer.tsx +416 -0
  17. package/src/tools/ImageViewer/README.md +161 -0
  18. package/src/tools/ImageViewer/index.ts +16 -0
  19. package/src/tools/VideoPlayer/README.md +196 -187
  20. package/src/tools/VideoPlayer/VideoErrorFallback.tsx +174 -0
  21. package/src/tools/VideoPlayer/VideoPlayer.tsx +189 -218
  22. package/src/tools/VideoPlayer/VideoPlayerContext.tsx +125 -0
  23. package/src/tools/VideoPlayer/index.ts +59 -7
  24. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +206 -0
  25. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +311 -0
  26. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +254 -0
  27. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  28. package/src/tools/VideoPlayer/types.ts +320 -71
  29. package/src/tools/index.ts +82 -4
  30. package/src/tools/VideoPlayer/NativePlayer.tsx +0 -141
@@ -0,0 +1,301 @@
1
+ # AudioPlayer
2
+
3
+ Audio player with waveform visualization, built on WaveSurfer.js.
4
+
5
+ ## Features
6
+
7
+ - Waveform visualization with interactive seeking
8
+ - Audio-reactive cover effects (glow, orbs, spotlight, mesh)
9
+ - Real-time frequency equalizer
10
+ - Keyboard shortcuts
11
+ - Volume control with mute
12
+ - Skip forward/backward
13
+ - Playback speed control
14
+
15
+ ## Quick Start (Recommended)
16
+
17
+ ```tsx
18
+ import { SimpleAudioPlayer } from '@djangocfg/ui-nextjs';
19
+
20
+ // Minimal
21
+ <SimpleAudioPlayer src="https://example.com/audio.mp3" />
22
+
23
+ // With metadata
24
+ <SimpleAudioPlayer
25
+ src={audioUrl}
26
+ title="Track Title"
27
+ artist="Artist Name"
28
+ coverArt="/path/to/cover.jpg"
29
+ />
30
+
31
+ // Full customization
32
+ <SimpleAudioPlayer
33
+ src={audioUrl}
34
+ title="Track Title"
35
+ coverArt={coverUrl}
36
+ showWaveform
37
+ showEqualizer={false}
38
+ reactiveCover
39
+ variant="spotlight"
40
+ layout="horizontal"
41
+ />
42
+ ```
43
+
44
+ ### SimpleAudioPlayer Props
45
+
46
+ | Prop | Type | Default | Description |
47
+ |------|------|---------|-------------|
48
+ | `src` | `string` | required | Audio URL |
49
+ | `title` | `string` | - | Track title |
50
+ | `artist` | `string` | - | Artist name |
51
+ | `coverArt` | `string \| ReactNode` | - | Cover image URL or custom element |
52
+ | `coverSize` | `'sm' \| 'md' \| 'lg'` | `'md'` | Cover art size |
53
+ | `showWaveform` | `boolean` | `true` | Show waveform |
54
+ | `showEqualizer` | `boolean` | `false` | Show equalizer bars |
55
+ | `showTimer` | `boolean` | `true` | Show time display |
56
+ | `showVolume` | `boolean` | `true` | Show volume control |
57
+ | `reactiveCover` | `boolean` | `true` | Enable reactive effects |
58
+ | `variant` | `VisualizationVariant` | - | Effect variant |
59
+ | `intensity` | `EffectIntensity` | - | Effect intensity |
60
+ | `colorScheme` | `EffectColorScheme` | - | Effect colors |
61
+ | `autoPlay` | `boolean` | `false` | Auto-play on load |
62
+ | `layout` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout direction |
63
+
64
+ ---
65
+
66
+ ## Advanced Usage
67
+
68
+ For full control, use `AudioProvider` + `AudioPlayer` directly:
69
+
70
+ ```tsx
71
+ import { useRef } from 'react';
72
+ import { AudioProvider, AudioPlayer } from '@djangocfg/ui-nextjs';
73
+
74
+ function MyAudioPlayer({ audioUrl }: { audioUrl: string }) {
75
+ const containerRef = useRef<HTMLDivElement>(null);
76
+
77
+ return (
78
+ <AudioProvider
79
+ source={{ uri: audioUrl }}
80
+ containerRef={containerRef}
81
+ >
82
+ <AudioPlayer
83
+ ref={containerRef}
84
+ showControls
85
+ showWaveform
86
+ showTimer
87
+ showVolume
88
+ />
89
+ </AudioProvider>
90
+ );
91
+ }
92
+ ```
93
+
94
+ ## Components
95
+
96
+ ### AudioProvider
97
+
98
+ Context provider for audio state. Wraps all audio components.
99
+
100
+ ```tsx
101
+ <AudioProvider
102
+ source={{ uri: 'https://example.com/audio.mp3' }}
103
+ containerRef={containerRef}
104
+ autoPlay={false}
105
+ waveformOptions={{
106
+ waveColor: 'hsl(217 91% 60% / 0.3)',
107
+ progressColor: 'hsl(217 91% 60%)',
108
+ height: 64,
109
+ barWidth: 3,
110
+ barRadius: 3,
111
+ barGap: 2,
112
+ }}
113
+ >
114
+ {children}
115
+ </AudioProvider>
116
+ ```
117
+
118
+ ### AudioPlayer
119
+
120
+ Main player component with waveform and controls.
121
+
122
+ | Prop | Type | Default | Description |
123
+ |------|------|---------|-------------|
124
+ | `showControls` | `boolean` | `true` | Show playback controls |
125
+ | `showWaveform` | `boolean` | `true` | Show waveform visualization |
126
+ | `showEqualizer` | `boolean` | `false` | Show equalizer animation |
127
+ | `showTimer` | `boolean` | `true` | Show time display |
128
+ | `showVolume` | `boolean` | `true` | Show volume slider |
129
+ | `className` | `string` | - | Additional CSS class |
130
+
131
+ ### AudioEqualizer
132
+
133
+ Real-time frequency visualizer with animated bars.
134
+
135
+ ```tsx
136
+ <AudioEqualizer
137
+ barCount={24}
138
+ height={48}
139
+ gap={2}
140
+ showPeaks
141
+ barColor="hsl(217 91% 60%)"
142
+ peakColor="hsl(217 91% 70%)"
143
+ />
144
+ ```
145
+
146
+ ### AudioReactiveCover
147
+
148
+ Album art wrapper with audio-reactive effects.
149
+
150
+ ```tsx
151
+ <AudioReactiveCover
152
+ variant="spotlight" // 'glow' | 'orbs' | 'spotlight' | 'mesh'
153
+ intensity="medium" // 'subtle' | 'medium' | 'strong'
154
+ colorScheme="primary" // 'primary' | 'vibrant' | 'cool' | 'warm'
155
+ onClick={() => nextVariant()}
156
+ >
157
+ <img src={coverArt} alt="Album cover" />
158
+ </AudioReactiveCover>
159
+ ```
160
+
161
+ ### VisualizationToggle
162
+
163
+ Button to cycle through visualization variants.
164
+
165
+ ```tsx
166
+ <VisualizationToggle compact />
167
+ ```
168
+
169
+ ## Hooks
170
+
171
+ ### useAudio
172
+
173
+ Access full audio state and controls.
174
+
175
+ ```tsx
176
+ const {
177
+ isReady,
178
+ isPlaying,
179
+ currentTime,
180
+ duration,
181
+ volume,
182
+ isMuted,
183
+ audioLevels, // { bass, mid, high, overall }
184
+ play,
185
+ pause,
186
+ togglePlay,
187
+ seek,
188
+ seekTo,
189
+ skip,
190
+ setVolume,
191
+ toggleMute,
192
+ restart,
193
+ } = useAudio();
194
+ ```
195
+
196
+ ### useAudioControls
197
+
198
+ Controls only (no re-render on time updates).
199
+
200
+ ```tsx
201
+ const { play, pause, togglePlay, skip, restart } = useAudioControls();
202
+ ```
203
+
204
+ ### useAudioState
205
+
206
+ Read-only playback state.
207
+
208
+ ```tsx
209
+ const { isPlaying, currentTime, duration, volume } = useAudioState();
210
+ ```
211
+
212
+ ### useAudioElement
213
+
214
+ Access audio element for custom visualizations.
215
+
216
+ ```tsx
217
+ const { audioElement, isPlaying, audioLevels } = useAudioElement();
218
+ ```
219
+
220
+ ### useAudioVisualization
221
+
222
+ Manage visualization settings (persisted in localStorage).
223
+
224
+ ```tsx
225
+ const {
226
+ settings, // { enabled, variant, intensity, colorScheme }
227
+ toggle,
228
+ setSetting,
229
+ nextVariant,
230
+ nextIntensity,
231
+ nextColorScheme,
232
+ reset,
233
+ } = useAudioVisualization();
234
+ ```
235
+
236
+ ### useAudioHotkeys
237
+
238
+ Enable keyboard shortcuts.
239
+
240
+ ```tsx
241
+ useAudioHotkeys({
242
+ enabled: true,
243
+ skipDuration: 10,
244
+ volumeStep: 0.1,
245
+ });
246
+ ```
247
+
248
+ ## Keyboard Shortcuts
249
+
250
+ | Key | Action |
251
+ |-----|--------|
252
+ | `Space` | Play/Pause |
253
+ | `←` / `J` | Skip 10s backward |
254
+ | `→` / `L` | Skip 10s forward |
255
+ | `↑` | Volume up |
256
+ | `↓` | Volume down |
257
+ | `M` | Mute/Unmute |
258
+ | `0-9` | Jump to 0-90% |
259
+
260
+ ## Waveform Options
261
+
262
+ ```typescript
263
+ interface WaveformOptions {
264
+ waveColor?: string; // Unplayed wave color
265
+ progressColor?: string; // Played wave color
266
+ cursorColor?: string; // Playhead cursor color
267
+ cursorWidth?: number; // Cursor width in px
268
+ height?: number; // Waveform height in px
269
+ barWidth?: number; // Bar width in px
270
+ barRadius?: number; // Bar corner radius
271
+ barGap?: number; // Gap between bars
272
+ }
273
+ ```
274
+
275
+ ## Effect Variants
276
+
277
+ | Variant | Description |
278
+ |---------|-------------|
279
+ | `spotlight` | Rotating conic gradient with bass pulse |
280
+ | `glow` | Multi-layered radial glows from edges |
281
+ | `orbs` | Floating orbs that react to frequencies |
282
+ | `mesh` | Large gradient blobs with movement |
283
+ | `none` | Effects disabled |
284
+
285
+ ## Architecture
286
+
287
+ ```
288
+ AudioPlayer/
289
+ ├── index.ts # Exports
290
+ ├── types.ts # Type definitions
291
+ ├── context.tsx # AudioProvider, useAudio hooks
292
+ ├── AudioPlayer.tsx # Main player component
293
+ ├── AudioEqualizer.tsx # Frequency bars
294
+ ├── AudioReactiveCover.tsx # Reactive album art
295
+ ├── AudioShortcutsPopover.tsx # Shortcuts help
296
+ ├── VisualizationToggle.tsx # Effect toggle button
297
+ ├── useAudioHotkeys.ts # Keyboard shortcuts
298
+ ├── useAudioVisualization.tsx # Visualization settings
299
+ └── effects/
300
+ └── index.ts # Effect calculations
301
+ ```
@@ -0,0 +1,275 @@
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 './AudioReactiveCover';
43
+ import { VisualizationProvider, useAudioVisualization } from './useAudioVisualization';
44
+ import type { WaveformOptions, EqualizerOptions } from './types';
45
+ import type { EffectIntensity, EffectColorScheme } from './effects';
46
+ import type { VisualizationVariant } from './useAudioVisualization';
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
+ /** Enable audio-reactive cover effects */
81
+ reactiveCover?: boolean;
82
+
83
+ /** Reactive effect variant */
84
+ variant?: VisualizationVariant;
85
+
86
+ /** Reactive effect intensity */
87
+ intensity?: EffectIntensity;
88
+
89
+ /** Reactive effect color scheme */
90
+ colorScheme?: EffectColorScheme;
91
+
92
+ /** Auto-play on load */
93
+ autoPlay?: boolean;
94
+
95
+ /** Waveform customization */
96
+ waveformOptions?: WaveformOptions;
97
+
98
+ /** Equalizer customization */
99
+ equalizerOptions?: EqualizerOptions;
100
+
101
+ /** Layout direction */
102
+ layout?: 'vertical' | 'horizontal';
103
+
104
+ /** Additional class name */
105
+ className?: string;
106
+ }
107
+
108
+ // =============================================================================
109
+ // CONSTANTS
110
+ // =============================================================================
111
+
112
+ const COVER_SIZES = {
113
+ sm: 'w-24 h-24',
114
+ md: 'w-32 h-32',
115
+ lg: 'w-48 h-48',
116
+ };
117
+
118
+ // =============================================================================
119
+ // COMPONENT
120
+ // =============================================================================
121
+
122
+ export function SimpleAudioPlayer(props: SimpleAudioPlayerProps) {
123
+ return (
124
+ <VisualizationProvider>
125
+ <SimpleAudioPlayerContent {...props} />
126
+ </VisualizationProvider>
127
+ );
128
+ }
129
+
130
+ function SimpleAudioPlayerContent({
131
+ src,
132
+ title,
133
+ artist,
134
+ coverArt,
135
+ coverSize = 'md',
136
+ showWaveform = true,
137
+ showEqualizer = false,
138
+ showTimer = true,
139
+ showVolume = true,
140
+ reactiveCover = true,
141
+ variant,
142
+ intensity,
143
+ colorScheme,
144
+ autoPlay = false,
145
+ waveformOptions,
146
+ equalizerOptions,
147
+ layout = 'vertical',
148
+ className,
149
+ }: SimpleAudioPlayerProps) {
150
+ const containerRef = useRef<HTMLDivElement>(null);
151
+ const { settings: vizSettings, nextVariant } = useAudioVisualization();
152
+
153
+ // Determine effective variant (from props or localStorage settings)
154
+ const effectiveVariant = variant ?? (vizSettings.variant !== 'none' ? vizSettings.variant : 'spotlight');
155
+ const effectiveIntensity = intensity ?? vizSettings.intensity;
156
+ const effectiveColorScheme = colorScheme ?? vizSettings.colorScheme;
157
+
158
+ // Show reactive cover if enabled and variant is not 'none'
159
+ const showReactiveCover = reactiveCover && effectiveVariant !== 'none';
160
+
161
+ // Render cover art content
162
+ const renderCoverContent = () => {
163
+ if (typeof coverArt === 'string') {
164
+ return (
165
+ <img
166
+ src={coverArt}
167
+ alt={title || 'Album cover'}
168
+ className="w-full h-full object-cover"
169
+ />
170
+ );
171
+ }
172
+
173
+ if (coverArt) {
174
+ return coverArt;
175
+ }
176
+
177
+ // Default placeholder
178
+ return (
179
+ <div className="w-full h-full bg-muted/30 flex items-center justify-center">
180
+ <Music className="w-1/3 h-1/3 text-muted-foreground/50" />
181
+ </div>
182
+ );
183
+ };
184
+
185
+ const isHorizontal = layout === 'horizontal';
186
+
187
+ return (
188
+ <AudioProvider
189
+ source={{ uri: src }}
190
+ containerRef={containerRef}
191
+ autoPlay={autoPlay}
192
+ waveformOptions={waveformOptions}
193
+ >
194
+ <div
195
+ className={cn(
196
+ 'flex gap-4',
197
+ isHorizontal ? 'flex-row items-center' : 'flex-col items-center',
198
+ className
199
+ )}
200
+ >
201
+ {/* Cover Art */}
202
+ {(coverArt || reactiveCover) && (
203
+ <div className="flex flex-col items-center gap-2 shrink-0">
204
+ {showReactiveCover ? (
205
+ <AudioReactiveCover
206
+ size={coverSize}
207
+ variant={effectiveVariant as 'glow' | 'orbs' | 'spotlight' | 'mesh'}
208
+ intensity={effectiveIntensity}
209
+ colorScheme={effectiveColorScheme}
210
+ onClick={nextVariant}
211
+ >
212
+ <div className={cn('rounded-lg overflow-hidden', COVER_SIZES[coverSize])}>
213
+ {renderCoverContent()}
214
+ </div>
215
+ </AudioReactiveCover>
216
+ ) : (
217
+ <div
218
+ className={cn(
219
+ 'rounded-lg overflow-hidden shadow-lg cursor-pointer',
220
+ COVER_SIZES[coverSize]
221
+ )}
222
+ onClick={nextVariant}
223
+ role="button"
224
+ tabIndex={0}
225
+ onKeyDown={(e) => e.key === 'Enter' && nextVariant()}
226
+ >
227
+ {renderCoverContent()}
228
+ </div>
229
+ )}
230
+
231
+ {/* Effect indicator */}
232
+ {reactiveCover && (
233
+ <span className="text-[10px] uppercase tracking-wider text-muted-foreground/50 select-none">
234
+ {vizSettings.variant === 'none' ? 'off' : vizSettings.variant}
235
+ </span>
236
+ )}
237
+ </div>
238
+ )}
239
+
240
+ {/* Track Info + Player */}
241
+ <div className={cn('flex flex-col gap-3', isHorizontal ? 'flex-1 min-w-0' : 'w-full max-w-md')}>
242
+ {/* Track Info */}
243
+ {(title || artist) && (
244
+ <div className={cn('text-center', isHorizontal && 'text-left')}>
245
+ {title && (
246
+ <h3 className="text-base font-medium text-foreground truncate">
247
+ {title}
248
+ </h3>
249
+ )}
250
+ {artist && (
251
+ <p className="text-sm text-muted-foreground truncate">
252
+ {artist}
253
+ </p>
254
+ )}
255
+ </div>
256
+ )}
257
+
258
+ {/* Audio Player */}
259
+ <AudioPlayer
260
+ ref={containerRef}
261
+ showControls
262
+ showWaveform={showWaveform}
263
+ showEqualizer={showEqualizer}
264
+ showTimer={showTimer}
265
+ showVolume={showVolume}
266
+ equalizerOptions={equalizerOptions}
267
+ className="border-0 bg-transparent"
268
+ />
269
+ </div>
270
+ </div>
271
+ </AudioProvider>
272
+ );
273
+ }
274
+
275
+ export default SimpleAudioPlayer;
@@ -0,0 +1,68 @@
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 {
13
+ useAudioVisualization,
14
+ VARIANT_INFO,
15
+ type VisualizationVariant,
16
+ } from './useAudioVisualization';
17
+
18
+ // =============================================================================
19
+ // TYPES
20
+ // =============================================================================
21
+
22
+ export interface VisualizationToggleProps {
23
+ /** Compact mode (icon only) */
24
+ compact?: boolean;
25
+ /** Additional class name */
26
+ className?: string;
27
+ }
28
+
29
+ // =============================================================================
30
+ // COMPONENT
31
+ // =============================================================================
32
+
33
+ export function VisualizationToggle({
34
+ compact = false,
35
+ className,
36
+ }: VisualizationToggleProps) {
37
+ const { settings, nextVariant } = useAudioVisualization();
38
+
39
+ const currentInfo = VARIANT_INFO[settings.variant];
40
+ const isEnabled = settings.variant !== 'none';
41
+
42
+ return (
43
+ <Button
44
+ variant={isEnabled ? 'secondary' : 'ghost'}
45
+ size={compact ? 'icon' : 'sm'}
46
+ className={cn(
47
+ 'transition-all',
48
+ isEnabled && 'bg-primary/10 text-primary hover:bg-primary/20',
49
+ className
50
+ )}
51
+ onClick={nextVariant}
52
+ title={`Visualization: ${currentInfo.label} (click to change)`}
53
+ >
54
+ <Sparkles
55
+ className={cn(
56
+ 'h-4 w-4',
57
+ isEnabled && 'text-primary',
58
+ !compact && 'mr-1.5'
59
+ )}
60
+ />
61
+ {!compact && (
62
+ <span className="text-xs">{currentInfo.label}</span>
63
+ )}
64
+ </Button>
65
+ );
66
+ }
67
+
68
+ export default VisualizationToggle;