From 253beecbf9ea75ce95d3eda336cedc2ec0cbf154 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 22:52:48 +0000 Subject: [PATCH] feat(site): tailwind demo variants for util and controller references Adds react/tailwind and html/tailwind demo variants for create-player, html-create-player, player-controller, render-element, use-button, use-player, and use-store, gated on the style preference via StyleCase. Refs #840 https://claude.ai/code/session_01LUcwUd8E4Ekakp7NTFb6c5 --- .../react/tailwind/BasicUsage.tsx | 40 ++++++++++ .../html/tailwind/BasicUsage.astro | 9 +++ .../html/tailwind/BasicUsage.html | 17 ++++ .../html/tailwind/BasicUsage.ts | 59 ++++++++++++++ .../html/tailwind/BasicUsage.astro | 9 +++ .../html/tailwind/BasicUsage.html | 36 +++++++++ .../html/tailwind/BasicUsage.ts | 78 +++++++++++++++++++ .../react/tailwind/BasicUsage.tsx | 68 ++++++++++++++++ .../use-button/react/tailwind/BasicUsage.tsx | 31 ++++++++ .../use-player/react/tailwind/Selector.tsx | 47 +++++++++++ .../use-player/react/tailwind/StoreAccess.tsx | 47 +++++++++++ .../use-store/react/tailwind/Selector.tsx | 45 +++++++++++ .../use-store/react/tailwind/StoreAccess.tsx | 48 ++++++++++++ .../content/docs/reference/create-player.mdx | 11 +++ .../docs/reference/html-create-player.mdx | 12 +++ .../docs/reference/player-controller.mdx | 13 ++++ .../content/docs/reference/render-element.mdx | 11 +++ .../src/content/docs/reference/use-button.mdx | 11 +++ .../src/content/docs/reference/use-player.mdx | 22 ++++++ site/src/content/docs/reference/use-store.mdx | 22 ++++++ 20 files changed, 636 insertions(+) create mode 100644 site/src/components/docs/demos/create-player/react/tailwind/BasicUsage.tsx create mode 100644 site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.astro create mode 100644 site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.html create mode 100644 site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.ts create mode 100644 site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.astro create mode 100644 site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.html create mode 100644 site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.ts create mode 100644 site/src/components/docs/demos/render-element/react/tailwind/BasicUsage.tsx create mode 100644 site/src/components/docs/demos/use-button/react/tailwind/BasicUsage.tsx create mode 100644 site/src/components/docs/demos/use-player/react/tailwind/Selector.tsx create mode 100644 site/src/components/docs/demos/use-player/react/tailwind/StoreAccess.tsx create mode 100644 site/src/components/docs/demos/use-store/react/tailwind/Selector.tsx create mode 100644 site/src/components/docs/demos/use-store/react/tailwind/StoreAccess.tsx diff --git a/site/src/components/docs/demos/create-player/react/tailwind/BasicUsage.tsx b/site/src/components/docs/demos/create-player/react/tailwind/BasicUsage.tsx new file mode 100644 index 00000000..1e97c34c --- /dev/null +++ b/site/src/components/docs/demos/create-player/react/tailwind/BasicUsage.tsx @@ -0,0 +1,40 @@ +import { createPlayer } from '@videojs/react'; +import { Video, videoFeatures } from '@videojs/react/video'; + +const Player = createPlayer({ + features: videoFeatures, +}); + +function Controls() { + const store = Player.usePlayer(); + const paused = Player.usePlayer((s) => s.paused); + + return ( +
+ +
+ ); +} + +export default function BasicUsage() { + return ( + + + + + ); +} diff --git a/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.astro b/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.astro new file mode 100644 index 00000000..c515bacd --- /dev/null +++ b/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.astro @@ -0,0 +1,9 @@ +--- +import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro'; +import html from './BasicUsage.html?raw'; +--- + + + diff --git a/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.html b/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.html new file mode 100644 index 00000000..cc8ba335 --- /dev/null +++ b/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.html @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.ts b/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.ts new file mode 100644 index 00000000..220b3298 --- /dev/null +++ b/site/src/components/docs/demos/html-create-player/html/tailwind/BasicUsage.ts @@ -0,0 +1,59 @@ +import { + applyElementProps, + applyStateDataAttrs, + createButton, + createPlayer, + MediaElement, + PlayerController, + selectPlayback, +} from '@videojs/html'; +import { videoFeatures } from '@videojs/html/video'; +import '@videojs/html/media/container'; + +const { ProviderMixin, context } = createPlayer({ + features: videoFeatures, +}); + +class VideoPlayer extends ProviderMixin(MediaElement) { + static readonly tagName = 'demo-video-player'; +} + +class PlayToggle extends MediaElement { + static readonly tagName = 'demo-play-toggle'; + + readonly #player = new PlayerController(this, context, selectPlayback); + + #disconnect: AbortController | null = null; + + override connectedCallback(): void { + super.connectedCallback(); + this.#disconnect = new AbortController(); + + const buttonProps = createButton({ + onActivate: () => { + const state = this.#player.value; + if (!state) return; + state.paused ? state.play() : state.pause(); + }, + isDisabled: () => !this.#player.value, + }); + + applyElementProps(this, buttonProps, { signal: this.#disconnect.signal }); + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.#disconnect?.abort(); + this.#disconnect = null; + } + + protected override update(changed: Map): void { + super.update(changed); + const state = this.#player.value; + if (!state) return; + applyStateDataAttrs(this, state, { paused: 'data-paused', ended: 'data-ended' }); + } +} + +customElements.define(VideoPlayer.tagName, VideoPlayer); +customElements.define(PlayToggle.tagName, PlayToggle); diff --git a/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.astro b/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.astro new file mode 100644 index 00000000..c515bacd --- /dev/null +++ b/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.astro @@ -0,0 +1,9 @@ +--- +import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro'; +import html from './BasicUsage.html?raw'; +--- + + + diff --git a/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.html b/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.html new file mode 100644 index 00000000..5a73939d --- /dev/null +++ b/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.html @@ -0,0 +1,36 @@ + + + +
+ + + + + + + Paused: Yes | Time: 0.0s | Volume: 100% + +
+
+
diff --git a/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.ts b/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.ts new file mode 100644 index 00000000..a384704c --- /dev/null +++ b/site/src/components/docs/demos/player-controller/html/tailwind/BasicUsage.ts @@ -0,0 +1,78 @@ +import { + applyElementProps, + createButton, + createPlayer, + MediaElement, + PlayerController, + selectPlayback, + selectTime, + selectVolume, +} from '@videojs/html'; +import { videoFeatures } from '@videojs/html/video'; +import '@videojs/html/media/container'; + +const { ProviderMixin, context } = createPlayer({ + features: videoFeatures, +}); + +class DemoPlayer extends ProviderMixin(MediaElement) { + static readonly tagName = 'demo-ctrl-player'; +} + +class PlayerActions extends MediaElement { + static readonly tagName = 'demo-ctrl-actions'; + + readonly #player = new PlayerController(this, context); + + #disconnect: AbortController | null = null; + + override connectedCallback(): void { + super.connectedCallback(); + this.#disconnect = new AbortController(); + const signal = this.#disconnect.signal; + + const playBtn = this.querySelector('.play')!; + const pauseBtn = this.querySelector('.pause')!; + const volumeBtn = this.querySelector('.volume')!; + + const bind = (el: HTMLElement, action: () => void) => { + const props = createButton({ onActivate: action, isDisabled: () => !this.#player.value }); + applyElementProps(el, props, { signal }); + }; + + bind(playBtn, () => this.#player.value?.play()); + bind(pauseBtn, () => this.#player.value?.pause()); + bind(volumeBtn, () => this.#player.value?.setVolume(0.5)); + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.#disconnect?.abort(); + this.#disconnect = null; + } +} + +class PlayerState extends MediaElement { + static readonly tagName = 'demo-ctrl-state'; + + readonly #playback = new PlayerController(this, context, selectPlayback); + readonly #time = new PlayerController(this, context, selectTime); + readonly #volume = new PlayerController(this, context, selectVolume); + + protected override update(changed: Map): void { + super.update(changed); + const playback = this.#playback.value; + const time = this.#time.value; + const volume = this.#volume.value; + if (!playback) return; + + const el = this.querySelector('.text'); + if (el) { + el.textContent = `Paused: ${playback.paused ? 'Yes' : 'No'} | Time: ${(time?.currentTime ?? 0).toFixed(1)}s | Volume: ${Math.round((volume?.volume ?? 0) * 100)}%`; + } + } +} + +customElements.define(DemoPlayer.tagName, DemoPlayer); +customElements.define(PlayerActions.tagName, PlayerActions); +customElements.define(PlayerState.tagName, PlayerState); diff --git a/site/src/components/docs/demos/render-element/react/tailwind/BasicUsage.tsx b/site/src/components/docs/demos/render-element/react/tailwind/BasicUsage.tsx new file mode 100644 index 00000000..2455d378 --- /dev/null +++ b/site/src/components/docs/demos/render-element/react/tailwind/BasicUsage.tsx @@ -0,0 +1,68 @@ +import { renderElement } from '@videojs/react'; +import { type ReactNode, useState } from 'react'; + +interface TagState { + active: boolean; +} + +function Tag({ + className, + style, + render, + active, + children, +}: renderElement.ComponentProps & { active: boolean; children?: ReactNode }) { + const state: TagState = { active }; + + return renderElement( + 'span', + { className, style, render }, + { + state, + props: { children }, + stateAttrMap: { active: 'data-active' }, + } + ); +} + +export default function BasicUsage() { + const [active, setActive] = useState(false); + + const className = (state: TagState) => + state.active + ? 'inline-flex items-center rounded-full bg-blue-500 px-3 py-1.5 text-white transition-all duration-200' + : 'inline-flex items-center rounded-full bg-gray-200 px-3 py-1.5 text-gray-700 transition-all duration-200'; + + const style = (state: TagState) => ({ + fontSize: state.active ? '1.125rem' : '0.875rem', + }); + + return ( +
+ + +
+ + Default <span> + + + }> + Element <strong> + + + {state.active ? 'Active!' : 'Inactive'}} + /> +
+
+ ); +} diff --git a/site/src/components/docs/demos/use-button/react/tailwind/BasicUsage.tsx b/site/src/components/docs/demos/use-button/react/tailwind/BasicUsage.tsx new file mode 100644 index 00000000..4731564d --- /dev/null +++ b/site/src/components/docs/demos/use-button/react/tailwind/BasicUsage.tsx @@ -0,0 +1,31 @@ +import { useButton } from '@videojs/react'; +import type { Ref } from 'react'; +import { useState } from 'react'; + +export default function BasicUsage() { + const [count, setCount] = useState(0); + const [disabled, setDisabled] = useState(false); + + const { getButtonProps, buttonRef } = useButton({ + displayName: 'ActivateButton', + onActivate: () => setCount((c) => c + 1), + isDisabled: () => disabled, + }); + + return ( +
+ + +
+ ); +} diff --git a/site/src/components/docs/demos/use-player/react/tailwind/Selector.tsx b/site/src/components/docs/demos/use-player/react/tailwind/Selector.tsx new file mode 100644 index 00000000..38c0a5fd --- /dev/null +++ b/site/src/components/docs/demos/use-player/react/tailwind/Selector.tsx @@ -0,0 +1,47 @@ +import { createPlayer } from '@videojs/react'; +import { Video, videoFeatures } from '@videojs/react/video'; + +const Player = createPlayer({ + features: videoFeatures, +}); + +function StateDisplay() { + const state = Player.usePlayer((s) => ({ + paused: s.paused, + currentTime: s.currentTime, + duration: s.duration, + })); + + return ( +
+
+
Paused
+
{String(state.paused)}
+
+
+
Time
+
+ {state.currentTime.toFixed(1)}s / {state.duration.toFixed(1)}s +
+
+
+ ); +} + +export default function Selector() { + return ( + + + + + ); +} diff --git a/site/src/components/docs/demos/use-player/react/tailwind/StoreAccess.tsx b/site/src/components/docs/demos/use-player/react/tailwind/StoreAccess.tsx new file mode 100644 index 00000000..09606367 --- /dev/null +++ b/site/src/components/docs/demos/use-player/react/tailwind/StoreAccess.tsx @@ -0,0 +1,47 @@ +import { createPlayer } from '@videojs/react'; +import { Video, videoFeatures } from '@videojs/react/video'; + +const Player = createPlayer({ + features: videoFeatures, +}); + +function Controls() { + const store = Player.usePlayer(); + + return ( +
+ + +
+ ); +} + +export default function StoreAccess() { + return ( + + + + + ); +} diff --git a/site/src/components/docs/demos/use-store/react/tailwind/Selector.tsx b/site/src/components/docs/demos/use-store/react/tailwind/Selector.tsx new file mode 100644 index 00000000..2f80f555 --- /dev/null +++ b/site/src/components/docs/demos/use-store/react/tailwind/Selector.tsx @@ -0,0 +1,45 @@ +import { createPlayer, useStore } from '@videojs/react'; +import { Video, videoFeatures } from '@videojs/react/video'; + +const Player = createPlayer({ + features: videoFeatures, +}); + +function DerivedState() { + const store = Player.usePlayer(); + const derived = useStore(store, (s) => ({ + remaining: s.duration - s.currentTime, + progress: s.duration > 0 ? (s.currentTime / s.duration) * 100 : 0, + })); + + return ( +
+
+
Remaining
+
{derived.remaining.toFixed(1)}s
+
+
+
Progress
+
{derived.progress.toFixed(1)}%
+
+
+ ); +} + +export default function Selector() { + return ( + + + + + ); +} diff --git a/site/src/components/docs/demos/use-store/react/tailwind/StoreAccess.tsx b/site/src/components/docs/demos/use-store/react/tailwind/StoreAccess.tsx new file mode 100644 index 00000000..98f1396b --- /dev/null +++ b/site/src/components/docs/demos/use-store/react/tailwind/StoreAccess.tsx @@ -0,0 +1,48 @@ +import { createPlayer, useStore } from '@videojs/react'; +import { Video, videoFeatures } from '@videojs/react/video'; + +const Player = createPlayer({ + features: videoFeatures, +}); + +function SeekControls() { + const store = Player.usePlayer(); + const s = useStore(store); + + return ( +
+ + +
+ ); +} + +export default function StoreAccess() { + return ( + + + + + ); +} diff --git a/site/src/content/docs/reference/create-player.mdx b/site/src/content/docs/reference/create-player.mdx index 9d98f5c7..ab870e21 100644 --- a/site/src/content/docs/reference/create-player.mdx +++ b/site/src/content/docs/reference/create-player.mdx @@ -10,6 +10,8 @@ import StyleCase from "@/components/docs/StyleCase.astro"; import BasicUsageDemoReact from "@/components/docs/demos/create-player/react/css/BasicUsage"; import basicUsageReactTsx from "@/components/docs/demos/create-player/react/css/BasicUsage.tsx?raw"; import basicUsageReactCss from "@/components/docs/demos/create-player/react/css/BasicUsage.css?raw"; +import BasicUsageDemoReactTailwind from "@/components/docs/demos/create-player/react/tailwind/BasicUsage"; +import basicUsageReactTailwindTsx from "@/components/docs/demos/create-player/react/tailwind/BasicUsage.tsx?raw"; `createPlayer` is the entry point for setting up a Video.js player in React. It accepts a configuration object with a `features` array and returns hooks and components for building a player. @@ -38,6 +40,15 @@ const Player = createPlayer({ features: videoFeatures }); + + + + + diff --git a/site/src/content/docs/reference/html-create-player.mdx b/site/src/content/docs/reference/html-create-player.mdx index 84d367db..c48815d2 100644 --- a/site/src/content/docs/reference/html-create-player.mdx +++ b/site/src/content/docs/reference/html-create-player.mdx @@ -11,6 +11,9 @@ import BasicUsageDemoHtml from "@/components/docs/demos/html-create-player/html/ import basicUsageHtml from "@/components/docs/demos/html-create-player/html/css/BasicUsage.html?raw"; import basicUsageHtmlCss from "@/components/docs/demos/html-create-player/html/css/BasicUsage.css?raw"; import basicUsageHtmlTs from "@/components/docs/demos/html-create-player/html/css/BasicUsage.ts?raw"; +import BasicUsageDemoHtmlTailwind from "@/components/docs/demos/html-create-player/html/tailwind/BasicUsage.astro"; +import basicUsageTailwindHtml from "@/components/docs/demos/html-create-player/html/tailwind/BasicUsage.html?raw"; +import basicUsageTailwindHtmlTs from "@/components/docs/demos/html-create-player/html/tailwind/BasicUsage.ts?raw"; `createPlayer` is the entry point for setting up a Video.js player with HTML custom elements. It accepts a configuration object with a `features` array and returns a typed `PlayerController`, `context`, `ProviderMixin`, `ContainerMixin`, and a `create` store factory. @@ -71,4 +74,13 @@ class ComposedPlayer extends ProviderMixin(ContainerMixin(MediaElement)) {} + + + + + + diff --git a/site/src/content/docs/reference/player-controller.mdx b/site/src/content/docs/reference/player-controller.mdx index 951441e6..850e06ac 100644 --- a/site/src/content/docs/reference/player-controller.mdx +++ b/site/src/content/docs/reference/player-controller.mdx @@ -12,6 +12,9 @@ import BasicUsageDemoHtml from "@/components/docs/demos/player-controller/html/c import basicUsageHtml from "@/components/docs/demos/player-controller/html/css/BasicUsage.html?raw"; import basicUsageHtmlCss from "@/components/docs/demos/player-controller/html/css/BasicUsage.css?raw"; import basicUsageHtmlTs from "@/components/docs/demos/player-controller/html/css/BasicUsage.ts?raw"; +import BasicUsageDemoHtmlTailwind from "@/components/docs/demos/player-controller/html/tailwind/BasicUsage.astro"; +import basicUsageTailwindHtml from "@/components/docs/demos/player-controller/html/tailwind/BasicUsage.html?raw"; +import basicUsageTailwindHtmlTs from "@/components/docs/demos/player-controller/html/tailwind/BasicUsage.ts?raw"; `PlayerController` is a reactive controller that consumes the player store from `playerContext`. Without a selector it returns the store instance directly (no subscription — use this for actions). With a selector it returns the selected value and subscribes to changes, triggering a host update on shallow-equal change. Access the current value via `.value`, which returns `undefined` until connected to a provider. @@ -31,6 +34,16 @@ import basicUsageHtmlTs from "@/components/docs/demos/player-controller/html/css + + + + + diff --git a/site/src/content/docs/reference/render-element.mdx b/site/src/content/docs/reference/render-element.mdx index 5bab0546..0ff283d1 100644 --- a/site/src/content/docs/reference/render-element.mdx +++ b/site/src/content/docs/reference/render-element.mdx @@ -11,6 +11,8 @@ import StyleCase from "@/components/docs/StyleCase.astro"; import BasicUsageDemoReact from "@/components/docs/demos/render-element/react/css/BasicUsage"; import basicUsageReactTsx from "@/components/docs/demos/render-element/react/css/BasicUsage.tsx?raw"; import basicUsageReactCss from "@/components/docs/demos/render-element/react/css/BasicUsage.css?raw"; +import BasicUsageDemoReactTailwind from "@/components/docs/demos/render-element/react/tailwind/BasicUsage"; +import basicUsageReactTailwindTsx from "@/components/docs/demos/render-element/react/tailwind/BasicUsage.tsx?raw"; `renderElement` renders a UI component element, handling default tag rendering, render props (element or function), props merging, ref composition, and state-driven `className`/`style`. @@ -66,6 +68,15 @@ The `render` prop lets consumers fully customize the rendered element while pres + + + + + diff --git a/site/src/content/docs/reference/use-button.mdx b/site/src/content/docs/reference/use-button.mdx index 75164e3d..0c6b47b5 100644 --- a/site/src/content/docs/reference/use-button.mdx +++ b/site/src/content/docs/reference/use-button.mdx @@ -11,6 +11,8 @@ import StyleCase from "@/components/docs/StyleCase.astro"; import BasicUsageDemoReact from "@/components/docs/demos/use-button/react/css/BasicUsage"; import basicUsageReactTsx from "@/components/docs/demos/use-button/react/css/BasicUsage.tsx?raw"; import basicUsageReactCss from "@/components/docs/demos/use-button/react/css/BasicUsage.css?raw"; +import BasicUsageDemoReactTailwind from "@/components/docs/demos/use-button/react/tailwind/BasicUsage"; +import basicUsageReactTailwindTsx from "@/components/docs/demos/use-button/react/tailwind/BasicUsage.tsx?raw"; `useButton` provides button behavior including keyboard activation and accessibility checks. It returns a `getButtonProps` function for spreading onto a `