@djangocfg/ui-tools 2.1.91

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 (174) hide show
  1. package/dist/LottiePlayer.client-LBEC2JKY.mjs +161 -0
  2. package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
  3. package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
  4. package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
  5. package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
  6. package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
  7. package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
  8. package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
  9. package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
  10. package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
  11. package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
  12. package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
  13. package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
  14. package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
  15. package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
  16. package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
  17. package/dist/chunk-37ZI6VD4.mjs +12 -0
  18. package/dist/chunk-37ZI6VD4.mjs.map +1 -0
  19. package/dist/chunk-3HK2OE62.cjs +81 -0
  20. package/dist/chunk-3HK2OE62.cjs.map +1 -0
  21. package/dist/chunk-7DGDQVQW.cjs +591 -0
  22. package/dist/chunk-7DGDQVQW.cjs.map +1 -0
  23. package/dist/chunk-M6P2FU7L.mjs +572 -0
  24. package/dist/chunk-M6P2FU7L.mjs.map +1 -0
  25. package/dist/chunk-UQ3XI5MY.cjs +15 -0
  26. package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
  27. package/dist/chunk-YFRNE2IR.mjs +79 -0
  28. package/dist/chunk-YFRNE2IR.mjs.map +1 -0
  29. package/dist/index.cjs +5042 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +1591 -0
  32. package/dist/index.d.ts +1591 -0
  33. package/dist/index.mjs +4941 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/package.json +86 -0
  36. package/src/components/markdown/MarkdownMessage.tsx +340 -0
  37. package/src/components/markdown/index.ts +5 -0
  38. package/src/index.ts +26 -0
  39. package/src/stores/index.ts +9 -0
  40. package/src/stores/mediaCache.ts +534 -0
  41. package/src/tools/AudioPlayer/README.md +206 -0
  42. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
  43. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
  44. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
  45. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
  46. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  47. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  48. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  49. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  50. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  51. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  52. package/src/tools/AudioPlayer/components/index.ts +22 -0
  53. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
  54. package/src/tools/AudioPlayer/context/index.ts +16 -0
  55. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  56. package/src/tools/AudioPlayer/hooks/index.ts +35 -0
  57. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
  58. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
  59. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
  60. package/src/tools/AudioPlayer/index.ts +133 -0
  61. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  62. package/src/tools/AudioPlayer/types/index.ts +27 -0
  63. package/src/tools/AudioPlayer/utils/debug.ts +14 -0
  64. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  65. package/src/tools/AudioPlayer/utils/index.ts +6 -0
  66. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  67. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  68. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  69. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  70. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  71. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  72. package/src/tools/ImageViewer/README.md +200 -0
  73. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  74. package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
  75. package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
  76. package/src/tools/ImageViewer/components/index.ts +7 -0
  77. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  78. package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
  79. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  80. package/src/tools/ImageViewer/index.ts +60 -0
  81. package/src/tools/ImageViewer/types.ts +81 -0
  82. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  83. package/src/tools/ImageViewer/utils/debug.ts +14 -0
  84. package/src/tools/ImageViewer/utils/index.ts +17 -0
  85. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  86. package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
  87. package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
  88. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
  89. package/src/tools/JsonForm/index.ts +46 -0
  90. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
  91. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
  92. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
  93. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
  94. package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
  95. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
  96. package/src/tools/JsonForm/templates/index.ts +12 -0
  97. package/src/tools/JsonForm/types.ts +83 -0
  98. package/src/tools/JsonForm/utils.ts +213 -0
  99. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
  100. package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
  101. package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
  102. package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
  103. package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
  104. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
  105. package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
  106. package/src/tools/JsonForm/widgets/index.ts +14 -0
  107. package/src/tools/JsonTree/index.tsx +243 -0
  108. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
  109. package/src/tools/LottiePlayer/index.tsx +56 -0
  110. package/src/tools/LottiePlayer/types.ts +108 -0
  111. package/src/tools/LottiePlayer/useLottie.ts +164 -0
  112. package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
  113. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
  114. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
  115. package/src/tools/Mermaid/hooks/index.ts +4 -0
  116. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
  117. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
  118. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
  119. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
  120. package/src/tools/Mermaid/index.tsx +44 -0
  121. package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
  122. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
  123. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
  124. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
  125. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
  126. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
  127. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  128. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
  129. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
  130. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  131. package/src/tools/OpenapiViewer/constants.ts +39 -0
  132. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
  133. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  134. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  135. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
  136. package/src/tools/OpenapiViewer/index.tsx +37 -0
  137. package/src/tools/OpenapiViewer/types.ts +151 -0
  138. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  139. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  140. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  141. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  142. package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
  143. package/src/tools/PrettyCode/index.tsx +47 -0
  144. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  145. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  146. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  147. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  148. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  149. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  150. package/src/tools/VideoPlayer/README.md +264 -0
  151. package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
  152. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
  153. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  154. package/src/tools/VideoPlayer/components/index.ts +14 -0
  155. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  156. package/src/tools/VideoPlayer/context/index.ts +8 -0
  157. package/src/tools/VideoPlayer/hooks/index.ts +12 -0
  158. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
  159. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
  160. package/src/tools/VideoPlayer/index.ts +77 -0
  161. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
  162. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
  163. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
  164. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  165. package/src/tools/VideoPlayer/types/index.ts +38 -0
  166. package/src/tools/VideoPlayer/types/player.ts +116 -0
  167. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  168. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  169. package/src/tools/VideoPlayer/utils/debug.ts +14 -0
  170. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  171. package/src/tools/VideoPlayer/utils/index.ts +12 -0
  172. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  173. package/src/tools/_shared.ts +29 -0
  174. package/src/tools/index.ts +172 -0
