mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
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
This commit is contained in:
@@ -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 (
|
||||||
|
<div className="absolute bottom-2.5 left-2.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer rounded-full border border-white/30 bg-white/70 px-5 py-2 text-black backdrop-blur-[10px]"
|
||||||
|
onClick={() => (paused ? store.play() : store.pause())}
|
||||||
|
>
|
||||||
|
{paused ? 'Play' : 'Pause'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BasicUsage() {
|
||||||
|
return (
|
||||||
|
<Player.Provider>
|
||||||
|
<Player.Container className="relative">
|
||||||
|
<Video
|
||||||
|
className="w-full"
|
||||||
|
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
/>
|
||||||
|
<Controls />
|
||||||
|
</Player.Container>
|
||||||
|
</Player.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||||
|
import html from './BasicUsage.html?raw';
|
||||||
|
---
|
||||||
|
|
||||||
|
<HtmlDemo html={html} />
|
||||||
|
<script>
|
||||||
|
import "./BasicUsage.ts";
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<demo-video-player class="relative">
|
||||||
|
<media-container>
|
||||||
|
<video
|
||||||
|
class="w-full"
|
||||||
|
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
></video>
|
||||||
|
<demo-play-toggle
|
||||||
|
class="absolute bottom-2.5 left-2.5 cursor-pointer rounded-full border border-white/30 bg-white/70 px-5 py-2 text-black backdrop-blur-[10px]"
|
||||||
|
>
|
||||||
|
<span class="hidden in-data-paused:inline">Play</span>
|
||||||
|
<span class="hidden not-in-data-paused:inline">Pause</span>
|
||||||
|
</demo-play-toggle>
|
||||||
|
</media-container>
|
||||||
|
</demo-video-player>
|
||||||
@@ -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<string, unknown>): 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);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||||
|
import html from './BasicUsage.html?raw';
|
||||||
|
---
|
||||||
|
|
||||||
|
<HtmlDemo html={html} />
|
||||||
|
<script>
|
||||||
|
import "./BasicUsage.ts";
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<demo-ctrl-player class="relative">
|
||||||
|
<media-container>
|
||||||
|
<video
|
||||||
|
class="w-full"
|
||||||
|
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
></video>
|
||||||
|
<div class="flex items-center gap-4 border-t border-black/10 bg-black/5 p-3">
|
||||||
|
<demo-ctrl-actions class="flex gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="play cursor-pointer rounded-md border border-gray-300 bg-white px-3 py-1 text-[13px]"
|
||||||
|
>
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="pause cursor-pointer rounded-md border border-gray-300 bg-white px-3 py-1 text-[13px]"
|
||||||
|
>
|
||||||
|
Pause
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="volume cursor-pointer rounded-md border border-gray-300 bg-white px-3 py-1 text-[13px]"
|
||||||
|
>
|
||||||
|
50% Volume
|
||||||
|
</button>
|
||||||
|
</demo-ctrl-actions>
|
||||||
|
<demo-ctrl-state class="text-[13px] text-gray-700 tabular-nums">
|
||||||
|
<span class="text">Paused: Yes | Time: 0.0s | Volume: 100%</span>
|
||||||
|
</demo-ctrl-state>
|
||||||
|
</div>
|
||||||
|
</media-container>
|
||||||
|
</demo-ctrl-player>
|
||||||
@@ -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<HTMLButtonElement>('.play')!;
|
||||||
|
const pauseBtn = this.querySelector<HTMLButtonElement>('.pause')!;
|
||||||
|
const volumeBtn = this.querySelector<HTMLButtonElement>('.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<string, unknown>): 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);
|
||||||
@@ -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<TagState> & { 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 (
|
||||||
|
<div className="flex flex-col gap-4 p-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer self-start rounded-md border border-gray-300 bg-neutral-100 px-4 py-1.5"
|
||||||
|
onClick={() => setActive((prev) => !prev)}
|
||||||
|
>
|
||||||
|
{active ? 'Deactivate' : 'Activate'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<Tag active={active} className={className} style={style}>
|
||||||
|
Default <span>
|
||||||
|
</Tag>
|
||||||
|
|
||||||
|
<Tag active={active} className={className} style={style} render={<strong />}>
|
||||||
|
Element <strong>
|
||||||
|
</Tag>
|
||||||
|
|
||||||
|
<Tag
|
||||||
|
active={active}
|
||||||
|
className={className}
|
||||||
|
style={style}
|
||||||
|
render={(props, state) => <em {...props}>{state.active ? 'Active!' : 'Inactive'}</em>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className="flex flex-col gap-3 p-4">
|
||||||
|
<button
|
||||||
|
ref={buttonRef as Ref<HTMLButtonElement>}
|
||||||
|
{...getButtonProps()}
|
||||||
|
className="cursor-pointer self-start rounded-md border border-gray-300 bg-neutral-100 px-5 py-2 tabular-nums transition-opacity duration-200 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
Activated {count} times
|
||||||
|
</button>
|
||||||
|
<label className="flex items-center gap-1.5 text-sm text-gray-500">
|
||||||
|
<input type="checkbox" checked={disabled} onChange={(e) => setDisabled(e.target.checked)} />
|
||||||
|
Disabled
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<dl className="m-0 flex gap-4 border-t border-black/10 bg-black/5 p-3 text-[13px]">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<dt className="text-gray-500">Paused</dt>
|
||||||
|
<dd className="m-0 tabular-nums">{String(state.paused)}</dd>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<dt className="text-gray-500">Time</dt>
|
||||||
|
<dd className="m-0 tabular-nums">
|
||||||
|
{state.currentTime.toFixed(1)}s / {state.duration.toFixed(1)}s
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Selector() {
|
||||||
|
return (
|
||||||
|
<Player.Provider>
|
||||||
|
<Player.Container className="relative">
|
||||||
|
<Video
|
||||||
|
className="w-full"
|
||||||
|
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
<StateDisplay />
|
||||||
|
</Player.Container>
|
||||||
|
</Player.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className="flex gap-1.5 border-t border-black/10 bg-black/5 p-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer rounded-md border border-gray-300 bg-white px-3 py-1 text-[13px]"
|
||||||
|
onClick={() => store.play()}
|
||||||
|
>
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer rounded-md border border-gray-300 bg-white px-3 py-1 text-[13px]"
|
||||||
|
onClick={() => store.pause()}
|
||||||
|
>
|
||||||
|
Pause
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StoreAccess() {
|
||||||
|
return (
|
||||||
|
<Player.Provider>
|
||||||
|
<Player.Container className="relative">
|
||||||
|
<Video
|
||||||
|
className="w-full"
|
||||||
|
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
<Controls />
|
||||||
|
</Player.Container>
|
||||||
|
</Player.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<dl className="m-0 flex gap-4 border-t border-black/10 bg-black/5 p-3 text-[13px]">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<dt className="text-gray-500">Remaining</dt>
|
||||||
|
<dd className="m-0 tabular-nums">{derived.remaining.toFixed(1)}s</dd>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<dt className="text-gray-500">Progress</dt>
|
||||||
|
<dd className="m-0 tabular-nums">{derived.progress.toFixed(1)}%</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Selector() {
|
||||||
|
return (
|
||||||
|
<Player.Provider>
|
||||||
|
<Player.Container className="relative">
|
||||||
|
<Video
|
||||||
|
className="w-full"
|
||||||
|
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
<DerivedState />
|
||||||
|
</Player.Container>
|
||||||
|
</Player.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className="flex gap-1.5 border-t border-black/10 bg-black/5 p-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer rounded-md border border-gray-300 bg-white px-3 py-1 text-[13px]"
|
||||||
|
onClick={() => s.seek(0)}
|
||||||
|
>
|
||||||
|
Go to start
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer rounded-md border border-gray-300 bg-white px-3 py-1 text-[13px]"
|
||||||
|
onClick={() => s.seek(s.state.duration / 2)}
|
||||||
|
>
|
||||||
|
Go to middle
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StoreAccess() {
|
||||||
|
return (
|
||||||
|
<Player.Provider>
|
||||||
|
<Player.Container className="relative">
|
||||||
|
<Video
|
||||||
|
className="w-full"
|
||||||
|
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
<SeekControls />
|
||||||
|
</Player.Container>
|
||||||
|
</Player.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import StyleCase from "@/components/docs/StyleCase.astro";
|
|||||||
import BasicUsageDemoReact from "@/components/docs/demos/create-player/react/css/BasicUsage";
|
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 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 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.
|
`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 });
|
|||||||
<BasicUsageDemoReact client:idle />
|
<BasicUsageDemoReact client:idle />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "App.tsx", code: basicUsageReactTailwindTsx, lang: "tsx" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<BasicUsageDemoReactTailwind client:idle />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
<UtilReference util="createPlayer" />
|
<UtilReference util="createPlayer" />
|
||||||
|
|||||||
@@ -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 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 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 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 <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink>, `context`, `ProviderMixin`, `ContainerMixin`, and a `create` store factory.
|
`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 <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink>, `context`, `ProviderMixin`, `ContainerMixin`, and a `create` store factory.
|
||||||
|
|
||||||
@@ -71,4 +74,13 @@ class ComposedPlayer extends ProviderMixin(ContainerMixin(MediaElement)) {}
|
|||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo files={[
|
||||||
|
{ title: "index.html", code: basicUsageTailwindHtml, lang: "html" },
|
||||||
|
{ title: "index.ts", code: basicUsageTailwindHtmlTs, lang: "ts" },
|
||||||
|
]}>
|
||||||
|
<BasicUsageDemoHtmlTailwind />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
|
|
||||||
<UtilReference util="createPlayer" slug="html-create-player" />
|
<UtilReference util="createPlayer" slug="html-create-player" />
|
||||||
|
|||||||
@@ -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 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 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 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 <DocsLink slug="reference/player-context">`playerContext`</DocsLink>. 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.
|
`PlayerController` is a reactive controller that consumes the player store from <DocsLink slug="reference/player-context">`playerContext`</DocsLink>. 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
|
|||||||
<BasicUsageDemoHtml />
|
<BasicUsageDemoHtml />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "index.html", code: basicUsageTailwindHtml, lang: "html" },
|
||||||
|
{ title: "index.ts", code: basicUsageTailwindHtmlTs, lang: "ts" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<BasicUsageDemoHtmlTailwind />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
<UtilReference util="PlayerController" />
|
<UtilReference util="PlayerController" />
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import StyleCase from "@/components/docs/StyleCase.astro";
|
|||||||
import BasicUsageDemoReact from "@/components/docs/demos/render-element/react/css/BasicUsage";
|
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 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 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`.
|
`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
|
|||||||
<BasicUsageDemoReact client:idle />
|
<BasicUsageDemoReact client:idle />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "App.tsx", code: basicUsageReactTailwindTsx, lang: "tsx" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<BasicUsageDemoReactTailwind client:idle />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
<UtilReference util="renderElement" />
|
<UtilReference util="renderElement" />
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import StyleCase from "@/components/docs/StyleCase.astro";
|
|||||||
import BasicUsageDemoReact from "@/components/docs/demos/use-button/react/css/BasicUsage";
|
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 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 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 `<button>` element and a `buttonRef` for validation.
|
`useButton` provides button behavior including keyboard activation and accessibility checks. It returns a `getButtonProps` function for spreading onto a `<button>` element and a `buttonRef` for validation.
|
||||||
|
|
||||||
@@ -31,6 +33,15 @@ import basicUsageReactCss from "@/components/docs/demos/use-button/react/css/Bas
|
|||||||
<BasicUsageDemoReact client:idle />
|
<BasicUsageDemoReact client:idle />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "App.tsx", code: basicUsageReactTailwindTsx, lang: "tsx" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<BasicUsageDemoReactTailwind client:idle />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
<UtilReference util="useButton" />
|
<UtilReference util="useButton" />
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ import StyleCase from "@/components/docs/StyleCase.astro";
|
|||||||
import StoreAccessDemoReact from "@/components/docs/demos/use-player/react/css/StoreAccess";
|
import StoreAccessDemoReact from "@/components/docs/demos/use-player/react/css/StoreAccess";
|
||||||
import storeAccessReactTsx from "@/components/docs/demos/use-player/react/css/StoreAccess.tsx?raw";
|
import storeAccessReactTsx from "@/components/docs/demos/use-player/react/css/StoreAccess.tsx?raw";
|
||||||
import storeAccessReactCss from "@/components/docs/demos/use-player/react/css/StoreAccess.css?raw";
|
import storeAccessReactCss from "@/components/docs/demos/use-player/react/css/StoreAccess.css?raw";
|
||||||
|
import StoreAccessDemoReactTailwind from "@/components/docs/demos/use-player/react/tailwind/StoreAccess";
|
||||||
|
import storeAccessReactTailwindTsx from "@/components/docs/demos/use-player/react/tailwind/StoreAccess.tsx?raw";
|
||||||
import SelectorDemoReact from "@/components/docs/demos/use-player/react/css/Selector";
|
import SelectorDemoReact from "@/components/docs/demos/use-player/react/css/Selector";
|
||||||
import selectorReactTsx from "@/components/docs/demos/use-player/react/css/Selector.tsx?raw";
|
import selectorReactTsx from "@/components/docs/demos/use-player/react/css/Selector.tsx?raw";
|
||||||
import selectorReactCss from "@/components/docs/demos/use-player/react/css/Selector.css?raw";
|
import selectorReactCss from "@/components/docs/demos/use-player/react/css/Selector.css?raw";
|
||||||
|
import SelectorDemoReactTailwind from "@/components/docs/demos/use-player/react/tailwind/Selector";
|
||||||
|
import selectorReactTailwindTsx from "@/components/docs/demos/use-player/react/tailwind/Selector.tsx?raw";
|
||||||
|
|
||||||
`Player.usePlayer` gives you access to the player store from any component within a `Player.Provider`. It has two overloads — without arguments for direct store access, or with a selector for reactive state subscriptions. `Player.usePlayer` is typed to the features you passed to <DocsLink slug="reference/create-player">`createPlayer`</DocsLink>. It's also available as a standalone import (`import { usePlayer } from '@videojs/react'`), but the standalone version returns an untyped store.
|
`Player.usePlayer` gives you access to the player store from any component within a `Player.Provider`. It has two overloads — without arguments for direct store access, or with a selector for reactive state subscriptions. `Player.usePlayer` is typed to the features you passed to <DocsLink slug="reference/create-player">`createPlayer`</DocsLink>. It's also available as a standalone import (`import { usePlayer } from '@videojs/react'`), but the standalone version returns an untyped store.
|
||||||
|
|
||||||
@@ -34,6 +38,15 @@ Call `Player.usePlayer()` without arguments to get the store instance. Use this
|
|||||||
<StoreAccessDemoReact client:idle />
|
<StoreAccessDemoReact client:idle />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "App.tsx", code: storeAccessReactTailwindTsx, lang: "tsx" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<StoreAccessDemoReactTailwind client:idle />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
### Selector Subscription
|
### Selector Subscription
|
||||||
@@ -51,6 +64,15 @@ Pass a selector function to subscribe to specific state. The component re-render
|
|||||||
<SelectorDemoReact client:idle />
|
<SelectorDemoReact client:idle />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "App.tsx", code: selectorReactTailwindTsx, lang: "tsx" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SelectorDemoReactTailwind client:idle />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
<UtilReference util="usePlayer" />
|
<UtilReference util="usePlayer" />
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ import StyleCase from "@/components/docs/StyleCase.astro";
|
|||||||
import StoreAccessDemoReact from "@/components/docs/demos/use-store/react/css/StoreAccess";
|
import StoreAccessDemoReact from "@/components/docs/demos/use-store/react/css/StoreAccess";
|
||||||
import storeAccessReactTsx from "@/components/docs/demos/use-store/react/css/StoreAccess.tsx?raw";
|
import storeAccessReactTsx from "@/components/docs/demos/use-store/react/css/StoreAccess.tsx?raw";
|
||||||
import storeAccessReactCss from "@/components/docs/demos/use-store/react/css/StoreAccess.css?raw";
|
import storeAccessReactCss from "@/components/docs/demos/use-store/react/css/StoreAccess.css?raw";
|
||||||
|
import StoreAccessDemoReactTailwind from "@/components/docs/demos/use-store/react/tailwind/StoreAccess";
|
||||||
|
import storeAccessReactTailwindTsx from "@/components/docs/demos/use-store/react/tailwind/StoreAccess.tsx?raw";
|
||||||
import SelectorDemoReact from "@/components/docs/demos/use-store/react/css/Selector";
|
import SelectorDemoReact from "@/components/docs/demos/use-store/react/css/Selector";
|
||||||
import selectorReactTsx from "@/components/docs/demos/use-store/react/css/Selector.tsx?raw";
|
import selectorReactTsx from "@/components/docs/demos/use-store/react/css/Selector.tsx?raw";
|
||||||
import selectorReactCss from "@/components/docs/demos/use-store/react/css/Selector.css?raw";
|
import selectorReactCss from "@/components/docs/demos/use-store/react/css/Selector.css?raw";
|
||||||
|
import SelectorDemoReactTailwind from "@/components/docs/demos/use-store/react/tailwind/Selector";
|
||||||
|
import selectorReactTailwindTsx from "@/components/docs/demos/use-store/react/tailwind/Selector.tsx?raw";
|
||||||
|
|
||||||
`useStore` subscribes to a store instance directly. It has the same two overloads as <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> — without a selector for store access, or with a selector for reactive state. Within a player provider, `usePlayer` is usually simpler since it reads the store from context. Reach for `useStore` when you have a store instance directly or need to derive computed values from the store.
|
`useStore` subscribes to a store instance directly. It has the same two overloads as <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> — without a selector for store access, or with a selector for reactive state. Within a player provider, `usePlayer` is usually simpler since it reads the store from context. Reach for `useStore` when you have a store instance directly or need to derive computed values from the store.
|
||||||
|
|
||||||
@@ -34,6 +38,15 @@ Call `useStore(store)` without a selector to get the store instance back for imp
|
|||||||
<StoreAccessDemoReact client:idle />
|
<StoreAccessDemoReact client:idle />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "App.tsx", code: storeAccessReactTailwindTsx, lang: "tsx" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<StoreAccessDemoReactTailwind client:idle />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
### Selector Subscription
|
### Selector Subscription
|
||||||
@@ -51,6 +64,15 @@ Pass a selector to derive and subscribe to computed values from the store. The c
|
|||||||
<SelectorDemoReact client:idle />
|
<SelectorDemoReact client:idle />
|
||||||
</Demo>
|
</Demo>
|
||||||
</StyleCase>
|
</StyleCase>
|
||||||
|
<StyleCase styles={["tailwind"]}>
|
||||||
|
<Demo
|
||||||
|
files={[
|
||||||
|
{ title: "App.tsx", code: selectorReactTailwindTsx, lang: "tsx" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SelectorDemoReactTailwind client:idle />
|
||||||
|
</Demo>
|
||||||
|
</StyleCase>
|
||||||
</FrameworkCase>
|
</FrameworkCase>
|
||||||
|
|
||||||
<UtilReference util="useStore" />
|
<UtilReference util="useStore" />
|
||||||
|
|||||||
Reference in New Issue
Block a user