@djangocfg/layouts 1.4.30 → 2.0.1

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 (119) hide show
  1. package/README.md +277 -18
  2. package/package.json +15 -24
  3. package/src/auth/context/AuthContext.tsx +5 -5
  4. package/src/auth/hooks/useAuthGuard.ts +1 -1
  5. package/src/auth/hooks/useAutoAuth.ts +8 -7
  6. package/src/components/ErrorBoundary.tsx +78 -0
  7. package/src/components/JsonLd.tsx +31 -0
  8. package/src/components/LucideIcon.tsx +91 -0
  9. package/src/components/PageProgress.tsx +127 -0
  10. package/src/components/Suspense.tsx +29 -0
  11. package/src/{layouts/AppLayout/components → components}/UpdateNotifier/UpdateNotifier.tsx +56 -49
  12. package/src/components/index.ts +10 -0
  13. package/src/index.ts +25 -7
  14. package/src/layouts/AdminLayout/AdminLayout.tsx +46 -0
  15. package/src/layouts/AdminLayout/index.ts +7 -0
  16. package/src/layouts/AppLayout/AppLayout.tsx +278 -326
  17. package/src/layouts/AppLayout/index.ts +2 -39
  18. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthContext.tsx +3 -2
  19. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthHelp.tsx +1 -0
  20. package/src/layouts/AuthLayout/AuthLayout.tsx +61 -0
  21. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/IdentifierForm.tsx +47 -34
  22. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/OTPForm.tsx +2 -3
  23. package/src/layouts/AuthLayout/index.ts +24 -0
  24. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/types.ts +1 -0
  25. package/src/layouts/PrivateLayout/PrivateLayout.tsx +144 -0
  26. package/src/layouts/PrivateLayout/components/PrivateContent.tsx +32 -0
  27. package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +57 -0
  28. package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +141 -0
  29. package/src/layouts/PrivateLayout/components/index.ts +8 -0
  30. package/src/layouts/PrivateLayout/index.ts +7 -0
  31. package/src/layouts/ProfileLayout/ProfileLayout.tsx +15 -7
  32. package/src/layouts/PublicLayout/PublicLayout.tsx +121 -0
  33. package/src/layouts/PublicLayout/components/PublicFooter.tsx +190 -0
  34. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +117 -0
  35. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +101 -0
  36. package/src/layouts/PublicLayout/components/index.ts +8 -0
  37. package/src/layouts/PublicLayout/index.ts +7 -0
  38. package/src/layouts/_components/UserMenu.tsx +160 -0
  39. package/src/layouts/_components/index.ts +7 -0
  40. package/src/layouts/index.ts +15 -8
  41. package/src/snippets/Analytics/AnalyticsProvider.tsx +8 -4
  42. package/src/snippets/Analytics/useAnalytics.ts +11 -21
  43. package/src/snippets/Chat/ChatWidget.tsx +4 -4
  44. package/src/snippets/ContactForm/ContactFormProvider.tsx +32 -19
  45. package/src/snippets/ContactForm/ContactPage.tsx +2 -4
  46. package/src/snippets/ContactForm/types.ts +3 -2
  47. package/src/snippets/index.ts +0 -1
  48. package/src/layouts/AppLayout/README.md +0 -204
  49. package/src/layouts/AppLayout/SUMMARY.md +0 -240
  50. package/src/layouts/AppLayout/USAGE.md +0 -312
  51. package/src/layouts/AppLayout/components/ErrorBoundary.tsx +0 -112
  52. package/src/layouts/AppLayout/components/PageProgress.tsx +0 -123
  53. package/src/layouts/AppLayout/components/Seo.tsx +0 -171
  54. package/src/layouts/AppLayout/components/UserMenu.tsx +0 -385
  55. package/src/layouts/AppLayout/components/index.ts +0 -11
  56. package/src/layouts/AppLayout/context/AppContext.tsx +0 -151
  57. package/src/layouts/AppLayout/context/index.ts +0 -5
  58. package/src/layouts/AppLayout/hooks/index.ts +0 -8
  59. package/src/layouts/AppLayout/hooks/useLayoutMode.ts +0 -26
  60. package/src/layouts/AppLayout/hooks/useNavigation.ts +0 -51
  61. package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +0 -224
  62. package/src/layouts/AppLayout/layouts/AdminLayout/README.md +0 -409
  63. package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.example.tsx +0 -98
  64. package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.tsx +0 -149
  65. package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +0 -146
  66. package/src/layouts/AppLayout/layouts/AdminLayout/components/index.ts +0 -3
  67. package/src/layouts/AppLayout/layouts/AdminLayout/context/CfgAppContext.tsx +0 -48
  68. package/src/layouts/AppLayout/layouts/AdminLayout/context/index.ts +0 -2
  69. package/src/layouts/AppLayout/layouts/AdminLayout/hooks/index.ts +0 -6
  70. package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +0 -279
  71. package/src/layouts/AppLayout/layouts/AdminLayout/index.ts +0 -24
  72. package/src/layouts/AppLayout/layouts/AdminLayout/lottie/energizing.json +0 -1
  73. package/src/layouts/AppLayout/layouts/AdminLayout/types/index.ts +0 -45
  74. package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +0 -41
  75. package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +0 -15
  76. package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +0 -82
  77. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +0 -62
  78. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +0 -89
  79. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +0 -181
  80. package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +0 -9
  81. package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +0 -5
  82. package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +0 -44
  83. package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +0 -242
  84. package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileDrawer.tsx +0 -150
  85. package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +0 -169
  86. package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +0 -5
  87. package/src/layouts/AppLayout/layouts/index.ts +0 -7
  88. package/src/layouts/AppLayout/providers/CoreProviders.tsx +0 -80
  89. package/src/layouts/AppLayout/providers/index.ts +0 -5
  90. package/src/layouts/AppLayout/types/config.ts +0 -79
  91. package/src/layouts/AppLayout/types/index.ts +0 -11
  92. package/src/layouts/AppLayout/types/layout.ts +0 -54
  93. package/src/layouts/AppLayout/types/navigation.ts +0 -43
  94. package/src/layouts/AppLayout/types/page.ts +0 -80
  95. package/src/layouts/AppLayout/types/routes.ts +0 -43
  96. package/src/layouts/AppLayout/utils/index.ts +0 -5
  97. package/src/layouts/AppLayout/utils/routeDetection.ts +0 -31
  98. package/src/layouts/ErrorLayout/ErrorLayout.tsx +0 -173
  99. package/src/layouts/ErrorLayout/errorConfig.tsx +0 -152
  100. package/src/layouts/ErrorLayout/index.ts +0 -8
  101. package/src/layouts/SimpleLayout/SimpleLayout.tsx +0 -72
  102. package/src/layouts/SimpleLayout/index.ts +0 -3
  103. package/src/snippets/VideoPlayer/README.md +0 -238
  104. package/src/snippets/VideoPlayer/VideoControls.tsx +0 -137
  105. package/src/snippets/VideoPlayer/VideoPlayer.tsx +0 -248
  106. package/src/snippets/VideoPlayer/index.ts +0 -8
  107. package/src/snippets/VideoPlayer/types.ts +0 -61
  108. package/src/types/index.ts +0 -2
  109. package/src/types/pageConfig.ts +0 -100
  110. /package/src/{validation → components/ErrorsTracker}/README.md +0 -0
  111. /package/src/{validation → components/ErrorsTracker}/components/ErrorButtons.tsx +0 -0
  112. /package/src/{validation → components/ErrorsTracker}/components/ErrorToast.tsx +0 -0
  113. /package/src/{validation → components/ErrorsTracker}/hooks.ts +0 -0
  114. /package/src/{validation → components/ErrorsTracker}/index.ts +0 -0
  115. /package/src/{validation → components/ErrorsTracker}/providers/ErrorTrackingProvider.tsx +0 -0
  116. /package/src/{validation → components/ErrorsTracker}/types.ts +0 -0
  117. /package/src/{validation → components/ErrorsTracker}/utils/curl-generator.ts +0 -0
  118. /package/src/{validation → components/ErrorsTracker}/utils/formatters.ts +0 -0
  119. /package/src/{layouts/AppLayout/components → components}/UpdateNotifier/index.ts +0 -0
