feat(core): add play button component (#383)

This commit is contained in:
rahim
2026-02-04 00:33:14 +11:00
committed by GitHub
parent 9fa172e8a4
commit 9cfab264d8
41 changed files with 2083 additions and 38 deletions
+63
View File
@@ -0,0 +1,63 @@
'use client';
import { createButton } from '@videojs/core/dom';
import type { ComponentPropsWithRef, Ref } from 'react';
import { useCallback } from 'react';
import { mergeProps } from '../../utils/merge-props';
export interface UseButtonParameters {
displayName: string;
onActivate: () => void;
isDisabled: () => boolean;
}
export interface UseButtonReturnValue {
getButtonProps: (externalProps?: ComponentPropsWithRef<'button'>) => ComponentPropsWithRef<'button'>;
buttonRef: Ref<HTMLElement>;
}
/**
* Hook for button behavior with keyboard and pointer interaction.
*
* @example
* ```tsx
* const { getButtonProps, buttonRef } = useButton({
* displayName: 'PlayButton',
* onActivate: () => togglePlayback(),
* isDisabled: () => disabled,
* });
*
* return useRender('button', componentProps, {
* state,
* ref: [forwardedRef, buttonRef],
* props: [elementProps, getButtonProps],
* });
* ```
*/
export function useButton(params: UseButtonParameters): UseButtonReturnValue {
const { displayName, onActivate, isDisabled } = params;
const buttonRef = useCallback(
(element: HTMLElement | null) => {
if (element && element.tagName !== 'BUTTON') {
console.warn(`${displayName} should render a <button> element for accessibility`);
}
},
[displayName]
);
const getButtonProps = useCallback(
(externalProps?: ComponentPropsWithRef<'button'>): ComponentPropsWithRef<'button'> => {
const buttonProps = createButton({ onActivate, isDisabled });
return mergeProps(buttonProps, externalProps);
},
[onActivate, isDisabled]
);
return { getButtonProps, buttonRef };
}
export namespace useButton {
export type Parameters = UseButtonParameters;
export type ReturnValue = UseButtonReturnValue;
}
@@ -0,0 +1,67 @@
'use client';
import { PlayButtonCore } from '@videojs/core';
import { logMissingFeature, selectPlayback } from '@videojs/core/dom';
import type { ForwardedRef } from 'react';
import { forwardRef, useState } from 'react';
import { usePlayer } from '../../player/context';
import type { UIComponentProps } from '../../utils/types';
import { renderElement } from '../../utils/use-render';
import { useButton } from '../hooks/use-button';
export interface PlayButtonProps extends UIComponentProps<'button', PlayButtonCore.State>, PlayButtonCore.Props {}
/**
* A button that toggles playback.
*
* @example
* ```tsx
* <PlayButton />
*
* <PlayButton
* render={(props, state) => (
* <button {...props}>
* {state.paused ? <PlayIcon /> : <PauseIcon />}
* </button>
* )}
* />
* ```
*/
export const PlayButton = forwardRef(function PlayButton(
componentProps: PlayButtonProps,
forwardedRef: ForwardedRef<HTMLButtonElement>
) {
const { render, className, style, label, disabled = false, ...elementProps } = componentProps;
const playback = usePlayer(selectPlayback);
const [core] = useState(() => new PlayButtonCore());
core.setProps({ label, disabled });
const { getButtonProps, buttonRef } = useButton({
displayName: 'PlayButton',
onActivate: () => core.toggle(playback!),
isDisabled: () => disabled || !playback,
});
if (!playback) {
logMissingFeature('PlayButton', 'playback');
return null;
}
return renderElement(
'button',
{ render, className, style },
{
state: core.getState(playback),
ref: [forwardedRef, buttonRef],
props: [core.getAttrs(playback), elementProps, getButtonProps()],
}
);
});
export namespace PlayButton {
export type Props = PlayButtonProps;
export type State = PlayButtonCore.State;
}