docs(site): add thumbnail reference page (#654)

This commit is contained in:
rahim
2026-02-28 00:40:44 -08:00
committed by GitHub
parent ed43d52f8f
commit e63d591d6e
20 changed files with 532 additions and 2 deletions
+22
View File
@@ -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);