ydb-embedded-ui 3.2.0 → 3.2.2
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 +18 -0
- package/dist/components/EntitiesCount/EntitiesCount.tsx +34 -0
- package/dist/components/EntitiesCount/i18n/en.json +3 -0
- package/dist/components/{AsideNavigation/Settings → EntitiesCount}/i18n/index.ts +2 -2
- package/dist/components/EntitiesCount/i18n/ru.json +3 -0
- package/dist/components/EntitiesCount/index.ts +1 -0
- package/dist/components/Fullscreen/Fullscreen.scss +7 -5
- package/dist/components/TabletsOverall/TabletsOverall.tsx +4 -4
- package/dist/components/TabletsStatistic/TabletsStatistic.tsx +56 -0
- package/dist/components/TabletsStatistic/index.ts +1 -0
- package/dist/containers/App/App.scss +4 -12
- package/dist/containers/AsideNavigation/AsideNavigation.scss +0 -18
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +95 -33
- package/dist/containers/Heatmap/Heatmap.scss +0 -7
- package/dist/containers/Heatmap/Heatmap.tsx +203 -0
- package/dist/containers/Heatmap/HeatmapCanvas/HeatmapCanvas.js +2 -1
- package/dist/containers/Heatmap/index.ts +1 -0
- package/dist/containers/Node/Node.tsx +1 -1
- package/dist/containers/Storage/Storage.js +12 -19
- package/dist/containers/Tablets/Tablets.scss +0 -5
- package/dist/containers/Tablets/Tablets.tsx +172 -0
- package/dist/containers/Tablets/i18n/en.json +6 -0
- package/dist/{components/AsideNavigation → containers/Tablets}/i18n/index.ts +1 -1
- package/dist/containers/Tablets/i18n/ru.json +6 -0
- package/dist/containers/Tablets/index.ts +1 -0
- package/dist/containers/TabletsFilters/TabletsFilters.js +4 -8
- package/dist/containers/TabletsFilters/TabletsFilters.scss +6 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -8
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +7 -7
- package/dist/containers/Tenants/Tenants.js +1 -1
- package/dist/containers/UserSettings/UserSettings.tsx +4 -3
- package/dist/routes.ts +1 -1
- package/dist/store/reducers/{heatmap.js → heatmap.ts} +33 -18
- package/dist/store/reducers/settings.js +12 -2
- package/dist/types/api/compute.ts +1 -1
- package/dist/types/api/schema.ts +16 -3
- package/dist/types/store/heatmap.ts +51 -0
- package/dist/utils/constants.ts +1 -37
- package/dist/utils/getNodesColumns.js +7 -2
- package/dist/utils/tablet.ts +53 -0
- package/package.json +2 -1
- package/dist/components/AsideNavigation/AsideHeader.scss +0 -147
- package/dist/components/AsideNavigation/AsideHeader.tsx +0 -389
- package/dist/components/AsideNavigation/AsideHeaderFooterItem/AsideHeaderFooterItem.scss +0 -82
- package/dist/components/AsideNavigation/AsideHeaderFooterItem/AsideHeaderFooterItem.tsx +0 -138
- package/dist/components/AsideNavigation/AsideHeaderFooterSlot/AsideHeaderFooterSlot.tsx +0 -33
- package/dist/components/AsideNavigation/AsideHeaderFooterSlot/SlotsContext.tsx +0 -49
- package/dist/components/AsideNavigation/AsideHeaderTooltip/AsideHeaderTooltip.scss +0 -16
- package/dist/components/AsideNavigation/AsideHeaderTooltip/AsideHeaderTooltip.tsx +0 -37
- package/dist/components/AsideNavigation/CompositeBar/CompositeBar.scss +0 -108
- package/dist/components/AsideNavigation/CompositeBar/CompositeBar.tsx +0 -282
- package/dist/components/AsideNavigation/Content/Content.tsx +0 -35
- package/dist/components/AsideNavigation/Drawer/Drawer.scss +0 -76
- package/dist/components/AsideNavigation/Drawer/Drawer.tsx +0 -134
- package/dist/components/AsideNavigation/Drawer/index.ts +0 -1
- package/dist/components/AsideNavigation/Logo/Logo.scss +0 -43
- package/dist/components/AsideNavigation/Logo/Logo.tsx +0 -82
- package/dist/components/AsideNavigation/Settings/README.md +0 -92
- package/dist/components/AsideNavigation/Settings/Settings.scss +0 -128
- package/dist/components/AsideNavigation/Settings/Settings.tsx +0 -270
- package/dist/components/AsideNavigation/Settings/SettingsMenu/SettingsMenu.scss +0 -78
- package/dist/components/AsideNavigation/Settings/SettingsMenu/SettingsMenu.tsx +0 -141
- package/dist/components/AsideNavigation/Settings/SettingsSearch/SettingsSearch.tsx +0 -57
- package/dist/components/AsideNavigation/Settings/collect-settings.ts +0 -156
- package/dist/components/AsideNavigation/Settings/filter-settings.ts +0 -38
- package/dist/components/AsideNavigation/Settings/helpers.ts +0 -39
- package/dist/components/AsideNavigation/Settings/i18n/en.json +0 -5
- package/dist/components/AsideNavigation/Settings/i18n/ru.json +0 -5
- package/dist/components/AsideNavigation/Settings/index.ts +0 -1
- package/dist/components/AsideNavigation/constants.ts +0 -28
- package/dist/components/AsideNavigation/helpers.ts +0 -34
- package/dist/components/AsideNavigation/i18n/en.json +0 -4
- package/dist/components/AsideNavigation/i18n/ru.json +0 -4
- package/dist/components/AsideNavigation/icons.ts +0 -32
- package/dist/components/AsideNavigation/types.ts +0 -23
- package/dist/components/TabletsStatistic/TabletsStatistic.js +0 -58
- package/dist/containers/Heatmap/Heatmap.js +0 -244
- package/dist/containers/Tablets/Tablets.js +0 -228
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
.nv-aside-header-logo {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-shrink: 0;
|
|
4
|
-
align-items: center;
|
|
5
|
-
|
|
6
|
-
height: 32px;
|
|
7
|
-
margin: 12px 0;
|
|
8
|
-
|
|
9
|
-
font-family: 'Rubik', sans-serif;
|
|
10
|
-
|
|
11
|
-
&__logo-btn-place {
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-shrink: 0;
|
|
14
|
-
justify-content: center;
|
|
15
|
-
align-items: center;
|
|
16
|
-
|
|
17
|
-
width: var(--nv-aside-header-min-width);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
&__logo {
|
|
21
|
-
font-family: 'Rubik', sans-serif;
|
|
22
|
-
font-size: 16px;
|
|
23
|
-
line-height: 20px;
|
|
24
|
-
vertical-align: middle;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
&__logo-link {
|
|
28
|
-
&,
|
|
29
|
-
&:hover,
|
|
30
|
-
&:active,
|
|
31
|
-
&:visited,
|
|
32
|
-
&:focus {
|
|
33
|
-
text-decoration: none;
|
|
34
|
-
|
|
35
|
-
color: inherit;
|
|
36
|
-
outline: none;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.yc-root &__btn-logo.button2_theme_flat.button2_hovered_yes::before {
|
|
41
|
-
background-color: transparent;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import block from 'bem-cn-lite';
|
|
3
|
-
import {Button, Icon} from '@gravity-ui/uikit';
|
|
4
|
-
|
|
5
|
-
import './Logo.scss';
|
|
6
|
-
|
|
7
|
-
const b = block('nv-aside-header-logo');
|
|
8
|
-
|
|
9
|
-
export interface LogoProps {
|
|
10
|
-
onLogoIconClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
|
11
|
-
logoText: (() => React.ReactNode) | string;
|
|
12
|
-
logoIcon: SVGIconData;
|
|
13
|
-
logoIconClassName?: string;
|
|
14
|
-
logoIconSize?: string | number;
|
|
15
|
-
logoTextSize?: string | number;
|
|
16
|
-
logoHref?: string;
|
|
17
|
-
isCompact: boolean;
|
|
18
|
-
logoWrapper?: (node: React.ReactNode, isCompact: boolean) => React.ReactNode;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const Logo: React.FC<LogoProps> = ({
|
|
22
|
-
onLogoIconClick,
|
|
23
|
-
logoText,
|
|
24
|
-
logoIcon,
|
|
25
|
-
logoIconSize = 24,
|
|
26
|
-
logoTextSize = 16,
|
|
27
|
-
logoHref = '/',
|
|
28
|
-
logoIconClassName,
|
|
29
|
-
isCompact,
|
|
30
|
-
logoWrapper,
|
|
31
|
-
}) => {
|
|
32
|
-
const hasClickHandler = typeof onLogoIconClick === 'function';
|
|
33
|
-
const hasLogoWrapper = typeof logoWrapper === 'function';
|
|
34
|
-
|
|
35
|
-
const linkProps = hasClickHandler
|
|
36
|
-
? {}
|
|
37
|
-
: {
|
|
38
|
-
target: '_self',
|
|
39
|
-
href: logoHref,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const button = (
|
|
43
|
-
<Button
|
|
44
|
-
view="flat"
|
|
45
|
-
size="l"
|
|
46
|
-
className={b('btn-logo')}
|
|
47
|
-
component={hasLogoWrapper ? 'span' : undefined}
|
|
48
|
-
onClick={onLogoIconClick}
|
|
49
|
-
{...linkProps}
|
|
50
|
-
>
|
|
51
|
-
<Icon data={logoIcon} size={logoIconSize} className={logoIconClassName} />
|
|
52
|
-
</Button>
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
let logo: React.ReactNode;
|
|
56
|
-
|
|
57
|
-
if (typeof logoText === 'function') {
|
|
58
|
-
logo = logoText();
|
|
59
|
-
} else {
|
|
60
|
-
logo = (
|
|
61
|
-
<div className={b('logo')} style={{fontSize: logoTextSize}}>
|
|
62
|
-
{logoText}
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div className={b()}>
|
|
69
|
-
<div className={b('logo-btn-place')}>
|
|
70
|
-
{typeof logoWrapper === 'function' ? logoWrapper(button, isCompact) : button}
|
|
71
|
-
</div>
|
|
72
|
-
{!isCompact &&
|
|
73
|
-
(typeof logoWrapper === 'function' ? (
|
|
74
|
-
logoWrapper(logo, isCompact)
|
|
75
|
-
) : (
|
|
76
|
-
<a {...linkProps} className={b('logo-link')} onClick={onLogoIconClick}>
|
|
77
|
-
{logo}
|
|
78
|
-
</a>
|
|
79
|
-
))}
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
### Settings
|
|
2
|
-
|
|
3
|
-
Компонент для отображения настроек сервиса. Поддерживает
|
|
4
|
-
|
|
5
|
-
- одно-уровневую и двух-уровневую группировки настроек;
|
|
6
|
-
- поиск по заголовкам настроек;
|
|
7
|
-
- отображение состояния загрузки;
|
|
8
|
-
|
|
9
|
-
### PropTypes
|
|
10
|
-
|
|
11
|
-
#### Settings
|
|
12
|
-
|
|
13
|
-
| Property | Type | Required | Default | Description |
|
|
14
|
-
| :------------- | :------- | :------: | :------ | :----------------------------------------------------------------------- |
|
|
15
|
-
| loading | boolean | | | флаг неготовности настроек для отображения |
|
|
16
|
-
| renderLoading | Function | | | Содержимое компонента на время загрузки |
|
|
17
|
-
| renderNotFound | Function | | | Содержимое страницы при отсутствии найденных элементов |
|
|
18
|
-
| initialPage | string | | | Активный раздел настроек по умолчанию в формате "/ид группы/ид страницы" |
|
|
19
|
-
| onPageChange | Function | | | Обработчик события изменения активной страницы настроек |
|
|
20
|
-
|
|
21
|
-
#### Settings.Group
|
|
22
|
-
|
|
23
|
-
| Property | Type | Required | Default | Description |
|
|
24
|
-
| :--------- | :----- | :------: | :------ | :--------------------------------------- |
|
|
25
|
-
| id | string | | | Уникальный идентификатор группы настроек |
|
|
26
|
-
| groupTitle | string | true | | заголовок группы страниц настроек |
|
|
27
|
-
|
|
28
|
-
#### Settings.Page
|
|
29
|
-
|
|
30
|
-
| Property | Type | Required | Default | Description |
|
|
31
|
-
| :------- | :-------- | :------: | :------ | :--------------------------------------------------------- |
|
|
32
|
-
| id | string | | | Уникальный идентификатор страницы настроек в рамках группы |
|
|
33
|
-
| title | string | true | | заголовок страницы настроек |
|
|
34
|
-
| icon | IconProps | true | | данные иконки, отображаемой в меню |
|
|
35
|
-
|
|
36
|
-
#### Settings.Section
|
|
37
|
-
|
|
38
|
-
| Property | Type | Required | Default | Description |
|
|
39
|
-
| :-------- | :-------- | :------: | :------ | :------------------------------------- |
|
|
40
|
-
| title | string | true | | заголовок раздела настроек на странице |
|
|
41
|
-
| header | ReactNode | | | Шапка секции |
|
|
42
|
-
| withBadge | boolean | | | Рисует бэйдж у секции и меню |
|
|
43
|
-
|
|
44
|
-
#### Settings.Item
|
|
45
|
-
|
|
46
|
-
| Property | Type | Required | Default | Description |
|
|
47
|
-
| :------------------- | :-------------- | :------: | :------- | :------------------------------------------ |
|
|
48
|
-
| title | string | true | | заголовок настройки |
|
|
49
|
-
| renderTitleComponent | Function | | | Произвольное содержимое заголовка настройки |
|
|
50
|
-
| align | 'top', 'center' | | 'center' | выравнивание заголовка и контрола |
|
|
51
|
-
|
|
52
|
-
### Использование
|
|
53
|
-
|
|
54
|
-
Смотрите в storybook пример `src/strories/demo/SettingsDemo`.
|
|
55
|
-
|
|
56
|
-
### Описание
|
|
57
|
-
|
|
58
|
-
Меню настроек может быть одно-уровневым:
|
|
59
|
-
|
|
60
|
-
```jsx
|
|
61
|
-
<Settings>
|
|
62
|
-
<Settings.Page title="Оформление">...</Settings.Page>
|
|
63
|
-
<Settings.Page title="Коммуникации">...</Settings.Page>
|
|
64
|
-
</Settings>
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
или двух-уровневым:
|
|
68
|
-
|
|
69
|
-
```jsx
|
|
70
|
-
<Settings>
|
|
71
|
-
<Settings.Group groupTitle="Arcanum">
|
|
72
|
-
<Settings.Page title="Оформление">...</Settings.Page>
|
|
73
|
-
<Settings.Page title="Коммуникации">...</Settings.Page>
|
|
74
|
-
</Settings.Group>
|
|
75
|
-
<Settings.Group groupTitle="General">
|
|
76
|
-
<Settings.Page title="Оформление">...</Settings.Page>
|
|
77
|
-
<Settings.Page title="Коммуникации">...</Settings.Page>
|
|
78
|
-
</Settings.Group>
|
|
79
|
-
</Settings>
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Страницы настроек делятся на секции с наборами настроек:
|
|
83
|
-
|
|
84
|
-
```jsx
|
|
85
|
-
<Settings.Page title="Features" icon={'...'}>
|
|
86
|
-
<Settings.Section title="Common">
|
|
87
|
-
<Settings.Item title="Default VCS">...</Settings.Item>
|
|
88
|
-
<Settings.Item title="Search root">...</Settings.Item>
|
|
89
|
-
</Settings.Section>
|
|
90
|
-
<Settings.Section title="Beta functionality">...</Settings.Section>
|
|
91
|
-
</Settings.Page>
|
|
92
|
-
```
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
.nv-settings {
|
|
2
|
-
display: grid;
|
|
3
|
-
grid-template-columns: 216px 1fr;
|
|
4
|
-
|
|
5
|
-
width: 834px;
|
|
6
|
-
height: 100%;
|
|
7
|
-
|
|
8
|
-
&_loading {
|
|
9
|
-
grid-template-columns: auto;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
&__loader {
|
|
13
|
-
align-self: center;
|
|
14
|
-
justify-self: center;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
&__not-found {
|
|
18
|
-
display: grid;
|
|
19
|
-
align-items: center;
|
|
20
|
-
justify-items: center;
|
|
21
|
-
|
|
22
|
-
height: 100%;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
&__menu {
|
|
26
|
-
border-right: 1px solid var(--yc-color-line-generic);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
&__heading {
|
|
30
|
-
margin: 20px 20px 0;
|
|
31
|
-
|
|
32
|
-
font-size: var(--yc-text-body-2-font-size);
|
|
33
|
-
font-weight: 500;
|
|
34
|
-
line-height: var(--yc-text-body-2-line-height);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
&__search {
|
|
38
|
-
margin: 12px 20px 16px;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
&__page {
|
|
42
|
-
overflow-y: auto;
|
|
43
|
-
|
|
44
|
-
padding: 20px;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
&__section {
|
|
48
|
-
&-heading {
|
|
49
|
-
margin: 0;
|
|
50
|
-
|
|
51
|
-
font-size: var(--yc-text-body-2-font-size);
|
|
52
|
-
font-weight: 500;
|
|
53
|
-
line-height: var(--yc-text-body-2-line-height);
|
|
54
|
-
|
|
55
|
-
&_badge {
|
|
56
|
-
position: relative;
|
|
57
|
-
|
|
58
|
-
display: inline-block;
|
|
59
|
-
|
|
60
|
-
&::after {
|
|
61
|
-
position: absolute;
|
|
62
|
-
top: 1px;
|
|
63
|
-
right: -8px;
|
|
64
|
-
|
|
65
|
-
display: block;
|
|
66
|
-
|
|
67
|
-
width: 6px;
|
|
68
|
-
height: 6px;
|
|
69
|
-
|
|
70
|
-
content: '';
|
|
71
|
-
|
|
72
|
-
border-radius: 50%;
|
|
73
|
-
background-color: var(--yc-color-text-danger);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
&-item {
|
|
79
|
-
margin-top: 24px;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
& + & {
|
|
83
|
-
margin-top: 32px;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
&__item {
|
|
88
|
-
display: grid;
|
|
89
|
-
grid-template-columns: 216px 1fr;
|
|
90
|
-
justify-items: start;
|
|
91
|
-
|
|
92
|
-
&_align_top {
|
|
93
|
-
align-items: start;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
&_align_center {
|
|
97
|
-
align-items: center;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
&__item-heading {
|
|
102
|
-
&_badge {
|
|
103
|
-
position: relative;
|
|
104
|
-
|
|
105
|
-
&::after {
|
|
106
|
-
position: absolute;
|
|
107
|
-
top: 1px;
|
|
108
|
-
right: -10px;
|
|
109
|
-
|
|
110
|
-
display: block;
|
|
111
|
-
|
|
112
|
-
width: 4px;
|
|
113
|
-
height: 4px;
|
|
114
|
-
|
|
115
|
-
content: '';
|
|
116
|
-
|
|
117
|
-
border-radius: 50%;
|
|
118
|
-
background-color: var(--yc-color-text-danger);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
&__found {
|
|
124
|
-
font-weight: 500;
|
|
125
|
-
|
|
126
|
-
background: var(--yc-color-base-selection);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import block from 'bem-cn-lite';
|
|
3
|
-
import i18n from './i18n';
|
|
4
|
-
|
|
5
|
-
import {IconProps, Loader} from '@gravity-ui/uikit';
|
|
6
|
-
import {SettingsSearch} from './SettingsSearch/SettingsSearch';
|
|
7
|
-
import {SettingsMenu, SettingsMenuItems, SettingsMenuInstance} from './SettingsMenu/SettingsMenu';
|
|
8
|
-
|
|
9
|
-
import {getSettingsFromChildren} from './collect-settings';
|
|
10
|
-
import {filterSettings} from './filter-settings';
|
|
11
|
-
import {escapeStringForRegExp} from './helpers';
|
|
12
|
-
|
|
13
|
-
import './Settings.scss';
|
|
14
|
-
|
|
15
|
-
const b = block('nv-settings');
|
|
16
|
-
|
|
17
|
-
interface SettingsProps {
|
|
18
|
-
initialPage?: string;
|
|
19
|
-
onPageChange?: (page: string | undefined) => void;
|
|
20
|
-
children: React.ReactNode;
|
|
21
|
-
renderNotFound?: () => React.ReactNode;
|
|
22
|
-
renderLoading?: () => React.ReactNode;
|
|
23
|
-
loading?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface SettingsGroupProps {
|
|
27
|
-
id?: string;
|
|
28
|
-
groupTitle: string;
|
|
29
|
-
children: React.ReactNode;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface SettingsPageProps {
|
|
33
|
-
id?: string;
|
|
34
|
-
title: string;
|
|
35
|
-
icon: IconProps;
|
|
36
|
-
children: React.ReactNode;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface SettingsSectionProps {
|
|
40
|
-
title: string;
|
|
41
|
-
header?: React.ReactNode;
|
|
42
|
-
children: React.ReactNode;
|
|
43
|
-
withBadge?: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface SettingsItemProps {
|
|
47
|
-
title: string;
|
|
48
|
-
renderTitleComponent?: (highlightedTitle: React.ReactNode | null) => React.ReactNode;
|
|
49
|
-
align?: 'top' | 'center';
|
|
50
|
-
children: React.ReactNode;
|
|
51
|
-
withBadge?: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const ItemContext = React.createContext<React.ReactNode>(undefined);
|
|
55
|
-
|
|
56
|
-
export function Settings({
|
|
57
|
-
initialPage,
|
|
58
|
-
onPageChange,
|
|
59
|
-
children,
|
|
60
|
-
renderNotFound,
|
|
61
|
-
loading,
|
|
62
|
-
renderLoading,
|
|
63
|
-
}: SettingsProps) {
|
|
64
|
-
const [search, setSearch] = React.useState('');
|
|
65
|
-
const [selectedPage, setCurrentPage] = React.useState<string | undefined>(initialPage);
|
|
66
|
-
const prevSelectedPageRef = React.useRef(initialPage);
|
|
67
|
-
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
|
68
|
-
const menuRef = React.useRef<SettingsMenuInstance>(null);
|
|
69
|
-
|
|
70
|
-
React.useEffect(() => {
|
|
71
|
-
menuRef.current?.clearFocus();
|
|
72
|
-
}, [search]);
|
|
73
|
-
|
|
74
|
-
React.useEffect(() => {
|
|
75
|
-
const handler = () => {
|
|
76
|
-
menuRef.current?.clearFocus();
|
|
77
|
-
};
|
|
78
|
-
window.addEventListener('click', handler);
|
|
79
|
-
return () => {
|
|
80
|
-
window.removeEventListener('click', handler);
|
|
81
|
-
};
|
|
82
|
-
}, []);
|
|
83
|
-
|
|
84
|
-
if (selectedPage !== prevSelectedPageRef.current) {
|
|
85
|
-
onPageChange?.(selectedPage);
|
|
86
|
-
prevSelectedPageRef.current = selectedPage;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (loading) {
|
|
90
|
-
return (
|
|
91
|
-
<div className={b({loading: true})}>
|
|
92
|
-
{typeof renderLoading === 'function' ? (
|
|
93
|
-
renderLoading()
|
|
94
|
-
) : (
|
|
95
|
-
<Loader className={b('loader')} size="m" />
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const {menu, pages} = getSettingsFromChildren(children);
|
|
102
|
-
filterSettings(pages, search, prepareTitle);
|
|
103
|
-
|
|
104
|
-
const settingsMenu: SettingsMenuItems = [];
|
|
105
|
-
for (const fistLevel of menu) {
|
|
106
|
-
if ('groupTitle' in fistLevel) {
|
|
107
|
-
settingsMenu.push({
|
|
108
|
-
groupTitle: fistLevel.groupTitle,
|
|
109
|
-
items: fistLevel.items.map((item) => ({
|
|
110
|
-
id: item.pageId,
|
|
111
|
-
title: item.title,
|
|
112
|
-
icon: item.icon,
|
|
113
|
-
disabled: pages[item.pageId].hide,
|
|
114
|
-
withBadge: item.withBadge,
|
|
115
|
-
})),
|
|
116
|
-
});
|
|
117
|
-
} else {
|
|
118
|
-
settingsMenu.push({
|
|
119
|
-
id: fistLevel.pageId,
|
|
120
|
-
title: fistLevel.title,
|
|
121
|
-
icon: fistLevel.icon,
|
|
122
|
-
disabled: pages[fistLevel.pageId].hide,
|
|
123
|
-
withBadge: fistLevel.withBadge,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
let activePage = selectedPage;
|
|
129
|
-
if (!activePage || pages[activePage]?.hide) {
|
|
130
|
-
activePage = undefined;
|
|
131
|
-
for (const firstLevel of settingsMenu) {
|
|
132
|
-
if ('groupTitle' in firstLevel) {
|
|
133
|
-
for (const item of firstLevel.items) {
|
|
134
|
-
if (!item.disabled) {
|
|
135
|
-
activePage = item.id;
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (activePage) {
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
} else if (!firstLevel.disabled) {
|
|
143
|
-
activePage = firstLevel.id;
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (activePage !== selectedPage) {
|
|
150
|
-
setCurrentPage(activePage);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<div className={b()}>
|
|
155
|
-
<div
|
|
156
|
-
className={b('menu')}
|
|
157
|
-
onClick={() => {
|
|
158
|
-
if (searchInputRef.current) {
|
|
159
|
-
searchInputRef.current.focus();
|
|
160
|
-
}
|
|
161
|
-
}}
|
|
162
|
-
onKeyDown={(event) => {
|
|
163
|
-
if (menuRef.current) {
|
|
164
|
-
if (menuRef.current.handleKeyDown(event)) {
|
|
165
|
-
event.preventDefault();
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}}
|
|
169
|
-
>
|
|
170
|
-
<h2 className={b('heading')}>{i18n('heading_settings')}</h2>
|
|
171
|
-
<SettingsSearch
|
|
172
|
-
inputRef={searchInputRef}
|
|
173
|
-
className={b('search')}
|
|
174
|
-
onChange={setSearch}
|
|
175
|
-
/>
|
|
176
|
-
<SettingsMenu
|
|
177
|
-
ref={menuRef}
|
|
178
|
-
items={settingsMenu}
|
|
179
|
-
onChange={setCurrentPage}
|
|
180
|
-
activeItem={activePage}
|
|
181
|
-
/>
|
|
182
|
-
</div>
|
|
183
|
-
<div className={b('page')}>
|
|
184
|
-
{activePage ? (
|
|
185
|
-
pages[activePage].sections.map((section) => {
|
|
186
|
-
if (section.hide) {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
return (
|
|
190
|
-
<div key={section.title} className={b('section')}>
|
|
191
|
-
<h3 className={b('section-heading', {badge: section.withBadge})}>
|
|
192
|
-
{section.title}
|
|
193
|
-
</h3>
|
|
194
|
-
{section.header ? section.header : null}
|
|
195
|
-
{section.items.map(({hide, title, children, titleComponent}) =>
|
|
196
|
-
hide ? null : (
|
|
197
|
-
<div key={title} className={b('section-item')}>
|
|
198
|
-
<ItemContext.Provider value={titleComponent}>
|
|
199
|
-
{children}
|
|
200
|
-
</ItemContext.Provider>
|
|
201
|
-
</div>
|
|
202
|
-
),
|
|
203
|
-
)}
|
|
204
|
-
</div>
|
|
205
|
-
);
|
|
206
|
-
})
|
|
207
|
-
) : typeof renderNotFound === 'function' ? (
|
|
208
|
-
renderNotFound()
|
|
209
|
-
) : (
|
|
210
|
-
<div className={b('not-found')}>{i18n('not-found')}</div>
|
|
211
|
-
)}
|
|
212
|
-
</div>
|
|
213
|
-
</div>
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
Settings.Group = function SettingsGroup({children}: SettingsGroupProps) {
|
|
218
|
-
return <React.Fragment>{children}</React.Fragment>;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
Settings.Page = function SettingsPage({children}: SettingsPageProps) {
|
|
222
|
-
return <React.Fragment>{children}</React.Fragment>;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
Settings.Section = function SettingsSection({children}: SettingsSectionProps) {
|
|
226
|
-
return <React.Fragment>{children}</React.Fragment>;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
Settings.Item = function SettingsItem({
|
|
230
|
-
title,
|
|
231
|
-
children,
|
|
232
|
-
align = 'center',
|
|
233
|
-
withBadge,
|
|
234
|
-
}: SettingsItemProps) {
|
|
235
|
-
const contextTitle = React.useContext(ItemContext);
|
|
236
|
-
return (
|
|
237
|
-
<div className={b('item', {align})}>
|
|
238
|
-
<label className={b('item-heading', {badge: withBadge})}>{contextTitle ?? title}</label>
|
|
239
|
-
<div>{children}</div>
|
|
240
|
-
</div>
|
|
241
|
-
);
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
function prepareTitle(string: string, search: string) {
|
|
245
|
-
let temp = string.slice(0);
|
|
246
|
-
const title: React.ReactNode[] = [];
|
|
247
|
-
const parts = escapeStringForRegExp(search).split(' ').filter(Boolean);
|
|
248
|
-
let key = 0;
|
|
249
|
-
for (const part of parts) {
|
|
250
|
-
const regex = new RegExp(part, 'ig');
|
|
251
|
-
const match = regex.exec(temp);
|
|
252
|
-
if (match) {
|
|
253
|
-
const m = match[0];
|
|
254
|
-
const i = match.index;
|
|
255
|
-
if (i > 0) {
|
|
256
|
-
title.push(temp.slice(0, i));
|
|
257
|
-
}
|
|
258
|
-
title.push(
|
|
259
|
-
<strong key={key++} className={b('found')}>
|
|
260
|
-
{m}
|
|
261
|
-
</strong>,
|
|
262
|
-
);
|
|
263
|
-
temp = temp.slice(i + m.length);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (temp) {
|
|
267
|
-
title.push(temp);
|
|
268
|
-
}
|
|
269
|
-
return title;
|
|
270
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
.nv-settings-menu {
|
|
2
|
-
&__group {
|
|
3
|
-
&-heading {
|
|
4
|
-
display: inline-block;
|
|
5
|
-
|
|
6
|
-
margin-bottom: 12px;
|
|
7
|
-
padding: 0 20px;
|
|
8
|
-
|
|
9
|
-
font-weight: 500;
|
|
10
|
-
line-height: 18px;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
& + & {
|
|
14
|
-
margin-top: 24px;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
&__item {
|
|
19
|
-
$item: &;
|
|
20
|
-
display: flex;
|
|
21
|
-
align-items: center;
|
|
22
|
-
|
|
23
|
-
height: 40px;
|
|
24
|
-
padding: 0 20px;
|
|
25
|
-
|
|
26
|
-
&[class][class] {
|
|
27
|
-
color: var(--yc-color-text-primary);
|
|
28
|
-
|
|
29
|
-
&#{$item}_disabled {
|
|
30
|
-
color: var(--yc-color-text-secondary);
|
|
31
|
-
|
|
32
|
-
#{$item}-icon {
|
|
33
|
-
color: var(--yc-color-base-misc-heavy);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
&-icon {
|
|
39
|
-
margin-right: 5px;
|
|
40
|
-
|
|
41
|
-
color: var(--yc-color-text-misc);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
&:hover,
|
|
45
|
-
&_focused {
|
|
46
|
-
background: var(--yc-color-base-simple-hover);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
&_selected {
|
|
50
|
-
background: var(--yc-color-base-selection);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
&_selected:hover,
|
|
54
|
-
&_selected#{$item}_focused {
|
|
55
|
-
background: var(--yc-color-base-selection-hover);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
&_badge {
|
|
59
|
-
position: relative;
|
|
60
|
-
|
|
61
|
-
&::after {
|
|
62
|
-
position: absolute;
|
|
63
|
-
top: calc(50% - 3px);
|
|
64
|
-
right: 9px;
|
|
65
|
-
|
|
66
|
-
display: block;
|
|
67
|
-
|
|
68
|
-
width: 6px;
|
|
69
|
-
height: 6px;
|
|
70
|
-
|
|
71
|
-
content: '';
|
|
72
|
-
|
|
73
|
-
border-radius: 50%;
|
|
74
|
-
background-color: var(--yc-color-text-danger);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|