Skip to content

Commit

Permalink
Merge pull request #93 from sima-land/90-interactive-image
Browse files Browse the repository at this point in the history
#90  Сделать компонент интерактивного изображения
  • Loading branch information
krutoo authored Mar 17, 2022
2 parents 46dd98c + c569e60 commit 0d24161
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { action } from '@storybook/addon-actions';
import React from 'react';
import { InteractiveImage, Parts } from '..';
import imageSrc from './image.png';

export default {
title: 'common/InteractiveImage',
component: InteractiveImage,
parameters: {
layout: 'padded',
},
};

interface TitledPoint {
x: number;
y: number;
title: string;
}

export const Primary = () => {
const points: TitledPoint[] = [
{ x: 28, y: 30, title: 'Яйца' },
{ x: 63, y: 35, title: 'Кружка' },
{ x: 49, y: 70, title: 'Тарелка' },
{ x: 83, y: 69, title: 'Приборы' },
];

const style: React.CSSProperties = {
borderRadius: '8px',
width: '600px',
maxWidth: '100%',
marginBottom: '32px',
};

const ClickHandler = (value: string) => () => {
action('click')(value);
};

return (
<>
<h3>Простое изображение</h3>
<img src={imageSrc} style={style} />

<h3>Интерактивное изображение</h3>
<InteractiveImage style={style}>
<Parts.Image src={imageSrc} />

{points.map((point, index) => (
<Parts.Point key={index} role='button' {...point} onClick={ClickHandler(point.title)} />
))}
</InteractiveImage>
</>
);
};

Primary.storyName = 'Простой пример';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`InteractiveImage should handle props 1`] = `
<DocumentFragment>
<div
class="root"
>
<img
class="image"
data-testid="interactive-image:image"
src="https://www.images.com/123"
/>
<a
aria-label="Точка на изображении"
class="point"
data-testid="interactive-image:point"
role="button"
style="top: 2%; left: 1%;"
/>
<a
aria-label="Точка на изображении"
class="point"
data-testid="interactive-image:point"
role="button"
style="top: 3%; left: 2%;"
/>
<a
aria-label="Точка на изображении"
class="point"
data-testid="interactive-image:point"
role="button"
style="top: 4%; left: 3%;"
/>
<a
aria-label="Точка на изображении"
class="point"
data-testid="interactive-image:point"
role="button"
style="top: 5%; left: 4%;"
/>
</div>
</DocumentFragment>
`;
22 changes: 22 additions & 0 deletions src/common/components/intractive-image/__test__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { render } from '@testing-library/react';
import React from 'react';
import { InteractiveImage, Parts } from '..';

describe('InteractiveImage', () => {
it('should handle props', () => {
const { asFragment, queryAllByTestId } = render(
<InteractiveImage>
<Parts.Image src='https://www.images.com/123' />
<Parts.Point role='button' x={1} y={2} />
<Parts.Point role='button' x={2} y={3} />
<Parts.Point role='button' x={3} y={4} />
<Parts.Point role='button' x={4} y={5} />
</InteractiveImage>,
);

expect(asFragment()).toMatchSnapshot();

expect(queryAllByTestId('interactive-image:image')).toHaveLength(1);
expect(queryAllByTestId('interactive-image:point')).toHaveLength(4);
});
});
52 changes: 52 additions & 0 deletions src/common/components/intractive-image/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { Children, forwardRef, isValidElement } from 'react';
import classNames from 'classnames';
import styles from './interactive-image.module.scss';

export interface InteractiveImageProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
'data-testid'?: string;
}

export interface InteractiveImageImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
'data-testid'?: string;
}

export interface InteractiveImagePointProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
x: number;
y: number;
'data-testid'?: string;
}

export const InteractiveImage = ({
children,
'data-testid': testId,
className,
...rest
}: InteractiveImageProps) => (
<div className={classNames(styles.root, className)} {...rest} data-testid={testId}>
{Children.toArray(children).filter(
child => isValidElement(child) && (child.type === Image || child.type === Point),
)}
</div>
);

const Image = forwardRef<HTMLImageElement, InteractiveImageImageProps>(
({ className, 'data-testid': testId = 'interactive-image:image', ...rest }, ref) => (
<img ref={ref} className={classNames(styles.image, className)} data-testid={testId} {...rest} />
),
);

const Point = forwardRef<HTMLAnchorElement, InteractiveImagePointProps>(
({ x, y, className, style, 'data-testid': testId = 'interactive-image:point', ...rest }, ref) => (
<a
ref={ref}
aria-label='Точка на изображении'
data-testid={testId}
className={classNames(styles.point, className)}
style={{ ...style, top: `${y}%`, left: `${x}%` }}
{...rest}
/>
),
);

export const Parts = { Image, Point } as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@use 'node_modules/@sima-land/ui-nucleons/colors';
@use 'node_modules/@sima-land/ui-nucleons/breakpoints';

.root {
display: inline-block;
font-size: 0;
position: relative;
overflow: hidden;
background: colors.$basic-gray4;
}

.image {
display: block;
max-width: 100%;
max-height: 100%;
min-width: 100%;
min-height: 100%;
}

.point {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 64px;
height: 64px;
background: rgba(colors.$basic-gray87, 0.4);
border-radius: 50%;
transform: translate(-50%, -50%);
&:hover {
cursor: pointer;
background: rgba(colors.$basic-gray87, 0.64);
}
&::after {
content: '';
display: block;
width: 24px;
height: 24px;
background: #fff;
border-radius: 50%;
}
@include breakpoints.down('xs') {
width: 40px;
height: 40px;
&::after {
width: 16px;
height: 16px;
}
}
}
9 changes: 9 additions & 0 deletions src/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ declare module '*.svg' {
const content: React.FC<React.SVGProps<SVGSVGElement>>;
export default content;
}

// изображения
declare module '*.jpg' {
export default string;
}

declare module '*.png' {
export default string;
}

0 comments on commit 0d24161

Please sign in to comment.