mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
docs(site): complete menu radio group references with demos and options hooks (#1807)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
82b9e43bb5
commit
a9a09a7e52
@@ -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,75 @@
|
||||
.video-player,
|
||||
.video-player media-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-media {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
media-menu-radio-item[aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<video-player class="video-player">
|
||||
<media-container>
|
||||
<hlsjs-video
|
||||
class="video-media"
|
||||
src="https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8"
|
||||
autoplay
|
||||
crossorigin="anonymous"
|
||||
muted
|
||||
playsinline
|
||||
loop
|
||||
></hlsjs-video>
|
||||
<div class="menu-bar">
|
||||
<button type="button" commandfor="audio-menu" class="settings-trigger">Audio</button>
|
||||
<media-menu id="audio-menu" side="top" align="end" class="menu">
|
||||
<media-audio-track-radio-group class="menu-group" label="Audio tracks">
|
||||
<template>
|
||||
<media-menu-radio-item class="menu-item">
|
||||
<span data-part="label"></span>
|
||||
<media-menu-item-indicator force-mount class="menu-indicator">✓</media-menu-item-indicator>
|
||||
</media-menu-radio-item>
|
||||
</template>
|
||||
</media-audio-track-radio-group>
|
||||
</media-menu>
|
||||
</div>
|
||||
</media-container>
|
||||
</video-player>
|
||||
@@ -0,0 +1,4 @@
|
||||
import '@videojs/html/video/player';
|
||||
import '@videojs/html/media/hlsjs-video';
|
||||
import '@videojs/html/ui/menu';
|
||||
import '@videojs/html/ui/audio-track-radio-group';
|
||||
@@ -0,0 +1,75 @@
|
||||
.media-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-container video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { createPlayer, Menu, useAudioTrackOptions } from '@videojs/react';
|
||||
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
|
||||
import { videoFeatures } from '@videojs/react/video';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
const src = 'https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8';
|
||||
|
||||
function AudioMenu(): ReactNode {
|
||||
const audioTrack = useAudioTrackOptions();
|
||||
if (audioTrack?.state.availability !== 'available') return null;
|
||||
|
||||
return (
|
||||
<Menu.Root side="top" align="end">
|
||||
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
|
||||
Audio
|
||||
</Menu.Trigger>
|
||||
<Menu.Content className="menu">
|
||||
<Menu.RadioGroup
|
||||
className="menu-group"
|
||||
value={audioTrack.value}
|
||||
onValueChange={audioTrack.setValue}
|
||||
aria-label="Audio tracks"
|
||||
>
|
||||
{audioTrack.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === audioTrack.value} forceMount className="menu-indicator">
|
||||
✓
|
||||
</Menu.ItemIndicator>
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
</Menu.Content>
|
||||
</Menu.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BasicUsage() {
|
||||
return (
|
||||
<Player.Provider>
|
||||
<Player.Container className="media-container">
|
||||
<HlsJsVideo src={src} autoPlay crossOrigin="anonymous" muted playsInline loop />
|
||||
<div className="menu-bar">
|
||||
<AudioMenu />
|
||||
</div>
|
||||
</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,75 @@
|
||||
.video-player,
|
||||
.video-player media-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-player video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
media-menu-radio-item[aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<video-player class="video-player">
|
||||
<media-container>
|
||||
<video
|
||||
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
loop
|
||||
>
|
||||
<track kind="captions" src="/docs/demos/captions-button/captions.vtt" srclang="en" label="English" />
|
||||
<track kind="subtitles" src="/docs/demos/captions-button/captions.vtt" srclang="es" label="Spanish" />
|
||||
</video>
|
||||
<div class="menu-bar">
|
||||
<button type="button" commandfor="captions-menu" class="settings-trigger">Captions</button>
|
||||
<media-menu id="captions-menu" side="top" align="end" class="menu">
|
||||
<media-captions-radio-group class="menu-group" label="Captions">
|
||||
<template>
|
||||
<media-menu-radio-item class="menu-item">
|
||||
<span data-part="label"></span>
|
||||
<media-menu-item-indicator force-mount class="menu-indicator">✓</media-menu-item-indicator>
|
||||
</media-menu-radio-item>
|
||||
</template>
|
||||
</media-captions-radio-group>
|
||||
</media-menu>
|
||||
</div>
|
||||
</media-container>
|
||||
</video-player>
|
||||
@@ -0,0 +1,3 @@
|
||||
import '@videojs/html/video/player';
|
||||
import '@videojs/html/ui/menu';
|
||||
import '@videojs/html/ui/captions-radio-group';
|
||||
@@ -0,0 +1,75 @@
|
||||
.media-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-container video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { createPlayer, Menu, useCaptionsOptions } from '@videojs/react';
|
||||
import { Video, videoFeatures } from '@videojs/react/video';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
|
||||
function CaptionsMenu(): ReactNode {
|
||||
const captions = useCaptionsOptions();
|
||||
if (captions?.state.availability !== 'available') return null;
|
||||
|
||||
return (
|
||||
<Menu.Root side="top" align="end">
|
||||
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
|
||||
Captions
|
||||
</Menu.Trigger>
|
||||
<Menu.Content className="menu">
|
||||
<Menu.RadioGroup
|
||||
className="menu-group"
|
||||
value={captions.value}
|
||||
onValueChange={captions.setValue}
|
||||
aria-label="Captions"
|
||||
>
|
||||
{captions.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === captions.value} forceMount className="menu-indicator">
|
||||
✓
|
||||
</Menu.ItemIndicator>
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
</Menu.Content>
|
||||
</Menu.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BasicUsage() {
|
||||
return (
|
||||
<Player.Provider>
|
||||
<Player.Container className="media-container">
|
||||
<Video
|
||||
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
>
|
||||
<track kind="captions" src="/docs/demos/captions-button/captions.vtt" srcLang="en" label="English" />
|
||||
<track kind="subtitles" src="/docs/demos/captions-button/captions.vtt" srcLang="es" label="Spanish" />
|
||||
</Video>
|
||||
<div className="menu-bar">
|
||||
<CaptionsMenu />
|
||||
</div>
|
||||
</Player.Container>
|
||||
</Player.Provider>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<media-container>
|
||||
<hlsjs-video
|
||||
class="video-media"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8"
|
||||
src="https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8"
|
||||
autoplay
|
||||
crossorigin="anonymous"
|
||||
muted
|
||||
|
||||
@@ -11,7 +11,7 @@ import { videoFeatures } from '@videojs/react/video';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
const src = 'https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8';
|
||||
const src = 'https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8';
|
||||
|
||||
function SettingsMenu(): ReactNode {
|
||||
const playbackRate = usePlaybackRateOptions();
|
||||
|
||||
@@ -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,75 @@
|
||||
.video-player,
|
||||
.video-player media-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-player video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
media-menu-radio-item[aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<video-player class="video-player">
|
||||
<media-container>
|
||||
<video
|
||||
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
loop
|
||||
></video>
|
||||
<div class="menu-bar">
|
||||
<button type="button" commandfor="speed-menu" class="settings-trigger">Speed</button>
|
||||
<media-menu id="speed-menu" side="top" align="end" class="menu">
|
||||
<media-playback-rate-radio-group class="menu-group" aria-label="Speed">
|
||||
<template>
|
||||
<media-menu-radio-item class="menu-item">
|
||||
<span data-part="label"></span>
|
||||
<media-menu-item-indicator force-mount class="menu-indicator">✓</media-menu-item-indicator>
|
||||
</media-menu-radio-item>
|
||||
</template>
|
||||
</media-playback-rate-radio-group>
|
||||
</media-menu>
|
||||
</div>
|
||||
</media-container>
|
||||
</video-player>
|
||||
@@ -0,0 +1,3 @@
|
||||
import '@videojs/html/video/player';
|
||||
import '@videojs/html/ui/menu';
|
||||
import '@videojs/html/ui/playback-rate-radio-group';
|
||||
@@ -0,0 +1,75 @@
|
||||
.media-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-container video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createPlayer, Menu, usePlaybackRateOptions } from '@videojs/react';
|
||||
import { Video, videoFeatures } from '@videojs/react/video';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
|
||||
function SpeedMenu(): ReactNode {
|
||||
const playbackRate = usePlaybackRateOptions();
|
||||
if (playbackRate?.state.availability !== 'available') return null;
|
||||
|
||||
return (
|
||||
<Menu.Root side="top" align="end">
|
||||
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
|
||||
Speed
|
||||
</Menu.Trigger>
|
||||
<Menu.Content className="menu">
|
||||
<Menu.RadioGroup
|
||||
className="menu-group"
|
||||
value={playbackRate.value}
|
||||
onValueChange={playbackRate.setValue}
|
||||
aria-label="Speed"
|
||||
>
|
||||
{playbackRate.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === playbackRate.value} forceMount className="menu-indicator">
|
||||
✓
|
||||
</Menu.ItemIndicator>
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
</Menu.Content>
|
||||
</Menu.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BasicUsage() {
|
||||
return (
|
||||
<Player.Provider>
|
||||
<Player.Container className="media-container">
|
||||
<Video
|
||||
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
/>
|
||||
<div className="menu-bar">
|
||||
<SpeedMenu />
|
||||
</div>
|
||||
</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,85 @@
|
||||
.video-player,
|
||||
.video-player media-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-media {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-tier {
|
||||
margin-left: 2px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.menu-badge {
|
||||
margin-left: auto;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
media-menu-radio-item[aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<video-player class="video-player">
|
||||
<media-container>
|
||||
<hlsjs-video
|
||||
class="video-media"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8"
|
||||
autoplay
|
||||
crossorigin="anonymous"
|
||||
muted
|
||||
playsinline
|
||||
loop
|
||||
></hlsjs-video>
|
||||
<div class="menu-bar">
|
||||
<button type="button" commandfor="quality-menu" class="settings-trigger">Quality</button>
|
||||
<media-menu id="quality-menu" side="top" align="end" class="menu">
|
||||
<media-quality-radio-group class="menu-group" label="Quality">
|
||||
<template>
|
||||
<media-menu-radio-item class="menu-item">
|
||||
<span>
|
||||
<span data-part="label"></span>
|
||||
<sup data-part="tier" class="menu-tier"></sup>
|
||||
</span>
|
||||
<span data-part="badge" class="menu-badge"></span>
|
||||
<media-menu-item-indicator force-mount class="menu-indicator">✓</media-menu-item-indicator>
|
||||
</media-menu-radio-item>
|
||||
</template>
|
||||
</media-quality-radio-group>
|
||||
</media-menu>
|
||||
</div>
|
||||
</media-container>
|
||||
</video-player>
|
||||
@@ -0,0 +1,4 @@
|
||||
import '@videojs/html/video/player';
|
||||
import '@videojs/html/media/hlsjs-video';
|
||||
import '@videojs/html/ui/menu';
|
||||
import '@videojs/html/ui/quality-radio-group';
|
||||
@@ -0,0 +1,85 @@
|
||||
.media-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-container video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
padding: 6px 16px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 9999px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu {
|
||||
--media-menu-side-offset: 8px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 180px;
|
||||
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
|
||||
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.menu-item[data-highlighted] {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.menu-tier {
|
||||
margin-left: 2px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.menu-badge {
|
||||
margin-left: auto;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.menu-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createPlayer, Menu, useQualityOptions } from '@videojs/react';
|
||||
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
|
||||
import { videoFeatures } from '@videojs/react/video';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
const src = 'https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8';
|
||||
|
||||
function QualityMenu(): ReactNode {
|
||||
const quality = useQualityOptions();
|
||||
if (quality?.state.availability !== 'available') return null;
|
||||
|
||||
return (
|
||||
<Menu.Root side="top" align="end">
|
||||
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
|
||||
Quality
|
||||
</Menu.Trigger>
|
||||
<Menu.Content className="menu">
|
||||
<Menu.RadioGroup
|
||||
className="menu-group"
|
||||
value={quality.value}
|
||||
onValueChange={quality.setValue}
|
||||
aria-label="Quality"
|
||||
>
|
||||
{quality.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
|
||||
<span>
|
||||
{option.label}
|
||||
{option.tier ? <sup className="menu-tier">{option.tier}</sup> : null}
|
||||
</span>
|
||||
{option.badge ? <span className="menu-badge">{option.badge}</span> : null}
|
||||
<Menu.ItemIndicator checked={option.value === quality.value} forceMount className="menu-indicator">
|
||||
✓
|
||||
</Menu.ItemIndicator>
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
</Menu.Content>
|
||||
</Menu.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BasicUsage() {
|
||||
return (
|
||||
<Player.Provider>
|
||||
<Player.Container className="media-container">
|
||||
<HlsJsVideo src={src} autoPlay crossOrigin="anonymous" muted playsInline loop />
|
||||
<div className="menu-bar">
|
||||
<QualityMenu />
|
||||
</div>
|
||||
</Player.Container>
|
||||
</Player.Provider>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,19 @@ description: A menu radio group for selecting an audio track
|
||||
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
import StyleCase from "@/components/docs/StyleCase.astro";
|
||||
import Demo from "@/components/docs/demos/Demo.astro";
|
||||
|
||||
{/* React demos */}
|
||||
import BasicUsageDemoReact from "@/components/docs/demos/audio-track-radio-group/react/css/BasicUsage";
|
||||
import basicUsageReactTsx from "@/components/docs/demos/audio-track-radio-group/react/css/BasicUsage.tsx?raw";
|
||||
import basicUsageReactCss from "@/components/docs/demos/audio-track-radio-group/react/css/BasicUsage.css?raw";
|
||||
|
||||
{/* HTML demos */}
|
||||
import BasicUsageDemoHtml from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.astro";
|
||||
import basicUsageHtml from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.html?raw";
|
||||
import basicUsageHtmlCss from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.css?raw";
|
||||
import basicUsageHtmlTs from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.ts?raw";
|
||||
|
||||
Creates radio items from the player audio track state and selects the enabled track.
|
||||
|
||||
@@ -15,16 +28,11 @@ Creates radio items from the player audio track state and selects the enabled tr
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
```tsx
|
||||
const audioTrack = useAudioTrackOptions();
|
||||
|
||||
<Menu.RadioGroup value={audioTrack.value} onValueChange={audioTrack.setValue}>
|
||||
<Menu.GroupLabel>Audio</Menu.GroupLabel>
|
||||
{audioTrack.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === audioTrack.value} />
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
<Menu.RadioGroup>
|
||||
<Menu.GroupLabel />
|
||||
<Menu.RadioItem>
|
||||
<Menu.ItemIndicator />
|
||||
</Menu.RadioItem>
|
||||
</Menu.RadioGroup>
|
||||
```
|
||||
</FrameworkCase>
|
||||
@@ -47,7 +55,7 @@ Creates radio items from the player audio track state and selects the enabled tr
|
||||
The group is available when the configured media exposes more than one audio track. Labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
`useAudioTrackOptions` returns `null` when the <DocsLink slug="reference/feature-audio-track">audio track feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
|
||||
<DocsLink slug="reference/use-audio-track-options">`useAudioTrackOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-audio-track">audio track feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
@@ -72,7 +80,42 @@ The group is available when the configured media exposes more than one audio tra
|
||||
|
||||
## Accessibility
|
||||
|
||||
The group uses the menu radio group pattern. It receives an accessible label from the `label` prop or defaults to `Audio`.
|
||||
The group uses the menu radio group pattern.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
The element receives an accessible label from the `label` property or defaults to `Audio`.
|
||||
</FrameworkCase>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic usage
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
|
||||
]}>
|
||||
<BasicUsageDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "index.html", code: basicUsageHtml, lang: "html" },
|
||||
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
|
||||
]}>
|
||||
<BasicUsageDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<ComponentReference component="AudioTrackRadioGroup" />
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: CaptionsRadioGroup
|
||||
frameworkTitle:
|
||||
html: media-captions-radio-group
|
||||
description: A menu radio group for selecting caption and subtitle tracks
|
||||
---
|
||||
|
||||
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
import StyleCase from "@/components/docs/StyleCase.astro";
|
||||
import Demo from "@/components/docs/demos/Demo.astro";
|
||||
|
||||
{/* React demos */}
|
||||
import BasicUsageDemoReact from "@/components/docs/demos/captions-radio-group/react/css/BasicUsage";
|
||||
import basicUsageReactTsx from "@/components/docs/demos/captions-radio-group/react/css/BasicUsage.tsx?raw";
|
||||
import basicUsageReactCss from "@/components/docs/demos/captions-radio-group/react/css/BasicUsage.css?raw";
|
||||
|
||||
{/* HTML demos */}
|
||||
import BasicUsageDemoHtml from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.astro";
|
||||
import basicUsageHtml from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.html?raw";
|
||||
import basicUsageHtmlCss from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.css?raw";
|
||||
import basicUsageHtmlTs from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.ts?raw";
|
||||
|
||||
Creates radio items from the player text track state and selects the showing caption or subtitle track.
|
||||
|
||||
## Anatomy
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
```tsx
|
||||
<Menu.RadioGroup>
|
||||
<Menu.GroupLabel />
|
||||
<Menu.RadioItem>
|
||||
<Menu.ItemIndicator />
|
||||
</Menu.RadioItem>
|
||||
</Menu.RadioGroup>
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
```html
|
||||
<media-captions-radio-group>
|
||||
<template>
|
||||
<media-menu-radio-item>
|
||||
<span data-part="label"></span>
|
||||
<media-menu-item-indicator></media-menu-item-indicator>
|
||||
</media-menu-radio-item>
|
||||
</template>
|
||||
</media-captions-radio-group>
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
The group is available when the configured media exposes at least one caption or subtitle track. The first generated option is `Off`; selecting it hides captions. Track labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<DocsLink slug="reference/use-captions-options">`useCaptionsOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-text-tracks">text tracks feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
`<media-captions-radio-group>` generates `<media-menu-radio-item>` children, including the `Off` item. Add an optional `<template>` with a single `<media-menu-radio-item>` to customize each generated item.
|
||||
</FrameworkCase>
|
||||
|
||||
## Styling
|
||||
|
||||
| Attribute | Values | Description |
|
||||
| --- | --- | --- |
|
||||
| `data-active` | Present / absent | Present when captions are enabled. |
|
||||
| `data-disabled` | Present / absent | Present when track selection is disabled. |
|
||||
| `data-availability` | `"available"` / `"unavailable"` | Whether caption or subtitle tracks are available. |
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
```css
|
||||
media-captions-radio-group[data-availability="unavailable"] {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Accessibility
|
||||
|
||||
The group uses the menu radio group pattern.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
The element receives an accessible label from the `label` property or defaults to `Enable captions` / `Disable captions` based on the current state.
|
||||
</FrameworkCase>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic usage
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
|
||||
]}>
|
||||
<BasicUsageDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "index.html", code: basicUsageHtml, lang: "html" },
|
||||
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
|
||||
]}>
|
||||
<BasicUsageDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<ComponentReference component="CaptionsRadioGroup" />
|
||||
</FrameworkCase>
|
||||
@@ -108,7 +108,7 @@ Menus open from a trigger and close when you select an item, click outside, move
|
||||
|
||||
Create a submenu by putting another menu inside the root menu's content. Its trigger stays in the root list, and its content becomes the next view. In HTML, give the submenu a matching `id` and point the trigger at it with `commandfor`. Wrap the root list in `Menu.View` or `<media-menu-view>` when the root list and submenus share one animated viewport.
|
||||
|
||||
Set `type="playback-rate"`, `type="quality"`, `type="audio-track"`, or `type="captions"` on a submenu trigger when it represents a media setting. `Menu.ItemValue` and `<media-menu-item-value>` read that setting context and display the current value, such as `1.5x`, `Auto`, `English`, or `Off`. See <DocsLink slug="reference/quality-radio-group">QualityRadioGroup</DocsLink> and <DocsLink slug="reference/audio-track-radio-group">AudioTrackRadioGroup</DocsLink> for the generated option groups.
|
||||
Set `type="playback-rate"`, `type="quality"`, `type="audio-track"`, or `type="captions"` on a submenu trigger when it represents a media setting. `Menu.ItemValue` and `<media-menu-item-value>` read that setting context and display the current value, such as `1.5x`, `Auto`, `English`, or `Off`. See <DocsLink slug="reference/playback-rate-radio-group">PlaybackRateRadioGroup</DocsLink>, <DocsLink slug="reference/quality-radio-group">QualityRadioGroup</DocsLink>, <DocsLink slug="reference/audio-track-radio-group">AudioTrackRadioGroup</DocsLink>, and <DocsLink slug="reference/captions-radio-group">CaptionsRadioGroup</DocsLink> for the generated option groups.
|
||||
|
||||
## Styling
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: PlaybackRateRadioGroup
|
||||
frameworkTitle:
|
||||
html: media-playback-rate-radio-group
|
||||
description: A menu radio group for selecting playback speed
|
||||
---
|
||||
|
||||
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
import StyleCase from "@/components/docs/StyleCase.astro";
|
||||
import Demo from "@/components/docs/demos/Demo.astro";
|
||||
|
||||
{/* React demos */}
|
||||
import BasicUsageDemoReact from "@/components/docs/demos/playback-rate-radio-group/react/css/BasicUsage";
|
||||
import basicUsageReactTsx from "@/components/docs/demos/playback-rate-radio-group/react/css/BasicUsage.tsx?raw";
|
||||
import basicUsageReactCss from "@/components/docs/demos/playback-rate-radio-group/react/css/BasicUsage.css?raw";
|
||||
|
||||
{/* HTML demos */}
|
||||
import BasicUsageDemoHtml from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.astro";
|
||||
import basicUsageHtml from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.html?raw";
|
||||
import basicUsageHtmlCss from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.css?raw";
|
||||
import basicUsageHtmlTs from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.ts?raw";
|
||||
|
||||
Creates radio items from the player playback rate state and selects the playback speed.
|
||||
|
||||
## Anatomy
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
```tsx
|
||||
<Menu.RadioGroup>
|
||||
<Menu.GroupLabel />
|
||||
<Menu.RadioItem>
|
||||
<Menu.ItemIndicator />
|
||||
</Menu.RadioItem>
|
||||
</Menu.RadioGroup>
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
```html
|
||||
<media-playback-rate-radio-group>
|
||||
<template>
|
||||
<media-menu-radio-item>
|
||||
<span data-part="label"></span>
|
||||
<media-menu-item-indicator></media-menu-item-indicator>
|
||||
</media-menu-radio-item>
|
||||
</template>
|
||||
</media-playback-rate-radio-group>
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
The group is available when the configured media exposes playback rates. Option labels default to the rate followed by a multiplication sign (e.g. `1.5×`); pass `formatRate` to customize them.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<DocsLink slug="reference/use-playback-rate-options">`usePlaybackRateOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-playback-rate">playback rate feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
`<media-playback-rate-radio-group>` generates `<media-menu-radio-item>` children. Add an optional `<template>` with a single `<media-menu-radio-item>` to customize each generated item.
|
||||
</FrameworkCase>
|
||||
|
||||
## Styling
|
||||
|
||||
| Attribute | Values | Description |
|
||||
| --- | --- | --- |
|
||||
| `data-rate` | `number` | Current playback rate. |
|
||||
| `data-disabled` | Present / absent | Present when playback rate selection is disabled. |
|
||||
| `data-availability` | `"available"` / `"unavailable"` | Whether playback rates are available. |
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
```css
|
||||
media-playback-rate-radio-group[data-availability="unavailable"] {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Accessibility
|
||||
|
||||
The group uses the menu radio group pattern.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
The element defaults to `Playback rate {rate}` (e.g. `Playback rate 1.5`). Override its accessible name with `aria-label` or `aria-labelledby`.
|
||||
</FrameworkCase>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic usage
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
|
||||
]}>
|
||||
<BasicUsageDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "index.html", code: basicUsageHtml, lang: "html" },
|
||||
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
|
||||
]}>
|
||||
<BasicUsageDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<ComponentReference component="PlaybackRateRadioGroup" />
|
||||
</FrameworkCase>
|
||||
@@ -8,6 +8,19 @@ description: A menu radio group for selecting video quality
|
||||
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
import StyleCase from "@/components/docs/StyleCase.astro";
|
||||
import Demo from "@/components/docs/demos/Demo.astro";
|
||||
|
||||
{/* React demos */}
|
||||
import BasicUsageDemoReact from "@/components/docs/demos/quality-radio-group/react/css/BasicUsage";
|
||||
import basicUsageReactTsx from "@/components/docs/demos/quality-radio-group/react/css/BasicUsage.tsx?raw";
|
||||
import basicUsageReactCss from "@/components/docs/demos/quality-radio-group/react/css/BasicUsage.css?raw";
|
||||
|
||||
{/* HTML demos */}
|
||||
import BasicUsageDemoHtml from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.astro";
|
||||
import basicUsageHtml from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.html?raw";
|
||||
import basicUsageHtmlCss from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.css?raw";
|
||||
import basicUsageHtmlTs from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.ts?raw";
|
||||
|
||||
Creates radio items from the player video rendition state and selects automatic or manual quality.
|
||||
|
||||
@@ -15,16 +28,11 @@ Creates radio items from the player video rendition state and selects automatic
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
```tsx
|
||||
const quality = useQualityOptions();
|
||||
|
||||
<Menu.RadioGroup value={quality.value} onValueChange={quality.setValue}>
|
||||
<Menu.GroupLabel>Quality</Menu.GroupLabel>
|
||||
{quality.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === quality.value} />
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
<Menu.RadioGroup>
|
||||
<Menu.GroupLabel />
|
||||
<Menu.RadioItem>
|
||||
<Menu.ItemIndicator />
|
||||
</Menu.RadioItem>
|
||||
</Menu.RadioGroup>
|
||||
```
|
||||
</FrameworkCase>
|
||||
@@ -49,7 +57,7 @@ Creates radio items from the player video rendition state and selects automatic
|
||||
The group is available when the configured media exposes more than one video rendition. The first generated option is `Auto`; selecting it returns control to adaptive bitrate selection. Pass `formatRendition` to customize visible rendition labels.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
`useQualityOptions` returns `null` when the <DocsLink slug="reference/feature-quality">quality feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
|
||||
<DocsLink slug="reference/use-quality-options">`useQualityOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-quality">quality feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
@@ -74,7 +82,42 @@ The group is available when the configured media exposes more than one video ren
|
||||
|
||||
## Accessibility
|
||||
|
||||
The group uses the menu radio group pattern. It receives an accessible label from the `label` prop or defaults to `Quality`.
|
||||
The group uses the menu radio group pattern.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
The element receives an accessible label from the `label` property or defaults to `Quality`.
|
||||
</FrameworkCase>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic usage
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
|
||||
]}>
|
||||
<BasicUsageDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo files={[
|
||||
{ title: "index.html", code: basicUsageHtml, lang: "html" },
|
||||
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
|
||||
]}>
|
||||
<BasicUsageDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<ComponentReference component="QualityRadioGroup" />
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: useAudioTrackOptions
|
||||
description: Hook to build audio track menu options from the player audio track state
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
|
||||
`useAudioTrackOptions` returns the currently enabled audio track, the available options, and a `setValue` callback for wiring track selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-audio-track">audio track feature</DocsLink> is not configured.
|
||||
|
||||
```tsx title="AudioMenu.tsx"
|
||||
import { Menu, useAudioTrackOptions } from "@videojs/react";
|
||||
|
||||
function AudioMenu() {
|
||||
const audioTrack = useAudioTrackOptions();
|
||||
if (audioTrack?.state.availability !== "available") return null;
|
||||
|
||||
return (
|
||||
<Menu.RadioGroup value={audioTrack.value} onValueChange={audioTrack.setValue} aria-label="Audio">
|
||||
{audioTrack.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === audioTrack.value} />
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Option labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels.
|
||||
|
||||
See <DocsLink slug="reference/audio-track-radio-group">AudioTrackRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
|
||||
|
||||
<UtilReference util="useAudioTrackOptions" />
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: useCaptionsOptions
|
||||
description: Hook to build captions menu options from the player text track state
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
|
||||
`useCaptionsOptions` returns the currently showing caption track, the available options (starting with an `Off` option), and a `setValue` callback for wiring caption selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-text-tracks">text tracks feature</DocsLink> is not configured.
|
||||
|
||||
```tsx title="CaptionsMenu.tsx"
|
||||
import { Menu, useCaptionsOptions } from "@videojs/react";
|
||||
|
||||
function CaptionsMenu() {
|
||||
const captions = useCaptionsOptions();
|
||||
if (captions?.state.availability !== "available") return null;
|
||||
|
||||
return (
|
||||
<Menu.RadioGroup value={captions.value} onValueChange={captions.setValue} aria-label="Captions">
|
||||
{captions.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === captions.value} />
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Option labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels. The result also exposes `showMenu`, which is `true` when more than one caption or subtitle track is available — the same threshold <DocsLink slug="reference/captions-button">CaptionsButton</DocsLink> uses to open a menu instead of toggling in `menuTrigger` mode.
|
||||
|
||||
See <DocsLink slug="reference/captions-radio-group">CaptionsRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
|
||||
|
||||
<UtilReference util="useCaptionsOptions" />
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: usePlaybackRateOptions
|
||||
description: Hook to build playback rate menu options from the player playback rate state
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
|
||||
`usePlaybackRateOptions` returns the current playback rate, the available options, and `setRate` / `setValue` callbacks for wiring rate selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-playback-rate">playback rate feature</DocsLink> is not configured.
|
||||
|
||||
```tsx title="SpeedMenu.tsx"
|
||||
import { Menu, usePlaybackRateOptions } from "@videojs/react";
|
||||
|
||||
function SpeedMenu() {
|
||||
const playbackRate = usePlaybackRateOptions();
|
||||
if (playbackRate?.state.availability !== "available") return null;
|
||||
|
||||
return (
|
||||
<Menu.RadioGroup value={playbackRate.value} onValueChange={playbackRate.setValue} aria-label="Speed">
|
||||
{playbackRate.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === playbackRate.value} />
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Options come from the player's configured playback rates. Labels default to the rate followed by a multiplication sign (e.g. `1.5×`); pass `formatRate` to customize them. `setValue` accepts an option's string value, while `setRate` accepts a number directly.
|
||||
|
||||
See <DocsLink slug="reference/playback-rate-radio-group">PlaybackRateRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
|
||||
|
||||
<UtilReference util="usePlaybackRateOptions" />
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: useQualityOptions
|
||||
description: Hook to build video quality menu options from the player rendition state
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
|
||||
`useQualityOptions` returns the current quality value, the available options, and a `setValue` callback for wiring quality selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-quality">quality feature</DocsLink> is not configured.
|
||||
|
||||
```tsx title="QualityMenu.tsx"
|
||||
import { Menu, useQualityOptions } from "@videojs/react";
|
||||
|
||||
function QualityMenu() {
|
||||
const quality = useQualityOptions();
|
||||
if (quality?.state.availability !== "available") return null;
|
||||
|
||||
return (
|
||||
<Menu.RadioGroup value={quality.value} onValueChange={quality.setValue} aria-label="Quality">
|
||||
{quality.options.map((option) => (
|
||||
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
{option.label}
|
||||
<Menu.ItemIndicator checked={option.value === quality.value} />
|
||||
</Menu.RadioItem>
|
||||
))}
|
||||
</Menu.RadioGroup>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The first option is always `Auto`; selecting it returns control to adaptive bitrate selection. Options carry optional `tier` and `badge` fields (e.g. `4K`, `HD`) for richer labels. Pass `formatRendition` to customize the visible rendition labels.
|
||||
|
||||
See <DocsLink slug="reference/quality-radio-group">QualityRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
|
||||
|
||||
<UtilReference util="useQualityOptions" />
|
||||
@@ -98,7 +98,7 @@ React and HTML demos for the same variant should use matching BEM structures.
|
||||
|
||||
### Video and poster sources
|
||||
|
||||
All demos use these URLs:
|
||||
Demos default to these URLs:
|
||||
|
||||
These demo assets are hosted by [Mux](https://www.mux.com?utm_source=videojs&utm_campaign=vjs10):
|
||||
|
||||
@@ -107,6 +107,15 @@ Video: https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highe
|
||||
Poster: https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.jpg
|
||||
```
|
||||
|
||||
Components that need adaptive streams use `<HlsJsVideo>` / `<hlsjs-video>` with an HLS source instead. Pick by what the component consumes:
|
||||
|
||||
```
|
||||
Renditions (quality): https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8
|
||||
Multi-language audio (+ renditions): https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8
|
||||
```
|
||||
|
||||
The default mp4 has a single rendition and a single audio track — quality and audio-track demos render nothing against it.
|
||||
|
||||
All demo videos use `autoplay muted playsinline loop` (React: `autoPlay muted playsInline loop`).
|
||||
|
||||
## Create the MDX reference page
|
||||
@@ -161,7 +170,7 @@ import basicUsageHtmlTs from "@/components/docs/demos/{name}/html/css/BasicUsage
|
||||
|
||||
After imports, the page follows this order:
|
||||
|
||||
1. **Anatomy** — show the component markup for each framework using `<FrameworkCase>`
|
||||
1. **Anatomy** — show part nesting with self-closing placeholders for each framework using `<FrameworkCase>`, following the [Base UI anatomy convention](https://base-ui.com/react/overview/quick-start). No hooks, state, handlers, or option mapping — working code belongs in Examples.
|
||||
2. **Prose sections** (optional, as needed):
|
||||
- **Behavior** — state transitions, timing, interaction logic
|
||||
- **Styling** — data attribute CSS selectors
|
||||
|
||||
@@ -65,6 +65,7 @@ export const sidebar: Sidebar = [
|
||||
{ slug: 'reference/audio-track-radio-group' },
|
||||
{ slug: 'reference/buffering-indicator' },
|
||||
{ slug: 'reference/captions-button' },
|
||||
{ slug: 'reference/captions-radio-group' },
|
||||
{ slug: 'reference/cast-button' },
|
||||
{ slug: 'reference/controls' },
|
||||
{ slug: 'reference/fullscreen-button' },
|
||||
@@ -73,6 +74,7 @@ export const sidebar: Sidebar = [
|
||||
{ slug: 'reference/pip-button' },
|
||||
{ slug: 'reference/play-button' },
|
||||
{ slug: 'reference/playback-rate-button' },
|
||||
{ slug: 'reference/playback-rate-radio-group' },
|
||||
{ slug: 'reference/popover' },
|
||||
{ slug: 'reference/poster' },
|
||||
{ slug: 'reference/quality-radio-group' },
|
||||
@@ -113,10 +115,14 @@ export const sidebar: Sidebar = [
|
||||
defaultOpen: false,
|
||||
contents: [
|
||||
{ slug: 'reference/render-element' },
|
||||
{ slug: 'reference/use-audio-track-options' },
|
||||
{ slug: 'reference/use-button' },
|
||||
{ slug: 'reference/use-captions-options' },
|
||||
{ slug: 'reference/use-container-attach' },
|
||||
{ slug: 'reference/use-media-attach' },
|
||||
{ slug: 'reference/use-playback-rate-options' },
|
||||
{ slug: 'reference/use-player-context' },
|
||||
{ slug: 'reference/use-quality-options' },
|
||||
{ slug: 'reference/use-selector' },
|
||||
{ slug: 'reference/use-snapshot' },
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user