@djangocfg/layouts 1.4.27 → 1.4.29
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 +8 -8
- package/src/auth/context/AuthContext.tsx +4 -1
- package/src/auth/hooks/index.ts +2 -0
- package/src/auth/hooks/useAuthForm.ts +2 -0
- package/src/auth/hooks/useAuthGuard.ts +2 -0
- package/src/auth/hooks/useAutoAuth.ts +16 -11
- package/src/auth/hooks/useLocalStorage.ts +2 -0
- package/src/auth/hooks/useProfileCache.ts +2 -0
- package/src/auth/hooks/useSessionStorage.ts +2 -0
- package/src/auth/middlewares/index.ts +1 -1
- package/src/auth/middlewares/proxy.ts +10 -2
- package/src/layouts/AppLayout/AppLayout.tsx +9 -7
- package/src/layouts/AppLayout/components/ErrorBoundary.tsx +6 -3
- package/src/layouts/AppLayout/components/PageProgress.tsx +2 -0
- package/src/layouts/AppLayout/components/Seo.tsx +2 -0
- package/src/layouts/AppLayout/components/UpdateNotifier/UpdateNotifier.tsx +3 -2
- package/src/layouts/AppLayout/hooks/index.ts +2 -0
- package/src/layouts/AppLayout/hooks/useNavigation.ts +3 -1
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +1 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthContext.tsx +2 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +2 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +4 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +2 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +1 -0
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +1 -0
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +1 -0
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +1 -0
- package/src/layouts/PaymentsLayout/events.ts +2 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +1 -0
- package/src/layouts/ProfileLayout/components/ProfileForm.tsx +1 -0
- package/src/layouts/SimpleLayout/SimpleLayout.tsx +72 -0
- package/src/layouts/SimpleLayout/index.ts +3 -0
- package/src/layouts/SupportLayout/SupportLayout.tsx +1 -0
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +1 -0
- package/src/layouts/SupportLayout/components/TicketList.tsx +6 -5
- package/src/layouts/SupportLayout/events.ts +2 -0
- package/src/layouts/index.ts +1 -3
- package/src/snippets/AuthDialog/useAuthDialog.ts +2 -0
- package/src/snippets/Chat/components/MessageList.tsx +12 -11
- package/src/snippets/Chat/index.tsx +1 -0
- package/src/snippets/ContactForm/ContactForm.tsx +7 -2
- package/src/snippets/ContactForm/ContactPage.tsx +16 -3
- package/src/snippets/VideoPlayer/README.md +35 -0
- package/src/snippets/VideoPlayer/VideoControls.tsx +13 -9
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +159 -25
- package/src/snippets/VideoPlayer/index.ts +1 -1
- package/src/validation/utils/curl-generator.ts +5 -1
- package/src/layouts/UILayout/README.md +0 -267
- package/src/layouts/UILayout/SUMMARY.md +0 -298
- package/src/layouts/UILayout/TOOLS_INTEGRATION.md +0 -216
- package/src/layouts/UILayout/components/AutoComponentDemo.tsx +0 -77
- package/src/layouts/UILayout/components/CategoryRenderer.tsx +0 -45
- package/src/layouts/UILayout/components/TailwindGuideRenderer.tsx +0 -138
- package/src/layouts/UILayout/components/index.ts +0 -15
- package/src/layouts/UILayout/components/layout/Header/CopyAIButton.tsx +0 -58
- package/src/layouts/UILayout/components/layout/Header/Header.tsx +0 -60
- package/src/layouts/UILayout/components/layout/Header/HeaderDesktop.tsx +0 -51
- package/src/layouts/UILayout/components/layout/Header/HeaderMobile.tsx +0 -71
- package/src/layouts/UILayout/components/layout/Header/TestValidationButton.tsx +0 -268
- package/src/layouts/UILayout/components/layout/Header/index.ts +0 -11
- package/src/layouts/UILayout/components/layout/MobileOverlay/MobileOverlay.tsx +0 -47
- package/src/layouts/UILayout/components/layout/MobileOverlay/index.ts +0 -6
- package/src/layouts/UILayout/components/layout/Sidebar/Sidebar.tsx +0 -95
- package/src/layouts/UILayout/components/layout/Sidebar/SidebarCategory.tsx +0 -54
- package/src/layouts/UILayout/components/layout/Sidebar/SidebarContent.tsx +0 -93
- package/src/layouts/UILayout/components/layout/Sidebar/SidebarFooter.tsx +0 -49
- package/src/layouts/UILayout/components/layout/Sidebar/index.ts +0 -9
- package/src/layouts/UILayout/components/layout/index.ts +0 -8
- package/src/layouts/UILayout/components/shared/Badge/CountBadge.tsx +0 -38
- package/src/layouts/UILayout/components/shared/Badge/index.ts +0 -5
- package/src/layouts/UILayout/components/shared/CodeBlock/CodeBlock.tsx +0 -48
- package/src/layouts/UILayout/components/shared/CodeBlock/CopyButton.tsx +0 -49
- package/src/layouts/UILayout/components/shared/CodeBlock/index.ts +0 -6
- package/src/layouts/UILayout/components/shared/Section/Section.tsx +0 -63
- package/src/layouts/UILayout/components/shared/Section/index.ts +0 -5
- package/src/layouts/UILayout/components/shared/index.ts +0 -8
- package/src/layouts/UILayout/config/ai-export.config.ts +0 -89
- package/src/layouts/UILayout/config/categories.config.tsx +0 -122
- package/src/layouts/UILayout/config/components/blocks.config.tsx +0 -239
- package/src/layouts/UILayout/config/components/data.config.tsx +0 -433
- package/src/layouts/UILayout/config/components/feedback.config.tsx +0 -290
- package/src/layouts/UILayout/config/components/forms.config.tsx +0 -996
- package/src/layouts/UILayout/config/components/hooks.config.tsx +0 -168
- package/src/layouts/UILayout/config/components/index.ts +0 -72
- package/src/layouts/UILayout/config/components/layout.config.tsx +0 -246
- package/src/layouts/UILayout/config/components/navigation.config.tsx +0 -352
- package/src/layouts/UILayout/config/components/overlay.config.tsx +0 -569
- package/src/layouts/UILayout/config/components/specialized.config.tsx +0 -400
- package/src/layouts/UILayout/config/components/tools.config.tsx +0 -234
- package/src/layouts/UILayout/config/components/types.ts +0 -14
- package/src/layouts/UILayout/config/index.ts +0 -42
- package/src/layouts/UILayout/config/tailwind.config.ts +0 -131
- package/src/layouts/UILayout/constants.ts +0 -23
- package/src/layouts/UILayout/context/ShowcaseContext.tsx +0 -81
- package/src/layouts/UILayout/context/index.ts +0 -1
- package/src/layouts/UILayout/core/UIGuideApp.client.tsx +0 -18
- package/src/layouts/UILayout/core/UIGuideApp.tsx +0 -33
- package/src/layouts/UILayout/core/UIGuideLanding.tsx +0 -172
- package/src/layouts/UILayout/core/UIGuideView.tsx +0 -61
- package/src/layouts/UILayout/core/UILayout.tsx +0 -125
- package/src/layouts/UILayout/core/UILayoutSidebar.tsx +0 -11
- package/src/layouts/UILayout/core/index.ts +0 -10
- package/src/layouts/UILayout/hooks/index.ts +0 -9
- package/src/layouts/UILayout/hooks/useAIExport.ts +0 -78
- package/src/layouts/UILayout/hooks/useCategoryNavigation.ts +0 -92
- package/src/layouts/UILayout/hooks/useComponentSearch.ts +0 -81
- package/src/layouts/UILayout/hooks/useSidebarState.ts +0 -36
- package/src/layouts/UILayout/index.ts +0 -160
- package/src/layouts/UILayout/types/component.ts +0 -45
- package/src/layouts/UILayout/types/index.ts +0 -23
- package/src/layouts/UILayout/types/layout.ts +0 -57
- package/src/layouts/UILayout/types/navigation.ts +0 -33
- package/src/layouts/UILayout/utils/ai-export/formatters.ts +0 -71
- package/src/layouts/UILayout/utils/ai-export/index.ts +0 -5
- package/src/layouts/UILayout/utils/component-helpers/filter.ts +0 -109
- package/src/layouts/UILayout/utils/component-helpers/index.ts +0 -6
- package/src/layouts/UILayout/utils/component-helpers/search.ts +0 -95
- package/src/layouts/UILayout/utils/index.ts +0 -6
|
@@ -79,6 +79,41 @@ function AdvancedPlayer() {
|
|
|
79
79
|
|
|
80
80
|
## Supported Video Sources
|
|
81
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
|
+
|
|
82
117
|
### YouTube
|
|
83
118
|
```tsx
|
|
84
119
|
<VideoPlayer
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import React from 'react';
|
|
8
8
|
import { useMediaStore, useMediaRemote } from '@vidstack/react';
|
|
9
|
-
import type {
|
|
9
|
+
import type { MediaPlayerInstance } from '@vidstack/react';
|
|
10
10
|
import { Play, Pause, Volume2, VolumeX, Maximize, Minimize } from 'lucide-react';
|
|
11
11
|
import { cn } from '@djangocfg/ui';
|
|
12
12
|
|
|
13
13
|
interface VideoControlsProps {
|
|
14
|
-
player: React.RefObject<
|
|
14
|
+
player: React.RefObject<MediaPlayerInstance | null>;
|
|
15
15
|
className?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -55,13 +55,17 @@ export function VideoControls({ player, className }: VideoControlsProps) {
|
|
|
55
55
|
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
|
|
56
56
|
|
|
57
57
|
return (
|
|
58
|
-
<div
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
>
|
|
63
67
|
{/* Progress Bar */}
|
|
64
|
-
<div className="px-4 pb-2">
|
|
68
|
+
<div className="px-4 pb-2 pointer-events-auto">
|
|
65
69
|
<div
|
|
66
70
|
className="h-1.5 bg-white/20 rounded-full cursor-pointer hover:h-2 transition-all group"
|
|
67
71
|
onClick={handleProgressClick}
|
|
@@ -76,7 +80,7 @@ export function VideoControls({ player, className }: VideoControlsProps) {
|
|
|
76
80
|
</div>
|
|
77
81
|
|
|
78
82
|
{/* Control Bar */}
|
|
79
|
-
<div className="flex items-center gap-4 px-4 pb-4">
|
|
83
|
+
<div className="flex items-center gap-4 px-4 pb-4 pointer-events-auto">
|
|
80
84
|
{/* Play/Pause */}
|
|
81
85
|
<button
|
|
82
86
|
onClick={() => remote.togglePaused()}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/**
|
|
2
3
|
* Professional VideoPlayer - Vidstack Implementation
|
|
3
4
|
* Supports YouTube, Vimeo, MP4, HLS and more with custom controls
|
|
@@ -5,12 +6,103 @@
|
|
|
5
6
|
|
|
6
7
|
'use client';
|
|
7
8
|
|
|
8
|
-
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
9
|
-
import { MediaPlayer,
|
|
10
|
-
import {
|
|
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';
|
|
11
14
|
import { cn } from '@djangocfg/ui';
|
|
12
15
|
import { type VideoPlayerProps, type VideoPlayerRef } from './types';
|
|
13
|
-
|
|
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
|
+
}
|
|
14
106
|
|
|
15
107
|
export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
|
|
16
108
|
source,
|
|
@@ -27,26 +119,54 @@ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
|
|
|
27
119
|
onEnded,
|
|
28
120
|
onError,
|
|
29
121
|
}, ref) => {
|
|
30
|
-
const playerRef = useRef<
|
|
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]);
|
|
31
146
|
|
|
32
147
|
// Expose player methods via ref
|
|
33
148
|
useImperativeHandle(ref, () => {
|
|
34
|
-
const
|
|
35
|
-
if (!playerRef.current) return null;
|
|
36
|
-
const remote = new MediaRemoteControl();
|
|
37
|
-
remote.setTarget(playerRef.current as unknown as EventTarget);
|
|
38
|
-
return remote;
|
|
39
|
-
};
|
|
149
|
+
const player = playerRef.current;
|
|
40
150
|
|
|
41
151
|
return {
|
|
42
|
-
play: () =>
|
|
43
|
-
pause: () =>
|
|
44
|
-
togglePlay: () =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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(),
|
|
50
170
|
};
|
|
51
171
|
}, []);
|
|
52
172
|
|
|
@@ -71,7 +191,7 @@ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
|
|
|
71
191
|
{/* Video Player */}
|
|
72
192
|
<div
|
|
73
193
|
className={cn(
|
|
74
|
-
"relative w-full
|
|
194
|
+
"relative w-full rounded-sm bg-black overflow-hidden",
|
|
75
195
|
theme === 'minimal' && "rounded-none",
|
|
76
196
|
theme === 'modern' && "rounded-xl shadow-2xl"
|
|
77
197
|
)}
|
|
@@ -80,8 +200,7 @@ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
|
|
|
80
200
|
<MediaPlayer
|
|
81
201
|
ref={playerRef}
|
|
82
202
|
title={source.title || 'Video'}
|
|
83
|
-
src={
|
|
84
|
-
poster={source.poster}
|
|
203
|
+
src={normalizedUrl}
|
|
85
204
|
autoPlay={autoplay}
|
|
86
205
|
muted={muted}
|
|
87
206
|
playsInline={playsInline}
|
|
@@ -91,10 +210,25 @@ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
|
|
|
91
210
|
onError={handleError}
|
|
92
211
|
className="w-full h-full"
|
|
93
212
|
>
|
|
94
|
-
<
|
|
213
|
+
<MediaProvider />
|
|
95
214
|
|
|
96
|
-
{/*
|
|
97
|
-
{
|
|
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
|
+
)}
|
|
98
232
|
</MediaPlayer>
|
|
99
233
|
</div>
|
|
100
234
|
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* Export all components and types
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export { VideoPlayer } from './VideoPlayer';
|
|
6
|
+
export { VideoPlayer, VideoUrlError } from './VideoPlayer';
|
|
7
7
|
export { VideoControls } from './VideoControls';
|
|
8
8
|
export type { VideoSource, VideoPlayerProps, VideoPlayerRef } from './types';
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* cURL Generator
|
|
3
5
|
*
|
|
4
6
|
* Generates cURL commands from API request details with authentication token
|
|
5
7
|
*/
|
|
6
8
|
|
|
9
|
+
import consola from 'consola';
|
|
10
|
+
|
|
7
11
|
export interface CurlOptions {
|
|
8
12
|
method: string;
|
|
9
13
|
path: string;
|
|
@@ -28,7 +32,7 @@ export function getAuthToken(): string | null {
|
|
|
28
32
|
|
|
29
33
|
return token;
|
|
30
34
|
} catch (error) {
|
|
31
|
-
|
|
35
|
+
consola.error('Failed to get auth token:', error);
|
|
32
36
|
return null;
|
|
33
37
|
}
|
|
34
38
|
}
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
# UILayout - Config-Driven UI Documentation System
|
|
2
|
-
|
|
3
|
-
Modern, type-safe layout system for showcasing UI component libraries with built-in "Copy for AI" functionality.
|
|
4
|
-
|
|
5
|
-
## 🎯 Key Features
|
|
6
|
-
|
|
7
|
-
- **Config-Driven**: Single source of truth for all component documentation
|
|
8
|
-
- **Type-Safe**: Full TypeScript support with strict typing
|
|
9
|
-
- **Auto-Rendering**: Components automatically rendered from config
|
|
10
|
-
- **AI-Ready**: One-click export of entire documentation for AI consumption
|
|
11
|
-
- **Responsive**: Mobile-first design with adaptive sidebar
|
|
12
|
-
- **Dark Mode**: Built-in theme support
|
|
13
|
-
- **Organized**: Modular config structure by category
|
|
14
|
-
|
|
15
|
-
## 📁 Structure
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
UILayout/
|
|
19
|
-
├── config/ # All configuration (Single Source of Truth)
|
|
20
|
-
│ ├── components/ # Component configs by category
|
|
21
|
-
│ │ ├── forms.config.tsx # 8 form components
|
|
22
|
-
│ │ ├── layout.config.tsx # 5 layout components
|
|
23
|
-
│ │ ├── navigation.config.tsx # 4 navigation components
|
|
24
|
-
│ │ ├── overlay.config.tsx # 11 overlay components
|
|
25
|
-
│ │ ├── feedback.config.tsx # 5 feedback components
|
|
26
|
-
│ │ ├── data.config.tsx # 5 data display components
|
|
27
|
-
│ │ ├── specialized.config.tsx # 2 specialized components
|
|
28
|
-
│ │ ├── blocks.config.tsx # 7 landing page blocks
|
|
29
|
-
│ │ ├── hooks.config.tsx # 6 custom hooks
|
|
30
|
-
│ │ └── index.ts # Aggregates all configs
|
|
31
|
-
│ ├── categories.config.tsx # Category definitions
|
|
32
|
-
│ ├── tailwind.config.ts # Tailwind 4 guidelines
|
|
33
|
-
│ ├── ai-export.config.ts # AI context generator
|
|
34
|
-
│ └── index.ts # Main config exports
|
|
35
|
-
├── components/ # React components
|
|
36
|
-
│ ├── AutoComponentDemo.tsx # Auto-renders from config
|
|
37
|
-
│ ├── CategoryRenderer.tsx # Renders entire category
|
|
38
|
-
│ ├── TailwindGuideRenderer.tsx # Renders Tailwind guide
|
|
39
|
-
│ ├── Header.tsx # Header with "Copy for AI"
|
|
40
|
-
│ ├── Sidebar.tsx
|
|
41
|
-
│ └── MobileOverlay.tsx
|
|
42
|
-
├── context/ # React Context
|
|
43
|
-
│ └── ShowcaseContext.tsx # Navigation state management
|
|
44
|
-
├── UILayout.tsx # Main layout component
|
|
45
|
-
├── UIGuideView.tsx # Complete UI guide view
|
|
46
|
-
├── UIGuideLanding.tsx # Landing page
|
|
47
|
-
└── UIGuideApp.tsx # Full app wrapper
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## 🚀 Quick Start
|
|
51
|
-
|
|
52
|
-
### Basic Usage
|
|
53
|
-
|
|
54
|
-
```tsx
|
|
55
|
-
import { UILayout, CATEGORIES } from '@djangocfg/layouts';
|
|
56
|
-
|
|
57
|
-
function MyComponentGuide() {
|
|
58
|
-
const [category, setCategory] = useState('forms');
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<UILayout
|
|
62
|
-
title="My Component Library"
|
|
63
|
-
categories={CATEGORIES}
|
|
64
|
-
currentCategory={category}
|
|
65
|
-
onCategoryChange={setCategory}
|
|
66
|
-
>
|
|
67
|
-
{/* Your content */}
|
|
68
|
-
</UILayout>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Using Category Renderer (Auto-render from config)
|
|
74
|
-
|
|
75
|
-
```tsx
|
|
76
|
-
import { UILayout, CATEGORIES, CategoryRenderer } from '@djangocfg/layouts';
|
|
77
|
-
|
|
78
|
-
function MyComponentGuide() {
|
|
79
|
-
const [category, setCategory] = useState('forms');
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<UILayout
|
|
83
|
-
title="My Component Library"
|
|
84
|
-
categories={CATEGORIES}
|
|
85
|
-
currentCategory={category}
|
|
86
|
-
onCategoryChange={setCategory}
|
|
87
|
-
>
|
|
88
|
-
{/* Automatically renders all components in category */}
|
|
89
|
-
<CategoryRenderer categoryId={category} />
|
|
90
|
-
</UILayout>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Using Complete UI Guide
|
|
96
|
-
|
|
97
|
-
```tsx
|
|
98
|
-
import { UIGuideApp } from '@djangocfg/layouts';
|
|
99
|
-
|
|
100
|
-
// Complete pre-configured UI guide with all components
|
|
101
|
-
export default function Page() {
|
|
102
|
-
return <UIGuideApp />;
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## 📝 Adding New Components
|
|
107
|
-
|
|
108
|
-
### 1. Add to Config
|
|
109
|
-
|
|
110
|
-
Edit the appropriate config file in `config/components/`:
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
// config/components/forms.config.tsx
|
|
114
|
-
export const FORM_COMPONENTS: ComponentConfig[] = [
|
|
115
|
-
{
|
|
116
|
-
name: 'MyComponent',
|
|
117
|
-
category: 'forms',
|
|
118
|
-
description: 'A custom form component',
|
|
119
|
-
importPath: "import { MyComponent } from '@mylib/ui';",
|
|
120
|
-
example: `<MyComponent value="test" onChange={handler} />`,
|
|
121
|
-
preview: <MyComponent value="test" onChange={() => {}} />
|
|
122
|
-
},
|
|
123
|
-
// ... other components
|
|
124
|
-
];
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### 2. That's It!
|
|
128
|
-
|
|
129
|
-
No need to:
|
|
130
|
-
- ❌ Create separate demo files
|
|
131
|
-
- ❌ Write duplicate rendering code
|
|
132
|
-
- ❌ Update multiple places
|
|
133
|
-
|
|
134
|
-
The component automatically:
|
|
135
|
-
- ✅ Appears in the UI guide
|
|
136
|
-
- ✅ Gets included in "Copy for AI"
|
|
137
|
-
- ✅ Shows in the category with proper formatting
|
|
138
|
-
|
|
139
|
-
## 🤖 Copy for AI Feature
|
|
140
|
-
|
|
141
|
-
Click the "Copy for AI" button in the header to export entire documentation including:
|
|
142
|
-
|
|
143
|
-
- ✅ Tailwind CSS v4 guidelines and best practices
|
|
144
|
-
- ✅ All 53 components with full examples
|
|
145
|
-
- ✅ Import statements
|
|
146
|
-
- ✅ Usage examples
|
|
147
|
-
- ✅ Properly formatted for AI consumption
|
|
148
|
-
|
|
149
|
-
Perfect for giving AI assistants complete context about your UI library!
|
|
150
|
-
|
|
151
|
-
## 📊 Component Statistics
|
|
152
|
-
|
|
153
|
-
| Category | Components |
|
|
154
|
-
|----------|------------|
|
|
155
|
-
| Forms | 8 |
|
|
156
|
-
| Layout | 5 |
|
|
157
|
-
| Navigation | 4 |
|
|
158
|
-
| Overlay | 11 |
|
|
159
|
-
| Feedback | 5 |
|
|
160
|
-
| Data Display | 5 |
|
|
161
|
-
| Specialized | 2 |
|
|
162
|
-
| Blocks | 7 |
|
|
163
|
-
| Hooks | 6 |
|
|
164
|
-
| **Total** | **53** |
|
|
165
|
-
|
|
166
|
-
## 🎨 Customization
|
|
167
|
-
|
|
168
|
-
### Custom Categories
|
|
169
|
-
|
|
170
|
-
```tsx
|
|
171
|
-
import type { ComponentCategory } from '@djangocfg/layouts';
|
|
172
|
-
|
|
173
|
-
const customCategories: ComponentCategory[] = [
|
|
174
|
-
{
|
|
175
|
-
id: 'custom',
|
|
176
|
-
label: 'My Category',
|
|
177
|
-
icon: <MyIcon />,
|
|
178
|
-
count: 5,
|
|
179
|
-
description: 'Custom component category'
|
|
180
|
-
}
|
|
181
|
-
];
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Custom Config
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
|
-
import type { ComponentConfig } from '@djangocfg/layouts';
|
|
188
|
-
|
|
189
|
-
const customComponents: ComponentConfig[] = [
|
|
190
|
-
{
|
|
191
|
-
name: 'Component',
|
|
192
|
-
category: 'custom',
|
|
193
|
-
description: '...',
|
|
194
|
-
importPath: '...',
|
|
195
|
-
example: '...',
|
|
196
|
-
preview: <Component />
|
|
197
|
-
}
|
|
198
|
-
];
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
## 🔧 API Reference
|
|
202
|
-
|
|
203
|
-
### UILayout Props
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
interface UILayoutProps {
|
|
207
|
-
children: ReactNode;
|
|
208
|
-
title?: string;
|
|
209
|
-
description?: string;
|
|
210
|
-
categories: ComponentCategory[];
|
|
211
|
-
currentCategory?: string;
|
|
212
|
-
onCategoryChange?: (categoryId: string) => void;
|
|
213
|
-
logo?: ReactNode;
|
|
214
|
-
projectName?: string;
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### ComponentConfig Type
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
interface ComponentConfig {
|
|
222
|
-
name: string; // Component name
|
|
223
|
-
category: string; // Category ID
|
|
224
|
-
description: string; // Short description
|
|
225
|
-
importPath: string; // Import statement
|
|
226
|
-
example: string; // Code example
|
|
227
|
-
preview: ReactNode; // Live preview component
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Available Exports
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
// Components
|
|
235
|
-
export { UILayout, CategoryRenderer, AutoComponentDemo };
|
|
236
|
-
|
|
237
|
-
// Views
|
|
238
|
-
export { UIGuideView, UIGuideApp, UIGuideLanding };
|
|
239
|
-
|
|
240
|
-
// Config
|
|
241
|
-
export {
|
|
242
|
-
CATEGORIES,
|
|
243
|
-
COMPONENTS_CONFIG,
|
|
244
|
-
generateAIContext
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// Types
|
|
248
|
-
export type {
|
|
249
|
-
ComponentConfig,
|
|
250
|
-
ComponentCategory,
|
|
251
|
-
UILayoutProps
|
|
252
|
-
};
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## 🎯 Benefits
|
|
256
|
-
|
|
257
|
-
1. **Single Source of Truth**: All component data in one place
|
|
258
|
-
2. **Zero Duplication**: Write component config once, use everywhere
|
|
259
|
-
3. **Type-Safe**: Full TypeScript support prevents errors
|
|
260
|
-
4. **Auto-Update**: Add to config, automatically appears everywhere
|
|
261
|
-
5. **AI-Friendly**: Built-in export for AI assistants
|
|
262
|
-
6. **Maintainable**: Easy to update and extend
|
|
263
|
-
7. **Scalable**: Add categories and components effortlessly
|
|
264
|
-
|
|
265
|
-
## 📖 More Info
|
|
266
|
-
|
|
267
|
-
See the main package README for complete documentation and examples.
|