fix: skin syntax usage cleanup (#48)

This commit is contained in:
Christian Pillsbury
2025-10-10 12:59:26 -07:00
committed by GitHub
parent 284f63f33b
commit d4f3f4b75b
26 changed files with 678 additions and 267 deletions
+1
View File
@@ -11,6 +11,7 @@
},
"dependencies": {
"@vjs-10/react": "workspace:*",
"@vjs-10/react-icons": "workspace:*",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"tailwindcss": "^4.1.13"
+5 -1
View File
@@ -1,9 +1,13 @@
import type { ChangeEventHandler } from 'react';
import { MediaProvider, MediaSkinDefault, MediaSkinToasted, Video } from '@vjs-10/react';
// 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 './globals.css';
const skins = [
@@ -0,0 +1,136 @@
import type { PropsWithChildren } from 'react';
import {
CurrentTimeDisplay,
DurationDisplay,
FullscreenButton,
MediaContainer,
MuteButton,
PlayButton,
Popover,
TimeSlider,
Tooltip,
VolumeSlider,
} from '@vjs-10/react';
import {
FullscreenEnterIcon,
FullscreenExitIcon,
PauseIcon,
PlayIcon,
VolumeHighIcon,
VolumeLowIcon,
VolumeOffIcon,
} from '@vjs-10/react-icons';
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}
{/* Background gradient to help with controls contrast. */}
<div className={styles.Overlay} aria-hidden="true" />
<div className={styles.Controls} data-testid="media-controls">
<Tooltip.Root delay={600} closeDelay={0}>
<Tooltip.Trigger>
<PlayButton className={`${styles.Button} ${styles.IconButton} ${styles.PlayButton}`}>
<PlayIcon className={styles.PlayIcon}></PlayIcon>
<PauseIcon className={styles.PauseIcon}></PauseIcon>
</PlayButton>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Positioner side="top" sideOffset={12} collisionPadding={12}>
<Tooltip.Popup className={`${styles.TooltipPopup} ${styles.PlayTooltipPopup}`}>
<span className={styles.PlayTooltip}>Play</span>
<span className={styles.PauseTooltip}>Pause</span>
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
<div className={styles.TimeControls}>
<CurrentTimeDisplay
// Use showRemaining to show count down/remaining time
// showRemaining
className={styles.TimeDisplay}
/>
<TimeSlider.Root className={styles.SliderRoot}>
<TimeSlider.Track className={styles.SliderTrack}>
<TimeSlider.Progress className={styles.SliderProgress} />
<TimeSlider.Pointer className={styles.SliderPointer} />
</TimeSlider.Track>
<TimeSlider.Thumb className={styles.SliderThumb} />
</TimeSlider.Root>
<DurationDisplay className={styles.TimeDisplay} />
</div>
<Popover.Root openOnHover delay={200} closeDelay={100}>
<Popover.Trigger>
<MuteButton className={`${styles.Button} ${styles.IconButton} ${styles.MuteButton}`}>
<VolumeHighIcon className={styles.VolumeHighIcon} />
<VolumeLowIcon className={styles.VolumeLowIcon} />
<VolumeOffIcon className={styles.VolumeOffIcon} />
</MuteButton>
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner side="top" sideOffset={12}>
<Popover.Popup className={styles.PopoverPopup}>
<VolumeSlider.Root className={styles.SliderRoot} orientation="vertical">
<VolumeSlider.Track className={styles.SliderTrack}>
<VolumeSlider.Progress className={styles.SliderProgress} />
</VolumeSlider.Track>
<VolumeSlider.Thumb className={styles.SliderThumb} />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
<Tooltip.Root delay={600} closeDelay={0}>
<Tooltip.Trigger>
<FullscreenButton className={`${styles.Button} ${styles.IconButton} ${styles.FullScreenButton}`}>
<FullscreenEnterIcon className={styles.FullScreenEnterIcon} />
<FullscreenExitIcon className={styles.FullScreenExitIcon} />
</FullscreenButton>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Positioner side="top" sideOffset={12} collisionPadding={12}>
<Tooltip.Popup className={`${styles.TooltipPopup} ${styles.FullScreenTooltipPopup}`}>
<span className={styles.FullScreenEnterTooltip}>Enter Fullscreen</span>
<span className={styles.FullScreenExitTooltip}>Exit Fullscreen</span>
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
</div>
</MediaContainer>
);
}
// function ArrowSvg(props: React.ComponentProps<'svg'>) {
// return (
// <svg width="20" height="10" viewBox="0 0 20 10" fill="none" {...props}>
// <path
// d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
// className={styles.ArrowFill}
// />
// <path
// d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
// className={styles.ArrowOuterStroke}
// />
// <path
// d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
// className={styles.ArrowInnerStroke}
// />
// </svg>
// );
// }
@@ -0,0 +1,171 @@
import type { MediaDefaultSkinStyles } from './types';
// NOTE: Removing import to sidestep for compiler complexity (CJP)
// import { cn } from '../../utils/cn';
// 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.
function cn(...classes: (string | undefined)[]): string {
return classes.filter(Boolean).join(' ');
}
const styles: MediaDefaultSkinStyles = {
MediaContainer: cn(
'relative @container/root group/root overflow-clip',
// Base typography
'text-sm',
// 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',
// Ensure the nested video inherits the radius.
'[&_video]:rounded-[inherit] [&_video]:w-full [&_video]:h-auto',
),
Overlay: cn(
'opacity-0 delay-500 rounded-[inherit] absolute inset-0 pointer-events-none 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).
// 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', // 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 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',
// 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',
),
Button: cn(
'group/button cursor-pointer relative shrink-0 transition select-none p-2 rounded-full',
// Background/foreground
'bg-transparent text-white/90',
// Hover and focus states
'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
'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-opacity [&_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'),
TooltipPopup: cn(
'whitespace-nowrap flex origin-[var(--transform-origin)] flex-col rounded-md text-white text-xs @7xl/root:text-sm px-2 py-1',
// Background
'bg-white/10 backdrop-blur-3xl backdrop-saturate-150 backdrop-brightness-90',
// Animation
'transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[instant]:duration-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0',
// Ring
'ring-1 ring-white/10 ring-inset',
// Text shadow
'text-shadow shadow-black/10',
// 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',
),
PlayTooltipPopup: cn(
'[&_.pause-tooltip]:inline [&[data-paused]_.pause-tooltip]:hidden',
'[&_.play-tooltip]:hidden [&[data-paused]_.play-tooltip]:inline',
),
PlayTooltip: cn('play-tooltip'),
PauseTooltip: cn('pause-tooltip'),
MuteButton: cn(
'[&_svg]:opacity-0',
'[&[data-volume-level="high"]_.volume-high-icon]:opacity-100',
'[&[data-volume-level="medium"]_.volume-low-icon]:opacity-100',
'[&[data-volume-level="low"]_.volume-low-icon]:opacity-100',
'[&[data-volume-level="off"]_.volume-off-icon]:opacity-100',
),
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',
),
FullScreenTooltipPopup: cn(
'[&_.fullscreen-enter-tooltip]:inline [&[data-fullscreen]_.fullscreen-enter-tooltip]:hidden',
'[&_.fullscreen-exit-tooltip]:hidden [&[data-fullscreen]_.fullscreen-exit-tooltip]:inline',
),
FullScreenEnterTooltip: cn('fullscreen-enter-tooltip'),
FullScreenExitTooltip: cn('fullscreen-exit-tooltip'),
TimeControls: cn('flex-1 flex items-center gap-3 px-1.5'),
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(
'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.
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 hover:cursor-ew-resize',
),
PopoverPopup: cn(
'relative 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',
),
};
/*
[1] @custom-variant reduced-transparency @media (prefers-reduced-transparency: reduce);
*/
export default styles;
@@ -0,0 +1,32 @@
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 PlayTooltipPopup: string;
readonly PlayTooltip: string;
readonly PauseTooltip: string;
readonly TooltipPopup: string;
readonly MuteButton: string;
readonly VolumeHighIcon: string;
readonly VolumeLowIcon: string;
readonly VolumeOffIcon: string;
readonly FullScreenButton: string;
readonly FullScreenEnterIcon: string;
readonly FullScreenExitIcon: string;
readonly FullScreenTooltipPopup: string;
readonly FullScreenEnterTooltip: string;
readonly FullScreenExitTooltip: string;
readonly SliderRoot: string;
readonly SliderTrack: string;
readonly SliderProgress: string;
readonly SliderPointer: string;
readonly SliderThumb: string;
readonly PopoverPopup: string;
readonly TimeControls: string;
readonly TimeDisplay: string;
}
@@ -0,0 +1,75 @@
import type { PropsWithChildren } from 'react';
import {
CurrentTimeDisplay,
DurationDisplay,
FullscreenButton,
MediaContainer,
MuteButton,
PlayButton,
TimeSlider,
} from '@vjs-10/react';
import {
FullscreenEnterIcon,
FullscreenExitIcon,
PauseIcon,
PlayIcon,
VolumeHighIcon,
VolumeLowIcon,
VolumeOffIcon,
} from '@vjs-10/react-icons';
import styles from './styles';
type SkinProps = PropsWithChildren<{
className?: string;
}>;
export default function MediaSkinToasted({ children, className = '' }: SkinProps): JSX.Element {
return (
<MediaContainer className={`${styles.MediaContainer} ${className}`}>
{children}
<div className={styles.Overlay} aria-hidden="true" />
<div className={styles.Controls}>
<PlayButton className={`${styles.Button} ${styles.IconButton} ${styles.PlayButton}`}>
<PlayIcon className={`${styles.PlayIcon} ${styles.Icon}`} />
<PauseIcon className={`${styles.PauseIcon} ${styles.Icon}`} />
</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>
<TimeSlider.Root className={`${styles.SliderRoot} ${styles.TimeSliderRoot}`}>
<TimeSlider.Track className={styles.SliderTrack}>
<TimeSlider.Progress className={styles.SliderProgress} />
<TimeSlider.Pointer className={styles.SliderPointer} />
</TimeSlider.Track>
<TimeSlider.Thumb className={`${styles.SliderThumb} ${styles.TimeSliderThumb}`} />
</TimeSlider.Root>
<div className={styles.ButtonGroup}>
<MuteButton className={`${styles.Button} ${styles.IconButton} ${styles.MuteButton}`}>
<VolumeHighIcon className={`${styles.VolumeHighIcon} ${styles.Icon}`} />
<VolumeLowIcon className={`${styles.VolumeLowIcon} ${styles.Icon}`} />
<VolumeOffIcon className={`${styles.VolumeOffIcon} ${styles.Icon}`} />
</MuteButton>
<FullscreenButton className={`${styles.Button} ${styles.IconButton} ${styles.FullscreenButton}`}>
<FullscreenEnterIcon className={`${styles.FullscreenEnterIcon} ${styles.Icon}`} />
<FullscreenExitIcon className={`${styles.FullscreenExitIcon} ${styles.Icon}`} />
</FullscreenButton>
</div>
</div>
</MediaContainer>
);
}
@@ -0,0 +1 @@
export { default as MediaSkinToasted } from './MediaSkinToasted';
@@ -0,0 +1,121 @@
import type { MediaToastedSkinStyles } from './types';
// NOTE: Removing import to sidestep for compiler complexity (CJP)
// import { cn } from '../../utils/cn';
// 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.
function cn(...classes: (string | undefined)[]): string {
return classes.filter(Boolean).join(' ');
}
const styles: MediaToastedSkinStyles = {
MediaContainer: cn(
'relative @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',
'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',
),
Overlay: cn(
'absolute inset-0 rounded-[inherit] bg-black/30',
'bg-gradient-to-t from-black/30 to-transparent to-[120px]',
'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',
'group-hover/root:opacity-100 group-hover/root:delay-0 group-hover/root:duration-75',
),
Controls: cn(
'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-0 bottom-0 flex items-center gap-3.5 z-20 px-6 pb-6 pt-10 text-white text-shadow',
'shadow-sm shadow-black/15',
// Animation
'transition ease-in-out',
// FIXME: Temporary hide/show logic
'translate-y-full opacity-0 delay-500 duration-300',
'has-[[data-paused]]:translate-y-0 has-[[data-paused]]:opacity-100 has-[[data-paused]]:delay-0 has-[[data-paused]]:duration-75',
'group-hover/root:translate-y-0 group-hover/root:opacity-100 group-hover/root:delay-0 group-hover/root:duration-75',
),
Icon: cn('icon'),
Button: cn(
'group/button cursor-pointer relative shrink-0 transition select-none p-2 rounded-md',
// Background/foreground
'bg-transparent text-white',
// Hover and focus states
'hover:text-white/70 focus-visible:text-white/70',
// Focus state
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white',
// 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:text-white/70',
// Pressed state
'active:scale-95',
),
ButtonGroup: cn('flex items-center gap-1.5'),
IconButton: cn(
'grid [&_.icon]:[grid-area:1/1]',
'[&_.icon]:shrink-0 [&_.icon]:transition [&_.icon]:duration-300 [&_.icon]:ease-out [&_.icon]:drop-shadow-[0_1px_0_var(--tw-shadow-color)] [&_.icon]: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'),
MuteButton: cn(
'[&_.icon]: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',
),
TimeSliderRoot: cn('mx-2'),
TimeSliderThumb: cn('opacity-0'),
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/10',
'[&[data-orientation="horizontal"]]:w-full [&[data-orientation="horizontal"]]:h-1',
'[&[data-orientation="vertical"]]:w-1',
),
SliderProgress: cn('bg-white rounded-[inherit]'),
SliderPointer: cn('hidden'),
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 Overlay: string;
readonly Controls: string;
readonly Icon: string;
readonly Button: string;
readonly ButtonGroup: string;
readonly IconButton: string;
readonly PlayButton: string;
readonly PlayIcon: string;
readonly PauseIcon: string;
readonly MuteButton: string;
readonly VolumeHighIcon: string;
readonly VolumeLowIcon: string;
readonly VolumeOffIcon: string;
readonly FullscreenButton: string;
readonly FullscreenEnterIcon: string;
readonly FullscreenExitIcon: string;
readonly TimeSliderRoot: string;
readonly TimeSliderThumb: string;
readonly TimeDisplay: string;
readonly SliderRoot: string;
readonly SliderTrack: string;
readonly SliderProgress: string;
readonly SliderPointer: string;
readonly SliderThumb: string;
}