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;
+}