From 75a90e71041c2d0bf434823e59dd04f86f89b0bb Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sat, 25 Oct 2025 14:35:48 +1100 Subject: [PATCH] feat(ui): skin design improvements, add html frosted skin (WIP) (#133) --- cspell.json | 2 +- examples/html-demo/index.html | 9 + examples/html-demo/src/main.ts | 9 +- packages/html/package.json | 10 +- packages/html/src/components/media-popover.ts | 2 +- packages/html/src/skins/default/index.ts | 390 ------------------ packages/html/src/skins/frosted/index.ts | 119 ++++++ packages/html/src/skins/frosted/styles.css | 351 ++++++++++++++++ packages/html/src/skins/minimal/index.ts | 26 ++ packages/html/src/skins/minimal/styles.css | 0 packages/html/src/types.d.ts | 4 + packages/html/tsdown.config.ts | 4 +- packages/react/src/skins/frosted/styles.ts | 31 +- packages/react/src/skins/minimal/styles.ts | 18 +- 14 files changed, 547 insertions(+), 428 deletions(-) delete mode 100644 packages/html/src/skins/default/index.ts create mode 100644 packages/html/src/skins/frosted/index.ts create mode 100644 packages/html/src/skins/frosted/styles.css create mode 100644 packages/html/src/skins/minimal/index.ts create mode 100644 packages/html/src/skins/minimal/styles.css create mode 100644 packages/html/src/types.d.ts diff --git a/cspell.json b/cspell.json index bedffbce..4e35f97a 100644 --- a/cspell.json +++ b/cspell.json @@ -3,7 +3,7 @@ "ignorePaths": ["package.json", "tsconfig.json"], "dictionaryDefinitions": [], "dictionaries": ["css", "html", "node", "typescript", "npm"], - "words": ["antfu", "noto", "nums", "rahim", "segoe"], + "words": ["antfu", "noto", "nums", "rahim", "segoe", "videojs"], "ignoreWords": [], "import": [] } diff --git a/examples/html-demo/index.html b/examples/html-demo/index.html index d602e3b6..b4cb9a24 100644 --- a/examples/html-demo/index.html +++ b/examples/html-demo/index.html @@ -3,6 +3,15 @@ VJS HTML Demo + + +
diff --git a/examples/html-demo/src/main.ts b/examples/html-demo/src/main.ts index 72d3d538..19202c30 100644 --- a/examples/html-demo/src/main.ts +++ b/examples/html-demo/src/main.ts @@ -1,16 +1,15 @@ import { defineVjsPlayer } from '@videojs/html'; -import '@videojs/html/skins/default'; +import '@videojs/html/skins/frosted'; defineVjsPlayer(); document.body.innerHTML = /* html */ ` -
- +
+ - +
-
`; diff --git a/packages/html/package.json b/packages/html/package.json index e241579d..8708239f 100644 --- a/packages/html/package.json +++ b/packages/html/package.json @@ -25,9 +25,13 @@ "types": "./dist/icons.d.ts", "default": "./dist/icons.js" }, - "./skins/default": { - "types": "./dist/skins/default.d.ts", - "default": "./dist/skins/default.js" + "./skins/frosted": { + "types": "./dist/skins/frosted.d.ts", + "default": "./dist/skins/frosted.js" + }, + "./skins/minimal": { + "types": "./dist/skins/minimal.d.ts", + "default": "./dist/skins/minimal.js" } }, "main": "dist/index.js", diff --git a/packages/html/src/components/media-popover.ts b/packages/html/src/components/media-popover.ts index 56b2c355..6a5f5156 100644 --- a/packages/html/src/components/media-popover.ts +++ b/packages/html/src/components/media-popover.ts @@ -288,7 +288,7 @@ export class MediaPopoverPortal extends HTMLElement { if (!portalId) return; /* @TODO We need to make sure portal logic is non-brittle longer term (CJP) */ - // NOTE: Hacky solution in part to ensure styling propogates from skin to container's baked in portal (TL;DR - Shadow DOM vs. Light DOM CSS) (CJP) + // NOTE: Hacky solution in part to ensure styling propagates from skin to container's baked in portal (TL;DR - Shadow DOM vs. Light DOM CSS) (CJP) const portalContainer = ((this.getRootNode() as ShadowRoot | Document).getElementById(portalId) ?? (this.getRootNode() as ShadowRoot | Document) diff --git a/packages/html/src/skins/default/index.ts b/packages/html/src/skins/default/index.ts deleted file mode 100644 index dea96aaa..00000000 --- a/packages/html/src/skins/default/index.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { MediaSkin } from '@/media/media-skin'; -import '@/media/media-container'; -import '@/media/media-provider'; -import '@/components/media-play-button'; -import '@/components/media-mute-button'; -import '@/components/media-volume-slider'; -import '@/components/media-time-slider'; -import '@/components/media-fullscreen-button'; -import '@/components/media-duration-display'; -import '@/components/media-current-time-display'; -import '@/components/media-preview-time-display'; -import '@/components/media-popover'; -import '@/components/media-tooltip'; -import '@/icons'; - -export function getTemplateHTML() { - return /* html */` - ${MediaSkin.getTemplateHTML()} - - - -
- -
- - - - - - - - - - - - Play - Pause - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Enter Fullscreen - Exit Fullscreen - - - - -
-
- `; -} - -export class MediaSkinDefault extends MediaSkin { - static getTemplateHTML: () => string = getTemplateHTML; -} - -customElements.define('media-skin-default', MediaSkinDefault); diff --git a/packages/html/src/skins/frosted/index.ts b/packages/html/src/skins/frosted/index.ts new file mode 100644 index 00000000..7529c3f6 --- /dev/null +++ b/packages/html/src/skins/frosted/index.ts @@ -0,0 +1,119 @@ +import { MediaSkin } from '@/media/media-skin'; +import styles from './styles.css'; +import '@/media/media-container'; +import '@/media/media-provider'; +import '@/components/media-play-button'; +import '@/components/media-mute-button'; +import '@/components/media-volume-slider'; +import '@/components/media-time-slider'; +import '@/components/media-fullscreen-button'; +import '@/components/media-duration-display'; +import '@/components/media-current-time-display'; +import '@/components/media-preview-time-display'; +import '@/components/media-popover'; +import '@/components/media-tooltip'; + +import '@/icons'; + +export function getTemplateHTML() { + return /* html */` + ${MediaSkin.getTemplateHTML()} + + + + +
+ +
+ + + + + + + + + + + + Play + Pause + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enter Fullscreen + Exit Fullscreen + + + + +
+
+ `; +} + +export class MediaSkinFrosted extends MediaSkin { + static getTemplateHTML: () => string = getTemplateHTML; +} + +customElements.define('media-skin-frosted', MediaSkinFrosted); diff --git a/packages/html/src/skins/frosted/styles.css b/packages/html/src/skins/frosted/styles.css new file mode 100644 index 00000000..c79b5651 --- /dev/null +++ b/packages/html/src/skins/frosted/styles.css @@ -0,0 +1,351 @@ +media-container * { + box-sizing: border-box; +} + +media-container { + position: relative; + isolation: isolate; + container: root / inline-size; + overflow: clip; + font-size: 0.8125rem; + line-height: 1.5; + border-radius: inherit; +} +media-container::before, +media-container::after { + content: ''; + position: absolute; + pointer-events: none; + border-radius: inherit; + z-index: 10; +} +media-container::before { + inset: 1px; + box-shadow: inset 0 0 0 1px oklab(1 0 0 / 0.15); +} +media-container::after { + inset: 0; + box-shadow: inset 0 0 0 1px oklab(0 0 0 / 0.1); +} + +/* Fullscreen */ +media-container:fullscreen { + border-radius: 0; +} +media-container:fullscreen video { + height: 100%; + width: 100%; +} + +media-container > ::slotted([slot='media']) { + display: block; + width: 100%; + height: 100%; +} + +/* Media Container UI Overlay Styling */ +media-container > .overlay { + position: absolute; + inset: 0; + display: flex; + flex-flow: column nowrap; + align-items: start; + pointer-events: none; + background-image: linear-gradient(to top, oklab(0 0 0 / 0.5), oklab(0 0 0 / 0.3), rgba(0, 0, 0, 0)); + backdrop-filter: saturate(1.5) brightness(0.9); + transition: opacity 0.15s ease-out; + transition-delay: 500ms; + opacity: 0; +} +media-container:hover > .overlay, +media-container:has([data-paused]) > .overlay, +media-container:has([aria-expanded='true']) > .overlay { + opacity: 1; + transition-delay: 0ms; +} + +/* Common Surface Styles - e.g. tooltips, popovers, controls */ +.surface { + background-color: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(64px) brightness(0.9) saturate(1.5); + box-shadow: + inset 0 0 0 1px oklab(1 0 0 / 0.15), + 0 0 0 1px oklab(0 0 0 / 0.15), + oklab(0 0 0 / 0.15) 0px 1px 3px 0px, + oklab(0 0 0 / 0.15) 0px 1px 2px -1px; +} +@media (prefers-reduced-transparency: reduce) { + .surface { + background-color: oklab(0 0 0 / 0.7); + box-shadow: + inset 0 0 0 1px oklab(0 0 0), + 0 0 0 1px oklab(1 0 0 / 0.2); + } +} +@media (prefers-contrast: more) { + .surface { + background-color: oklab(0 0 0 / 0.9); + box-shadow: + inset 0 0 0 1px oklab(0 0 0), + 0 0 0 1px oklab(1 0 0 / 0.2); + } +} + +/* Media Control Bar UI/Styles */ +.control-bar { + position: absolute; + bottom: 0.75rem; + inset-inline: 0.75rem; + padding: 0.25rem; + display: flex; + align-items: center; + gap: 0.125rem; + border-radius: calc(infinity * 1px); + will-change: scale, transform, filter, opacity; + scale: 0.9; + opacity: 0; + filter: blur(8px); + transition-property: scale, transform, filter, opacity; + transition-delay: 500ms; + transition-duration: 300ms; + transition-timing-function: ease-out; + transform-origin: bottom; + color: oklab(1 0 0); +} +media-container:hover > .control-bar, +media-container:has([data-paused]) > .control-bar, +media-container:has([aria-expanded='true']) > .control-bar { + opacity: 1; + scale: 1; + filter: blur(0px); + transition-delay: 0ms; + transition-duration: 100ms; +} + +/* Time Display Styling */ +.time-controls { + display: flex; + align-items: center; + flex: 1; + gap: 0.75rem; + padding-inline: 0.375rem; +} +media-current-time-display, +media-duration-display { + text-shadow: 0 1px 0 oklab(0 0 0 / 0.25); + font-variant-numeric: tabular-nums; +} + +/* Generic Media Button Styling */ +.button { + display: grid; + flex-shrink: 0; + padding: 0.5rem; + cursor: pointer; + background: transparent; + border: none; + border-radius: calc(infinity * 1px); + color: oklab(1 0 0 / 0.9); + user-select: none; + outline: 2px solid transparent; + outline-offset: -2px; + transition-property: background-color, color, outline-offset; + transition-duration: 150ms; + transition-timing-function: ease-out; +} +.button:hover, +.button:focus-visible, +.button[aria-expanded='true'] { + background-color: oklab(1 0 0 / 0.1); + color: oklab(1 0 0); + text-decoration: none; +} +.button:focus-visible { + outline-color: oklch(62.3% 0.214 259.815); + outline-offset: 2px; +} +.button[disabled] { + cursor: not-allowed; + opacity: 0.5; + filter: grayscale(1); +} + +.button .icon { + grid-area: 1 / 1; + width: 18px; + height: 18px; + filter: drop-shadow(0 1px 0 oklab(0 0 0 / 0.25)); +} + +/* Media Play Button UI/Styles */ +media-play-button .icon { + opacity: 0; + transition: opacity 150ms linear; +} + +media-play-button:not([data-paused]) .pause-icon, +media-play-button[data-paused] .play-icon { + opacity: 1; +} + +/* Media Fullscreen Button UI/Styles */ +media-fullscreen-button .icon { + display: none; +} +media-fullscreen-button:not([data-fullscreen]) .fullscreen-enter-icon, +media-fullscreen-button[data-fullscreen] .fullscreen-exit-icon { + display: inline; +} + +/* One way to define the "default visible" icon (CJP) */ +media-mute-button .icon { + display: none; +} +media-mute-button:not([data-volume-level]) .volume-low-icon, +media-mute-button[data-volume-level='high'] .volume-high-icon, +media-mute-button[data-volume-level='low'] .volume-low-icon, +media-mute-button[data-volume-level='medium'] .volume-low-icon, +media-mute-button[data-volume-level='off'] .volume-off-icon { + display: inline; +} + +/* TimeSlider Component Styles */ +.slider-root { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + position: relative; + border-radius: calc(infinity * 1px); + outline: none; +} + +/* Horizontal orientation styles */ +.slider-root[data-orientation='horizontal'] { + min-width: 80px; + width: 100%; + height: 20px; +} + +/* Vertical orientation styles */ +.slider-root[data-orientation='vertical'] { + height: 80px; + width: 20px; +} + +.slider-track { + position: relative; + isolation: isolate; + background-color: oklab(1 0 0 / 0.2); + border-radius: inherit; + overflow: hidden; + user-select: none; + outline: 2px solid transparent; + outline-offset: -2px; + transition: outline-offset 150ms ease-out; + box-shadow: 0 0 0 1px oklab(0 0 0 / 0.05); +} + +/* Horizontal track styles */ +.slider-track[data-orientation='horizontal'] { + width: 100%; + height: 0.25rem; +} + +/* Vertical track styles */ +.slider-track[data-orientation='vertical'] { + width: 0.25rem; + height: 100%; +} + +.slider-root:focus-visible .slider-track { + outline-color: oklch(62.3% 0.214 259.815); + outline-offset: 6px; +} + +.slider-thumb { + width: 0.625rem; + height: 0.625rem; + background-color: oklab(1 0 0); + border-radius: calc(infinity * 1px); + user-select: none; + z-index: 10; + box-shadow: + 0 0 0 1px oklab(0 0 0 / 0.1), + 0 1px 3px 0 oklab(0 0 0 / 0.15), + 0 1px 2px -1px oklab(0 0 0 / 0.15); + opacity: 0; + transition-property: opacity, height, width; + transition-duration: 150ms; + transition-timing-function: ease-in-out; +} +.slider-thumb:active { + width: 0.75rem; + height: 0.75rem; +} +.slider-root:hover .slider-thumb, +.slider-root:focus-within .slider-thumb { + opacity: 1; +} +.slider-track[data-orientation='horizontal'] .slider-thumb { + cursor: ew-resize; +} +.slider-track[data-orientation='vertical'] .slider-thumb { + cursor: ns-resize; +} + +.slider-pointer { + background-color: oklab(1 0 0 / 0.2); + border-radius: inherit; +} + +.slider-progress { + background-color: oklab(1 0 0); + border-radius: inherit; +} + +.popup-animation { + transition-property: transform, scale, opacity, filter; + transition-duration: 200ms; + transform: scale(1); + transform-origin: bottom; + opacity: 1; + filter: blur(0px); +} +.popup-animation[data-starting-style], +.popup-animation[data-ending-style] { + transform: scale(0); + opacity: 0; + filter: blur(8px); +} +.popup-animation[data-instant] { + transition-duration: 0ms; +} + +media-popover-popup { + padding: 0.75rem 0.25rem; + border-radius: calc(infinity * 1px); +} + +/* Tooltip Component Styles */ +media-tooltip-popup { + color: oklab(1 0 0); + padding: 0.25rem 0.625rem; + border-radius: calc(infinity * 1px); + font-size: 0.75rem; +} + +.tooltip { + display: none; + white-space: nowrap; +} + +media-tooltip-popup[data-paused] .play-tooltip, +media-tooltip-popup:not([data-paused]) .pause-tooltip { + display: block; +} + +media-tooltip-popup[data-fullscreen] .fullscreen-exit-tooltip, +media-tooltip-popup:not([data-fullscreen]) .fullscreen-enter-tooltip { + display: block; +} diff --git a/packages/html/src/skins/minimal/index.ts b/packages/html/src/skins/minimal/index.ts new file mode 100644 index 00000000..e06afd25 --- /dev/null +++ b/packages/html/src/skins/minimal/index.ts @@ -0,0 +1,26 @@ +import { MediaSkin } from '@/media/media-skin'; +import '@/media/media-container'; +import '@/media/media-provider'; +import '@/components/media-play-button'; +import '@/components/media-mute-button'; +import '@/components/media-volume-slider'; +import '@/components/media-time-slider'; +import '@/components/media-fullscreen-button'; +import '@/components/media-duration-display'; +import '@/components/media-current-time-display'; +import '@/components/media-preview-time-display'; +import '@/components/media-popover'; +import '@/components/media-tooltip'; +import '@/icons'; + +export function getTemplateHTML() { + return /* html */` + ${MediaSkin.getTemplateHTML()} + `; +} + +export class MediaSkinMinimal extends MediaSkin { + static getTemplateHTML: () => string = getTemplateHTML; +} + +customElements.define('media-skin-minimal', MediaSkinMinimal); diff --git a/packages/html/src/skins/minimal/styles.css b/packages/html/src/skins/minimal/styles.css new file mode 100644 index 00000000..e69de29b diff --git a/packages/html/src/types.d.ts b/packages/html/src/types.d.ts new file mode 100644 index 00000000..31f07ea5 --- /dev/null +++ b/packages/html/src/types.d.ts @@ -0,0 +1,4 @@ +declare module '*.css' { + const content: string; + export default content; +} diff --git a/packages/html/tsdown.config.ts b/packages/html/tsdown.config.ts index 207ddca0..b0b53557 100644 --- a/packages/html/tsdown.config.ts +++ b/packages/html/tsdown.config.ts @@ -5,7 +5,8 @@ export default defineConfig({ index: './src/index.ts', store: './src/store/index.ts', icons: './src/icons/index.ts', - 'skins/default': './src/skins/default/index.ts', + 'skins/frosted': './src/skins/frosted/index.ts', + 'skins/minimal': './src/skins/minimal/index.ts', }, platform: 'browser', format: 'es', @@ -19,5 +20,6 @@ export default defineConfig({ }, loader: { '.svg': 'text', + '.css': 'text', }, }); diff --git a/packages/react/src/skins/frosted/styles.ts b/packages/react/src/skins/frosted/styles.ts index 66a5ba29..49f36e32 100644 --- a/packages/react/src/skins/frosted/styles.ts +++ b/packages/react/src/skins/frosted/styles.ts @@ -46,18 +46,18 @@ const styles: FrostedSkinStyles = { Controls: cn( 'vjs:@container/controls vjs:absolute vjs:inset-x-3 vjs:bottom-3 vjs:rounded-full vjs:flex vjs:items-center vjs:p-1 vjs:gap-0.5 vjs:text-white', // Animation - 'vjs:transition vjs:will-change-transform vjs:origin-bottom vjs:ease-out', + 'vjs:transition vjs:will-change-[transform,scale,filter,opacity] vjs:origin-bottom vjs:ease-out', // FIXME: Temporary className hook for above logic in the overlay. Can be removed once have a proper way to handle controls visibility. 'controls', // FIXME: Temporary hide/show logic - 'vjs:scale-90 vjs:opacity-0 vjs:delay-500 vjs:duration-300', - 'vjs:has-[[data-paused]]:scale-100 vjs:has-[[data-paused]]:opacity-100 vjs:has-[[data-paused]]:delay-0', - 'vjs:has-[[aria-expanded="true"]]:scale-100 vjs:has-[[aria-expanded="true"]]:opacity-100 vjs:has-[[aria-expanded="true"]]:delay-0', - 'vjs:group-hover/root:scale-100 vjs:group-hover/root:opacity-100 vjs:group-hover/root:delay-0', + 'vjs:scale-90 vjs:opacity-0 vjs:blur-sm vjs:delay-500 vjs:duration-300', + 'vjs:has-[[data-paused]]:scale-100 vjs:has-[[data-paused]]:opacity-100 vjs:has-[[data-paused]]:blur-none vjs:has-[[data-paused]]:delay-0 vjs:has-[[data-paused]]:duration-100', + 'vjs:has-[[aria-expanded="true"]]:scale-100 vjs:has-[[aria-expanded="true"]]:opacity-100 vjs:has-[[aria-expanded="true"]]:blur-none vjs:has-[[aria-expanded="true"]]:delay-0 vjs:has-[[aria-expanded="true"]]:duration-100', + 'vjs:group-hover/root:scale-100 vjs:group-hover/root:opacity-100 vjs:group-hover/root:blur-none vjs:group-hover/root:delay-0 vjs:group-hover/root:duration-100', ), Icon: cn('icon'), Button: cn( - 'vjs:group/button vjs:cursor-pointer vjs:relative vjs:shrink-0 vjs:transition vjs:select-none vjs:p-2 vjs:rounded-full', + 'vjs:group/button vjs:cursor-pointer vjs:relative vjs:shrink-0 vjs:transition-[color,background,outline-offset] vjs:select-none vjs:p-2 vjs:rounded-full', // Background/foreground 'vjs:bg-transparent vjs:text-white/90', // Hover and focus states @@ -65,21 +65,19 @@ const styles: FrostedSkinStyles = { // Focus state 'vjs:-outline-offset-2 vjs:focus-visible:outline-2 vjs:focus-visible:outline-offset-2 vjs:focus-visible:outline-blue-500', // Disabled state - 'vjs:aria-disabled:grayscale vjs:aria-disabled:opacity-50 vjs:aria-disabled:cursor-not-allowed', + 'vjs:disabled:grayscale vjs:disabled:opacity-50 vjs:disabled:cursor-not-allowed', // Loading state 'vjs:aria-busy:pointer-events-none vjs:aria-busy:cursor-not-allowed', // Expanded state 'vjs:aria-expanded:bg-white/10 vjs:aria-expanded:text-white', - // Pressed state - 'vjs:active:scale-95', ), IconButton: cn( 'vjs:grid vjs:[&_.icon]:[grid-area:1/1]', - 'vjs:[&_.icon]:shrink-0 vjs:[&_.icon]:transition-[opacity,filter] vjs:[&_.icon]:duration-500 vjs:[&_.icon]:linear vjs:[&_.icon]:drop-shadow-[0_1px_0_var(--tw-shadow-color)] vjs:[&_.icon]:shadow-black/25', + 'vjs:[&_.icon]:shrink-0 vjs:[&_.icon]:transition-opacity vjs:[&_.icon]:duration-150 vjs:[&_.icon]:ease-linear vjs:[&_.icon]:drop-shadow-[0_1px_0_var(--tw-shadow-color)] vjs:[&_.icon]:shadow-black/25', ), PlayButton: cn( - 'vjs:[&_.pause-icon]:opacity-100 vjs:[&[data-paused]_.pause-icon]:opacity-0 vjs:[&[data-paused]_.pause-icon]:blur-sm', - 'vjs:[&_.play-icon]:opacity-0 vjs:[&_.play-icon]:blur-sm vjs:[&[data-paused]_.play-icon]:opacity-100 vjs:[&[data-paused]_.play-icon]:blur-none', + 'vjs:[&_.pause-icon]:opacity-100 vjs:[&[data-paused]_.pause-icon]:opacity-0', + 'vjs:[&_.play-icon]:opacity-0 vjs:[&[data-paused]_.play-icon]:opacity-100', ), PlayIcon: cn('play-icon'), PauseIcon: cn('pause-icon'), @@ -132,10 +130,10 @@ const styles: FrostedSkinStyles = { 'vjs:[&[data-orientation="vertical"]]:w-5 vjs:[&[data-orientation="vertical"]]:h-20', ), SliderTrack: cn( - 'vjs:-outline-offset-2 vjs:group-focus-visible/slider-root:outline-2 vjs:group-focus-visible/slider-root:outline-offset-2 vjs:group-focus-visible/slider-root:outline-blue-500', - 'vjs:w-full vjs:relative vjs:select-none vjs:rounded-full vjs:bg-white/20 vjs:ring-1 vjs:ring-black/5', - 'vjs:[&[data-orientation="horizontal"]]:h-1', + 'vjs:relative vjs:select-none vjs:transition-[outline-offset] vjs:rounded-[inherit] vjs:bg-white/20 vjs:ring-1 vjs:ring-black/5', + 'vjs:[&[data-orientation="horizontal"]]:w-full vjs:[&[data-orientation="horizontal"]]:h-1', 'vjs:[&[data-orientation="vertical"]]:w-1', + 'vjs:-outline-offset-2 vjs:group-focus-visible/slider-root:outline-2 vjs:group-focus-visible/slider-root:outline-offset-6 vjs:group-focus-visible/slider-root:outline-blue-500', ), SliderProgress: cn('vjs:bg-white vjs:rounded-[inherit]'), // TODO: Work out what we want to do here. @@ -143,7 +141,6 @@ const styles: FrostedSkinStyles = { SliderThumb: cn( 'vjs:bg-white vjs:z-10 vjs:select-none vjs:ring vjs:ring-black/10 vjs:rounded-full vjs:shadow-sm vjs:shadow-black/15', 'vjs:opacity-0 vjs:transition-[opacity,height,width] vjs:ease-in-out', - 'vjs:-outline-offset-2 vjs:focus-visible:outline-2 vjs:focus-visible:outline-offset-2 vjs:focus-visible:outline-blue-500', 'vjs:group-hover/slider:opacity-100 vjs:group-focus-within/slider:opacity-100', 'vjs:size-2.5 vjs:active:size-3 vjs:group-active/slider:size-3', 'vjs:[&[data-orientation="horizontal"]]:hover:cursor-ew-resize', @@ -160,7 +157,7 @@ const styles: FrostedSkinStyles = { 'vjs:relative vjs:px-1 vjs:py-3 vjs:rounded-full', ), TooltipPopup: cn( - 'vjs:whitespace-nowrap vjs:flex vjs:flex-col vjs:rounded-full vjs:text-white vjs:text-xs vjs:@7xl/root:text-sm vjs:px-2.5 vjs:py-1', + 'vjs:whitespace-nowrap vjs:rounded-full vjs:text-white vjs:text-xs vjs:@7xl/root:text-sm vjs:px-2.5 vjs:py-1', ), }; diff --git a/packages/react/src/skins/minimal/styles.ts b/packages/react/src/skins/minimal/styles.ts index 1fc91285..fa165051 100644 --- a/packages/react/src/skins/minimal/styles.ts +++ b/packages/react/src/skins/minimal/styles.ts @@ -46,7 +46,7 @@ const styles: MinimalSkinStyles = { ), Icon: cn('icon'), Button: cn( - 'vjs:group/button vjs:cursor-pointer vjs:relative vjs:shrink-0 vjs:transition vjs:select-none vjs:p-2.5 vjs:rounded-md', + 'vjs:group/button vjs:cursor-pointer vjs:relative vjs:shrink-0 vjs:transition-[color,background,outline-offset] vjs:select-none vjs:p-2.5 vjs:rounded-md', // Background/foreground 'vjs:bg-transparent vjs:text-white', // Hover and focus states @@ -54,7 +54,7 @@ const styles: MinimalSkinStyles = { // Focus state 'vjs:-outline-offset-2 vjs:focus-visible:outline-2 vjs:focus-visible:outline-offset-2 vjs:focus-visible:outline-white', // Disabled state - 'vjs:aria-disabled:grayscale vjs:aria-disabled:opacity-50 vjs:aria-disabled:cursor-not-allowed', + 'vjs:disabled:grayscale vjs:disabled:opacity-50 vjs:disabled:cursor-not-allowed', // Loading state 'vjs:aria-busy:pointer-events-none vjs:aria-busy:cursor-not-allowed', // Expanded state @@ -63,11 +63,11 @@ const styles: MinimalSkinStyles = { ButtonGroup: cn('vjs:flex vjs:items-center vjs:gap-1.5'), IconButton: cn( 'vjs:grid vjs:[&_.icon]:[grid-area:1/1]', - 'vjs:[&_.icon]:shrink-0 vjs:[&_.icon]:transition-[opacity,filter] vjs:[&_.icon]:duration-300 vjs:[&_.icon]:linear vjs:[&_.icon]:drop-shadow-[0_1px_0_var(--tw-shadow-color)] vjs:[&_.icon]:shadow-black/40', + 'vjs:[&_.icon]:shrink-0 vjs:[&_.icon]:transition-opacity vjs:[&_.icon]:duration-150 vjs:[&_.icon]:ease-linear vjs:[&_.icon]:drop-shadow-[0_1px_0_var(--tw-shadow-color)] vjs:[&_.icon]:shadow-black/40', ), PlayButton: cn( - 'vjs:[&_.pause-icon]:opacity-100 vjs:[&_.pause-icon]:blur-none vjs:[&[data-paused]_.pause-icon]:opacity-0 vjs:[&[data-paused]_.pause-icon]:blur-xs', - 'vjs:[&_.play-icon]:opacity-0 vjs:[&_.play-icon]:blur-xs vjs:[&[data-paused]_.play-icon]:opacity-100 vjs:[&[data-paused]_.play-icon]:blur-none', + 'vjs:[&_.pause-icon]:opacity-100 vjs:[&[data-paused]_.pause-icon]:opacity-0', + 'vjs:[&_.play-icon]:opacity-0 vjs:[&[data-paused]_.play-icon]:opacity-100', ), PlayIcon: cn('play-icon'), PauseIcon: cn('pause-icon'), @@ -114,23 +114,21 @@ const styles: MinimalSkinStyles = { TimeDisplay: cn('vjs:tabular-nums vjs:text-shadow-2xs/20'), DurationDisplay: cn('vjs:text-white/50 vjs:contents'), SliderRoot: cn( - 'vjs:flex vjs:items-center vjs:justify-center vjs:flex-1 vjs:group/slider vjs:relative vjs:rounded-sm', + 'vjs:flex vjs:items-center vjs:justify-center vjs:flex-1 vjs:group/slider vjs:relative vjs:rounded-full', 'vjs:[&[data-orientation="horizontal"]]:h-5 vjs:[&[data-orientation="horizontal"]]:min-w-20', 'vjs:[&[data-orientation="vertical"]]:w-5 vjs:[&[data-orientation="vertical"]]:h-18', - // Focus state - 'vjs:-outline-offset-8 vjs:focus-visible:outline-2 vjs:focus-visible:outline-offset-8 vjs:focus-visible:outline-white', ), SliderTrack: cn( - 'vjs:relative vjs:select-none vjs:rounded-full vjs:bg-white/10 vjs:transition-[height,width] vjs:ease-in-out', + 'vjs:relative vjs:select-none vjs:rounded-[inherit] vjs:bg-white/10', 'vjs:[&[data-orientation="horizontal"]]:w-full vjs:[&[data-orientation="horizontal"]]:h-[0.1875rem]', 'vjs:[&[data-orientation="vertical"]]:w-[0.1875rem]', + 'vjs:-outline-offset-2 vjs:group-focus-visible/slider-root:outline-2 vjs:group-focus-visible/slider-root:outline-offset-2 vjs:group-focus-visible/slider-root:outline-white', ), SliderProgress: cn('vjs:bg-white vjs:rounded-[inherit]'), SliderPointer: cn('vjs:hidden'), SliderThumb: cn( 'vjs:opacity-0 vjs:scale-70 vjs:group-hover/slider:opacity-100 vjs:group-hover/slider:scale-100 vjs:focus-visible:opacity-100 vjs:focus-visible:scale-100', 'vjs:bg-white vjs:z-10 vjs:size-3 vjs:select-none vjs:ring vjs:ring-black/10 vjs:rounded-full vjs:shadow-sm vjs:shadow-black/15 vjs:transition-[opacity,scale] vjs:ease-out', - 'vjs:-outline-offset-2 vjs:focus-visible:outline-2 vjs:focus-visible:outline-offset-2 vjs:focus-visible:outline-amber-500', 'vjs:[&[data-orientation="horizontal"]]:hover:cursor-ew-resize', 'vjs:[&[data-orientation="vertical"]]:hover:cursor-ns-resize', ),