@koslibs/components 1.3.0 → 1.5.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 +53 -0
- package/dist/components/Button/button.js +1 -0
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.js +1 -0
- package/dist/components/Input/index.module.js +25 -0
- package/dist/components/Input/index_module.css +312 -0
- package/dist/components/Input/input.d.ts +29 -0
- package/dist/components/Input/input.js +179 -0
- package/dist/components/Input/types.d.ts +115 -0
- package/dist/components/Input/types.js +0 -0
- package/dist/components/Input/vars.css +32 -0
- package/dist/components/Modal/colors.module.js +5 -0
- package/dist/components/Modal/colors_module.css +10 -0
- package/dist/components/Modal/components/content/index.d.ts +15 -0
- package/dist/components/Modal/components/content/index.js +19 -0
- package/dist/components/Modal/components/content/index.module.js +5 -0
- package/dist/components/Modal/components/content/index_module.css +12 -0
- package/dist/components/Modal/components/controls/index.d.ts +56 -0
- package/dist/components/Modal/components/controls/index.js +24 -0
- package/dist/components/Modal/components/controls/index.module.js +19 -0
- package/dist/components/Modal/components/controls/index_module.css +52 -0
- package/dist/components/Modal/components/footer/index.d.ts +15 -0
- package/dist/components/Modal/components/footer/index.js +19 -0
- package/dist/components/Modal/components/footer/index.module.js +5 -0
- package/dist/components/Modal/components/footer/index_module.css +9 -0
- package/dist/components/Modal/components/header/index.d.ts +43 -0
- package/dist/components/Modal/components/header/index.js +54 -0
- package/dist/components/Modal/components/header/index.module.js +8 -0
- package/dist/components/Modal/components/header/index_module.css +31 -0
- package/dist/components/Modal/components/modal-surface/index.d.ts +4 -0
- package/dist/components/Modal/components/modal-surface/index.js +81 -0
- package/dist/components/Modal/context.d.ts +6 -0
- package/dist/components/Modal/context.js +3 -0
- package/dist/components/Modal/index.d.ts +10 -0
- package/dist/components/Modal/index.js +5 -0
- package/dist/components/Modal/index.module.js +15 -0
- package/dist/components/Modal/index_module.css +46 -0
- package/dist/components/Modal/modal.d.ts +10 -0
- package/dist/components/Modal/modal.js +13 -0
- package/dist/components/Modal/types.d.ts +41 -0
- package/dist/components/Modal/types.js +0 -0
- package/dist/components/Modal/utils/lock-scroll.d.ts +1 -0
- package/dist/components/Modal/utils/lock-scroll.js +31 -0
- package/dist/components/Modal/vars.css +9 -0
- package/dist/components/Popover/popover.js +9 -7
- package/dist/components/Portal/index.d.ts +2 -0
- package/dist/components/Portal/index.js +1 -0
- package/dist/components/Portal/portal.d.ts +2 -0
- package/dist/components/Portal/portal.js +19 -0
- package/dist/components/Portal/types.d.ts +18 -0
- package/dist/components/Portal/types.js +0 -0
- package/dist/hooks/use-data-test-id.d.ts +5 -0
- package/dist/hooks/use-data-test-id.js +7 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/package.json +2 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { ChangeEvent, CSSProperties, InputHTMLAttributes, MouseEvent, ReactNode } from 'react';
|
|
2
|
+
export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'maxLength' | 'onChange' | 'children'> & {
|
|
3
|
+
/**
|
|
4
|
+
* Значение управляемого поля.
|
|
5
|
+
*/
|
|
6
|
+
value?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Начальное значение неуправляемого поля.
|
|
9
|
+
*/
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Максимальная длина ввода. Передаётся в нативный maxLength.
|
|
13
|
+
* Длина считается в кодовых единицах UTF-16, как в HTML input.
|
|
14
|
+
*/
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Показывает текущую длину и maxLength, например 13/64.
|
|
18
|
+
* Работает только при заданном неотрицательном целом maxLength.
|
|
19
|
+
* При inner расположен внутри поля, при outer — рядом с внешним label.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
showValueLength?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Вид поля: белый primary или светлый secondary.
|
|
25
|
+
* @default 'primary'
|
|
26
|
+
*/
|
|
27
|
+
view?: 'primary' | 'secondary';
|
|
28
|
+
/**
|
|
29
|
+
* Высота поля в пикселях без внешнего label и сообщения.
|
|
30
|
+
* @default 40
|
|
31
|
+
*/
|
|
32
|
+
size?: 40 | 48 | 56 | 64 | 72;
|
|
33
|
+
/**
|
|
34
|
+
* Растягивает компонент на ширину контейнера.
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
block?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Название поля. Связывается с input через htmlFor и id.
|
|
40
|
+
*/
|
|
41
|
+
label?: ReactNode;
|
|
42
|
+
/**
|
|
43
|
+
* Положение label: снаружи или внутри поля.
|
|
44
|
+
* Внутренний label поднимается при фокусе или заполнении поля.
|
|
45
|
+
* @default 'outer'
|
|
46
|
+
*/
|
|
47
|
+
labelView?: 'outer' | 'inner';
|
|
48
|
+
/**
|
|
49
|
+
* Подсказка под полем. При наличии сообщения об ошибке заменяется им.
|
|
50
|
+
*/
|
|
51
|
+
hint?: ReactNode;
|
|
52
|
+
/**
|
|
53
|
+
* Сообщение об ошибке или true для состояния ошибки без сообщения.
|
|
54
|
+
* Сообщение заменяет hint; без hint отображается только состояние ошибки.
|
|
55
|
+
*/
|
|
56
|
+
error?: ReactNode | boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Аддон слева от значения, например иконка поиска.
|
|
59
|
+
*/
|
|
60
|
+
leftAddon?: ReactNode;
|
|
61
|
+
/**
|
|
62
|
+
* Аддон справа от значения, например единица измерения или кнопка.
|
|
63
|
+
*/
|
|
64
|
+
rightAddon?: ReactNode;
|
|
65
|
+
/**
|
|
66
|
+
* Показывает кнопку очистки заполненного поля. Скрыта в disabled и readOnly.
|
|
67
|
+
* @default false
|
|
68
|
+
*/
|
|
69
|
+
clear?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Доступное имя кнопки очистки.
|
|
72
|
+
* @default 'Очистить поле'
|
|
73
|
+
*/
|
|
74
|
+
clearLabel?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Обработчик ввода с нативным событием React и текущим значением.
|
|
77
|
+
*/
|
|
78
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>, payload: {
|
|
79
|
+
value: string;
|
|
80
|
+
}) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Обработчик очистки. Не вызывает onChange.
|
|
83
|
+
* Для управляемого поля обновите value в этом обработчике.
|
|
84
|
+
*/
|
|
85
|
+
onClear?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Класс обёртки компонента.
|
|
88
|
+
*/
|
|
89
|
+
className?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Стили обёртки компонента, включая ширину и CSS-переменные.
|
|
92
|
+
*/
|
|
93
|
+
style?: CSSProperties;
|
|
94
|
+
/**
|
|
95
|
+
* Класс визуального поля с аддонами.
|
|
96
|
+
*/
|
|
97
|
+
fieldClassName?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Класс нативного input.
|
|
100
|
+
*/
|
|
101
|
+
inputClassName?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Класс label.
|
|
104
|
+
*/
|
|
105
|
+
labelClassName?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Класс подсказки или сообщения об ошибке.
|
|
108
|
+
*/
|
|
109
|
+
messageClassName?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Идентификатор нативного input. Части получают суффиксы -form-control,
|
|
112
|
+
* -field, -label, -hint, -error, -left-addon, -right-addon, -clear и -counter.
|
|
113
|
+
*/
|
|
114
|
+
dataTestId?: string;
|
|
115
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--input-primary-background-color: #fff;
|
|
3
|
+
--input-secondary-background-color: #f5f3ec;
|
|
4
|
+
--input-color: #1c1a18;
|
|
5
|
+
--input-placeholder-color: #736554;
|
|
6
|
+
--input-label-color: #736554;
|
|
7
|
+
--input-hint-color: #736554;
|
|
8
|
+
--input-icon-color: #736554;
|
|
9
|
+
--input-border-color: transparent;
|
|
10
|
+
--input-hover-border-color: #c8beaa;
|
|
11
|
+
--input-focus-border-color: #d95300;
|
|
12
|
+
--input-error-border-color: #ef5350;
|
|
13
|
+
--input-error-message-color: #b42318;
|
|
14
|
+
--input-disabled-background-color: #cfd1cb;
|
|
15
|
+
--input-disabled-color: #858981;
|
|
16
|
+
--input-read-only-border-color: #c8beaa;
|
|
17
|
+
--input-border-radius: 8px;
|
|
18
|
+
--input-gap: 8px;
|
|
19
|
+
--input-icon-size: 16px;
|
|
20
|
+
--input-width: 320px;
|
|
21
|
+
--input-font-size-40: 13px;
|
|
22
|
+
--input-font-size-48: 13px;
|
|
23
|
+
--input-font-size-56: 14px;
|
|
24
|
+
--input-font-size-64: 14px;
|
|
25
|
+
--input-font-size-72: 16px;
|
|
26
|
+
--input-padding-40: 12px;
|
|
27
|
+
--input-padding-48: 12px;
|
|
28
|
+
--input-padding-56: 16px;
|
|
29
|
+
--input-padding-64: 16px;
|
|
30
|
+
--input-padding-72: 20px;
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
export type ContentProps = HTMLAttributes<HTMLDivElement> & {
|
|
3
|
+
/**
|
|
4
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -content.
|
|
5
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
6
|
+
*/
|
|
7
|
+
dataTestId?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const Content: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
|
|
10
|
+
/**
|
|
11
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -content.
|
|
12
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
13
|
+
*/
|
|
14
|
+
dataTestId?: string;
|
|
15
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { forwardRef, useContext } from "react";
|
|
4
|
+
import { useDataTestId } from "../../../../hooks/use-data-test-id.js";
|
|
5
|
+
import { ModalContext } from "../../context.js";
|
|
6
|
+
import index_module from "./index.module.js";
|
|
7
|
+
const Content = /*#__PURE__*/ forwardRef(({ children, className, dataTestId, ...restProps }, ref)=>{
|
|
8
|
+
const { dataTestId: modalDataTestId } = useContext(ModalContext);
|
|
9
|
+
const getDataTestId = useDataTestId(dataTestId ?? modalDataTestId);
|
|
10
|
+
return /*#__PURE__*/ jsx("div", {
|
|
11
|
+
...restProps,
|
|
12
|
+
ref: ref,
|
|
13
|
+
className: classnames(index_module.component, className),
|
|
14
|
+
"data-test-id": getDataTestId('content'),
|
|
15
|
+
children: children
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
Content.displayName = 'ModalContent';
|
|
19
|
+
export { Content };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export type ControlsProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
/**
|
|
5
|
+
* Основное действие, отображается после дополнительного.
|
|
6
|
+
*/
|
|
7
|
+
primary?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Дополнительное действие.
|
|
10
|
+
*/
|
|
11
|
+
secondary?: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Выравнивание действий.
|
|
14
|
+
* В строковых раскладках block-кнопки делят свободную ширину поровну.
|
|
15
|
+
* В column кнопки занимают всю ширину и располагаются друг под другом.
|
|
16
|
+
* @default 'end'
|
|
17
|
+
*/
|
|
18
|
+
layout?: 'start' | 'end' | 'center' | 'space-between' | 'column';
|
|
19
|
+
/**
|
|
20
|
+
* Расстояние между действиями.
|
|
21
|
+
* @default 12
|
|
22
|
+
*/
|
|
23
|
+
gap?: 8 | 12 | 16 | 24;
|
|
24
|
+
/**
|
|
25
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -controls.
|
|
26
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
27
|
+
*/
|
|
28
|
+
dataTestId?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const Controls: React.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
|
|
31
|
+
/**
|
|
32
|
+
* Основное действие, отображается после дополнительного.
|
|
33
|
+
*/
|
|
34
|
+
primary?: ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* Дополнительное действие.
|
|
37
|
+
*/
|
|
38
|
+
secondary?: ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* Выравнивание действий.
|
|
41
|
+
* В строковых раскладках block-кнопки делят свободную ширину поровну.
|
|
42
|
+
* В column кнопки занимают всю ширину и располагаются друг под другом.
|
|
43
|
+
* @default 'end'
|
|
44
|
+
*/
|
|
45
|
+
layout?: "start" | "end" | "center" | "space-between" | "column";
|
|
46
|
+
/**
|
|
47
|
+
* Расстояние между действиями.
|
|
48
|
+
* @default 12
|
|
49
|
+
*/
|
|
50
|
+
gap?: 8 | 12 | 16 | 24;
|
|
51
|
+
/**
|
|
52
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -controls.
|
|
53
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
54
|
+
*/
|
|
55
|
+
dataTestId?: string;
|
|
56
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import react, { forwardRef, useContext } from "react";
|
|
4
|
+
import { useDataTestId } from "../../../../hooks/use-data-test-id.js";
|
|
5
|
+
import { ModalContext } from "../../context.js";
|
|
6
|
+
import index_module from "./index.module.js";
|
|
7
|
+
const Controls = /*#__PURE__*/ forwardRef(({ primary, secondary, layout = 'end', gap = 12, className, children, dataTestId, ...restProps }, ref)=>{
|
|
8
|
+
const { dataTestId: modalDataTestId } = useContext(ModalContext);
|
|
9
|
+
const getDataTestId = useDataTestId(dataTestId ?? modalDataTestId);
|
|
10
|
+
return /*#__PURE__*/ jsx("div", {
|
|
11
|
+
...restProps,
|
|
12
|
+
ref: ref,
|
|
13
|
+
className: classnames(index_module.component, index_module[layout], index_module[`gap-${gap}`], className),
|
|
14
|
+
"data-test-id": getDataTestId('controls'),
|
|
15
|
+
children: children ?? /*#__PURE__*/ jsxs(react.Fragment, {
|
|
16
|
+
children: [
|
|
17
|
+
secondary,
|
|
18
|
+
primary
|
|
19
|
+
]
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
Controls.displayName = 'ModalControls';
|
|
24
|
+
export { Controls };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "./index_module.css";
|
|
2
|
+
const index_module = {
|
|
3
|
+
component: "component-IPNCFT",
|
|
4
|
+
column: "column-dBeQXP",
|
|
5
|
+
start: "start-ouHlcU",
|
|
6
|
+
end: "end-TYAvEX",
|
|
7
|
+
center: "center-I4ALjs",
|
|
8
|
+
"space-between": "space-between-OxpVhA",
|
|
9
|
+
spaceBetween: "space-between-OxpVhA",
|
|
10
|
+
"gap-8": "gap-8-xwhlts",
|
|
11
|
+
gap8: "gap-8-xwhlts",
|
|
12
|
+
"gap-12": "gap-12-IFyQQK",
|
|
13
|
+
gap12: "gap-12-IFyQQK",
|
|
14
|
+
"gap-16": "gap-16-iZpxeN",
|
|
15
|
+
gap16: "gap-16-iZpxeN",
|
|
16
|
+
"gap-24": "gap-24-eEhuId",
|
|
17
|
+
gap24: "gap-24-eEhuId"
|
|
18
|
+
};
|
|
19
|
+
export default index_module;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.component-IPNCFT {
|
|
2
|
+
flex-wrap: wrap;
|
|
3
|
+
align-items: center;
|
|
4
|
+
width: 100%;
|
|
5
|
+
display: flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.component-IPNCFT:not(.column-dBeQXP) > button[data-block="true"] {
|
|
9
|
+
flex: 1 1 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.start-ouHlcU {
|
|
13
|
+
justify-content: flex-start;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.end-TYAvEX {
|
|
17
|
+
justify-content: flex-end;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.center-I4ALjs {
|
|
21
|
+
justify-content: center;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.space-between-OxpVhA {
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.column-dBeQXP {
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
align-items: stretch;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.column-dBeQXP > button {
|
|
34
|
+
width: 100%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.gap-8-xwhlts {
|
|
38
|
+
gap: var(--size-8, 8px);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.gap-12-IFyQQK {
|
|
42
|
+
gap: var(--size-12, 12px);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.gap-16-iZpxeN {
|
|
46
|
+
gap: var(--size-16, 16px);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.gap-24-eEhuId {
|
|
50
|
+
gap: var(--size-24, 24px);
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
export type FooterProps = HTMLAttributes<HTMLDivElement> & {
|
|
3
|
+
/**
|
|
4
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -footer.
|
|
5
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
6
|
+
*/
|
|
7
|
+
dataTestId?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const Footer: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
|
|
10
|
+
/**
|
|
11
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -footer.
|
|
12
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
13
|
+
*/
|
|
14
|
+
dataTestId?: string;
|
|
15
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { forwardRef, useContext } from "react";
|
|
4
|
+
import { useDataTestId } from "../../../../hooks/use-data-test-id.js";
|
|
5
|
+
import { ModalContext } from "../../context.js";
|
|
6
|
+
import index_module from "./index.module.js";
|
|
7
|
+
const Footer = /*#__PURE__*/ forwardRef(({ children, className, dataTestId, ...restProps }, ref)=>{
|
|
8
|
+
const { dataTestId: modalDataTestId } = useContext(ModalContext);
|
|
9
|
+
const getDataTestId = useDataTestId(dataTestId ?? modalDataTestId);
|
|
10
|
+
return /*#__PURE__*/ jsx("div", {
|
|
11
|
+
...restProps,
|
|
12
|
+
ref: ref,
|
|
13
|
+
className: classnames(index_module.component, className),
|
|
14
|
+
"data-test-id": getDataTestId('footer'),
|
|
15
|
+
children: children
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
Footer.displayName = 'ModalFooter';
|
|
19
|
+
export { Footer };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
export type HeaderProps = Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'onClose' | 'children'> & {
|
|
3
|
+
/**
|
|
4
|
+
* Заголовок окна.
|
|
5
|
+
*/
|
|
6
|
+
title?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Описание под заголовком.
|
|
9
|
+
*/
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Показывать кнопку закрытия.
|
|
13
|
+
* Кнопка вызывает onClose ближайшего Modal.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
hasCloser?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -header.
|
|
19
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
20
|
+
*/
|
|
21
|
+
dataTestId?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const Header: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "title" | "children" | "onClose"> & {
|
|
24
|
+
/**
|
|
25
|
+
* Заголовок окна.
|
|
26
|
+
*/
|
|
27
|
+
title?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Описание под заголовком.
|
|
30
|
+
*/
|
|
31
|
+
subtitle?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Показывать кнопку закрытия.
|
|
34
|
+
* Кнопка вызывает onClose ближайшего Modal.
|
|
35
|
+
* @default true
|
|
36
|
+
*/
|
|
37
|
+
hasCloser?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Базовый идентификатор для автоматизированных тестов с суффиксом -header.
|
|
40
|
+
* По умолчанию наследуется от ближайшего Modal; явно заданный имеет приоритет.
|
|
41
|
+
*/
|
|
42
|
+
dataTestId?: string;
|
|
43
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CrossHeavyMIcon } from "@koslibs/icons/CrossHeavyMIcon";
|
|
3
|
+
import classnames from "classnames";
|
|
4
|
+
import { forwardRef, useContext } from "react";
|
|
5
|
+
import { useDataTestId } from "../../../../hooks/use-data-test-id.js";
|
|
6
|
+
import { Button } from "../../../Button/index.js";
|
|
7
|
+
import { TypographyText } from "../../../Typography/index.js";
|
|
8
|
+
import { ModalContext } from "../../context.js";
|
|
9
|
+
import index_module from "./index.module.js";
|
|
10
|
+
const Header = /*#__PURE__*/ forwardRef(({ title, subtitle, hasCloser = true, className, dataTestId, ...restProps }, ref)=>{
|
|
11
|
+
const { onClose, dataTestId: modalDataTestId } = useContext(ModalContext);
|
|
12
|
+
const getDataTestId = useDataTestId(dataTestId ?? modalDataTestId);
|
|
13
|
+
const handleClick = ()=>{
|
|
14
|
+
onClose?.();
|
|
15
|
+
};
|
|
16
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
17
|
+
...restProps,
|
|
18
|
+
ref: ref,
|
|
19
|
+
className: classnames(index_module.component, className),
|
|
20
|
+
"data-test-id": getDataTestId('header'),
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ jsxs("div", {
|
|
23
|
+
className: index_module.content,
|
|
24
|
+
children: [
|
|
25
|
+
Boolean(title) && /*#__PURE__*/ jsx(TypographyText, {
|
|
26
|
+
size: 20,
|
|
27
|
+
weight: "semibold",
|
|
28
|
+
children: title
|
|
29
|
+
}),
|
|
30
|
+
Boolean(subtitle) && /*#__PURE__*/ jsx(TypographyText, {
|
|
31
|
+
size: 14,
|
|
32
|
+
color: "secondary",
|
|
33
|
+
children: subtitle
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
}),
|
|
37
|
+
hasCloser && /*#__PURE__*/ jsx(Button, {
|
|
38
|
+
size: 32,
|
|
39
|
+
view: "transparent",
|
|
40
|
+
className: index_module.closer,
|
|
41
|
+
labelClassName: index_module.closerLabel,
|
|
42
|
+
"aria-label": "Закрыть окно",
|
|
43
|
+
onClick: handleClick,
|
|
44
|
+
children: /*#__PURE__*/ jsx(CrossHeavyMIcon, {
|
|
45
|
+
width: 18,
|
|
46
|
+
height: 18,
|
|
47
|
+
"aria-hidden": "true"
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
Header.displayName = 'ModalHeader';
|
|
54
|
+
export { Header };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.component-sqEuYd {
|
|
2
|
+
justify-content: space-between;
|
|
3
|
+
align-items: flex-start;
|
|
4
|
+
gap: var(--size-24, 24px);
|
|
5
|
+
padding: var(--modal-padding) var(--modal-padding) 0;
|
|
6
|
+
flex-shrink: 0;
|
|
7
|
+
display: flex;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.content-cnwKDB {
|
|
11
|
+
gap: var(--size-4, 4px);
|
|
12
|
+
overflow-wrap: anywhere;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
flex: 1;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
display: flex;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.component-sqEuYd > .closer-odHjP5 {
|
|
20
|
+
flex-shrink: 0;
|
|
21
|
+
width: 48px;
|
|
22
|
+
min-width: 48px;
|
|
23
|
+
padding: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.closerLabel-r61y3R {
|
|
27
|
+
justify-content: center;
|
|
28
|
+
align-items: center;
|
|
29
|
+
display: flex;
|
|
30
|
+
}
|
|
31
|
+
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ModalProps } from '../../types.js';
|
|
2
|
+
type SurfaceProps = Omit<ModalProps, 'open' | 'getPortalContainer'>;
|
|
3
|
+
export declare const ModalSurface: import("react").ForwardRefExoticComponent<SurfaceProps & import("react").RefAttributes<HTMLDialogElement>>;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { forwardRef, useCallback, useLayoutEffect, useMemo, useRef } from "react";
|
|
4
|
+
import { ModalContext } from "../../context.js";
|
|
5
|
+
import { lockScroll } from "../../utils/lock-scroll.js";
|
|
6
|
+
import colors_module from "../../colors.module.js";
|
|
7
|
+
import index_module from "../../index.module.js";
|
|
8
|
+
const ModalSurface = /*#__PURE__*/ forwardRef(({ onClose, size = 720, escapeKeyDown = true, backdropClick = true, lockScroll: shouldLockScroll = true, className, dataTestId, children, onPointerDown, onClick, ...restProps }, ref)=>{
|
|
9
|
+
const dialogRef = useRef(null);
|
|
10
|
+
const backdropPointerDown = useRef(false);
|
|
11
|
+
const mounted = useRef(false);
|
|
12
|
+
const contextValue = useMemo(()=>({
|
|
13
|
+
onClose,
|
|
14
|
+
dataTestId
|
|
15
|
+
}), [
|
|
16
|
+
onClose,
|
|
17
|
+
dataTestId
|
|
18
|
+
]);
|
|
19
|
+
const mergedRef = useCallback((element)=>{
|
|
20
|
+
dialogRef.current = element;
|
|
21
|
+
if ('function' == typeof ref) ref(element);
|
|
22
|
+
else if (ref) ref.current = element;
|
|
23
|
+
}, [
|
|
24
|
+
ref
|
|
25
|
+
]);
|
|
26
|
+
useLayoutEffect(()=>{
|
|
27
|
+
const dialog = dialogRef.current;
|
|
28
|
+
if (!dialog) return;
|
|
29
|
+
mounted.current = true;
|
|
30
|
+
if (!dialog.open) dialog.showModal();
|
|
31
|
+
return ()=>{
|
|
32
|
+
mounted.current = false;
|
|
33
|
+
dialog.close();
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
useLayoutEffect(()=>{
|
|
37
|
+
const dialog = dialogRef.current;
|
|
38
|
+
if (dialog && shouldLockScroll) return lockScroll(dialog.ownerDocument);
|
|
39
|
+
}, [
|
|
40
|
+
shouldLockScroll
|
|
41
|
+
]);
|
|
42
|
+
const isBackdrop = (event)=>{
|
|
43
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
44
|
+
return event.target === event.currentTarget && (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom);
|
|
45
|
+
};
|
|
46
|
+
const handleCancel = (event)=>{
|
|
47
|
+
event.stopPropagation();
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
if (escapeKeyDown) onClose?.();
|
|
50
|
+
};
|
|
51
|
+
const handleClose = (event)=>{
|
|
52
|
+
event.stopPropagation();
|
|
53
|
+
if (mounted.current && !event.currentTarget.open) onClose?.();
|
|
54
|
+
};
|
|
55
|
+
const handlePointerDown = (event)=>{
|
|
56
|
+
onPointerDown?.(event);
|
|
57
|
+
backdropPointerDown.current = !event.defaultPrevented && isBackdrop(event);
|
|
58
|
+
};
|
|
59
|
+
const handleClick = (event)=>{
|
|
60
|
+
onClick?.(event);
|
|
61
|
+
const close = backdropPointerDown.current && isBackdrop(event);
|
|
62
|
+
backdropPointerDown.current = false;
|
|
63
|
+
if (backdropClick && close && !event.defaultPrevented) onClose?.();
|
|
64
|
+
};
|
|
65
|
+
return /*#__PURE__*/ jsx("dialog", {
|
|
66
|
+
...restProps,
|
|
67
|
+
ref: mergedRef,
|
|
68
|
+
className: classnames(index_module.component, colors_module.component, index_module[`size-${size}`], className),
|
|
69
|
+
"data-test-id": dataTestId,
|
|
70
|
+
onCancel: handleCancel,
|
|
71
|
+
onClose: handleClose,
|
|
72
|
+
onPointerDown: handlePointerDown,
|
|
73
|
+
onClick: handleClick,
|
|
74
|
+
children: /*#__PURE__*/ jsx(ModalContext.Provider, {
|
|
75
|
+
value: contextValue,
|
|
76
|
+
children: children
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
ModalSurface.displayName = 'ModalSurface';
|
|
81
|
+
export { ModalSurface };
|