From b63c22ef9f7d3c1fe797da26abe2ecbec65189be Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 19 Oct 2025 10:16:47 +1100 Subject: [PATCH] chore: rename skins, minor style tweaks (#61) --- examples/react-demo/index.html | 2 +- examples/react-demo/package.json | 2 + examples/react-demo/src/App.tsx | 65 +++++--- .../react-demo/src/hooks/useFullscreen.ts | 132 +++++++++++++++ .../{MediaSkinDefault.tsx => FrostedSkin.tsx} | 2 +- .../react-demo/src/skins/frosted/styles.ts | 4 +- .../react-demo/src/skins/frosted/types.ts | 2 +- .../MinimalSkin.tsx} | 2 +- .../src/skins/{toasted => minimal}/styles.ts | 4 +- .../src/skins/{toasted => minimal}/types.ts | 2 +- .../react-demo/src/skins/toasted/index.ts | 1 - .../react/react/src/skins/default/index.ts | 1 - .../FrostedSkin.tsx} | 2 +- .../react/react/src/skins/frosted/index.ts | 1 + .../src/skins/{default => frosted}/styles.ts | 10 +- .../src/skins/{default => frosted}/types.ts | 2 +- packages/react/react/src/skins/index.ts | 4 +- .../MinimalSkin.tsx} | 2 +- .../react/react/src/skins/minimal/index.ts | 1 + .../src/skins/{toasted => minimal}/styles.ts | 14 +- .../src/skins/{toasted => minimal}/types.ts | 2 +- .../react/react/src/skins/toasted/index.ts | 1 - pnpm-lock.yaml | 154 ++++++++++-------- 23 files changed, 292 insertions(+), 120 deletions(-) create mode 100644 examples/react-demo/src/hooks/useFullscreen.ts rename examples/react-demo/src/skins/frosted/{MediaSkinDefault.tsx => FrostedSkin.tsx} (98%) rename examples/react-demo/src/skins/{toasted/MediaSkinToasted.tsx => minimal/MinimalSkin.tsx} (96%) rename examples/react-demo/src/skins/{toasted => minimal}/styles.ts (98%) rename examples/react-demo/src/skins/{toasted => minimal}/types.ts (95%) delete mode 100644 examples/react-demo/src/skins/toasted/index.ts delete mode 100644 packages/react/react/src/skins/default/index.ts rename packages/react/react/src/skins/{default/MediaSkinDefault.tsx => frosted/FrostedSkin.tsx} (98%) create mode 100644 packages/react/react/src/skins/frosted/index.ts rename packages/react/react/src/skins/{default => frosted}/styles.ts (97%) rename packages/react/react/src/skins/{default => frosted}/types.ts (96%) rename packages/react/react/src/skins/{toasted/MediaSkinToasted.tsx => minimal/MinimalSkin.tsx} (98%) create mode 100644 packages/react/react/src/skins/minimal/index.ts rename packages/react/react/src/skins/{toasted => minimal}/styles.ts (95%) rename packages/react/react/src/skins/{toasted => minimal}/types.ts (96%) delete mode 100644 packages/react/react/src/skins/toasted/index.ts diff --git a/examples/react-demo/index.html b/examples/react-demo/index.html index e6d535d5..7a6a9840 100644 --- a/examples/react-demo/index.html +++ b/examples/react-demo/index.html @@ -6,7 +6,7 @@ - +
diff --git a/examples/react-demo/package.json b/examples/react-demo/package.json index fad175f5..5096ba3a 100644 --- a/examples/react-demo/package.json +++ b/examples/react-demo/package.json @@ -12,8 +12,10 @@ "dependencies": { "@vjs-10/react": "workspace:*", "@vjs-10/react-icons": "workspace:*", + "clsx": "^2.1.1", "react": "^18.0.0", "react-dom": "^18.0.0", + "screenfull": "^6.0.2", "tailwindcss": "^4.1.13" }, "devDependencies": { diff --git a/examples/react-demo/src/App.tsx b/examples/react-demo/src/App.tsx index 6e11a9b2..20594042 100644 --- a/examples/react-demo/src/App.tsx +++ b/examples/react-demo/src/App.tsx @@ -1,25 +1,28 @@ import type { ChangeEventHandler } from 'react'; -import { MediaProvider, MediaSkinDefault, MediaSkinToasted, Video } from '@vjs-10/react'; +import { FrostedSkin, MediaProvider, MinimalSkin, Video } from '@vjs-10/react'; +import { FullscreenEnterAltIcon, FullscreenExitAltIcon } from '@vjs-10/react-icons'; + +// import FrostedSkin from './skins/frosted/FrostedSkin'; +// import MinimalSkin from './skins/toasted/MinimalSkin'; + +import clsx from 'clsx'; // NOTE: Commented out imports are for testing locally/externally defined skins. // import { MediaProvider, Video } from '@vjs-10/react'; -import { useCallback, useMemo, useState } from 'react'; - -// import MediaSkinDefault from './skins/frosted/MediaSkinDefault'; -// import MediaSkinToasted from './skins/toasted/MediaSkinToasted'; - +import { useCallback, useMemo, useRef, useState } from 'react'; +import { useFullscreen } from './hooks/useFullscreen'; import './globals.css'; const skins = [ { - key: 'default', + key: 'frosted', name: 'Frosted', - component: MediaSkinDefault, + component: FrostedSkin, }, { - key: 'toasted', - name: 'Toasted', - component: MediaSkinToasted, + key: 'minimal', + name: 'Minimal', + component: MinimalSkin, }, ] as const; @@ -62,12 +65,14 @@ function setParam(key: string, value: string) { window.history.replaceState(null, '', url); } -const DEFAULT_SKIN: SkinKey = 'toasted'; +const DEFAULT_SKIN: SkinKey = 'frosted'; const DEFAULT_MEDIA_SOURCE: MediaSourceKey = '1'; export default function App(): JSX.Element { const [skinKey, setSkinKey] = useState(() => getParam('skin', DEFAULT_SKIN)); const [mediaSourceKey, setMediaSourceKey] = useState(() => getParam('source', DEFAULT_MEDIA_SOURCE)); + const containerRef = useRef(null); + const { isFullscreen, toggle: toggleFullscreen } = useFullscreen(containerRef); const mediaSource = useMemo(() => { let match = mediaSources.find(m => m.key === mediaSourceKey); @@ -103,10 +108,10 @@ export default function App(): JSX.Element { const skinClassName = useMemo(() => { switch (skinKey) { - case 'default': + case 'frosted': return 'rounded-4xl shadow shadow-lg shadow-black/15'; - case 'toasted': - return 'rounded-lg shadow shadow-lg shadow-black/15'; + case 'minimal': + return 'rounded-2xl shadow shadow-lg shadow-black/15'; default: return ''; } @@ -116,8 +121,20 @@ export default function App(): JSX.Element { const poster = playbackId ? `https://image.mux.com/${playbackId}/thumbnail.webp` : undefined; return ( - <> -
+
+
-
+
@@ -161,6 +188,6 @@ export default function App(): JSX.Element {
- +
); } diff --git a/examples/react-demo/src/hooks/useFullscreen.ts b/examples/react-demo/src/hooks/useFullscreen.ts new file mode 100644 index 00000000..94768ce5 --- /dev/null +++ b/examples/react-demo/src/hooks/useFullscreen.ts @@ -0,0 +1,132 @@ +import type { RefObject } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import screenfull from 'screenfull'; + +export interface UseFullscreenOptions { + /** + * When true, automatically handles fullscreen change events + * to synchronize the state with browser fullscreen status + * @default true + */ + trackChanges?: boolean; + /** + * Callback fired when fullscreen state changes + */ + onFullscreenChange?: (isFullscreen: boolean) => void; + /** + * Callback fired when fullscreen encounters an error + */ + onFullscreenError?: (error: Error) => void; +} + +export interface UseFullscreenResult { + /** + * Whether the element is currently in fullscreen mode + */ + isFullscreen: boolean; + /** + * Whether fullscreen is supported in the current environment + */ + isSupported: boolean; + /** + * Request fullscreen for the target element + * @returns Promise that resolves when fullscreen is entered + */ + enter: () => Promise; + /** + * Exit fullscreen mode + * @returns Promise that resolves when fullscreen is exited + */ + exit: () => Promise; + /** + * Toggle fullscreen mode for the target element + * @returns Promise that resolves when the toggle is complete + */ + toggle: () => Promise; +} + +/** + * Hook that provides fullscreen functionality for a DOM element + * + * @param elementRef - Ref to the element to make fullscreen + * @param options - Configuration options + * @returns Methods and state for managing fullscreen + */ +export function useFullscreen( + elementRef: RefObject, + options: UseFullscreenOptions = {}, +): UseFullscreenResult { + const { + trackChanges = true, + onFullscreenChange, + onFullscreenError, + } = options; + + const [isFullscreen, setIsFullscreen] = useState( + screenfull.isEnabled ? screenfull.isFullscreen : false, + ); + + // Handle fullscreen change events + useEffect(() => { + if (!screenfull.isEnabled || !trackChanges) return; + + const handleChange = () => { + const newIsFullscreen = screenfull.isFullscreen; + setIsFullscreen(newIsFullscreen); + onFullscreenChange?.(newIsFullscreen); + }; + + const handleError = () => { + onFullscreenError?.(new Error('Fullscreen error occurred')); + }; + + screenfull.on('change', handleChange); + screenfull.on('error', handleError); + + return () => { + screenfull.off('change', handleChange); + screenfull.off('error', handleError); + }; + }, [trackChanges, onFullscreenChange, onFullscreenError]); + + // Request fullscreen for the target element + const enter = useCallback(async (): Promise => { + if (!screenfull.isEnabled || !elementRef.current) return; + + try { + await screenfull.request(elementRef.current); + } catch (error) { + onFullscreenError?.(error instanceof Error ? error : new Error(String(error))); + } + }, [elementRef, onFullscreenError]); + + // Exit fullscreen mode + const exit = useCallback(async (): Promise => { + if (!screenfull.isEnabled) return; + + try { + await screenfull.exit(); + } catch (error) { + onFullscreenError?.(error instanceof Error ? error : new Error(String(error))); + } + }, [onFullscreenError]); + + // Toggle fullscreen mode for the target element + const toggle = useCallback(async (): Promise => { + if (!screenfull.isEnabled || !elementRef.current) return; + + try { + await screenfull.toggle(elementRef.current); + } catch (error) { + onFullscreenError?.(error instanceof Error ? error : new Error(String(error))); + } + }, [elementRef, onFullscreenError]); + + return { + isFullscreen, + isSupported: screenfull.isEnabled, + enter, + exit, + toggle, + }; +} diff --git a/examples/react-demo/src/skins/frosted/MediaSkinDefault.tsx b/examples/react-demo/src/skins/frosted/FrostedSkin.tsx similarity index 98% rename from examples/react-demo/src/skins/frosted/MediaSkinDefault.tsx rename to examples/react-demo/src/skins/frosted/FrostedSkin.tsx index d1c70265..177b5ef5 100644 --- a/examples/react-demo/src/skins/frosted/MediaSkinDefault.tsx +++ b/examples/react-demo/src/skins/frosted/FrostedSkin.tsx @@ -29,7 +29,7 @@ type SkinProps = PropsWithChildren<{ className?: string; }>; -export default function MediaSkinDefault({ children, className = '' }: SkinProps): JSX.Element { +export default function FrostedSkin({ children, className = '' }: SkinProps): JSX.Element { return ( {children} diff --git a/examples/react-demo/src/skins/frosted/styles.ts b/examples/react-demo/src/skins/frosted/styles.ts index b17c3770..9acd533c 100644 --- a/examples/react-demo/src/skins/frosted/styles.ts +++ b/examples/react-demo/src/skins/frosted/styles.ts @@ -1,4 +1,4 @@ -import type { MediaDefaultSkinStyles } from './types'; +import type { FrostedSkinStyles } from './types'; // NOTE: Removing import to sidestep for compiler complexity (CJP) // import { cn } from '../../utils/cn'; @@ -9,7 +9,7 @@ function cn(...classes: (string | undefined)[]): string { return classes.filter(Boolean).join(' '); } -const styles: MediaDefaultSkinStyles = { +const styles: FrostedSkinStyles = { MediaContainer: cn( 'relative @container/root group/root overflow-clip', // Base typography diff --git a/examples/react-demo/src/skins/frosted/types.ts b/examples/react-demo/src/skins/frosted/types.ts index bfed7182..c526b672 100644 --- a/examples/react-demo/src/skins/frosted/types.ts +++ b/examples/react-demo/src/skins/frosted/types.ts @@ -1,4 +1,4 @@ -export interface MediaDefaultSkinStyles { +export interface FrostedSkinStyles { readonly MediaContainer: string; readonly Overlay: string; readonly Controls: string; diff --git a/examples/react-demo/src/skins/toasted/MediaSkinToasted.tsx b/examples/react-demo/src/skins/minimal/MinimalSkin.tsx similarity index 96% rename from examples/react-demo/src/skins/toasted/MediaSkinToasted.tsx rename to examples/react-demo/src/skins/minimal/MinimalSkin.tsx index 87b6a266..ca7b309f 100644 --- a/examples/react-demo/src/skins/toasted/MediaSkinToasted.tsx +++ b/examples/react-demo/src/skins/minimal/MinimalSkin.tsx @@ -26,7 +26,7 @@ type SkinProps = PropsWithChildren<{ className?: string; }>; -export default function MediaSkinToasted({ children, className = '' }: SkinProps): JSX.Element { +export default function MinimalSkin({ children, className = '' }: SkinProps): JSX.Element { return ( {children} diff --git a/examples/react-demo/src/skins/toasted/styles.ts b/examples/react-demo/src/skins/minimal/styles.ts similarity index 98% rename from examples/react-demo/src/skins/toasted/styles.ts rename to examples/react-demo/src/skins/minimal/styles.ts index fbc2f444..069c7ee2 100644 --- a/examples/react-demo/src/skins/toasted/styles.ts +++ b/examples/react-demo/src/skins/minimal/styles.ts @@ -1,4 +1,4 @@ -import type { MediaToastedSkinStyles } from './types'; +import type { MinimalSkinStyles } from './types'; // NOTE: Removing import to sidestep for compiler complexity (CJP) // import { cn } from '../../utils/cn'; @@ -9,7 +9,7 @@ function cn(...classes: (string | undefined)[]): string { return classes.filter(Boolean).join(' '); } -const styles: MediaToastedSkinStyles = { +const styles: MinimalSkinStyles = { MediaContainer: cn( 'relative @container/root group/root overflow-clip bg-black', // Base typography diff --git a/examples/react-demo/src/skins/toasted/types.ts b/examples/react-demo/src/skins/minimal/types.ts similarity index 95% rename from examples/react-demo/src/skins/toasted/types.ts rename to examples/react-demo/src/skins/minimal/types.ts index 3cf00216..593ee067 100644 --- a/examples/react-demo/src/skins/toasted/types.ts +++ b/examples/react-demo/src/skins/minimal/types.ts @@ -1,4 +1,4 @@ -export interface MediaToastedSkinStyles { +export interface MinimalSkinStyles { readonly MediaContainer: string; readonly Overlay: string; readonly Controls: string; diff --git a/examples/react-demo/src/skins/toasted/index.ts b/examples/react-demo/src/skins/toasted/index.ts deleted file mode 100644 index f09f4f82..00000000 --- a/examples/react-demo/src/skins/toasted/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MediaSkinToasted } from './MediaSkinToasted'; diff --git a/packages/react/react/src/skins/default/index.ts b/packages/react/react/src/skins/default/index.ts deleted file mode 100644 index 09ab0fb4..00000000 --- a/packages/react/react/src/skins/default/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MediaSkinDefault } from './MediaSkinDefault'; diff --git a/packages/react/react/src/skins/default/MediaSkinDefault.tsx b/packages/react/react/src/skins/frosted/FrostedSkin.tsx similarity index 98% rename from packages/react/react/src/skins/default/MediaSkinDefault.tsx rename to packages/react/react/src/skins/frosted/FrostedSkin.tsx index 5f1639ea..2eb2a16c 100644 --- a/packages/react/react/src/skins/default/MediaSkinDefault.tsx +++ b/packages/react/react/src/skins/frosted/FrostedSkin.tsx @@ -27,7 +27,7 @@ type SkinProps = PropsWithChildren<{ className?: string; }>; -export default function MediaSkinDefault({ children, className = '' }: SkinProps): JSX.Element { +export default function FrostedSkin({ children, className = '' }: SkinProps): JSX.Element { return ( {children} diff --git a/packages/react/react/src/skins/frosted/index.ts b/packages/react/react/src/skins/frosted/index.ts new file mode 100644 index 00000000..88df44a3 --- /dev/null +++ b/packages/react/react/src/skins/frosted/index.ts @@ -0,0 +1 @@ +export { default as FrostedSkin } from './FrostedSkin'; diff --git a/packages/react/react/src/skins/default/styles.ts b/packages/react/react/src/skins/frosted/styles.ts similarity index 97% rename from packages/react/react/src/skins/default/styles.ts rename to packages/react/react/src/skins/frosted/styles.ts index 17eba036..c06db070 100644 --- a/packages/react/react/src/skins/default/styles.ts +++ b/packages/react/react/src/skins/frosted/styles.ts @@ -1,4 +1,4 @@ -import type { MediaDefaultSkinStyles } from './types'; +import type { FrostedSkinStyles } from './types'; // NOTE: Removing import to sidestep for compiler complexity (CJP) // import { cn } from '../../utils/cn'; @@ -9,16 +9,16 @@ function cn(...classes: (string | undefined)[]): string { return classes.filter(Boolean).join(' '); } -const styles: MediaDefaultSkinStyles = { +const styles: FrostedSkinStyles = { MediaContainer: cn( - 'relative @container/root group/root overflow-clip', + 'relative isolate @container/root group/root overflow-clip', // Base typography 'text-[0.8125rem]', // 13px // Prevent rounded corners in fullscreen. '[&:fullscreen]:rounded-none [&:fullscreen]:[&_video]:h-full [&:fullscreen]:[&_video]:w-full', // Fancy borders. - 'after:absolute after:inset-0 after:ring-black/10 after:ring-1 dark:after:ring-black/40 after:ring-inset after:z-10 after:pointer-events-none after:rounded-[inherit]', - 'before:absolute before:inset-px before:rounded-[inherit] before:ring-white/15 before:ring-1 before:ring-inset before:z-10 before:pointer-events-none', + 'after:absolute after:inset-0 after:ring-black/10 after:ring-1 dark:after:ring-white/10 after:ring-inset after:z-10 after:pointer-events-none after:rounded-[inherit]', + 'before:absolute before:inset-px before:rounded-[inherit] before:ring-white/15 before:ring-1 before:ring-inset before:z-10 before:pointer-events-none dark:before:ring-0', // Ensure the nested video inherits the radius. '[&_video]:rounded-[inherit] [&_video]:w-full [&_video]:h-auto', ), diff --git a/packages/react/react/src/skins/default/types.ts b/packages/react/react/src/skins/frosted/types.ts similarity index 96% rename from packages/react/react/src/skins/default/types.ts rename to packages/react/react/src/skins/frosted/types.ts index 3b9d1eaf..c5f5896f 100644 --- a/packages/react/react/src/skins/default/types.ts +++ b/packages/react/react/src/skins/frosted/types.ts @@ -1,4 +1,4 @@ -export interface MediaDefaultSkinStyles { +export interface FrostedSkinStyles { readonly MediaContainer: string; readonly Overlay: string; readonly Controls: string; diff --git a/packages/react/react/src/skins/index.ts b/packages/react/react/src/skins/index.ts index 885060fc..88c5e35f 100644 --- a/packages/react/react/src/skins/index.ts +++ b/packages/react/react/src/skins/index.ts @@ -1,2 +1,2 @@ -export * from './default'; -export * from './toasted'; +export * from './frosted'; +export * from './minimal'; diff --git a/packages/react/react/src/skins/toasted/MediaSkinToasted.tsx b/packages/react/react/src/skins/minimal/MinimalSkin.tsx similarity index 98% rename from packages/react/react/src/skins/toasted/MediaSkinToasted.tsx rename to packages/react/react/src/skins/minimal/MinimalSkin.tsx index 37ef4502..c942dcf1 100644 --- a/packages/react/react/src/skins/toasted/MediaSkinToasted.tsx +++ b/packages/react/react/src/skins/minimal/MinimalSkin.tsx @@ -27,7 +27,7 @@ type SkinProps = PropsWithChildren<{ className?: string; }>; -export default function MediaSkinDefault({ children, className = '' }: SkinProps): JSX.Element { +export default function MinimalSkin({ children, className = '' }: SkinProps): JSX.Element { return ( {children} diff --git a/packages/react/react/src/skins/minimal/index.ts b/packages/react/react/src/skins/minimal/index.ts new file mode 100644 index 00000000..2e17db8c --- /dev/null +++ b/packages/react/react/src/skins/minimal/index.ts @@ -0,0 +1 @@ +export { default as MinimalSkin } from './MinimalSkin'; diff --git a/packages/react/react/src/skins/toasted/styles.ts b/packages/react/react/src/skins/minimal/styles.ts similarity index 95% rename from packages/react/react/src/skins/toasted/styles.ts rename to packages/react/react/src/skins/minimal/styles.ts index 12b15465..16265eb5 100644 --- a/packages/react/react/src/skins/toasted/styles.ts +++ b/packages/react/react/src/skins/minimal/styles.ts @@ -1,4 +1,4 @@ -import type { MediaToastedSkinStyles } from './types'; +import type { MinimalSkinStyles } from './types'; // NOTE: Removing import to sidestep for compiler complexity (CJP) // import { cn } from '../../utils/cn'; @@ -9,9 +9,9 @@ function cn(...classes: (string | undefined)[]): string { return classes.filter(Boolean).join(' '); } -const styles: MediaToastedSkinStyles = { +const styles: MinimalSkinStyles = { MediaContainer: cn( - 'relative @container/root group/root overflow-clip bg-black', + 'relative isolate @container/root group/root overflow-clip bg-black', // Base typography 'text-[0.8125rem]', // 13px // 'ring-1 ring-inset ring-black/10 dark:ring-white/10', @@ -22,8 +22,8 @@ const styles: MediaToastedSkinStyles = { '[&_video]:rounded-[inherit] [&_video]:w-full [&_video]:h-auto', ), Overlay: cn( - 'absolute inset-0 rounded-[inherit] bg-black/30', - 'bg-gradient-to-t from-black/30 to-transparent to-[120px]', + 'absolute inset-0 rounded-[inherit]', + 'bg-gradient-to-t from-black/70 via-black/50 via-[120px] to-transparent', 'opacity-0 delay-500 duration-300', // FIXME: Temporary hide/show logic 'has-[+.controls_[data-paused]]:opacity-100 has-[+.controls_[data-paused]]:delay-0 has-[+.controls_[data-paused]]:duration-75', @@ -125,8 +125,8 @@ const styles: MediaToastedSkinStyles = { ), SliderTrack: cn( 'relative select-none rounded-full bg-white/10 transition-[height,width] ease-in-out', - '[&[data-orientation="horizontal"]]:w-full [&[data-orientation="horizontal"]]:h-0.5', - '[&[data-orientation="vertical"]]:w-0.5', + '[&[data-orientation="horizontal"]]:w-full [&[data-orientation="horizontal"]]:h-[0.1875rem]', + '[&[data-orientation="vertical"]]:w-[0.1875rem]', ), SliderProgress: cn('bg-white rounded-[inherit]'), SliderPointer: cn('hidden'), diff --git a/packages/react/react/src/skins/toasted/types.ts b/packages/react/react/src/skins/minimal/types.ts similarity index 96% rename from packages/react/react/src/skins/toasted/types.ts rename to packages/react/react/src/skins/minimal/types.ts index 307320ac..f3f932ea 100644 --- a/packages/react/react/src/skins/toasted/types.ts +++ b/packages/react/react/src/skins/minimal/types.ts @@ -1,4 +1,4 @@ -export interface MediaToastedSkinStyles { +export interface MinimalSkinStyles { readonly MediaContainer: string; readonly Overlay: string; readonly Controls: string; diff --git a/packages/react/react/src/skins/toasted/index.ts b/packages/react/react/src/skins/toasted/index.ts deleted file mode 100644 index f09f4f82..00000000 --- a/packages/react/react/src/skins/toasted/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MediaSkinToasted } from './MediaSkinToasted'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f1848bc..ced8d7bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,12 +84,18 @@ importers: '@vjs-10/react-icons': specifier: workspace:* version: link:../../packages/react/react-icons + clsx: + specifier: ^2.1.1 + version: 2.1.1 react: specifier: ^18.0.0 version: 18.3.1 react-dom: specifier: ^18.0.0 version: 18.3.1(react@18.3.1) + screenfull: + specifier: ^6.0.2 + version: 6.0.2 tailwindcss: specifier: ^4.1.13 version: 4.1.14 @@ -1093,8 +1099,8 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} - '@napi-rs/wasm-runtime@1.0.6': - resolution: {integrity: sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g==} + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1133,89 +1139,89 @@ packages: '@quansync/fs@0.1.5': resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} - '@rolldown/binding-android-arm64@1.0.0-beta.42': - resolution: {integrity: sha512-W5ZKF3TP3bOWuBfotAGp+UGjxOkGV7jRmIRbBA7NFjggx7Oi6vOmGDqpHEIX7kDCiry1cnIsWQaxNvWbMdkvzQ==} + '@rolldown/binding-android-arm64@1.0.0-beta.43': + resolution: {integrity: sha512-TP8bcPOb1s6UmY5syhXrDn9k0XkYcw+XaoylTN4cJxf0JOVS2j682I3aTcpfT51hOFGr2bRwNKN9RZ19XxeQbA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-beta.42': - resolution: {integrity: sha512-abw/wtgJA8OCgaTlL+xJxnN/Z01BwV1rfzIp5Hh9x+IIO6xOBfPsQ0nzi0+rWx3TyZ9FZXyC7bbC+5NpQ9EaXQ==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.43': + resolution: {integrity: sha512-kuVWnZsE4vEjMF/10SbSUyzucIW2zmdsqFghYMqy+fsjXnRHg0luTU6qWF8IqJf4Cbpm9NEZRnjIEPpAbdiSNQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.42': - resolution: {integrity: sha512-Y/UrZIRVr8CvXVEB88t6PeC46r1K9/QdPEo2ASE/b/KBEyXIx+QbM6kv9QfQVWU2Atly2+SVsQzxQsIvuk3lZQ==} + '@rolldown/binding-darwin-x64@1.0.0-beta.43': + resolution: {integrity: sha512-u9Ps4sh6lcmJ3vgLtyEg/x4jlhI64U0mM93Ew+tlfFdLDe7yKyA+Fe80cpr2n1mNCeZXrvTSbZluKpXQ0GxLjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.42': - resolution: {integrity: sha512-zRM0oOk7BZiy6DoWBvdV4hyEg+j6+WcBZIMHVirMEZRu8hd18kZdJkg+bjVMfCEhwpWeFUfBfZ1qcaZ5UdYzlQ==} + '@rolldown/binding-freebsd-x64@1.0.0-beta.43': + resolution: {integrity: sha512-h9lUtVtXgfbk/tnicMpbFfZ3DJvk5Zn2IvmlC1/e0+nUfwoc/TFqpfrRRqcNBXk/e+xiWMSKv6b0MF8N+Rtvlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.42': - resolution: {integrity: sha512-6RjFaC52QNwo7ilU8C5H7swbGlgfTkG9pudXwzr3VYyT18s0C9gLg3mvc7OMPIGqNxnQ0M5lU8j6aQCk2DTRVg==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': + resolution: {integrity: sha512-IX2C6bA6wM2rX/RvD75ko+ix9yxPKjKGGq7pOhB8wGI4Z4fqX5B1nDHga/qMDmAdCAR1m9ymzxkmqhm/AFYf7A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.42': - resolution: {integrity: sha512-LMYHM5Sf6ROq+VUwHMDVX2IAuEsWTv4SnlFEedBnMGpvRuQ14lCmD4m5Q8sjyAQCgyha9oghdGoK8AEg1sXZKg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': + resolution: {integrity: sha512-mcjd57vEj+CEQbZAzUiaxNzNgwwgOpFtZBWcINm8DNscvkXl5b/s622Z1dqGNWSdrZmdjdC6LWMvu8iHM6v9sQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.42': - resolution: {integrity: sha512-/bNTYb9aKNhzdbPn3O4MK2aLv55AlrkUKPE4KNfBYjkoZUfDr4jWp7gsSlvTc5A/99V1RCm9axvt616ZzeXGyA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': + resolution: {integrity: sha512-Pa8QMwlkrztTo/1mVjZmPIQ44tCSci10TBqxzVBvXVA5CFh5EpiEi99fPSll2dHG2uT4dCOMeC6fIhyDdb0zXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.42': - resolution: {integrity: sha512-n/SLa4h342oyeGykZdch7Y3GNCNliRPL4k5wkeZ/5eQZs+c6/ZG1SHCJQoy7bZcmxiMyaXs9HoFmv1PEKrZgWg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': + resolution: {integrity: sha512-BgynXKMjeaX4AfWLARhOKDetBOOghnSiVRjAHVvhiAaDXgdQN8e65mSmXRiVoVtD3cHXx/cfU8Gw0p0K+qYKVQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.42': - resolution: {integrity: sha512-4PSd46sFzqpLHSGdaSViAb1mk55sCUMpJg+X8ittXaVocQsV3QLG/uydSH8RyL0ngHX5fy3D70LcCzlB15AgHw==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': + resolution: {integrity: sha512-VIsoPlOB/tDSAw9CySckBYysoIBqLeps1/umNSYUD8pMtalJyzMTneAVI1HrUdf4ceFmQ5vARoLIXSsPwVFxNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.42': - resolution: {integrity: sha512-BmWoeJJyeZXmZBcfoxG6J9+rl2G7eO47qdTkAzEegj4n3aC6CBIHOuDcbE8BvhZaEjQR0nh0nJrtEDlt65Q7Sw==} + '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': + resolution: {integrity: sha512-YDXTxVJG67PqTQMKyjVJSddoPbSWJ4yRz/E3xzTLHqNrTDGY0UuhG8EMr8zsYnfH/0cPFJ3wjQd/hJWHuR6nkA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.42': - resolution: {integrity: sha512-2Ft32F7uiDTrGZUKws6CLNTlvTWHC33l4vpXrzUucf9rYtUThAdPCOt89Pmn13tNX6AulxjGEP2R0nZjTSW3eQ==} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': + resolution: {integrity: sha512-3M+2DmorXvDuAIGYQ9Z93Oy1G9ETkejLwdXXb1uRTgKN9pMcu7N+KG2zDrJwqyxeeLIFE22AZGtSJm3PJbNu9Q==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.42': - resolution: {integrity: sha512-hC1kShXW/z221eG+WzQMN06KepvPbMBknF0iGR3VMYJLOe9gwnSTfGxFT5hf8XrPv7CEZqTWRd0GQpkSHRbGsw==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': + resolution: {integrity: sha512-/B1j1pJs33y9ywtslOMxryUPHq8zIGu/OGEc2gyed0slimJ8fX2uR/SaJVhB4+NEgCFIeYDR4CX6jynAkeRuCA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.42': - resolution: {integrity: sha512-AICBYromawouGjj+GS33369E8Vwhy6UwhQEhQ5evfS8jPCsyVvoICJatbDGDGH01dwtVGLD5eDFzPicUOVpe4g==} + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': + resolution: {integrity: sha512-29oG1swCz7hNP+CQYrsM4EtylsKwuYzM8ljqbqC5TsQwmKat7P8ouDpImsqg/GZxFSXcPP9ezQm0Q0wQwGM3JA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.42': - resolution: {integrity: sha512-XpZ0M+tjoEiSc9c+uZR7FCnOI0uxDRNs1elGOMjeB0pUP1QmvVbZGYNsyLbLoP4u7e3VQN8rie1OQ8/mB6rcJg==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': + resolution: {integrity: sha512-eWBV1Ef3gfGNehxVGCyXs7wLayRIgCmyItuCZwYYXW5bsk4EvR4n2GP5m3ohjnx7wdiY3nLmwQfH2Knb5gbNZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1226,8 +1232,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.35': resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==} - '@rolldown/pluginutils@1.0.0-beta.42': - resolution: {integrity: sha512-N7pQzk9CyE7q0bBN/q0J8s6Db279r5kUZc6d7/wWRe9/zXqC52HQovVyu6iXPIDY4BEzzgbVLhVFXrOuGJ22ZQ==} + '@rolldown/pluginutils@1.0.0-beta.43': + resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==} '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} @@ -3921,8 +3927,8 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.42: - resolution: {integrity: sha512-xaPcckj+BbJhYLsv8gOqezc8EdMcKKe/gk8v47B0KPvgABDrQ0qmNPAiT/gh9n9Foe0bUkEv2qzj42uU5q1WRg==} + rolldown@1.0.0-beta.43: + resolution: {integrity: sha512-6RcqyRx0tY1MlRLnjXPp/849Rl/CPFhzpGGwNPEPjKwqBMqPq/Rbbkxasa8s0x+IkUk46ty4jazb5skZ/Vgdhw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3952,6 +3958,10 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + screenfull@6.0.2: + resolution: {integrity: sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==} + engines: {node: ^14.13.1 || >=16.0.0} + scslre@0.3.0: resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} engines: {node: ^14.0.0 || >=16.0.0} @@ -5450,7 +5460,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@napi-rs/wasm-runtime@1.0.6': + '@napi-rs/wasm-runtime@1.0.7': dependencies: '@emnapi/core': 1.5.0 '@emnapi/runtime': 1.5.0 @@ -5488,55 +5498,55 @@ snapshots: dependencies: quansync: 0.2.11 - '@rolldown/binding-android-arm64@1.0.0-beta.42': + '@rolldown/binding-android-arm64@1.0.0-beta.43': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.42': + '@rolldown/binding-darwin-arm64@1.0.0-beta.43': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.42': + '@rolldown/binding-darwin-x64@1.0.0-beta.43': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.42': + '@rolldown/binding-freebsd-x64@1.0.0-beta.43': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.42': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.42': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.42': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.42': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.42': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.42': + '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.42': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': dependencies: - '@napi-rs/wasm-runtime': 1.0.6 + '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.42': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.42': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.42': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': optional: true '@rolldown/pluginutils@1.0.0-beta.27': {} '@rolldown/pluginutils@1.0.0-beta.35': {} - '@rolldown/pluginutils@1.0.0-beta.42': {} + '@rolldown/pluginutils@1.0.0-beta.43': {} '@rollup/pluginutils@5.3.0(rollup@4.52.0)': dependencies: @@ -8932,7 +8942,7 @@ snapshots: rfdc@1.4.1: {} - rolldown-plugin-dts@0.16.7(rolldown@1.0.0-beta.42)(typescript@5.9.2): + rolldown-plugin-dts@0.16.7(rolldown@1.0.0-beta.43)(typescript@5.9.2): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.4 @@ -8943,33 +8953,33 @@ snapshots: dts-resolver: 2.1.2 get-tsconfig: 4.10.1 magic-string: 0.30.19 - rolldown: 1.0.0-beta.42 + rolldown: 1.0.0-beta.43 optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: - oxc-resolver - supports-color - rolldown@1.0.0-beta.42: + rolldown@1.0.0-beta.43: dependencies: '@oxc-project/types': 0.94.0 - '@rolldown/pluginutils': 1.0.0-beta.42 + '@rolldown/pluginutils': 1.0.0-beta.43 ansis: 4.2.0 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.42 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.42 - '@rolldown/binding-darwin-x64': 1.0.0-beta.42 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.42 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.42 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.42 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.42 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.42 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.42 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.42 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.42 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.42 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.42 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.42 + '@rolldown/binding-android-arm64': 1.0.0-beta.43 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.43 + '@rolldown/binding-darwin-x64': 1.0.0-beta.43 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.43 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.43 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.43 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.43 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.43 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.43 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.43 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.43 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.43 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.43 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.43 rollup@4.52.0: dependencies: @@ -9028,6 +9038,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + screenfull@6.0.2: {} + scslre@0.3.0: dependencies: '@eslint-community/regexpp': 4.12.1 @@ -9356,8 +9368,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.42 - rolldown-plugin-dts: 0.16.7(rolldown@1.0.0-beta.42)(typescript@5.9.2) + rolldown: 1.0.0-beta.43 + rolldown-plugin-dts: 0.16.7(rolldown@1.0.0-beta.43)(typescript@5.9.2) semver: 7.7.2 tinyexec: 1.0.1 tinyglobby: 0.2.15