mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
docs(site): add thumbnail reference page (#654)
This commit is contained in:
@@ -15,6 +15,11 @@ export interface PlayerContextValue {
|
||||
}
|
||||
|
||||
const PlayerContext = createContext<PlayerContextValue | null>(null);
|
||||
const EMPTY_UNSUBSCRIBE = () => {};
|
||||
const EMPTY_STORE = {
|
||||
state: {} as UnknownState,
|
||||
subscribe: () => EMPTY_UNSUBSCRIBE,
|
||||
} as Pick<UnknownStore, 'state' | 'subscribe'>;
|
||||
|
||||
export function PlayerContextProvider({
|
||||
value,
|
||||
@@ -51,6 +56,23 @@ export function usePlayer<R>(selector?: (state: UnknownState) => R) {
|
||||
return useStore(store, selector as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access player state when available, but return `undefined` outside Provider.
|
||||
*
|
||||
* This is useful for components that can operate without player context
|
||||
* (e.g. they accept fully explicit props as a fallback).
|
||||
*/
|
||||
/** @label Without Selector */
|
||||
export function useOptionalPlayer(): UnknownStore | undefined;
|
||||
/** @label With Selector */
|
||||
export function useOptionalPlayer<R>(selector: (state: UnknownState) => R): R | undefined;
|
||||
export function useOptionalPlayer<R>(selector?: (state: UnknownState) => R) {
|
||||
const ctx = useContext(PlayerContext);
|
||||
const store = (ctx?.store ?? (EMPTY_STORE as unknown as UnknownStore)) as UnknownStore;
|
||||
const value = useStore(store, (ctx ? selector : undefined) as any);
|
||||
return ctx ? value : undefined;
|
||||
}
|
||||
|
||||
/** Access the media element from within a Player Provider. */
|
||||
export function useMedia(): Media | null {
|
||||
const { media } = usePlayerContext();
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type PlayerContextValue,
|
||||
useMedia,
|
||||
useMediaRegistration,
|
||||
useOptionalPlayer,
|
||||
usePlayer,
|
||||
usePlayerContext,
|
||||
} from '../context';
|
||||
@@ -83,6 +84,47 @@ describe('usePlayer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('useOptionalPlayer', () => {
|
||||
it('returns undefined outside Provider', () => {
|
||||
const { result } = renderHook(() => useOptionalPlayer());
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined outside Provider with selector', () => {
|
||||
const { result } = renderHook(() => useOptionalPlayer((state: any) => state.paused));
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not run selector outside Provider', () => {
|
||||
const selector = vi.fn(() => true);
|
||||
const { result } = renderHook(() => useOptionalPlayer(selector));
|
||||
expect(result.current).toBeUndefined();
|
||||
expect(selector).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns store inside Provider', () => {
|
||||
const store = createMockStore();
|
||||
const value: PlayerContextValue = { store: store as any, media: null, setMedia: vi.fn() };
|
||||
|
||||
const { result } = renderHook(() => useOptionalPlayer(), {
|
||||
wrapper: createWrapper(value),
|
||||
});
|
||||
|
||||
expect(result.current).toBe(store);
|
||||
});
|
||||
|
||||
it('returns selected state inside Provider', () => {
|
||||
const store = createMockStore();
|
||||
const value: PlayerContextValue = { store: store as any, media: null, setMedia: vi.fn() };
|
||||
|
||||
const { result } = renderHook(() => useOptionalPlayer((state: any) => state.paused), {
|
||||
wrapper: createWrapper(value),
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMedia', () => {
|
||||
it('returns media from context', () => {
|
||||
const store = createMockStore();
|
||||
|
||||
@@ -11,7 +11,7 @@ import { createThumbnail, selectTextTrack } from '@videojs/core/dom';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { usePlayer } from '../../player/context';
|
||||
import { useOptionalPlayer } from '../../player/context';
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
|
||||
@@ -36,7 +36,7 @@ export const Thumbnail = forwardRef<HTMLDivElement, ThumbnailProps>(function Thu
|
||||
const [core] = useState(() => new ThumbnailCore());
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const textTrack = usePlayer(selectTextTrack);
|
||||
const textTrack = useOptionalPlayer(selectTextTrack);
|
||||
|
||||
// Force re-render when the handle's state changes (img load/error, resize).
|
||||
const [, setRenderToken] = useState(0);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
WEBVTT
|
||||
|
||||
00:00:00.000 --> 00:00:10.000
|
||||
https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=0
|
||||
|
||||
00:00:10.000 --> 00:00:20.000
|
||||
https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=10
|
||||
|
||||
00:00:20.000 --> 00:00:30.000
|
||||
https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=20
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||
import html from './BasicUsage.html?raw';
|
||||
import './BasicUsage.css';
|
||||
---
|
||||
|
||||
<HtmlDemo html={html} />
|
||||
<script>
|
||||
import './BasicUsage.ts';
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
.html-thumbnail-text-track {
|
||||
position: relative;
|
||||
display: block;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.html-thumbnail-text-track__media {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.html-thumbnail-text-track__thumbnail {
|
||||
display: block;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.html-thumbnail-text-track__thumbnail[data-hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<section class="html-thumbnail-text-track">
|
||||
<video-player>
|
||||
<media-container>
|
||||
<video
|
||||
class="html-thumbnail-text-track__media"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
preload="auto"
|
||||
muted
|
||||
playsinline
|
||||
crossorigin="anonymous"
|
||||
>
|
||||
<track
|
||||
kind="metadata"
|
||||
label="thumbnails"
|
||||
src="/docs/demos/thumbnail/basic.vtt"
|
||||
default
|
||||
/>
|
||||
</video>
|
||||
<media-thumbnail class="html-thumbnail-text-track__thumbnail" time="12"></media-thumbnail>
|
||||
</media-container>
|
||||
</video-player>
|
||||
</section>
|
||||
@@ -0,0 +1,2 @@
|
||||
import '@videojs/html/video/player';
|
||||
import '@videojs/html/ui/thumbnail';
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||
import html from './JsonSpriteUsage.html?raw';
|
||||
---
|
||||
|
||||
<HtmlDemo html={html} />
|
||||
<script>
|
||||
import './JsonSpriteUsage.ts';
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
<media-thumbnail time="12" style="max-width: 240px;"></media-thumbnail>
|
||||
@@ -0,0 +1,41 @@
|
||||
import '@videojs/html/ui/thumbnail';
|
||||
|
||||
type DemoThumbnailImage = {
|
||||
url: string;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
coords?: { x: number; y: number };
|
||||
};
|
||||
|
||||
type ThumbnailDemoElement = HTMLElement & { thumbnails?: DemoThumbnailImage[] };
|
||||
|
||||
const thumbnail = document.querySelector<ThumbnailDemoElement>('media-thumbnail');
|
||||
if (thumbnail) {
|
||||
thumbnail.thumbnails = [
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.jpg',
|
||||
startTime: 0,
|
||||
endTime: 10,
|
||||
width: 284,
|
||||
height: 160,
|
||||
coords: { x: 0, y: 0 },
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.jpg',
|
||||
startTime: 10,
|
||||
endTime: 20,
|
||||
width: 284,
|
||||
height: 160,
|
||||
coords: { x: 284, y: 0 },
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.jpg',
|
||||
startTime: 20,
|
||||
width: 284,
|
||||
height: 160,
|
||||
coords: { x: 568, y: 0 },
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||
import html from './JsonUsage.html?raw';
|
||||
---
|
||||
|
||||
<HtmlDemo html={html} />
|
||||
<script>
|
||||
import './JsonUsage.ts';
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
<media-thumbnail time="12" style="max-width: 240px;"></media-thumbnail>
|
||||
@@ -0,0 +1,29 @@
|
||||
import '@videojs/html/ui/thumbnail';
|
||||
|
||||
type DemoThumbnailImage = {
|
||||
url: string;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
};
|
||||
|
||||
type ThumbnailDemoElement = HTMLElement & { thumbnails?: DemoThumbnailImage[] };
|
||||
|
||||
const thumbnail = document.querySelector<ThumbnailDemoElement>('media-thumbnail');
|
||||
if (thumbnail) {
|
||||
thumbnail.thumbnails = [
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=0',
|
||||
startTime: 0,
|
||||
endTime: 10,
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=10',
|
||||
startTime: 10,
|
||||
endTime: 20,
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=20',
|
||||
startTime: 20,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.react-thumbnail-text-track {
|
||||
position: relative;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.react-thumbnail-text-track__media {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-thumbnail-text-track__thumbnail {
|
||||
display: block;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.react-thumbnail-text-track__thumbnail[data-hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { createPlayer, Thumbnail } from '@videojs/react';
|
||||
import { Video, videoFeatures } from '@videojs/react/video';
|
||||
|
||||
import './BasicUsage.css';
|
||||
|
||||
const Player = createPlayer({ features: videoFeatures });
|
||||
|
||||
export default function TextTrackUsage() {
|
||||
return (
|
||||
<Player.Provider>
|
||||
<Player.Container className="react-thumbnail-text-track">
|
||||
<Video
|
||||
className="react-thumbnail-text-track__media"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
preload="auto"
|
||||
muted
|
||||
playsInline
|
||||
crossOrigin="anonymous"
|
||||
>
|
||||
<track kind="metadata" label="thumbnails" src="/docs/demos/thumbnail/basic.vtt" default />
|
||||
</Video>
|
||||
<Thumbnail className="react-thumbnail-text-track__thumbnail" time={12} />
|
||||
</Player.Container>
|
||||
</Player.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Thumbnail } from '@videojs/react';
|
||||
|
||||
const THUMBNAILS = [
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.jpg',
|
||||
startTime: 0,
|
||||
endTime: 10,
|
||||
width: 284,
|
||||
height: 160,
|
||||
coords: { x: 0, y: 0 },
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.jpg',
|
||||
startTime: 10,
|
||||
endTime: 20,
|
||||
width: 284,
|
||||
height: 160,
|
||||
coords: { x: 284, y: 0 },
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.jpg',
|
||||
startTime: 20,
|
||||
width: 284,
|
||||
height: 160,
|
||||
coords: { x: 568, y: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
export default function JsonSpriteUsage() {
|
||||
return <Thumbnail thumbnails={THUMBNAILS} time={12} style={{ maxWidth: 240 }} />;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Thumbnail } from '@videojs/react';
|
||||
|
||||
const THUMBNAILS = [
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=0',
|
||||
startTime: 0,
|
||||
endTime: 10,
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=10',
|
||||
startTime: 10,
|
||||
endTime: 20,
|
||||
},
|
||||
{
|
||||
url: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.jpg?time=20',
|
||||
startTime: 20,
|
||||
},
|
||||
];
|
||||
|
||||
export default function JsonUsage() {
|
||||
return <Thumbnail thumbnails={THUMBNAILS} time={12} style={{ maxWidth: 240 }} />;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
title: Thumbnail
|
||||
frameworkTitle:
|
||||
html: media-thumbnail
|
||||
description: Time-based thumbnail preview component for timeline scrubbing and hover previews
|
||||
---
|
||||
|
||||
import ComponentReference from "@/components/docs/api-reference/ComponentReference.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 TextTrackDemoReact from "@/components/docs/demos/thumbnail/react/css/BasicUsage";
|
||||
import textTrackReactTsx from "@/components/docs/demos/thumbnail/react/css/BasicUsage.tsx?raw";
|
||||
import textTrackReactCss from "@/components/docs/demos/thumbnail/react/css/BasicUsage.css?raw";
|
||||
import JsonDemoReact from "@/components/docs/demos/thumbnail/react/css/JsonUsage";
|
||||
import jsonReactTsx from "@/components/docs/demos/thumbnail/react/css/JsonUsage.tsx?raw";
|
||||
import JsonSpriteDemoReact from "@/components/docs/demos/thumbnail/react/css/JsonSpriteUsage";
|
||||
import jsonSpriteReactTsx from "@/components/docs/demos/thumbnail/react/css/JsonSpriteUsage.tsx?raw";
|
||||
|
||||
{/* HTML demos */}
|
||||
import TextTrackDemoHtml from "@/components/docs/demos/thumbnail/html/css/BasicUsage.astro";
|
||||
import textTrackHtml from "@/components/docs/demos/thumbnail/html/css/BasicUsage.html?raw";
|
||||
import textTrackHtmlCss from "@/components/docs/demos/thumbnail/html/css/BasicUsage.css?raw";
|
||||
import textTrackHtmlTs from "@/components/docs/demos/thumbnail/html/css/BasicUsage.ts?raw";
|
||||
import JsonDemoHtml from "@/components/docs/demos/thumbnail/html/css/JsonUsage.astro";
|
||||
import jsonHtml from "@/components/docs/demos/thumbnail/html/css/JsonUsage.html?raw";
|
||||
import jsonHtmlTs from "@/components/docs/demos/thumbnail/html/css/JsonUsage.ts?raw";
|
||||
import JsonSpriteDemoHtml from "@/components/docs/demos/thumbnail/html/css/JsonSpriteUsage.astro";
|
||||
import jsonSpriteHtml from "@/components/docs/demos/thumbnail/html/css/JsonSpriteUsage.html?raw";
|
||||
import jsonSpriteHtmlTs from "@/components/docs/demos/thumbnail/html/css/JsonSpriteUsage.ts?raw";
|
||||
|
||||
## Quick Start: Video Track
|
||||
|
||||
`Thumbnail` can read thumbnail cues directly from your video track. Add a `<track>` with `kind="metadata"` and `label="thumbnails"` to your media element.
|
||||
|
||||
Mux provides this as `storyboard.vtt`:
|
||||
|
||||
`https://image.mux.com/{PLAYBACK_ID}/storyboard.vtt`
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
```tsx
|
||||
<Video src="video.mp4">
|
||||
<track
|
||||
kind="metadata"
|
||||
label="thumbnails"
|
||||
src="https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.vtt"
|
||||
default
|
||||
/>
|
||||
</Video>
|
||||
<Thumbnail time={12} />
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
```html
|
||||
<video src="video.mp4">
|
||||
<track
|
||||
kind="metadata"
|
||||
label="thumbnails"
|
||||
src="https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/storyboard.vtt"
|
||||
default
|
||||
/>
|
||||
</video>
|
||||
<media-thumbnail time="12"></media-thumbnail>
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Anatomy
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
```tsx
|
||||
<Thumbnail time={12} />
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
```html
|
||||
<media-thumbnail time="12"></media-thumbnail>
|
||||
```
|
||||
</FrameworkCase>
|
||||
|
||||
## Behavior
|
||||
|
||||
`Thumbnail` resolves an image for the current `time`.
|
||||
|
||||
Supported source formats:
|
||||
|
||||
- Text track: `<track kind="metadata" label="thumbnails" src="...vtt">`
|
||||
- JSON array: `{ url, startTime, endTime? }[]`
|
||||
- JSON sprite array: `{ url, startTime, endTime?, width, height, coords }[]`
|
||||
|
||||
In React, text-track mode needs `Player.Provider` because it reads track state from the player store. JSON modes (`thumbnails` prop) work without `Provider`.
|
||||
|
||||
The component picks the latest thumbnail whose `startTime` is less than or equal to the current `time`, then scales/clips sprite tiles to fit CSS min/max constraints while preserving aspect ratio.
|
||||
|
||||
## Styling
|
||||
|
||||
Use state data attributes for pure CSS styling:
|
||||
|
||||
```css
|
||||
media-thumbnail[data-hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
media-thumbnail[data-loading] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
media-thumbnail[data-error] {
|
||||
outline: 1px solid #ef4444;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
`Thumbnail` is decorative by default (`aria-hidden="true"`). It is intended for visual preview UX (for example, timeline hover previews) rather than primary accessible content.
|
||||
|
||||
## Examples
|
||||
|
||||
### Text Track (VTT)
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "App.tsx", code: textTrackReactTsx, lang: "tsx" },
|
||||
{ title: "App.css", code: textTrackReactCss, lang: "css" },
|
||||
]}
|
||||
>
|
||||
<TextTrackDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "index.html", code: textTrackHtml, lang: "html" },
|
||||
{ title: "index.css", code: textTrackHtmlCss, lang: "css" },
|
||||
{ title: "index.ts", code: textTrackHtmlTs, lang: "ts" },
|
||||
]}
|
||||
>
|
||||
<TextTrackDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
### JSON Array
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "App.tsx", code: jsonReactTsx, lang: "tsx" },
|
||||
]}
|
||||
>
|
||||
<JsonDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "index.html", code: jsonHtml, lang: "html" },
|
||||
{ title: "index.ts", code: jsonHtmlTs, lang: "ts" },
|
||||
]}
|
||||
>
|
||||
<JsonDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
### JSON Sprite Array
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "App.tsx", code: jsonSpriteReactTsx, lang: "tsx" },
|
||||
]}
|
||||
>
|
||||
<JsonSpriteDemoReact client:idle />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<StyleCase styles={["css"]}>
|
||||
<Demo
|
||||
files={[
|
||||
{ title: "index.html", code: jsonSpriteHtml, lang: "html" },
|
||||
{ title: "index.ts", code: jsonSpriteHtmlTs, lang: "ts" },
|
||||
]}
|
||||
>
|
||||
<JsonSpriteDemoHtml />
|
||||
</Demo>
|
||||
</StyleCase>
|
||||
</FrameworkCase>
|
||||
|
||||
<ComponentReference component="Thumbnail" />
|
||||
@@ -39,6 +39,7 @@ export const sidebar: Sidebar = [
|
||||
{ slug: 'reference/playback-rate-button' },
|
||||
{ slug: 'reference/poster' },
|
||||
{ slug: 'reference/seek-button' },
|
||||
{ slug: 'reference/thumbnail' },
|
||||
{ slug: 'reference/time' },
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user