@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +398 -0
  2. package/LICENSE +201 -0
  3. package/README.md +74 -0
  4. package/dist/components/Modal/Modal.d.ts +9 -0
  5. package/dist/components/Modal/Modal.js +30 -0
  6. package/dist/components/Modal/index.d.ts +1 -0
  7. package/dist/components/Modal/index.js +1 -0
  8. package/dist/components/Modal/styles.module.css +3 -0
  9. package/dist/components/Modal/types.d.ts +50 -0
  10. package/dist/components/Modal/types.js +1 -0
  11. package/dist/components/ModalCustom/ModalCustom.d.ts +40 -0
  12. package/dist/components/ModalCustom/ModalCustom.js +37 -0
  13. package/dist/components/ModalCustom/index.d.ts +1 -0
  14. package/dist/components/ModalCustom/index.js +1 -0
  15. package/dist/components/ModalCustom/styles.module.css +40 -0
  16. package/dist/components/ModalCustom/utils.d.ts +1 -0
  17. package/dist/components/ModalCustom/utils.js +9 -0
  18. package/dist/components/index.d.ts +2 -0
  19. package/dist/components/index.js +2 -0
  20. package/dist/constants.d.ts +37 -0
  21. package/dist/constants.js +41 -0
  22. package/dist/helperComponents/Body/Body.d.ts +14 -0
  23. package/dist/helperComponents/Body/Body.js +22 -0
  24. package/dist/helperComponents/Body/index.d.ts +1 -0
  25. package/dist/helperComponents/Body/index.js +1 -0
  26. package/dist/helperComponents/Body/styles.module.css +13 -0
  27. package/dist/helperComponents/ButtonClose/ButtonClose.d.ts +5 -0
  28. package/dist/helperComponents/ButtonClose/ButtonClose.js +7 -0
  29. package/dist/helperComponents/ButtonClose/index.d.ts +1 -0
  30. package/dist/helperComponents/ButtonClose/index.js +1 -0
  31. package/dist/helperComponents/ButtonClose/styles.module.css +35 -0
  32. package/dist/helperComponents/Footer/Footer.d.ts +16 -0
  33. package/dist/helperComponents/Footer/Footer.js +21 -0
  34. package/dist/helperComponents/Footer/index.d.ts +1 -0
  35. package/dist/helperComponents/Footer/index.js +1 -0
  36. package/dist/helperComponents/Footer/styles.module.css +39 -0
  37. package/dist/helperComponents/Header/Header.d.ts +23 -0
  38. package/dist/helperComponents/Header/Header.js +26 -0
  39. package/dist/helperComponents/Header/index.d.ts +2 -0
  40. package/dist/helperComponents/Header/index.js +2 -0
  41. package/dist/helperComponents/Header/styles.module.css +52 -0
  42. package/dist/helperComponents/Header/types.d.ts +4 -0
  43. package/dist/helperComponents/Header/types.js +1 -0
  44. package/dist/helperComponents/OverlayElement/OverlayElement.d.ts +7 -0
  45. package/dist/helperComponents/OverlayElement/OverlayElement.js +10 -0
  46. package/dist/helperComponents/OverlayElement/index.d.ts +1 -0
  47. package/dist/helperComponents/OverlayElement/index.js +1 -0
  48. package/dist/helperComponents/OverlayElement/styles.module.css +13 -0
  49. package/dist/helperComponents/index.d.ts +5 -0
  50. package/dist/helperComponents/index.js +5 -0
  51. package/dist/index.d.ts +1 -0
  52. package/dist/index.js +1 -0
  53. package/dist/utils.d.ts +33 -0
  54. package/dist/utils.js +60 -0
  55. package/package.json +51 -0
  56. package/src/components/Modal/Modal.tsx +104 -0
  57. package/src/components/Modal/index.ts +1 -0
  58. package/src/components/Modal/styles.module.scss +7 -0
  59. package/src/components/Modal/types.ts +54 -0
  60. package/src/components/ModalCustom/ModalCustom.tsx +96 -0
  61. package/src/components/ModalCustom/index.ts +1 -0
  62. package/src/components/ModalCustom/styles.module.scss +38 -0
  63. package/src/components/ModalCustom/utils.ts +12 -0
  64. package/src/components/index.ts +2 -0
  65. package/src/constants.ts +41 -0
  66. package/src/helperComponents/Body/Body.tsx +32 -0
  67. package/src/helperComponents/Body/index.ts +1 -0
  68. package/src/helperComponents/Body/styles.module.scss +14 -0
  69. package/src/helperComponents/ButtonClose/ButtonClose.tsx +21 -0
  70. package/src/helperComponents/ButtonClose/index.ts +1 -0
  71. package/src/helperComponents/ButtonClose/styles.module.scss +44 -0
  72. package/src/helperComponents/Footer/Footer.tsx +38 -0
  73. package/src/helperComponents/Footer/index.ts +1 -0
  74. package/src/helperComponents/Footer/styles.module.scss +44 -0
  75. package/src/helperComponents/Header/Header.tsx +81 -0
  76. package/src/helperComponents/Header/index.ts +2 -0
  77. package/src/helperComponents/Header/styles.module.scss +52 -0
  78. package/src/helperComponents/Header/types.ts +4 -0
  79. package/src/helperComponents/OverlayElement/OverlayElement.tsx +30 -0
  80. package/src/helperComponents/OverlayElement/index.ts +1 -0
  81. package/src/helperComponents/OverlayElement/styles.module.scss +17 -0
  82. package/src/helperComponents/index.ts +5 -0
  83. package/src/index.ts +1 -0
  84. package/src/utils.ts +69 -0
