feat: add slider preview thumbnails (#935)

This commit is contained in:
Sam Potts
2026-03-14 08:30:51 +11:00
committed by GitHub
parent dc12cc2162
commit e3f438e9f4
51 changed files with 551 additions and 58 deletions
@@ -2,7 +2,7 @@ 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',
'items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation',
'font-medium',
'outline-2 outline-transparent -outline-offset-2',
'transition-[background-color,color,outline-offset,scale] duration-150 ease-out',
@@ -4,9 +4,9 @@ const base = cn(
// Reset default popover styles
'm-0 border-0 text-inherit overflow-visible',
// Animation
'transition-[transform,scale,opacity,filter] duration-200',
'data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm',
'data-ending-style:opacity-0 data-ending-style:scale-0 data-ending-style:blur-sm',
'transition-[transform,scale,opacity,filter] duration-150',
'data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm',
'data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm',
'data-instant:duration-0',
// Ensure we animate from the correct origin based on the side the popover is on
'data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left'
@@ -17,7 +17,7 @@ export const popup = {
tooltip: cn(
base,
'py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap',
'[--media-tooltip-side-offset:0.5rem]'
'[--media-tooltip-side-offset:0.75rem]'
),
volume: 'py-2.5 px-1 rounded-full',
};
@@ -0,0 +1,18 @@
import { cn } from '@videojs/utils/style';
export const preview = {
root: cn('group/preview pointer-events-none bg-black/90 rounded-xl'),
thumbnail: cn(
'block relative overflow-clip rounded-[inherit]',
'transition-opacity duration-150 ease-out',
'after:absolute after:inset-0 after:rounded-[inherit]',
'after:bg-linear-to-t after:from-black/80 after:via-black/30 after:to-black/0',
'data-loading:opacity-0'
),
timestamp: 'absolute bottom-2 inset-x-0 text-center tabular-nums',
spinner: cn(
'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0',
'transition-opacity duration-150 ease-out',
'group-has-[[role=img][data-loading]]/preview:opacity-100'
),
};
@@ -7,7 +7,7 @@ export const root = cn(
'rounded-(--media-border-radius,2rem)',
'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased',
// Resets
'**:box-border **:m-0',
'**:box-border',
'[&_button]:font-[inherit]',
'motion-safe:[interpolate-size:allow-keywords]'
);
@@ -2,7 +2,7 @@ import { cn } from '@videojs/utils/style';
export const slider = {
root: cn(
'group/slider relative flex flex-1 items-center justify-center rounded-full outline-none',
'group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer',
// Horizontal
'data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5',
// Vertical
@@ -2,7 +2,7 @@ import { cn } from '@videojs/utils/style';
export const surface = cn(
// Border and shadow
'ring ring-inset shadow-sm',
'ring shadow-sm',
// Border to enhance contrast on lighter videos
'after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10'
'after:absolute after:inset-0 after:ring after:ring-inset after:rounded-[inherit] after:pointer-events-none after:z-10'
);
@@ -3,6 +3,7 @@ import { bufferingIndicator as baseBufferingIndicator } from './components/buffe
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';
import { slider as baseSlider } from './components/slider';
import { surface as baseSurface } from './components/surface';
@@ -66,9 +67,9 @@ export const surface = cn(
'bg-white/10',
'backdrop-saturate-150 backdrop-blur-lg',
// Border and shadow
'ring-white/5 shadow-black/10',
'ring-black/15 shadow-black/10',
// Border to enhance contrast on lighter videos
'after:ring-black/15',
'after:ring-white/5',
// Reduced transparency for users with preference
'[@media(prefers-reduced-transparency:reduce)]:bg-black/70',
// High contrast mode
@@ -101,6 +102,25 @@ export const controls = cn(
'motion-reduce:not-data-visible:scale-100'
);
/* ==========================================================================
Preview (with video surface)
========================================================================== */
export const preview = {
...basePreview,
root: cn(
'absolute left-(--media-slider-pointer) bottom-[calc(100%+1.2rem)] -translate-x-1/2',
'opacity-0 scale-80 blur-sm origin-bottom',
'transition-[scale,opacity,filter] duration-150',
'group-data-pointing/slider:opacity-100 group-data-pointing/slider:scale-100 group-data-pointing/slider:blur-none',
'[&:has([role=img][data-hidden])]:opacity-0 [&:has([role=img][data-hidden])]:scale-80 [&:has([role=img][data-hidden])]:blur-sm',
'[&:has([role=img][data-loading])]:max-h-24',
surface,
basePreview.root
),
thumbnail: cn(basePreview.thumbnail, 'max-w-44'),
};
/* ==========================================================================
Sliders
========================================================================== */