@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.
- package/package.json +9 -6
- package/src/blocks/SplitHero/SplitHeroMedia.tsx +2 -1
- package/src/tools/AudioPlayer/AudioEqualizer.tsx +235 -0
- package/src/tools/AudioPlayer/AudioPlayer.tsx +223 -0
- package/src/tools/AudioPlayer/AudioReactiveCover.tsx +389 -0
- package/src/tools/AudioPlayer/AudioShortcutsPopover.tsx +95 -0
- package/src/tools/AudioPlayer/README.md +301 -0
- package/src/tools/AudioPlayer/SimpleAudioPlayer.tsx +275 -0
- package/src/tools/AudioPlayer/VisualizationToggle.tsx +68 -0
- package/src/tools/AudioPlayer/context.tsx +426 -0
- package/src/tools/AudioPlayer/effects/index.ts +412 -0
- package/src/tools/AudioPlayer/index.ts +84 -0
- package/src/tools/AudioPlayer/types.ts +162 -0
- package/src/tools/AudioPlayer/useAudioHotkeys.ts +142 -0
- package/src/tools/AudioPlayer/useAudioVisualization.tsx +195 -0
- package/src/tools/ImageViewer/ImageViewer.tsx +416 -0
- package/src/tools/ImageViewer/README.md +161 -0
- package/src/tools/ImageViewer/index.ts +16 -0
- package/src/tools/VideoPlayer/README.md +196 -187
- package/src/tools/VideoPlayer/VideoErrorFallback.tsx +174 -0
- package/src/tools/VideoPlayer/VideoPlayer.tsx +189 -218
- package/src/tools/VideoPlayer/VideoPlayerContext.tsx +125 -0
- package/src/tools/VideoPlayer/index.ts +59 -7
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +206 -0
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +311 -0
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +254 -0
- package/src/tools/VideoPlayer/providers/index.ts +8 -0
- package/src/tools/VideoPlayer/types.ts +320 -71
- package/src/tools/index.ts +82 -4
- package/src/tools/VideoPlayer/NativePlayer.tsx +0 -141
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Audio Player Hotkeys Hook
|
|
5
|
+
*
|
|
6
|
+
* Provides keyboard shortcuts for audio playback control.
|
|
7
|
+
* Uses useHotkey from @djangocfg/ui-nextjs.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useHotkey, useDeviceDetect } from '@djangocfg/ui-nextjs';
|
|
11
|
+
import { useAudioControls, useAudioState } from './context';
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// TYPES
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
export interface AudioHotkeyOptions {
|
|
18
|
+
/** Enable hotkeys (default: true) */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/** Skip duration in seconds (default: 10) */
|
|
21
|
+
skipDuration?: number;
|
|
22
|
+
/** Volume step (default: 0.1) */
|
|
23
|
+
volumeStep?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// HOOK
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
export function useAudioHotkeys(options: AudioHotkeyOptions = {}) {
|
|
31
|
+
const { enabled = true, skipDuration = 10, volumeStep = 0.1 } = options;
|
|
32
|
+
|
|
33
|
+
const { togglePlay, skip, setVolume, toggleMute, isReady } = useAudioControls();
|
|
34
|
+
const { volume, duration } = useAudioState();
|
|
35
|
+
const device = useDeviceDetect();
|
|
36
|
+
|
|
37
|
+
// Play/Pause - Space
|
|
38
|
+
useHotkey(
|
|
39
|
+
'space',
|
|
40
|
+
(e) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
togglePlay();
|
|
43
|
+
},
|
|
44
|
+
{ enabled: enabled && isReady, description: 'Play/Pause' }
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Skip backward - ArrowLeft or J
|
|
48
|
+
useHotkey(
|
|
49
|
+
['ArrowLeft', 'j'],
|
|
50
|
+
() => skip(-skipDuration),
|
|
51
|
+
{ enabled: enabled && isReady, description: `Skip ${skipDuration}s backward` }
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Skip forward - ArrowRight or L
|
|
55
|
+
useHotkey(
|
|
56
|
+
['ArrowRight', 'l'],
|
|
57
|
+
() => skip(skipDuration),
|
|
58
|
+
{ enabled: enabled && isReady, description: `Skip ${skipDuration}s forward` }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Volume up - ArrowUp
|
|
62
|
+
useHotkey(
|
|
63
|
+
'ArrowUp',
|
|
64
|
+
(e) => {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
setVolume(Math.min(1, volume + volumeStep));
|
|
67
|
+
},
|
|
68
|
+
{ enabled: enabled && isReady, description: 'Volume up' }
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Volume down - ArrowDown
|
|
72
|
+
useHotkey(
|
|
73
|
+
'ArrowDown',
|
|
74
|
+
(e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
setVolume(Math.max(0, volume - volumeStep));
|
|
77
|
+
},
|
|
78
|
+
{ enabled: enabled && isReady, description: 'Volume down' }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Mute/Unmute - M
|
|
82
|
+
useHotkey(
|
|
83
|
+
'm',
|
|
84
|
+
() => toggleMute(),
|
|
85
|
+
{ enabled: enabled && isReady, description: 'Mute/Unmute' }
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Number keys 0-9 to seek to percentage
|
|
89
|
+
useHotkey(
|
|
90
|
+
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
|
91
|
+
(e) => {
|
|
92
|
+
if (!duration) return;
|
|
93
|
+
const percent = parseInt(e.key, 10) / 10;
|
|
94
|
+
skip(duration * percent - duration * (volume || 0));
|
|
95
|
+
},
|
|
96
|
+
{ enabled: enabled && isReady, description: 'Seek to percentage' }
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
isMac: device.isMacOs,
|
|
101
|
+
isEnabled: enabled && isReady,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// =============================================================================
|
|
106
|
+
// SHORTCUTS CONFIG (for display)
|
|
107
|
+
// =============================================================================
|
|
108
|
+
|
|
109
|
+
export interface ShortcutItem {
|
|
110
|
+
keys: string[];
|
|
111
|
+
label: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ShortcutGroup {
|
|
115
|
+
title: string;
|
|
116
|
+
shortcuts: ShortcutItem[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const AUDIO_SHORTCUTS: ShortcutGroup[] = [
|
|
120
|
+
{
|
|
121
|
+
title: 'Playback',
|
|
122
|
+
shortcuts: [
|
|
123
|
+
{ keys: ['Space'], label: 'Play/Pause' },
|
|
124
|
+
{ keys: ['←'], label: 'Skip 10s back' },
|
|
125
|
+
{ keys: ['→'], label: 'Skip 10s forward' },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
title: 'Volume',
|
|
130
|
+
shortcuts: [
|
|
131
|
+
{ keys: ['↑'], label: 'Volume up' },
|
|
132
|
+
{ keys: ['↓'], label: 'Volume down' },
|
|
133
|
+
{ keys: ['M'], label: 'Mute/Unmute' },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
title: 'Seek',
|
|
138
|
+
shortcuts: [
|
|
139
|
+
{ keys: ['0-9'], label: 'Jump to 0-90%' },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
];
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useAudioVisualization - Hook for managing audio visualization settings
|
|
5
|
+
*
|
|
6
|
+
* Persists settings in localStorage for user preferences
|
|
7
|
+
* Uses React Context for shared state between components
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createContext, useContext, useCallback, useMemo, type ReactNode } from 'react';
|
|
11
|
+
import { useLocalStorage } from '@djangocfg/ui-nextjs';
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// TYPES
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
export type VisualizationVariant = 'glow' | 'orbs' | 'spotlight' | 'mesh' | 'none';
|
|
18
|
+
export type VisualizationIntensity = 'subtle' | 'medium' | 'strong';
|
|
19
|
+
export type VisualizationColorScheme = 'primary' | 'vibrant' | 'cool' | 'warm';
|
|
20
|
+
|
|
21
|
+
export interface VisualizationSettings {
|
|
22
|
+
/** Enable reactive cover animation */
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
/** Visual effect variant */
|
|
25
|
+
variant: VisualizationVariant;
|
|
26
|
+
/** Effect intensity */
|
|
27
|
+
intensity: VisualizationIntensity;
|
|
28
|
+
/** Color scheme */
|
|
29
|
+
colorScheme: VisualizationColorScheme;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UseAudioVisualizationReturn {
|
|
33
|
+
/** Current settings */
|
|
34
|
+
settings: VisualizationSettings;
|
|
35
|
+
/** Toggle visualization on/off */
|
|
36
|
+
toggle: () => void;
|
|
37
|
+
/** Set specific setting */
|
|
38
|
+
setSetting: <K extends keyof VisualizationSettings>(
|
|
39
|
+
key: K,
|
|
40
|
+
value: VisualizationSettings[K]
|
|
41
|
+
) => void;
|
|
42
|
+
/** Cycle to next variant */
|
|
43
|
+
nextVariant: () => void;
|
|
44
|
+
/** Cycle to next intensity */
|
|
45
|
+
nextIntensity: () => void;
|
|
46
|
+
/** Cycle to next color scheme */
|
|
47
|
+
nextColorScheme: () => void;
|
|
48
|
+
/** Reset to defaults */
|
|
49
|
+
reset: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// CONSTANTS
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
const STORAGE_KEY = 'audio-visualization-settings';
|
|
57
|
+
|
|
58
|
+
const DEFAULT_SETTINGS: VisualizationSettings = {
|
|
59
|
+
enabled: true,
|
|
60
|
+
variant: 'spotlight',
|
|
61
|
+
intensity: 'medium',
|
|
62
|
+
colorScheme: 'primary',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const VARIANTS: VisualizationVariant[] = ['spotlight', 'glow', 'orbs', 'mesh', 'none'];
|
|
66
|
+
const INTENSITIES: VisualizationIntensity[] = ['subtle', 'medium', 'strong'];
|
|
67
|
+
const COLOR_SCHEMES: VisualizationColorScheme[] = ['primary', 'vibrant', 'cool', 'warm'];
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// CONTEXT
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
const VisualizationContext = createContext<UseAudioVisualizationReturn | null>(null);
|
|
74
|
+
|
|
75
|
+
// =============================================================================
|
|
76
|
+
// PROVIDER
|
|
77
|
+
// =============================================================================
|
|
78
|
+
|
|
79
|
+
export interface VisualizationProviderProps {
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function VisualizationProvider({ children }: VisualizationProviderProps) {
|
|
84
|
+
const value = useVisualizationState();
|
|
85
|
+
return (
|
|
86
|
+
<VisualizationContext.Provider value={value}>
|
|
87
|
+
{children}
|
|
88
|
+
</VisualizationContext.Provider>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// INTERNAL HOOK (creates the actual state)
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
function useVisualizationState(): UseAudioVisualizationReturn {
|
|
97
|
+
const [settings, setSettings] = useLocalStorage<VisualizationSettings>(
|
|
98
|
+
STORAGE_KEY,
|
|
99
|
+
DEFAULT_SETTINGS
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const toggle = useCallback(() => {
|
|
103
|
+
setSettings((prev) => ({ ...prev, enabled: !prev.enabled }));
|
|
104
|
+
}, [setSettings]);
|
|
105
|
+
|
|
106
|
+
const setSetting = useCallback(
|
|
107
|
+
<K extends keyof VisualizationSettings>(
|
|
108
|
+
key: K,
|
|
109
|
+
value: VisualizationSettings[K]
|
|
110
|
+
) => {
|
|
111
|
+
setSettings((prev) => ({ ...prev, [key]: value }));
|
|
112
|
+
},
|
|
113
|
+
[setSettings]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const nextVariant = useCallback(() => {
|
|
117
|
+
setSettings((prev) => {
|
|
118
|
+
const currentIndex = VARIANTS.indexOf(prev.variant);
|
|
119
|
+
const nextIndex = (currentIndex + 1) % VARIANTS.length;
|
|
120
|
+
return { ...prev, variant: VARIANTS[nextIndex] };
|
|
121
|
+
});
|
|
122
|
+
}, [setSettings]);
|
|
123
|
+
|
|
124
|
+
const nextIntensity = useCallback(() => {
|
|
125
|
+
setSettings((prev) => {
|
|
126
|
+
const currentIndex = INTENSITIES.indexOf(prev.intensity);
|
|
127
|
+
const nextIndex = (currentIndex + 1) % INTENSITIES.length;
|
|
128
|
+
return { ...prev, intensity: INTENSITIES[nextIndex] };
|
|
129
|
+
});
|
|
130
|
+
}, [setSettings]);
|
|
131
|
+
|
|
132
|
+
const nextColorScheme = useCallback(() => {
|
|
133
|
+
setSettings((prev) => {
|
|
134
|
+
const currentIndex = COLOR_SCHEMES.indexOf(prev.colorScheme);
|
|
135
|
+
const nextIndex = (currentIndex + 1) % COLOR_SCHEMES.length;
|
|
136
|
+
return { ...prev, colorScheme: COLOR_SCHEMES[nextIndex] };
|
|
137
|
+
});
|
|
138
|
+
}, [setSettings]);
|
|
139
|
+
|
|
140
|
+
const reset = useCallback(() => {
|
|
141
|
+
setSettings(DEFAULT_SETTINGS);
|
|
142
|
+
}, [setSettings]);
|
|
143
|
+
|
|
144
|
+
return useMemo(
|
|
145
|
+
() => ({
|
|
146
|
+
settings,
|
|
147
|
+
toggle,
|
|
148
|
+
setSetting,
|
|
149
|
+
nextVariant,
|
|
150
|
+
nextIntensity,
|
|
151
|
+
nextColorScheme,
|
|
152
|
+
reset,
|
|
153
|
+
}),
|
|
154
|
+
[settings, toggle, setSetting, nextVariant, nextIntensity, nextColorScheme, reset]
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// HOOK (uses context when available)
|
|
160
|
+
// =============================================================================
|
|
161
|
+
|
|
162
|
+
export function useAudioVisualization(): UseAudioVisualizationReturn {
|
|
163
|
+
const context = useContext(VisualizationContext);
|
|
164
|
+
|
|
165
|
+
// Always call the fallback hooks (React hooks rules require consistent calls)
|
|
166
|
+
const fallbackState = useVisualizationState();
|
|
167
|
+
|
|
168
|
+
// If inside a provider, use shared context; otherwise use fallback
|
|
169
|
+
return context ?? fallbackState;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// VARIANT INFO
|
|
174
|
+
// =============================================================================
|
|
175
|
+
|
|
176
|
+
export const VARIANT_INFO: Record<VisualizationVariant, { label: string; icon: string }> = {
|
|
177
|
+
spotlight: { label: 'Spotlight', icon: '💫' },
|
|
178
|
+
glow: { label: 'Glow', icon: '✨' },
|
|
179
|
+
orbs: { label: 'Orbs', icon: '🔮' },
|
|
180
|
+
mesh: { label: 'Mesh', icon: '🌈' },
|
|
181
|
+
none: { label: 'Off', icon: '⭕' },
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const INTENSITY_INFO: Record<VisualizationIntensity, { label: string }> = {
|
|
185
|
+
subtle: { label: 'Subtle' },
|
|
186
|
+
medium: { label: 'Medium' },
|
|
187
|
+
strong: { label: 'Strong' },
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const COLOR_SCHEME_INFO: Record<VisualizationColorScheme, { label: string; preview: string }> = {
|
|
191
|
+
primary: { label: 'Primary', preview: '🔵' },
|
|
192
|
+
vibrant: { label: 'Vibrant', preview: '🌈' },
|
|
193
|
+
cool: { label: 'Cool', preview: '💙' },
|
|
194
|
+
warm: { label: 'Warm', preview: '🔥' },
|
|
195
|
+
};
|