refactor(react,html): implement hook-style component architecture for MuteButton

React changes:
- Add muteButtonStateDef with keys, stateTransform, and requestMethods
- Refactor useMuteButtonState to use shallowEqual optimization
- Migrate from useMediaDispatch to useMediaStore pattern
- Create request methods factory for consistent state management
- Improve type safety and performance with optimized state selection

HTML changes:
- Rename MediaMuteButton class to MuteButtonBase for consistency
- Restructure event handling logic to match PlayButton pattern
- Add TODO comment for React vs. W.C. data-* attribute discrepancies
- Update component factory usage to use renamed base class

This completes the migration to hook-style architecture that can be
shared between HTML and React implementations, matching the PlayButton pattern.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2025-09-08 17:15:40 -07:00
committed by Christian Pillsbury
co-authored by Claude
parent 115f181548
commit 882019f458
2 changed files with 56 additions and 24 deletions
@@ -5,21 +5,21 @@ import {
} from '../utils/component-factory';
import { MediaChromeButton } from './media-chrome-button';
export class MediaMuteButton extends MediaChromeButton {
export class MuteButtonBase extends MediaChromeButton {
_state:
| {
muted: boolean;
volumeLevel: string;
requestUnmute: () => void;
requestMute: () => void;
requestUnmute: () => void;
}
| undefined;
handleEvent(event: Event) {
const { type } = event;
if (type === 'click') {
const state = this._state;
if (state) {
const state = this._state;
if (state) {
if (type === 'click') {
if (state.muted) {
state.requestUnmute();
} else {
@@ -39,6 +39,7 @@ export class MediaMuteButton extends MediaChromeButton {
_update(props: any, state: any) {
this._state = state;
/** @TODO Follow up with React vs. W.C. data-* attributes discrepancies (CJP) */
// Make generic
this.toggleAttribute('data-muted', props['data-muted']);
this.setAttribute('data-volume-level', props['data-volume-level']);
@@ -102,7 +103,7 @@ export const useMuteButtonProps: PropsHook<{
* Equivalent to React's MuteButton = toConnectedComponent(...)
*/
export const MuteButton = toConnectedHTMLComponent(
MediaMuteButton,
MuteButtonBase,
useMuteButtonState,
useMuteButtonProps,
'MuteButton',
@@ -1,27 +1,58 @@
import { useMediaDispatch, useMediaSelector } from '@vjs-10/react-media-store';
import {
shallowEqual,
// useMediaDispatch,
useMediaSelector,
useMediaStore,
} from '@vjs-10/react-media-store';
import * as React from 'react';
import { toConnectedComponent } from '../utils/component-factory';
export const useMuteButtonState = (_props: any) => {
/** @TODO Fix type issues with hooks (CJP) */
const volumeLevel = useMediaSelector(
(state: any) => state.volumeLevel,
) as string;
const muted = useMediaSelector((state: any) => state.muted) as boolean;
/**
* MuteButton state hook - equivalent to React's useMuteButtonState
* Handles media store state subscription and transformation
*/
export const muteButtonStateDef = {
keys: ['muted', 'volumeLevel'],
stateTransform: (rawState: any) => ({
muted: rawState.muted ?? false,
volumeLevel: rawState.volumeLevel ?? 'off',
}),
/** @TODO Consider "promoting" this up to state-mediator defs + media store (CJP) */
requestMethods: (mediaStore: ReturnType<typeof useMediaStore>) => {
return {
requestMute() {
const type = 'muterequest';
mediaStore.dispatch({ type });
},
requestUnmute() {
const type = 'unmuterequest';
mediaStore.dispatch({ type });
},
};
},
} as const;
const dispatch = useMediaDispatch();
const requestMute = React.useCallback(() => {
dispatch({ type: 'muterequest' });
}, [dispatch]);
const requestUnmute = React.useCallback(() => {
dispatch({ type: 'unmuterequest' });
}, [dispatch]);
export const useMuteButtonState = (_props: any) => {
const mediaStore = useMediaStore();
/** @TODO Fix type issues with hooks (CJP) */
const mediaState = useMediaSelector(
muteButtonStateDef.stateTransform,
shallowEqual,
);
const [methods, setMethods] = React.useState(
muteButtonStateDef.requestMethods(mediaStore),
);
React.useEffect(() => {
setMethods(muteButtonStateDef.requestMethods(mediaStore));
}, [mediaStore]);
return {
volumeLevel,
muted,
requestMute,
requestUnmute,
volumeLevel: mediaState.volumeLevel,
muted: mediaState.muted,
requestMute: methods.requestMute,
requestUnmute: methods.requestUnmute,
} as const;
};