@@ -0,0 +1,206 @@
1
+ # AudioPlayer
2
+
3
+ Audio player with native HTML5 streaming and audio-reactive visualizations.
4
+
5
+ ## Features
6
+
7
+ - Native HTML5 audio playback (no crackling, native streaming support)
8
+ - Web Audio API for real-time frequency analysis
9
+ - Audio-reactive cover effects (glow, orbs, spotlight, mesh)
10
+ - Frequency visualization waveform
11
+ - Full playback controls
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ import { HybridSimplePlayer } from '@djangocfg/ui-nextjs';
17
+
18
+ // Simple usage
19
+ <HybridSimplePlayer src="https://example.com/audio.mp3" />
20
+
21
+ // With metadata and reactive cover
22
+ <HybridSimplePlayer
23
+ src={audioUrl}
24
+ title="Track Title"
25
+ artist="Artist Name"
26
+ coverArt="/path/to/cover.jpg"
27
+ reactiveCover
28
+ variant="spotlight"
29
+ />
30
+
31
+ // Full customization
32
+ <HybridSimplePlayer
33
+ src={audioUrl}
34
+ title="Track Title"
35
+ coverArt={coverUrl}
36
+ showWaveform
37
+ waveformMode="frequency" // 'frequency' | 'static'
38
+ showLoop
39
+ reactiveCover
40
+ variant="spotlight" // 'glow' | 'orbs' | 'spotlight' | 'mesh' | 'none'
41
+ intensity="medium" // 'subtle' | 'medium' | 'strong'
42
+ colorScheme="primary" // 'primary' | 'vibrant' | 'cool' | 'warm'
43
+ layout="horizontal" // 'vertical' | 'horizontal'
44
+ />
45
+ ```
46
+
47
+ ## Props
48
+
49
+ | Prop | Type | Default | Description |
50
+ |------|------|---------|-------------|
51
+ | `src` | `string` | required | Audio URL |
52
+ | `title` | `string` | - | Track title |
53
+ | `artist` | `string` | - | Artist name |
54
+ | `coverArt` | `string \| ReactNode` | - | Cover image URL or custom element |
55
+ | `coverSize` | `'sm' \| 'md' \| 'lg'` | `'md'` | Cover art size |
56
+ | `showWaveform` | `boolean` | `true` | Show frequency visualization |
57
+ | `waveformMode` | `'frequency' \| 'static'` | `'frequency'` | Visualization mode |
58
+ | `waveformHeight` | `number` | `64` | Waveform height in pixels |
59
+ | `showTimer` | `boolean` | `true` | Show time display |
60
+ | `showVolume` | `boolean` | `true` | Show volume control |
61
+ | `showLoop` | `boolean` | `true` | Show loop button |
62
+ | `reactiveCover` | `boolean` | `true` | Enable reactive effects |
63
+ | `variant` | `VisualizationVariant` | `'spotlight'` | Effect variant |
64
+ | `intensity` | `EffectIntensity` | `'medium'` | Effect intensity |
65
+ | `colorScheme` | `EffectColorScheme` | `'primary'` | Effect colors |
66
+ | `autoPlay` | `boolean` | `false` | Auto-play on load |
67
+ | `loop` | `boolean` | `false` | Loop playback |
68
+ | `layout` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout direction |
69
+
70
+ ## Advanced Usage
71
+
72
+ ### HybridAudioProvider + HybridAudioPlayer
73
+
74
+ For custom layouts:
75
+
76
+ ```tsx
77
+ import {
78
+ HybridAudioProvider,
79
+ HybridAudioPlayer,
80
+ AudioReactiveCover,
81
+ useHybridAudioContext
82
+ } from '@djangocfg/ui-nextjs';
83
+
84
+ function MyPlayer({ audioUrl }: { audioUrl: string }) {
85
+ return (
86
+ <HybridAudioProvider src={audioUrl}>
87
+ <AudioReactiveCover variant="spotlight" onClick={handleClick}>
88
+ <img src={coverUrl} alt="Cover" />
89
+ </AudioReactiveCover>
90
+ <HybridAudioPlayer showWaveform showControls />
91
+ <CustomControls />
92
+ </HybridAudioProvider>
93
+ );
94
+ }
95
+
96
+ function CustomControls() {
97
+ const { state, controls, audioLevels } = useHybridAudioContext();
98
+
99
+ return (
100
+ <div>
101
+ <p>Bass level: {(audioLevels.bass * 100).toFixed(0)}%</p>
102
+ <button onClick={controls.togglePlay}>
103
+ {state.isPlaying ? 'Pause' : 'Play'}
104
+ </button>
105
+ </div>
106
+ );
107
+ }
108
+ ```
109
+
110
+ ## Hooks
111
+
112
+ ### useHybridAudioContext
113
+
114
+ Full context access:
115
+
116
+ ```tsx
117
+ const {
118
+ state, // { isReady, isPlaying, currentTime, duration, volume, isMuted, isLooping }
119
+ controls, // { play, pause, togglePlay, seek, skip, setVolume, toggleMute, toggleLoop }
120
+ audioLevels, // { bass, mid, high, overall }
121
+ webAudio, // { context, analyser, sourceNode }
122
+ audioRef, // React ref to HTMLAudioElement
123
+ } = useHybridAudioContext();
124
+ ```
125
+
126
+ ### Specialized Hooks
127
+
128
+ ```tsx
129
+ // State only
130
+ const { isPlaying, currentTime, duration } = useHybridAudioState();
131
+
132
+ // Controls only (no re-render on time updates)
133
+ const { play, pause, togglePlay, seek } = useHybridAudioControls();
134
+
135
+ // Audio levels for reactive effects
136
+ const { bass, mid, high, overall } = useHybridAudioLevels();
137
+
138
+ // Web Audio API access
139
+ const { context, analyser, sourceNode } = useHybridWebAudio();
140
+ ```
141
+
142
+ ## Components
143
+
144
+ ### AudioReactiveCover
145
+
146
+ Album art wrapper with audio-reactive effects:
147
+
148
+ ```tsx
149
+ <AudioReactiveCover
150
+ variant="spotlight" // 'glow' | 'orbs' | 'spotlight' | 'mesh'
151
+ intensity="medium" // 'subtle' | 'medium' | 'strong'
152
+ colorScheme="primary" // 'primary' | 'vibrant' | 'cool' | 'warm'
153
+ onClick={() => nextVariant()}
154
+ >
155
+ <img src={coverArt} alt="Album cover" />
156
+ </AudioReactiveCover>
157
+ ```
158
+
159
+ ### HybridWaveform
160
+
161
+ Real-time frequency visualization:
162
+
163
+ ```tsx
164
+ <HybridWaveform
165
+ mode="frequency" // 'frequency' | 'static'
166
+ height={64}
167
+ barWidth={3}
168
+ barGap={2}
169
+ />
170
+ ```
171
+
172
+ ## Effect Variants
173
+
174
+ | Variant | Description |
175
+ |---------|-------------|
176
+ | `spotlight` | Rotating conic gradient with bass pulse |
177
+ | `glow` | Multi-layered radial glows from edges |
178
+ | `orbs` | Floating orbs that react to frequencies |
179
+ | `mesh` | Large gradient blobs with movement |
180
+ | `none` | Effects disabled |
181
+
182
+ ## Architecture
183
+
184
+ ```
185
+ AudioPlayer/
186
+ ├── index.ts # Public API exports
187
+ ├── types/ # TypeScript types
188
+ ├── hooks/
189
+ │ ├── useHybridAudio.ts # HTML5 audio + Web Audio hook
190
+ │ ├── useHybridAudioAnalysis.ts # Frequency analysis
191
+ │ └── useVisualization.tsx # Visualization settings
192
+ ├── context/
193
+ │ └── HybridAudioProvider.tsx # Audio context provider
194
+ ├── components/
195
+ │ ├── HybridAudioPlayer.tsx # Main player component
196
+ │ ├── HybridSimplePlayer.tsx # All-in-one wrapper
197
+ │ ├── HybridWaveform.tsx # Frequency visualization
198
+ │ └── ReactiveCover/ # Reactive effects
199
+ ├── effects/ # Effect calculations
200
+ └── utils/ # Utilities
201
+ ```
202
+
203
+ Key design:
204
+ - HTML5 `<audio>` for playback (native streaming, no crackling)
205
+ - Web Audio API AnalyserNode for visualization only (not connected to output)
206
+ - Audio graph: `source → destination` + `source → analyser` (passive)
@@ -0,0 +1,216 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * HybridAudioPlayer - Audio playback controls with frequency visualization
5
+ *
6
+ * Uses HybridAudioContext for state management.
7
+ * Native HTML5 audio for playback, Web Audio API for visualization only.
8
+ *
9
+ * Features:
10
+ * - Real-time frequency visualization
11
+ * - Playback controls (play, pause, skip, restart)
12
+ * - Volume control with mute
13
+ * - Loop toggle
14
+ * - Keyboard shortcuts
15
+ * - Timer display
16
+ */
17
+
18
+ import { memo } from 'react';
19
+ import {
20
+ Play,
21
+ Pause,
22
+ RotateCcw,
23
+ SkipBack,
24
+ SkipForward,
25
+ Volume2,
26
+ VolumeX,
27
+ Loader2,
28
+ Repeat,
29
+ } from 'lucide-react';
30
+ import { Button, Slider, cn } from '../../_shared';
31
+ import { useHybridAudioContext } from '../context/HybridAudioProvider';
32
+ import { HybridWaveform } from './HybridWaveform';
33
+ import { formatTime } from '../utils';
34
+
35
+ // =============================================================================
36
+ // TYPES
37
+ // =============================================================================
38
+
39
+ export interface HybridAudioPlayerProps {
40
+ /** Show playback controls */
41
+ showControls?: boolean;
42
+ /** Show frequency waveform */
43
+ showWaveform?: boolean;
44
+ /** Waveform visualization mode */
45
+ waveformMode?: 'frequency' | 'static';
46
+ /** Waveform height in pixels */
47
+ waveformHeight?: number;
48
+ /** Show time display */
49
+ showTimer?: boolean;
50
+ /** Show volume control */
51
+ showVolume?: boolean;
52
+ /** Show loop button */
53
+ showLoop?: boolean;
54
+ /** Additional CSS class */
55
+ className?: string;
56
+ /** Inline styles */
57
+ style?: React.CSSProperties;
58
+ }
59
+
60
+ // =============================================================================
61
+ // COMPONENT
62
+ // =============================================================================
63
+
64
+ export const HybridAudioPlayer = memo(function HybridAudioPlayer({
65
+ showControls = true,
66
+ showWaveform = true,
67
+ waveformMode = 'frequency',
68
+ waveformHeight = 64,
69
+ showTimer = true,
70
+ showVolume = true,
71
+ showLoop = true,
72
+ className,
73
+ style,
74
+ }: HybridAudioPlayerProps) {
75
+ const { state, controls } = useHybridAudioContext();
76
+
77
+ const isLoading = !state.isReady;
78
+
79
+ const handleVolumeChange = (value: number[]) => {
80
+ controls.setVolume(value[0] / 100);
81
+ };
82
+
83
+ return (
84
+ <div
85
+ className={cn('flex flex-col gap-3 p-4 rounded-lg bg-card border', className)}
86
+ style={style}
87
+ >
88
+ {/* Frequency Waveform */}
89
+ {showWaveform && (
90
+ <div className="relative">
91
+ <HybridWaveform
92
+ mode={waveformMode}
93
+ height={waveformHeight}
94
+ className={cn(isLoading && 'opacity-50')}
95
+ />
96
+ {isLoading && (
97
+ <div className="absolute inset-0 flex items-center justify-center">
98
+ <Loader2 className="h-6 w-6 animate-spin text-primary" />
99
+ </div>
100
+ )}
101
+ </div>
102
+ )}
103
+
104
+ {/* Timer */}
105
+ {showTimer && (
106
+ <div className="flex justify-between text-xs text-muted-foreground tabular-nums px-1">
107
+ <span>{formatTime(state.currentTime)}</span>
108
+ <span>{formatTime(state.duration)}</span>
109
+ </div>
110
+ )}
111
+
112
+ {/* Controls */}
113
+ {showControls && (
114
+ <div className="flex items-center justify-center gap-1">
115
+ {/* Restart */}
116
+ <Button
117
+ variant="ghost"
118
+ size="icon"
119
+ className="h-9 w-9"
120
+ onClick={controls.restart}
121
+ disabled={!state.isReady}
122
+ title="Restart"
123
+ >
124
+ <RotateCcw className="h-4 w-4" />
125
+ </Button>
126
+
127
+ {/* Skip back 5s */}
128
+ <Button
129
+ variant="ghost"
130
+ size="icon"
131
+ className="h-9 w-9"
132
+ onClick={() => controls.skip(-5)}
133
+ disabled={!state.isReady}
134
+ title="Back 5 seconds"
135
+ >
136
+ <SkipBack className="h-4 w-4" />
137
+ </Button>
138
+
139
+ {/* Play/Pause */}
140
+ <Button
141
+ variant="default"
142
+ size="icon"
143
+ className="h-12 w-12 rounded-full"
144
+ onClick={controls.togglePlay}
145
+ disabled={!state.isReady && !isLoading}
146
+ title={state.isPlaying ? 'Pause' : 'Play'}
147
+ >
148
+ {isLoading ? (
149
+ <Loader2 className="h-5 w-5 animate-spin" />
150
+ ) : state.isPlaying ? (
151
+ <Pause className="h-5 w-5" />
152
+ ) : (
153
+ <Play className="h-5 w-5 ml-0.5" />
154
+ )}
155
+ </Button>
156
+
157
+ {/* Skip forward 5s */}
158
+ <Button
159
+ variant="ghost"
160
+ size="icon"
161
+ className="h-9 w-9"
162
+ onClick={() => controls.skip(5)}
163
+ disabled={!state.isReady}
164
+ title="Forward 5 seconds"
165
+ >
166
+ <SkipForward className="h-4 w-4" />
167
+ </Button>
168
+
169
+ {/* Volume */}
170
+ {showVolume && (
171
+ <>
172
+ <Button
173
+ variant="ghost"
174
+ size="icon"
175
+ className="h-9 w-9"
176
+ onClick={controls.toggleMute}
177
+ title={state.isMuted ? 'Unmute' : 'Mute'}
178
+ >
179
+ {state.isMuted || state.volume === 0 ? (
180
+ <VolumeX className="h-4 w-4" />
181
+ ) : (
182
+ <Volume2 className="h-4 w-4" />
183
+ )}
184
+ </Button>
185
+
186
+ <Slider
187
+ value={[state.isMuted ? 0 : state.volume * 100]}
188
+ max={100}
189
+ step={1}
190
+ onValueChange={handleVolumeChange}
191
+ className="w-20"
192
+ aria-label="Volume"
193
+ />
194
+ </>
195
+ )}
196
+
197
+ {/* Loop/Repeat */}
198
+ {showLoop && (
199
+ <Button
200
+ variant="ghost"
201
+ size="icon"
202
+ className={cn('h-9 w-9', state.isLooping && 'text-primary')}
203
+ onClick={controls.toggleLoop}
204
+ disabled={!state.isReady}
205
+ title={state.isLooping ? 'Disable loop' : 'Enable loop'}
206
+ >
207
+ <Repeat className="h-4 w-4" />
208
+ </Button>
209
+ )}
210
+ </div>
211
+ )}
212
+ </div>
213
+ );
214
+ });
215
+
216
+ export default HybridAudioPlayer;
@@ -0,0 +1,280 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * HybridSimplePlayer - Easy-to-use hybrid audio player wrapper
5
+ *
6
+ * Combines HybridAudioProvider + HybridAudioPlayer + optional reactive cover
7
+ * in a single component with sensible defaults.
8
+ *
9
+ * Uses native HTML5 audio for playback (no crackling) with Web Audio API
10
+ * for visualization only.
11
+ *
12
+ * @example
13
+ * // Minimal usage
14
+ * <HybridSimplePlayer src="https://example.com/audio.mp3" />
15
+ *
16
+ * @example
17
+ * // With cover art and reactive effects
18
+ * <HybridSimplePlayer
19
+ * src={audioUrl}
20
+ * title="Track Title"
21
+ * artist="Artist Name"
22
+ * coverArt="/path/to/cover.jpg"
23
+ * reactiveCover
24
+ * variant="spotlight"
25
+ * />
26
+ */
27
+
28
+ import { type ReactNode } from 'react';
29
+ import { Music } from 'lucide-react';
30
+ import { cn } from '@djangocfg/ui-core';
31
+
32
+ import { HybridAudioProvider } from '../context/HybridAudioProvider';
33
+ import { HybridAudioPlayer } from './HybridAudioPlayer';
34
+ import { AudioReactiveCover } from './ReactiveCover';
35
+ import { VisualizationProvider, useVisualization } from '../hooks';
36
+ import type { EffectIntensity, EffectColorScheme } from '../effects';
37
+ import type { VisualizationVariant } from '../hooks';
38
+
39
+ // =============================================================================
40
+ // TYPES
41
+ // =============================================================================
42
+
43
+ export interface HybridSimplePlayerProps {
44
+ /** Audio source URL */
45
+ src: string;
46
+
47
+ /** Track title */
48
+ title?: string;
49
+
50
+ /** Artist name */
51
+ artist?: string;
52
+
53
+ /** Cover art URL or ReactNode */
54
+ coverArt?: string | ReactNode;
55
+
56
+ /** Cover art size */
57
+ coverSize?: 'sm' | 'md' | 'lg';
58
+
59
+ /** Show frequency waveform */
60
+ showWaveform?: boolean;
61
+
62
+ /** Waveform visualization mode */
63
+ waveformMode?: 'frequency' | 'static';
64
+
65
+ /** Waveform height in pixels */
66
+ waveformHeight?: number;
67
+
68
+ /** Show timer */
69
+ showTimer?: boolean;
70
+
71
+ /** Show volume control */
72
+ showVolume?: boolean;
73
+
74
+ /** Show loop/repeat button */
75
+ showLoop?: boolean;
76
+
77
+ /** Enable audio-reactive cover effects */
78
+ reactiveCover?: boolean;
79
+
80
+ /** Reactive effect variant */
81
+ variant?: VisualizationVariant;
82
+
83
+ /** Reactive effect intensity */
84
+ intensity?: EffectIntensity;
85
+
86
+ /** Reactive effect color scheme */
87
+ colorScheme?: EffectColorScheme;
88
+
89
+ /** Auto-play on load */
90
+ autoPlay?: boolean;
91
+
92
+ /** Loop playback */
93
+ loop?: boolean;
94
+
95
+ /** Initial volume (0-1) */
96
+ initialVolume?: number;
97
+
98
+ /** Layout direction */
99
+ layout?: 'vertical' | 'horizontal';
100
+
101
+ /** Additional class name */
102
+ className?: string;
103
+
104
+ /** Callbacks */
105
+ onPlay?: () => void;
106
+ onPause?: () => void;
107
+ onEnded?: () => void;
108
+ onError?: (error: Error) => void;
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 HybridSimplePlayer(props: HybridSimplePlayerProps) {
126
+ return (
127
+ <VisualizationProvider>
128
+ <HybridSimplePlayerContent {...props} />
129
+ </VisualizationProvider>
130
+ );
131
+ }
132
+
133
+ function HybridSimplePlayerContent({
134
+ src,
135
+ title,
136
+ artist,
137
+ coverArt,
138
+ coverSize = 'md',
139
+ showWaveform = true,
140
+ waveformMode = 'frequency',
141
+ waveformHeight = 64,
142
+ showTimer = true,
143
+ showVolume = true,
144
+ showLoop = true,
145
+ reactiveCover = true,
146
+ variant,
147
+ intensity,
148
+ colorScheme,
149
+ autoPlay = false,
150
+ loop = false,
151
+ initialVolume = 1,
152
+ layout = 'vertical',
153
+ className,
154
+ onPlay,
155
+ onPause,
156
+ onEnded,
157
+ onError,
158
+ }: HybridSimplePlayerProps) {
159
+ const { settings: vizSettings, nextVariant } = useVisualization();
160
+
161
+ // Determine effective variant (from props or localStorage settings)
162
+ const effectiveVariant =
163
+ variant ?? (vizSettings.variant !== 'none' ? vizSettings.variant : 'spotlight');
164
+ const effectiveIntensity = intensity ?? vizSettings.intensity;
165
+ const effectiveColorScheme = colorScheme ?? vizSettings.colorScheme;
166
+
167
+ // Show reactive cover if enabled and variant is not 'none'
168
+ const showReactiveCover = reactiveCover && effectiveVariant !== 'none';
169
+
170
+ // Render cover art content
171
+ const renderCoverContent = () => {
172
+ if (typeof coverArt === 'string') {
173
+ return (
174
+ <img src={coverArt} alt={title || 'Album cover'} className="w-full h-full object-cover" />
175
+ );
176
+ }
177
+
178
+ if (coverArt) {
179
+ return coverArt;
180
+ }
181
+
182
+ // Default placeholder
183
+ return (
184
+ <div className="w-full h-full bg-muted/30 flex items-center justify-center">
185
+ <Music className="w-1/3 h-1/3 text-muted-foreground/50" />
186
+ </div>
187
+ );
188
+ };
189
+
190
+ const isHorizontal = layout === 'horizontal';
191
+
192
+ return (
193
+ <HybridAudioProvider
194
+ src={src}
195
+ autoPlay={autoPlay}
196
+ loop={loop}
197
+ initialVolume={initialVolume}
198
+ onPlay={onPlay}
199
+ onPause={onPause}
200
+ onEnded={onEnded}
201
+ onError={onError}
202
+ >
203
+ <div
204
+ className={cn(
205
+ 'flex gap-4',
206
+ isHorizontal ? 'flex-row items-center' : 'flex-col items-center',
207
+ className
208
+ )}
209
+ >
210
+ {/* Cover Art */}
211
+ {(coverArt || reactiveCover) && (
212
+ <div className="flex flex-col items-center gap-2 shrink-0">
213
+ {showReactiveCover ? (
214
+ <AudioReactiveCover
215
+ size={coverSize}
216
+ variant={effectiveVariant as 'glow' | 'orbs' | 'spotlight' | 'mesh'}
217
+ intensity={effectiveIntensity}
218
+ colorScheme={effectiveColorScheme}
219
+ onClick={nextVariant}
220
+ >
221
+ <div className={cn('rounded-lg overflow-hidden', COVER_SIZES[coverSize])}>
222
+ {renderCoverContent()}
223
+ </div>
224
+ </AudioReactiveCover>
225
+ ) : (
226
+ <div
227
+ className={cn(
228
+ 'rounded-lg overflow-hidden shadow-lg cursor-pointer',
229
+ COVER_SIZES[coverSize]
230
+ )}
231
+ onClick={nextVariant}
232
+ role="button"
233
+ tabIndex={0}
234
+ onKeyDown={(e) => e.key === 'Enter' && nextVariant()}
235
+ >
236
+ {renderCoverContent()}
237
+ </div>
238
+ )}
239
+
240
+ {/* Effect indicator */}
241
+ {reactiveCover && (
242
+ <span className="text-[10px] uppercase tracking-wider text-muted-foreground/50 select-none">
243
+ {vizSettings.variant === 'none' ? 'off' : vizSettings.variant}
244
+ </span>
245
+ )}
246
+ </div>
247
+ )}
248
+
249
+ {/* Track Info + Player */}
250
+ <div
251
+ className={cn('flex flex-col gap-3', isHorizontal ? 'flex-1 min-w-0' : 'w-full max-w-md')}
252
+ >
253
+ {/* Track Info */}
254
+ {(title || artist) && (
255
+ <div className={cn('text-center', isHorizontal && 'text-left')}>
256
+ {title && (
257
+ <h3 className="text-base font-medium text-foreground truncate">{title}</h3>
258
+ )}
259
+ {artist && <p className="text-sm text-muted-foreground truncate">{artist}</p>}
260
+ </div>
261
+ )}
262
+
263
+ {/* Audio Player */}
264
+ <HybridAudioPlayer
265
+ showControls
266
+ showWaveform={showWaveform}
267
+ waveformMode={waveformMode}
268
+ waveformHeight={waveformHeight}
269
+ showTimer={showTimer}
270
+ showVolume={showVolume}
271
+ showLoop={showLoop}
272
+ className="border-0 bg-transparent"
273
+ />
274
+ </div>
275
+ </div>
276
+ </HybridAudioProvider>
277
+ );
278
+ }
279
+
280
+ export default HybridSimplePlayer;