feat(react): add fullscreen button component

Implement React fullscreen button with native React patterns:
- useFullscreenButtonState hook for media store integration
- useFullscreenButtonProps hook for element attribute management
- renderFullscreenButton function for button rendering
- Connected component via toConnectedComponent factory
- Explicit enter/exit methods matching HTML implementation
- Proper accessibility and data attributes

Provides consistent fullscreen functionality across HTML and React
platforms using shared state definition from media-store.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2025-09-12 08:06:08 -07:00
committed by Christian Pillsbury
co-authored by Claude
parent 63a340213f
commit 7d819dfcd9
2 changed files with 91 additions and 1 deletions
@@ -0,0 +1,89 @@
import {
shallowEqual,
useMediaSelector,
useMediaStore,
} from '@vjs-10/react-media-store';
import * as React from 'react';
import { toConnectedComponent } from '../utils/component-factory';
import { fullscreenButtonStateDefinition } from '@vjs-10/media-store';
export const useFullscreenButtonState = (_props: any) => {
const mediaStore = useMediaStore();
/** @TODO Fix type issues with hooks (CJP) */
const mediaState = useMediaSelector(
fullscreenButtonStateDefinition.stateTransform,
shallowEqual,
);
const methods = React.useMemo(
() => fullscreenButtonStateDefinition.createRequestMethods(mediaStore.dispatch),
[mediaStore],
);
return {
fullscreen: mediaState.fullscreen,
requestEnterFullscreen: methods.requestEnterFullscreen,
requestExitFullscreen: methods.requestExitFullscreen,
} as const;
};
export type useFullscreenButtonState = typeof useFullscreenButtonState;
export type FullscreenButtonState = ReturnType<useFullscreenButtonState>;
export const useFullscreenButtonProps = (
props: React.PropsWithChildren<{ [k: string]: any }>,
state: ReturnType<typeof useFullscreenButtonState>,
) => {
const baseProps: Record<string, any> = {
/** @TODO Need another state provider in core for i18n (CJP) */
/** aria attributes/props */
role: 'button',
['aria-label']: state.fullscreen ? 'exit fullscreen' : 'enter fullscreen',
/** tooltip */
['data-tooltip']: state.fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen',
/** external props spread last to allow for overriding */
...props,
};
// Handle boolean data attribute: present with empty string when true, absent when false
if (state.fullscreen) {
baseProps['data-fullscreen'] = '';
}
return baseProps;
};
export type useFullscreenButtonProps = typeof useFullscreenButtonProps;
type FullscreenButtonProps = ReturnType<useFullscreenButtonProps>;
export const renderFullscreenButton = (
props: FullscreenButtonProps,
state: FullscreenButtonState,
) => {
return (
<button
{...props}
onClick={() => {
/** @ts-ignore */
if (props.disabled) return;
if (state.fullscreen) {
state.requestExitFullscreen();
} else {
state.requestEnterFullscreen();
}
}}
>
{props.children}
</button>
);
};
export type renderFullscreenButton = typeof renderFullscreenButton;
export const FullscreenButton = toConnectedComponent(
useFullscreenButtonState,
useFullscreenButtonProps,
renderFullscreenButton,
'FullscreenButton',
);
export default FullscreenButton;
+2 -1
View File
@@ -7,4 +7,5 @@ export { Video, MediaElementVideo } from './components/Video';
// New hook-style components
export { PlayButton } from './components/PlayButton';
export { MuteButton } from './components/MuteButton';
export { VolumeRange } from './components/VolumeRange';
export { VolumeRange } from './components/VolumeRange';
export { FullscreenButton } from './components/FullscreenButton';