feat(skin): add error handling for audio players (#1048)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sam Potts
2026-03-23 09:33:55 +11:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 559d5169b5
commit df927f67fc
43 changed files with 648 additions and 377 deletions
@@ -1,5 +1,6 @@
import { cn } from '@videojs/utils/style';
import { controls as baseControls } from './components/controls';
import { error as baseError } from './components/error';
import { popup as basePopup } from './components/popup';
import { root as baseRoot } from './components/root';
@@ -11,10 +12,11 @@ export const root = cn(
baseRoot,
'[--media-controls-background-color:oklch(1_0_0)]',
'[--media-controls-border-color:oklch(0_0_0/0.1)]',
'[--media-controls-text-color:var(--media-color-primary,oklch(0_0_0))]',
'[--media-controls-padding:0.375rem]',
'[--media-text-color:var(--media-color-primary,oklch(0_0_0))]',
'dark:[--media-controls-background-color:oklch(0_0_0)]',
'dark:[--media-controls-border-color:oklch(1_0_0/0.1)]',
'dark:[--media-controls-text-color:var(--media-color-primary,oklch(1_0_0))]'
'dark:[--media-text-color:var(--media-color-primary,oklch(1_0_0))]'
);
/* ==========================================================================
@@ -25,10 +27,11 @@ export const controls = cn(
baseControls,
// Layout
'gap-2 p-1.5',
'peer-data-open/error:[&_*]:invisible',
// Appearance
'rounded-(--media-border-radius,0.75rem)',
'bg-(--media-controls-background-color)',
'text-(--media-controls-text-color)',
'text-(--media-text-color)',
// Backdrop filter
'backdrop-blur backdrop-brightness-[0.98] backdrop-saturate-[1.2]',
// Border
@@ -49,6 +52,23 @@ export const popup = {
),
};
/* ==========================================================================
Error
========================================================================== */
export const error = {
...baseError,
dialog: cn(
'absolute inset-0 z-20 flex items-center gap-4 rounded-full px-5 pr-2',
'bg-(--media-controls-background-color)',
'transition-[opacity,filter,scale] duration-300 delay-100 ease-out',
'group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-[4px] group-data-starting-style/error:scale-95',
'group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-[4px] group-data-ending-style/error:scale-95',
'motion-reduce:duration-100 motion-reduce:delay-0'
),
content: 'flex flex-1 items-center gap-2',
};
/* ==========================================================================
Shared components (no overrides)
========================================================================== */
@@ -58,7 +78,6 @@ export { tooltipState } from '../../shared/tailwind/tooltip-state';
export { bufferingIndicator } from './components/buffering';
export { button } from './components/button';
export { buttonGroup } from './components/button-group';
export { error } from './components/error';
export { icon, iconContainer, iconFlipped, iconHidden } from './components/icon';
export { seek } from './components/seek';
export { slider } from './components/slider';
@@ -2,21 +2,23 @@ import { cn } from '@videojs/utils/style';
export const button = {
base: cn(
'items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation',
'flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation',
'py-2 px-4 rounded-[calc(var(--media-controls-radius)-var(--media-controls-padding))]',
'outline-2 outline-transparent -outline-offset-2',
'font-medium text-shadow-inherit',
'transition-[background-color,color,outline-offset,scale] duration-150 ease-out',
'transition-[background-color,outline-offset,scale] duration-150 ease-out',
'focus-visible:outline-current focus-visible:outline-offset-2',
'active:scale-[0.98]',
'disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale',
'supports-[corner-shape:squircle]:rounded-(--media-controls-radius)',
'supports-[corner-shape:squircle]:[corner-shape:squircle]',
'data-[availability=unavailable]:hidden'
),
icon: cn(
'grid w-[2.375rem] aspect-square bg-transparent rounded-lg',
'text-inherit',
'hover:text-current/80 hover:no-underline',
'focus-visible:text-current/80',
'aria-expanded:text-current/80',
'active:scale-90'
primary: 'bg-white text-black font-medium text-center',
subtle: cn(
'bg-transparent text-inherit text-shadow-inherit',
'hover:bg-current/10',
'focus-visible:bg-current/10',
'aria-expanded:bg-current/10'
),
default: cn('flex py-2 px-4 bg-white rounded-lg', 'text-black'),
icon: cn('grid w-[2.375rem] aspect-square p-0', 'active:scale-90'),
};
@@ -1,9 +1,9 @@
import { cn } from '@videojs/utils/style';
export const error = {
root: 'peer/error group/error flex absolute inset-0 z-20 items-center justify-center',
root: 'peer/error group/error flex absolute inset-0 z-20 items-center justify-center outline-none',
dialog: cn(
'flex flex-col gap-3 max-w-64 p-4 text-white text-sm',
'flex flex-col gap-3 max-w-64 p-4 text-white',
'text-shadow-2xs text-shadow-black/50',
// Animation
'transition-[opacity,scale,transform] duration-500 delay-100',
@@ -5,6 +5,7 @@ export const root = cn(
'block relative isolate @container/media-root',
// Appearance
'rounded-(--media-border-radius,0.75rem)',
'[--media-controls-radius:var(--media-border-radius,1rem)]',
'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased',
// Resets
'**:box-border',
@@ -1,6 +1,6 @@
export const seek = {
button: '@max-md/media-controls:hidden',
label: 'text-[0.75em] font-[480] tabular-nums',
label: 'text-[10px] font-[480] tabular-nums',
labelForward: 'absolute -right-px -bottom-0.75',
labelBackward: 'absolute -left-px -bottom-0.75',
};
@@ -1,5 +1,6 @@
import { cn } from '@videojs/utils/style';
import { controls as baseControls } from './components/controls';
import { error as baseError } from './components/error';
import { popup as basePopup } from './components/popup';
import { preview as basePreview } from './components/preview';
import { root as baseRoot } from './components/root';
@@ -24,6 +25,7 @@ export const root = (isShadowDOM: boolean) =>
!isShadowDOM,
},
'[--media-video-border-radius:var(--media-border-radius,0.75rem)]',
'[--media-controls-padding:0.375rem]',
'[--media-controls-transition-duration:100ms]',
'[--media-controls-transition-delay:0ms]',
'[@media(pointer:fine)]:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-delay:500ms]',
@@ -64,8 +66,9 @@ export const controls = cn(
baseControls,
// Position
'absolute bottom-0 inset-x-0',
'pt-8 px-1.5 pb-1.5 gap-2',
'pt-8 px-(--media-controls-padding) pb-(--media-controls-padding) gap-2',
'[color:var(--media-color-primary,oklch(1_0_0))] z-10',
'peer-data-open/error:hidden',
'ease-out',
'duration-(--media-controls-transition-duration)',
'delay-(--media-controls-transition-delay)',
@@ -82,6 +85,17 @@ export const controls = cn(
'@sm/media-root:gap-3.5'
);
/* ==========================================================================
Error
========================================================================== */
export const error = {
...baseError,
root: cn(baseError.root, 'pointer-events-none outline-none'),
dialog: cn(baseError.dialog, 'pointer-events-auto'),
title: cn(baseError.title, 'text-lg'),
};
/* ==========================================================================
Preview
========================================================================== */
@@ -132,7 +146,6 @@ export { tooltipState } from '../../shared/tailwind/tooltip-state';
export { bufferingIndicator } from './components/buffering';
export { button } from './components/button';
export { buttonGroup } from './components/button-group';
export { error } from './components/error';
export { icon, iconContainer, iconFlipped, iconHidden } from './components/icon';
export { overlay } from './components/overlay';
export { playbackRate } from './components/playback-rate';