@koslibs/components 1.12.0 → 1.14.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 +20 -0
- package/dist/components/Button/index_module.css +4 -0
- package/dist/components/Input/index_module.css +1 -0
- package/dist/components/Modal/components/modal-surface/index.js +1 -1
- package/dist/components/Select/index_module.css +3 -2
- package/dist/components/Sidepanel/components/content/index.d.ts +13 -0
- package/dist/components/Sidepanel/components/content/index.js +21 -0
- package/dist/components/Sidepanel/components/content/index.module.js +5 -0
- package/dist/components/Sidepanel/components/content/index_module.css +14 -0
- package/dist/components/Sidepanel/components/footer/index.d.ts +66 -0
- package/dist/components/Sidepanel/components/footer/index.js +25 -0
- package/dist/components/Sidepanel/components/footer/index.module.js +19 -0
- package/dist/components/Sidepanel/components/footer/index_module.css +57 -0
- package/dist/components/Sidepanel/components/header/index.d.ts +33 -0
- package/dist/components/Sidepanel/components/header/index.js +45 -0
- package/dist/components/Sidepanel/components/header/index.module.js +8 -0
- package/dist/components/Sidepanel/components/header/index_module.css +32 -0
- package/dist/components/Sidepanel/components/sidepanel-surface/index.d.ts +4 -0
- package/dist/components/Sidepanel/components/sidepanel-surface/index.js +92 -0
- package/dist/components/Sidepanel/context.d.ts +6 -0
- package/dist/components/Sidepanel/context.js +3 -0
- package/dist/components/Sidepanel/hooks/use-dialog-transition.d.ts +5 -0
- package/dist/components/Sidepanel/hooks/use-dialog-transition.js +83 -0
- package/dist/components/Sidepanel/index.d.ts +8 -0
- package/dist/components/Sidepanel/index.js +4 -0
- package/dist/components/Sidepanel/index.module.js +8 -0
- package/dist/components/Sidepanel/index_module.css +68 -0
- package/dist/components/Sidepanel/scrollbar.module.js +5 -0
- package/dist/components/Sidepanel/scrollbar_module.css +41 -0
- package/dist/components/Sidepanel/sidepanel.d.ts +9 -0
- package/dist/components/Sidepanel/sidepanel.js +28 -0
- package/dist/components/Sidepanel/types.d.ts +36 -0
- package/dist/components/Sidepanel/types.js +0 -0
- package/dist/components/Sidepanel/vars.css +12 -0
- package/dist/components/Tabs/index_module.css +1 -1
- package/dist/components/Tabs/tabs.d.ts +0 -1
- package/dist/components/Tabs/tabs.js +4 -3
- package/dist/components/Tabs/types.d.ts +0 -4
- package/dist/components/Textarea/index_module.css +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utils/lock-scroll.d.ts +1 -0
- package/dist/utils/lock-scroll.js +49 -0
- package/package.json +1 -1
- package/dist/components/Modal/utils/lock-scroll.d.ts +0 -1
- package/dist/components/Modal/utils/lock-scroll.js +0 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @koslibs/components
|
|
2
2
|
|
|
3
|
+
## 1.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#16](https://github.com/koslibs/components/pull/16) [`45a76bb`](https://github.com/koslibs/components/commit/45a76bb4b230b95762017468d9497c37d63e2855) Thanks [@holypower777](https://github.com/holypower777)! - Tabs больше не скрывает ряд, когда все вкладки disabled: заголовки и счётчики остаются видимыми без активного индикатора. Удалён проп emptyState: уберите его из вызовов Tabs и отображайте пустое состояние данных в приложении. Без дочерних Tab компонент возвращает null. Добавлены регрессионные тесты и пример в Storybook.
|
|
8
|
+
|
|
9
|
+
## 1.13.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#15](https://github.com/koslibs/components/pull/15) [`061457d`](https://github.com/koslibs/components/commit/061457d29c63e8fc4ad1929d7c7ca2b9fffae075) Thanks [@holypower777](https://github.com/holypower777)! - Установлен курсор `not-allowed` для disabled-состояний Button и Tab. У Input, Textarea и Select курсор применяется ко всему компоненту, включая внешние подписи и подсказки. У Select disabled имеет приоритет над readOnly.
|
|
14
|
+
|
|
15
|
+
Добавлен Sidepanel: панель шириной 500 px с выездом и уходом справа, бекдропом Modal, Header с необязательным заголовком и hasCloser, прокручиваемым Content и Footer только для Button. Поддержаны строковая и вертикальная раскладки кнопок, управляемое закрытие, удержание и возврат фокуса, Portal и блокировка прокрутки до завершения анимации. Добавлены Storybook, типы, тесты и отдельный импорт.
|
|
16
|
+
|
|
17
|
+
Общая блокировка прокрутки Modal и Sidepanel вынесена в src/utils/lock-scroll.ts. Она сохраняет положение страницы при временном снятии scrollbar-gutter, чтобы панель доходила до края окна.
|
|
18
|
+
|
|
19
|
+
Пустой Header Sidepanel не занимает места. Скроллбар показывает только слайдер без стрелок и фона. Footer поддерживает sticky с дефолтом true; при false кнопки прокручиваются вместе с содержимым. Переключение режима выполнено через классы CSS Modules без служебного data-атрибута.
|
|
20
|
+
|
|
21
|
+
Footer Sidepanel принимает компоненты ComponentType<ButtonProps>: обязательный primary справа и необязательный secondary слева. Внешний вид кнопок задаётся снаружи. gap принимает 12/16/24/32 с дефолтом 12; layout — start/end/center/space-between/column с дефолтом space-between.
|
|
22
|
+
|
|
3
23
|
## 1.12.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import classnames from "classnames";
|
|
3
3
|
import { forwardRef, useCallback, useLayoutEffect, useMemo, useRef } from "react";
|
|
4
|
+
import { lockScroll } from "../../../../utils/lock-scroll.js";
|
|
4
5
|
import { ModalContext } from "../../context.js";
|
|
5
|
-
import { lockScroll } from "../../utils/lock-scroll.js";
|
|
6
6
|
import colors_module from "../../colors.module.js";
|
|
7
7
|
import index_module from "../../index.module.js";
|
|
8
8
|
const ModalSurface = /*#__PURE__*/ forwardRef(({ onClose, size = 720, escapeKeyDown = true, backdropClick = true, lockScroll: shouldLockScroll = true, className, dataTestId, children, onPointerDown, onClick, ...restProps }, ref)=>{
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
--field-icon-color: var(--select-disabled-color);
|
|
42
42
|
--field-placeholder-color: var(--select-disabled-color);
|
|
43
43
|
--field-border: transparent;
|
|
44
|
+
cursor: not-allowed;
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -204,11 +205,11 @@
|
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
.component-cM8DTg.disabled-fZIeN_ .trigger-nKdmww {
|
|
208
|
+
.component-cM8DTg.disabled-fZIeN_ .trigger-nKdmww, .component-cM8DTg.disabled-fZIeN_ .label-rrakc7 {
|
|
208
209
|
cursor: not-allowed;
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
.component-cM8DTg.readOnly-zwkIeK .trigger-nKdmww {
|
|
212
|
+
.component-cM8DTg.readOnly-zwkIeK:not(.disabled-fZIeN_) .trigger-nKdmww {
|
|
212
213
|
cursor: default;
|
|
213
214
|
}
|
|
214
215
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
export type ContentProps = HTMLAttributes<HTMLDivElement> & {
|
|
3
|
+
/**
|
|
4
|
+
* Базовый идентификатор части -content. По умолчанию наследуется от Sidepanel.
|
|
5
|
+
*/
|
|
6
|
+
dataTestId?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const Content: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
|
|
9
|
+
/**
|
|
10
|
+
* Базовый идентификатор части -content. По умолчанию наследуется от Sidepanel.
|
|
11
|
+
*/
|
|
12
|
+
dataTestId?: string;
|
|
13
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
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 { SidepanelContext } from "../../context.js";
|
|
6
|
+
import index_module from "../../index.module.js";
|
|
7
|
+
import scrollbar_module from "../../scrollbar.module.js";
|
|
8
|
+
import index_module_0 from "./index.module.js";
|
|
9
|
+
const Content = /*#__PURE__*/ forwardRef(({ className, dataTestId, children, ...restProps }, ref)=>{
|
|
10
|
+
const { dataTestId: panelDataTestId } = useContext(SidepanelContext);
|
|
11
|
+
const getDataTestId = useDataTestId(dataTestId ?? panelDataTestId);
|
|
12
|
+
return /*#__PURE__*/ jsx("div", {
|
|
13
|
+
...restProps,
|
|
14
|
+
ref: ref,
|
|
15
|
+
className: classnames(index_module_0.component, index_module.content, scrollbar_module.scrollbar, className),
|
|
16
|
+
"data-test-id": getDataTestId('content'),
|
|
17
|
+
children: children
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
Content.displayName = 'SidepanelContent';
|
|
21
|
+
export { Content };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.component-vYcjBo {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
gap: var(--size-16);
|
|
4
|
+
min-width: 0;
|
|
5
|
+
min-height: 0;
|
|
6
|
+
padding: var(--sidepanel-padding);
|
|
7
|
+
overscroll-behavior: contain;
|
|
8
|
+
overflow-wrap: anywhere;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
flex: auto;
|
|
11
|
+
display: flex;
|
|
12
|
+
overflow: auto;
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ComponentType, type HTMLAttributes } from 'react';
|
|
2
|
+
import type { ButtonProps } from '../../../Button/index.js';
|
|
3
|
+
export type FooterProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'dangerouslySetInnerHTML'> & {
|
|
4
|
+
/**
|
|
5
|
+
* Компонент основной кнопки. Обязателен; располагается после secondary (справа в строке).
|
|
6
|
+
* Внешний вид и обработчики задаются внутри переданного компонента.
|
|
7
|
+
*/
|
|
8
|
+
primary: ComponentType<ButtonProps>;
|
|
9
|
+
/**
|
|
10
|
+
* Компонент вторичной кнопки. Располагается перед primary (слева в строке).
|
|
11
|
+
* Не выводится, если проп не передан.
|
|
12
|
+
*/
|
|
13
|
+
secondary?: ComponentType<ButtonProps>;
|
|
14
|
+
/**
|
|
15
|
+
* Минимальное расстояние между кнопками в пикселях из общей шкалы отступов.
|
|
16
|
+
* @default 12
|
|
17
|
+
*/
|
|
18
|
+
gap?: 12 | 16 | 24 | 32;
|
|
19
|
+
/**
|
|
20
|
+
* Оставляет кнопки видимыми при прокрутке. При false Footer прокручивается с Content.
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
sticky?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Выравнивание кнопок. В space-between кнопки находятся у противоположных краёв,
|
|
26
|
+
* единственная primary — справа. В column secondary находится над primary.
|
|
27
|
+
* @default 'space-between'
|
|
28
|
+
*/
|
|
29
|
+
layout?: 'start' | 'end' | 'center' | 'space-between' | 'column';
|
|
30
|
+
/**
|
|
31
|
+
* Базовый идентификатор части -footer. По умолчанию наследуется от Sidepanel.
|
|
32
|
+
*/
|
|
33
|
+
dataTestId?: string;
|
|
34
|
+
};
|
|
35
|
+
export declare const Footer: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "children" | "dangerouslySetInnerHTML"> & {
|
|
36
|
+
/**
|
|
37
|
+
* Компонент основной кнопки. Обязателен; располагается после secondary (справа в строке).
|
|
38
|
+
* Внешний вид и обработчики задаются внутри переданного компонента.
|
|
39
|
+
*/
|
|
40
|
+
primary: ComponentType<ButtonProps>;
|
|
41
|
+
/**
|
|
42
|
+
* Компонент вторичной кнопки. Располагается перед primary (слева в строке).
|
|
43
|
+
* Не выводится, если проп не передан.
|
|
44
|
+
*/
|
|
45
|
+
secondary?: ComponentType<ButtonProps>;
|
|
46
|
+
/**
|
|
47
|
+
* Минимальное расстояние между кнопками в пикселях из общей шкалы отступов.
|
|
48
|
+
* @default 12
|
|
49
|
+
*/
|
|
50
|
+
gap?: 12 | 16 | 24 | 32;
|
|
51
|
+
/**
|
|
52
|
+
* Оставляет кнопки видимыми при прокрутке. При false Footer прокручивается с Content.
|
|
53
|
+
* @default true
|
|
54
|
+
*/
|
|
55
|
+
sticky?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Выравнивание кнопок. В space-between кнопки находятся у противоположных краёв,
|
|
58
|
+
* единственная primary — справа. В column secondary находится над primary.
|
|
59
|
+
* @default 'space-between'
|
|
60
|
+
*/
|
|
61
|
+
layout?: "start" | "end" | "center" | "space-between" | "column";
|
|
62
|
+
/**
|
|
63
|
+
* Базовый идентификатор части -footer. По умолчанию наследуется от Sidepanel.
|
|
64
|
+
*/
|
|
65
|
+
dataTestId?: string;
|
|
66
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx, jsxs } 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 { SidepanelContext } from "../../context.js";
|
|
6
|
+
import index_module from "../../index.module.js";
|
|
7
|
+
import index_module_0 from "./index.module.js";
|
|
8
|
+
const Footer = /*#__PURE__*/ forwardRef(({ primary: Primary, secondary: Secondary, gap = 12, sticky = true, layout = 'space-between', className, dataTestId, ...restProps }, ref)=>{
|
|
9
|
+
const { dataTestId: panelDataTestId } = useContext(SidepanelContext);
|
|
10
|
+
const getDataTestId = useDataTestId(dataTestId ?? panelDataTestId);
|
|
11
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
12
|
+
...restProps,
|
|
13
|
+
ref: ref,
|
|
14
|
+
className: classnames(index_module_0.component, index_module_0[layout], index_module_0[`gap-${gap}`], {
|
|
15
|
+
[index_module.nonStickyFooter]: !sticky
|
|
16
|
+
}, className),
|
|
17
|
+
"data-test-id": getDataTestId('footer'),
|
|
18
|
+
children: [
|
|
19
|
+
Secondary && /*#__PURE__*/ jsx(Secondary, {}),
|
|
20
|
+
/*#__PURE__*/ jsx(Primary, {})
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
Footer.displayName = 'SidepanelFooter';
|
|
25
|
+
export { Footer };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "./index_module.css";
|
|
2
|
+
const index_module = {
|
|
3
|
+
component: "component-skRGIM",
|
|
4
|
+
column: "column-eFrOSV",
|
|
5
|
+
start: "start-AsGQua",
|
|
6
|
+
end: "end-GUmizA",
|
|
7
|
+
center: "center-ZCtISA",
|
|
8
|
+
"space-between": "space-between-JgT7jT",
|
|
9
|
+
spaceBetween: "space-between-JgT7jT",
|
|
10
|
+
"gap-12": "gap-12-vpXLHP",
|
|
11
|
+
gap12: "gap-12-vpXLHP",
|
|
12
|
+
"gap-16": "gap-16-YnuaQb",
|
|
13
|
+
gap16: "gap-16-YnuaQb",
|
|
14
|
+
"gap-24": "gap-24-Z4lCD9",
|
|
15
|
+
gap24: "gap-24-Z4lCD9",
|
|
16
|
+
"gap-32": "gap-32-dJPD9x",
|
|
17
|
+
gap32: "gap-32-dJPD9x"
|
|
18
|
+
};
|
|
19
|
+
export default index_module;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.component-skRGIM {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
padding: var(--sidepanel-padding);
|
|
4
|
+
flex-shrink: 0;
|
|
5
|
+
align-items: center;
|
|
6
|
+
display: flex;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.component-skRGIM:not(.column-eFrOSV) > button[data-block="true"] {
|
|
10
|
+
flex: 1 1 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.start-AsGQua {
|
|
14
|
+
justify-content: flex-start;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.end-GUmizA {
|
|
18
|
+
justify-content: flex-end;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.center-ZCtISA {
|
|
22
|
+
justify-content: center;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.space-between-JgT7jT {
|
|
26
|
+
justify-content: space-between;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.space-between-JgT7jT > :only-child {
|
|
30
|
+
margin-left: auto;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.column-eFrOSV {
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
align-items: stretch;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.column-eFrOSV > button {
|
|
39
|
+
width: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.gap-12-vpXLHP {
|
|
43
|
+
gap: var(--size-12);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.gap-16-YnuaQb {
|
|
47
|
+
gap: var(--size-16);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.gap-24-Z4lCD9 {
|
|
51
|
+
gap: var(--size-24);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.gap-32-dJPD9x {
|
|
55
|
+
gap: var(--size-32);
|
|
56
|
+
}
|
|
57
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
export type HeaderProps = Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children'> & {
|
|
3
|
+
/**
|
|
4
|
+
* Необязательный заголовок панели. Без заголовка и крестика Header не выводится.
|
|
5
|
+
*/
|
|
6
|
+
title?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Показывает крестик, который вызывает onClose ближайшего Sidepanel.
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
hasCloser?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Базовый идентификатор частей -header и -closer.
|
|
14
|
+
* По умолчанию наследуется от Sidepanel.
|
|
15
|
+
*/
|
|
16
|
+
dataTestId?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare const Header: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> & {
|
|
19
|
+
/**
|
|
20
|
+
* Необязательный заголовок панели. Без заголовка и крестика Header не выводится.
|
|
21
|
+
*/
|
|
22
|
+
title?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Показывает крестик, который вызывает onClose ближайшего Sidepanel.
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
hasCloser?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Базовый идентификатор частей -header и -closer.
|
|
30
|
+
* По умолчанию наследуется от Sidepanel.
|
|
31
|
+
*/
|
|
32
|
+
dataTestId?: string;
|
|
33
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
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 { SidepanelContext } from "../../context.js";
|
|
9
|
+
import index_module from "../../index.module.js";
|
|
10
|
+
import index_module_0 from "./index.module.js";
|
|
11
|
+
const Header = /*#__PURE__*/ forwardRef(({ title, hasCloser = true, className, dataTestId, ...restProps }, ref)=>{
|
|
12
|
+
const { onClose, dataTestId: panelDataTestId } = useContext(SidepanelContext);
|
|
13
|
+
const getDataTestId = useDataTestId(dataTestId ?? panelDataTestId);
|
|
14
|
+
if (!title?.trim() && !hasCloser) return null;
|
|
15
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
16
|
+
...restProps,
|
|
17
|
+
ref: ref,
|
|
18
|
+
className: classnames(index_module_0.component, index_module.header, className),
|
|
19
|
+
"data-test-id": getDataTestId('header'),
|
|
20
|
+
children: [
|
|
21
|
+
/*#__PURE__*/ jsx("div", {
|
|
22
|
+
className: index_module_0.title,
|
|
23
|
+
children: title && /*#__PURE__*/ jsx(TypographyText, {
|
|
24
|
+
size: 20,
|
|
25
|
+
weight: "semibold",
|
|
26
|
+
children: title
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
hasCloser && /*#__PURE__*/ jsx(Button, {
|
|
30
|
+
size: 32,
|
|
31
|
+
view: "transparent",
|
|
32
|
+
className: index_module_0.closer,
|
|
33
|
+
labelClassName: index_module_0.closerLabel,
|
|
34
|
+
onClick: onClose,
|
|
35
|
+
dataTestId: getDataTestId('closer'),
|
|
36
|
+
children: /*#__PURE__*/ jsx(CrossHeavyMIcon, {
|
|
37
|
+
width: 24,
|
|
38
|
+
height: 24
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
Header.displayName = 'SidepanelHeader';
|
|
45
|
+
export { Header };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.component-pnHhxm {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
justify-content: space-between;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: var(--size-16);
|
|
6
|
+
min-height: var(--size-80);
|
|
7
|
+
padding: var(--sidepanel-padding);
|
|
8
|
+
flex-shrink: 0;
|
|
9
|
+
display: flex;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.title-nolsR9 {
|
|
13
|
+
overflow-wrap: anywhere;
|
|
14
|
+
flex: 1;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.component-pnHhxm > .closer-PhFI6O {
|
|
19
|
+
width: var(--size-24);
|
|
20
|
+
min-width: var(--size-24);
|
|
21
|
+
height: var(--size-32);
|
|
22
|
+
min-height: var(--size-32);
|
|
23
|
+
padding: var(--size-0);
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.closerLabel-q4m2AK {
|
|
28
|
+
justify-content: center;
|
|
29
|
+
align-items: center;
|
|
30
|
+
display: flex;
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { forwardRef, useCallback, useLayoutEffect, useMemo, useRef } from "react";
|
|
4
|
+
import { lockScroll } from "../../../../utils/lock-scroll.js";
|
|
5
|
+
import { SidepanelContext } from "../../context.js";
|
|
6
|
+
import { useDialogTransition } from "../../hooks/use-dialog-transition.js";
|
|
7
|
+
import index_module from "../../index.module.js";
|
|
8
|
+
import scrollbar_module from "../../scrollbar.module.js";
|
|
9
|
+
const SidepanelSurface = /*#__PURE__*/ forwardRef(({ open, onClose, onExited, escapeKeyDown = true, backdropClick = true, lockScroll: shouldLockScroll = true, className, dataTestId, children, onPointerDown, onClick, onTransitionEnd, ...restProps }, ref)=>{
|
|
10
|
+
const dialogRef = useRef(null);
|
|
11
|
+
const backdropPointerDown = useRef(false);
|
|
12
|
+
const mounted = useRef(false);
|
|
13
|
+
useLayoutEffect(()=>{
|
|
14
|
+
mounted.current = true;
|
|
15
|
+
return ()=>{
|
|
16
|
+
mounted.current = false;
|
|
17
|
+
};
|
|
18
|
+
}, []);
|
|
19
|
+
const { active, finishClosing } = useDialogTransition(dialogRef, open, onExited);
|
|
20
|
+
const requestClose = useCallback(()=>{
|
|
21
|
+
if (open) onClose?.();
|
|
22
|
+
}, [
|
|
23
|
+
onClose,
|
|
24
|
+
open
|
|
25
|
+
]);
|
|
26
|
+
const mergedRef = useCallback((element)=>{
|
|
27
|
+
dialogRef.current = element;
|
|
28
|
+
if ('function' == typeof ref) ref(element);
|
|
29
|
+
else if (ref) ref.current = element;
|
|
30
|
+
}, [
|
|
31
|
+
ref
|
|
32
|
+
]);
|
|
33
|
+
const context = useMemo(()=>({
|
|
34
|
+
onClose: requestClose,
|
|
35
|
+
dataTestId
|
|
36
|
+
}), [
|
|
37
|
+
dataTestId,
|
|
38
|
+
requestClose
|
|
39
|
+
]);
|
|
40
|
+
useLayoutEffect(()=>{
|
|
41
|
+
const dialog = dialogRef.current;
|
|
42
|
+
if (active && shouldLockScroll && dialog) return lockScroll(dialog.ownerDocument, true);
|
|
43
|
+
}, [
|
|
44
|
+
active,
|
|
45
|
+
shouldLockScroll
|
|
46
|
+
]);
|
|
47
|
+
const isBackdrop = (event)=>{
|
|
48
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
49
|
+
return event.target === event.currentTarget && (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom);
|
|
50
|
+
};
|
|
51
|
+
const handleCancel = (event)=>{
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
event.stopPropagation();
|
|
54
|
+
if (escapeKeyDown) requestClose();
|
|
55
|
+
};
|
|
56
|
+
const handleClose = (event)=>{
|
|
57
|
+
event.stopPropagation();
|
|
58
|
+
if (mounted.current && !event.currentTarget.open) requestClose();
|
|
59
|
+
};
|
|
60
|
+
const handlePointerDown = (event)=>{
|
|
61
|
+
onPointerDown?.(event);
|
|
62
|
+
backdropPointerDown.current = !event.defaultPrevented && isBackdrop(event);
|
|
63
|
+
};
|
|
64
|
+
const handleClick = (event)=>{
|
|
65
|
+
onClick?.(event);
|
|
66
|
+
const close = backdropPointerDown.current && isBackdrop(event);
|
|
67
|
+
backdropPointerDown.current = false;
|
|
68
|
+
if (backdropClick && close && !event.defaultPrevented) requestClose();
|
|
69
|
+
};
|
|
70
|
+
const handleTransitionEnd = (event)=>{
|
|
71
|
+
onTransitionEnd?.(event);
|
|
72
|
+
if (event.target === event.currentTarget && 'transform' === event.propertyName) finishClosing();
|
|
73
|
+
};
|
|
74
|
+
return /*#__PURE__*/ jsx("dialog", {
|
|
75
|
+
...restProps,
|
|
76
|
+
ref: mergedRef,
|
|
77
|
+
className: classnames(index_module.component, scrollbar_module.scrollbar, className),
|
|
78
|
+
"data-test-id": dataTestId,
|
|
79
|
+
"data-state": "closed",
|
|
80
|
+
onCancel: handleCancel,
|
|
81
|
+
onClose: handleClose,
|
|
82
|
+
onPointerDown: handlePointerDown,
|
|
83
|
+
onClick: handleClick,
|
|
84
|
+
onTransitionEnd: handleTransitionEnd,
|
|
85
|
+
children: /*#__PURE__*/ jsx(SidepanelContext.Provider, {
|
|
86
|
+
value: context,
|
|
87
|
+
children: children
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
SidepanelSurface.displayName = 'SidepanelSurface';
|
|
92
|
+
export { SidepanelSurface };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
function milliseconds(value) {
|
|
3
|
+
return (parseFloat(value) || 0) * (value.trim().endsWith('ms') ? 1 : 1000);
|
|
4
|
+
}
|
|
5
|
+
function transitionTime(dialog) {
|
|
6
|
+
const style = dialog.ownerDocument.defaultView?.getComputedStyle(dialog);
|
|
7
|
+
const durations = style?.transitionDuration.split(',').map(milliseconds) ?? [
|
|
8
|
+
0
|
|
9
|
+
];
|
|
10
|
+
const delays = style?.transitionDelay.split(',').map(milliseconds) ?? [
|
|
11
|
+
0
|
|
12
|
+
];
|
|
13
|
+
return Math.max(0, ...durations.map((duration, index)=>duration + delays[index % delays.length]));
|
|
14
|
+
}
|
|
15
|
+
function useDialogTransition(dialogRef, open, onExited) {
|
|
16
|
+
const [active, setActive] = useState(open);
|
|
17
|
+
const latest = useRef({
|
|
18
|
+
open,
|
|
19
|
+
onExited
|
|
20
|
+
});
|
|
21
|
+
useLayoutEffect(()=>{
|
|
22
|
+
latest.current = {
|
|
23
|
+
open,
|
|
24
|
+
onExited
|
|
25
|
+
};
|
|
26
|
+
}, [
|
|
27
|
+
open,
|
|
28
|
+
onExited
|
|
29
|
+
]);
|
|
30
|
+
const finishClosing = useCallback(()=>{
|
|
31
|
+
const dialog = dialogRef.current;
|
|
32
|
+
if (!dialog || latest.current.open) return;
|
|
33
|
+
if (dialog.open) dialog.close();
|
|
34
|
+
setActive(false);
|
|
35
|
+
latest.current.onExited();
|
|
36
|
+
}, [
|
|
37
|
+
dialogRef
|
|
38
|
+
]);
|
|
39
|
+
useLayoutEffect(()=>{
|
|
40
|
+
const dialog = dialogRef.current;
|
|
41
|
+
const view = dialog?.ownerDocument.defaultView;
|
|
42
|
+
if (!dialog || !view) return;
|
|
43
|
+
let frame;
|
|
44
|
+
let timeout;
|
|
45
|
+
if (open) {
|
|
46
|
+
if (!dialog.open) {
|
|
47
|
+
dialog.dataset.state = 'closed';
|
|
48
|
+
dialog.showModal();
|
|
49
|
+
dialog.getBoundingClientRect();
|
|
50
|
+
}
|
|
51
|
+
setActive(true);
|
|
52
|
+
frame = view.requestAnimationFrame(()=>{
|
|
53
|
+
dialog.dataset.state = 'open';
|
|
54
|
+
});
|
|
55
|
+
} else if (dialog.open && 'open' === dialog.dataset.state) {
|
|
56
|
+
dialog.dataset.state = 'closed';
|
|
57
|
+
const duration = transitionTime(dialog);
|
|
58
|
+
if (0 === duration) finishClosing();
|
|
59
|
+
else timeout = view.setTimeout(finishClosing, duration + 50);
|
|
60
|
+
} else finishClosing();
|
|
61
|
+
return ()=>{
|
|
62
|
+
if (void 0 !== frame) view.cancelAnimationFrame(frame);
|
|
63
|
+
if (void 0 !== timeout) view.clearTimeout(timeout);
|
|
64
|
+
};
|
|
65
|
+
}, [
|
|
66
|
+
dialogRef,
|
|
67
|
+
finishClosing,
|
|
68
|
+
open
|
|
69
|
+
]);
|
|
70
|
+
useLayoutEffect(()=>{
|
|
71
|
+
const dialog = dialogRef.current;
|
|
72
|
+
return ()=>{
|
|
73
|
+
if (dialog?.open) dialog.close();
|
|
74
|
+
};
|
|
75
|
+
}, [
|
|
76
|
+
dialogRef
|
|
77
|
+
]);
|
|
78
|
+
return {
|
|
79
|
+
active,
|
|
80
|
+
finishClosing
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export { useDialogTransition };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Sidepanel } from './sidepanel.js';
|
|
2
|
+
export type { SidepanelProps } from './types.js';
|
|
3
|
+
export { Header } from './components/header/index.js';
|
|
4
|
+
export type { HeaderProps } from './components/header/index.js';
|
|
5
|
+
export { Content } from './components/content/index.js';
|
|
6
|
+
export type { ContentProps } from './components/content/index.js';
|
|
7
|
+
export { Footer } from './components/footer/index.js';
|
|
8
|
+
export type { FooterProps } from './components/footer/index.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@import "../../styles/theme.css";
|
|
2
|
+
@import "./vars.css";
|
|
3
|
+
|
|
4
|
+
.component-kIdcz9 {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
width: 500px;
|
|
7
|
+
min-width: 0;
|
|
8
|
+
max-width: 100vw;
|
|
9
|
+
height: 100dvh;
|
|
10
|
+
max-height: none;
|
|
11
|
+
margin: var(--size-0);
|
|
12
|
+
padding: var(--size-0);
|
|
13
|
+
border-radius: var(--border-radius-0);
|
|
14
|
+
background: var(--sidepanel-background-color);
|
|
15
|
+
color: var(--sidepanel-color);
|
|
16
|
+
font-family: var(--font-primary);
|
|
17
|
+
transition: transform var(--sidepanel-animation-duration) ease-in-out;
|
|
18
|
+
border: 0;
|
|
19
|
+
outline: none;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
position: fixed;
|
|
22
|
+
inset: 0 0 0 auto;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
transform: translateX(100%);
|
|
25
|
+
|
|
26
|
+
&[open] {
|
|
27
|
+
display: flex;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:has( > .nonStickyFooter-rXnHkK) {
|
|
31
|
+
overscroll-behavior: contain;
|
|
32
|
+
overflow: auto;
|
|
33
|
+
|
|
34
|
+
& > .header-UlW3Pm {
|
|
35
|
+
z-index: 1;
|
|
36
|
+
background: var(--sidepanel-background-color);
|
|
37
|
+
position: sticky;
|
|
38
|
+
top: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
& > .content-RhY7Xk {
|
|
42
|
+
flex: 1 0 auto;
|
|
43
|
+
overflow: visible;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&[data-state="open"] {
|
|
48
|
+
transform: translateX(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&::backdrop {
|
|
52
|
+
background: var(--sidepanel-backdrop-color);
|
|
53
|
+
backdrop-filter: blur(var(--sidepanel-backdrop-blur));
|
|
54
|
+
opacity: 0;
|
|
55
|
+
transition: opacity var(--sidepanel-animation-duration) ease-in-out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&[data-state="open"]::backdrop {
|
|
59
|
+
opacity: 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@media (prefers-reduced-motion: reduce) {
|
|
64
|
+
.component-kIdcz9, .component-kIdcz9::backdrop {
|
|
65
|
+
transition-duration: 0s;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.scrollbar-haYNj_ {
|
|
2
|
+
scrollbar-color: auto;
|
|
3
|
+
scrollbar-width: auto;
|
|
4
|
+
|
|
5
|
+
&::-webkit-scrollbar {
|
|
6
|
+
width: var(--size-6);
|
|
7
|
+
height: var(--size-6);
|
|
8
|
+
background: none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
&::-webkit-scrollbar-button {
|
|
12
|
+
width: 0;
|
|
13
|
+
height: 0;
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&::-webkit-scrollbar-track {
|
|
18
|
+
background: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&::-webkit-scrollbar-track-piece {
|
|
22
|
+
background: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&::-webkit-scrollbar-corner {
|
|
26
|
+
background: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&::-webkit-scrollbar-thumb {
|
|
30
|
+
border-radius: var(--border-radius-pill);
|
|
31
|
+
background: var(--sidepanel-scrollbar-color);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
36
|
+
.scrollbar-haYNj_ {
|
|
37
|
+
scrollbar-width: thin;
|
|
38
|
+
scrollbar-color: var(--sidepanel-scrollbar-color) transparent;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const Sidepanel: import("react").ForwardRefExoticComponent<Omit<import("react").DialogHTMLAttributes<HTMLDialogElement>, "open" | "onClose" | "onCancel"> & {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onClose?: VoidFunction;
|
|
4
|
+
escapeKeyDown?: boolean;
|
|
5
|
+
backdropClick?: boolean;
|
|
6
|
+
lockScroll?: boolean;
|
|
7
|
+
getPortalContainer?: import("../Portal/index.js").PortalProps["getPortalContainer"];
|
|
8
|
+
dataTestId?: string;
|
|
9
|
+
} & import("react").RefAttributes<HTMLDialogElement>>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useLayoutEffect, useState } from "react";
|
|
3
|
+
import { Portal } from "../Portal/index.js";
|
|
4
|
+
import { SidepanelSurface } from "./components/sidepanel-surface/index.js";
|
|
5
|
+
const Sidepanel = /*#__PURE__*/ forwardRef(({ open, getPortalContainer, ...restProps }, ref)=>{
|
|
6
|
+
const [mounted, setMounted] = useState(open);
|
|
7
|
+
useLayoutEffect(()=>{
|
|
8
|
+
if (open) setMounted(true);
|
|
9
|
+
}, [
|
|
10
|
+
open
|
|
11
|
+
]);
|
|
12
|
+
const handleExited = useCallback(()=>{
|
|
13
|
+
if (!open) setMounted(false);
|
|
14
|
+
}, [
|
|
15
|
+
open
|
|
16
|
+
]);
|
|
17
|
+
return mounted ? /*#__PURE__*/ jsx(Portal, {
|
|
18
|
+
getPortalContainer: getPortalContainer,
|
|
19
|
+
children: /*#__PURE__*/ jsx(SidepanelSurface, {
|
|
20
|
+
...restProps,
|
|
21
|
+
ref: ref,
|
|
22
|
+
open: open,
|
|
23
|
+
onExited: handleExited
|
|
24
|
+
})
|
|
25
|
+
}) : null;
|
|
26
|
+
});
|
|
27
|
+
Sidepanel.displayName = 'Sidepanel';
|
|
28
|
+
export { Sidepanel };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DialogHTMLAttributes } from 'react';
|
|
2
|
+
import type { PortalProps } from '../Portal/index.js';
|
|
3
|
+
export type SidepanelProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClose' | 'onCancel'> & {
|
|
4
|
+
/**
|
|
5
|
+
* Видимость панели. Для закрытия обновите open в обработчике onClose.
|
|
6
|
+
* Панель остаётся открытой до завершения анимации ухода вправо.
|
|
7
|
+
*/
|
|
8
|
+
open: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Запрос закрытия по крестику, Escape или бекдропу.
|
|
11
|
+
*/
|
|
12
|
+
onClose?: VoidFunction;
|
|
13
|
+
/**
|
|
14
|
+
* Разрешает запрос закрытия по Escape.
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
escapeKeyDown?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Разрешает запрос закрытия по бекдропу.
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
backdropClick?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Блокирует прокрутку страницы до завершения закрытия.
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
lockScroll?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Контейнер портала. По умолчанию используется body текущего документа.
|
|
30
|
+
*/
|
|
31
|
+
getPortalContainer?: PortalProps['getPortalContainer'];
|
|
32
|
+
/**
|
|
33
|
+
* Идентификатор панели. Части получают суффиксы -header, -closer, -content и -footer.
|
|
34
|
+
*/
|
|
35
|
+
dataTestId?: string;
|
|
36
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@import "../Modal/vars.css";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--sidepanel-background-color: var(--modal-background-color);
|
|
5
|
+
--sidepanel-color: var(--modal-color);
|
|
6
|
+
--sidepanel-backdrop-color: var(--modal-backdrop-color);
|
|
7
|
+
--sidepanel-backdrop-blur: var(--modal-backdrop-blur);
|
|
8
|
+
--sidepanel-padding: var(--size-24);
|
|
9
|
+
--sidepanel-scrollbar-color: #a49b89;
|
|
10
|
+
--sidepanel-animation-duration: .2s;
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -7,7 +7,6 @@ export declare const Tabs: import("react").ForwardRefExoticComponent<Omit<import
|
|
|
7
7
|
selectedId: string;
|
|
8
8
|
}) => void;
|
|
9
9
|
size?: 40 | 48;
|
|
10
|
-
emptyState?: ReactNode;
|
|
11
10
|
listClassName?: string;
|
|
12
11
|
dataTestId?: string;
|
|
13
12
|
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -40,7 +40,7 @@ function resolveValue(items, value, previous) {
|
|
|
40
40
|
const start = Math.max(0, index);
|
|
41
41
|
return items.slice(start).find((item)=>!item.disabled)?.id ?? items.slice(0, start).reverse().find((item)=>!item.disabled)?.id;
|
|
42
42
|
}
|
|
43
|
-
const Tabs = /*#__PURE__*/ forwardRef(({ children, selectedId, defaultSelectedId, onChange, size = 40,
|
|
43
|
+
const Tabs = /*#__PURE__*/ forwardRef(({ children, selectedId, defaultSelectedId, onChange, size = 40, listClassName, dataTestId, className, ...restProps }, ref)=>{
|
|
44
44
|
const getDataTestId = useDataTestId(dataTestId);
|
|
45
45
|
const items = useMemo(()=>collectTabs(children), [
|
|
46
46
|
children
|
|
@@ -78,12 +78,13 @@ const Tabs = /*#__PURE__*/ forwardRef(({ children, selectedId, defaultSelectedId
|
|
|
78
78
|
selectedId: id
|
|
79
79
|
});
|
|
80
80
|
};
|
|
81
|
+
if (0 === items.length) return null;
|
|
81
82
|
return /*#__PURE__*/ jsx("div", {
|
|
82
83
|
...restProps,
|
|
83
84
|
ref: ref,
|
|
84
85
|
className: classnames(index_module.component, className),
|
|
85
86
|
"data-test-id": dataTestId,
|
|
86
|
-
children:
|
|
87
|
+
children: /*#__PURE__*/ jsx(TabsContext.Provider, {
|
|
87
88
|
value: {
|
|
88
89
|
selectedId: selectedValue,
|
|
89
90
|
size,
|
|
@@ -102,7 +103,7 @@ const Tabs = /*#__PURE__*/ forwardRef(({ children, selectedId, defaultSelectedId
|
|
|
102
103
|
items.map((item)=>/*#__PURE__*/ jsx(Tab, {
|
|
103
104
|
...item
|
|
104
105
|
}, item.id)),
|
|
105
|
-
/*#__PURE__*/ jsx("div", {
|
|
106
|
+
void 0 !== selectedValue && /*#__PURE__*/ jsx("div", {
|
|
106
107
|
ref: indicatorRef,
|
|
107
108
|
className: index_module.indicator,
|
|
108
109
|
"data-test-id": getDataTestId('indicator')
|
|
@@ -45,10 +45,6 @@ export type TabsProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onCha
|
|
|
45
45
|
* @default 40
|
|
46
46
|
*/
|
|
47
47
|
size?: 40 | 48;
|
|
48
|
-
/**
|
|
49
|
-
* Общее пустое состояние, когда нет доступных вкладок.
|
|
50
|
-
*/
|
|
51
|
-
emptyState?: ReactNode;
|
|
52
48
|
/**
|
|
53
49
|
* Класс ряда заголовков.
|
|
54
50
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -25,4 +25,6 @@ export type { CollapseProps } from './components/Collapse/index.js';
|
|
|
25
25
|
export { Modal, ModalComponent, ModalHeader, ModalContent, ModalFooter, ModalControls, } from './components/Modal/index.js';
|
|
26
26
|
export { Portal } from './components/Portal/index.js';
|
|
27
27
|
export type { PortalProps } from './components/Portal/index.js';
|
|
28
|
+
export { Sidepanel, Header as SidepanelHeader, Content as SidepanelContent, Footer as SidepanelFooter, } from './components/Sidepanel/index.js';
|
|
29
|
+
export type { SidepanelProps, HeaderProps as SidepanelHeaderProps, ContentProps as SidepanelContentProps, FooterProps as SidepanelFooterProps, } from './components/Sidepanel/index.js';
|
|
28
30
|
export type { ModalProps, HeaderProps as ModalHeaderProps, ContentProps as ModalContentProps, FooterProps as ModalFooterProps, ControlsProps as ModalControlsProps, } from './components/Modal/index.js';
|
package/dist/index.js
CHANGED
|
@@ -12,3 +12,4 @@ export { Popover } from "./components/Popover/index.js";
|
|
|
12
12
|
export { Collapse } from "./components/Collapse/index.js";
|
|
13
13
|
export { Modal, ModalComponent, ModalContent, ModalControls, ModalFooter, ModalHeader } from "./components/Modal/index.js";
|
|
14
14
|
export { Portal } from "./components/Portal/index.js";
|
|
15
|
+
export { Content as SidepanelContent, Footer as SidepanelFooter, Header as SidepanelHeader, Sidepanel } from "./components/Sidepanel/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function lockScroll(document: Document, removeScrollbarGutter?: boolean): () => void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const locks = new WeakMap();
|
|
2
|
+
function lockScroll(document, removeScrollbarGutter = false) {
|
|
3
|
+
const { body, documentElement, defaultView } = document;
|
|
4
|
+
const existing = locks.get(document);
|
|
5
|
+
if (existing) existing.count += 1;
|
|
6
|
+
else {
|
|
7
|
+
locks.set(document, {
|
|
8
|
+
count: 1,
|
|
9
|
+
overflow: body.style.overflow,
|
|
10
|
+
paddingRight: body.style.paddingRight,
|
|
11
|
+
paddingLeft: body.style.paddingLeft
|
|
12
|
+
});
|
|
13
|
+
const scrollbarWidth = (defaultView?.innerWidth ?? 0) - documentElement.clientWidth;
|
|
14
|
+
const hasStableGutter = defaultView?.getComputedStyle(documentElement).scrollbarGutter?.split(/\s+/).includes('stable');
|
|
15
|
+
if (scrollbarWidth > 0 && defaultView && !hasStableGutter) {
|
|
16
|
+
const paddingRight = parseFloat(defaultView.getComputedStyle(body).paddingRight) || 0;
|
|
17
|
+
body.style.paddingRight = `${paddingRight + scrollbarWidth}px`;
|
|
18
|
+
}
|
|
19
|
+
body.style.overflow = 'hidden';
|
|
20
|
+
}
|
|
21
|
+
const gutterLock = locks.get(document);
|
|
22
|
+
if (removeScrollbarGutter && !gutterLock.gutter && defaultView?.getComputedStyle(documentElement).scrollbarGutter?.includes('stable')) {
|
|
23
|
+
const before = documentElement.getBoundingClientRect();
|
|
24
|
+
const paddingLeft = parseFloat(defaultView.getComputedStyle(body).paddingLeft) || 0;
|
|
25
|
+
const paddingRight = parseFloat(defaultView.getComputedStyle(body).paddingRight) || 0;
|
|
26
|
+
gutterLock.gutter = {
|
|
27
|
+
value: documentElement.style.getPropertyValue('scrollbar-gutter'),
|
|
28
|
+
priority: documentElement.style.getPropertyPriority('scrollbar-gutter')
|
|
29
|
+
};
|
|
30
|
+
documentElement.style.setProperty('scrollbar-gutter', 'auto', 'important');
|
|
31
|
+
const after = documentElement.getBoundingClientRect();
|
|
32
|
+
body.style.paddingLeft = `${paddingLeft + Math.max(0, before.left - after.left)}px`;
|
|
33
|
+
body.style.paddingRight = `${paddingRight + Math.max(0, after.right - before.right)}px`;
|
|
34
|
+
}
|
|
35
|
+
return ()=>{
|
|
36
|
+
const lock = locks.get(document);
|
|
37
|
+
if (!lock) return;
|
|
38
|
+
lock.count -= 1;
|
|
39
|
+
if (0 === lock.count) {
|
|
40
|
+
body.style.overflow = lock.overflow;
|
|
41
|
+
body.style.paddingRight = lock.paddingRight;
|
|
42
|
+
body.style.paddingLeft = lock.paddingLeft;
|
|
43
|
+
if (lock.gutter) if (lock.gutter.value) documentElement.style.setProperty('scrollbar-gutter', lock.gutter.value, lock.gutter.priority);
|
|
44
|
+
else documentElement.style.removeProperty('scrollbar-gutter');
|
|
45
|
+
locks.delete(document);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export { lockScroll };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function lockScroll(document: Document): () => void;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
const locks = new WeakMap();
|
|
2
|
-
function lockScroll(document) {
|
|
3
|
-
const { body, documentElement, defaultView } = document;
|
|
4
|
-
const existing = locks.get(document);
|
|
5
|
-
if (existing) existing.count += 1;
|
|
6
|
-
else {
|
|
7
|
-
locks.set(document, {
|
|
8
|
-
count: 1,
|
|
9
|
-
overflow: body.style.overflow,
|
|
10
|
-
paddingRight: body.style.paddingRight
|
|
11
|
-
});
|
|
12
|
-
const scrollbarWidth = (defaultView?.innerWidth ?? 0) - documentElement.clientWidth;
|
|
13
|
-
const hasStableGutter = defaultView?.getComputedStyle(documentElement).scrollbarGutter?.split(/\s+/).includes('stable');
|
|
14
|
-
if (scrollbarWidth > 0 && defaultView && !hasStableGutter) {
|
|
15
|
-
const paddingRight = parseFloat(defaultView.getComputedStyle(body).paddingRight) || 0;
|
|
16
|
-
body.style.paddingRight = `${paddingRight + scrollbarWidth}px`;
|
|
17
|
-
}
|
|
18
|
-
body.style.overflow = 'hidden';
|
|
19
|
-
}
|
|
20
|
-
return ()=>{
|
|
21
|
-
const lock = locks.get(document);
|
|
22
|
-
if (!lock) return;
|
|
23
|
-
lock.count -= 1;
|
|
24
|
-
if (0 === lock.count) {
|
|
25
|
-
body.style.overflow = lock.overflow;
|
|
26
|
-
body.style.paddingRight = lock.paddingRight;
|
|
27
|
-
locks.delete(document);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
export { lockScroll };
|