diff --git a/src/common/components/intractive-image/__stories__/image.png b/src/common/components/intractive-image/__stories__/image.png new file mode 100644 index 00000000..4f1e6056 Binary files /dev/null and b/src/common/components/intractive-image/__stories__/image.png differ diff --git a/src/common/components/intractive-image/__stories__/index.stories.tsx b/src/common/components/intractive-image/__stories__/index.stories.tsx new file mode 100644 index 00000000..19d13ce1 --- /dev/null +++ b/src/common/components/intractive-image/__stories__/index.stories.tsx @@ -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 ( + <> +

Простое изображение

+ + +

Интерактивное изображение

+ + + + {points.map((point, index) => ( + + ))} + + + ); +}; + +Primary.storyName = 'Простой пример'; diff --git a/src/common/components/intractive-image/__test__/__snapshots__/index.test.tsx.snap b/src/common/components/intractive-image/__test__/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000..ae71ba97 --- /dev/null +++ b/src/common/components/intractive-image/__test__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InteractiveImage should handle props 1`] = ` + +
+ + + + + +
+
+`; diff --git a/src/common/components/intractive-image/__test__/index.test.tsx b/src/common/components/intractive-image/__test__/index.test.tsx new file mode 100644 index 00000000..76787e55 --- /dev/null +++ b/src/common/components/intractive-image/__test__/index.test.tsx @@ -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( + + + + + + + , + ); + + expect(asFragment()).toMatchSnapshot(); + + expect(queryAllByTestId('interactive-image:image')).toHaveLength(1); + expect(queryAllByTestId('interactive-image:point')).toHaveLength(4); + }); +}); diff --git a/src/common/components/intractive-image/index.tsx b/src/common/components/intractive-image/index.tsx new file mode 100644 index 00000000..4c994db7 --- /dev/null +++ b/src/common/components/intractive-image/index.tsx @@ -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 { + children?: React.ReactNode; + 'data-testid'?: string; +} + +export interface InteractiveImageImageProps extends React.ImgHTMLAttributes { + 'data-testid'?: string; +} + +export interface InteractiveImagePointProps extends React.AnchorHTMLAttributes { + x: number; + y: number; + 'data-testid'?: string; +} + +export const InteractiveImage = ({ + children, + 'data-testid': testId, + className, + ...rest +}: InteractiveImageProps) => ( +
+ {Children.toArray(children).filter( + child => isValidElement(child) && (child.type === Image || child.type === Point), + )} +
+); + +const Image = forwardRef( + ({ className, 'data-testid': testId = 'interactive-image:image', ...rest }, ref) => ( + + ), +); + +const Point = forwardRef( + ({ x, y, className, style, 'data-testid': testId = 'interactive-image:point', ...rest }, ref) => ( +
+ ), +); + +export const Parts = { Image, Point } as const; diff --git a/src/common/components/intractive-image/interactive-image.module.scss b/src/common/components/intractive-image/interactive-image.module.scss new file mode 100644 index 00000000..3da1b09b --- /dev/null +++ b/src/common/components/intractive-image/interactive-image.module.scss @@ -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; + } + } +} diff --git a/src/custom.d.ts b/src/custom.d.ts index 42b23797..7d0f39d5 100644 --- a/src/custom.d.ts +++ b/src/custom.d.ts @@ -15,3 +15,12 @@ declare module '*.svg' { const content: React.FC>; export default content; } + +// изображения +declare module '*.jpg' { + export default string; +} + +declare module '*.png' { + export default string; +}