Skip to content

Commit

Permalink
Merge pull request #174 from sima-land/170-modifier-media-modal
Browse files Browse the repository at this point in the history
Шаг 4 #170 Внести доработки в компоненты на основе изменений в гайдах
  • Loading branch information
krutoo authored Jun 22, 2023
2 parents 8819003 + cfc4316 commit 0de319f
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/common/components/media-modal/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const mixed: MediaData[] = [
type: 'video',
data: {
src: 'https://cdn2.static1-sima-land.com/flv/3404953.mp4',
thumbnail: 'https://loremflickr.com/240/240?random=16',
},
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { Fragment, useState } from 'react';
import { Modal } from '@sima-land/ui-nucleons/modal';
import {
MediaLayout,
MediaHeader,
MediaMain,
MediaAside,
MediaFooter,
HeaderLayout,
Preset,
MediaContent,
MediaView,
Thumbnails,
Thumbnail,
ProductBrief,
} from '..';
import { Tabs } from '@sima-land/ui-nucleons/tabs';
import { mixed } from '../__mocks__';
import { Layout } from '@sima-land/ui-nucleons/layout';
import { Button } from '@sima-land/ui-nucleons/button';
import { Stepper } from '@sima-land/ui-nucleons/stepper';
import { MediaData } from '../types';

export default {
title: 'common/MediaLayout',
parameters: {
layout: 'fullscreen',
},
};

export function TestImagesBroken() {
const [targetIndex, setTargetIndex] = useState(0);
const [broken, setBroken] = useState(false);

const processSrc = (src: string | undefined) => (broken ? 'http://non-existed-site.com/' : src);

const processMedia = (media: MediaData): MediaData => ({
...media,
data: {
...media.data,
...(media.type === '360' && { photos: media.data.photos.map(processSrc) }),
...(media.type === 'image' && { src: processSrc(media.data.src) }),
...(media.type === 'video' && { thumbnail: processSrc(media.data.thumbnail) }),
} as any,
});

return (
<Modal size='fullscreen' footerStub={false} withScrollDisable>
<Modal.Header
onClose={() => void 0}
buttons={{
endSecondary: {
text: broken ? 'Починить' : 'Сломать',
onClick: () => setBroken(a => !a),
},
}}
/>
<Modal.Body>
<MediaLayout>
<MediaHeader>
<HeaderLayout>
<HeaderLayout.Tabs>
<Tabs {...Preset.headerTabs()}>
<Tabs.Item name='Фото' />
<Tabs.Item name='Видео' selected />
<Tabs.Item name='360' />
<Tabs.Item name='Фото покупателей' />
</Tabs>
</HeaderLayout.Tabs>
</HeaderLayout>
</MediaHeader>

<MediaMain>
<MediaContent targetIndex={targetIndex} onChangeTargetIndex={setTargetIndex}>
{mixed.map(processMedia).map((item, index) => (
<MediaView key={index} media={item} />
))}
</MediaContent>
</MediaMain>

<MediaAside>
<Thumbnails targetIndex={targetIndex}>
{mixed.map(processMedia).map((item, index) => (
<Fragment key={index}>
{item.type === '360' && (
<Thumbnail
type='icon-360'
checked={targetIndex === index}
onClick={() => setTargetIndex(index)}
/>
)}
{item.type === 'video' && (
<Thumbnail
type='preview-video'
src={item.data.thumbnail}
checked={targetIndex === index}
onClick={() => setTargetIndex(index)}
/>
)}
{item.type === 'image' && (
<Thumbnail
type='preview-image'
src={item.data.src}
checked={targetIndex === index}
onClick={() => setTargetIndex(index)}
/>
)}
</Fragment>
))}
</Thumbnails>
</MediaAside>

<MediaFooter>
<Layout disabledOn={['xs', 's', 'm', 'l', 'xl']}>
<ProductBrief
href='https://sima-land.ru'
imageSrc={processSrc('https://loremflickr.com/240/320')}
title='Игровая приставка Sony PlayStation 5 Digital 3,5 ГГц модифицированная'
price={60235}
currency='₽'
footer={
<>
<Button size='s'>В корзину</Button>
<Stepper size='s' defaultValue={1} />
</>
}
/>
</Layout>
</MediaFooter>
</MediaLayout>
</Modal.Body>
</Modal>
);
}

TestImagesBroken.storyName = 'Тест: ошибка загрузки картинок';
2 changes: 1 addition & 1 deletion src/common/components/media-modal/parts/media-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ function MediaSlider({
}

/**
* Получив пропсы items и children вернет массив пропсов для MediaVuew.
* Получив пропсы items и children вернет массив пропсов для MediaView.
* @param items Проп items.
* @param children Проп children.
* @return Массив пропсов для MediaVuew.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@
height: 100%;
object-fit: contain;
}
.image svg {
display: block;
width: 100%;
height: 100%;
}
}
16 changes: 14 additions & 2 deletions src/common/components/media-modal/parts/media-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React, { useEffect, useRef, VideoHTMLAttributes } from 'react';
import { ImageOverlay } from '../../../../desktop/components/gallery-modal/components/image-overlay';
import { MediaData } from '../types';
import { useBreakpoint } from '@sima-land/ui-nucleons/hooks/breakpoint';
import styles from './media-view.module.scss';
import classNames from 'classnames';
import { AllRoundView } from './all-round-view';
import BrokenSVG from '../../../icons/image-broken.svg';
import styles from './media-view.module.scss';
import { useImageStub } from '../../../hooks';

export interface MediaViewProps {
media?: MediaData;
Expand All @@ -19,6 +21,15 @@ export interface MediaViewProps {
*/
export function MediaView({ media, loading, videoProps }: MediaViewProps) {
const desktop = useBreakpoint('xs+');
let imageSrc: string | undefined = undefined;

if (media?.type === 'image') {
imageSrc = media.data.src;
} else if (media?.type === 'video') {
imageSrc = media.data.thumbnail;
}

const { failed, handleError } = useImageStub(imageSrc);

const videoRef = useRef<HTMLVideoElement>(null);
const videoSrc = media?.type === 'video' ? media.data.src : null;
Expand All @@ -38,7 +49,8 @@ export function MediaView({ media, loading, videoProps }: MediaViewProps) {
<div className={classNames(styles.root)}>
{media?.type === 'image' && (
<ImageOverlay className={styles.image}>
<img src={media.data.src} alt={media.data.alt || ''} />
{failed && <BrokenSVG />}
{!failed && <img src={media.data.src} alt={media.data.alt || ''} onError={handleError} />}
</ImageOverlay>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
height: 100%;
object-fit: contain;
}
> svg {
display: block;
width: 100%;
height: 100%;
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/common/components/media-modal/parts/product-brief.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { HTMLAttributes, MouseEventHandler, ReactNode } from 'react';
import { ImageOverlay } from '../../../../desktop/components/gallery-modal/components/image-overlay';
import { Price } from '@sima-land/ui-nucleons/price';
import { useBreakpoint } from '@sima-land/ui-nucleons/hooks/breakpoint';
import { useImageStub } from '../../../hooks';
import classNames from 'classnames/bind';
import BrokenSVG from '../../../icons/image-broken.svg';
import styles from './product-brief.module.scss';
import { useBreakpoint } from '@sima-land/ui-nucleons/hooks/breakpoint';

export interface ProductBriefProps extends HTMLAttributes<HTMLDivElement> {
size?: 's' | 'l';
Expand Down Expand Up @@ -38,6 +40,7 @@ export function ProductBrief({
...restProps
}: ProductBriefProps) {
const desktop = useBreakpoint('xs+');
const { failed, handleError } = useImageStub(imageSrc);
const size = sizeProp ?? desktop ? 'l' : 's';

if (loading) {
Expand All @@ -48,7 +51,8 @@ export function ProductBrief({
<div className={cx('root', `size-${size}`, className)} {...restProps}>
<a className={styles.image} href={href} onClick={onLinkClick}>
<ImageOverlay className={styles.overlay}>
<img src={imageSrc} />
{failed && <BrokenSVG />}
{!failed && <img src={imageSrc} onError={handleError} />}
</ImageOverlay>
</a>

Expand Down
5 changes: 5 additions & 0 deletions src/common/components/media-modal/parts/thumbnail.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@
height: 100%;
object-fit: contain;
}
> svg {
display: block;
width: 100%;
height: 100%;
}
}
&::after {
content: '';
Expand Down
11 changes: 8 additions & 3 deletions src/common/components/media-modal/parts/thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React, { createContext, useContext } from 'react';
import { ImageOverlay } from '../../../../desktop/components/gallery-modal/components/image-overlay';
import PlaySVG from '@sima-land/ui-quarks/icons/24x24/Filled/Play';
import AllRoundSVG from '../../../../desktop/components/gallery-modal/icons/360.svg';
import { Price } from '@sima-land/ui-nucleons/price';
import { useImageStub } from '../../../hooks';
import classNames from 'classnames/bind';
import BrokenSVG from '../../../icons/image-broken.svg';
import styles from './thumbnail.module.scss';
import { Price } from '@sima-land/ui-nucleons/price';

export type ThumbnailSize = 's' | 'l';

Expand Down Expand Up @@ -45,6 +47,8 @@ export function Thumbnail({
currency,
}: ThumbnailProps) {
const { size: sizeFromContext } = useContext(ThumbnailContext);
const { failed, handleError } = useImageStub(src);

const size = sizeFromProps ?? sizeFromContext ?? 'l';

const rootClassName = cx(
Expand All @@ -70,11 +74,12 @@ export function Thumbnail({
<span className={cx('content')}>
{type?.startsWith('preview-') && (
<ImageOverlay className={cx('image')}>
<img src={src} alt={alt} />
{failed && <BrokenSVG />}
{!failed && <img src={src} alt={alt} onError={handleError} />}
</ImageOverlay>
)}

{type === 'preview-video' && <span className={cx('play')}></span>}
{!failed && type === 'preview-video' && <span className={cx('play')}></span>}

{type === 'icon-360' && <AllRoundSVG fill='currentColor' />}

Expand Down
29 changes: 5 additions & 24 deletions src/common/components/modifier/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import React, {
ReactEventHandler,
AnchorHTMLAttributes,
ImgHTMLAttributes,
useCallback,
useEffect,
useState,
} from 'react';
import React, { AnchorHTMLAttributes, ImgHTMLAttributes, useEffect, useState } from 'react';
import { Hint, useHintFloating, useHintOnHover } from '@sima-land/ui-nucleons/hint';
import ImageBrokenSVG from '@sima-land/ui-quarks/icons/40x40/Stroked/ImageBroken';
import classNames from 'classnames/bind';
import styles from './modifiers.module.scss';
import { useIsomorphicLayoutEffect } from '@sima-land/ui-nucleons/hooks';
import { useImageStub } from '../../hooks';

interface TextContent {
type: 'text';
Expand Down Expand Up @@ -162,23 +155,11 @@ export function Modifier({
* @return Элемент.
*/
function Image({ src, onError, ...rest }: ImgHTMLAttributes<HTMLImageElement>) {
const [fail, setFail] = useState(false);

useIsomorphicLayoutEffect(() => {
setFail(false);
}, [src]);

const handleError = useCallback<ReactEventHandler<HTMLImageElement>>(
event => {
setFail(true);
onError?.(event);
},
[onError],
);
const { failed, handleError } = useImageStub(src, onError);

return (
<span className={cx('image-wrapper', { fail })}>
{fail && <ImageBrokenSVG />}
<span className={cx('image-wrapper', { fail: failed })}>
{failed && <ImageBrokenSVG />}
<img className={cx('image')} {...rest} src={src} onError={handleError} />
</span>
);
Expand Down
26 changes: 26 additions & 0 deletions src/common/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useIsomorphicLayoutEffect } from '@sima-land/ui-nucleons/hooks';
import { ReactEventHandler, useCallback, useState } from 'react';

/**
* Хук состояния ошибки загрузки изображения.
* @param src Ссылка на картинку.
* @param onError Обработчик ошибки.
* @return Управление состоянием.
*/
export function useImageStub(src?: string, onError?: ReactEventHandler<HTMLImageElement>) {
const [failed, setFailed] = useState(false);

useIsomorphicLayoutEffect(() => {
setFailed(false);
}, [src]);

const handleError = useCallback<ReactEventHandler<HTMLImageElement>>(
event => {
setFailed(true);
onError?.(event);
},
[onError],
);

return { failed, setFailed, handleError };
}

0 comments on commit 0de319f

Please sign in to comment.