From 8b7ca7dd7ab4f9f8d0f8fe86dd3a8302d282bff4 Mon Sep 17 00:00:00 2001 From: Kyzyl-ool Kezhik Date: Fri, 29 Sep 2023 16:40:04 +0200 Subject: [PATCH] feat: media custom consol with play/pause button changed (#566) * feat: changed `with-play-pause-button` custom control buttons * feat: added `positioning`, `ratio` and `muteButtonShown` props for video * fix: some simplifications for ReactPlayer, bug fixes --- .../Media/__stories__/Media.stories.tsx | 12 +-- src/blocks/Media/__stories__/data.json | 68 +++++++++------ src/components/Media/Media.tsx | 3 + src/components/Media/Video/Video.tsx | 3 + .../ReactPlayer/CustomBarControls.scss | 86 ++++++++++++------- .../ReactPlayer/CustomBarControls.tsx | 57 +++++++----- src/components/ReactPlayer/ReactPlayer.scss | 23 +---- src/components/ReactPlayer/ReactPlayer.tsx | 51 +++++++---- src/icons/MuteSmall.tsx | 46 ---------- src/icons/UnmuteSmall.tsx | 57 ------------ src/icons/VideoControlPause.tsx | 48 ----------- src/icons/VideoControlPlay.tsx | 27 ------ src/models/constructor-items/common.ts | 9 ++ 13 files changed, 191 insertions(+), 299 deletions(-) delete mode 100644 src/icons/MuteSmall.tsx delete mode 100644 src/icons/UnmuteSmall.tsx delete mode 100644 src/icons/VideoControlPause.tsx delete mode 100644 src/icons/VideoControlPlay.tsx diff --git a/src/blocks/Media/__stories__/Media.stories.tsx b/src/blocks/Media/__stories__/Media.stories.tsx index 2c2802444..37642eadb 100644 --- a/src/blocks/Media/__stories__/Media.stories.tsx +++ b/src/blocks/Media/__stories__/Media.stories.tsx @@ -68,6 +68,12 @@ const VideoTemplate: StoryFn = (args) => ( title: data.video.staticWithAutoPlay.title, media: data.video.staticWithAutoPlay.media, }, + { + ...args, + title: data.video.videoWithAutoPlayCustomControlsWithPlayPauseButton.title, + media: data.video.videoWithAutoPlayCustomControlsWithPlayPauseButton + .media as MediaProps, + }, { ...args, title: data.video.staticWithControls.title, @@ -84,12 +90,6 @@ const VideoTemplate: StoryFn = (args) => ( media: data.video.videoWithPreviewAndCustomControlsWithMuteButton .media as MediaProps, }, - { - ...args, - title: data.video.videoWithPreviewAndCustomControlsWithPlayPauseButton.title, - media: data.video.videoWithPreviewAndCustomControlsWithPlayPauseButton - .media as MediaProps, - }, { ...args, title: data.video.youtube.title, diff --git a/src/blocks/Media/__stories__/data.json b/src/blocks/Media/__stories__/data.json index c61fafccd..09a99f5d0 100644 --- a/src/blocks/Media/__stories__/data.json +++ b/src/blocks/Media/__stories__/data.json @@ -114,8 +114,8 @@ } } }, - "staticWithControls": { - "title": "Video with controls and without autoplay", + "videoWithAutoPlayCustomControlsWithPlayPauseButton": { + "title": "Video with autoplay and custom controls with play/pause button", "media": { "light": { "video": { @@ -125,8 +125,19 @@ "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.mp4", "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png" ], - "autoplay": false, - "ariaLabel": "Video accessible name example" + "autoplay": true, + "ariaLabel": "Video accessible name example", + "controls": "custom", + "customControlsOptions": { + "type": "with-play-pause-button", + "muteButtonShown": false, + "backgroundShadowHidden": true, + "positioning": "left" + }, + "muted": true, + "loop": { + "start": 0 + } } }, "dark": { @@ -137,23 +148,27 @@ "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.mp4", "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.png" ], - "autoplay": false, - "ariaLabel": "Video accessible name example" + "autoplay": true, + "ariaLabel": "Video accessible name example", + "controls": "custom", + "customControlsOptions": { + "type": "with-play-pause-button", + "muteButtonShown": false, + "backgroundShadowHidden": true, + "positioning": "left" + }, + "muted": true, + "loop": { + "start": 0 + } } } } }, - "youtube": { - "title": "Video from video-hosting", - "media": { - "youtube": "https://youtu.be/0Qd3T6skprA" - } - }, - "videoWithPreview": { - "title": "Video with preview image and play button", + "staticWithControls": { + "title": "Video with controls and without autoplay", "media": { "light": { - "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png", "video": { "type": "player", "src": [ @@ -166,7 +181,6 @@ } }, "dark": { - "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.png", "video": { "type": "player", "src": [ @@ -180,8 +194,14 @@ } } }, - "videoWithPreviewAndCustomControlsWithMuteButton": { - "title": "Video with preview image, play button and custom controls with mute button", + "youtube": { + "title": "Video from video-hosting", + "media": { + "youtube": "https://youtu.be/0Qd3T6skprA" + } + }, + "videoWithPreview": { + "title": "Video with preview image and play button", "media": { "light": { "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png", @@ -193,11 +213,7 @@ "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png" ], "autoplay": false, - "ariaLabel": "Video accessible name example", - "controls": "custom", - "customControlsOptions": { - "type": "with-mute-button" - } + "ariaLabel": "Video accessible name example" } }, "dark": { @@ -215,8 +231,8 @@ } } }, - "videoWithPreviewAndCustomControlsWithPlayPauseButton": { - "title": "Video with preview image, play button and custom controls with play/pause and mute button", + "videoWithPreviewAndCustomControlsWithMuteButton": { + "title": "Video with preview image, play button and custom controls with mute button", "media": { "light": { "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png", @@ -231,7 +247,7 @@ "ariaLabel": "Video accessible name example", "controls": "custom", "customControlsOptions": { - "type": "with-play-pause-button" + "type": "with-mute-button" } } }, diff --git a/src/components/Media/Media.tsx b/src/components/Media/Media.tsx index d065a77d6..ce71fe055 100644 --- a/src/components/Media/Media.tsx +++ b/src/components/Media/Media.tsx @@ -40,6 +40,7 @@ export const Media = (props: MediaAllProps) => { playButton, customBarControlsClassName, qa, + ratio, } = props; const [hasVideoFallback, setHasVideoFallback] = useState(false); @@ -80,6 +81,7 @@ export const Media = (props: MediaAllProps) => { customBarControlsClassName, hasVideoFallback, setHasVideoFallback, + ratio, }; if (fullscreen) { @@ -128,6 +130,7 @@ export const Media = (props: MediaAllProps) => { previewImg, playButton, customBarControlsClassName, + ratio, youtubeClassName, ]); diff --git a/src/components/Media/Video/Video.tsx b/src/components/Media/Video/Video.tsx index 8d82e00d6..1f4ab5b81 100644 --- a/src/components/Media/Video/Video.tsx +++ b/src/components/Media/Video/Video.tsx @@ -41,6 +41,7 @@ const Video = (props: VideoAllProps) => { setHasVideoFallback, hasVideoFallback, qa, + ratio, } = props; const qaAttributes = getQaAttrubutes(qa, 'source'); @@ -105,6 +106,7 @@ const Video = (props: VideoAllProps) => { height={height} ariaLabel={ariaLabel} customControlsOptions={customControlsOptions} + ratio={ratio} /> ); }, [ @@ -117,6 +119,7 @@ const Video = (props: VideoAllProps) => { customBarControlsClassName, metrika, analyticsEvents, + ratio, ]); const defaultVideoBlock = useMemo(() => { diff --git a/src/components/ReactPlayer/CustomBarControls.scss b/src/components/ReactPlayer/CustomBarControls.scss index 368a67676..5393c3f4b 100644 --- a/src/components/ReactPlayer/CustomBarControls.scss +++ b/src/components/ReactPlayer/CustomBarControls.scss @@ -3,18 +3,44 @@ $block: '.#{$ns}CustomBarControls'; $controlSize: 64px; +// custom controls sizes +$withPlayPauseControlSize: 42px; +$withPlayPauseControlIconSize: 16px; +$withMuteControlHeight: 22px; +$withMuteControlWidth: 32px; +// --- #{$block} { &__wrapper { position: absolute; bottom: 0; + opacity: 0; + transition: opacity $animationDuration ease 3s; + + &_shown { + opacity: 1; + transition: opacity 0s ease 0s; + } &_type { &_with-play-pause-button { - width: 100%; - padding: $indentS; + gap: $indentXXXS; + padding: $indentXXXS; + } + } + + &_positioning { + &_left, + &_right, + &_center { display: flex; - gap: $indentS; + width: 100%; + } + &_right { + flex-direction: row-reverse; + } + &_center { + justify-content: center; } } } @@ -25,25 +51,6 @@ $controlSize: 64px; cursor: pointer; &_type { - &_with-play-pause-button { - opacity: 0.9; - background-color: transparent; - transition: opacity $animationDuration ease 3s; - - &:hover, - &:focus { - opacity: 1; - } - &:focus { - outline: 1px solid var(--g-color-line-light); - outline-offset: 2px; - border-radius: 4px; - } - &:focus:not(:focus-visible) { - outline: none; - } - } - &_with-mute-button { border-radius: 50%; display: flex; @@ -55,7 +62,7 @@ $controlSize: 64px; height: $controlSize; background: var(--g-color-base-background); transition: background-color $animationDuration; - margin: 12px; + margin: $indentXXS; &:hover, &:focus { @@ -69,23 +76,44 @@ $controlSize: 64px; outline: none; } } + + &_with-play-pause-button { + width: $withPlayPauseControlSize; + height: $withPlayPauseControlSize; + border-radius: 50%; + background: var(--g-color-base-background); + @include shadow(); + + &:focus { + outline: 2px solid var(--g-color-line-misc); + } + &:focus:not(:focus-visible) { + outline: none; + } + } } } &__play-icon { - height: 24px; - width: 24px; + &_type { + &_with-play-pause-button { + height: $withPlayPauseControlIconSize; + width: $withPlayPauseControlIconSize; + color: var(--g-color-base-neutral-heavy); + } + } } &__mute-icon { &_type { &_with-mute-button { - height: 22px; - width: 32px; + height: $withMuteControlHeight; + width: $withMuteControlWidth; } &_with-play-pause-button { - height: 24px; - width: 24px; + height: $withPlayPauseControlIconSize; + width: $withPlayPauseControlIconSize; + color: var(--g-color-base-neutral-heavy); } } } diff --git a/src/components/ReactPlayer/CustomBarControls.tsx b/src/components/ReactPlayer/CustomBarControls.tsx index b48a4e004..1e2f51c03 100644 --- a/src/components/ReactPlayer/CustomBarControls.tsx +++ b/src/components/ReactPlayer/CustomBarControls.tsx @@ -1,14 +1,11 @@ import React, {useMemo} from 'react'; +import {Pause, Play, VolumeLow, VolumeXmark} from '@gravity-ui/icons'; import {Icon} from '@gravity-ui/uikit'; import {Mute} from '../../icons/Mute'; -import {MuteSmall} from '../../icons/MuteSmall'; import {Unmute} from '../../icons/Unmute'; -import {UnmuteSmall} from '../../icons/UnmuteSmall'; -import {VideoControlPause} from '../../icons/VideoControlPause'; -import {VideoControlPlay} from '../../icons/VideoControlPlay'; -import {ClassNameProps, CustomControlsType} from '../../models'; +import {ClassNameProps, CustomControlsOptions, CustomControlsType} from '../../models'; import {block} from '../../utils'; import CircleProgress from './CircleProgress'; @@ -18,17 +15,35 @@ import './CustomBarControls.scss'; const b = block('CustomBarControls'); +const playIconsMap = { + [CustomControlsType.WithMuteButton]: null, + [CustomControlsType.WithPlayPauseButton]: Play, +}; +const pauseIconsMap = { + [CustomControlsType.WithMuteButton]: null, + [CustomControlsType.WithPlayPauseButton]: Pause, +}; +const muteIconsMap = { + [CustomControlsType.WithMuteButton]: Mute, + [CustomControlsType.WithPlayPauseButton]: VolumeLow, +}; +const unmuteIconsMap = { + [CustomControlsType.WithMuteButton]: Unmute, + [CustomControlsType.WithPlayPauseButton]: VolumeXmark, +}; + interface MuteConfigProps { isMuted: boolean; changeMute: (event: React.MouseEvent) => void; } -export interface CustomBarControlsProps extends ClassNameProps { +export interface CustomBarControlsProps extends ClassNameProps, CustomControlsOptions { mute?: MuteConfigProps; elapsedTimePercent?: number; type?: CustomControlsType; isPaused?: boolean; onPlayClick?: () => void; + shown?: boolean; } const CustomBarControls = (props: CustomBarControlsProps) => { @@ -39,17 +54,18 @@ const CustomBarControls = (props: CustomBarControlsProps) => { type = CustomControlsType.WithMuteButton, isPaused, onPlayClick, + muteButtonShown: isMuteButtonShown = true, + shown, + positioning, } = props; - const muteIcon = useMemo(() => { - return type === CustomControlsType.WithMuteButton ? Mute : MuteSmall; - }, [type]); - const unmuteIcon = useMemo(() => { - return type === CustomControlsType.WithMuteButton ? Unmute : UnmuteSmall; - }, [type]); + const muteIcon = muteIconsMap[type]; + const unmuteIcon = unmuteIconsMap[type]; + const playIcon = playIconsMap[type]; + const pauseIcon = pauseIconsMap[type]; const muteButton = useMemo(() => { - if (!mute) { + if (!mute || !isMuteButtonShown) { return null; } @@ -67,10 +83,12 @@ const CustomBarControls = (props: CustomBarControlsProps) => { )} ); - }, [elapsedTimePercent, mute, muteIcon, type, unmuteIcon]); + }, [elapsedTimePercent, isMuteButtonShown, mute, muteIcon, type, unmuteIcon]); const playPauseButton = useMemo(() => { - if (type !== CustomControlsType.WithPlayPauseButton) { + const icon = isPaused ? playIcon : pauseIcon; + + if (type === CustomControlsType.WithMuteButton || !icon) { return null; } @@ -80,16 +98,13 @@ const CustomBarControls = (props: CustomBarControlsProps) => { className={b('button', {type})} aria-label={i18n(isPaused ? 'play' : 'pause')} > - + ); - }, [isPaused, onPlayClick, type]); + }, [isPaused, onPlayClick, type, playIcon, pauseIcon]); return ( -
+
{playPauseButton} {muteButton}
diff --git a/src/components/ReactPlayer/ReactPlayer.scss b/src/components/ReactPlayer/ReactPlayer.scss index 2b1fe3007..5dae4b38a 100644 --- a/src/components/ReactPlayer/ReactPlayer.scss +++ b/src/components/ReactPlayer/ReactPlayer.scss @@ -53,18 +53,9 @@ $block: '.#{$ns}ReactPlayer'; margin-left: 6px; } - &:hover { - & #{$block}__custom-bar-controls { - opacity: 1; - transition: opacity $animationDuration ease 0s; - } - } - &_started#{$block}_controls_custom#{$block}_hovered::before { - opacity: 1; - } - - &_started#{$block}_controls_custom { + &_controls_custom { &::before { + display: none; position: absolute; width: 100%; height: 100%; @@ -75,16 +66,6 @@ $block: '.#{$ns}ReactPlayer'; } } - &__custom-bar-controls { - opacity: 0; - transition: opacity $animationDuration ease 3s; - } - - &__custom-bar-controls_muted { - opacity: 1; - transition: opacity 0s ease 0s; - } - @media only screen and (max-width: map-get($gridBreakpoints, 'sm')) { &__button_text { font-size: 20px; diff --git a/src/components/ReactPlayer/ReactPlayer.tsx b/src/components/ReactPlayer/ReactPlayer.tsx index eb9d9bb47..5014b8abc 100644 --- a/src/components/ReactPlayer/ReactPlayer.tsx +++ b/src/components/ReactPlayer/ReactPlayer.tsx @@ -21,6 +21,7 @@ import {PlayVideo} from '../../icons'; import { AnalyticsEvent, ClassNameProps, + CustomControlsButtonPositioning, CustomControlsType, DefaultEventNames, MediaVideoControlsType, @@ -53,6 +54,7 @@ export interface ReactPlayerBlockProps showPreview?: boolean; onClickPreview?: () => void; height?: number; + ratio?: number; children?: React.ReactNode; } @@ -82,6 +84,7 @@ export const ReactPlayerBlock = React.forwardRef { - if (playerRef) { + if (playerRef && !started) { setIsPlaying(autoPlay); } - }, [autoPlay, playerRef]); + }, [autoPlay, playerRef, started]); useEffect(() => setMuted(mute), [mute]); @@ -172,7 +178,7 @@ export const ReactPlayerBlock = React.forwardRef { window.removeEventListener('resize', updateSize); }; - }, []); + }, [ratio]); const playEvents = useMemo( () => eventsArray?.filter((e: AnalyticsEvent) => e.type === PredefinedEventTypes.Play), @@ -220,7 +226,7 @@ export const ReactPlayerBlock = React.forwardRef - {controls === MediaVideoControlsType.Custom && started && ( + {controls === MediaVideoControlsType.Custom && ( { @@ -394,6 +406,9 @@ export const ReactPlayerBlock = React.forwardRef )} @@ -403,8 +418,8 @@ export const ReactPlayerBlock = React.forwardRef> = (props) => { - return ( - - - - - - - - - ); -}; diff --git a/src/icons/UnmuteSmall.tsx b/src/icons/UnmuteSmall.tsx deleted file mode 100644 index 2a224558e..000000000 --- a/src/icons/UnmuteSmall.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; - -import {a11yHiddenSvgProps} from '../utils/svg'; - -export const UnmuteSmall: React.FC> = (props) => { - return ( - - - - - - - - - - - ); -}; diff --git a/src/icons/VideoControlPause.tsx b/src/icons/VideoControlPause.tsx deleted file mode 100644 index 65418bfb0..000000000 --- a/src/icons/VideoControlPause.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; - -import {a11yHiddenSvgProps} from '../utils/svg'; - -export const VideoControlPause: React.FC> = (props) => { - return ( - - - - - - - - - - - ); -}; diff --git a/src/icons/VideoControlPlay.tsx b/src/icons/VideoControlPlay.tsx deleted file mode 100644 index 755989fa4..000000000 --- a/src/icons/VideoControlPlay.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -import {a11yHiddenSvgProps} from '../utils/svg'; - -export const VideoControlPlay: React.FC> = (props) => { - return ( - - - - - ); -}; diff --git a/src/models/constructor-items/common.ts b/src/models/constructor-items/common.ts index 608db3ba3..e75d39592 100644 --- a/src/models/constructor-items/common.ts +++ b/src/models/constructor-items/common.ts @@ -39,6 +39,12 @@ export enum CustomControlsType { WithPlayPauseButton = 'with-play-pause-button', } +export enum CustomControlsButtonPositioning { + Left = 'left', + Right = 'right', + Center = 'center', +} + export enum MediaVideoType { Default = 'default', Player = 'player', @@ -212,6 +218,8 @@ export interface ButtonImageProps { export interface CustomControlsOptions { type?: CustomControlsType; + muteButtonShown?: boolean; + positioning?: CustomControlsButtonPositioning; } export interface PlayButtonProps extends ClassNameProps { @@ -225,6 +233,7 @@ export type ThemedMediaVideoProps = ThemeSupporting; export interface MediaComponentVideoProps extends AnalyticsEventsBase { video: MediaVideoProps; height?: number; + ratio?: number; metrika?: MetrikaVideo; previewImg?: string; }