feat(ui): add toasted skin

This commit is contained in:
Sam Potts
2025-10-04 19:26:02 +10:00
committed by GitHub
parent 5195c09af6
commit bdd2f36e13
17 changed files with 475 additions and 300 deletions
+1
View File
@@ -0,0 +1 @@
22.19.0
+3 -1
View File
@@ -14,8 +14,10 @@
],
"words": [
"antfu",
"noto",
"nums",
"rahim"
"rahim",
"segoe"
],
"ignoreWords": [],
"import": []
+1 -1
View File
@@ -6,7 +6,7 @@
<link rel="preconnect" href="https://rsms.me/" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
</head>
<body>
<body class="bg-white text-stone-700 dark:bg-stone-900 dark:text-stone-200">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
+149
View File
@@ -0,0 +1,149 @@
import { MediaProvider, MediaSkinDefault, MediaSkinToasted, Video } from '@vjs-10/react';
import './globals.css';
import { useCallback, useMemo, useState, type ChangeEventHandler } from 'react';
const skins = [{
key: 'default',
name: 'Frosted',
component: MediaSkinDefault,
}, {
key: 'toasted',
name: 'Toasted',
component: MediaSkinToasted,
}] as const;
type SkinKey = (typeof skins)[number]['key'];
const mediaSources = [{
key: '1',
name: 'Mux 1',
value: 'https://stream.mux.com/a4nOgmxGWg6gULfcBbAa00gXyfcwPnAFldF8RdsNyk8M.m3u8'
}, {
key: '2',
name: 'Mux 2',
value: 'https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ.m3u8'
}, {
key: '3',
name: 'Mux 3',
value: 'https://stream.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/high.mp4'
}, {
key: '4',
name: 'Mux 4',
value: 'https://stream.mux.com/lyrKpPcGfqyzeI00jZAfW6MvP6GNPrkML.m3u8'
}] as const;
type MediaSourceKey = (typeof mediaSources)[number]['key'];
function getParam<T>(key: string, defaultValue: T): T {
const params = new URLSearchParams(window.location.search);
return params.get(key) as T || defaultValue;
}
function setParam(key: string, value: string) {
const params = new URLSearchParams(window.location.search);
params.set(key, value);
const search = params.toString();
const url = window.location.pathname + (search ? `?${search}` : '');
window.history.replaceState(null, '', url);
}
const DEFAULT_SKIN: SkinKey = 'toasted';
const DEFAULT_MEDIA_SOURCE: MediaSourceKey = '1';
export default function App(): JSX.Element {
const [skinKey, setSkinKey] = useState<SkinKey>(getParam('skin', DEFAULT_SKIN));
const [mediaSourceKey, setMediaSourceKey] = useState<MediaSourceKey>(getParam('source', DEFAULT_MEDIA_SOURCE));
const mediaSource = useMemo(() => {
let match = mediaSources.find(m => m.key === mediaSourceKey);
if (!match) {
match = mediaSources.find(m => m.key === DEFAULT_MEDIA_SOURCE)!;
setMediaSourceKey(match.key);
}
return match.value;
}, [mediaSourceKey]);
const Skin = useMemo(() => {
let match = skins.find(s => s.key === skinKey);
if (!match) {
match = skins.find(s => s.key === DEFAULT_SKIN)!;
setSkinKey(match.key);
}
return match.component;
}, [skinKey]);
const onChangeSkin: ChangeEventHandler<HTMLSelectElement> = useCallback((event) => {
const value = event.target.value as SkinKey;
setSkinKey(value);
setParam('skin', value);
}, []);
const onChangeMediaSource: ChangeEventHandler<HTMLSelectElement> = useCallback((event) => {
const value = event.target.value as MediaSourceKey;
setMediaSourceKey(value);
setParam('source', value);
}, []);
// Force a re-render on changes.
const key = `${skinKey}-${mediaSourceKey}`;
const skinClassName = useMemo(() => {
switch (skinKey) {
case 'default':
return 'rounded-4xl shadow shadow-lg shadow-black/15';
case 'toasted':
return 'rounded-lg shadow shadow-lg shadow-black/15';
default:
return '';
}
}, [skinKey]);
return (
<>
<header className="fixed top-0 inset-x-0 bg-white dark:bg-stone-800 shadow shadow-black/10 after:h-px after:absolute after:inset-x-0 after:top-full after:bg-black/5">
<div className='grid grid-cols-5 h-2' aria-hidden="true">
<div className='bg-yellow-500'></div>
<div className='bg-orange-500'></div>
<div className='bg-red-500'></div>
<div className='bg-purple-500'></div>
<div className='bg-blue-500'></div>
</div>
<div className="py-3 px-6 flex items-center justify-between">
<div className="space-y-1">
<h1 className="font-medium text-lg tracking-tight leading-tight dark:text-white">Playground</h1>
<small className='block text-stone-400 text-sm'>Test out the various skins for Video.js.</small>
</div>
<nav className='flex items-center gap-3'>
<select value={mediaSourceKey} onChange={onChangeMediaSource}>
{mediaSources.map(({ key, name }) => (
<option key={key} value={key}>
{name}
</option>
))}
</select>
<select value={skinKey} onChange={onChangeSkin}>
{skins.map(({ key, name }) => (
<option key={key} value={key}>
{name}
</option>
))}
</select>
</nav>
</div>
</header>
<main className="min-h-screen flex justify-center items-center bg-radial bg-size-[16px_16px] from-stone-300 dark:from-stone-700 via-10% via-transparent to-transparent">
<div className='w-full max-w-4xl mx-auto p-6'>
<MediaProvider key={key}>
<Skin className={skinClassName}>
{/* @ts-ignore -- types are incorrect */}
<Video src={mediaSource} />
</Skin>
</MediaProvider>
</div>
</main>
</>
);
}
+5 -7
View File
@@ -4,18 +4,16 @@
@source "../../../packages/react";
@theme {
--font-sans:
InterVariable, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
--font-sans: InterVariable, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
.body {
@apply antialiased font-[510] font-sans text-[0.8125rem] @7xl/root:text-[0.9375rem] leading-normal tracking-[-0.0125em];
}
:root {
font-feature-settings: 'cv01', 'ss01', 'ss03';
}
/* Make hocus (hover + focus-visible) variants */
@custom-variant hocus (&:is(:hover, :focus-visible));
@custom-variant group-hocus (&:is(:hover, :focus-visible) &);
@custom-variant peer-hocus (&:is(:hover, :focus-visible) ~ &);
/* Make reduced-transparency variant */
@custom-variant reduced-transparency @media (prefers-reduced-transparency: reduce);
+8 -36
View File
@@ -1,38 +1,10 @@
import ReactDOM from 'react-dom/client';
import { MediaProvider, MediaSkinDefault, Video } from '@vjs-10/react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './globals.css';
function DemoPlayer() {
return (
<div className="min-h-screen grid place-items-center px-6">
<div className="space-y-12 w-full">
<MediaProvider>
<div className="w-full max-w-4xl mx-auto">
<MediaSkinDefault className="rounded-4xl aspect-video">
{/* @ts-ignore -- types are incorrect */}
{/* <Video
muted
src="https://stream.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/high.mp4"
/> */}
{/* @ts-ignore -- types are incorrect */}
<Video muted src="https://stream.mux.com/a4nOgmxGWg6gULfcBbAa00gXyfcwPnAFldF8RdsNyk8M.m3u8" />
</MediaSkinDefault>
</div>
</MediaProvider>
<MediaProvider>
<div className="w-full max-w-4xl mx-auto">
<MediaSkinDefault className="aspect-video">
{/* @ts-ignore -- types are incorrect */}
<Video muted src="https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ.m3u8" />
</MediaSkinDefault>
</div>
</MediaProvider>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')!).render(<DemoPlayer />);
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);
-1
View File
@@ -38,7 +38,6 @@
"eslint": "^9.36.0",
"eslint-plugin-format": "^1.0.2",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.21",
"lint-staged": "^16.2.0",
@@ -46,12 +46,12 @@ export default function MediaSkinDefault({ children, className = '' }: SkinProps
className={styles.TimeDisplay}
/>
<TimeRange.Root className={styles.TimeRangeRoot}>
<TimeRange.Track className={styles.TimeRangeTrack}>
<TimeRange.Progress className={styles.TimeRangeProgress} />
<TimeRange.Pointer className={styles.TimeRangePointer} />
<TimeRange.Root className={styles.SliderRoot}>
<TimeRange.Track className={styles.SliderTrack}>
<TimeRange.Progress className={styles.SliderProgress} />
<TimeRange.Pointer className={styles.SliderPointer} />
</TimeRange.Track>
<TimeRange.Thumb className={styles.TimeRangeThumb} />
<TimeRange.Thumb className={styles.SliderThumb} />
</TimeRange.Root>
<DurationDisplay className={styles.TimeDisplay} />
@@ -66,12 +66,12 @@ export default function MediaSkinDefault({ children, className = '' }: SkinProps
</MuteButton>
</Popover.Trigger>
<Popover.Positioner side="top" sideOffset={8}>
<Popover.Popup className={styles.VolumePopup}>
<VolumeRange.Root className={styles.VolumeRangeRoot} orientation="vertical">
<VolumeRange.Track className={styles.VolumeRangeTrack}>
<VolumeRange.Progress className={styles.VolumeRangeProgress} />
<Popover.Popup className={styles.PopoverPopup}>
<VolumeRange.Root className={styles.SliderRoot} orientation="vertical">
<VolumeRange.Track className={styles.SliderTrack}>
<VolumeRange.Progress className={styles.SliderProgress} />
</VolumeRange.Track>
<VolumeRange.Thumb className={styles.VolumeRangeThumb} />
<VolumeRange.Thumb className={styles.SliderThumb} />
</VolumeRange.Root>
</Popover.Popup>
</Popover.Positioner>
@@ -1,43 +1,12 @@
// A (very crude) utility to merge class names
// Usually I'd use something like `clsx` or `classnames` but this is ok for our simple use case.
// It just makes the billions of Tailwind classes a little easier to read.
const cn = (...classes: (string | undefined)[]): string => classes.filter(Boolean).join(' ');
import type { MediaDefaultSkinStyles } from "./types";
export interface MediaDefaultSkinStyles {
readonly MediaContainer: string;
readonly Overlay: string;
readonly Controls: string;
readonly Button: string;
readonly IconButton: string;
readonly PlayButton: string;
readonly PlayIcon: string;
readonly PauseIcon: string;
readonly VolumeButton: string;
readonly VolumeHighIcon: string;
readonly VolumeLowIcon: string;
readonly VolumeOffIcon: string;
readonly FullScreenButton: string;
readonly FullScreenEnterIcon: string;
readonly FullScreenExitIcon: string;
readonly TimeControls: string;
readonly TimeDisplay: string;
readonly TimeRangeRoot: string;
readonly TimeRangeTrack: string;
readonly TimeRangeProgress: string;
readonly TimeRangePointer: string;
readonly TimeRangeThumb: string;
readonly VolumePopup: string;
readonly VolumeRangeRoot: string;
readonly VolumeRangeTrack: string;
readonly VolumeRangeProgress: string;
readonly VolumeRangeThumb: string;
}
import { cn } from "../../utils/cn";
const styles: MediaDefaultSkinStyles = {
MediaContainer: cn(
'relative @container/root group/root overflow-clip',
// Base typography
'antialiased font-[510] font-sans text-[0.8125rem] @7xl/root:text-[0.9375rem] leading-normal tracking-[-0.0125em]',
'text-sm',
// Prevent rounded corners in fullscreen.
'[&:fullscreen]:rounded-none [&:fullscreen]:[&_video]:h-full [&:fullscreen]:[&_video]:w-full',
// Fancy borders.
@@ -49,25 +18,26 @@ const styles: MediaDefaultSkinStyles = {
Overlay: cn(
'opacity-0 delay-500 rounded-[inherit] absolute inset-0 pointer-events-none z-10 bg-gradient-to-t from-black/50 via-black/20 to-transparent transition-opacity backdrop-saturate-150 backdrop-brightness-90',
// Hide when playing (for now).
// This is crude temporary logic, well improve it later I guess with a [data-show-controls] attribute or something?
// FIXME: This is crude temporary logic, well improve it later I guess with a [data-show-controls] attribute or something?
'has-[+.controls_[data-paused]]:opacity-100 has-[+.controls_[data-paused]]:delay-0',
'group-hover/root:opacity-100 group-hover/root:delay-0'
),
Controls: cn(
'controls', // Temporary className hook for above logic. Can be removed once have a proper selector as above.
'controls', // FIXME: Temporary className hook for above logic in the overlay. Can be removed once have a proper way to handle controls visibility.
'@container/controls absolute inset-x-3 bottom-3 rounded-full z-20 flex items-center p-1 ring ring-white/10 ring-inset gap-0.5 text-white text-shadow',
'shadow-sm shadow-black/15',
// Background
'bg-white/10 backdrop-blur-3xl backdrop-saturate-150 backdrop-brightness-90',
// Animation
'transition will-change-transform origin-bottom ease-out',
// Temporary hide/show logic
// FIXME: Temporary hide/show logic
'scale-90 opacity-0 delay-500',
'has-[[data-paused]]:scale-100 has-[[data-paused]]:opacity-100 has-[[data-paused]]:delay-0',
'group-hover/root:scale-100 group-hover/root:opacity-100 group-hover/root:delay-0',
// Border to enhance contrast on lighter videos
'after:absolute after:inset-0 after:ring after:rounded-[inherit] after:ring-black/15 after:pointer-events-none after:z-10',
// Reduced transparency for users with preference
// XXX: This requires a Tailwind custom variant (see 1 below)
'reduced-transparency:bg-black/70 reduced-transparency:ring-black reduced-transparency:after:ring-white/20',
// High contrast mode
'contrast-more:bg-black/90 contrast-more:ring-black contrast-more:after:ring-white/20'
@@ -77,7 +47,7 @@ const styles: MediaDefaultSkinStyles = {
// Background/foreground
'bg-transparent text-white/90',
// Hover and focus states
'hocus:no-underline hocus:bg-white/10 hocus:text-white',
'hover:no-underline hover:bg-white/10 hover:text-white focus-visible:no-underline focus-visible:bg-white/10 focus-visible:text-white',
// Focus state
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500',
// Disabled state
@@ -128,39 +98,42 @@ const styles: MediaDefaultSkinStyles = {
),
TimeControls: cn('flex-1 flex items-center gap-3 px-1.5'),
TimeDisplay: cn('tabular-nums text-shadow-2xs shadow-black/50'),
TimeRangeRoot: cn('flex [&[data-orientation="horizontal"]]:h-5 [&[data-orientation="vertical"]]:w-5 [&[data-orientation="vertical"]]:h-20 items-center justify-center flex-1 group/slider relative'),
TimeRangeTrack: cn('[&[data-orientation="horizontal"]]:h-1 [&[data-orientation="vertical"]]:w-1 w-full relative select-none rounded-full bg-white/20 ring-1 ring-black/5'),
TimeRangeProgress: cn('bg-white rounded-[inherit]'),
SliderRoot: cn(
'flex items-center justify-center flex-1 group/slider relative',
'[&[data-orientation="horizontal"]]:h-5 [&[data-orientation="horizontal"]]:min-w-20',
'[&[data-orientation="vertical"]]:w-5 [&[data-orientation="vertical"]]:h-20',
),
SliderTrack: cn(
'w-full relative select-none rounded-full bg-white/20 ring-1 ring-black/5',
'[&[data-orientation="horizontal"]]:h-1',
'[&[data-orientation="vertical"]]:w-1'
),
SliderProgress: cn('bg-white rounded-[inherit]'),
// TODO: Work out what we want to do here.
TimeRangePointer: cn('rounded-[inherit]'),
TimeRangeThumb: cn(
SliderPointer: cn('rounded-[inherit]'),
SliderThumb: cn(
'bg-white z-10 select-none ring ring-black/10 rounded-full shadow-sm shadow-black/15',
'opacity-0 transition-[opacity,height,width] ease-in-out',
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500',
'group-hover/slider:opacity-100 group-focus-within/slider:opacity-100',
'size-2.5 active:size-3 group-active/slider:size-3'
'size-2.5 active:size-3 group-active/slider:size-3 hover:cursor-ew-resize'
),
VolumePopup: cn(
PopoverPopup: cn(
'relative z-30 px-2 py-4 rounded-2xl',
'bg-white/10 backdrop-blur-3xl backdrop-saturate-150 backdrop-brightness-90',
'ring ring-white/10 ring-inset shadow-sm shadow-black/15',
// Border to enhance contrast on lighter videos
'after:absolute after:inset-0 after:ring after:rounded-[inherit] after:ring-black/15 after:pointer-events-none after:z-10',
// Reduced transparency for users with preference
// XXX: This requires a Tailwind custom variant (see 1 below)
'reduced-transparency:bg-black/70 reduced-transparency:ring-black reduced-transparency:after:ring-white/20',
// High contrast mode
'contrast-more:bg-black/90 contrast-more:ring-black contrast-more:after:ring-white/20'
),
VolumeRangeRoot: cn('flex [&[data-orientation="horizontal"]]:w-20 [&[data-orientation="horizontal"]]:h-5 [&[data-orientation="vertical"]]:w-5 [&[data-orientation="vertical"]]:h-20 items-center justify-center group/slider relative'),
VolumeRangeTrack: cn('[&[data-orientation="horizontal"]]:h-1 [&[data-orientation="vertical"]]:w-1 w-full relative select-none rounded-full bg-white/20 ring-1 ring-black/5'),
VolumeRangeProgress: cn('bg-white rounded-[inherit]'),
VolumeRangeThumb: cn(
'bg-white z-10 select-none ring ring-black/10 rounded-full shadow-sm shadow-black/15',
'opacity-0 transition-[opacity,height,width] ease-in-out',
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500',
'group-hover/slider:opacity-100 group-focus-within/slider:opacity-100',
'size-2.5 active:size-3 group-active/slider:size-3'
),
};
/*
[1] @custom-variant reduced-transparency @media (prefers-reduced-transparency: reduce);
*/
export default styles;
@@ -0,0 +1,25 @@
export interface MediaDefaultSkinStyles {
readonly MediaContainer: string;
readonly Overlay: string;
readonly Controls: string;
readonly Button: string;
readonly IconButton: string;
readonly PlayButton: string;
readonly PlayIcon: string;
readonly PauseIcon: string;
readonly VolumeButton: string;
readonly VolumeHighIcon: string;
readonly VolumeLowIcon: string;
readonly VolumeOffIcon: string;
readonly FullScreenButton: string;
readonly FullScreenEnterIcon: string;
readonly FullScreenExitIcon: string;
readonly TimeControls: string;
readonly TimeDisplay: string;
readonly SliderRoot: string;
readonly SliderTrack: string;
readonly SliderProgress: string;
readonly SliderPointer: string;
readonly SliderThumb: string;
readonly PopoverPopup: string;
}
+2 -1
View File
@@ -1 +1,2 @@
export * from './default'
export * from './default'
export * from './toasted'
@@ -0,0 +1,94 @@
import type { PropsWithChildren } from 'react';
import {
FullscreenEnterIcon,
FullscreenExitIcon,
PauseIcon,
PlayIcon,
VolumeHighIcon,
VolumeLowIcon,
VolumeOffIcon,
} from '@vjs-10/react-icons';
import { CurrentTimeDisplay } from '../../components/CurrentTimeDisplay';
import { DurationDisplay } from '../../components/DurationDisplay';
import { FullscreenButton } from '../../components/FullscreenButton';
import { MediaContainer } from '../../components/MediaContainer';
import MuteButton from '../../components/MuteButton';
import PlayButton from '../../components/PlayButton';
import { TimeRange } from '../../components/TimeRange';
import { VolumeRange } from '../../components/VolumeRange';
import styles from './styles';
type SkinProps = PropsWithChildren<{
className?: string;
}>;
export default function MediaSkinDefault({ children, className = '' }: SkinProps): JSX.Element {
return (
<MediaContainer className={`${styles.MediaContainer} ${className}`}>
{children}
<div className={styles.Controls} data-testid="media-controls">
{/* <header className='py-2 px-4 text-shadow-sm text-shadow-black/10'>
<h1 className="text-base font-medium">View From a Blue Moon</h1>
<p className="text-stone-400">A story about Jon Jon Florence, a surfer from Hawaii.</p>
</header> */}
<div className={styles.TimeSlider}>
<TimeRange.Root className={styles.SliderRoot}>
<TimeRange.Track className={styles.SliderTrack}>
<TimeRange.Progress className={styles.SliderProgress} />
<TimeRange.Pointer className={styles.SliderPointer} />
</TimeRange.Track>
<TimeRange.Thumb className={`${styles.SliderThumb} ${styles.TimeSliderThumb}`} />
</TimeRange.Root>
</div>
<div className={styles.ControlsRow}>
<div className='flex items-center gap-3'>
<PlayButton className={`${styles.Button} ${styles.IconButton} ${styles.PlayButton}`}>
<PlayIcon className={styles.PlayIcon}></PlayIcon>
<PauseIcon className={styles.PauseIcon}></PauseIcon>
</PlayButton>
<div className="flex items-center gap-1">
<CurrentTimeDisplay
// Use showRemaining to show count down/remaining time
// showRemaining
className={styles.TimeDisplay}
/>
<span className='opacity-50'>/</span>
<DurationDisplay className={`${styles.TimeDisplay} opacity-50`} />
</div>
</div>
<div className='flex items-center gap-0.5'>
<div className={styles.VolumeControls}>
<MuteButton className={`${styles.Button} ${styles.IconButton} ${styles.VolumeButton}`}>
<VolumeHighIcon className={styles.VolumeHighIcon} />
<VolumeLowIcon className={styles.VolumeLowIcon} />
<VolumeOffIcon className={styles.VolumeOffIcon} />
</MuteButton>
<div className={styles.VolumeSlider}>
<VolumeRange.Root className={styles.SliderRoot}>
<VolumeRange.Track className={styles.SliderTrack}>
<VolumeRange.Progress className={styles.SliderProgress} />
</VolumeRange.Track>
<VolumeRange.Thumb className={styles.SliderThumb} />
</VolumeRange.Root>
</div>
</div>
<FullscreenButton className={`${styles.Button} ${styles.IconButton} ${styles.FullScreenButton}`}>
<FullscreenEnterIcon className={styles.FullScreenEnterIcon} />
<FullscreenExitIcon className={styles.FullScreenExitIcon} />
</FullscreenButton>
</div>
</div>
</div>
</MediaContainer>
);
};
@@ -0,0 +1 @@
export { default as MediaSkinToasted } from './MediaSkinToasted';
@@ -0,0 +1,115 @@
import type { MediaToastedSkinStyles } from "./types";
import { cn } from "../../utils/cn";
const styles: MediaToastedSkinStyles = {
MediaContainer: cn(
'relative @container/root group/root overflow-clip bg-black',
// Base typography
'text-sm',
// 'ring-1 ring-inset ring-black/10 dark:ring-white/10',
'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]',
// Prevent rounded corners in fullscreen.
'[&:fullscreen]:rounded-none [&:fullscreen]:[&_video]:h-full [&:fullscreen]:[&_video]:w-full',
// Ensure the nested video inherits the radius.
'[&_video]:rounded-[inherit] [&_video]:w-full [&_video]:h-auto',
),
Controls: cn(
'@container/controls absolute inset-x-0 bottom-0 top-1/3 flex flex-col justify-end z-20 px-1 pb-1.5 text-white text-shadow',
'shadow-sm shadow-black/15',
// Background
'bg-gradient-to-t from-stone-900/70 via-stone-900/60 via-35% to-transparent',
// Animation
'transition ease-in-out',
// FIXME: Temporary hide/show logic
'translate-y-full opacity-0 delay-500 pointer-events-none',
'has-[[data-paused]]:translate-y-0 has-[[data-paused]]:opacity-100 has-[[data-paused]]:delay-0 has-[[data-paused]]:pointer-events-auto',
'group-hover/root:translate-y-0 group-hover/root:opacity-100 group-hover/root:delay-0 group-hover/root:pointer-events-auto',
),
ControlsRow: cn('flex items-center justify-between px-1.5 pb-0.5'),
Button: cn(
'group/button cursor-pointer relative shrink-0 transition select-none p-2 rounded-md',
// Background/foreground
'bg-transparent text-white/90',
// Hover and focus states
'hover:no-underline hover:bg-white/15 hover:backdrop-blur-md hover:text-white focus-visible:no-underline focus-visible:bg-white/10 focus-visible:text-white',
// Focus state
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500',
// Disabled state
'aria-disabled:grayscale aria-disabled:opacity-50 aria-disabled:cursor-not-allowed',
// Loading state
'aria-busy:pointer-events-none aria-busy:cursor-not-allowed',
// Expanded state
'aria-expanded:bg-white/10 aria-expanded:text-white',
// Pressed state
'active:scale-95',
),
IconButton: cn(
'grid [&_svg]:[grid-area:1/1]',
'[&_svg]:shrink-0 [&_svg]:transition [&_svg]:duration-300 [&_svg]:ease-out [&_svg]:drop-shadow-[0_1px_0_var(--tw-shadow-color)] [&_svg]:shadow-black/20',
),
PlayButton: cn(
'[&_.pause-icon]:opacity-100 [&[data-paused]_.pause-icon]:opacity-0',
'[&_.play-icon]:opacity-0 [&[data-paused]_.play-icon]:opacity-100',
),
PlayIcon: cn('play-icon'),
PauseIcon: cn('pause-icon'),
VolumeControls: cn('flex items-center flex-row-reverse group/volume'),
VolumeSlider: cn(
'w-0 px-3 overflow-hidden pointer-events-none transition-[opacity,width] opacity-0 ease-out delay-500',
'group-hover/volume:w-28 group-hover/volume:pointer-events-auto group-hover/volume:opacity-100 group-hover/volume:delay-0',
'group-focus-within/volume:w-28 group-focus-within/volume:pointer-events-auto group-focus-within/volume:opacity-100 group-focus-within/volume:delay-0',
),
VolumeButton: cn(
'[&_svg]:hidden',
'[&[data-volume-level="high"]_.volume-high-icon]:inline',
'[&[data-volume-level="medium"]_.volume-low-icon]:inline',
'[&[data-volume-level="low"]_.volume-low-icon]:inline',
'[&[data-volume-level="off"]_.volume-off-icon]:inline',
),
VolumeHighIcon: cn('volume-high-icon'),
VolumeLowIcon: cn('volume-low-icon'),
VolumeOffIcon: cn('volume-off-icon'),
FullScreenButton: cn(
'[&_.fullscreen-enter-icon]:opacity-100 [&[data-fullscreen]_.fullscreen-enter-icon]:opacity-0',
'[&_.fullscreen-exit-icon]:opacity-0 [&[data-fullscreen]_.fullscreen-exit-icon]:opacity-100',
'[&_path]:transition-transform ease-out',
),
FullScreenEnterIcon: cn(
'fullscreen-enter-icon',
'group-hover/button:[&_.arrow-1]:-translate-x-px group-hover/button:[&_.arrow-1]:-translate-y-px',
'group-hover/button:[&_.arrow-2]:translate-x-px group-hover/button:[&_.arrow-2]:translate-y-px',
),
FullScreenExitIcon: cn(
'fullscreen-exit-icon',
'[&_.arrow-1]:-translate-x-px [&_.arrow-1]:-translate-y-px',
'[&_.arrow-2]:translate-x-px [&_.arrow-2]:translate-y-px',
'group-hover/button:[&_.arrow-1]:translate-0',
'group-hover/button:[&_.arrow-2]:translate-0',
),
TimeSlider: cn('px-1.5'),
TimeSliderThumb: cn(
'opacity-0',
'group-hover/slider:opacity-100 group-focus-within/slider:opacity-100',
),
TimeDisplay: cn('tabular-nums text-shadow-2xs shadow-black/50'),
SliderRoot: cn(
'flex items-center justify-center flex-1 group/slider relative',
'[&[data-orientation="horizontal"]]:h-5 [&[data-orientation="horizontal"]]:min-w-20',
'[&[data-orientation="vertical"]]:w-5 [&[data-orientation="vertical"]]:h-20',
),
SliderTrack: cn(
'relative select-none rounded-full bg-white/25 backdrop-blur-sm backdrop-brightness-90 backdrop-saturate-150 shadow-sm shadow-black/10',
'[&[data-orientation="horizontal"]]:w-full [&[data-orientation="horizontal"]]:h-1',
'[&[data-orientation="vertical"]]:w-1',
),
SliderProgress: cn('bg-amber-500 rounded-[inherit]'),
// TODO: Work out what we want to do here.
SliderPointer: cn('rounded-[inherit]'),
SliderThumb: cn(
'bg-white z-10 select-none ring ring-black/10 rounded-full shadow-sm shadow-black/15 transition-[opacity,height,width] ease-in-out',
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500',
'size-3 active:size-3.5 group-active/slider:size-3.5 hover:cursor-ew-resize',
),
};
export default styles;
@@ -0,0 +1,27 @@
export interface MediaToastedSkinStyles {
readonly MediaContainer: string;
readonly Controls: string;
readonly ControlsRow: string;
readonly Button: string;
readonly IconButton: string;
readonly PlayButton: string;
readonly PlayIcon: string;
readonly PauseIcon: string;
readonly VolumeControls: string;
readonly VolumeSlider: string;
readonly VolumeButton: string;
readonly VolumeHighIcon: string;
readonly VolumeLowIcon: string;
readonly VolumeOffIcon: string;
readonly FullScreenButton: string;
readonly FullScreenEnterIcon: string;
readonly FullScreenExitIcon: string;
readonly TimeSlider: string;
readonly TimeSliderThumb: string;
readonly TimeDisplay: string;
readonly SliderRoot: string;
readonly SliderTrack: string;
readonly SliderProgress: string;
readonly SliderPointer: string;
readonly SliderThumb: string;
}
+6
View File
@@ -0,0 +1,6 @@
// A (very crude) utility to merge class names
// Usually I'd use something like `clsx` or `classnames` but this is ok for our simple use case.
// It just makes the billions of Tailwind classes a little easier to read.
export function cn(...classes: (string | undefined)[]): string {
return classes.filter(Boolean).join(' ');
}
-188
View File
@@ -35,9 +35,6 @@ importers:
eslint-plugin-jsx-a11y:
specifier: ^6.10.2
version: 6.10.2(eslint@9.36.0(jiti@2.5.1))
eslint-plugin-react:
specifier: ^7.37.5
version: 7.37.5(eslint@9.36.0(jiti@2.5.1))
eslint-plugin-react-hooks:
specifier: ^5.2.0
version: 5.2.0(eslint@9.36.0(jiti@2.5.1))
@@ -1547,10 +1544,6 @@ packages:
resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
engines: {node: '>= 0.4'}
array.prototype.findlast@1.2.5:
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
engines: {node: '>= 0.4'}
array.prototype.flat@1.3.3:
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
engines: {node: '>= 0.4'}
@@ -1559,10 +1552,6 @@ packages:
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
engines: {node: '>= 0.4'}
array.prototype.tosorted@1.1.4:
resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
engines: {node: '>= 0.4'}
arraybuffer.prototype.slice@1.0.4:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
@@ -1840,10 +1829,6 @@ packages:
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
engines: {node: '>=0.3.1'}
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
@@ -1913,10 +1898,6 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-iterator-helpers@1.2.1:
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -2142,12 +2123,6 @@ packages:
typescript:
optional: true
eslint-plugin-react@7.37.5:
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
engines: {node: '>=4'}
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
eslint-plugin-regexp@2.10.0:
resolution: {integrity: sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==}
engines: {node: ^18 || >=20}
@@ -2495,10 +2470,6 @@ packages:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
is-data-view@1.0.2:
resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
engines: {node: '>= 0.4'}
@@ -2591,10 +2562,6 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
iterator.prototype@1.1.5:
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
engines: {node: '>= 0.4'}
jiti@2.5.1:
resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==}
hasBin: true
@@ -2988,10 +2955,6 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
object-deep-merge@1.0.5:
resolution: {integrity: sha512-3DioFgOzetbxbeUq8pB2NunXo8V0n4EvqsWM/cJoI6IA9zghd7cl/2pBOuWRf4dlvA+fcg5ugFMZaN2/RuoaGg==}
@@ -3007,10 +2970,6 @@ packages:
resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
engines: {node: '>= 0.4'}
object.entries@1.1.9:
resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
engines: {node: '>= 0.4'}
object.fromentries@2.0.8:
resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
engines: {node: '>= 0.4'}
@@ -3071,9 +3030,6 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -3140,9 +3096,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -3158,9 +3111,6 @@ packages:
peerDependencies:
react: ^18.3.1
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-refresh@0.17.0:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
@@ -3207,10 +3157,6 @@ packages:
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
resolve@2.0.0-next.5:
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
hasBin: true
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -3372,13 +3318,6 @@ packages:
resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
engines: {node: '>= 0.4'}
string.prototype.matchall@4.0.12:
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
engines: {node: '>= 0.4'}
string.prototype.repeat@1.0.0:
resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
string.prototype.trim@1.2.10:
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
engines: {node: '>= 0.4'}
@@ -3407,10 +3346,6 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
svg-parser@2.0.4:
resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
@@ -4837,15 +4772,6 @@ snapshots:
is-string: 1.1.1
math-intrinsics: 1.1.0
array.prototype.findlast@1.2.5:
dependencies:
call-bind: 1.0.8
define-properties: 1.2.1
es-abstract: 1.24.0
es-errors: 1.3.0
es-object-atoms: 1.1.1
es-shim-unscopables: 1.1.0
array.prototype.flat@1.3.3:
dependencies:
call-bind: 1.0.8
@@ -4860,14 +4786,6 @@ snapshots:
es-abstract: 1.24.0
es-shim-unscopables: 1.1.0
array.prototype.tosorted@1.1.4:
dependencies:
call-bind: 1.0.8
define-properties: 1.2.1
es-abstract: 1.24.0
es-errors: 1.3.0
es-shim-unscopables: 1.1.0
arraybuffer.prototype.slice@1.0.4:
dependencies:
array-buffer-byte-length: 1.0.2
@@ -5120,10 +5038,6 @@ snapshots:
diff@8.0.2: {}
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
@@ -5237,25 +5151,6 @@ snapshots:
es-errors@1.3.0: {}
es-iterator-helpers@1.2.1:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.4
define-properties: 1.2.1
es-abstract: 1.24.0
es-errors: 1.3.0
es-set-tostringtag: 2.1.0
function-bind: 1.1.2
get-intrinsic: 1.3.0
globalthis: 1.0.4
gopd: 1.2.0
has-property-descriptors: 1.0.2
has-proto: 1.2.0
has-symbols: 1.1.0
internal-slot: 1.1.0
iterator.prototype: 1.1.5
safe-array-concat: 1.1.3
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -5602,28 +5497,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-react@7.37.5(eslint@9.36.0(jiti@2.5.1)):
dependencies:
array-includes: 3.1.9
array.prototype.findlast: 1.2.5
array.prototype.flatmap: 1.3.3
array.prototype.tosorted: 1.1.4
doctrine: 2.1.0
es-iterator-helpers: 1.2.1
eslint: 9.36.0(jiti@2.5.1)
estraverse: 5.3.0
hasown: 2.0.2
jsx-ast-utils: 3.3.5
minimatch: 3.1.2
object.entries: 1.1.9
object.fromentries: 2.0.8
object.values: 1.2.1
prop-types: 15.8.1
resolve: 2.0.0-next.5
semver: 6.3.1
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
eslint-plugin-regexp@2.10.0(eslint@9.36.0(jiti@2.5.1)):
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.5.1))
@@ -6010,10 +5883,6 @@ snapshots:
is-callable@1.2.7: {}
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
is-data-view@1.0.2:
dependencies:
call-bound: 1.0.4
@@ -6110,15 +5979,6 @@ snapshots:
isexe@2.0.0: {}
iterator.prototype@1.1.5:
dependencies:
define-data-property: 1.1.4
es-object-atoms: 1.1.1
get-intrinsic: 1.3.0
get-proto: 1.0.1
has-symbols: 1.1.0
set-function-name: 2.0.2
jiti@2.5.1: {}
js-tokens@4.0.0: {}
@@ -6660,8 +6520,6 @@ snapshots:
dependencies:
boolbase: 1.0.0
object-assign@4.1.1: {}
object-deep-merge@1.0.5:
dependencies:
type-fest: 4.2.0
@@ -6679,13 +6537,6 @@ snapshots:
has-symbols: 1.1.0
object-keys: 1.1.1
object.entries@1.1.9:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.4
define-properties: 1.2.1
es-object-atoms: 1.1.1
object.fromentries@2.0.8:
dependencies:
call-bind: 1.0.8
@@ -6756,8 +6607,6 @@ snapshots:
path-key@3.1.1: {}
path-parse@1.0.7: {}
path-type@4.0.0: {}
pathe@2.0.3: {}
@@ -6811,12 +6660,6 @@ snapshots:
prettier@3.6.2: {}
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
punycode@2.3.1: {}
quansync@0.2.11: {}
@@ -6829,8 +6672,6 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
react-is@16.13.1: {}
react-refresh@0.17.0: {}
react@18.3.1:
@@ -6880,12 +6721,6 @@ snapshots:
resolve-pkg-maps@1.0.0: {}
resolve@2.0.0-next.5:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
@@ -7108,27 +6943,6 @@ snapshots:
define-properties: 1.2.1
es-abstract: 1.24.0
string.prototype.matchall@4.0.12:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.4
define-properties: 1.2.1
es-abstract: 1.24.0
es-errors: 1.3.0
es-object-atoms: 1.1.1
get-intrinsic: 1.3.0
gopd: 1.2.0
has-symbols: 1.1.0
internal-slot: 1.1.0
regexp.prototype.flags: 1.5.4
set-function-name: 2.0.2
side-channel: 1.1.0
string.prototype.repeat@1.0.0:
dependencies:
define-properties: 1.2.1
es-abstract: 1.24.0
string.prototype.trim@1.2.10:
dependencies:
call-bind: 1.0.8
@@ -7164,8 +6978,6 @@ snapshots:
dependencies:
has-flag: 4.0.0
supports-preserve-symlinks-flag@1.0.0: {}
svg-parser@2.0.4: {}
svgo@3.3.2: