Skip to content

Commit 6ccb56e

Browse files
authored
feat: adding new media component custom controls type (#532)
1 parent 71915e0 commit 6ccb56e

File tree

15 files changed

+370
-145
lines changed

15 files changed

+370
-145
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gravity-ui/page-constructor",
3-
"version": "1.26.3",
3+
"version": "1.27.0-alpha.0",
44
"description": "Gravity UI Page Constructor",
55
"license": "MIT",
66
"repository": {

src/blocks/Media/__stories__/Media.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ const VideoTemplate: Story<MediaBlockModel> = (args) => (
6666
title: data.video.videoWithPreview.title,
6767
media: data.video.videoWithPreview.media as MediaProps,
6868
},
69+
{
70+
...args,
71+
title: data.video.videoWithPreviewAndCustomControlsVariant1.title,
72+
media: data.video.videoWithPreviewAndCustomControlsVariant1.media as MediaProps,
73+
},
74+
{
75+
...args,
76+
title: data.video.videoWithPreviewAndCustomControlsVariant2.title,
77+
media: data.video.videoWithPreviewAndCustomControlsVariant2.media as MediaProps,
78+
},
6979
{
7080
...args,
7181
title: data.video.youtube.title,

src/blocks/Media/__stories__/data.json

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,76 @@
149149
}
150150
}
151151
}
152+
},
153+
"videoWithPreviewAndCustomControlsVariant1": {
154+
"title": "Video with preview image, play button and custom controls (Variant 1)",
155+
"media": {
156+
"light": {
157+
"previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png",
158+
"video": {
159+
"type": "player",
160+
"src": [
161+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.webm",
162+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.mp4",
163+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png"
164+
],
165+
"autoplay": false,
166+
"ariaLabel": "Video accessible name example",
167+
"controls": "custom",
168+
"customControlsOptions": {
169+
"type": "with-mute-button"
170+
}
171+
}
172+
},
173+
"dark": {
174+
"previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.png",
175+
"video": {
176+
"type": "player",
177+
"src": [
178+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.webm",
179+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.mp4",
180+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.png"
181+
],
182+
"autoplay": false,
183+
"ariaLabel": "Video accessible name example"
184+
}
185+
}
186+
}
187+
},
188+
"videoWithPreviewAndCustomControlsVariant2": {
189+
"title": "Video with preview image, play button and custom controls (Variant 2)",
190+
"media": {
191+
"light": {
192+
"previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png",
193+
"video": {
194+
"type": "player",
195+
"src": [
196+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.webm",
197+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.mp4",
198+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_white.png"
199+
],
200+
"autoplay": false,
201+
"ariaLabel": "Video accessible name example",
202+
"controls": "custom",
203+
"customControlsOptions": {
204+
"type": "with-play-pause-button"
205+
}
206+
}
207+
},
208+
"dark": {
209+
"previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.png",
210+
"video": {
211+
"type": "player",
212+
"src": [
213+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.webm",
214+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.mp4",
215+
"https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.png"
216+
],
217+
"autoplay": false,
218+
"ariaLabel": "Video accessible name example"
219+
}
220+
}
221+
}
152222
}
153223
},
154224
"dataLens": {

src/components/Media/Video/Video.tsx

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React, {useEffect, useMemo, useRef} from 'react';
22

33
import {MediaComponentVideoProps, MediaVideoType, PlayButtonProps} from '../../../models';
4+
import {block} from '../../../utils';
45
import ReactPlayerBlock from '../../ReactPlayer/ReactPlayer';
6+
57
import {getVideoTypesWithPriority} from './utils';
6-
import {block} from '../../../utils';
78

89
import './Video.scss';
910

@@ -21,7 +22,7 @@ interface InnerVideoProps {
2122
hasVideoFallback: boolean;
2223
}
2324

24-
type VideoAllProps = VideoAdditionProps & MediaComponentVideoProps & InnerVideoProps;
25+
export type VideoAllProps = VideoAdditionProps & MediaComponentVideoProps & InnerVideoProps;
2526

2627
const Video = (props: VideoAllProps) => {
2728
const {
@@ -47,15 +48,19 @@ const Video = (props: VideoAllProps) => {
4748
if (loop && typeof loop !== 'boolean') {
4849
const {start = 0, end} = loop;
4950

50-
ref.current.addEventListener('timeupdate', () => {
51-
const videoRef = ref.current;
52-
const endTime = end || (videoRef && videoRef.duration);
53-
54-
if (videoRef && videoRef.currentTime === endTime) {
55-
videoRef.currentTime = start;
56-
videoRef.play().catch(() => setHasVideoFallback(true));
57-
}
58-
});
51+
ref.current.addEventListener(
52+
'timeupdate',
53+
() => {
54+
const videoRef = ref.current;
55+
const endTime = end || (videoRef && videoRef.duration);
56+
57+
if (videoRef && videoRef.currentTime === endTime) {
58+
videoRef.currentTime = start;
59+
videoRef.play().catch(() => setHasVideoFallback(true));
60+
}
61+
},
62+
{passive: true},
63+
);
5964
}
6065

6166
if (playVideo) {
@@ -65,7 +70,17 @@ const Video = (props: VideoAllProps) => {
6570
}, [playVideo, video, setHasVideoFallback]);
6671

6772
const reactPlayerBlock = useMemo(() => {
68-
const {src, loop, controls, muted, autoplay = true, elapsedTime, playButton} = video;
73+
const {
74+
src,
75+
loop,
76+
controls,
77+
muted,
78+
autoplay = true,
79+
elapsedTime,
80+
playButton,
81+
ariaLabel,
82+
customControlsOptions,
83+
} = video;
6984

7085
return (
7186
<ReactPlayerBlock
@@ -82,6 +97,8 @@ const Video = (props: VideoAllProps) => {
8297
metrika={metrika}
8398
analyticsEvents={analyticsEvents}
8499
height={height}
100+
ariaLabel={ariaLabel}
101+
customControlsOptions={customControlsOptions}
85102
/>
86103
);
87104
}, [
@@ -100,14 +117,16 @@ const Video = (props: VideoAllProps) => {
100117
return video.src.length && !hasVideoFallback ? (
101118
<div className={b('wrap', videoClassName)} style={{height}}>
102119
<video
103-
disablePictureInPicture={true}
104-
playsInline={true}
120+
disablePictureInPicture
121+
playsInline
105122
// @ts-ignore
123+
// eslint-disable-next-line react/no-unknown-property
106124
pip="false"
107125
className={b('item')}
108126
ref={ref}
109127
preload="metadata"
110-
muted={true}
128+
muted
129+
aria-label={video.ariaLabel}
111130
>
112131
{getVideoTypesWithPriority(video.src).map(({src, type}, index) => (
113132
<source key={index} src={src} type={type} />

src/components/ReactPlayer/CustomBarControls.scss

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ $controlSize: 64px;
88
&__wrapper {
99
position: absolute;
1010
bottom: 0;
11+
12+
&_type {
13+
&_with-play-pause-button {
14+
width: 100%;
15+
padding: 20px;
16+
}
17+
}
1118
}
1219

1320
&__button {
@@ -27,6 +34,26 @@ $controlSize: 64px;
2734
}
2835
}
2936

37+
&__play-button {
38+
opacity: 0.9;
39+
background-color: transparent;
40+
border: 0;
41+
cursor: pointer;
42+
transition: opacity $animationDuration ease 3s;
43+
44+
&:hover,
45+
&:focus {
46+
opacity: 1;
47+
}
48+
&:focus {
49+
outline: 1px solid var(--g-color-line-light);
50+
border-radius: 4px;
51+
}
52+
&:focus:not(:focus-visible) {
53+
outline: none;
54+
}
55+
}
56+
3057
&__icon {
3158
margin: auto;
3259
}
Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import React, {useCallback} from 'react';
2-
import {ClassNameProps} from '../../models';
1+
import React, {useMemo} from 'react';
2+
import {Icon} from '@gravity-ui/uikit';
33

4+
import {VideoControlPause} from '../../icons/VideoControlPause';
5+
import {VideoControlPlay} from '../../icons/VideoControlPlay';
6+
import {ClassNameProps, CustomControlsType} from '../../models';
47
import {block} from '../../utils';
58
import CircleProgress from './CircleProgress';
9+
import i18n from './i18n';
610

711
import './CustomBarControls.scss';
812

913
const b = block('CustomBarControls');
14+
const PLAY_PAUSE_ICON_SIZE = 24;
1015

1116
interface MuteConfigProps {
1217
isMuted: boolean;
@@ -16,27 +21,65 @@ interface MuteConfigProps {
1621
export interface CustomBarControlsProps extends ClassNameProps {
1722
mute?: MuteConfigProps;
1823
elapsedTimePercent?: number;
24+
type?: CustomControlsType;
25+
isPaused?: boolean;
26+
onPlayClick?: () => void;
27+
isStarted?: boolean;
1928
}
2029

2130
const CustomBarControls = (props: CustomBarControlsProps) => {
22-
const {mute, elapsedTimePercent = 0, className} = props;
31+
const {
32+
mute,
33+
elapsedTimePercent = 0,
34+
className,
35+
type = CustomControlsType.WithMuteButton,
36+
isPaused,
37+
onPlayClick,
38+
isStarted = false,
39+
} = props;
2340

24-
const renderMute = useCallback((elapsedTime: number, muteConfig?: MuteConfigProps) => {
25-
if (!muteConfig) {
41+
const muteButton = useMemo(() => {
42+
if (!mute || type === CustomControlsType.WithPlayPauseButton) {
43+
// mute button is not provided for with-play-pause-button
2644
return null;
2745
}
2846

29-
const {isMuted, changeMute} = muteConfig;
47+
const {isMuted, changeMute} = mute;
3048

3149
return (
3250
<div className={b('button')} onClick={changeMute}>
3351
<div className={b('mute-button', {muted: isMuted})} />
34-
{!isMuted && <CircleProgress elapsedTime={elapsedTime} strokeWidth={5} />}
52+
{!isMuted && <CircleProgress elapsedTime={elapsedTimePercent} strokeWidth={5} />}
3553
</div>
3654
);
37-
}, []);
55+
}, [elapsedTimePercent, mute, type]);
3856

39-
return <div className={b('wrapper', className)}>{renderMute(elapsedTimePercent, mute)}</div>;
57+
const playPauseButton = useMemo(() => {
58+
if (type !== CustomControlsType.WithPlayPauseButton || !isStarted) {
59+
return null;
60+
}
61+
62+
return (
63+
<button
64+
onClick={onPlayClick}
65+
className={b('play-button')}
66+
aria-label={i18n(isPaused ? 'play' : 'pause')}
67+
>
68+
{isPaused ? (
69+
<Icon data={VideoControlPlay} size={PLAY_PAUSE_ICON_SIZE} />
70+
) : (
71+
<Icon data={VideoControlPause} size={PLAY_PAUSE_ICON_SIZE} />
72+
)}
73+
</button>
74+
);
75+
}, [isPaused, isStarted, onPlayClick, type]);
76+
77+
return (
78+
<div className={b('wrapper', {type}, className)}>
79+
{muteButton}
80+
{playPauseButton}
81+
</div>
82+
);
4083
};
4184

4285
export default CustomBarControls;

src/components/ReactPlayer/ReactPlayer.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ $block: '.#{$ns}ReactPlayer';
5959
transition: opacity $animationDuration ease 0s;
6060
}
6161
}
62+
&_started#{$block}_controls_custom#{$block}_hovered::before {
63+
opacity: 1;
64+
}
65+
66+
&_started#{$block}_controls_custom {
67+
&::before {
68+
position: absolute;
69+
width: 100%;
70+
height: 100%;
71+
content: '';
72+
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 65.36%, rgba(0, 0, 0, 0.35) 100%);
73+
opacity: 0;
74+
transition: opacity $animationDuration;
75+
}
76+
}
6277

6378
&__custom-bar-controls {
6479
opacity: 0;

0 commit comments

Comments
 (0)