mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(skin): add error handling for audio players (#1048)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
559d5169b5
commit
df927f67fc
@@ -12,6 +12,7 @@ import {
|
||||
button,
|
||||
buttonGroup,
|
||||
controls,
|
||||
error,
|
||||
icon,
|
||||
iconContainer,
|
||||
iconFlipped,
|
||||
@@ -34,23 +35,26 @@ import { Time } from '@/ui/time';
|
||||
import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { MinimalAudioSkinProps } from './minimal-skin';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: error.root,
|
||||
dialog: error.dialog,
|
||||
content: error.content,
|
||||
title: error.title,
|
||||
description: error.description,
|
||||
actions: error.actions,
|
||||
close: cn(button.base, button.subtle),
|
||||
};
|
||||
|
||||
/* --------------------------------------- Components ---------------------------------------- */
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'> & { variant?: 'icon' }>(function Button(
|
||||
{ className, variant, ...props },
|
||||
ref
|
||||
) {
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(button.base, variant === 'icon' ? button.icon : button.default, className)}
|
||||
{...props}
|
||||
/>
|
||||
<button ref={ref} type="button" className={cn(button.base, button.subtle, button.icon, className)} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -106,7 +110,7 @@ function VolumePopover(): ReactNode {
|
||||
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
|
||||
|
||||
const muteButton = (
|
||||
<MuteButton className={iconState.mute.button} render={<Button variant="icon" />}>
|
||||
<MuteButton className={iconState.mute.button} render={<Button />}>
|
||||
<VolumeOffIcon className={cn(icon, iconState.mute.volumeOff)} />
|
||||
<VolumeLowIcon className={cn(icon, iconState.mute.volumeLow)} />
|
||||
<VolumeHighIcon className={cn(icon, iconState.mute.volumeHigh)} />
|
||||
@@ -139,13 +143,15 @@ export function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNod
|
||||
<Container className={cn(root, className)} {...rest}>
|
||||
{children}
|
||||
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<div className={controls}>
|
||||
<Tooltip.Provider>
|
||||
<div className={buttonGroup}>
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<PlayButton className={iconState.play.button} render={<Button variant="icon" />}>
|
||||
<PlayButton className={iconState.play.button} render={<Button />}>
|
||||
<RestartIcon className={cn(icon, iconState.play.restart)} />
|
||||
<PlayIcon className={cn(icon, iconState.play.play)} />
|
||||
<PauseIcon className={cn(icon, iconState.play.pause)} />
|
||||
@@ -160,7 +166,7 @@ export function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={cn(icon, iconFlipped)} />
|
||||
<span className={cn(seek.label, seek.labelBackward)}>{SEEK_TIME}</span>
|
||||
@@ -174,7 +180,7 @@ export function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={icon} />
|
||||
<span className={cn(seek.label, seek.labelForward)}>{SEEK_TIME}</span>
|
||||
@@ -204,9 +210,7 @@ export function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNod
|
||||
|
||||
<div className={buttonGroup}>
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={<PlaybackRateButton className={playbackRate.button} render={<Button variant="icon" />} />}
|
||||
/>
|
||||
<Tooltip.Trigger render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />} />
|
||||
<Tooltip.Popup className={cn(popup.tooltip)}>Toggle playback rate</Tooltip.Popup>
|
||||
</Tooltip.Root>
|
||||
|
||||
|
||||
@@ -19,14 +19,32 @@ import { Time } from '@/ui/time';
|
||||
import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { BaseSkinProps } from '../types';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
export type MinimalAudioSkinProps = BaseSkinProps;
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: 'media-error',
|
||||
dialog: 'media-error__dialog',
|
||||
content: 'media-error__content',
|
||||
title: 'media-error__title',
|
||||
description: 'media-error__description',
|
||||
actions: 'media-error__actions',
|
||||
close: 'media-button media-button--subtle',
|
||||
};
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return <button ref={ref} type="button" className={cn('media-button media-button--icon', className)} {...props} />;
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn('media-button media-button--subtle media-button--icon', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
function PlayLabel(): string {
|
||||
@@ -71,6 +89,8 @@ export function MinimalAudioSkin(props: MinimalAudioSkinProps): ReactNode {
|
||||
<Container className={cn('media-minimal-skin media-minimal-skin--audio', className)} {...rest}>
|
||||
{children}
|
||||
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<div className="media-controls">
|
||||
<Tooltip.Provider>
|
||||
<div className="media-button-group">
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import {
|
||||
button,
|
||||
controls,
|
||||
error,
|
||||
icon,
|
||||
iconContainer,
|
||||
iconFlipped,
|
||||
@@ -33,23 +34,26 @@ import { Time } from '@/ui/time';
|
||||
import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { AudioSkinProps } from './skin';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: error.root,
|
||||
dialog: error.dialog,
|
||||
content: error.content,
|
||||
title: error.title,
|
||||
description: error.description,
|
||||
actions: error.actions,
|
||||
close: cn(button.base, button.subtle),
|
||||
};
|
||||
|
||||
/* --------------------------------------- Components ---------------------------------------- */
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'> & { variant?: 'icon' }>(function Button(
|
||||
{ className, variant, ...props },
|
||||
ref
|
||||
) {
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(button.base, variant === 'icon' ? button.icon : button.default, className)}
|
||||
{...props}
|
||||
/>
|
||||
<button ref={ref} type="button" className={cn(button.base, button.subtle, button.icon, className)} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -105,7 +109,7 @@ function VolumePopover(): ReactNode {
|
||||
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
|
||||
|
||||
const muteButton = (
|
||||
<MuteButton className={iconState.mute.button} render={<Button variant="icon" />}>
|
||||
<MuteButton className={iconState.mute.button} render={<Button />}>
|
||||
<VolumeOffIcon className={cn(icon, iconState.mute.volumeOff)} />
|
||||
<VolumeLowIcon className={cn(icon, iconState.mute.volumeLow)} />
|
||||
<VolumeHighIcon className={cn(icon, iconState.mute.volumeHigh)} />
|
||||
@@ -138,12 +142,14 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
|
||||
<Container className={cn(root, className)} {...rest}>
|
||||
{children}
|
||||
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<div className={controls}>
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<PlayButton className={iconState.play.button} render={<Button variant="icon" />}>
|
||||
<PlayButton className={iconState.play.button} render={<Button />}>
|
||||
<RestartIcon className={cn(icon, iconState.play.restart)} />
|
||||
<PlayIcon className={cn(icon, iconState.play.play)} />
|
||||
<PauseIcon className={cn(icon, iconState.play.pause)} />
|
||||
@@ -158,7 +164,7 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={cn(icon, iconFlipped)} />
|
||||
<span className={cn(seek.label, seek.labelBackward)}>{SEEK_TIME}</span>
|
||||
@@ -172,7 +178,7 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={icon} />
|
||||
<span className={cn(seek.label, seek.labelForward)}>{SEEK_TIME}</span>
|
||||
@@ -196,9 +202,7 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
|
||||
</Time.Group>
|
||||
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={<PlaybackRateButton className={playbackRate.button} render={<Button variant="icon" />} />}
|
||||
/>
|
||||
<Tooltip.Trigger render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />} />
|
||||
<Tooltip.Popup className={cn(popup.tooltip)}>Toggle playback rate</Tooltip.Popup>
|
||||
</Tooltip.Root>
|
||||
|
||||
|
||||
@@ -19,14 +19,32 @@ import { Time } from '@/ui/time';
|
||||
import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { BaseSkinProps } from '../types';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: 'media-error',
|
||||
dialog: 'media-error__dialog',
|
||||
content: 'media-error__content',
|
||||
title: 'media-error__title',
|
||||
description: 'media-error__description',
|
||||
actions: 'media-error__actions',
|
||||
close: 'media-button media-button--subtle',
|
||||
};
|
||||
|
||||
export type AudioSkinProps = BaseSkinProps;
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return <button ref={ref} type="button" className={cn('media-button media-button--icon', className)} {...props} />;
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn('media-button media-button--subtle media-button--icon', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
function PlayLabel(): string {
|
||||
@@ -71,6 +89,8 @@ export function AudioSkin(props: AudioSkinProps): ReactNode {
|
||||
<Container className={cn('media-default-skin media-default-skin--audio', className)} {...rest}>
|
||||
{children}
|
||||
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<div className="media-surface media-controls">
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root side="top">
|
||||
|
||||
+11
-11
@@ -5,7 +5,7 @@ import { type ReactNode, useRef } from 'react';
|
||||
import { usePlayer } from '@/player/context';
|
||||
import { AlertDialog } from '@/ui/alert-dialog';
|
||||
|
||||
export interface ErrorDialogClasses {
|
||||
export interface ErrorDialogClassNames {
|
||||
root?: string;
|
||||
dialog?: string;
|
||||
content?: string;
|
||||
@@ -15,7 +15,7 @@ export interface ErrorDialogClasses {
|
||||
close?: string;
|
||||
}
|
||||
|
||||
export function ErrorDialog({ classes }: { classes?: ErrorDialogClasses }): ReactNode {
|
||||
export function ErrorDialog({ classNames }: { classNames?: ErrorDialogClassNames }): ReactNode {
|
||||
const errorState = usePlayer(selectError);
|
||||
const lastError = useRef(errorState?.error);
|
||||
|
||||
@@ -25,21 +25,21 @@ export function ErrorDialog({ classes }: { classes?: ErrorDialogClasses }): Reac
|
||||
|
||||
return (
|
||||
<AlertDialog.Root
|
||||
open={!!errorState.error}
|
||||
open={Boolean(errorState.error)}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) errorState.dismissError();
|
||||
}}
|
||||
>
|
||||
<AlertDialog.Popup className={classes?.root}>
|
||||
<div className={classes?.dialog}>
|
||||
<div className={classes?.content}>
|
||||
<AlertDialog.Title className={classes?.title}>Something went wrong.</AlertDialog.Title>
|
||||
<AlertDialog.Description className={classes?.description}>
|
||||
{lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}
|
||||
<AlertDialog.Popup className={classNames?.root}>
|
||||
<div className={classNames?.dialog}>
|
||||
<div className={classNames?.content}>
|
||||
<AlertDialog.Title className={classNames?.title}>Something went wrong.</AlertDialog.Title>
|
||||
<AlertDialog.Description className={classNames?.description}>
|
||||
{lastError.current?.message ?? 'An error occurred. Please try again.'}
|
||||
</AlertDialog.Description>
|
||||
</div>
|
||||
<div className={classes?.actions}>
|
||||
<AlertDialog.Close className={classes?.close}>OK</AlertDialog.Close>
|
||||
<div className={classNames?.actions}>
|
||||
<AlertDialog.Close className={classNames?.close}>OK</AlertDialog.Close>
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialog.Popup>
|
||||
@@ -55,24 +55,26 @@ import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { isRenderProp } from '@/utils/use-render';
|
||||
import { ErrorDialog } from './error-dialog';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { MinimalVideoSkinProps } from './minimal-skin';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: error.root,
|
||||
dialog: error.dialog,
|
||||
content: error.content,
|
||||
title: error.title,
|
||||
description: error.description,
|
||||
actions: error.actions,
|
||||
close: cn(button.base, button.primary),
|
||||
};
|
||||
|
||||
/* --------------------------------------- Components ---------------------------------------- */
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'> & { variant?: 'icon' }>(function Button(
|
||||
{ className, variant, ...props },
|
||||
ref
|
||||
) {
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(button.base, variant === 'icon' ? button.icon : button.default, className)}
|
||||
{...props}
|
||||
/>
|
||||
<button ref={ref} type="button" className={cn(button.base, button.subtle, button.icon, className)} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -117,15 +119,6 @@ const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persist
|
||||
);
|
||||
});
|
||||
|
||||
const errorClasses = {
|
||||
root: error.root,
|
||||
dialog: error.dialog,
|
||||
content: error.content,
|
||||
title: error.title,
|
||||
actions: error.actions,
|
||||
close: cn(button.base, button.default),
|
||||
};
|
||||
|
||||
function PlayLabel(): string {
|
||||
const paused = usePlayer((s) => Boolean(s.paused));
|
||||
const ended = usePlayer((s) => Boolean(s.ended));
|
||||
@@ -152,7 +145,7 @@ function VolumePopover(): ReactNode {
|
||||
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
|
||||
|
||||
const muteButton = (
|
||||
<MuteButton className={iconState.mute.button} render={<Button variant="icon" />}>
|
||||
<MuteButton className={iconState.mute.button} render={<Button />}>
|
||||
<VolumeOffIcon className={cn(icon, iconState.mute.volumeOff)} />
|
||||
<VolumeLowIcon className={cn(icon, iconState.mute.volumeLow)} />
|
||||
<VolumeHighIcon className={cn(icon, iconState.mute.volumeHigh)} />
|
||||
@@ -201,7 +194,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
)}
|
||||
/>
|
||||
|
||||
<ErrorDialog classes={errorClasses} />
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<Controls.Root
|
||||
data-controls="" // Used as a hook for Tailwind has-[] styles
|
||||
@@ -212,7 +205,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<PlayButton className={iconState.play.button} render={<Button variant="icon" />}>
|
||||
<PlayButton className={iconState.play.button} render={<Button />}>
|
||||
<RestartIcon className={cn(icon, iconState.play.restart)} />
|
||||
<PlayIcon className={cn(icon, iconState.play.play)} />
|
||||
<PauseIcon className={cn(icon, iconState.play.pause)} />
|
||||
@@ -227,7 +220,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={cn(icon, iconFlipped)} />
|
||||
<span className={cn(seek.label, seek.labelBackward)}>{SEEK_TIME}</span>
|
||||
@@ -241,7 +234,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={icon} />
|
||||
<span className={cn(seek.label, seek.labelForward)}>{SEEK_TIME}</span>
|
||||
@@ -278,9 +271,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
|
||||
<div className={buttonGroup}>
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={<PlaybackRateButton className={playbackRate.button} render={<Button variant="icon" />} />}
|
||||
/>
|
||||
<Tooltip.Trigger render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />} />
|
||||
<Tooltip.Popup className={cn(popup.tooltip)}>Toggle playback rate</Tooltip.Popup>
|
||||
</Tooltip.Root>
|
||||
|
||||
@@ -289,7 +280,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<CaptionsButton className={iconState.captions.button} render={<Button variant="icon" />}>
|
||||
<CaptionsButton className={iconState.captions.button} render={<Button />}>
|
||||
<CaptionsOffIcon className={cn(icon, iconState.captions.off)} />
|
||||
<CaptionsOnIcon className={cn(icon, iconState.captions.on)} />
|
||||
</CaptionsButton>
|
||||
@@ -303,7 +294,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<PiPButton className={iconState.pip.button} render={<Button variant="icon" />}>
|
||||
<PiPButton className={iconState.pip.button} render={<Button />}>
|
||||
<PipEnterIcon className={cn(icon, iconState.pip.off)} />
|
||||
<PipExitIcon className={cn(icon, iconState.pip.on)} />
|
||||
</PiPButton>
|
||||
@@ -317,7 +308,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<FullscreenButton className={iconState.fullscreen.button} render={<Button variant="icon" />}>
|
||||
<FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
|
||||
<FullscreenEnterIcon className={cn(icon, iconState.fullscreen.enter)} />
|
||||
<FullscreenExitIcon className={cn(icon, iconState.fullscreen.exit)} />
|
||||
</FullscreenButton>
|
||||
|
||||
@@ -35,27 +35,34 @@ import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { isRenderProp } from '@/utils/use-render';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { BaseVideoSkinProps } from '../types';
|
||||
import { ErrorDialog } from './error-dialog';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
export type MinimalVideoSkinProps = BaseVideoSkinProps;
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return <button ref={ref} type="button" className={cn('media-button media-button--icon', className)} {...props} />;
|
||||
});
|
||||
|
||||
const errorClasses = {
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: 'media-error',
|
||||
dialog: 'media-error__dialog',
|
||||
content: 'media-error__content',
|
||||
title: 'media-error__title',
|
||||
description: 'media-error__description',
|
||||
actions: 'media-error__actions',
|
||||
close: 'media-button',
|
||||
close: 'media-button media-button--subtle',
|
||||
};
|
||||
|
||||
export type MinimalVideoSkinProps = BaseVideoSkinProps;
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn('media-button media-button--subtle media-button--icon', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
function PlayLabel(): string {
|
||||
const paused = usePlayer((s) => Boolean(s.paused));
|
||||
const ended = usePlayer((s) => Boolean(s.ended));
|
||||
@@ -125,7 +132,7 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
|
||||
)}
|
||||
/>
|
||||
|
||||
<ErrorDialog classes={errorClasses} />
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<Controls.Root className="media-controls">
|
||||
<Tooltip.Provider>
|
||||
|
||||
@@ -54,24 +54,26 @@ import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { isRenderProp } from '@/utils/use-render';
|
||||
import { ErrorDialog } from './error-dialog';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { VideoSkinProps } from './skin';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: error.root,
|
||||
dialog: error.dialog,
|
||||
content: error.content,
|
||||
title: error.title,
|
||||
description: error.description,
|
||||
actions: error.actions,
|
||||
close: cn(button.base, button.primary),
|
||||
};
|
||||
|
||||
/* --------------------------------------- Components ---------------------------------------- */
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'> & { variant?: 'icon' }>(function Button(
|
||||
{ className, variant, ...props },
|
||||
ref
|
||||
) {
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(button.base, variant === 'icon' ? button.icon : button.default, className)}
|
||||
{...props}
|
||||
/>
|
||||
<button ref={ref} type="button" className={cn(button.base, button.subtle, button.icon, className)} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -116,16 +118,6 @@ const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persist
|
||||
);
|
||||
});
|
||||
|
||||
const errorClasses = {
|
||||
root: error.root,
|
||||
dialog: error.dialog,
|
||||
content: error.content,
|
||||
title: error.title,
|
||||
description: error.description,
|
||||
actions: error.actions,
|
||||
close: cn(button.base, button.default),
|
||||
};
|
||||
|
||||
function PlayLabel(): string {
|
||||
const paused = usePlayer((s) => Boolean(s.paused));
|
||||
const ended = usePlayer((s) => Boolean(s.ended));
|
||||
@@ -152,7 +144,7 @@ function VolumePopover(): ReactNode {
|
||||
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
|
||||
|
||||
const muteButton = (
|
||||
<MuteButton className={iconState.mute.button} render={<Button variant="icon" />}>
|
||||
<MuteButton className={iconState.mute.button} render={<Button />}>
|
||||
<VolumeOffIcon className={cn(icon, iconState.mute.volumeOff)} />
|
||||
<VolumeLowIcon className={cn(icon, iconState.mute.volumeLow)} />
|
||||
<VolumeHighIcon className={cn(icon, iconState.mute.volumeHigh)} />
|
||||
@@ -203,7 +195,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
)}
|
||||
/>
|
||||
|
||||
<ErrorDialog classes={errorClasses} />
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<Controls.Root
|
||||
data-controls="" // Used as a hook for Tailwind has-[] styles
|
||||
@@ -213,7 +205,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<PlayButton className={iconState.play.button} render={<Button variant="icon" />}>
|
||||
<PlayButton className={iconState.play.button} render={<Button />}>
|
||||
<RestartIcon className={cn(icon, iconState.play.restart)} />
|
||||
<PlayIcon className={cn(icon, iconState.play.play)} />
|
||||
<PauseIcon className={cn(icon, iconState.play.pause)} />
|
||||
@@ -228,7 +220,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={-SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={cn(icon, iconFlipped)} />
|
||||
<span className={cn(seek.label, seek.labelBackward)}>{SEEK_TIME}</span>
|
||||
@@ -242,7 +234,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button variant="icon" />}>
|
||||
<SeekButton seconds={SEEK_TIME} className={seek.button} render={<Button />}>
|
||||
<span className={iconContainer}>
|
||||
<SeekIcon className={icon} />
|
||||
<span className={cn(seek.label, seek.labelForward)}>{SEEK_TIME}</span>
|
||||
@@ -271,9 +263,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
</Time.Group>
|
||||
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={<PlaybackRateButton className={playbackRate.button} render={<Button variant="icon" />} />}
|
||||
/>
|
||||
<Tooltip.Trigger render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />} />
|
||||
<Tooltip.Popup className={cn(popup.tooltip)}>Toggle playback rate</Tooltip.Popup>
|
||||
</Tooltip.Root>
|
||||
|
||||
@@ -282,7 +272,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<CaptionsButton className={iconState.captions.button} render={<Button variant="icon" />}>
|
||||
<CaptionsButton className={iconState.captions.button} render={<Button />}>
|
||||
<CaptionsOffIcon className={cn(icon, iconState.captions.off)} />
|
||||
<CaptionsOnIcon className={cn(icon, iconState.captions.on)} />
|
||||
</CaptionsButton>
|
||||
@@ -296,7 +286,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<PiPButton className={iconState.pip.button} render={<Button variant="icon" />}>
|
||||
<PiPButton className={iconState.pip.button} render={<Button />}>
|
||||
<PipEnterIcon className={cn(icon, iconState.pip.off)} />
|
||||
<PipExitIcon className={cn(icon, iconState.pip.on)} />
|
||||
</PiPButton>
|
||||
@@ -310,7 +300,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
|
||||
<Tooltip.Root side="top">
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<FullscreenButton className={iconState.fullscreen.button} render={<Button variant="icon" />}>
|
||||
<FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
|
||||
<FullscreenEnterIcon className={cn(icon, iconState.fullscreen.enter)} />
|
||||
<FullscreenExitIcon className={cn(icon, iconState.fullscreen.exit)} />
|
||||
</FullscreenButton>
|
||||
|
||||
@@ -35,27 +35,34 @@ import { TimeSlider } from '@/ui/time-slider';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { VolumeSlider } from '@/ui/volume-slider';
|
||||
import { isRenderProp } from '@/utils/use-render';
|
||||
import { ErrorDialog } from '../error-dialog';
|
||||
import type { BaseVideoSkinProps } from '../types';
|
||||
import { ErrorDialog } from './error-dialog';
|
||||
|
||||
const SEEK_TIME = 10;
|
||||
|
||||
export type VideoSkinProps = BaseVideoSkinProps;
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return <button ref={ref} type="button" className={cn('media-button media-button--icon', className)} {...props} />;
|
||||
});
|
||||
|
||||
const errorClasses = {
|
||||
const ERROR_CLASSNAMES = {
|
||||
root: 'media-error',
|
||||
dialog: 'media-error__dialog media-surface',
|
||||
dialog: 'media-error__dialog',
|
||||
content: 'media-error__content',
|
||||
title: 'media-error__title',
|
||||
description: 'media-error__description',
|
||||
actions: 'media-error__actions',
|
||||
close: 'media-button',
|
||||
close: 'media-button media-button--subtle',
|
||||
};
|
||||
|
||||
export type VideoSkinProps = BaseVideoSkinProps;
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn('media-button media-button--subtle media-button--icon', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
function PlayLabel(): string {
|
||||
const paused = usePlayer((s) => Boolean(s.paused));
|
||||
const ended = usePlayer((s) => Boolean(s.ended));
|
||||
@@ -127,7 +134,7 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
|
||||
)}
|
||||
/>
|
||||
|
||||
<ErrorDialog classes={errorClasses} />
|
||||
<ErrorDialog classNames={ERROR_CLASSNAMES} />
|
||||
|
||||
<Controls.Root className="media-surface media-controls">
|
||||
<Tooltip.Provider>
|
||||
|
||||
Reference in New Issue
Block a user