Skip to content

Commit b9815e3

Browse files
authored
Add youtube to card with image (#126)
* feat: add FullScreenMedia component * feat: meta info component
1 parent 22da2fa commit b9815e3

File tree

22 files changed

+404
-20
lines changed

22 files changed

+404
-20
lines changed

src/components/FullscreenImage/FullscreenImage.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useState, useCallback, CSSProperties} from 'react';
1+
import React, {useState, CSSProperties} from 'react';
22
import {Icon, Modal} from '@gravity-ui/uikit';
33

44
import {block} from '../../utils';
@@ -14,15 +14,18 @@ export interface FullScreenImageProps extends ImageProps {
1414
}
1515

1616
const b = block('FullScreenImage');
17+
const FULL_SCREEN_ICON_SIZE = 18;
18+
const CLOSE_ICON_SIZE = 30;
1719

1820
const FullScreenImage = (props: FullScreenImageProps) => {
1921
const {imageClassName, modalImageClass, imageStyle} = props;
2022
const [isOpened, setIsOpened] = useState(false);
2123
const [isMouseEnter, setIsMouseEnter] = useState(false);
22-
const openModal = useCallback(() => setIsOpened(true), []);
23-
const closeModal = useCallback(() => setIsOpened(false), []);
24-
const showFullScreenIcon = useCallback(() => setIsMouseEnter(true), []);
25-
const hideFullScreenIcon = useCallback(() => setIsMouseEnter(false), []);
24+
25+
const openModal = () => setIsOpened(true);
26+
const closeModal = () => setIsOpened(false);
27+
const showFullScreenIcon = () => setIsMouseEnter(true);
28+
const hideFullScreenIcon = () => setIsMouseEnter(false);
2629

2730
return (
2831
<div className={b()}>
@@ -38,7 +41,12 @@ const FullScreenImage = (props: FullScreenImageProps) => {
3841
style={imageStyle}
3942
/>
4043
<div className={b('icon-wrapper', {visible: isMouseEnter})} onClick={openModal}>
41-
<Icon data={FullScreen} width={18} height={18} className={b('icon')} />
44+
<Icon
45+
data={FullScreen}
46+
width={FULL_SCREEN_ICON_SIZE}
47+
height={FULL_SCREEN_ICON_SIZE}
48+
className={b('icon')}
49+
/>
4250
</div>
4351
</div>
4452
{isOpened && (
@@ -47,8 +55,8 @@ const FullScreenImage = (props: FullScreenImageProps) => {
4755
<div className={b('icon-wrapper', {visible: true})} onClick={closeModal}>
4856
<Icon
4957
data={PreviewClose}
50-
width={30}
51-
height={30}
58+
width={CLOSE_ICON_SIZE}
59+
height={CLOSE_ICON_SIZE}
5260
className={b('icon', {hover: true})}
5361
/>
5462
</div>
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
@import '../../../styles/mixins';
2+
@import '../../../styles/variables';
3+
4+
$block: '.#{$ns}full-screen-media';
5+
$closeButtonSize: 36px;
6+
$modalMediaHeight: 70vh;
7+
8+
%modal-media {
9+
display: block;
10+
position: relative;
11+
width: 100%;
12+
max-width: $newContentWidth;
13+
max-height: $modalMediaHeight;
14+
}
15+
16+
#{$block} {
17+
&__media {
18+
cursor: pointer;
19+
20+
&-wrapper {
21+
cursor: pointer;
22+
position: relative;
23+
}
24+
}
25+
26+
&__modal-content {
27+
position: relative;
28+
}
29+
30+
&__modal-media {
31+
border-radius: $borderRadius;
32+
33+
&_type_image {
34+
@extend %modal-media;
35+
}
36+
37+
&_type_video video {
38+
@extend %modal-media;
39+
}
40+
41+
&_type_youtube {
42+
@extend %modal-media;
43+
44+
width: calc(#{$modalMediaHeight} * 16 / 9);
45+
height: $modalMediaHeight;
46+
}
47+
}
48+
49+
&__modal .yc-modal__content,
50+
&__modal-image {
51+
border-radius: $borderRadius;
52+
}
53+
54+
&__icon-wrapper {
55+
display: flex;
56+
align-items: center;
57+
justify-content: center;
58+
position: absolute;
59+
right: $indentXS;
60+
top: $indentXS;
61+
width: $closeButtonSize;
62+
height: $closeButtonSize;
63+
border-radius: 8px;
64+
background-color: var(--yc-color-base-simple-hover-solid);
65+
cursor: pointer;
66+
z-index: 10;
67+
}
68+
69+
&__modal-content,
70+
&__media-wrapper {
71+
#{$block}__icon-wrapper {
72+
opacity: 0;
73+
transition: opacity 0.3s;
74+
pointer-events: none;
75+
}
76+
77+
&:hover {
78+
#{$block}__icon-wrapper {
79+
opacity: 1;
80+
pointer-events: inherit;
81+
}
82+
}
83+
}
84+
85+
&__icon {
86+
color: var(--yc-color-text-hint);
87+
88+
&_hover:hover {
89+
color: var(--yc-color-text-secondary);
90+
}
91+
}
92+
93+
@media (max-width: map-get($gridBreakpoints, 'lg')) {
94+
&__media-wrapper {
95+
pointer-events: none;
96+
}
97+
98+
&__icon-wrapper {
99+
display: none;
100+
}
101+
102+
&__modal {
103+
display: none !important; /* stylelint-disable-line declaration-no-important */
104+
}
105+
}
106+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React, {useContext, useState} from 'react';
2+
import {Icon, Modal} from '@gravity-ui/uikit';
3+
4+
import {block} from '../../utils';
5+
import {PreviewClose, FullScreen} from '../../icons';
6+
import {MediaAllProps} from '../Media/Media';
7+
import {MobileContext} from '../../../src/context/mobileContext';
8+
9+
import './FullScreenMedia.scss';
10+
11+
export type ChildMediaRenderProps = Pick<
12+
MediaAllProps,
13+
'fullScreen' | 'imageClassName' | 'videoClassName' | 'youtubeClassName'
14+
>;
15+
16+
export interface FullScreenMediaProps {
17+
children: (props?: ChildMediaRenderProps) => JSX.Element;
18+
}
19+
20+
const b = block('full-screen-media');
21+
const FULL_SCREEN_ICON_SIZE = 18;
22+
const CLOSE_ICON_SIZE = 30;
23+
24+
const getMediaClass = (type: string) => b('modal-media', {type});
25+
26+
const FullScreenMedia = ({children}: FullScreenMediaProps) => {
27+
const [isOpened, setIsOpened] = useState(false);
28+
const isMobile = useContext(MobileContext);
29+
30+
const openModal = (e: React.MouseEvent) => {
31+
e.stopPropagation();
32+
setIsOpened(true);
33+
};
34+
const closeModal = () => setIsOpened(false);
35+
36+
if (isMobile) {
37+
return children();
38+
}
39+
40+
return (
41+
<div className={b()}>
42+
<div className={b('media-wrapper')} onClickCapture={openModal}>
43+
{children()}
44+
<div className={b('icon-wrapper')} onClickCapture={openModal}>
45+
<Icon
46+
data={FullScreen}
47+
width={FULL_SCREEN_ICON_SIZE}
48+
height={FULL_SCREEN_ICON_SIZE}
49+
className={b('icon')}
50+
/>
51+
</div>
52+
</div>
53+
{isOpened && (
54+
<Modal open={isOpened} onClose={closeModal} className={b('modal')}>
55+
<div className={b('modal-content')}>
56+
<div className={b('icon-wrapper', {visible: true})} onClick={closeModal}>
57+
<Icon
58+
data={PreviewClose}
59+
width={CLOSE_ICON_SIZE}
60+
height={CLOSE_ICON_SIZE}
61+
className={b('icon', {hover: true})}
62+
/>
63+
</div>
64+
{children({
65+
imageClassName: getMediaClass('image'),
66+
videoClassName: getMediaClass('video'),
67+
youtubeClassName: getMediaClass('youtube'),
68+
fullScreen: true,
69+
})}
70+
</div>
71+
</Modal>
72+
)}
73+
</div>
74+
);
75+
};
76+
77+
export default FullScreenMedia;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {Meta, Story} from '@storybook/react/types-6-0';
2+
import React from 'react';
3+
4+
import {COMPONENTS, MEDIA} from '../../../demo/constants';
5+
import FullScreenMedia from '../FullScreenMedia';
6+
import Media from '../../Media/Media';
7+
import {MediaProps} from '../../../models';
8+
9+
import data from './data.json';
10+
11+
export default {
12+
component: FullScreenMedia,
13+
title: `${COMPONENTS}/${MEDIA}/FullScreenMedia`,
14+
} as Meta;
15+
16+
const DefaultTemplate: Story<MediaProps> = (args) => (
17+
<div style={{maxWidth: '500px'}}>
18+
<FullScreenMedia>
19+
{(fullScreenMediaProps = {}) => <Media {...args} {...fullScreenMediaProps} />}
20+
</FullScreenMedia>
21+
</div>
22+
);
23+
24+
export const Default = DefaultTemplate.bind({});
25+
26+
Default.args = data.default.content as MediaProps;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"default": {
3+
"content": {
4+
"youtube": "https://youtu.be/0Qd3T6skprA",
5+
"previewImg": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/video_8-12_dark.png"
6+
}
7+
}
8+
}

src/components/Media/Media.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const Media = (props: MediaAllProps) => {
2727
previewImg,
2828
parallax = false,
2929
metrika,
30+
fullScreen,
3031
} = props;
3132
const {
3233
className,
@@ -85,6 +86,7 @@ export const Media = (props: MediaAllProps) => {
8586
attributes={{color: 'white', rel: '0'}}
8687
previewImg={previewImg}
8788
height={height}
89+
fullScreen={fullScreen}
8890
/>
8991
);
9092
}
@@ -111,6 +113,7 @@ export const Media = (props: MediaAllProps) => {
111113
playButton,
112114
customBarControlsClassName,
113115
youtubeClassName,
116+
fullScreen,
114117
]);
115118

116119
return (
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@import '../../../styles/variables';
2+
@import '../../../styles/mixins';
3+
4+
$block: '.#{$ns}meta-info';
5+
6+
#{$block} {
7+
@include text-size(body-2);
8+
9+
display: flex;
10+
align-items: center;
11+
font-weight: 500;
12+
13+
margin: $indentXS $indentXS 0 0;
14+
color: var(--pc-media-card-meta-info-color);
15+
16+
&__item:not(:first-child) {
17+
margin-left: $indentXS;
18+
}
19+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
3+
import {block} from '../../utils';
4+
5+
import './MetaInfo.scss';
6+
7+
const b = block('meta-info');
8+
9+
export interface MetaInfpoProps {
10+
items: string[];
11+
}
12+
13+
const MetaInfo = ({items}: MetaInfpoProps) => (
14+
<div className={b()}>
15+
{items.map((metaInfoItem) => (
16+
<div key={metaInfoItem} className={b('item')}>
17+
{metaInfoItem}
18+
</div>
19+
))}
20+
</div>
21+
);
22+
export default MetaInfo;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {Meta, Story} from '@storybook/react/types-6-0';
2+
import React from 'react';
3+
4+
import MetaInfo, {MetaInfpoProps} from '../MetaInfo';
5+
import {COMPONENTS} from '../../../demo/constants';
6+
7+
import data from './data.json';
8+
9+
export default {
10+
component: MetaInfo,
11+
title: `${COMPONENTS}/MetaInfo`,
12+
} as Meta;
13+
14+
const DefaultTemplate: Story<MetaInfpoProps> = (args) => <MetaInfo {...args} />;
15+
16+
export const Default = DefaultTemplate.bind({});
17+
18+
Default.args = data.default.content as MetaInfpoProps;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"default": {
3+
"content": {
4+
"items": ["one", "two", "three"]
5+
}
6+
}
7+
}

0 commit comments

Comments
 (0)