@@ -1,72 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * SimpleLayout - Lightweight provider for docs and marketing sites
4
- *
5
- * Provides essential UI infrastructure without the overhead of full AppLayout:
6
- * - TooltipProvider for all tooltip components
7
- * - Toaster for notifications
8
- * - Basic theme support
9
- *
10
- * Perfect for documentation sites, landing pages, and simple apps.
11
- */
12
-
13
- 'use client';
14
-
15
- import React from 'react';
16
- import { TooltipProvider, Toaster } from '@djangocfg/ui';
17
-
18
- export interface SimpleLayoutProps {
19
- children: React.ReactNode;
20
- /**
21
- * Delay before tooltips appear (in milliseconds)
22
- * @default 200
23
- */
24
- tooltipDelayDuration?: number;
25
- /**
26
- * Skip delay when moving between tooltips
27
- * @default 300
28
- */
29
- tooltipSkipDelayDuration?: number;
30
- }
31
-
32
- /**
33
- * Lightweight layout provider for documentation and marketing sites.
34
- *
35
- * @example
36
- * ```tsx
37
- * // In your root layout.tsx
38
- * import { SimpleLayout } from '@djangocfg/layouts';
39
- *
40
- * export default function RootLayout({ children }) {
41
- * return (
42
- * <html>
43
- * <body>
44
- * <SimpleLayout>
45
- * {children}
46
- * </SimpleLayout>
47
- * </body>
48
- * </html>
49
- * );
50
- * }
51
- * ```
52
- */
53
- export function SimpleLayout({
54
- children,
55
- tooltipDelayDuration = 200,
56
- tooltipSkipDelayDuration = 300,
57
- }: SimpleLayoutProps) {
58
- return (
59
- <>
60
- <TooltipProvider
61
- delayDuration={tooltipDelayDuration}
62
- skipDelayDuration={tooltipSkipDelayDuration}
63
- >
64
- {children}
65
- </TooltipProvider>
66
- <Toaster />
67
- </>
68
- );
69
- }
70
-
71
- SimpleLayout.displayName = 'SimpleLayout';
72
-
@@ -1,3 +0,0 @@
1
- export { SimpleLayout } from './SimpleLayout';
2
- export type { SimpleLayoutProps } from './SimpleLayout';
3
-
@@ -1,238 +0,0 @@
1
- # VideoPlayer - Professional Vidstack Implementation
2
-
3
- A professional, accessible video player built with Vidstack React that supports YouTube, Vimeo, MP4, HLS, and more.
4
-
5
- ## Features
6
-
7
- - ✅ **Multi-platform support**: YouTube, Vimeo, MP4, HLS, DASH
8
- - ✅ **Custom controls**: Professional UI with hover effects
9
- - ✅ **Accessibility**: Full keyboard navigation and screen reader support
10
- - ✅ **Responsive**: Works on all screen sizes
11
- - ✅ **TypeScript**: Full type safety
12
- - ✅ **Themes**: Default, minimal, and modern themes
13
- - ✅ **No recommendations**: Clean playback without YouTube distractions
14
-
15
- ## Installation
16
-
17
- The VideoPlayer is already included in the UI package with all dependencies:
18
-
19
- ```bash
20
- pnpm add @vidstack/react@next media-icons@next
21
- ```
22
-
23
- ## Basic Usage
24
-
25
- ```tsx
26
- import { VideoPlayer } from '@repo/ui/snippets/VideoPlayer';
27
-
28
- function MyComponent() {
29
- return (
30
- <VideoPlayer
31
- source={{
32
- url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
33
- title: 'Never Gonna Give You Up',
34
- description: 'Rick Astley - Never Gonna Give You Up (Official Video)'
35
- }}
36
- autoplay={false}
37
- controls={true}
38
- className="max-w-4xl mx-auto"
39
- />
40
- );
41
- }
42
- ```
43
-
44
- ## Advanced Usage
45
-
46
- ```tsx
47
- import { VideoPlayer, VideoPlayerRef } from '@repo/ui/snippets/VideoPlayer';
48
- import { useRef } from 'react';
49
-
50
- function AdvancedPlayer() {
51
- const playerRef = useRef<VideoPlayerRef>(null);
52
-
53
- const handleCustomPlay = () => {
54
- playerRef.current?.play();
55
- };
56
-
57
- return (
58
- <VideoPlayer
59
- ref={playerRef}
60
- source={{
61
- url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
62
- title: 'Big Buck Bunny',
63
- poster: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg',
64
- duration: 596
65
- }}
66
- theme="modern"
67
- autoplay={false}
68
- muted={false}
69
- playsInline={true}
70
- showInfo={true}
71
- onPlay={() => console.log('Video started')}
72
- onPause={() => console.log('Video paused')}
73
- onEnded={() => console.log('Video ended')}
74
- onError={(error) => console.error('Video error:', error)}
75
- />
76
- );
77
- }
78
- ```
79
-
80
- ## Supported Video Sources
81
-
82
- ### YouTube
83
- - **URL Format**: `https://www.youtube.com/watch?v=VIDEO_ID` or `youtube/VIDEO_ID`
84
- - **Auto-conversion**: Full YouTube URLs are automatically converted to `youtube/ID` format
85
- - **Poster**: ⚠️ YouTube iframe ignores custom poster images and always shows YouTube's thumbnail
86
- - **Examples**:
87
- ```tsx
88
- url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
89
- url: 'https://youtu.be/dQw4w9WgXcQ'
90
- url: 'youtube/dQw4w9WgXcQ'
91
- ```
92
-
93
- ### Vimeo
94
- - **URL Format**: `https://vimeo.com/VIDEO_ID` or `vimeo/VIDEO_ID`
95
- - **Auto-conversion**: Full Vimeo URLs are automatically converted to `vimeo/ID` format
96
- - **Poster**: ⚠️ Vimeo may ignore custom poster and use their own thumbnail
97
- - **Example**: `url: 'vimeo/76979871'`
98
-
99
- ### Direct Video Files (MP4, WebM, OGG)
100
- - **Poster**: ✅ **Works perfectly!** Custom poster images are fully supported
101
- - **Examples**:
102
- ```tsx
103
- url: 'https://example.com/video.mp4',
104
- poster: '/images/video-poster.jpg' // This works!
105
- ```
106
-
107
- ### HLS Streams
108
- - **Poster**: ✅ Custom poster supported
109
- - **Example**: `url: 'https://example.com/stream.m3u8'`
110
-
111
- ### DASH Streams
112
- - **Poster**: ✅ Custom poster supported
113
- - **Example**: `url: 'https://example.com/stream.mpd'`
114
-
115
- > **Note**: The `poster` prop works for direct video files, HLS, and DASH streams. For YouTube and Vimeo, the platform's own thumbnail is displayed regardless of the `poster` prop due to iframe limitations.
116
-
117
- ### YouTube
118
- ```tsx
119
- <VideoPlayer
120
- source={{
121
- url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
122
- title: 'YouTube Video'
123
- }}
124
- />
125
- ```
126
-
127
- ### Vimeo
128
- ```tsx
129
- <VideoPlayer
130
- source={{
131
- url: 'https://vimeo.com/76979871',
132
- title: 'Vimeo Video'
133
- }}
134
- />
135
- ```
136
-
137
- ### Direct MP4
138
- ```tsx
139
- <VideoPlayer
140
- source={{
141
- url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
142
- title: 'Direct MP4',
143
- poster: 'https://example.com/poster.jpg'
144
- }}
145
- />
146
- ```
147
-
148
- ### HLS Stream
149
- ```tsx
150
- <VideoPlayer
151
- source={{
152
- url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
153
- title: 'HLS Stream'
154
- }}
155
- />
156
- ```
157
-
158
- ## API Reference
159
-
160
- ### VideoPlayerProps
161
-
162
- | Prop | Type | Default | Description |
163
- |------|------|---------|-------------|
164
- | `source` | `VideoSource` | - | Video source configuration |
165
- | `aspectRatio` | `number` | `16/9` | Video aspect ratio |
166
- | `autoplay` | `boolean` | `false` | Auto-play video |
167
- | `muted` | `boolean` | `false` | Mute video by default |
168
- | `playsInline` | `boolean` | `true` | Play inline on mobile |
169
- | `controls` | `boolean` | `true` | Show custom controls |
170
- | `showInfo` | `boolean` | `false` | Show video info below player |
171
- | `theme` | `'default' \| 'minimal' \| 'modern'` | `'default'` | Player theme |
172
- | `className` | `string` | - | Custom CSS class |
173
- | `onPlay` | `() => void` | - | Play event callback |
174
- | `onPause` | `() => void` | - | Pause event callback |
175
- | `onEnded` | `() => void` | - | End event callback |
176
- | `onError` | `(error: string) => void` | - | Error event callback |
177
-
178
- ### VideoSource
179
-
180
- | Property | Type | Description |
181
- |----------|------|-------------|
182
- | `url` | `string` | Video URL (YouTube, Vimeo, MP4, HLS, etc.) |
183
- | `title` | `string?` | Video title |
184
- | `description` | `string?` | Video description |
185
- | `poster` | `string?` | Custom poster/thumbnail URL |
186
- | `duration` | `number?` | Video duration in seconds |
187
-
188
- ### VideoPlayerRef Methods
189
-
190
- | Method | Description |
191
- |--------|-------------|
192
- | `play()` | Play the video |
193
- | `pause()` | Pause the video |
194
- | `togglePlay()` | Toggle play/pause |
195
- | `seekTo(time: number)` | Seek to specific time |
196
- | `setVolume(volume: number)` | Set volume (0-1) |
197
- | `toggleMute()` | Toggle mute |
198
- | `enterFullscreen()` | Enter fullscreen |
199
- | `exitFullscreen()` | Exit fullscreen |
200
-
201
- ## Themes
202
-
203
- ### Default Theme
204
- Clean, professional look with rounded corners and subtle shadows.
205
-
206
- ### Minimal Theme
207
- No rounded corners, minimal styling for embedding in tight spaces.
208
-
209
- ### Modern Theme
210
- Enhanced shadows and larger border radius for a contemporary look.
211
-
212
- ## Accessibility
213
-
214
- The VideoPlayer includes full accessibility support:
215
-
216
- - ✅ Keyboard navigation (Space, Arrow keys, F for fullscreen)
217
- - ✅ Screen reader announcements
218
- - ✅ Focus indicators
219
- - ✅ ARIA labels and roles
220
- - ✅ High contrast support
221
-
222
- ## Performance
223
-
224
- - ✅ Lazy loading of video content
225
- - ✅ Efficient re-renders with Vidstack's optimized state management
226
- - ✅ Minimal bundle size impact
227
- - ✅ Hardware-accelerated playback when available
228
-
229
- ## Browser Support
230
-
231
- Supports all modern browsers through Vidstack's comprehensive compatibility layer:
232
-
233
- - ✅ Chrome 63+
234
- - ✅ Firefox 67+
235
- - ✅ Safari 12+
236
- - ✅ Edge 79+
237
- - ✅ iOS Safari 12+
238
- - ✅ Chrome Android 63+
@@ -1,137 +0,0 @@
1
- /**
2
- * Custom Video Controls for Vidstack Player
3
- */
4
-
5
- 'use client';
6
-
7
- import React from 'react';
8
- import { useMediaStore, useMediaRemote } from '@vidstack/react';
9
- import type { MediaPlayerInstance } from '@vidstack/react';
10
- import { Play, Pause, Volume2, VolumeX, Maximize, Minimize } from 'lucide-react';
11
- import { cn } from '@djangocfg/ui';
12
-
13
- interface VideoControlsProps {
14
- player: React.RefObject<MediaPlayerInstance | null>;
15
- className?: string;
16
- }
17
-
18
- export function VideoControls({ player, className }: VideoControlsProps) {
19
- const store = useMediaStore(player);
20
- const remote = useMediaRemote();
21
-
22
- const isPaused = store.paused;
23
- const isMuted = store.muted;
24
- const isFullscreen = store.fullscreen;
25
- const currentTime = store.currentTime;
26
- const duration = store.duration;
27
- const volume = store.volume;
28
-
29
- const formatTime = (seconds: number): string => {
30
- if (!seconds || seconds < 0) return '0:00';
31
- const minutes = Math.floor(seconds / 60);
32
- const secs = Math.floor(seconds % 60);
33
- return `${minutes}:${secs.toString().padStart(2, '0')}`;
34
- };
35
-
36
- const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => {
37
- if (!duration) return;
38
- const rect = e.currentTarget.getBoundingClientRect();
39
- const clickX = e.clientX - rect.left;
40
- const percentage = clickX / rect.width;
41
- const newTime = percentage * duration;
42
- remote.seek(newTime);
43
- };
44
-
45
- const handleVolumeChange = (e: React.MouseEvent<HTMLDivElement>) => {
46
- const rect = e.currentTarget.getBoundingClientRect();
47
- const clickX = e.clientX - rect.left;
48
- const percentage = Math.max(0, Math.min(1, clickX / rect.width));
49
- remote.changeVolume(percentage);
50
- if (percentage > 0 && isMuted) {
51
- remote.toggleMuted();
52
- }
53
- };
54
-
55
- const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
56
-
57
- return (
58
- <div
59
- className={cn(
60
- "absolute inset-0 flex flex-col justify-end transition-opacity duration-300",
61
- "bg-gradient-to-t from-black/80 via-black/20 to-transparent",
62
- "opacity-0 group-hover:opacity-100 focus-within:opacity-100",
63
- "pointer-events-none group-hover:pointer-events-auto",
64
- className
65
- )}
66
- >
67
- {/* Progress Bar */}
68
- <div className="px-4 pb-2 pointer-events-auto">
69
- <div
70
- className="h-1.5 bg-white/20 rounded-full cursor-pointer hover:h-2 transition-all group"
71
- onClick={handleProgressClick}
72
- >
73
- <div
74
- className="h-full bg-primary rounded-full transition-all relative group-hover:bg-primary/90"
75
- style={{ width: `${progress}%` }}
76
- >
77
- <div className="absolute right-0 top-1/2 -translate-y-1/2 w-3 h-3 bg-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity" />
78
- </div>
79
- </div>
80
- </div>
81
-
82
- {/* Control Bar */}
83
- <div className="flex items-center gap-4 px-4 pb-4 pointer-events-auto">
84
- {/* Play/Pause */}
85
- <button
86
- onClick={() => remote.togglePaused()}
87
- className="text-white hover:text-primary transition-colors p-1.5 hover:bg-white/10 rounded-full"
88
- >
89
- {isPaused ? <Play className="h-6 w-6" /> : <Pause className="h-6 w-6" />}
90
- </button>
91
-
92
- {/* Time */}
93
- <div className="text-white text-sm font-medium">
94
- {formatTime(currentTime)} / {formatTime(duration)}
95
- </div>
96
-
97
- <div className="flex-1" />
98
-
99
- {/* Volume Control */}
100
- <div className="flex items-center gap-2 group/volume">
101
- <button
102
- onClick={() => remote.toggleMuted()}
103
- className="text-white hover:text-primary transition-colors p-1.5 hover:bg-white/10 rounded-full"
104
- >
105
- {isMuted || volume === 0 ? (
106
- <VolumeX className="h-5 w-5" />
107
- ) : (
108
- <Volume2 className="h-5 w-5" />
109
- )}
110
- </button>
111
-
112
- <div
113
- className="w-0 group-hover/volume:w-20 transition-all overflow-hidden"
114
- >
115
- <div
116
- className="h-1.5 bg-white/20 rounded-full cursor-pointer hover:h-2 transition-all"
117
- onClick={handleVolumeChange}
118
- >
119
- <div
120
- className="h-full bg-white rounded-full transition-all"
121
- style={{ width: `${volume * 100}%` }}
122
- />
123
- </div>
124
- </div>
125
- </div>
126
-
127
- {/* Fullscreen */}
128
- <button
129
- onClick={() => isFullscreen ? remote.exitFullscreen() : remote.enterFullscreen()}
130
- className="text-white hover:text-primary transition-colors p-1.5 hover:bg-white/10 rounded-full"
131
- >
132
- {isFullscreen ? <Minimize className="h-5 w-5" /> : <Maximize className="h-5 w-5" />}
133
- </button>
134
- </div>
135
- </div>
136
- );
137
- }
@@ -1,248 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * Professional VideoPlayer - Vidstack Implementation
4
- * Supports YouTube, Vimeo, MP4, HLS and more with custom controls
5
- */
6
-
7
- 'use client';
8
-
9
- import React, { forwardRef, useImperativeHandle, useRef, useMemo } from 'react';
10
- import { MediaPlayer, MediaProvider, Poster } from '@vidstack/react';
11
- import { defaultLayoutIcons, DefaultVideoLayout } from '@vidstack/react/player/layouts/default';
12
- import type { MediaPlayerInstance } from '@vidstack/react';
13
- import consola from 'consola';
14
- import { cn } from '@djangocfg/ui';
15
- import { type VideoPlayerProps, type VideoPlayerRef } from './types';
16
-
17
- // Import Vidstack base styles
18
- import '@vidstack/react/player/styles/base.css';
19
- // Import default theme
20
- import '@vidstack/react/player/styles/default/theme.css';
21
- import '@vidstack/react/player/styles/default/layouts/video.css';
22
-
23
- /**
24
- * Custom error class for invalid video URLs
25
- */
26
- export class VideoUrlError extends Error {
27
- constructor(
28
- message: string,
29
- public readonly url: string,
30
- public readonly suggestion?: string
31
- ) {
32
- super(message);
33
- this.name = 'VideoUrlError';
34
- }
35
- }
36
-
37
- /**
38
- * Validates and normalizes video URL for Vidstack
39
- * @throws {VideoUrlError} If URL format is invalid for the detected provider
40
- */
41
- function normalizeVideoUrl(url: string): string {
42
- if (!url || typeof url !== 'string') {
43
- throw new VideoUrlError('Video URL is required', url || '');
44
- }
45
-
46
- const trimmedUrl = url.trim();
47
-
48
- // Already in correct format (youtube/ID, vimeo/ID, or direct URL)
49
- if (trimmedUrl.startsWith('youtube/') || trimmedUrl.startsWith('vimeo/')) {
50
- return trimmedUrl;
51
- }
52
-
53
- // YouTube URL patterns - auto-convert to youtube/ID format
54
- const youtubePatterns = [
55
- /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/,
56
- /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/,
57
- ];
58
-
59
- for (const pattern of youtubePatterns) {
60
- const match = trimmedUrl.match(pattern);
61
- if (match) {
62
- // Auto-convert YouTube URL to youtube/ID format
63
- const videoId = match[1];
64
- if (process.env.NODE_ENV === 'development') {
65
- consola.info(
66
- `[VideoPlayer] Auto-converted YouTube URL to "youtube/${videoId}" format`
67
- );
68
- }
69
- return `youtube/${videoId}`;
70
- }
71
- }
72
-
73
- // Vimeo URL patterns - auto-convert to vimeo/ID format
74
- const vimeoPattern = /vimeo\.com\/(\d+)/;
75
- const vimeoMatch = trimmedUrl.match(vimeoPattern);
76
- if (vimeoMatch) {
77
- const videoId = vimeoMatch[1];
78
- if (process.env.NODE_ENV === 'development') {
79
- consola.info(
80
- `[VideoPlayer] Auto-converted Vimeo URL to "vimeo/${videoId}" format`
81
- );
82
- }
83
- return `vimeo/${videoId}`;
84
- }
85
-
86
- // Direct video URLs (mp4, webm, etc.) - allow as-is
87
- if (/\.(mp4|webm|ogg|m3u8|mpd)(\?.*)?$/i.test(trimmedUrl)) {
88
- return trimmedUrl;
89
- }
90
-
91
- // HLS/DASH streams
92
- if (trimmedUrl.includes('.m3u8') || trimmedUrl.includes('.mpd')) {
93
- return trimmedUrl;
94
- }
95
-
96
- // Unknown format - return as-is but warn in dev
97
- if (process.env.NODE_ENV === 'development') {
98
- consola.warn(
99
- `[VideoPlayer] Unknown URL format: "${trimmedUrl}". ` +
100
- `Supported formats: youtube/{id}, vimeo/{id}, or direct video URLs (.mp4, .webm, .m3u8)`
101
- );
102
- }
103
-
104
- return trimmedUrl;
105
- }
106
-
107
- export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
108
- source,
109
- aspectRatio = 16 / 9,
110
- autoplay = false,
111
- muted = false,
112
- playsInline = true,
113
- controls = true,
114
- className,
115
- showInfo = false,
116
- theme = 'default',
117
- onPlay,
118
- onPause,
119
- onEnded,
120
- onError,
121
- }, ref) => {
122
- const playerRef = useRef<MediaPlayerInstance | null>(null);
123
-
124
- // Debug log
125
- if (process.env.NODE_ENV === 'development') {
126
- consola.info('[VideoPlayer] Received props:', {
127
- url: source.url,
128
- poster: source.poster,
129
- title: source.title,
130
- });
131
- }
132
-
133
- // Validate and normalize URL - throws VideoUrlError if invalid
134
- const normalizedUrl = useMemo(() => {
135
- try {
136
- return normalizeVideoUrl(source.url);
137
- } catch (error) {
138
- if (error instanceof VideoUrlError) {
139
- // Call onError callback and re-throw
140
- onError?.(error.message + (error.suggestion ? ` Use: "${error.suggestion}"` : ''));
141
- throw error;
142
- }
143
- throw error;
144
- }
145
- }, [source.url, onError]);
146
-
147
- // Expose player methods via ref
148
- useImperativeHandle(ref, () => {
149
- const player = playerRef.current;
150
-
151
- return {
152
- play: () => player?.play(),
153
- pause: () => player?.pause(),
154
- togglePlay: () => {
155
- if (player) {
156
- player.paused ? player.play() : player.pause();
157
- }
158
- },
159
- seekTo: (time: number) => {
160
- if (player) player.currentTime = time;
161
- },
162
- setVolume: (volume: number) => {
163
- if (player) player.volume = Math.max(0, Math.min(1, volume));
164
- },
165
- toggleMute: () => {
166
- if (player) player.muted = !player.muted;
167
- },
168
- enterFullscreen: () => player?.enterFullscreen(),
169
- exitFullscreen: () => player?.exitFullscreen(),
170
- };
171
- }, []);
172
-
173
- const handlePlay = () => {
174
- onPlay?.();
175
- };
176
-
177
- const handlePause = () => {
178
- onPause?.();
179
- };
180
-
181
- const handleEnded = () => {
182
- onEnded?.();
183
- };
184
-
185
- const handleError = (detail: any) => {
186
- onError?.(detail?.message || 'Video playback error');
187
- };
188
-
189
- return (
190
- <div className={cn("w-full", className)}>
191
- {/* Video Player */}
192
- <div
193
- className={cn(
194
- "relative w-full rounded-sm bg-black overflow-hidden",
195
- theme === 'minimal' && "rounded-none",
196
- theme === 'modern' && "rounded-xl shadow-2xl"
197
- )}
198
- style={{ aspectRatio: aspectRatio }}
199
- >
200
- <MediaPlayer
201
- ref={playerRef}
202
- title={source.title || 'Video'}
203
- src={normalizedUrl}
204
- autoPlay={autoplay}
205
- muted={muted}
206
- playsInline={playsInline}
207
- onPlay={handlePlay}
208
- onPause={handlePause}
209
- onEnded={handleEnded}
210
- onError={handleError}
211
- className="w-full h-full"
212
- >
213
- <MediaProvider />
214
-
215
- {/* Poster with proper aspect ratio handling */}
216
- {source.poster && (
217
- <Poster
218
- className="vds-poster"
219
- src={source.poster}
220
- alt={source.title || 'Video poster'}
221
- style={{ objectFit: 'cover' }}
222
- />
223
- )}
224
-
225
- {/* Use Vidstack's built-in default layout */}
226
- {controls && (
227
- <DefaultVideoLayout
228
- icons={defaultLayoutIcons}
229
- thumbnails={source.poster}
230
- />
231
- )}
232
- </MediaPlayer>
233
- </div>
234
-
235
- {/* Video Info */}
236
- {showInfo && source.title && (
237
- <div className="mt-4 space-y-2">
238
- <h3 className="text-xl font-semibold text-foreground">{source.title}</h3>
239
- {source.description && (
240
- <p className="text-muted-foreground">{source.description}</p>
241
- )}
242
- </div>
243
- )}
244
- </div>
245
- );
246
- });
247
-
248
- VideoPlayer.displayName = 'VideoPlayer';
@@ -1,8 +0,0 @@
1
- /**
2
- * Professional VideoPlayer - Vidstack Implementation
3
- * Export all components and types
4
- */
5
-
6
- export { VideoPlayer, VideoUrlError } from './VideoPlayer';
7
- export { VideoControls } from './VideoControls';
8
- export type { VideoSource, VideoPlayerProps, VideoPlayerRef } from './types';