@@ -0,0 +1,14 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-modal';
2
+
3
+ .modalBody {
4
+ @include composite-var($modal-body);
5
+
6
+ flex: 1 1 auto;
7
+ box-sizing: border-box;
8
+ min-height: $dimension-2m;
9
+ color: $sys-neutral-text-main;
10
+
11
+ &[data-align='center'] {
12
+ text-align: center;
13
+ }
14
+ }
@@ -0,0 +1,21 @@
1
+ import { CrossSVG } from '@snack-uikit/icons';
2
+
3
+ import { TEST_IDS } from '../../constants';
4
+ import styles from './styles.module.scss';
5
+
6
+ type ButtonCloseProps = {
7
+ onClick(): void;
8
+ };
9
+
10
+ export function ButtonClose({ onClick }: ButtonCloseProps) {
11
+ return (
12
+ <button
13
+ className={styles.buttonClose}
14
+ onClick={onClick}
15
+ aria-label='close modal'
16
+ data-test-id={TEST_IDS.closeButton}
17
+ >
18
+ <CrossSVG />
19
+ </button>
20
+ );
21
+ }
@@ -0,0 +1 @@
1
+ export * from './ButtonClose';
@@ -0,0 +1,44 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-modal';
2
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
+
4
+ .buttonClose {
5
+ @include composite-var($modal-button-close);
6
+
7
+ cursor: pointer;
8
+
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+
13
+ box-sizing: border-box;
14
+ margin: 0;
15
+ padding: 0;
16
+
17
+ color: simple-var($sys-neutral-text-support);
18
+
19
+ background-color: simple-var($sys-neutral-decor-default);
20
+ border: 0 solid transparent;
21
+ outline: 0;
22
+ outline-offset: $spacing-state-focus-offset;
23
+
24
+ &:hover, &:focus-visible {
25
+ color: simple-var($sys-neutral-text-main);
26
+ background-color: simple-var($sys-neutral-decor-hovered);
27
+ }
28
+
29
+ &:focus-visible {
30
+ @include outline-var($container-focused-s);
31
+
32
+ outline-color: $sys-available-complementary;
33
+ }
34
+
35
+ &:active {
36
+ color: simple-var($sys-neutral-text-main);
37
+ background-color: simple-var($sys-neutral-decor-activated);
38
+ }
39
+
40
+ svg {
41
+ width: $icon-s !important; /* stylelint-disable-line declaration-no-important */
42
+ height: $icon-s !important; /* stylelint-disable-line declaration-no-important */
43
+ }
44
+ }
@@ -0,0 +1,38 @@
1
+ import cn from 'classnames';
2
+ import { ReactNode } from 'react';
3
+
4
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
5
+
6
+ import { Align, TEST_IDS } from '../../constants';
7
+ import styles from './styles.module.scss';
8
+
9
+ export type ModalFooterProps = WithSupportProps<{
10
+ /** Параметр для передачи кнопок */
11
+ actions: ReactNode;
12
+ /** Параметр для небольшого текста под кнопками */
13
+ disclaimer?: ReactNode;
14
+ /** Выравнивание контента */
15
+ align?: Align;
16
+ className?: string;
17
+ }>;
18
+
19
+ export function ModalFooter({ actions, disclaimer, align = Align.Default, className, ...rest }: ModalFooterProps) {
20
+ return (
21
+ <div
22
+ data-align={align}
23
+ className={cn(styles.footer, className)}
24
+ {...extractSupportProps(rest)}
25
+ data-test-id={TEST_IDS.footer}
26
+ >
27
+ <div className={styles.footerActions}>{actions}</div>
28
+
29
+ {disclaimer && (
30
+ <div className={styles.footerDisclaimer} data-test-id={TEST_IDS.disclaimer}>
31
+ {disclaimer}
32
+ </div>
33
+ )}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ ModalFooter.aligns = Align;
@@ -0,0 +1 @@
1
+ export * from './Footer';
@@ -0,0 +1,44 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-modal';
2
+
3
+ .footerActions {
4
+ @include composite-var($modal, 'action-row');
5
+
6
+ display: flex;
7
+ flex-direction: row-reverse;
8
+ flex-wrap: wrap;
9
+ align-items: center;
10
+ }
11
+
12
+ .footerDisclaimer {
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: flex-end;
16
+ color: simple-var($sys-neutral-text-disabled);
17
+ }
18
+
19
+ .footer {
20
+ @include composite-var($modal, 'footer');
21
+
22
+ display: flex;
23
+ flex-direction: column;
24
+
25
+ &[data-align='center'] {
26
+ align-items: center;
27
+
28
+ .footerDisclaimer {
29
+ align-items: center;
30
+ }
31
+ }
32
+
33
+ &[data-align='vertical'] {
34
+ align-items: center;
35
+
36
+ .footerActions {
37
+ width: 100%;
38
+ }
39
+
40
+ .footerDisclaimer {
41
+ align-items: center;
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,81 @@
1
+ import cn from 'classnames';
2
+ import { ReactNode } from 'react';
3
+
4
+ import { IconPredefined, IconPredefinedProps } from '@snack-uikit/icon-predefined';
5
+ import { QuestionTooltip, QuestionTooltipProps } from '@snack-uikit/tooltip';
6
+ import { Typography } from '@snack-uikit/typography';
7
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
8
+
9
+ import { ContentAlign, TEST_IDS } from '../../constants';
10
+ import { isPictureImage } from '../../utils';
11
+ import styles from './styles.module.scss';
12
+ import { ModalHeaderImage } from './types';
13
+
14
+ export type ModalHeaderProps = WithSupportProps<{
15
+ /** Заголовок модального окна */
16
+ title: ReactNode;
17
+ /** Тултип для заголовка */
18
+ titleTooltip?: QuestionTooltipProps['tip'];
19
+ /** Подзаголовок */
20
+ subtitle?: ReactNode;
21
+ /** Можно передать иконку из пакета `@snack-uikit/icon-predefined` или путь к картинке и атрибут `alt` */
22
+ picture?: IconPredefinedProps['icon'] | ModalHeaderImage;
23
+ /** Выравнивание контента */
24
+ align?: ContentAlign;
25
+ className?: string;
26
+ }>;
27
+
28
+ export function ModalHeader({
29
+ title,
30
+ titleTooltip,
31
+ subtitle,
32
+ align = ContentAlign.Default,
33
+ picture,
34
+ className,
35
+ ...rest
36
+ }: ModalHeaderProps) {
37
+ return (
38
+ <div className={cn(styles.header, className)} {...extractSupportProps(rest)} data-test-id={TEST_IDS.header}>
39
+ {picture &&
40
+ (isPictureImage(picture) ? (
41
+ <img src={picture.src} alt={picture.alt} className={styles.image} data-test-id={TEST_IDS.image} />
42
+ ) : (
43
+ <div className={styles.icon} data-test-id={TEST_IDS.icon}>
44
+ <IconPredefined icon={picture} size={IconPredefined.sizes.L} />
45
+ </div>
46
+ ))}
47
+
48
+ <div className={styles.headlineLayout} data-align={align}>
49
+ <div className={styles.headline}>
50
+ <Typography
51
+ family={Typography.families.Sans}
52
+ role={Typography.roles.Headline}
53
+ size={Typography.sizes.S}
54
+ className={styles.title}
55
+ data-test-id={TEST_IDS.title}
56
+ >
57
+ {title}
58
+ </Typography>
59
+
60
+ {titleTooltip && (
61
+ <QuestionTooltip tip={titleTooltip} size={QuestionTooltip.sizes.S} data-test-id={TEST_IDS.tooltip} />
62
+ )}
63
+ </div>
64
+
65
+ {subtitle && (
66
+ <Typography
67
+ family={Typography.families.Sans}
68
+ role={Typography.roles.Body}
69
+ size={Typography.sizes.M}
70
+ className={styles.subtitle}
71
+ data-test-id={TEST_IDS.subtitle}
72
+ >
73
+ {subtitle}
74
+ </Typography>
75
+ )}
76
+ </div>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ ModalHeader.aligns = ContentAlign;
@@ -0,0 +1,2 @@
1
+ export * from './Header';
2
+ export * from './types';
@@ -0,0 +1,52 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-modal';
2
+
3
+ $aligns: 'default', 'center';
4
+
5
+ .header {
6
+ /* stub for proper typings */
7
+ }
8
+
9
+ .image {
10
+ @include composite-var($modal, 'image');
11
+
12
+ display: block;
13
+ width: 100%;
14
+ object-fit: cover;
15
+ }
16
+
17
+ .icon {
18
+ @include composite-var($modal, 'icon-decor');
19
+
20
+ display: flex;
21
+ justify-content: center;
22
+ }
23
+
24
+ .headlineLayout {
25
+ @each $align in $aligns {
26
+ &[data-align="#{$align}"] {
27
+ @include composite-var($modal, 'headline-layout', $align);
28
+
29
+ display: flex;
30
+ flex-direction: column;
31
+
32
+ @if ($align == 'center') {
33
+ align-items: center;
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ .headline {
40
+ @include composite-var($modal-headline);
41
+
42
+ display: flex;
43
+ align-items: center;
44
+ }
45
+
46
+ .title {
47
+ display: grid;
48
+ }
49
+
50
+ .subtitle {
51
+ color: $sys-neutral-text-support;
52
+ }
@@ -0,0 +1,4 @@
1
+ export type ModalHeaderImage = {
2
+ src: string;
3
+ alt: string;
4
+ };
@@ -0,0 +1,30 @@
1
+ import { MouseEventHandler, ReactElement } from 'react';
2
+
3
+ import { TEST_IDS } from '../../constants';
4
+ import styles from './styles.module.scss';
5
+
6
+ export type OverlayElementProps = {
7
+ onClose(): void;
8
+ content: ReactElement;
9
+ blur?: boolean;
10
+ };
11
+
12
+ export function OverlayElement({ onClose, content, blur = false }: OverlayElementProps) {
13
+ const handleClick: MouseEventHandler = e => {
14
+ e.stopPropagation();
15
+ onClose();
16
+ };
17
+
18
+ return (
19
+ <>
20
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions,jsx-a11y/no-static-element-interactions */}
21
+ <div
22
+ className={styles.modalOverlay}
23
+ data-blur={blur || undefined}
24
+ onClick={handleClick}
25
+ data-test-id={TEST_IDS.overlay}
26
+ />
27
+ {content}
28
+ </>
29
+ );
30
+ }
@@ -0,0 +1 @@
1
+ export * from './OverlayElement';
@@ -0,0 +1,17 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-modal';
2
+
3
+ .modalOverlay {
4
+ position: fixed;
5
+ top: 0;
6
+ left: 0;
7
+
8
+ box-sizing: border-box;
9
+ width: 100%;
10
+ height: 100%;
11
+
12
+ background: simple-var($sys-blackout);
13
+
14
+ &[data-blur] {
15
+ backdrop-filter: blur(simple-var($background-blur-background-blur));
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ export * from './OverlayElement';
2
+ export * from './ButtonClose';
3
+ export * from './Header';
4
+ export * from './Body';
5
+ export * from './Footer';
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './components';
package/src/utils.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { ButtonFilled, ButtonOutline, ButtonSimple } from '@snack-uikit/button';
2
+
3
+ import { Align, ContentAlign, Size } from './constants';
4
+ import { ModalHeaderImage, ModalHeaderProps } from './helperComponents';
5
+
6
+ const MAP_ALIGNS = {
7
+ [Align.Default]: {
8
+ header: ContentAlign.Default,
9
+ body: ContentAlign.Default,
10
+ footer: Align.Default,
11
+ },
12
+ [Align.Center]: {
13
+ header: ContentAlign.Center,
14
+ body: ContentAlign.Center,
15
+ footer: Align.Center,
16
+ },
17
+ [Align.Vertical]: {
18
+ header: ContentAlign.Center,
19
+ body: ContentAlign.Center,
20
+ footer: Align.Vertical,
21
+ },
22
+ };
23
+
24
+ export function getAlignProps({ align, size }: { align: Align; size: Size }) {
25
+ if (size === Size.L) {
26
+ return MAP_ALIGNS[Align.Default];
27
+ }
28
+
29
+ if (size === Size.M && align === Align.Vertical) {
30
+ return MAP_ALIGNS[Align.Default];
31
+ }
32
+
33
+ return MAP_ALIGNS[align];
34
+ }
35
+
36
+ export function getButtonsSizes({ align, size }: { align: Align; size: Size }) {
37
+ switch (true) {
38
+ case align === Align.Vertical && size === Size.S:
39
+ return {
40
+ filled: ButtonFilled.sizes.L,
41
+ outline: ButtonOutline.sizes.L,
42
+ simple: ButtonSimple.sizes.L,
43
+ };
44
+ case [Align.Default, Align.Center].includes(align):
45
+ default:
46
+ return {
47
+ filled: ButtonFilled.sizes.M,
48
+ outline: ButtonOutline.sizes.M,
49
+ light: ButtonSimple.sizes.M,
50
+ };
51
+ }
52
+ }
53
+
54
+ export function isPictureImage(picture: ModalHeaderProps['picture']): picture is ModalHeaderImage {
55
+ if (!picture) return false;
56
+
57
+ return 'src' in picture;
58
+ }
59
+
60
+ export function getPicture({ size, picture }: { size: Size; picture: ModalHeaderProps['picture'] }) {
61
+ switch (size) {
62
+ case Size.S:
63
+ return picture;
64
+ case Size.M:
65
+ case Size.L:
66
+ default:
67
+ return isPictureImage(picture) ? picture : undefined;
68
+ }
69
+ }