mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
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
This commit is contained in:
committed by
Christian Pillsbury
parent
3fd26eb654
commit
ba8797cd21
@@ -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<CustomEvent, 'type'>) => void;
|
||||
onmediaunmuterequest: (event: Pick<CustomEvent, 'type'>) => void;
|
||||
};
|
||||
type ComponentType = ElementType<
|
||||
PropsWithChildren<
|
||||
Partial<DefaultMuteButtonState & DefaultMuteButtonEventCallbacks>
|
||||
>
|
||||
>;
|
||||
|
||||
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<useMuteButtonState>;
|
||||
|
||||
export const useMuteButtonProps = (
|
||||
props: React.PropsWithChildren<{ [k: string]: any }>,
|
||||
state: ReturnType<typeof useMuteButtonState>,
|
||||
) => {
|
||||
return {
|
||||
...props,
|
||||
['data-muted']: state.muted,
|
||||
['data-volume-level']: state.volumeLevel,
|
||||
};
|
||||
};
|
||||
|
||||
export type useMuteButtonProps = typeof useMuteButtonProps;
|
||||
type MuteButtonProps = ReturnType<useMuteButtonProps>;
|
||||
|
||||
export const renderMuteButton = (
|
||||
props: MuteButtonProps,
|
||||
state: MuteButtonState,
|
||||
) => {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
onClick={() => {
|
||||
if (state.muted) {
|
||||
state.requestUnmute();
|
||||
} else {
|
||||
state.requestMute();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -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<typeof ConnectedComponent>[0];
|
||||
type ConnectedComponentReturnType = ReturnType<typeof ConnectedComponent>;
|
||||
type DefaultedProps = 'component';
|
||||
type ConnectedComponentWithDefaults = (
|
||||
props: Omit<ConnectedComponentProps, DefaultedProps> &
|
||||
Partial<Pick<ConnectedComponentProps, DefaultedProps>>
|
||||
) => ConnectedComponentReturnType;
|
||||
|
||||
const Component: ConnectedComponentWithDefaults = ({
|
||||
component = BaseComponent,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<ConnectedComponent {...props} component={component}>
|
||||
{children}
|
||||
</ConnectedComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
@@ -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<typeof useMediaDispatch>;
|
||||
|
||||
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 (
|
||||
<Component
|
||||
{...props}
|
||||
onmediamuterequest={dispatch}
|
||||
onmediaunmuterequest={dispatch}
|
||||
mediaVolumeLevel={mediaVolumeLevel}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
export default MuteButton;
|
||||
@@ -1,47 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import type { ElementType, PropsWithChildren } from 'react';
|
||||
|
||||
type DefaultMuteButtonState = { mediaVolumeLevel: string };
|
||||
type DefaultMuteButtonEventCallbacks = {
|
||||
onmediamuterequest: (event: Pick<CustomEvent, 'type'>) => void;
|
||||
onmediaunmuterequest: (event: Pick<CustomEvent, 'type'>) => void;
|
||||
};
|
||||
type ComponentType = ElementType<
|
||||
PropsWithChildren<
|
||||
Partial<DefaultMuteButtonState & DefaultMuteButtonEventCallbacks>
|
||||
>
|
||||
>;
|
||||
|
||||
const MuteButton: ComponentType = ({
|
||||
mediaVolumeLevel,
|
||||
onmediamuterequest,
|
||||
onmediaunmuterequest,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const mediaMuted = mediaVolumeLevel === 'off';
|
||||
console.log('mediaVolumeLevel', mediaVolumeLevel);
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
data-muted={mediaMuted || undefined}
|
||||
data-volume-level={mediaVolumeLevel}
|
||||
onClick={() => {
|
||||
const type = mediaMuted ? 'mediaunmuterequest' : 'mediamuterequest';
|
||||
console.log('clicked', type);
|
||||
/** @TODO Discuss tradeoffs of requiring passing in object with type, esp. since callback already identifies action type (CJP) */
|
||||
mediaMuted
|
||||
? onmediaunmuterequest?.({
|
||||
type,
|
||||
})
|
||||
: onmediamuterequest?.({
|
||||
type,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default MuteButton;
|
||||
@@ -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 }> = ({
|
||||
<PlayIcon className={styles.PlayIcon}></PlayIcon>
|
||||
<PauseIcon className={styles.PauseIcon}></PauseIcon>
|
||||
</PlayButton>
|
||||
{/* @ts-ignore */}
|
||||
<MuteButton className={`${styles.Button} ${styles.MediaMuteButton}`}>
|
||||
<VolumeHighIcon
|
||||
className={`${styles.Icon} ${styles.VolumeHighIcon}`}
|
||||
|
||||
Reference in New Issue
Block a user