feat(skin): improved responsive design

This commit is contained in:
Sam Potts
2026-07-15 08:23:53 +10:00
parent 82b9e43bb5
commit a9b097d9de
100 changed files with 1559 additions and 1260 deletions
@@ -265,14 +265,14 @@ export function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNod
</div>
<div className={buttonGroup}>
<VolumePopover />
<Menu.Root side="top" align="center" boundary="viewport">
<PlaybackRateTrigger />
<Menu.Content className={cn(popup.popover, menu.root)}>
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Root>
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
@@ -216,14 +216,14 @@ export function MinimalAudioSkin(props: MinimalAudioSkinProps): ReactNode {
</div>
<div className="media-button-group">
<VolumePopover />
<Menu.Root side="top" align="center" boundary="viewport">
<PlaybackRateTrigger />
<Menu.Content className="media-popover media-menu media-menu--playback-rate">
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Root>
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
@@ -215,7 +215,7 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} render={<Button />}>
<SeekButton seconds={-SEEK_TIME} render={<Button className="@max-[32rem]/media-root:hidden" />}>
<span className={iconContainer}>
<SeekIcon className={cn(icon, iconFlipped)} />
<span className={cn(seek.label, seek.labelBackward)}>{SEEK_TIME}</span>
@@ -232,7 +232,7 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} render={<Button />}>
<SeekButton seconds={SEEK_TIME} render={<Button className="@max-[32rem]/media-root:hidden" />}>
<span className={iconContainer}>
<SeekIcon className={icon} />
<span className={cn(seek.label, seek.labelForward)}>{SEEK_TIME}</span>
@@ -126,10 +126,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Root openOnHover delay={200} closeDelay={100} side="right">
<Popover.Trigger render={muteButton} />
<Popover.Popup className={cn(popup.volume)}>
<VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
<VolumeSlider.Root orientation="horizontal" thumbAlignment="edge" render={<SliderRoot />}>
<VolumeSlider.Track render={<SliderTrack />}>
<VolumeSlider.Fill render={<SliderFill />} />
</VolumeSlider.Track>
@@ -262,13 +262,13 @@ export function MinimalLiveVideoSkinTailwind(props: MinimalLiveVideoSkinProps):
</Tooltip.Root>
<LiveButton className={cn(button.base, button.subtle, button.live)} />
<VolumePopover />
</div>
<div className="grow" aria-hidden="true" />
<div className={buttonGroupEnd}>
<VolumePopover />
<CaptionsTrigger />
<Tooltip.Root side="top">
@@ -79,10 +79,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Root openOnHover delay={200} closeDelay={100} side="right">
<Popover.Trigger render={muteButton} />
<Popover.Popup className="media-popover media-popover--volume">
<VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
<VolumeSlider.Root className="media-slider" orientation="horizontal" thumbAlignment="edge">
<VolumeSlider.Track className="media-slider__track">
<VolumeSlider.Fill className="media-slider__fill" />
</VolumeSlider.Track>
@@ -228,13 +228,13 @@ export function MinimalLiveVideoSkin(props: MinimalLiveVideoSkinProps): ReactNod
</Tooltip.Root>
<LiveButton className="media-button media-button--subtle media-button--live" />
<VolumePopover />
</div>
<div className="media-time-controls" aria-hidden="true" />
<div className="media-button-group">
<VolumePopover />
<CaptionsTrigger />
<Tooltip.Root side="top">
@@ -173,7 +173,7 @@ export function LiveVideoSkin(props: LiveVideoSkinProps): ReactNode {
return (
<Container
className={cn('media-default-skin media-default-skin--video', className)}
className={cn('media-default-skin media-default-skin--video media-default-skin--live', className)}
style={containerStyle}
{...rest}
>
@@ -205,7 +205,7 @@ export function LiveVideoSkin(props: LiveVideoSkinProps): ReactNode {
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root className="media-surface media-controls">
<Controls.Root className="media-surface media-controls media-controls--primary">
<Tooltip.Provider>
<div className="media-button-group">
<Tooltip.Root side="top">
@@ -7,7 +7,6 @@ import {
controls,
error,
icon,
iconContainer,
iconFlipped,
iconState,
inputFeedback,
@@ -16,7 +15,6 @@ import {
popup,
poster,
root,
seek,
slider,
thumbnail,
time,
@@ -43,7 +41,6 @@ import {
PlayIcon,
QualityIcon,
RestartIcon,
SeekIcon,
SpeechIcon,
SpeedIcon,
SpinnerIcon,
@@ -71,7 +68,6 @@ import { usePlaybackRateOptions } from '@/ui/playback-rate';
import { Popover } from '@/ui/popover';
import { Poster } from '@/ui/poster';
import { useQualityOptions } from '@/ui/quality';
import { SeekButton } from '@/ui/seek-button';
import { SeekIndicator } from '@/ui/seek-indicator';
import { Slider } from '@/ui/slider';
import { StatusAnnouncer } from '@/ui/status-announcer';
@@ -151,10 +147,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Root openOnHover delay={200} closeDelay={100} side="right">
<Popover.Trigger render={muteButton} />
<Popover.Popup className={cn(popup.volume)}>
<VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
<Popover.Popup className={popup.volume}>
<VolumeSlider.Root orientation="horizontal" thumbAlignment="edge" render={<SliderRoot />}>
<VolumeSlider.Track render={<SliderTrack />}>
<VolumeSlider.Fill render={<SliderFill />} />
</VolumeSlider.Track>
@@ -184,11 +180,7 @@ function SettingsMenu(): ReactNode {
return (
<Menu.Root side="top" align="center">
<Menu.Trigger
aria-label={t('Settings')}
className="media-button--settings"
render={<Button className={cn(button.icon, menu.settingsTrigger)} />}
>
<Menu.Trigger aria-label={t('Settings')} render={<Button className={cn(button.icon, menu.settingsTrigger)} />}>
<GearIcon className={cn(icon, menu.settingsIcon)} />
</Menu.Trigger>
<Menu.Content className={menu.settings}>
@@ -198,7 +190,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="quality"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<QualityIcon className={cn(icon, menu.icon)} />
@@ -252,7 +244,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="audio-track"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<SpeechIcon className={icon} />
@@ -302,7 +294,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="playback-rate"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<SpeedIcon className={cn(icon, menu.icon)} />
@@ -352,7 +344,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="captions"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<CaptionsOffIcon className={cn(icon, menu.icon)} />
@@ -469,39 +461,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={cn(icon, iconFlipped)} />
<span className={cn(seek.label, seek.labelBackward)}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={cn(popup.tooltip)}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={icon} />
<span className={cn(seek.label, seek.labelForward)}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={cn(popup.tooltip)}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<VolumePopover />
</div>
<div className={time.controls}>
@@ -531,8 +491,6 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
</div>
<div className={cn(buttonGroupEnd, menu.settingsGroup)}>
<VolumePopover />
<SettingsMenu />
<Tooltip.Root side="top">
@@ -20,7 +20,6 @@ import {
PlayIcon,
QualityIcon,
RestartIcon,
SeekIcon,
SpeechIcon,
SpeedIcon,
SpinnerIcon,
@@ -48,7 +47,6 @@ import { usePlaybackRateOptions } from '@/ui/playback-rate';
import { Popover } from '@/ui/popover';
import { Poster } from '@/ui/poster';
import { useQualityOptions } from '@/ui/quality';
import { SeekButton } from '@/ui/seek-button';
import { SeekIndicator } from '@/ui/seek-indicator';
import { Slider } from '@/ui/slider';
import { StatusAnnouncer } from '@/ui/status-announcer';
@@ -92,10 +90,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Root openOnHover delay={200} closeDelay={100} side="right">
<Popover.Trigger render={muteButton} />
<Popover.Popup className="media-popover media-popover--volume">
<VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
<VolumeSlider.Root className="media-slider" orientation="horizontal" thumbAlignment="edge">
<VolumeSlider.Track className="media-slider__track">
<VolumeSlider.Fill className="media-slider__fill" />
</VolumeSlider.Track>
@@ -401,39 +399,7 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<VolumePopover />
</div>
<div className="media-time-controls">
@@ -457,6 +423,7 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
<TimeSlider.Value type="pointer" className="media-time media-thumbnail__time" />
<SpinnerIcon className="media-thumbnail__spinner media-icon" />
</div>
<TimeSlider.Preview className="media-slider__preview">
<TimeSlider.Value type="pointer" className="media-time media-slider__value" />
</TimeSlider.Preview>
@@ -464,8 +431,6 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
</div>
<div className="media-button-group">
<VolumePopover />
<SettingsMenu />
<Tooltip.Root side="top">
@@ -16,6 +16,7 @@ import {
popup,
poster,
root,
secondaryControls,
seek,
slider,
thumbnail,
@@ -184,11 +185,7 @@ function SettingsMenu(): ReactNode {
return (
<Menu.Root side="top" align="center">
<Menu.Trigger
aria-label={t('Settings')}
className="media-button--settings"
render={<Button className={cn(button.icon, menu.settingsTrigger)} />}
>
<Menu.Trigger aria-label={t('Settings')} render={<Button className={cn(button.icon, menu.settingsTrigger)} />}>
<GearIcon className={cn(icon, menu.settingsIcon)} />
</Menu.Trigger>
<Menu.Content className={menu.settings}>
@@ -198,7 +195,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="quality"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<QualityIcon className={cn(icon, menu.icon)} />
@@ -252,7 +249,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="audio-track"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<SpeechIcon className={icon} />
@@ -302,7 +299,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="playback-rate"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<SpeedIcon className={cn(icon, menu.icon)} />
@@ -352,7 +349,7 @@ function SettingsMenu(): ReactNode {
<Menu.Root>
<Menu.Trigger
type="captions"
className={cn(menu.item, 'media-menu__item--submenu')}
className={menu.item}
render={(props) => (
<div {...props}>
<CaptionsOffIcon className={cn(icon, menu.icon)} />
@@ -447,6 +444,72 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root data-controls="" className={secondaryControls}>
<Tooltip.Provider>
<div className={buttonGroupEnd}>
<Tooltip.Root side="bottom">
<Tooltip.Trigger
render={
<CastButton className={iconState.cast.button} render={<Button />}>
<CastEnterIcon className={cn(icon, iconState.cast.enter)} />
<CastExitIcon className={cn(icon, iconState.cast.exit)} />
</CastButton>
}
/>
<Tooltip.Popup className={cn(popup.tooltip)}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="bottom">
<Tooltip.Trigger
render={
<AirPlayButton className={iconState.airplay.button} render={<Button />}>
<AirPlayEnterIcon className={cn(icon, iconState.airplay.enter)} />
<AirPlayExitIcon className={cn(icon, iconState.airplay.exit)} />
</AirPlayButton>
}
/>
<Tooltip.Popup className={cn(popup.tooltip)}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="bottom">
<Tooltip.Trigger
render={
<PiPButton className={iconState.pip.button} render={<Button />}>
<PipEnterIcon className={cn(icon, iconState.pip.off)} />
<PipExitIcon className={cn(icon, iconState.pip.on)} />
</PiPButton>
}
/>
<Tooltip.Popup className={cn(popup.tooltip)}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="bottom">
<Tooltip.Trigger
render={
<FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
<FullscreenEnterIcon className={cn(icon, iconState.fullscreen.enter)} />
<FullscreenExitIcon className={cn(icon, iconState.fullscreen.exit)} />
</FullscreenButton>
}
/>
<Tooltip.Popup className={cn(popup.tooltip)}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</div>
</Tooltip.Provider>
</Controls.Root>
<Controls.Root
data-controls="" // Used as a hook for Tailwind has-[] styles
className={controls}
@@ -472,7 +535,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} render={<Button />}>
<SeekButton seconds={-SEEK_TIME} render={<Button className="@max-[32rem]/media-root:hidden" />}>
<span className={iconContainer}>
<SeekIcon className={cn(icon, iconFlipped)} />
<span className={cn(seek.label, seek.labelBackward)}>{SEEK_TIME}</span>
@@ -489,7 +552,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} render={<Button />}>
<SeekButton seconds={SEEK_TIME} render={<Button className="@max-[32rem]/media-root:hidden" />}>
<span className={iconContainer}>
<SeekIcon className={icon} />
<span className={cn(seek.label, seek.labelForward)}>{SEEK_TIME}</span>
@@ -532,7 +595,10 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CastButton className={iconState.cast.button} render={<Button />}>
<CastButton
className={cn(iconState.cast.button, '@max-[32rem]/media-root:hidden')}
render={<Button />}
>
<CastEnterIcon className={cn(icon, iconState.cast.enter)} />
<CastExitIcon className={cn(icon, iconState.cast.exit)} />
</CastButton>
@@ -547,7 +613,10 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<AirPlayButton className={iconState.airplay.button} render={<Button />}>
<AirPlayButton
className={cn(iconState.airplay.button, '@max-[32rem]/media-root:hidden')}
render={<Button />}
>
<AirPlayEnterIcon className={cn(icon, iconState.airplay.enter)} />
<AirPlayExitIcon className={cn(icon, iconState.airplay.exit)} />
</AirPlayButton>
@@ -562,7 +631,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PiPButton className={iconState.pip.button} render={<Button />}>
<PiPButton className={cn(iconState.pip.button, '@max-[32rem]/media-root:hidden')} render={<Button />}>
<PipEnterIcon className={cn(icon, iconState.pip.off)} />
<PipExitIcon className={cn(icon, iconState.pip.on)} />
</PiPButton>
@@ -577,7 +646,10 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
<FullscreenButton
className={cn(iconState.fullscreen.button, '@max-[32rem]/media-root:hidden')}
render={<Button />}
>
<FullscreenEnterIcon className={cn(icon, iconState.fullscreen.enter)} />
<FullscreenExitIcon className={cn(icon, iconState.fullscreen.exit)} />
</FullscreenButton>
+92 -62
View File
@@ -1,3 +1,4 @@
import type { TooltipProps } from '@videojs/core';
import { isString } from '@videojs/utils/predicate';
import { cn } from '@videojs/utils/style';
import { type ComponentProps, type CSSProperties, forwardRef, type ReactNode } from 'react';
@@ -341,6 +342,85 @@ function SettingsMenu(): ReactNode {
);
}
type ControlProps = {
tooltipSide?: TooltipProps['side'];
};
function CastControl({ tooltipSide = 'top' }: ControlProps) {
return (
<Tooltip.Root side={tooltipSide}>
<Tooltip.Trigger
render={
<CastButton className="media-button--cast" render={<Button />}>
<CastEnterIcon className="media-icon media-icon--cast-enter" />
<CastExitIcon className="media-icon media-icon--cast-exit" />
</CastButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
function AirPlayControl({ tooltipSide = 'top' }: ControlProps) {
return (
<Tooltip.Root side={tooltipSide}>
<Tooltip.Trigger
render={
<AirPlayButton className="media-button--airplay" render={<Button />}>
<AirPlayEnterIcon className="media-icon media-icon--airplay-enter" />
<AirPlayExitIcon className="media-icon media-icon--airplay-exit" />
</AirPlayButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
function PiPControl({ tooltipSide = 'top' }: ControlProps) {
return (
<Tooltip.Root side={tooltipSide}>
<Tooltip.Trigger
render={
<PiPButton className="media-button--pip" render={<Button />}>
<PipEnterIcon className="media-icon media-icon--pip-enter" />
<PipExitIcon className="media-icon media-icon--pip-exit" />
</PiPButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
function FullscreenControl({ tooltipSide = 'top' }: ControlProps) {
return (
<Tooltip.Root side={tooltipSide}>
<Tooltip.Trigger
render={
<FullscreenButton className="media-button--fullscreen" render={<Button />}>
<FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
<FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
</FullscreenButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
export function VideoSkin(props: VideoSkinProps): ReactNode {
const { children, className, poster, placeholder, style, ...rest } = props;
@@ -382,7 +462,14 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root className="media-surface media-controls">
<Controls.Root className="media-surface media-controls media-controls--secondary">
<CastControl tooltipSide="bottom" />
<AirPlayControl tooltipSide="bottom" />
<PiPControl tooltipSide="bottom" />
<FullscreenControl tooltipSide="bottom" />
</Controls.Root>
<Controls.Root className="media-surface media-controls media-controls--primary">
<Tooltip.Provider>
<div className="media-button-group">
<Tooltip.Root side="top">
@@ -459,68 +546,11 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
<div className="media-button-group">
<VolumePopover />
<SettingsMenu />
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CastButton className="media-button--cast" render={<Button />}>
<CastEnterIcon className="media-icon media-icon--cast-enter" />
<CastExitIcon className="media-icon media-icon--cast-exit" />
</CastButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<AirPlayButton className="media-button--airplay" render={<Button />}>
<AirPlayEnterIcon className="media-icon media-icon--airplay-enter" />
<AirPlayExitIcon className="media-icon media-icon--airplay-exit" />
</AirPlayButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PiPButton className="media-button--pip" render={<Button />}>
<PipEnterIcon className="media-icon media-icon--pip-enter" />
<PipExitIcon className="media-icon media-icon--pip-exit" />
</PiPButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<FullscreenButton className="media-button--fullscreen" render={<Button />}>
<FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
<FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
</FullscreenButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<CastControl />
<AirPlayControl />
<PiPControl />
<FullscreenControl />
</div>
</Tooltip.Provider>
</Controls.Root>