From d751fdabea9782b9f6c73aaebfb93ed393e488f7 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 9 Mar 2026 12:12:49 +1100 Subject: [PATCH] feat(skin): add audio skins for HTML and React presets (#772) Co-authored-by: Claude Opus 4.6 --- .vscode/settings.json | 2 +- build/plugins/copy-css-plugin.mjs | 31 + build/plugins/inline-css-plugin.mjs | 45 ++ build/plugins/resolve-css-imports.mjs | 27 + {scripts => build/scripts}/link-aliases.mjs | 0 package.json | 4 +- .../html/src/define/audio/minimal-skin.css | 12 +- .../src/define/audio/minimal-skin.tailwind.ts | 113 +++ .../html/src/define/audio/minimal-skin.ts | 93 ++- packages/html/src/define/audio/skin.css | 10 +- .../html/src/define/audio/skin.tailwind.ts | 104 +++ packages/html/src/define/audio/skin.ts | 85 ++- packages/html/src/define/skin-mixin.ts | 98 ++- .../html/src/define/video/minimal-skin.css | 2 +- .../src/define/video/minimal-skin.tailwind.ts | 27 +- .../html/src/define/video/minimal-skin.ts | 18 +- packages/html/src/define/video/skin.css | 2 +- .../html/src/define/video/skin.tailwind.ts | 25 +- packages/html/src/define/video/skin.ts | 19 +- packages/html/src/globals.d.ts | 5 + packages/html/src/presets/audio.ts | 2 + packages/html/src/store/container-mixin.ts | 22 +- packages/html/tsdown.config.ts | 40 +- packages/icons/scripts/build.ts | 6 + packages/react/src/presets/audio/index.ts | 2 + .../react/src/presets/audio/minimal-skin.css | 4 +- .../presets/audio/minimal-skin.tailwind.tsx | 191 +++++ .../react/src/presets/audio/minimal-skin.tsx | 121 ++- packages/react/src/presets/audio/skin.css | 2 +- .../react/src/presets/audio/skin.tailwind.tsx | 180 +++++ packages/react/src/presets/audio/skin.tsx | 113 ++- .../react/src/presets/background/skin.css | 2 +- .../react/src/presets/video/minimal-skin.css | 2 +- .../presets/video/minimal-skin.tailwind.tsx | 10 +- .../react/src/presets/video/minimal-skin.tsx | 6 +- packages/react/src/presets/video/skin.css | 2 +- .../react/src/presets/video/skin.tailwind.tsx | 8 +- packages/react/src/presets/video/skin.tsx | 6 +- packages/react/tsdown.config.ts | 38 +- packages/sandbox/src/index.html | 4 +- packages/sandbox/templates/index.html | 4 +- .../sandbox/templates/react-tailwind/main.tsx | 4 +- packages/sandbox/templates/react/main.tsx | 3 +- packages/sandbox/templates/styles.css | 4 + .../sandbox/templates/utils/use-latest-ref.ts | 19 + .../templates/utils/use-skin-switcher.ts | 6 + .../templates/utils/use-web-storage.ts | 162 ++++ packages/sandbox/tsconfig.json | 2 +- packages/skins/src/audio/default.css | 3 - packages/skins/src/background/default.css | 21 - packages/skins/src/default/css/audio.css | 53 ++ packages/skins/src/default/css/background.css | 21 + .../src/default/css/components/buffering.css | 22 + .../src/default/css/components/buttons.css | 98 +++ .../src/default/css/components/captions.css | 91 +++ .../src/default/css/components/controls.css | 23 + .../src/default/css/components/error.css | 75 ++ .../src/default/css/components/icons.css | 21 + .../src/default/css/components/media.css | 30 + .../src/default/css/components/overlay.css | 32 + .../src/default/css/components/popup.css | 43 ++ .../src/default/css/components/reset.css | 24 + .../skins/src/default/css/components/root.css | 26 + .../src/default/css/components/slider.css | 148 ++++ .../src/default/css/components/surface.css | 31 + .../skins/src/default/css/components/time.css | 24 + .../src/default/css/components/tooltip.css | 13 + packages/skins/src/default/css/video.css | 87 +++ .../src/default/tailwind/audio.tailwind.ts | 96 +++ .../default/tailwind/components/buffering.ts | 4 + .../src/default/tailwind/components/button.ts | 22 + .../default/tailwind/components/captions.ts | 23 + .../default/tailwind/components/controls.ts | 17 + .../src/default/tailwind/components/error.ts | 19 + .../default/tailwind/components/icon-state.ts | 27 + .../src/default/tailwind/components/icon.ts | 11 + .../default/tailwind/components/overlay.ts | 25 + .../tailwind/components/playback-rate.ts | 3 + .../src/default/tailwind/components/popup.ts | 18 + .../src/default/tailwind/components/root.ts | 15 + .../src/default/tailwind/components/seek.ts | 6 + .../src/default/tailwind/components/slider.ts | 60 ++ .../default/tailwind/components/surface.ts | 8 + .../src/default/tailwind/components/time.ts | 5 + .../src/default/tailwind/video.tailwind.ts | 144 ++++ packages/skins/src/minimal/css/audio.css | 47 ++ .../src/minimal/css/components/buffering.css | 17 + .../src/minimal/css/components/buttons.css | 112 +++ .../src/minimal/css/components/captions.css | 91 +++ .../src/minimal/css/components/controls.css | 15 + .../src/minimal/css/components/error.css | 76 ++ .../src/minimal/css/components/icons.css | 21 + .../src/minimal/css/components/media.css | 30 + .../src/minimal/css/components/overlay.css | 32 + .../src/minimal/css/components/popup.css | 38 + .../src/minimal/css/components/reset.css | 24 + .../skins/src/minimal/css/components/root.css | 22 + .../src/minimal/css/components/slider.css | 145 ++++ .../skins/src/minimal/css/components/time.css | 36 + .../src/minimal/css/components/tooltip.css | 27 + packages/skins/src/minimal/css/video.css | 101 +++ .../src/minimal/tailwind/audio.tailwind.ts | 84 +++ .../minimal/tailwind/components/buffering.ts | 2 + .../tailwind/components/button-group.ts | 3 + .../src/minimal/tailwind/components/button.ts | 22 + .../minimal/tailwind/components/captions.ts | 23 + .../minimal/tailwind/components/controls.ts | 14 + .../src/minimal/tailwind/components/error.ts | 20 + .../minimal/tailwind/components/icon-state.ts | 27 + .../src/minimal/tailwind/components/icon.ts | 11 + .../minimal/tailwind/components/overlay.ts | 25 + .../src/minimal/tailwind/components/popup.ts | 15 + .../src/minimal/tailwind/components/root.ts | 13 + .../src/minimal/tailwind/components/seek.ts | 6 + .../src/minimal/tailwind/components/slider.ts | 58 ++ .../src/minimal/tailwind/components/time.ts | 9 + .../src/minimal/tailwind/video.tailwind.ts | 111 +++ .../skins/src/shared/audio-icon-state.css | 37 + packages/skins/src/shared/native-captions.css | 26 + .../video-icon-state.css} | 2 +- packages/skins/src/video/default.css | 706 ------------------ packages/skins/src/video/default.tailwind.ts | 314 -------- packages/skins/src/video/minimal.css | 704 ----------------- packages/skins/src/video/minimal.tailwind.ts | 291 -------- packages/utils/src/style/cn.ts | 5 +- 125 files changed, 4174 insertions(+), 2264 deletions(-) create mode 100644 build/plugins/copy-css-plugin.mjs create mode 100644 build/plugins/inline-css-plugin.mjs create mode 100644 build/plugins/resolve-css-imports.mjs rename {scripts => build/scripts}/link-aliases.mjs (100%) create mode 100644 packages/html/src/define/audio/minimal-skin.tailwind.ts create mode 100644 packages/html/src/define/audio/skin.tailwind.ts create mode 100644 packages/react/src/presets/audio/minimal-skin.tailwind.tsx create mode 100644 packages/react/src/presets/audio/skin.tailwind.tsx create mode 100644 packages/sandbox/templates/utils/use-latest-ref.ts create mode 100644 packages/sandbox/templates/utils/use-skin-switcher.ts create mode 100644 packages/sandbox/templates/utils/use-web-storage.ts delete mode 100644 packages/skins/src/audio/default.css delete mode 100644 packages/skins/src/background/default.css create mode 100644 packages/skins/src/default/css/audio.css create mode 100644 packages/skins/src/default/css/background.css create mode 100644 packages/skins/src/default/css/components/buffering.css create mode 100644 packages/skins/src/default/css/components/buttons.css create mode 100644 packages/skins/src/default/css/components/captions.css create mode 100644 packages/skins/src/default/css/components/controls.css create mode 100644 packages/skins/src/default/css/components/error.css create mode 100644 packages/skins/src/default/css/components/icons.css create mode 100644 packages/skins/src/default/css/components/media.css create mode 100644 packages/skins/src/default/css/components/overlay.css create mode 100644 packages/skins/src/default/css/components/popup.css create mode 100644 packages/skins/src/default/css/components/reset.css create mode 100644 packages/skins/src/default/css/components/root.css create mode 100644 packages/skins/src/default/css/components/slider.css create mode 100644 packages/skins/src/default/css/components/surface.css create mode 100644 packages/skins/src/default/css/components/time.css create mode 100644 packages/skins/src/default/css/components/tooltip.css create mode 100644 packages/skins/src/default/css/video.css create mode 100644 packages/skins/src/default/tailwind/audio.tailwind.ts create mode 100644 packages/skins/src/default/tailwind/components/buffering.ts create mode 100644 packages/skins/src/default/tailwind/components/button.ts create mode 100644 packages/skins/src/default/tailwind/components/captions.ts create mode 100644 packages/skins/src/default/tailwind/components/controls.ts create mode 100644 packages/skins/src/default/tailwind/components/error.ts create mode 100644 packages/skins/src/default/tailwind/components/icon-state.ts create mode 100644 packages/skins/src/default/tailwind/components/icon.ts create mode 100644 packages/skins/src/default/tailwind/components/overlay.ts create mode 100644 packages/skins/src/default/tailwind/components/playback-rate.ts create mode 100644 packages/skins/src/default/tailwind/components/popup.ts create mode 100644 packages/skins/src/default/tailwind/components/root.ts create mode 100644 packages/skins/src/default/tailwind/components/seek.ts create mode 100644 packages/skins/src/default/tailwind/components/slider.ts create mode 100644 packages/skins/src/default/tailwind/components/surface.ts create mode 100644 packages/skins/src/default/tailwind/components/time.ts create mode 100644 packages/skins/src/default/tailwind/video.tailwind.ts create mode 100644 packages/skins/src/minimal/css/audio.css create mode 100644 packages/skins/src/minimal/css/components/buffering.css create mode 100644 packages/skins/src/minimal/css/components/buttons.css create mode 100644 packages/skins/src/minimal/css/components/captions.css create mode 100644 packages/skins/src/minimal/css/components/controls.css create mode 100644 packages/skins/src/minimal/css/components/error.css create mode 100644 packages/skins/src/minimal/css/components/icons.css create mode 100644 packages/skins/src/minimal/css/components/media.css create mode 100644 packages/skins/src/minimal/css/components/overlay.css create mode 100644 packages/skins/src/minimal/css/components/popup.css create mode 100644 packages/skins/src/minimal/css/components/reset.css create mode 100644 packages/skins/src/minimal/css/components/root.css create mode 100644 packages/skins/src/minimal/css/components/slider.css create mode 100644 packages/skins/src/minimal/css/components/time.css create mode 100644 packages/skins/src/minimal/css/components/tooltip.css create mode 100644 packages/skins/src/minimal/css/video.css create mode 100644 packages/skins/src/minimal/tailwind/audio.tailwind.ts create mode 100644 packages/skins/src/minimal/tailwind/components/buffering.ts create mode 100644 packages/skins/src/minimal/tailwind/components/button-group.ts create mode 100644 packages/skins/src/minimal/tailwind/components/button.ts create mode 100644 packages/skins/src/minimal/tailwind/components/captions.ts create mode 100644 packages/skins/src/minimal/tailwind/components/controls.ts create mode 100644 packages/skins/src/minimal/tailwind/components/error.ts create mode 100644 packages/skins/src/minimal/tailwind/components/icon-state.ts create mode 100644 packages/skins/src/minimal/tailwind/components/icon.ts create mode 100644 packages/skins/src/minimal/tailwind/components/overlay.ts create mode 100644 packages/skins/src/minimal/tailwind/components/popup.ts create mode 100644 packages/skins/src/minimal/tailwind/components/root.ts create mode 100644 packages/skins/src/minimal/tailwind/components/seek.ts create mode 100644 packages/skins/src/minimal/tailwind/components/slider.ts create mode 100644 packages/skins/src/minimal/tailwind/components/time.ts create mode 100644 packages/skins/src/minimal/tailwind/video.tailwind.ts create mode 100644 packages/skins/src/shared/audio-icon-state.css create mode 100644 packages/skins/src/shared/native-captions.css rename packages/skins/src/{video/icon-state.css => shared/video-icon-state.css} (98%) delete mode 100644 packages/skins/src/video/default.css delete mode 100644 packages/skins/src/video/default.tailwind.ts delete mode 100644 packages/skins/src/video/minimal.css delete mode 100644 packages/skins/src/video/minimal.tailwind.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index f5b3f676..00e180a4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,7 +16,7 @@ "biome.requireConfiguration": true, "editor.defaultFormatter": "biomejs.biome", - "typescript.tsdk": "node_modules/typescript/lib", + "js/ts.tsdk.path": "node_modules/typescript/lib", "[astro]": { "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/build/plugins/copy-css-plugin.mjs b/build/plugins/copy-css-plugin.mjs new file mode 100644 index 00000000..2b39bd24 --- /dev/null +++ b/build/plugins/copy-css-plugin.mjs @@ -0,0 +1,31 @@ +import { globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { resolveImports } from './resolve-css-imports.mjs'; + +/** + * @param {{ skinsDir: string; outDir: string }} options + */ +export function copyCssPlugin(options) { + const { skinsDir, outDir } = options; + + return { + name: 'copy-css', + buildStart() { + for (const file of globSync('src/**/*.css')) { + this.addWatchFile(file); + } + for (const file of globSync(join(skinsDir, '**/*.css'))) { + this.addWatchFile(file); + } + }, + writeBundle() { + for (const file of globSync('src/**/*.css')) { + const content = readFileSync(file, 'utf-8'); + const resolved = resolveImports(content, dirname(file), skinsDir); + const outFile = join(outDir, file.replace(/^src\//, '')); + mkdirSync(dirname(outFile), { recursive: true }); + writeFileSync(outFile, resolved); + } + }, + }; +} diff --git a/build/plugins/inline-css-plugin.mjs b/build/plugins/inline-css-plugin.mjs new file mode 100644 index 00000000..c5e6fbb4 --- /dev/null +++ b/build/plugins/inline-css-plugin.mjs @@ -0,0 +1,45 @@ +import { readFileSync } from 'node:fs'; +import { dirname, relative, resolve } from 'node:path'; +import { resolveImports } from './resolve-css-imports.mjs'; + +const INLINE_PREFIX = 'inline-css:'; + +const INLINE_SUFFIX = '?inline'; + +/** + * Rolldown/tsdown plugin that inlines `.css?inline` imports as JavaScript + * modules exporting the resolved CSS string. Mirrors the Vite `?inline` + * convention so the same source works in both Vite dev and tsdown builds. + * + * @param {{ skinsDir: string; rootDir?: string }} options + */ +export function inlineCssPlugin(options) { + const { skinsDir, rootDir = process.cwd() } = options; + + return { + name: 'inline-css', + + resolveId(source, importer) { + if (!source.endsWith(INLINE_SUFFIX)) return null; + + const cssPath = source.slice(0, -INLINE_SUFFIX.length); + const abs = resolve(dirname(importer), cssPath); + const rel = relative(rootDir, abs); + + // Use .js extension so rolldown doesn't apply its CSS loader. + return { id: `${INLINE_PREFIX}${rel.replace(/\.css$/, '.js')}`, moduleSideEffects: false }; + }, + + load(id) { + if (!id.startsWith(INLINE_PREFIX)) return null; + + // Map back to the .css file path. + const rel = id.slice(INLINE_PREFIX.length).replace(/\.js$/, '.css'); + const file = resolve(rootDir, rel); + const raw = readFileSync(file, 'utf-8'); + const resolved = resolveImports(raw, dirname(file), skinsDir); + + return { code: `export default ${JSON.stringify(resolved)};`, moduleSideEffects: false }; + }, + }; +} diff --git a/build/plugins/resolve-css-imports.mjs b/build/plugins/resolve-css-imports.mjs new file mode 100644 index 00000000..23394b9c --- /dev/null +++ b/build/plugins/resolve-css-imports.mjs @@ -0,0 +1,27 @@ +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; + +/** + * Resolves `@import` directives in CSS content, inlining referenced files. + * + * @param {string} content - The CSS content to resolve. + * @param {string} baseDir - The directory to resolve relative imports from. + * @param {string} skinsDir - The directory to resolve `@videojs/skins` imports from. + * @returns {string} The resolved CSS content. + */ +export function resolveImports(content, baseDir, skinsDir) { + return content.replace(/@import\s+['"]([^'"]+)['"]\s*;/g, (_, importPath) => { + let file; + + if (importPath.startsWith('@videojs/skins/')) { + file = resolve(skinsDir, importPath.replace('@videojs/skins/', '')); + } else if (importPath.startsWith('.')) { + file = resolve(baseDir, importPath); + } else { + return _; + } + + const nested = readFileSync(file, 'utf-8'); + return resolveImports(nested, dirname(file), skinsDir); + }); +} diff --git a/scripts/link-aliases.mjs b/build/scripts/link-aliases.mjs similarity index 100% rename from scripts/link-aliases.mjs rename to build/scripts/link-aliases.mjs diff --git a/package.json b/package.json index 8dcfdb70..af20625e 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "scripts": { "prepare": "simple-git-hooks", - "postinstall": "node scripts/link-aliases.mjs", + "postinstall": "node build/scripts/link-aliases.mjs", "build:packages": "turbo run build --filter='./packages/*'", "build:site": "turbo run build --filter=site", "build": "turbo run build", @@ -29,7 +29,7 @@ "lint": "biome check .", "lint:fix": "biome check . --write", "lint:fix:file": "biome check --write", - "link:aliases": "node scripts/link-aliases.mjs", + "link:aliases": "node build/scripts/link-aliases.mjs", "size": "node .github/scripts/bundle-size.js | node .github/scripts/bundle-size-report.js", "test": "turbo run test", "format:astro": "prettier --write 'site/src/**/*.astro'", diff --git a/packages/html/src/define/audio/minimal-skin.css b/packages/html/src/define/audio/minimal-skin.css index 76c1d426..f7064f30 100644 --- a/packages/html/src/define/audio/minimal-skin.css +++ b/packages/html/src/define/audio/minimal-skin.css @@ -1,11 +1 @@ -.media-minimal-skin { - color: var(--media-color, red); -} - -audio-player { - display: contents; -} - -audio-minimal-skin { - display: contents; -} +@import "@videojs/skins/minimal/css/audio.css"; diff --git a/packages/html/src/define/audio/minimal-skin.tailwind.ts b/packages/html/src/define/audio/minimal-skin.tailwind.ts new file mode 100644 index 00000000..d4d76b06 --- /dev/null +++ b/packages/html/src/define/audio/minimal-skin.tailwind.ts @@ -0,0 +1,113 @@ +import { ReactiveElement } from '@videojs/element'; +import { renderIcon } from '@videojs/icons/render/minimal'; +import { playbackRate } from '@videojs/skins/default/tailwind/audio.tailwind'; +import { + button, + buttonGroup, + controls, + icon, + iconContainer, + iconFlipped, + iconState, + popup, + root, + seek, + slider, + time, +} from '@videojs/skins/minimal/tailwind/audio.tailwind'; +import { cn } from '@videojs/utils/style'; +import { SkinMixin } from '../skin-mixin'; + +// Side-effect imports: register all custom elements used in the template. +import '../media/container'; +import '../ui/mute-button'; +import '../ui/play-button'; +import '../ui/playback-rate-button'; +import '../ui/popover'; +import '../ui/seek-button'; +import '../ui/time'; +import '../ui/time-slider'; +import '../ui/volume-slider'; + +const SEEK_TIME = 10; + +function getTemplateHTML() { + return /*html*/ ` + + + +
+ + + ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} + ${renderIcon('play', { class: cn(icon, iconState.play.play) })} + ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + + + + ${renderIcon('seek', { class: cn(icon, iconFlipped) })} + ${SEEK_TIME} + + + + + + ${renderIcon('seek', { class: icon })} + ${SEEK_TIME} + + + + + + + + + + + + + + + + + + + + + + + + + + ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + + + + + + + + + + +
+
+ `; +} + +export class MinimalAudioSkinTailwindElement extends SkinMixin(ReactiveElement) { + static readonly tagName = 'audio-minimal-skin-tailwind'; + static getTemplateHTML = getTemplateHTML; +} + +customElements.define(MinimalAudioSkinTailwindElement.tagName, MinimalAudioSkinTailwindElement); + +declare global { + interface HTMLElementTagNameMap { + [MinimalAudioSkinTailwindElement.tagName]: MinimalAudioSkinTailwindElement; + } +} diff --git a/packages/html/src/define/audio/minimal-skin.ts b/packages/html/src/define/audio/minimal-skin.ts index a720a6c9..9ab891d0 100644 --- a/packages/html/src/define/audio/minimal-skin.ts +++ b/packages/html/src/define/audio/minimal-skin.ts @@ -1,20 +1,93 @@ import { ReactiveElement } from '@videojs/element'; +import { renderIcon } from '@videojs/icons/render/minimal'; +import { createStyles, SkinMixin } from '../skin-mixin'; +import styles from './minimal-skin.css?inline'; + +// Side-effect imports: register all custom elements used in the template. +import '../media/container'; +import '../ui/mute-button'; +import '../ui/play-button'; +import '../ui/playback-rate-button'; +import '../ui/popover'; +import '../ui/seek-button'; +import '../ui/time'; +import '../ui/time-slider'; +import '../ui/volume-slider'; + +const SEEK_TIME = 10; function getTemplateHTML() { - return /*html*/ `
`; + return /*html*/ ` + + + +
+ + + ${renderIcon('restart', { class: 'media-icon media-icon--restart' })} + ${renderIcon('play', { class: 'media-icon media-icon--play' })} + ${renderIcon('pause', { class: 'media-icon media-icon--pause' })} + + + + + ${renderIcon('seek', { class: 'media-icon media-icon--seek media-icon--flipped' })} + ${SEEK_TIME} + + + + + + ${renderIcon('seek', { class: 'media-icon media-icon--seek' })} + ${SEEK_TIME} + + + + + + + + + + + + + + + + + + + + + + + + + + ${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })} + ${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })} + ${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })} + + + + + + + + + + + +
+
+ `; } -export class MinimalAudioSkinElement extends ReactiveElement { +export class MinimalAudioSkinElement extends SkinMixin(ReactiveElement) { static readonly tagName = 'audio-minimal-skin'; + static styles = createStyles(styles); static getTemplateHTML = getTemplateHTML; - - constructor() { - super(); - const children = [...this.childNodes]; - this.innerHTML = getTemplateHTML(); - const container = this.firstElementChild; - if (container) for (const child of children) container.append(child); - } } customElements.define(MinimalAudioSkinElement.tagName, MinimalAudioSkinElement); diff --git a/packages/html/src/define/audio/skin.css b/packages/html/src/define/audio/skin.css index 83042de2..d02eea76 100644 --- a/packages/html/src/define/audio/skin.css +++ b/packages/html/src/define/audio/skin.css @@ -1,9 +1 @@ -@import "@videojs/skins/audio/default.css"; - -audio-player { - display: contents; -} - -audio-skin { - display: contents; -} +@import "@videojs/skins/default/css/audio.css"; diff --git a/packages/html/src/define/audio/skin.tailwind.ts b/packages/html/src/define/audio/skin.tailwind.ts new file mode 100644 index 00000000..ab517861 --- /dev/null +++ b/packages/html/src/define/audio/skin.tailwind.ts @@ -0,0 +1,104 @@ +import { ReactiveElement } from '@videojs/element'; +import { renderIcon } from '@videojs/icons/render'; +import { + button, + controls, + icon, + iconContainer, + iconFlipped, + iconState, + playbackRate, + popup, + root, + seek, + slider, + time, +} from '@videojs/skins/default/tailwind/audio.tailwind'; +import { cn } from '@videojs/utils/style'; +import { SkinMixin } from '../skin-mixin'; + +// Side-effect imports: register all custom elements used in the template. +import '../media/container'; +import '../ui/mute-button'; +import '../ui/play-button'; +import '../ui/playback-rate-button'; +import '../ui/popover'; +import '../ui/seek-button'; +import '../ui/time'; +import '../ui/time-slider'; +import '../ui/volume-slider'; + +const SEEK_TIME = 10; + +function getTemplateHTML() { + return /*html*/ ` + + + +
+ + ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} + ${renderIcon('play', { class: cn(icon, iconState.play.play) })} + ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + + + + ${renderIcon('seek', { class: cn(icon, iconFlipped) })} + ${SEEK_TIME} + + + + + + ${renderIcon('seek', { class: icon })} + ${SEEK_TIME} + + + + + + + + + + + + + + + + + + + + ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + + + + + + + + + +
+
+ `; +} + +export class AudioSkinTailwindElement extends SkinMixin(ReactiveElement) { + static readonly tagName = 'audio-skin-tailwind'; + static getTemplateHTML = getTemplateHTML; +} + +customElements.define(AudioSkinTailwindElement.tagName, AudioSkinTailwindElement); + +declare global { + interface HTMLElementTagNameMap { + [AudioSkinTailwindElement.tagName]: AudioSkinTailwindElement; + } +} diff --git a/packages/html/src/define/audio/skin.ts b/packages/html/src/define/audio/skin.ts index 17feef2d..7e86fab3 100644 --- a/packages/html/src/define/audio/skin.ts +++ b/packages/html/src/define/audio/skin.ts @@ -1,20 +1,85 @@ import { ReactiveElement } from '@videojs/element'; +import { renderIcon } from '@videojs/icons/render'; +import { createStyles, SkinMixin } from '../skin-mixin'; +import styles from './skin.css?inline'; + +// Side-effect imports: register all custom elements used in the template. +import '../media/container'; +import '../ui/mute-button'; +import '../ui/play-button'; +import '../ui/playback-rate-button'; +import '../ui/popover'; +import '../ui/seek-button'; +import '../ui/time'; +import '../ui/time-slider'; +import '../ui/volume-slider'; + +const SEEK_TIME = 10; function getTemplateHTML() { - return /*html*/ `
`; + return /*html*/ ` + + + +
+ + ${renderIcon('restart', { class: 'media-icon media-icon--restart' })} + ${renderIcon('play', { class: 'media-icon media-icon--play' })} + ${renderIcon('pause', { class: 'media-icon media-icon--pause' })} + + + + + ${renderIcon('seek', { class: 'media-icon media-icon--seek media-icon--flipped' })} + ${SEEK_TIME} + + + + + + ${renderIcon('seek', { class: 'media-icon media-icon--seek' })} + ${SEEK_TIME} + + + + + + + + + + + + + + + + + + + + ${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })} + ${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })} + ${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })} + + + + + + + + + + +
+
+ `; } -export class AudioSkinElement extends ReactiveElement { +export class AudioSkinElement extends SkinMixin(ReactiveElement) { static readonly tagName = 'audio-skin'; + static styles = createStyles(styles); static getTemplateHTML = getTemplateHTML; - - constructor() { - super(); - const children = [...this.childNodes]; - this.innerHTML = getTemplateHTML(); - const container = this.firstElementChild; - if (container) for (const child of children) container.append(child); - } } customElements.define(AudioSkinElement.tagName, AudioSkinElement); diff --git a/packages/html/src/define/skin-mixin.ts b/packages/html/src/define/skin-mixin.ts index 662ed068..46835daa 100644 --- a/packages/html/src/define/skin-mixin.ts +++ b/packages/html/src/define/skin-mixin.ts @@ -2,45 +2,81 @@ import type { ReactiveElement } from '@videojs/element'; import type { Constructor } from '@videojs/utils/types'; /** - * Mixin for skin elements that renders the template from a static - * `getTemplateHTML` method and resolves `` placeholders - * by replacing them with the actual media element children. + * Light DOM stylesheet that bridges CSS custom properties set by skins + * to native WebKit text track pseudo-elements on the slotted `