From ba8797cd21339c8385c50bb68104127585988d50 Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Tue, 19 Aug 2025 10:42:54 -0500 Subject: [PATCH] refactor(react): consolidate MuteButton components into unified implementation - Merge three separate MuteButton variants into single composable component - Move from connected-with-defaults/MuteButton.tsx to components/MuteButton.tsx - Remove connected/ and ui/ MuteButton variants to reduce code duplication - Update MediaSkinDefault to use consolidated MuteButton component - Implement composable pattern with hooks for state, props, and rendering - Add temporary @ts-ignore for type compatibility during refactoring --- .../react/react/src/components/MuteButton.tsx | 103 ++++++++++++++++++ .../connected-with-defaults/MuteButton.tsx | 31 ------ .../src/components/connected/MuteButton.tsx | 54 --------- .../react/src/components/ui/MuteButton.tsx | 47 -------- .../react/src/skins/MediaSkinDefault.tsx | 9 +- 5 files changed, 110 insertions(+), 134 deletions(-) create mode 100644 packages/react/react/src/components/MuteButton.tsx delete mode 100644 packages/react/react/src/components/connected-with-defaults/MuteButton.tsx delete mode 100644 packages/react/react/src/components/connected/MuteButton.tsx delete mode 100644 packages/react/react/src/components/ui/MuteButton.tsx diff --git a/packages/react/react/src/components/MuteButton.tsx b/packages/react/react/src/components/MuteButton.tsx new file mode 100644 index 00000000..b7dbdec5 --- /dev/null +++ b/packages/react/react/src/components/MuteButton.tsx @@ -0,0 +1,103 @@ +import { useMediaDispatch, useMediaSelector } from '@vjs-10/react-media-store'; +import * as React from 'react'; +import type { ElementType, PropsWithChildren } from 'react'; + +type DefaultMuteButtonState = { mediaVolumeLevel: string }; +type DefaultMuteButtonEventCallbacks = { + onmediamuterequest: (event: Pick) => void; + onmediaunmuterequest: (event: Pick) => void; +}; +type ComponentType = ElementType< + PropsWithChildren< + Partial + > +>; + +export const useMuteButtonState = (_props: any) => { + /** @TODO Fix type issues with hooks (CJP) */ + const volumeLevel = useMediaSelector( + (state: any) => state.mediaVolumeLevel, + ) as string; + const muted = useMediaSelector((state: any) => state.mediaMuted) as boolean; + + const dispatch = useMediaDispatch(); + const requestMute = React.useCallback(() => { + dispatch({ type: 'mediamuterequest' }); + }, [dispatch]); + const requestUnmute = React.useCallback(() => { + dispatch({ type: 'mediaunmuterequest' }); + }, [dispatch]); + + return { + volumeLevel, + muted, + requestMute, + requestUnmute, + } as const; +}; + +export type useMuteButtonState = typeof useMuteButtonState; +export type MuteButtonState = ReturnType; + +export const useMuteButtonProps = ( + props: React.PropsWithChildren<{ [k: string]: any }>, + state: ReturnType, +) => { + return { + ...props, + ['data-muted']: state.muted, + ['data-volume-level']: state.volumeLevel, + }; +}; + +export type useMuteButtonProps = typeof useMuteButtonProps; +type MuteButtonProps = ReturnType; + +export const renderMuteButton = ( + props: MuteButtonProps, + state: MuteButtonState, +) => { + return ( + + ); +}; + +export type renderMuteButton = typeof renderMuteButton; + +export const toConnectedComponent = ( + useStateHook: useMuteButtonState, + usePropsHook: useMuteButtonProps, + defaultRender: renderMuteButton, + displayName: string, +) => { + const ConnectedComponent = ({ + render = defaultRender, + ...props + }: MuteButtonProps & { render: renderMuteButton }) => { + const connectedState = useStateHook(props); + const connectedProps = usePropsHook(props, connectedState); + return render(connectedProps, connectedState); + }; + + ConnectedComponent.displayName = displayName; + return ConnectedComponent; +}; + +export const MuteButton = toConnectedComponent( + useMuteButtonState, + useMuteButtonProps, + renderMuteButton, + 'MuteButton', +); +export default MuteButton; diff --git a/packages/react/react/src/components/connected-with-defaults/MuteButton.tsx b/packages/react/react/src/components/connected-with-defaults/MuteButton.tsx deleted file mode 100644 index c782727f..00000000 --- a/packages/react/react/src/components/connected-with-defaults/MuteButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// NOTE: This is just a thin wrapper around the "skeletal" connected component that also applies a default (but overridable) definition of the -// "BaseComponent" that defines the actual UI. -// NOTE: Definitions like this should be able to be autogenerated via codegen, defined via a factory function (HoC or higher order component), or both. - -import * as React from 'react'; -// import { MediaMuteButton as BaseComponent } from 'media-chrome/react'; -import BaseComponent from '../ui/MuteButton'; -import ConnectedComponent from '../connected/MuteButton'; - -// NOTE: Assuming FC or "functional component" here for type expediency (though may be fine for our use cases). -type ConnectedComponentProps = Parameters[0]; -type ConnectedComponentReturnType = ReturnType; -type DefaultedProps = 'component'; -type ConnectedComponentWithDefaults = ( - props: Omit & - Partial> -) => ConnectedComponentReturnType; - -const Component: ConnectedComponentWithDefaults = ({ - component = BaseComponent, - children, - ...props -}) => { - return ( - - {children} - - ); -}; - -export default Component; diff --git a/packages/react/react/src/components/connected/MuteButton.tsx b/packages/react/react/src/components/connected/MuteButton.tsx deleted file mode 100644 index ccd30e1b..00000000 --- a/packages/react/react/src/components/connected/MuteButton.tsx +++ /dev/null @@ -1,54 +0,0 @@ -// NOTE: This is an example of a "skeletal" connected component definition of a Mute Button. It "knows about" A Media (UI) Store and expects -// to be provided a non-connected component -import * as React from 'react'; -import { useMediaDispatch, useMediaSelector } from '@vjs-10/react-media-store'; -import type { CSSProperties, ElementType, PropsWithChildren } from 'react'; - -/** @TODO Export more types. Define more contracts (CJP) */ -type MediaCallbackType = ReturnType; - -type DefaultMuteButtonState = { mediaVolumeLevel: string }; -/** @TODO Support camel case deeply for callbacks. Define these as consts and types in core lib with clear relationships to event types (CJP) */ -type DefaultMuteButtonEventCallbacks = { - onmediamuterequest: MediaCallbackType; - onmediaunmuterequest: MediaCallbackType; -}; -type ComponentType = ElementType< - PropsWithChildren< - Partial< - DefaultMuteButtonState & - DefaultMuteButtonEventCallbacks & { - className: string | undefined; - style: CSSProperties | undefined; - } - > - > ->; - -const MuteButton = ({ - component, - children, - ...props -}: PropsWithChildren<{ - component: ComponentType; - className?: string | undefined; - style?: CSSProperties | undefined; -}>) => { - const Component = component; - const dispatch = useMediaDispatch(); - // @ts-ignore - State type issues - const mediaVolumeLevel = useMediaSelector((state) => state.mediaVolumeLevel); - console.log('mediaVolumeLevel', mediaVolumeLevel); - return ( - - {children} - - ); -}; - -export default MuteButton; diff --git a/packages/react/react/src/components/ui/MuteButton.tsx b/packages/react/react/src/components/ui/MuteButton.tsx deleted file mode 100644 index 67aa9e29..00000000 --- a/packages/react/react/src/components/ui/MuteButton.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react'; -import type { ElementType, PropsWithChildren } from 'react'; - -type DefaultMuteButtonState = { mediaVolumeLevel: string }; -type DefaultMuteButtonEventCallbacks = { - onmediamuterequest: (event: Pick) => void; - onmediaunmuterequest: (event: Pick) => void; -}; -type ComponentType = ElementType< - PropsWithChildren< - Partial - > ->; - -const MuteButton: ComponentType = ({ - mediaVolumeLevel, - onmediamuterequest, - onmediaunmuterequest, - children, - ...props -}) => { - const mediaMuted = mediaVolumeLevel === 'off'; - console.log('mediaVolumeLevel', mediaVolumeLevel); - return ( - - ); -}; - -export default MuteButton; diff --git a/packages/react/react/src/skins/MediaSkinDefault.tsx b/packages/react/react/src/skins/MediaSkinDefault.tsx index 082e7e11..b36e12c6 100644 --- a/packages/react/react/src/skins/MediaSkinDefault.tsx +++ b/packages/react/react/src/skins/MediaSkinDefault.tsx @@ -1,8 +1,12 @@ import * as React from 'react'; import { PauseIcon, PlayIcon } from '@vjs-10/react-icons'; import PlayButton from '../components/connected-with-defaults/PlayButton'; -import MuteButton from '../components/connected-with-defaults/MuteButton'; -import { VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@vjs-10/react-icons'; +import MuteButton from '../components/MuteButton'; +import { + VolumeHighIcon, + VolumeLowIcon, + VolumeOffIcon, +} from '@vjs-10/react-icons'; import styles from './styles.module.css'; export const MediaSkinDefault: React.FC<{ children: React.ReactNode }> = ({ @@ -18,6 +22,7 @@ export const MediaSkinDefault: React.FC<{ children: React.ReactNode }> = ({ + {/* @ts-ignore */}