@sima-land/moleculas 16.0.0-alpha.2 → 16.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/components/media-modal/__mocks__/index.d.ts +3 -0
- package/common/components/media-modal/__mocks__/index.js +225 -0
- package/common/components/media-modal/index.d.ts +11 -0
- package/common/components/media-modal/index.js +26 -0
- package/common/components/media-modal/layout.d.ts +36 -0
- package/common/components/media-modal/layout.js +106 -0
- package/common/components/media-modal/layout.module.scss +80 -0
- package/common/components/media-modal/parts/all-round-view.d.ts +8 -0
- package/common/components/media-modal/parts/all-round-view.js +78 -0
- package/common/components/media-modal/parts/all-round-view.module.scss +30 -0
- package/common/components/media-modal/parts/index.d.ts +36 -0
- package/common/components/media-modal/parts/index.js +60 -0
- package/common/components/media-modal/parts/media-content.d.ts +19 -0
- package/common/components/media-modal/parts/media-content.js +103 -0
- package/common/components/media-modal/parts/media-content.module.scss +42 -0
- package/common/components/media-modal/parts/media-view.d.ts +13 -0
- package/common/components/media-modal/parts/media-view.js +30 -0
- package/common/components/media-modal/parts/media-view.module.scss +30 -0
- package/common/components/media-modal/parts/parts.module.scss +21 -0
- package/common/components/media-modal/parts/product-brief.d.ts +18 -0
- package/common/components/media-modal/parts/product-brief.js +57 -0
- package/common/components/media-modal/parts/product-brief.module.scss +147 -0
- package/common/components/media-modal/parts/review.d.ts +15 -0
- package/common/components/media-modal/parts/review.js +44 -0
- package/common/components/media-modal/parts/review.module.scss +86 -0
- package/common/components/media-modal/parts/scroll-section.d.ts +11 -0
- package/common/components/media-modal/parts/scroll-section.js +22 -0
- package/common/components/media-modal/parts/scroll-section.module.scss +32 -0
- package/common/components/media-modal/parts/thumbnail.d.ts +22 -0
- package/common/components/media-modal/parts/thumbnail.js +65 -0
- package/common/components/media-modal/parts/thumbnail.module.scss +134 -0
- package/common/components/media-modal/parts/thumbnails.d.ts +13 -0
- package/common/components/media-modal/parts/thumbnails.js +94 -0
- package/common/components/media-modal/parts/thumbnails.module.scss +16 -0
- package/common/components/media-modal/types.d.ts +17 -0
- package/common/components/media-modal/types.js +2 -0
- package/common/components/media-modal/utils.d.ts +47 -0
- package/common/components/media-modal/utils.js +122 -0
- package/common/components/modifier/index.d.ts +3 -1
- package/common/components/modifier/index.js +3 -2
- package/common/components/modifier/modifiers.module.scss +31 -0
- package/common/components/product-info/parts.js +6 -2
- package/common/components/product-info/product-info.module.scss +9 -0
- package/common/icons/image-broken.svg +7 -0
- package/common/icons/touch-x.svg +6 -0
- package/desktop/components/gallery-modal/components/all-round-view.d.ts +5 -1
- package/desktop/components/gallery-modal/components/all-round-view.js +9 -6
- package/desktop/components/gallery-modal/components/image-overlay.js +1 -1
- package/desktop/components/gallery-modal/components/image-overlay.module.scss +1 -0
- package/package.json +6 -5
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HeaderLayout = exports.Preset = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const layout_1 = require("@sima-land/ui-nucleons/layout");
|
|
9
|
+
const define_slots_1 = require("@sima-land/ui-nucleons/helpers/define-slots");
|
|
10
|
+
const breakpoint_1 = require("@sima-land/ui-nucleons/hooks/breakpoint");
|
|
11
|
+
const utils_1 = require("../utils");
|
|
12
|
+
const scroll_section_1 = require("./scroll-section");
|
|
13
|
+
const left_1 = __importDefault(require("@sima-land/ui-quarks/icons/16x16/Stroked/Arrows/left"));
|
|
14
|
+
const parts_module_scss_1 = __importDefault(require("./parts.module.scss"));
|
|
15
|
+
exports.Preset = {
|
|
16
|
+
headerTabs: (userProps) => (Object.assign({ view: 'clean-underline' }, userProps)),
|
|
17
|
+
headerButton: (userProps) => (Object.assign({ size: 's', startIcon: left_1.default, iconGutter: 4, className: parts_module_scss_1.default['header-button'], children: 'Вернуться к основному товару' }, userProps)),
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Формирует layout для шапки модального окна медиа.
|
|
21
|
+
* @param props Свойства.
|
|
22
|
+
* @return Элемент.
|
|
23
|
+
*/
|
|
24
|
+
function HeaderLayout({ children }) {
|
|
25
|
+
const mounted = (0, utils_1.useMounted)();
|
|
26
|
+
const desktop = (0, breakpoint_1.useBreakpoint)('xs+');
|
|
27
|
+
const { tabs, button } = (0, define_slots_1.defineSlots)(children, {
|
|
28
|
+
tabs: HeaderLayoutTabs,
|
|
29
|
+
button: HeaderLayoutButton,
|
|
30
|
+
});
|
|
31
|
+
if (!mounted) {
|
|
32
|
+
return react_1.default.createElement("div", { className: parts_module_scss_1.default['header-layout'] });
|
|
33
|
+
}
|
|
34
|
+
return (react_1.default.createElement("div", { className: parts_module_scss_1.default['header-layout'] },
|
|
35
|
+
!desktop && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
36
|
+
tabs && react_1.default.createElement(scroll_section_1.ScrollSection, null, tabs),
|
|
37
|
+
button && react_1.default.createElement(layout_1.Layout, null, button))),
|
|
38
|
+
desktop && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
39
|
+
tabs,
|
|
40
|
+
button))));
|
|
41
|
+
}
|
|
42
|
+
exports.HeaderLayout = HeaderLayout;
|
|
43
|
+
HeaderLayout.Tabs = HeaderLayoutTabs;
|
|
44
|
+
HeaderLayout.Button = HeaderLayoutButton;
|
|
45
|
+
/**
|
|
46
|
+
* Слот вкладок для layout'а шапки модального окна медиа.
|
|
47
|
+
* @param props Свойства.
|
|
48
|
+
* @return Элемент.
|
|
49
|
+
*/
|
|
50
|
+
function HeaderLayoutTabs({ children }) {
|
|
51
|
+
return react_1.default.createElement(react_1.default.Fragment, null, children);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Слот кнопки для layout'а шапки модального окна медиа.
|
|
55
|
+
* @param props Свойства.
|
|
56
|
+
* @return Элемент.
|
|
57
|
+
*/
|
|
58
|
+
function HeaderLayoutButton({ children }) {
|
|
59
|
+
return react_1.default.createElement(react_1.default.Fragment, null, children);
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { MediaData } from '../types';
|
|
3
|
+
export interface MediaContentProps {
|
|
4
|
+
items: MediaData[];
|
|
5
|
+
targetIndex?: number;
|
|
6
|
+
onChangeTargetIndex?: (newIndex: number) => void;
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface FitSquareProps {
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Отображает медиа-контент в контексте модального окна.
|
|
16
|
+
* @param props Свойства.
|
|
17
|
+
* @return Элемент.
|
|
18
|
+
*/
|
|
19
|
+
export declare function MediaContent(props: MediaContentProps): JSX.Element;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.MediaContent = void 0;
|
|
30
|
+
const react_1 = __importStar(require("react"));
|
|
31
|
+
const carousel_1 = require("@sima-land/ui-nucleons/carousel");
|
|
32
|
+
const arrow_button_1 = require("@sima-land/ui-nucleons/arrow-button");
|
|
33
|
+
const breakpoint_1 = require("@sima-land/ui-nucleons/hooks/breakpoint");
|
|
34
|
+
const utils_1 = require("../utils");
|
|
35
|
+
const media_view_1 = require("./media-view");
|
|
36
|
+
const page_scroll_lock_1 = require("@sima-land/ui-nucleons/_internal/page-scroll-lock");
|
|
37
|
+
const media_content_module_scss_1 = __importDefault(require("./media-content.module.scss"));
|
|
38
|
+
/**
|
|
39
|
+
* Отображает медиа-контент в контексте модального окна.
|
|
40
|
+
* @param props Свойства.
|
|
41
|
+
* @return Элемент.
|
|
42
|
+
*/
|
|
43
|
+
function MediaContent(props) {
|
|
44
|
+
const desktop = (0, breakpoint_1.useBreakpoint)('xs+');
|
|
45
|
+
return (react_1.default.createElement("div", { className: media_content_module_scss_1.default.root }, desktop ? react_1.default.createElement(MediaSlider, Object.assign({}, props)) : react_1.default.createElement(MediaCarousel, Object.assign({}, props))));
|
|
46
|
+
}
|
|
47
|
+
exports.MediaContent = MediaContent;
|
|
48
|
+
/**
|
|
49
|
+
* Отображает медиа-контент в виде карусели.
|
|
50
|
+
* @param props Свойства.
|
|
51
|
+
* @return Элемент.
|
|
52
|
+
*/
|
|
53
|
+
function MediaCarousel({ items, targetIndex, onChangeTargetIndex, loading }) {
|
|
54
|
+
const ref = (0, react_1.useRef)(null);
|
|
55
|
+
const area = (0, utils_1.useClientRect)(ref);
|
|
56
|
+
return (react_1.default.createElement("div", { ref: ref, className: media_content_module_scss_1.default.carousel }, area.ready && (react_1.default.createElement(carousel_1.Carousel, { infinite: false, targetIndex: targetIndex, onChangeTargetIndex: onChangeTargetIndex, draggable: true, containerProps: {
|
|
57
|
+
className: media_content_module_scss_1.default.carousel,
|
|
58
|
+
// в случае если будет работать некорректно - дать возможность задавать пропсы для Carousel
|
|
59
|
+
[page_scroll_lock_1.BSL_IGNORE_ATTR]: true,
|
|
60
|
+
}, withControls: false, items: items, renderItem: data => (react_1.default.createElement("div", { className: media_content_module_scss_1.default['carousel-item'] },
|
|
61
|
+
react_1.default.createElement(FitSquare, Object.assign({}, area),
|
|
62
|
+
react_1.default.createElement(media_view_1.MediaView, { media: data, loading: loading })))) }))));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Отображает медиа-контент в виде слайдера.
|
|
66
|
+
* @param props Свойства.
|
|
67
|
+
* @return Элемент.
|
|
68
|
+
*/
|
|
69
|
+
function MediaSlider({ items, targetIndex, onChangeTargetIndex, loading }) {
|
|
70
|
+
const [position, setPosition] = (0, react_1.useState)(0);
|
|
71
|
+
const ref = (0, react_1.useRef)(null);
|
|
72
|
+
const area = (0, utils_1.useClientRect)(ref);
|
|
73
|
+
const total = items.length;
|
|
74
|
+
const controls = total > 1;
|
|
75
|
+
const correction = controls ? -(2 * (56 + 24)) : 0;
|
|
76
|
+
(0, react_1.useEffect)(() => {
|
|
77
|
+
if (typeof targetIndex === 'number') {
|
|
78
|
+
setPosition(targetIndex);
|
|
79
|
+
}
|
|
80
|
+
}, [targetIndex]);
|
|
81
|
+
return (react_1.default.createElement("div", { ref: ref, className: media_content_module_scss_1.default.slider }, area.ready && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
82
|
+
controls && (react_1.default.createElement(arrow_button_1.ArrowButton, { direction: 'left', onClick: () => {
|
|
83
|
+
const next = position - 1 >= 0 ? position - 1 : total - 1;
|
|
84
|
+
setPosition(next);
|
|
85
|
+
onChangeTargetIndex === null || onChangeTargetIndex === void 0 ? void 0 : onChangeTargetIndex(next);
|
|
86
|
+
} })),
|
|
87
|
+
react_1.default.createElement(FitSquare, { width: area.width + correction, height: area.height },
|
|
88
|
+
react_1.default.createElement(media_view_1.MediaView, { media: items[position], loading: loading })),
|
|
89
|
+
controls && (react_1.default.createElement(arrow_button_1.ArrowButton, { direction: 'right', onClick: () => {
|
|
90
|
+
const next = (position + 1) % total;
|
|
91
|
+
setPosition(next);
|
|
92
|
+
onChangeTargetIndex === null || onChangeTargetIndex === void 0 ? void 0 : onChangeTargetIndex(next);
|
|
93
|
+
} }))))));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Вписывает квадрат в заданную прямоугольную область.
|
|
97
|
+
* @param props Свойства.
|
|
98
|
+
* @return Элемент.
|
|
99
|
+
*/
|
|
100
|
+
function FitSquare({ children, width, height }) {
|
|
101
|
+
const size = (0, react_1.useMemo)(() => Math.min(width, height), [width, height]);
|
|
102
|
+
return (react_1.default.createElement("div", { className: media_content_module_scss_1.default.square, style: { '--square-size': `${size}px` } }, children));
|
|
103
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@use 'node_modules/@sima-land/ui-nucleons/colors';
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
position: absolute;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.carousel {
|
|
10
|
+
position: absolute;
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.carousel-item {
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
width: var(--media-width);
|
|
18
|
+
height: var(--media-height);
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.slider {
|
|
26
|
+
position: absolute;
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: 24px;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.square {
|
|
36
|
+
position: relative;
|
|
37
|
+
flex-shrink: 0;
|
|
38
|
+
width: var(--square-size);
|
|
39
|
+
height: var(--square-size);
|
|
40
|
+
--media-width: var(--square-size);
|
|
41
|
+
--media-height: var(--square-size);
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VideoHTMLAttributes } from 'react';
|
|
2
|
+
import { MediaData } from '../types';
|
|
3
|
+
export interface MediaViewProps {
|
|
4
|
+
media?: MediaData;
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
videoProps?: VideoHTMLAttributes<HTMLVideoElement>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Отображает медиа-контент - 360, видео или изображение.
|
|
10
|
+
* @param props Свойства.
|
|
11
|
+
* @return Элемент.
|
|
12
|
+
*/
|
|
13
|
+
export declare function MediaView({ media, loading, videoProps }: MediaViewProps): JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MediaView = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const image_overlay_1 = require("../../../../desktop/components/gallery-modal/components/image-overlay");
|
|
9
|
+
const breakpoint_1 = require("@sima-land/ui-nucleons/hooks/breakpoint");
|
|
10
|
+
const media_view_module_scss_1 = __importDefault(require("./media-view.module.scss"));
|
|
11
|
+
const classnames_1 = __importDefault(require("classnames"));
|
|
12
|
+
const all_round_view_1 = require("./all-round-view");
|
|
13
|
+
/**
|
|
14
|
+
* Отображает медиа-контент - 360, видео или изображение.
|
|
15
|
+
* @param props Свойства.
|
|
16
|
+
* @return Элемент.
|
|
17
|
+
*/
|
|
18
|
+
function MediaView({ media, loading, videoProps }) {
|
|
19
|
+
const desktop = (0, breakpoint_1.useBreakpoint)('xs+');
|
|
20
|
+
if (loading) {
|
|
21
|
+
return react_1.default.createElement("div", { className: (0, classnames_1.default)(media_view_module_scss_1.default.root, media_view_module_scss_1.default.loading) });
|
|
22
|
+
}
|
|
23
|
+
return (react_1.default.createElement("div", { className: (0, classnames_1.default)(media_view_module_scss_1.default.root) },
|
|
24
|
+
(media === null || media === void 0 ? void 0 : media.type) === 'image' && (react_1.default.createElement(image_overlay_1.ImageOverlay, { className: media_view_module_scss_1.default.image },
|
|
25
|
+
react_1.default.createElement("img", { src: media.data.src, alt: media.data.alt || '' }))),
|
|
26
|
+
(media === null || media === void 0 ? void 0 : media.type) === 'video' && (react_1.default.createElement("video", Object.assign({ autoPlay: false, controls: true, controlsList: 'nodownload' }, videoProps),
|
|
27
|
+
react_1.default.createElement("source", { src: media.data.src }))),
|
|
28
|
+
(media === null || media === void 0 ? void 0 : media.type) === '360' && react_1.default.createElement(all_round_view_1.AllRoundView, { photos: media.data.photos, controls: desktop })));
|
|
29
|
+
}
|
|
30
|
+
exports.MediaView = MediaView;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@use 'node_modules/@sima-land/ui-nucleons/colors';
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
width: var(--media-width, 320px);
|
|
5
|
+
height: var(--media-height, 240px);
|
|
6
|
+
position: absolute;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
&.loading {
|
|
11
|
+
background: colors.$basic-gray4;
|
|
12
|
+
}
|
|
13
|
+
--all-round-view-size: min(var(--media-width), var(--media-height));
|
|
14
|
+
video,
|
|
15
|
+
.image {
|
|
16
|
+
position: absolute;
|
|
17
|
+
display: block;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
}
|
|
21
|
+
video {
|
|
22
|
+
background: colors.$basic-gray87;
|
|
23
|
+
}
|
|
24
|
+
.image img {
|
|
25
|
+
display: block;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
object-fit: contain;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@use 'node_modules/@sima-land/ui-nucleons/breakpoints';
|
|
2
|
+
@use 'node_modules/@sima-land/ui-nucleons/utils';
|
|
3
|
+
|
|
4
|
+
.header-layout {
|
|
5
|
+
min-height: 40px; // ВАЖНО: чтобы при отсутствии табов не уменьшалась высота
|
|
6
|
+
@include breakpoints.up('xs') {
|
|
7
|
+
flex-wrap: wrap;
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: flex-end;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.header-button {
|
|
15
|
+
margin-top: 16px !important;
|
|
16
|
+
@include breakpoints.down('xs') {
|
|
17
|
+
&:first-child {
|
|
18
|
+
margin-left: auto;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HTMLAttributes, MouseEventHandler, ReactNode } from 'react';
|
|
2
|
+
export interface ProductBriefProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
size?: 's' | 'l';
|
|
4
|
+
title?: string;
|
|
5
|
+
price?: string | number;
|
|
6
|
+
currency?: string;
|
|
7
|
+
footer?: ReactNode;
|
|
8
|
+
href?: string;
|
|
9
|
+
onLinkClick?: MouseEventHandler<HTMLAnchorElement>;
|
|
10
|
+
imageSrc?: string;
|
|
11
|
+
loading?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Блок товара-рекомендации.
|
|
15
|
+
* @param props Свойства.
|
|
16
|
+
* @return Элемент.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ProductBrief({ size: sizeProp, imageSrc, title, price, currency, footer, className, href, onLinkClick, loading, ...restProps }: ProductBriefProps): JSX.Element;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ProductBrief = void 0;
|
|
18
|
+
const react_1 = __importDefault(require("react"));
|
|
19
|
+
const image_overlay_1 = require("../../../../desktop/components/gallery-modal/components/image-overlay");
|
|
20
|
+
const price_1 = require("@sima-land/ui-nucleons/price");
|
|
21
|
+
const bind_1 = __importDefault(require("classnames/bind"));
|
|
22
|
+
const product_brief_module_scss_1 = __importDefault(require("./product-brief.module.scss"));
|
|
23
|
+
const breakpoint_1 = require("@sima-land/ui-nucleons/hooks/breakpoint");
|
|
24
|
+
const cx = bind_1.default.bind(product_brief_module_scss_1.default);
|
|
25
|
+
/**
|
|
26
|
+
* Блок товара-рекомендации.
|
|
27
|
+
* @param props Свойства.
|
|
28
|
+
* @return Элемент.
|
|
29
|
+
*/
|
|
30
|
+
function ProductBrief(_a) {
|
|
31
|
+
var { size: sizeProp, imageSrc, title, price, currency, footer, className, href, onLinkClick, loading } = _a, restProps = __rest(_a, ["size", "imageSrc", "title", "price", "currency", "footer", "className", "href", "onLinkClick", "loading"]);
|
|
32
|
+
const desktop = (0, breakpoint_1.useBreakpoint)('xs+');
|
|
33
|
+
const size = (sizeProp !== null && sizeProp !== void 0 ? sizeProp : desktop) ? 'l' : 's';
|
|
34
|
+
if (loading) {
|
|
35
|
+
return react_1.default.createElement(ProductBriefSkeleton, { size: size });
|
|
36
|
+
}
|
|
37
|
+
return (react_1.default.createElement("div", Object.assign({ className: cx('root', `size-${size}`, className) }, restProps),
|
|
38
|
+
react_1.default.createElement("a", { className: product_brief_module_scss_1.default.image, href: href, onClick: onLinkClick },
|
|
39
|
+
react_1.default.createElement(image_overlay_1.ImageOverlay, { className: product_brief_module_scss_1.default.overlay },
|
|
40
|
+
react_1.default.createElement("img", { src: imageSrc }))),
|
|
41
|
+
react_1.default.createElement("div", { className: product_brief_module_scss_1.default.info },
|
|
42
|
+
react_1.default.createElement("a", { className: product_brief_module_scss_1.default.title, href: href, onClick: onLinkClick }, title),
|
|
43
|
+
price && react_1.default.createElement(price_1.Price, { className: product_brief_module_scss_1.default.price, value: price, currencyGrapheme: currency })),
|
|
44
|
+
footer && react_1.default.createElement("div", { className: product_brief_module_scss_1.default.footer }, footer)));
|
|
45
|
+
}
|
|
46
|
+
exports.ProductBrief = ProductBrief;
|
|
47
|
+
/**
|
|
48
|
+
* Заглушка блока товара-рекомендации.
|
|
49
|
+
* @param props Свойства.
|
|
50
|
+
* @return Элемент.
|
|
51
|
+
*/
|
|
52
|
+
function ProductBriefSkeleton({ size }) {
|
|
53
|
+
return (react_1.default.createElement("div", { className: cx('skeleton', size) },
|
|
54
|
+
react_1.default.createElement("div", null),
|
|
55
|
+
react_1.default.createElement("div", null),
|
|
56
|
+
react_1.default.createElement("div", null)));
|
|
57
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
@use 'node_modules/@sima-land/ui-nucleons/colors';
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
display: grid;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.image {
|
|
8
|
+
display: block;
|
|
9
|
+
.overlay {
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
> img {
|
|
13
|
+
display: block;
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
object-fit: contain;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.info {
|
|
22
|
+
font-size: 20px;
|
|
23
|
+
line-height: 28px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.title {
|
|
27
|
+
display: block;
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
color: colors.$basic-gray87;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.price {
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.footer {
|
|
37
|
+
display: flex;
|
|
38
|
+
> * {
|
|
39
|
+
width: 128px;
|
|
40
|
+
--stepper-width: 128px;
|
|
41
|
+
}
|
|
42
|
+
> *:last-child {
|
|
43
|
+
margin-left: 8px;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.size-s {
|
|
48
|
+
column-gap: 8px;
|
|
49
|
+
row-gap: 16px;
|
|
50
|
+
grid-template-columns: 88px 1fr;
|
|
51
|
+
.image {
|
|
52
|
+
width: 88px;
|
|
53
|
+
height: 88px;
|
|
54
|
+
grid-column: 1 / 2;
|
|
55
|
+
grid-row: 1 / 2;
|
|
56
|
+
}
|
|
57
|
+
.info {
|
|
58
|
+
grid-column: 2 / 3;
|
|
59
|
+
grid-row: 1 / 2;
|
|
60
|
+
}
|
|
61
|
+
.title {
|
|
62
|
+
display: -webkit-box;
|
|
63
|
+
-webkit-box-orient: vertical;
|
|
64
|
+
-webkit-line-clamp: 2;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
}
|
|
67
|
+
.title + .price {
|
|
68
|
+
margin-top: 4px;
|
|
69
|
+
}
|
|
70
|
+
.footer {
|
|
71
|
+
grid-column: 1 / 3;
|
|
72
|
+
grid-row: 2 / 3;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.size-l {
|
|
77
|
+
column-gap: 16px;
|
|
78
|
+
row-gap: 16px;
|
|
79
|
+
grid-template-columns: 112px 1fr;
|
|
80
|
+
.image {
|
|
81
|
+
width: 112px;
|
|
82
|
+
height: 112px;
|
|
83
|
+
grid-column: 1 / 2;
|
|
84
|
+
grid-row: 1 / 3;
|
|
85
|
+
}
|
|
86
|
+
.info {
|
|
87
|
+
min-width: 0; // ВАЖНО: чтобы title не вызывал переполнение
|
|
88
|
+
grid-column: 2 / 3;
|
|
89
|
+
grid-row: 1 / 2;
|
|
90
|
+
.title {
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
text-overflow: ellipsis;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
.footer {
|
|
97
|
+
grid-column: 2 / 3;
|
|
98
|
+
grid-row: 2 / 3;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.skeleton {
|
|
103
|
+
display: grid;
|
|
104
|
+
> * {
|
|
105
|
+
border-radius: 4px;
|
|
106
|
+
background: colors.$basic-gray4;
|
|
107
|
+
}
|
|
108
|
+
&.s {
|
|
109
|
+
grid-template-columns: 88px 1fr;
|
|
110
|
+
row-gap: 16px;
|
|
111
|
+
column-gap: 16px;
|
|
112
|
+
:first-child {
|
|
113
|
+
width: 88px;
|
|
114
|
+
height: 88px;
|
|
115
|
+
}
|
|
116
|
+
:nth-child(2) {
|
|
117
|
+
width: auto;
|
|
118
|
+
height: 88px;
|
|
119
|
+
}
|
|
120
|
+
:last-child {
|
|
121
|
+
width: 128px;
|
|
122
|
+
height: 40px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
&.l {
|
|
126
|
+
grid-template-columns: 112px 1fr;
|
|
127
|
+
row-gap: 12px;
|
|
128
|
+
column-gap: 16px;
|
|
129
|
+
:first-child {
|
|
130
|
+
grid-row: 1/3;
|
|
131
|
+
width: 112px;
|
|
132
|
+
height: 112px;
|
|
133
|
+
}
|
|
134
|
+
:nth-child(2) {
|
|
135
|
+
grid-row: 1;
|
|
136
|
+
grid-column: 2;
|
|
137
|
+
width: auto;
|
|
138
|
+
height: 60px;
|
|
139
|
+
}
|
|
140
|
+
:last-child {
|
|
141
|
+
grid-row: 2;
|
|
142
|
+
grid-column: 2;
|
|
143
|
+
width: 128px;
|
|
144
|
+
height: 40px;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface ReviewProps {
|
|
3
|
+
rating?: number;
|
|
4
|
+
ratingPlaceholder?: ReactNode;
|
|
5
|
+
author?: ReactNode;
|
|
6
|
+
button?: ReactNode;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Отзыв.
|
|
12
|
+
* @param props Свойства.
|
|
13
|
+
* @return Элемент.
|
|
14
|
+
*/
|
|
15
|
+
export declare function Review({ rating, ratingPlaceholder, author, children, loading, button, }: ReviewProps): JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Review = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const rating_1 = require("@sima-land/ui-nucleons/rating");
|
|
9
|
+
const bind_1 = __importDefault(require("classnames/bind"));
|
|
10
|
+
const review_module_scss_1 = __importDefault(require("./review.module.scss"));
|
|
11
|
+
const cx = bind_1.default.bind(review_module_scss_1.default);
|
|
12
|
+
/**
|
|
13
|
+
* Отзыв.
|
|
14
|
+
* @param props Свойства.
|
|
15
|
+
* @return Элемент.
|
|
16
|
+
*/
|
|
17
|
+
function Review({ rating, ratingPlaceholder, author, children, loading, button, }) {
|
|
18
|
+
if (loading) {
|
|
19
|
+
return react_1.default.createElement(ReviewSkeleton, { button: button });
|
|
20
|
+
}
|
|
21
|
+
return (react_1.default.createElement("div", { className: cx('root') },
|
|
22
|
+
react_1.default.createElement("div", { className: cx('header') },
|
|
23
|
+
(rating || ratingPlaceholder || author) && (react_1.default.createElement("div", { className: cx('col', 'meta') },
|
|
24
|
+
(typeof rating === 'number' || ratingPlaceholder) && (react_1.default.createElement("div", { className: cx('col', 'rating') }, rating ? react_1.default.createElement(rating_1.Rating, { size: 's', value: rating }) : ratingPlaceholder)),
|
|
25
|
+
author && react_1.default.createElement("div", { className: cx('col', 'author') }, author))),
|
|
26
|
+
button && react_1.default.createElement("div", { className: cx('col', 'button') }, button)),
|
|
27
|
+
Boolean(children) && react_1.default.createElement("div", { className: review_module_scss_1.default.content }, children)));
|
|
28
|
+
}
|
|
29
|
+
exports.Review = Review;
|
|
30
|
+
/**
|
|
31
|
+
* Заглушка отзыва.
|
|
32
|
+
* @param props Свойства.
|
|
33
|
+
* @return Элемент.
|
|
34
|
+
*/
|
|
35
|
+
function ReviewSkeleton({ button }) {
|
|
36
|
+
return (react_1.default.createElement("div", { className: cx('root') },
|
|
37
|
+
react_1.default.createElement("div", { className: cx('header') },
|
|
38
|
+
react_1.default.createElement("div", { className: cx('col', 'meta') },
|
|
39
|
+
react_1.default.createElement("div", { className: cx('skeleton', 'skeleton-meta') })),
|
|
40
|
+
button && react_1.default.createElement("div", { className: cx('col', 'button') }, button)),
|
|
41
|
+
react_1.default.createElement("div", { className: cx('content') },
|
|
42
|
+
react_1.default.createElement("div", { className: cx('skeleton') }),
|
|
43
|
+
react_1.default.createElement("div", { className: cx('skeleton') }))));
|
|
44
|
+
}
|