@snack-uikit/modal 0.7.9-preview-85c5f47b.0
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/CHANGELOG.md +398 -0
- package/LICENSE +201 -0
- package/README.md +74 -0
- package/dist/components/Modal/Modal.d.ts +9 -0
- package/dist/components/Modal/Modal.js +30 -0
- package/dist/components/Modal/index.d.ts +1 -0
- package/dist/components/Modal/index.js +1 -0
- package/dist/components/Modal/styles.module.css +3 -0
- package/dist/components/Modal/types.d.ts +50 -0
- package/dist/components/Modal/types.js +1 -0
- package/dist/components/ModalCustom/ModalCustom.d.ts +40 -0
- package/dist/components/ModalCustom/ModalCustom.js +37 -0
- package/dist/components/ModalCustom/index.d.ts +1 -0
- package/dist/components/ModalCustom/index.js +1 -0
- package/dist/components/ModalCustom/styles.module.css +40 -0
- package/dist/components/ModalCustom/utils.d.ts +1 -0
- package/dist/components/ModalCustom/utils.js +9 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/constants.d.ts +37 -0
- package/dist/constants.js +41 -0
- package/dist/helperComponents/Body/Body.d.ts +14 -0
- package/dist/helperComponents/Body/Body.js +22 -0
- package/dist/helperComponents/Body/index.d.ts +1 -0
- package/dist/helperComponents/Body/index.js +1 -0
- package/dist/helperComponents/Body/styles.module.css +13 -0
- package/dist/helperComponents/ButtonClose/ButtonClose.d.ts +5 -0
- package/dist/helperComponents/ButtonClose/ButtonClose.js +7 -0
- package/dist/helperComponents/ButtonClose/index.d.ts +1 -0
- package/dist/helperComponents/ButtonClose/index.js +1 -0
- package/dist/helperComponents/ButtonClose/styles.module.css +35 -0
- package/dist/helperComponents/Footer/Footer.d.ts +16 -0
- package/dist/helperComponents/Footer/Footer.js +21 -0
- package/dist/helperComponents/Footer/index.d.ts +1 -0
- package/dist/helperComponents/Footer/index.js +1 -0
- package/dist/helperComponents/Footer/styles.module.css +39 -0
- package/dist/helperComponents/Header/Header.d.ts +23 -0
- package/dist/helperComponents/Header/Header.js +26 -0
- package/dist/helperComponents/Header/index.d.ts +2 -0
- package/dist/helperComponents/Header/index.js +2 -0
- package/dist/helperComponents/Header/styles.module.css +52 -0
- package/dist/helperComponents/Header/types.d.ts +4 -0
- package/dist/helperComponents/Header/types.js +1 -0
- package/dist/helperComponents/OverlayElement/OverlayElement.d.ts +7 -0
- package/dist/helperComponents/OverlayElement/OverlayElement.js +10 -0
- package/dist/helperComponents/OverlayElement/index.d.ts +1 -0
- package/dist/helperComponents/OverlayElement/index.js +1 -0
- package/dist/helperComponents/OverlayElement/styles.module.css +13 -0
- package/dist/helperComponents/index.d.ts +5 -0
- package/dist/helperComponents/index.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils.d.ts +33 -0
- package/dist/utils.js +60 -0
- package/package.json +51 -0
- package/src/components/Modal/Modal.tsx +104 -0
- package/src/components/Modal/index.ts +1 -0
- package/src/components/Modal/styles.module.scss +7 -0
- package/src/components/Modal/types.ts +54 -0
- package/src/components/ModalCustom/ModalCustom.tsx +96 -0
- package/src/components/ModalCustom/index.ts +1 -0
- package/src/components/ModalCustom/styles.module.scss +38 -0
- package/src/components/ModalCustom/utils.ts +12 -0
- package/src/components/index.ts +2 -0
- package/src/constants.ts +41 -0
- package/src/helperComponents/Body/Body.tsx +32 -0
- package/src/helperComponents/Body/index.ts +1 -0
- package/src/helperComponents/Body/styles.module.scss +14 -0
- package/src/helperComponents/ButtonClose/ButtonClose.tsx +21 -0
- package/src/helperComponents/ButtonClose/index.ts +1 -0
- package/src/helperComponents/ButtonClose/styles.module.scss +44 -0
- package/src/helperComponents/Footer/Footer.tsx +38 -0
- package/src/helperComponents/Footer/index.ts +1 -0
- package/src/helperComponents/Footer/styles.module.scss +44 -0
- package/src/helperComponents/Header/Header.tsx +81 -0
- package/src/helperComponents/Header/index.ts +2 -0
- package/src/helperComponents/Header/styles.module.scss +52 -0
- package/src/helperComponents/Header/types.ts +4 -0
- package/src/helperComponents/OverlayElement/OverlayElement.tsx +30 -0
- package/src/helperComponents/OverlayElement/index.ts +1 -0
- package/src/helperComponents/OverlayElement/styles.module.scss +17 -0
- package/src/helperComponents/index.ts +5 -0
- package/src/index.ts +1 -0
- package/src/utils.ts +69 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.header{
|
|
2
|
+
}
|
|
3
|
+
|
|
4
|
+
.image{
|
|
5
|
+
border-top-left-radius:var(--radius-modal-modal-window, 32px);
|
|
6
|
+
border-top-right-radius:var(--radius-modal-modal-window, 32px);
|
|
7
|
+
height:var(--size-modal-image, 184px);
|
|
8
|
+
display:block;
|
|
9
|
+
width:100%;
|
|
10
|
+
-o-object-fit:cover;
|
|
11
|
+
object-fit:cover;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.icon{
|
|
15
|
+
padding-top:var(--space-modal-icon-decor, 32px);
|
|
16
|
+
display:flex;
|
|
17
|
+
justify-content:center;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.headlineLayout[data-align=default]{
|
|
21
|
+
padding-top:var(--space-modal-headline-layout-top, 32px);
|
|
22
|
+
padding-left:var(--space-modal-headline-layout-side-s, 32px);
|
|
23
|
+
padding-right:var(--space-modal-headline-layout-side-m, 64px);
|
|
24
|
+
padding-bottom:var(--space-modal-headline-layout-bottom, 24px);
|
|
25
|
+
gap:var(--space-modal-headline-layout-gap, 8px);
|
|
26
|
+
display:flex;
|
|
27
|
+
flex-direction:column;
|
|
28
|
+
}
|
|
29
|
+
.headlineLayout[data-align=center]{
|
|
30
|
+
padding-top:var(--space-modal-headline-layout-top, 32px);
|
|
31
|
+
padding-left:var(--space-modal-headline-layout-side-m, 64px);
|
|
32
|
+
padding-right:var(--space-modal-headline-layout-side-m, 64px);
|
|
33
|
+
padding-bottom:var(--space-modal-headline-layout-bottom, 24px);
|
|
34
|
+
gap:var(--space-modal-headline-layout-gap, 8px);
|
|
35
|
+
display:flex;
|
|
36
|
+
flex-direction:column;
|
|
37
|
+
align-items:center;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.headline{
|
|
41
|
+
gap:var(--space-modal-headline-layout-headline, 4px);
|
|
42
|
+
display:flex;
|
|
43
|
+
align-items:center;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.title{
|
|
47
|
+
display:grid;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.subtitle{
|
|
51
|
+
color:var(--sys-neutral-text-support, #565656);
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
export type OverlayElementProps = {
|
|
3
|
+
onClose(): void;
|
|
4
|
+
content: ReactElement;
|
|
5
|
+
blur?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function OverlayElement({ onClose, content, blur }: OverlayElementProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TEST_IDS } from '../../constants';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
export function OverlayElement({ onClose, content, blur = false }) {
|
|
5
|
+
const handleClick = e => {
|
|
6
|
+
e.stopPropagation();
|
|
7
|
+
onClose();
|
|
8
|
+
};
|
|
9
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.modalOverlay, "data-blur": blur || undefined, onClick: handleClick, "data-test-id": TEST_IDS.overlay }), content] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './OverlayElement';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './OverlayElement';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.modalOverlay{
|
|
2
|
+
position:fixed;
|
|
3
|
+
top:0;
|
|
4
|
+
left:0;
|
|
5
|
+
box-sizing:border-box;
|
|
6
|
+
width:100%;
|
|
7
|
+
height:100%;
|
|
8
|
+
background:var(--sys-blackout, rgba(0, 0, 0, 0.4784313725));
|
|
9
|
+
}
|
|
10
|
+
.modalOverlay[data-blur]{
|
|
11
|
+
-webkit-backdrop-filter:blur(var(--background-blur-background-blur, 4px));
|
|
12
|
+
backdrop-filter:blur(var(--background-blur-background-blur, 4px));
|
|
13
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components';
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { Align, ContentAlign, Size } from './constants';
|
|
3
|
+
import { ModalHeaderImage, ModalHeaderProps } from './helperComponents';
|
|
4
|
+
export declare function getAlignProps({ align, size }: {
|
|
5
|
+
align: Align;
|
|
6
|
+
size: Size;
|
|
7
|
+
}): {
|
|
8
|
+
header: ContentAlign;
|
|
9
|
+
body: ContentAlign;
|
|
10
|
+
footer: Align;
|
|
11
|
+
};
|
|
12
|
+
export declare function getButtonsSizes({ align, size }: {
|
|
13
|
+
align: Align;
|
|
14
|
+
size: Size;
|
|
15
|
+
}): {
|
|
16
|
+
filled: import("@snack-uikit/button/dist/constants").SizeSL;
|
|
17
|
+
outline: import("@snack-uikit/button/dist/constants").SizeSL;
|
|
18
|
+
simple: import("@snack-uikit/button/dist/constants").SizeSL;
|
|
19
|
+
light?: undefined;
|
|
20
|
+
} | {
|
|
21
|
+
filled: import("@snack-uikit/button/dist/constants").SizeSL;
|
|
22
|
+
outline: import("@snack-uikit/button/dist/constants").SizeSL;
|
|
23
|
+
light: import("@snack-uikit/button/dist/constants").SizeSL;
|
|
24
|
+
simple?: undefined;
|
|
25
|
+
};
|
|
26
|
+
export declare function isPictureImage(picture: ModalHeaderProps['picture']): picture is ModalHeaderImage;
|
|
27
|
+
export declare function getPicture({ size, picture }: {
|
|
28
|
+
size: Size;
|
|
29
|
+
picture: ModalHeaderProps['picture'];
|
|
30
|
+
}): import("react").JSXElementConstructor<{
|
|
31
|
+
size?: number | undefined;
|
|
32
|
+
className?: string | undefined;
|
|
33
|
+
}> | ModalHeaderImage | undefined;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ButtonFilled, ButtonOutline, ButtonSimple } from '@snack-uikit/button';
|
|
2
|
+
import { Align, ContentAlign, Size } from './constants';
|
|
3
|
+
const MAP_ALIGNS = {
|
|
4
|
+
[Align.Default]: {
|
|
5
|
+
header: ContentAlign.Default,
|
|
6
|
+
body: ContentAlign.Default,
|
|
7
|
+
footer: Align.Default,
|
|
8
|
+
},
|
|
9
|
+
[Align.Center]: {
|
|
10
|
+
header: ContentAlign.Center,
|
|
11
|
+
body: ContentAlign.Center,
|
|
12
|
+
footer: Align.Center,
|
|
13
|
+
},
|
|
14
|
+
[Align.Vertical]: {
|
|
15
|
+
header: ContentAlign.Center,
|
|
16
|
+
body: ContentAlign.Center,
|
|
17
|
+
footer: Align.Vertical,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export function getAlignProps({ align, size }) {
|
|
21
|
+
if (size === Size.L) {
|
|
22
|
+
return MAP_ALIGNS[Align.Default];
|
|
23
|
+
}
|
|
24
|
+
if (size === Size.M && align === Align.Vertical) {
|
|
25
|
+
return MAP_ALIGNS[Align.Default];
|
|
26
|
+
}
|
|
27
|
+
return MAP_ALIGNS[align];
|
|
28
|
+
}
|
|
29
|
+
export function getButtonsSizes({ align, size }) {
|
|
30
|
+
switch (true) {
|
|
31
|
+
case align === Align.Vertical && size === Size.S:
|
|
32
|
+
return {
|
|
33
|
+
filled: ButtonFilled.sizes.L,
|
|
34
|
+
outline: ButtonOutline.sizes.L,
|
|
35
|
+
simple: ButtonSimple.sizes.L,
|
|
36
|
+
};
|
|
37
|
+
case [Align.Default, Align.Center].includes(align):
|
|
38
|
+
default:
|
|
39
|
+
return {
|
|
40
|
+
filled: ButtonFilled.sizes.M,
|
|
41
|
+
outline: ButtonOutline.sizes.M,
|
|
42
|
+
light: ButtonSimple.sizes.M,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function isPictureImage(picture) {
|
|
47
|
+
if (!picture)
|
|
48
|
+
return false;
|
|
49
|
+
return 'src' in picture;
|
|
50
|
+
}
|
|
51
|
+
export function getPicture({ size, picture }) {
|
|
52
|
+
switch (size) {
|
|
53
|
+
case Size.S:
|
|
54
|
+
return picture;
|
|
55
|
+
case Size.M:
|
|
56
|
+
case Size.L:
|
|
57
|
+
default:
|
|
58
|
+
return isPictureImage(picture) ? picture : undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@snack-uikit/modal",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"title": "Modal",
|
|
7
|
+
"version": "0.7.9-preview-85c5f47b.0",
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"*.css",
|
|
10
|
+
"*.woff",
|
|
11
|
+
"*.woff2"
|
|
12
|
+
],
|
|
13
|
+
"description": "",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"homepage": "https://github.com/cloud-ru-tech/snack-uikit/tree/master/packages/modal",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/cloud-ru-tech/snack-uikit.git",
|
|
20
|
+
"directory": "packages/modal"
|
|
21
|
+
},
|
|
22
|
+
"author": "Белов Алексей <anbelov@cloud.ru>",
|
|
23
|
+
"contributors": [
|
|
24
|
+
"Белов Алексей <anbelov@cloud.ru>"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src",
|
|
29
|
+
"./CHANGELOG.md",
|
|
30
|
+
"./LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"license": "Apache-2.0",
|
|
33
|
+
"scripts": {},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@snack-uikit/button": "0.13.7-preview-85c5f47b.0",
|
|
36
|
+
"@snack-uikit/icon-predefined": "0.2.6-preview-85c5f47b.0",
|
|
37
|
+
"@snack-uikit/icons": "0.18.2-preview-85c5f47b.0",
|
|
38
|
+
"@snack-uikit/link": "0.7.1-preview-85c5f47b.0",
|
|
39
|
+
"@snack-uikit/scroll": "0.3.5-preview-85c5f47b.0",
|
|
40
|
+
"@snack-uikit/tooltip": "0.8.1-preview-85c5f47b.0",
|
|
41
|
+
"@snack-uikit/truncate-string": "0.2.17-preview-85c5f47b.0",
|
|
42
|
+
"@snack-uikit/typography": "0.4.5-preview-85c5f47b.0",
|
|
43
|
+
"@snack-uikit/utils": "2.0.2-preview-85c5f47b.0",
|
|
44
|
+
"classnames": "2.3.2",
|
|
45
|
+
"react-modal": "3.16.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/react-modal": "3.16.0"
|
|
49
|
+
},
|
|
50
|
+
"gitHead": "6a7eef41d780d945f64f791448eaa72b9e0ae72d"
|
|
51
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ButtonFilled, ButtonOutline, ButtonSimple } from '@snack-uikit/button';
|
|
2
|
+
import { Link } from '@snack-uikit/link';
|
|
3
|
+
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
4
|
+
import { Typography } from '@snack-uikit/typography';
|
|
5
|
+
|
|
6
|
+
import { Align, Mode, Size, TEST_IDS } from '../../constants';
|
|
7
|
+
import { getAlignProps, getButtonsSizes, getPicture } from '../../utils';
|
|
8
|
+
import { ModalCustom } from '../ModalCustom';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
import { ModalLProps, ModalMProps, ModalSProps } from './types';
|
|
11
|
+
|
|
12
|
+
export type ModalProps = ModalSProps | ModalMProps | ModalLProps;
|
|
13
|
+
|
|
14
|
+
export function Modal({
|
|
15
|
+
open,
|
|
16
|
+
onClose,
|
|
17
|
+
size = Size.S,
|
|
18
|
+
mode = Mode.Regular,
|
|
19
|
+
align = Align.Default,
|
|
20
|
+
title,
|
|
21
|
+
titleTooltip,
|
|
22
|
+
subtitle,
|
|
23
|
+
picture: pictureProp,
|
|
24
|
+
content,
|
|
25
|
+
approveButton,
|
|
26
|
+
cancelButton,
|
|
27
|
+
additionalButton,
|
|
28
|
+
disclaimer,
|
|
29
|
+
className,
|
|
30
|
+
...rest
|
|
31
|
+
}: ModalProps) {
|
|
32
|
+
const aligns = getAlignProps({ align, size });
|
|
33
|
+
const buttonsSizes = getButtonsSizes({ align, size });
|
|
34
|
+
const picture = getPicture({ size, picture: pictureProp });
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ModalCustom open={open} onClose={onClose} size={size} mode={mode} className={className} {...rest}>
|
|
38
|
+
<ModalCustom.Header
|
|
39
|
+
title={<TruncateString text={title} />}
|
|
40
|
+
titleTooltip={titleTooltip}
|
|
41
|
+
subtitle={subtitle}
|
|
42
|
+
picture={picture}
|
|
43
|
+
align={aligns.header}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
{Boolean(content) && <ModalCustom.Body content={content} align={aligns.body} />}
|
|
47
|
+
|
|
48
|
+
<ModalCustom.Footer
|
|
49
|
+
actions={
|
|
50
|
+
<>
|
|
51
|
+
<ButtonFilled
|
|
52
|
+
{...approveButton}
|
|
53
|
+
size={buttonsSizes.filled}
|
|
54
|
+
className={styles.footerButton}
|
|
55
|
+
data-test-id={TEST_IDS.approveButton}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
{cancelButton && (
|
|
59
|
+
<ButtonOutline
|
|
60
|
+
{...cancelButton}
|
|
61
|
+
size={buttonsSizes.outline}
|
|
62
|
+
className={styles.footerButton}
|
|
63
|
+
data-test-id={TEST_IDS.cancelButton}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{additionalButton && (
|
|
68
|
+
<ButtonSimple
|
|
69
|
+
{...additionalButton}
|
|
70
|
+
size={buttonsSizes.light}
|
|
71
|
+
className={styles.footerButton}
|
|
72
|
+
data-test-id={TEST_IDS.additionalButton}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</>
|
|
76
|
+
}
|
|
77
|
+
disclaimer={
|
|
78
|
+
disclaimer && (
|
|
79
|
+
<>
|
|
80
|
+
<Typography
|
|
81
|
+
family={Typography.families.Sans}
|
|
82
|
+
role={Typography.roles.Body}
|
|
83
|
+
size={Typography.sizes.S}
|
|
84
|
+
data-test-id={TEST_IDS.disclaimerText}
|
|
85
|
+
>
|
|
86
|
+
{disclaimer.text}
|
|
87
|
+
</Typography>
|
|
88
|
+
|
|
89
|
+
{disclaimer.link && (
|
|
90
|
+
<Link {...disclaimer.link} size={Link.sizes.S} external data-test-id={TEST_IDS.disclaimerLink} />
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
align={aligns.footer}
|
|
96
|
+
className={styles.modalFooter}
|
|
97
|
+
/>
|
|
98
|
+
</ModalCustom>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Modal.aligns = Align;
|
|
103
|
+
Modal.modes = Mode;
|
|
104
|
+
Modal.sizes = Size;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Modal';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ButtonFilledProps, ButtonOutlineProps, ButtonSimpleProps } from '@snack-uikit/button';
|
|
2
|
+
import { LinkProps } from '@snack-uikit/link';
|
|
3
|
+
|
|
4
|
+
import { Align, Size } from '../../constants';
|
|
5
|
+
import { ModalBodyProps, ModalHeaderImage, ModalHeaderProps } from '../../helperComponents';
|
|
6
|
+
import { ModalCustomProps } from '../ModalCustom';
|
|
7
|
+
|
|
8
|
+
type BaseModalProps = Omit<ModalCustomProps, 'children' | 'size'> & {
|
|
9
|
+
/** Заголовок модального окна */
|
|
10
|
+
title: string;
|
|
11
|
+
/** Всплывающая подсказка для заголовка */
|
|
12
|
+
titleTooltip?: ModalHeaderProps['titleTooltip'];
|
|
13
|
+
/** Подзаголовок */
|
|
14
|
+
subtitle?: string;
|
|
15
|
+
/** Содержимое модального окна */
|
|
16
|
+
content?: ModalBodyProps['content'];
|
|
17
|
+
/** Основная кнопка действия */
|
|
18
|
+
approveButton: Omit<ButtonFilledProps, 'size' | 'data-test-id'>;
|
|
19
|
+
/** Кнопка отмены */
|
|
20
|
+
cancelButton?: Omit<ButtonOutlineProps, 'size' | 'data-test-id'>;
|
|
21
|
+
/** Вторая кнопка действия */
|
|
22
|
+
additionalButton?: Omit<ButtonSimpleProps, 'size' | 'data-test-id'>;
|
|
23
|
+
/** Небольшой текст под кнопками футера с возможностью передать дополнительно ссылку */
|
|
24
|
+
disclaimer?: {
|
|
25
|
+
text: string;
|
|
26
|
+
link?: Pick<LinkProps, 'text' | 'href' | 'target'>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ModalSProps = BaseModalProps & {
|
|
31
|
+
/** Размер */
|
|
32
|
+
size?: Size.S;
|
|
33
|
+
/**
|
|
34
|
+
* Выравнивание, для разных размеров доступны разные значения
|
|
35
|
+
* <br> для `Size.S` - все
|
|
36
|
+
*/
|
|
37
|
+
align?: Align;
|
|
38
|
+
/** Можно передать иконку из пакета `@snack-uikit/icon-predefined`, или путь к картинке и атрибут `alt` */
|
|
39
|
+
picture?: ModalHeaderProps['picture'];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ModalMProps = BaseModalProps & {
|
|
43
|
+
size?: Size.M;
|
|
44
|
+
/** <br> для `Size.M` - `Align.Default | Align.Center` */
|
|
45
|
+
align?: Align.Default | Align.Center;
|
|
46
|
+
picture?: ModalHeaderImage;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type ModalLProps = BaseModalProps & {
|
|
50
|
+
size?: Size.L;
|
|
51
|
+
/** <br> для `Size.L` - `Align.Default` */
|
|
52
|
+
align?: Align.Default;
|
|
53
|
+
picture?: ModalHeaderImage;
|
|
54
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import RCModal from 'react-modal';
|
|
4
|
+
|
|
5
|
+
import { WithSupportProps } from '@snack-uikit/utils';
|
|
6
|
+
|
|
7
|
+
import { Mode, Size } from '../../constants';
|
|
8
|
+
import {
|
|
9
|
+
ButtonClose,
|
|
10
|
+
ModalBody,
|
|
11
|
+
ModalBodyProps,
|
|
12
|
+
ModalFooter,
|
|
13
|
+
ModalFooterProps,
|
|
14
|
+
ModalHeader,
|
|
15
|
+
ModalHeaderProps,
|
|
16
|
+
OverlayElement,
|
|
17
|
+
} from '../../helperComponents';
|
|
18
|
+
import styles from './styles.module.scss';
|
|
19
|
+
import { getDataTestAttributes } from './utils';
|
|
20
|
+
|
|
21
|
+
export type ModalCustomProps = WithSupportProps<{
|
|
22
|
+
/** Управляет состоянием показан/не показан. */
|
|
23
|
+
open: boolean;
|
|
24
|
+
/** Колбек закрытия компонента. */
|
|
25
|
+
onClose(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Режим отображения модального окна:
|
|
28
|
+
* <br> - __`Regular`__ - есть кнопка закрытия, клик на оверлей и нажатие кнопки `Esc` закрывают модалку
|
|
29
|
+
* <br> - __`Aggressive`__ - есть кнопка закрытия, но выключен клик на оверлей и не работает закрытие по клавише `Esc`
|
|
30
|
+
* <br> - __`Forced`__ - закрыть модальное окно можно только по нажатию на кнопку действия в нижней части
|
|
31
|
+
* @default Mode.Regular
|
|
32
|
+
*/
|
|
33
|
+
mode?: Mode;
|
|
34
|
+
/**
|
|
35
|
+
* Размер модального окна
|
|
36
|
+
* @default Size.S
|
|
37
|
+
*/
|
|
38
|
+
size?: Size;
|
|
39
|
+
/** CSS-класс */
|
|
40
|
+
className?: string;
|
|
41
|
+
/** Контент */
|
|
42
|
+
children: ReactNode;
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
export function ModalCustom({
|
|
46
|
+
open,
|
|
47
|
+
onClose,
|
|
48
|
+
size = Size.S,
|
|
49
|
+
mode = Mode.Regular,
|
|
50
|
+
children,
|
|
51
|
+
className,
|
|
52
|
+
...rest
|
|
53
|
+
}: ModalCustomProps) {
|
|
54
|
+
const handleCloseButtonClick = () => {
|
|
55
|
+
onClose();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleClose = () => {
|
|
59
|
+
if (mode === Mode.Regular) {
|
|
60
|
+
onClose();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<RCModal
|
|
66
|
+
data={{ ...getDataTestAttributes(rest), size }}
|
|
67
|
+
isOpen={open}
|
|
68
|
+
onRequestClose={handleClose}
|
|
69
|
+
appElement={document.body}
|
|
70
|
+
overlayElement={(_, content) => (
|
|
71
|
+
<OverlayElement blur={[Mode.Forced, Mode.Aggressive].includes(mode)} content={content} onClose={handleClose} />
|
|
72
|
+
)}
|
|
73
|
+
className={cn(styles.modal, className)}
|
|
74
|
+
>
|
|
75
|
+
{mode !== Mode.Forced && (
|
|
76
|
+
<div className={styles.headerElements}>
|
|
77
|
+
<ButtonClose onClick={handleCloseButtonClick} />
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{children}
|
|
82
|
+
</RCModal>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
ModalCustom.modes = Mode;
|
|
87
|
+
ModalCustom.sizes = Size;
|
|
88
|
+
|
|
89
|
+
export namespace ModalCustom {
|
|
90
|
+
export type HeaderProps = ModalHeaderProps;
|
|
91
|
+
export type BodyProps = ModalBodyProps;
|
|
92
|
+
export type FooterProps = ModalFooterProps;
|
|
93
|
+
export const Header = ModalHeader;
|
|
94
|
+
export const Body = ModalBody;
|
|
95
|
+
export const Footer = ModalFooter;
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ModalCustom';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-modal';
|
|
2
|
+
|
|
3
|
+
$sizes: 's', 'm', 'l';
|
|
4
|
+
|
|
5
|
+
.modal {
|
|
6
|
+
position: absolute;
|
|
7
|
+
top: 50%;
|
|
8
|
+
right: auto;
|
|
9
|
+
bottom: auto;
|
|
10
|
+
left: 50%;
|
|
11
|
+
transform: translate(-50%, -50%);
|
|
12
|
+
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
max-width: calc(100% - ($space-modal-outside-gap * 2));
|
|
18
|
+
max-height: calc(100vh - ($space-modal-outside-gap * 2));
|
|
19
|
+
|
|
20
|
+
background-color: simple-var($sys-neutral-background1-level);
|
|
21
|
+
outline: none;
|
|
22
|
+
|
|
23
|
+
@each $size in $sizes {
|
|
24
|
+
&[data-size="#{$size}"] {
|
|
25
|
+
@include composite-var($modal, 'window', $size);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.headerElements {
|
|
31
|
+
@include composite-var($modal, 'header-elements', 'container');
|
|
32
|
+
@include composite-var($modal, 'header-elements', 'elements-layout');
|
|
33
|
+
|
|
34
|
+
position: absolute;
|
|
35
|
+
top: 0;
|
|
36
|
+
right: 0;
|
|
37
|
+
display: flex;
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { extractDataTestProps } from '@snack-uikit/utils';
|
|
2
|
+
|
|
3
|
+
export const getDataTestAttributes = (rest: Record<string, unknown>) => {
|
|
4
|
+
const dataTestProps = extractDataTestProps(rest);
|
|
5
|
+
|
|
6
|
+
return Object.keys(dataTestProps).reduce<Record<string, unknown>>((acc, key) => {
|
|
7
|
+
const newKey = key.replace('data-', '');
|
|
8
|
+
acc[newKey] = dataTestProps[key];
|
|
9
|
+
|
|
10
|
+
return acc;
|
|
11
|
+
}, {});
|
|
12
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export enum Size {
|
|
2
|
+
S = 's',
|
|
3
|
+
M = 'm',
|
|
4
|
+
L = 'l',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum Mode {
|
|
8
|
+
Regular = 'regular',
|
|
9
|
+
Aggressive = 'aggressive',
|
|
10
|
+
Forced = 'forced',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum Align {
|
|
14
|
+
Default = 'default',
|
|
15
|
+
Center = 'center',
|
|
16
|
+
Vertical = 'vertical',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export enum ContentAlign {
|
|
20
|
+
Default = 'default',
|
|
21
|
+
Center = 'center',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const TEST_IDS = {
|
|
25
|
+
overlay: 'modal__overlay',
|
|
26
|
+
closeButton: 'modal__close-button',
|
|
27
|
+
header: 'modal__header',
|
|
28
|
+
title: 'modal__title',
|
|
29
|
+
subtitle: 'modal__subtitle',
|
|
30
|
+
tooltip: 'modal__title-tooltip',
|
|
31
|
+
icon: 'modal__icon',
|
|
32
|
+
image: 'modal__image',
|
|
33
|
+
content: 'modal__body',
|
|
34
|
+
footer: 'modal__footer',
|
|
35
|
+
approveButton: 'modal__approve-button',
|
|
36
|
+
cancelButton: 'modal__cancel-button',
|
|
37
|
+
additionalButton: 'modal__additional-button',
|
|
38
|
+
disclaimer: 'modal__disclaimer',
|
|
39
|
+
disclaimerText: 'modal__disclaimer-text',
|
|
40
|
+
disclaimerLink: 'modal__disclaimer-link',
|
|
41
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Scroll } from '@snack-uikit/scroll';
|
|
5
|
+
import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
|
|
6
|
+
|
|
7
|
+
import { ContentAlign, TEST_IDS } from '../../constants';
|
|
8
|
+
import styles from './styles.module.scss';
|
|
9
|
+
|
|
10
|
+
export type ModalBodyProps = WithSupportProps<{
|
|
11
|
+
/** Содержимое модального окна */
|
|
12
|
+
content: ReactNode;
|
|
13
|
+
/** Выравнивание контента */
|
|
14
|
+
align?: ContentAlign;
|
|
15
|
+
className?: string;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export function ModalBody({ content, align = ContentAlign.Default, className, ...rest }: ModalBodyProps) {
|
|
19
|
+
return (
|
|
20
|
+
<Scroll
|
|
21
|
+
size={Scroll.sizes.M}
|
|
22
|
+
className={cn(styles.modalBody, className)}
|
|
23
|
+
{...extractSupportProps(rest)}
|
|
24
|
+
data-align={align}
|
|
25
|
+
data-test-id={TEST_IDS.content}
|
|
26
|
+
>
|
|
27
|
+
{content}
|
|
28
|
+
</Scroll>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ModalBody.aligns = ContentAlign;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Body';
|