ydb-embedded-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +28 -0
  3. package/dist/HOCS/WithSearch/WithSearch.js +26 -0
  4. package/dist/HOCS/index.js +1 -0
  5. package/dist/assets/icons/bug.svg +1 -0
  6. package/dist/assets/icons/close.svg +1 -0
  7. package/dist/assets/icons/control-menu-button.svg +1 -0
  8. package/dist/assets/icons/databases.svg +3 -0
  9. package/dist/assets/icons/dots.svg +1 -0
  10. package/dist/assets/icons/server.svg +1 -0
  11. package/dist/assets/icons/settings-with-dot.svg +1 -0
  12. package/dist/assets/icons/settings.svg +1 -0
  13. package/dist/assets/icons/signIn.svg +1 -0
  14. package/dist/assets/icons/signOut.svg +1 -0
  15. package/dist/assets/icons/star.svg +1 -0
  16. package/dist/assets/icons/storage.svg +1 -0
  17. package/dist/assets/icons/support.svg +1 -0
  18. package/dist/assets/icons/user-check.svg +1 -0
  19. package/dist/assets/icons/user-secret.svg +1 -0
  20. package/dist/assets/icons/ydb.svg +4 -0
  21. package/dist/components/AsideNavigation/AsideHeader.scss +148 -0
  22. package/dist/components/AsideNavigation/AsideHeader.tsx +388 -0
  23. package/dist/components/AsideNavigation/AsideHeaderFooterItem/AsideHeaderFooterItem.scss +82 -0
  24. package/dist/components/AsideNavigation/AsideHeaderFooterItem/AsideHeaderFooterItem.tsx +138 -0
  25. package/dist/components/AsideNavigation/AsideHeaderFooterSlot/AsideHeaderFooterSlot.tsx +33 -0
  26. package/dist/components/AsideNavigation/AsideHeaderFooterSlot/SlotsContext.tsx +49 -0
  27. package/dist/components/AsideNavigation/AsideHeaderTooltip/AsideHeaderTooltip.scss +16 -0
  28. package/dist/components/AsideNavigation/AsideHeaderTooltip/AsideHeaderTooltip.tsx +37 -0
  29. package/dist/components/AsideNavigation/CompositeBar/CompositeBar.scss +108 -0
  30. package/dist/components/AsideNavigation/CompositeBar/CompositeBar.tsx +282 -0
  31. package/dist/components/AsideNavigation/Content/Content.tsx +35 -0
  32. package/dist/components/AsideNavigation/Drawer/Drawer.scss +76 -0
  33. package/dist/components/AsideNavigation/Drawer/Drawer.tsx +134 -0
  34. package/dist/components/AsideNavigation/Drawer/index.ts +1 -0
  35. package/dist/components/AsideNavigation/Logo/Logo.scss +44 -0
  36. package/dist/components/AsideNavigation/Logo/Logo.tsx +82 -0
  37. package/dist/components/AsideNavigation/Settings/README.md +92 -0
  38. package/dist/components/AsideNavigation/Settings/Settings.scss +113 -0
  39. package/dist/components/AsideNavigation/Settings/Settings.tsx +270 -0
  40. package/dist/components/AsideNavigation/Settings/SettingsMenu/SettingsMenu.scss +70 -0
  41. package/dist/components/AsideNavigation/Settings/SettingsMenu/SettingsMenu.tsx +141 -0
  42. package/dist/components/AsideNavigation/Settings/SettingsSearch/SettingsSearch.tsx +57 -0
  43. package/dist/components/AsideNavigation/Settings/collect-settings.ts +156 -0
  44. package/dist/components/AsideNavigation/Settings/filter-settings.ts +38 -0
  45. package/dist/components/AsideNavigation/Settings/helpers.ts +39 -0
  46. package/dist/components/AsideNavigation/Settings/i18n/en.json +5 -0
  47. package/dist/components/AsideNavigation/Settings/i18n/index.ts +11 -0
  48. package/dist/components/AsideNavigation/Settings/i18n/ru.json +5 -0
  49. package/dist/components/AsideNavigation/Settings/index.ts +1 -0
  50. package/dist/components/AsideNavigation/constants.ts +28 -0
  51. package/dist/components/AsideNavigation/helpers.ts +34 -0
  52. package/dist/components/AsideNavigation/i18n/en.json +4 -0
  53. package/dist/components/AsideNavigation/i18n/index.ts +11 -0
  54. package/dist/components/AsideNavigation/i18n/ru.json +4 -0
  55. package/dist/components/AsideNavigation/icons.ts +32 -0
  56. package/dist/components/AsideNavigation/types.ts +23 -0
  57. package/dist/components/Breadcrumbs/Breadcrumbs.js +25 -0
  58. package/dist/components/Breadcrumbs/Breadcrumbs.scss +4 -0
  59. package/dist/components/ClusterInfo/ClusterInfo.scss +65 -0
  60. package/dist/components/ClusterInfo/ClusterInfo.tsx +157 -0
  61. package/dist/components/Collapse/Collapse.js +84 -0
  62. package/dist/components/Collapse/Collapse.scss +70 -0
  63. package/dist/components/CriticalActionDialog/CriticalActionDialog.js +53 -0
  64. package/dist/components/CriticalActionDialog/CriticalActionDialog.scss +31 -0
  65. package/dist/components/EmptyState/EmptyState.js +48 -0
  66. package/dist/components/EmptyState/EmptyState.scss +70 -0
  67. package/dist/components/EntityStatus/EntityStatus.js +96 -0
  68. package/dist/components/EntityStatus/EntityStatus.scss +81 -0
  69. package/dist/components/FullGroupViewer/FullGroupViewer.js +149 -0
  70. package/dist/components/FullGroupViewer/FullGroupViewer.scss +32 -0
  71. package/dist/components/FullNodeViewer/FullNodeViewer.js +108 -0
  72. package/dist/components/FullNodeViewer/FullNodeViewer.scss +78 -0
  73. package/dist/components/GroupTreeViewer/GroupTreeViewer.js +86 -0
  74. package/dist/components/GroupTreeViewer/GroupTreeViewer.scss +17 -0
  75. package/dist/components/GroupViewer/GroupViewer.js +100 -0
  76. package/dist/components/GroupViewer/GroupViewer.scss +41 -0
  77. package/dist/components/Hotkey/Hotkey.js +102 -0
  78. package/dist/components/Icon/Icon.js +26 -0
  79. package/dist/components/InfoViewer/InfoViewer.js +47 -0
  80. package/dist/components/InfoViewer/InfoViewer.scss +48 -0
  81. package/dist/components/InternalLink/InternalLink.js +15 -0
  82. package/dist/components/NodesViewer/NodesViewer.js +183 -0
  83. package/dist/components/NodesViewer/NodesViewer.scss +66 -0
  84. package/dist/components/PDiskViewer/PDiskViewer.js +80 -0
  85. package/dist/components/PDiskViewer/PDiskViewer.scss +40 -0
  86. package/dist/components/Pagination/Pagination.js +63 -0
  87. package/dist/components/Pagination/Pagination.scss +25 -0
  88. package/dist/components/PoolBar/PoolBar.js +52 -0
  89. package/dist/components/PoolBar/PoolBar.scss +40 -0
  90. package/dist/components/PoolUsage/PoolUsage.js +54 -0
  91. package/dist/components/PoolUsage/PoolUsage.scss +65 -0
  92. package/dist/components/PoolsGraph/PoolsGraph.js +33 -0
  93. package/dist/components/PoolsGraph/PoolsGraph.scss +3 -0
  94. package/dist/components/ProblemFilter/ProblemFilter.js +24 -0
  95. package/dist/components/ProgressViewer/ProgressViewer.js +92 -0
  96. package/dist/components/ProgressViewer/ProgressViewer.scss +84 -0
  97. package/dist/components/SplitPane/SplitPane.js +368 -0
  98. package/dist/components/SplitPane/SplitPane.scss +107 -0
  99. package/dist/components/SplitPane/index.js +3 -0
  100. package/dist/components/Tablet/Tablet.js +61 -0
  101. package/dist/components/Tablet/Tablet.scss +49 -0
  102. package/dist/components/TabletsStatistic/TabletsStatistic.js +58 -0
  103. package/dist/components/TabletsStatistic/TabletsStatistic.scss +41 -0
  104. package/dist/components/TabletsViewer/TabletsViewer.js +44 -0
  105. package/dist/components/TabletsViewer/TabletsViewer.scss +37 -0
  106. package/dist/components/Tag/Tag.js +14 -0
  107. package/dist/components/Tag/Tag.scss +17 -0
  108. package/dist/components/Tags/Tags.js +36 -0
  109. package/dist/components/Tags/Tags.scss +5 -0
  110. package/dist/components/TenantOverview/TenantOverview.js +148 -0
  111. package/dist/components/TenantOverview/TenantOverview.scss +75 -0
  112. package/dist/components/TreeView/TreeView.js +60 -0
  113. package/dist/components/TreeView/TreeView.scss +30 -0
  114. package/dist/components/TruncatedQuery/TruncatedQuery.js +26 -0
  115. package/dist/components/TruncatedQuery/TruncatedQuery.scss +12 -0
  116. package/dist/containers/App/App.js +67 -0
  117. package/dist/containers/App/App.scss +154 -0
  118. package/dist/containers/App/Content.js +109 -0
  119. package/dist/containers/App/TipPopup/TipPopup.js +66 -0
  120. package/dist/containers/App/TipPopup/TipPopup.scss +42 -0
  121. package/dist/containers/AppIcons/AppIcons.js +477 -0
  122. package/dist/containers/AsideNavigation/AsideNavigation.scss +43 -0
  123. package/dist/containers/AsideNavigation/AsideNavigation.tsx +228 -0
  124. package/dist/containers/Authentication/Authentication.scss +37 -0
  125. package/dist/containers/Authentication/Authentication.tsx +89 -0
  126. package/dist/containers/Cluster/Cluster.js +168 -0
  127. package/dist/containers/Cluster/Cluster.scss +52 -0
  128. package/dist/containers/Group/Group.js +97 -0
  129. package/dist/containers/Group/Group.scss +6 -0
  130. package/dist/containers/Header/Header.js +88 -0
  131. package/dist/containers/Header/Header.scss +72 -0
  132. package/dist/containers/Header/Host/Host.js +66 -0
  133. package/dist/containers/Header/Host/Host.scss +43 -0
  134. package/dist/containers/Heatmap/Heatmap.js +246 -0
  135. package/dist/containers/Heatmap/Heatmap.scss +94 -0
  136. package/dist/containers/Heatmap/HeatmapCanvas/HeatmapCanvas.js +167 -0
  137. package/dist/containers/Heatmap/Histogram/Histogram.js +108 -0
  138. package/dist/containers/Heatmap/Histogram/Histogram.scss +49 -0
  139. package/dist/containers/Heatmap/util.js +110 -0
  140. package/dist/containers/Node/Node.js +184 -0
  141. package/dist/containers/Node/Node.scss +34 -0
  142. package/dist/containers/Node/NodePages.js +13 -0
  143. package/dist/containers/Nodes/Nodes.js +193 -0
  144. package/dist/containers/Nodes/Nodes.scss +50 -0
  145. package/dist/containers/Pdisk/Pdisk.js +159 -0
  146. package/dist/containers/Pdisk/Pdisk.scss +40 -0
  147. package/dist/containers/Pool/Pool.js +169 -0
  148. package/dist/containers/Pool/Pool.scss +32 -0
  149. package/dist/containers/ReduxTooltip/ReduxTooltip.js +108 -0
  150. package/dist/containers/ReduxTooltip/ReduxTooltip.scss +67 -0
  151. package/dist/containers/StorageV2/DiskStateProgressBar/DiskStateProgressBar.scss +81 -0
  152. package/dist/containers/StorageV2/DiskStateProgressBar/DiskStateProgressBar.tsx +56 -0
  153. package/dist/containers/StorageV2/Pdisk/Pdisk.scss +32 -0
  154. package/dist/containers/StorageV2/Pdisk/Pdisk.tsx +167 -0
  155. package/dist/containers/StorageV2/Storage.js +249 -0
  156. package/dist/containers/StorageV2/Storage.scss +57 -0
  157. package/dist/containers/StorageV2/StorageFilter/StorageFilter.js +39 -0
  158. package/dist/containers/StorageV2/StorageGroups/StorageGroups.scss +26 -0
  159. package/dist/containers/StorageV2/StorageGroups/StorageGroups.tsx +234 -0
  160. package/dist/containers/StorageV2/StorageNodes/StorageNodes.scss +30 -0
  161. package/dist/containers/StorageV2/StorageNodes/StorageNodes.tsx +135 -0
  162. package/dist/containers/StorageV2/Vdisk/Vdisk.js +250 -0
  163. package/dist/containers/StorageV2/Vdisk/Vdisk.scss +32 -0
  164. package/dist/containers/Tablet/Tablet.js +453 -0
  165. package/dist/containers/Tablet/Tablet.scss +88 -0
  166. package/dist/containers/Tablets/Tablets.js +306 -0
  167. package/dist/containers/Tablets/Tablets.scss +77 -0
  168. package/dist/containers/TabletsFilters/TabletsFilters.js +412 -0
  169. package/dist/containers/TabletsFilters/TabletsFilters.scss +104 -0
  170. package/dist/containers/Tenant/Acl/Acl.js +149 -0
  171. package/dist/containers/Tenant/Acl/Acl.scss +34 -0
  172. package/dist/containers/Tenant/Compute/Compute.js +110 -0
  173. package/dist/containers/Tenant/Compute/Compute.scss +6 -0
  174. package/dist/containers/Tenant/Describe/Describe.js +81 -0
  175. package/dist/containers/Tenant/Describe/Describe.scss +25 -0
  176. package/dist/containers/Tenant/Healthcheck/Healthcheck.js +116 -0
  177. package/dist/containers/Tenant/Healthcheck/Healthcheck.scss +64 -0
  178. package/dist/containers/Tenant/Healthcheck/IssuesViewer/IssueViewer.scss +164 -0
  179. package/dist/containers/Tenant/Healthcheck/IssuesViewer/IssuesViewer.js +185 -0
  180. package/dist/containers/Tenant/Network/Network.js +341 -0
  181. package/dist/containers/Tenant/Network/Network.scss +145 -0
  182. package/dist/containers/Tenant/Network/NodeNetwork/NodeNetwork.js +71 -0
  183. package/dist/containers/Tenant/Network/NodeNetwork/NodeNetwork.scss +52 -0
  184. package/dist/containers/Tenant/Preview/Preview.js +169 -0
  185. package/dist/containers/Tenant/Preview/Preview.scss +20 -0
  186. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +649 -0
  187. package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +71 -0
  188. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +168 -0
  189. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.scss +42 -0
  190. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +58 -0
  191. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +24 -0
  192. package/dist/containers/Tenant/QueryEditor/SaveQuery/SaveQuery.js +171 -0
  193. package/dist/containers/Tenant/QueryEditor/SaveQuery/SaveQuery.scss +51 -0
  194. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.js +156 -0
  195. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.scss +82 -0
  196. package/dist/containers/Tenant/Schema/HotKeys/HotKeys.js +149 -0
  197. package/dist/containers/Tenant/Schema/HotKeys/HotKeys.scss +48 -0
  198. package/dist/containers/Tenant/Schema/Info/Info.js +84 -0
  199. package/dist/containers/Tenant/Schema/Info/Info.scss +3 -0
  200. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +67 -0
  201. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.scss +21 -0
  202. package/dist/containers/Tenant/Schema/SchemaMain/SchemaMain.js +439 -0
  203. package/dist/containers/Tenant/Schema/SchemaMain/SchemaMain.scss +90 -0
  204. package/dist/containers/Tenant/Schema/SchemaNode/SchemaNode.js +150 -0
  205. package/dist/containers/Tenant/Schema/SchemaNode/SchemaNode.scss +41 -0
  206. package/dist/containers/Tenant/Schema/SchemaPages.js +56 -0
  207. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.js +115 -0
  208. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.scss +13 -0
  209. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.js +63 -0
  210. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.scss +28 -0
  211. package/dist/containers/Tenant/Tenant.js +199 -0
  212. package/dist/containers/Tenant/Tenant.scss +94 -0
  213. package/dist/containers/Tenant/TenantPages.js +35 -0
  214. package/dist/containers/Tenant/TopQueries/TopQueries.js +184 -0
  215. package/dist/containers/Tenant/TopQueries/TopQueries.scss +53 -0
  216. package/dist/containers/Tenant/TopShards/TopShards.js +171 -0
  217. package/dist/containers/Tenant/TopShards/TopShards.scss +23 -0
  218. package/dist/containers/Tenants/Tenants.js +375 -0
  219. package/dist/containers/Tenants/Tenants.scss +73 -0
  220. package/dist/containers/UserSettings/UserSettings.tsx +57 -0
  221. package/dist/containers/Vdisk/Vdisk.js +160 -0
  222. package/dist/containers/Vdisk/Vdisk.scss +40 -0
  223. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +528 -0
  224. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +60 -0
  225. package/dist/contexts/HistoryContext.ts +6 -0
  226. package/dist/index.css +11 -0
  227. package/dist/index.js +28 -0
  228. package/dist/index.test.js +5 -0
  229. package/dist/react-app-env.d.ts +1 -0
  230. package/dist/reportWebVitals.js +13 -0
  231. package/dist/routes.js +40 -0
  232. package/dist/services/api.js +224 -0
  233. package/dist/setupTests.js +5 -0
  234. package/dist/store/index.js +61 -0
  235. package/dist/store/reducers/authentication.js +77 -0
  236. package/dist/store/reducers/cluster.js +52 -0
  237. package/dist/store/reducers/clusterInfo.js +48 -0
  238. package/dist/store/reducers/clusterNodes.js +70 -0
  239. package/dist/store/reducers/describe.js +45 -0
  240. package/dist/store/reducers/executeQuery.js +158 -0
  241. package/dist/store/reducers/executeTopQueries.js +69 -0
  242. package/dist/store/reducers/explainQuery.js +174 -0
  243. package/dist/store/reducers/group.js +49 -0
  244. package/dist/store/reducers/healthcheckInfo.js +45 -0
  245. package/dist/store/reducers/heatmap.js +105 -0
  246. package/dist/store/reducers/host.js +44 -0
  247. package/dist/store/reducers/hotKeys.js +57 -0
  248. package/dist/store/reducers/index.js +78 -0
  249. package/dist/store/reducers/network.js +45 -0
  250. package/dist/store/reducers/node.js +42 -0
  251. package/dist/store/reducers/nodes.js +58 -0
  252. package/dist/store/reducers/olapStats.js +74 -0
  253. package/dist/store/reducers/pdisk.js +51 -0
  254. package/dist/store/reducers/pool.js +42 -0
  255. package/dist/store/reducers/preview.js +73 -0
  256. package/dist/store/reducers/schema.js +95 -0
  257. package/dist/store/reducers/schemaAcl.js +44 -0
  258. package/dist/store/reducers/settings.js +76 -0
  259. package/dist/store/reducers/shardsWorkload.js +75 -0
  260. package/dist/store/reducers/storage.js +280 -0
  261. package/dist/store/reducers/tablet.js +94 -0
  262. package/dist/store/reducers/tablets.js +90 -0
  263. package/dist/store/reducers/tabletsFilters.js +126 -0
  264. package/dist/store/reducers/tenant.js +76 -0
  265. package/dist/store/reducers/tenants.js +61 -0
  266. package/dist/store/reducers/tooltip.js +64 -0
  267. package/dist/store/reducers/vdisk.js +49 -0
  268. package/dist/store/state-url-mapping.js +133 -0
  269. package/dist/store/utils.js +55 -0
  270. package/dist/styles/mixins.scss +254 -0
  271. package/dist/styles/react-treeview.scss +45 -0
  272. package/dist/types/assets.d.ts +12 -0
  273. package/dist/types/react-list.d.ts +4 -0
  274. package/dist/types/window.d.ts +33 -0
  275. package/dist/utils/actionsConstants.js +4 -0
  276. package/dist/utils/constants.js +126 -0
  277. package/dist/utils/getNodesColumns.js +156 -0
  278. package/dist/utils/i18n/i18n.ts +7 -0
  279. package/dist/utils/i18n/index.ts +1 -0
  280. package/dist/utils/index.js +136 -0
  281. package/dist/utils/monaco.js +69 -0
  282. package/dist/utils/prepareQueryExplain.ts +101 -0
  283. package/dist/utils/tooltip.js +197 -0
  284. package/dist/utils/utils.js +75 -0
  285. package/package.json +89 -0
@@ -0,0 +1,141 @@
1
+ import React from 'react';
2
+ import block from 'bem-cn-lite';
3
+
4
+ import {Icon, IconProps, Link} from '@yandex-cloud/uikit';
5
+
6
+ import {useCurrent, useStableCallback} from '../helpers';
7
+
8
+ import './SettingsMenu.scss';
9
+
10
+ const b = block('nv-settings-menu');
11
+
12
+ interface GroupItem {
13
+ groupTitle: string;
14
+ items: Item[];
15
+ }
16
+
17
+ interface Item {
18
+ id: string;
19
+ title: string;
20
+ icon?: IconProps;
21
+ disabled?: boolean;
22
+ withBadge?: boolean;
23
+ }
24
+
25
+ export type SettingsMenuItems = (GroupItem | Item)[];
26
+
27
+ interface SettingsMenuProps {
28
+ items: SettingsMenuItems;
29
+ onChange: (id: string) => void;
30
+ activeItem?: string;
31
+ focusItem?: string;
32
+ }
33
+
34
+ export interface SettingsMenuInstance {
35
+ handleKeyDown(event: React.KeyboardEvent): boolean;
36
+ clearFocus(): void;
37
+ }
38
+
39
+ export const SettingsMenu = React.forwardRef<SettingsMenuInstance, SettingsMenuProps>(
40
+ function SettingsMenu({items, onChange, activeItem}, ref) {
41
+ const [focusItem, setFocus] = React.useState<string>();
42
+ const containerRef = React.useRef<HTMLDivElement>(null);
43
+ const handleChange = useStableCallback(onChange);
44
+ const getFocused = useCurrent(focusItem);
45
+
46
+ React.useImperativeHandle(
47
+ ref,
48
+ () => ({
49
+ handleKeyDown(event) {
50
+ if (!containerRef.current) {
51
+ return false;
52
+ }
53
+ const focused = getFocused();
54
+ if (focused && event.key === 'Enter') {
55
+ handleChange(focused);
56
+ return true;
57
+ } else if (event.key === 'ArrowDown') {
58
+ setFocus(focusNext(containerRef.current, focused, 1));
59
+ return true;
60
+ } else if (event.key === 'ArrowUp') {
61
+ setFocus(focusNext(containerRef.current, focused, -1));
62
+ return true;
63
+ }
64
+ return false;
65
+ },
66
+ clearFocus() {
67
+ setFocus(undefined);
68
+ },
69
+ }),
70
+ [getFocused, handleChange],
71
+ );
72
+
73
+ return (
74
+ <div ref={containerRef} className={b()}>
75
+ {items.map((firstLevelItem) => {
76
+ if ('groupTitle' in firstLevelItem) {
77
+ return (
78
+ <div key={firstLevelItem.groupTitle} className={b('group')}>
79
+ <span className={b('group-heading')}>
80
+ {firstLevelItem.groupTitle}
81
+ </span>
82
+ {firstLevelItem.items.map((item) => {
83
+ return renderMenuItem(item, onChange, activeItem, focusItem);
84
+ })}
85
+ </div>
86
+ );
87
+ }
88
+ return renderMenuItem(firstLevelItem, onChange, activeItem, focusItem);
89
+ })}
90
+ </div>
91
+ );
92
+ },
93
+ );
94
+
95
+ function renderMenuItem(
96
+ item: Item,
97
+ onChange: (id: string) => void,
98
+ activeItem: string | undefined,
99
+ focusItem: string | undefined,
100
+ ) {
101
+ return (
102
+ <Link
103
+ key={item.title}
104
+ // @ts-ignore
105
+ extraProps={{'data-id': item.id, tabIndex: -1, disabled: item.disabled}}
106
+ className={b('item', {
107
+ selected: activeItem === item.id,
108
+ disabled: item.disabled,
109
+ focused: focusItem === item.id,
110
+ badge: item.withBadge,
111
+ })}
112
+ iconLeft={
113
+ item.icon ? <Icon size={16} {...item.icon} className={b('item-icon')} /> : undefined
114
+ }
115
+ onClick={() => {
116
+ onChange(item.id);
117
+ }}
118
+ >
119
+ {item.icon ? <Icon size={16} {...item.icon} className={b('item-icon')} /> : undefined}
120
+ <span>{item.title}</span>
121
+ </Link>
122
+ );
123
+ }
124
+
125
+ function focusNext(container: HTMLElement, focused: string | undefined, direction: number) {
126
+ const elements = container.querySelectorAll(`.${b('item')}:not(.${b('item')}_disabled)`);
127
+ if (elements.length === 0) {
128
+ return undefined;
129
+ }
130
+
131
+ let currentIndex = direction > 0 ? -1 : 0;
132
+ if (focused) {
133
+ currentIndex = Array.prototype.findIndex.call(
134
+ elements,
135
+ (element) => element.getAttribute('data-id') === focused,
136
+ );
137
+ }
138
+
139
+ currentIndex = (elements.length + currentIndex + direction) % elements.length;
140
+ return elements[currentIndex].getAttribute('data-id') ?? undefined;
141
+ }
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import block from 'bem-cn-lite';
3
+ import {TextInput} from '@yandex-cloud/uikit';
4
+
5
+ import i18n from '../i18n';
6
+
7
+ const b = block('nv-settings-search');
8
+
9
+ interface SettingsSearchProps {
10
+ className?: string;
11
+ onChange: (search: string) => void;
12
+ debounce?: number;
13
+ inputRef?: React.Ref<HTMLInputElement>;
14
+ }
15
+
16
+ export function SettingsSearch({
17
+ className,
18
+ onChange,
19
+ debounce = 200,
20
+ inputRef,
21
+ }: SettingsSearchProps) {
22
+ const [search, setSearch] = React.useState<string>();
23
+ const onChangeRef = React.useRef(onChange);
24
+ onChangeRef.current = onChange;
25
+ const debounceRef = React.useRef(debounce);
26
+ debounceRef.current = debounce;
27
+
28
+ React.useEffect(() => {
29
+ let timerId: number;
30
+ if (search !== undefined) {
31
+ timerId = window.setTimeout(() => {
32
+ onChangeRef.current(search);
33
+ }, debounceRef.current);
34
+ }
35
+ return () => {
36
+ clearTimeout(timerId);
37
+ };
38
+ }, [search]);
39
+ return (
40
+ <div className={b(null, className)}>
41
+ <TextInput
42
+ controlRef={(node) => {
43
+ if (typeof inputRef === 'function') {
44
+ inputRef(node as HTMLInputElement);
45
+ } else if (inputRef) {
46
+ (inputRef.current as HTMLInputElement) = node as HTMLInputElement;
47
+ }
48
+ }}
49
+ hasClear
50
+ autoFocus
51
+ placeholder={i18n('placeholder_search')}
52
+ value={search}
53
+ onUpdate={setSearch}
54
+ />
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,156 @@
1
+ import React from 'react';
2
+ import {IconProps} from '@yandex-cloud/uikit';
3
+ import {invariant} from './helpers';
4
+
5
+ type SettingsMenu = (SettingsMenuGroup | SettingsMenuItem)[];
6
+
7
+ interface SettingsMenuGroup {
8
+ groupTitle: string;
9
+ items: SettingsMenuItem[];
10
+ }
11
+
12
+ interface SettingsMenuItem {
13
+ title: string;
14
+ icon?: IconProps;
15
+ pageId: string;
16
+ withBadge?: boolean;
17
+ }
18
+
19
+ export interface SettingsPage {
20
+ sections: SettingsPageSection[];
21
+ hide?: boolean;
22
+ withBadge?: boolean;
23
+ }
24
+
25
+ interface SettingsPageSection {
26
+ title: string;
27
+ header?: React.ReactNode;
28
+ items: SettingsItem[];
29
+ hide?: boolean;
30
+ withBadge?: boolean;
31
+ }
32
+
33
+ interface SettingsItem {
34
+ title: string;
35
+ children: React.ReactNode;
36
+ hide?: boolean;
37
+ titleComponent?: React.ReactNode;
38
+ renderTitleComponent?: (highlightedTitle: React.ReactNode | null) => React.ReactNode;
39
+ }
40
+
41
+ export function getSettingsFromChildren(
42
+ children: React.ReactNode,
43
+ basepath = '',
44
+ ): {menu: SettingsMenu; pages: Record<string, SettingsPage>} {
45
+ const menu: SettingsMenu = [];
46
+ const pages: Record<string, SettingsPage> = {};
47
+ let hasGroup = false;
48
+ let hasItems = false;
49
+ React.Children.forEach(children, (element) => {
50
+ if (!React.isValidElement(element)) {
51
+ // Ignore non-elements.
52
+ return;
53
+ }
54
+ if (element.type === React.Fragment) {
55
+ // Transparently support React.Fragment and its children.
56
+ const {menu: menuFragment, pages: pagesFragment} = getSettingsFromChildren(
57
+ element.props.children,
58
+ basepath,
59
+ );
60
+ menu.push(...menuFragment);
61
+ Object.assign(pages, pagesFragment);
62
+ } else if (element.props.groupTitle) {
63
+ if (process.env.NODE_ENV === 'development') {
64
+ invariant(!hasItems, 'Setting menu must not mix groups and pages on one level');
65
+ }
66
+
67
+ const pageId = `${basepath}/${element.props.id ?? element.props.groupTitle}`;
68
+ hasGroup = true;
69
+
70
+ const {menu: menuFragment, pages: pagesFragment} = getSettingsFromChildren(
71
+ element.props.children,
72
+ pageId,
73
+ );
74
+
75
+ if (process.env.NODE_ENV === 'development') {
76
+ const hasInnerGroup = menuFragment.some((item) => 'groupTitle' in item);
77
+ invariant(
78
+ !hasInnerGroup,
79
+ `Group ${element.props.groupTitle} should not include groups`,
80
+ );
81
+ }
82
+
83
+ menu.push({
84
+ groupTitle: element.props.groupTitle,
85
+ // @ts-ignore
86
+ items: menuFragment,
87
+ });
88
+ Object.assign(pages, pagesFragment);
89
+ } else {
90
+ hasItems = true;
91
+ const pageId = `${basepath}/${element.props.id ?? element.props.title}`;
92
+
93
+ if (process.env.NODE_ENV === 'development') {
94
+ invariant(Boolean(element.props.title), 'Component must include title prop');
95
+ invariant(!hasGroup, 'Setting menu must not mix groups and pages on one level');
96
+ invariant(!pages[pageId], `Setting menu page id must be uniq (${pageId})`);
97
+ }
98
+
99
+ pages[pageId] = getSettingsPageFromChildren(element.props.children);
100
+ menu.push({
101
+ pageId,
102
+ title: element.props.title,
103
+ icon: element.props.icon,
104
+ withBadge: pages[pageId].withBadge,
105
+ });
106
+ }
107
+ });
108
+ return {menu, pages};
109
+ }
110
+
111
+ function getSettingsPageFromChildren(children: React.ReactNode): SettingsPage {
112
+ const page: SettingsPage = {sections: []};
113
+ React.Children.forEach(children, (element) => {
114
+ if (!React.isValidElement(element)) {
115
+ // Ignore non-elements.
116
+ return;
117
+ }
118
+ if (element.type === React.Fragment) {
119
+ // Transparently support React.Fragment and its children.
120
+ const {sections, withBadge} = getSettingsPageFromChildren(element.props.children);
121
+ page.sections.push(...sections);
122
+ page.withBadge = withBadge || page.withBadge;
123
+ } else {
124
+ const {title, header} = element.props;
125
+ page.withBadge = element.props.withBadge || page.withBadge;
126
+ page.sections.push({
127
+ title,
128
+ header,
129
+ withBadge: element.props.withBadge,
130
+ items: getSettingsItemsFromChildren(element.props.children),
131
+ });
132
+ }
133
+ });
134
+ return page;
135
+ }
136
+
137
+ function getSettingsItemsFromChildren(children: React.ReactNode): SettingsItem[] {
138
+ const items: SettingsItem[] = [];
139
+ React.Children.forEach(children, (element) => {
140
+ if (!React.isValidElement(element)) {
141
+ // Ignore non-elements.
142
+ return;
143
+ }
144
+ if (element.type === React.Fragment) {
145
+ // Transparently support React.Fragment and its children.
146
+ items.push(...getSettingsItemsFromChildren(element.props.children));
147
+ } else {
148
+ items.push({
149
+ title: element.props.title,
150
+ renderTitleComponent: element.props.renderTitleComponent,
151
+ children: element,
152
+ });
153
+ }
154
+ });
155
+ return items;
156
+ }
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import {escapeStringForRegExp} from './helpers';
3
+ import {SettingsPage} from './collect-settings';
4
+
5
+ function identity(x: any) {
6
+ return x;
7
+ }
8
+
9
+ export function filterSettings(
10
+ pages: Record<string, SettingsPage>,
11
+ search = '',
12
+ wrapFoundTitle: (title: string, search: string) => React.ReactNode = identity,
13
+ ) {
14
+ // 'abc def fg' -> abc.*?cde.*?fg
15
+ const preparedFilter = escapeStringForRegExp(search).replace(/\s+/g, '.*?');
16
+ const filterRe = new RegExp(preparedFilter, 'i');
17
+ for (const page of Object.values(pages)) {
18
+ let hidePage = true;
19
+ for (const section of page.sections) {
20
+ let hideSection = true;
21
+ for (const item of section.items) {
22
+ item.hide = Boolean(search) && !filterRe.test(item.title);
23
+ if (item.renderTitleComponent) {
24
+ item.titleComponent = item.renderTitleComponent(
25
+ search && !item.hide ? wrapFoundTitle(item.title, search) : null,
26
+ );
27
+ } else {
28
+ item.titleComponent =
29
+ search && !item.hide ? wrapFoundTitle(item.title, search) : item.title;
30
+ }
31
+ hideSection = hideSection && item.hide;
32
+ }
33
+ section.hide = hideSection;
34
+ hidePage = hidePage && hideSection;
35
+ }
36
+ page.hide = hidePage;
37
+ }
38
+ }
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+
3
+ type AnyFunc = (...args: any[]) => any;
4
+
5
+ export function useStableCallback<T extends AnyFunc>(
6
+ func: T,
7
+ ): (...args: Parameters<T>) => ReturnType<T> | undefined {
8
+ const funcRef = React.useRef<T>();
9
+
10
+ React.useEffect(() => {
11
+ funcRef.current = func;
12
+ return () => {
13
+ funcRef.current = undefined;
14
+ };
15
+ }, [func]);
16
+
17
+ return React.useCallback((...args: Parameters<T>) => {
18
+ if (typeof funcRef.current === 'function') {
19
+ return funcRef.current(...args);
20
+ }
21
+ return undefined;
22
+ }, []);
23
+ }
24
+
25
+ export function useCurrent<T>(value: T) {
26
+ const ref = React.useRef(value);
27
+ ref.current = value;
28
+ return React.useCallback(() => ref.current, []);
29
+ }
30
+
31
+ export function invariant(cond: boolean, message: string): void {
32
+ if (!cond) {
33
+ throw new Error(message);
34
+ }
35
+ }
36
+
37
+ export function escapeStringForRegExp(input: string) {
38
+ return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
39
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "heading_settings": "Settings",
3
+ "placeholder_search": "Search settings",
4
+ "not-found": "No results found"
5
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, I18N} from '../../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'nv-settings';
7
+
8
+ i18n.registerKeyset(I18N.LANGS.en, COMPONENT, en);
9
+ i18n.registerKeyset(I18N.LANGS.ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,5 @@
1
+ {
2
+ "heading_settings": "Настройки",
3
+ "placeholder_search": "Найти настройки",
4
+ "not-found": "Ничего не найдено"
5
+ }
@@ -0,0 +1 @@
1
+ export * from './Settings';
@@ -0,0 +1,28 @@
1
+ export const ASIDE_HEADER_COMPACT_WIDTH = 56;
2
+ export const ASIDE_HEADER_EXPANDED_WIDTH = 236;
3
+ export const ASIDE_HEADER_COLLAPSE_BUTTON_SIZE = 28;
4
+
5
+ export const ASIDE_HEADER_ICON_SIZE = 24;
6
+
7
+ export const ASIDE_HEADER_STORE_KEY = 'nvAsideHeader';
8
+
9
+ export enum AsideHeaderEvent {
10
+ SETTINGS_OPEN = 'SETTINGS_OPEN',
11
+ SETTINGS_CLOSE = 'SETTINGS_CLOSE',
12
+ }
13
+
14
+ export enum AsideHeaderVisibleItem {
15
+ Settings = 'settings',
16
+ }
17
+
18
+ export enum FooterItemIcon {
19
+ Bug = 'bug',
20
+ Support = 'support',
21
+ Settings = 'settings',
22
+ SettingsWithDot = 'settings-with-dot',
23
+ }
24
+
25
+ export enum FooterItemIconView {
26
+ Normal = 'normal',
27
+ WithDot = 'with-dot',
28
+ }
@@ -0,0 +1,34 @@
1
+ import {ASIDE_HEADER_STORE_KEY} from './constants';
2
+ import {AsideHeaderLocalStorage} from './types';
3
+
4
+ function store<T = unknown>(key: string, data: T) {
5
+ try {
6
+ window.localStorage.setItem(key, JSON.stringify(data));
7
+ } catch (err) {
8
+ console.error(`data not saved in localeStorage: ${err}`);
9
+ }
10
+ }
11
+
12
+ function restore<T = any>(key: string): T | null {
13
+ try {
14
+ const data = window.localStorage.getItem(key);
15
+ if (data === null) {
16
+ return null;
17
+ }
18
+ return JSON.parse(data) as T;
19
+ } catch (err) {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ export function getLocalData() {
25
+ return restore<AsideHeaderLocalStorage>(ASIDE_HEADER_STORE_KEY);
26
+ }
27
+
28
+ export function setLocalData(data: Partial<AsideHeaderLocalStorage> | null) {
29
+ const storeData = getLocalData();
30
+ store<AsideHeaderLocalStorage>(ASIDE_HEADER_STORE_KEY, {
31
+ ...storeData,
32
+ ...data,
33
+ });
34
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "switch_settings": "Settings",
3
+ "label_more": "More"
4
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, I18N} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-embedded-ui';
7
+
8
+ i18n.registerKeyset(I18N.LANGS.en, COMPONENT, en);
9
+ i18n.registerKeyset(I18N.LANGS.ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,4 @@
1
+ {
2
+ "switch_settings": "Настройки",
3
+ "label_more": "Ещё"
4
+ }
@@ -0,0 +1,32 @@
1
+ import {FooterItemIcon, FooterItemIconView} from './constants';
2
+ import {SlotName} from './AsideHeaderFooterSlot/AsideHeaderFooterSlot';
3
+
4
+ import bugIcon from '../../assets/icons/bug.svg';
5
+ import supportIcon from '../../assets/icons/support.svg';
6
+ import settingsIcon from '../../assets/icons/settings.svg';
7
+ import settingsWithDotIcon from '../../assets/icons/settings-with-dot.svg';
8
+
9
+ export const footerItemIconMap = {
10
+ [FooterItemIcon.Bug]: bugIcon,
11
+ [FooterItemIcon.Support]: supportIcon,
12
+ [FooterItemIcon.Settings]: settingsIcon,
13
+ [FooterItemIcon.SettingsWithDot]: settingsWithDotIcon,
14
+ };
15
+
16
+ export function getFooterItemIcon(slot: SlotName, view?: FooterItemIconView) {
17
+ switch (slot) {
18
+ case SlotName.BugReport:
19
+ return footerItemIconMap[FooterItemIcon.Bug];
20
+ case SlotName.Support:
21
+ return footerItemIconMap[FooterItemIcon.Support];
22
+ case SlotName.Settings:
23
+ switch (view) {
24
+ case FooterItemIconView.WithDot:
25
+ return footerItemIconMap[FooterItemIcon.SettingsWithDot];
26
+ default:
27
+ return footerItemIconMap[FooterItemIcon.Settings];
28
+ }
29
+ default:
30
+ return undefined;
31
+ }
32
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+
3
+ export interface AsideHeaderMenuItem {
4
+ id: string;
5
+ title: string;
6
+ tooltipText?: string;
7
+ icon?: SVGIconData;
8
+ iconSize?: number | string;
9
+ link?: string;
10
+ current?: boolean;
11
+ pinned?: boolean;
12
+ onItemClick?: (item: AsideHeaderMenuItem, isCollapsed: boolean) => void;
13
+ itemWrapper?: (
14
+ node: React.ReactNode,
15
+ item: AsideHeaderMenuItem,
16
+ isCollapsed: boolean,
17
+ isCompact: boolean,
18
+ ) => React.ReactNode;
19
+ }
20
+
21
+ export interface AsideHeaderLocalStorage {
22
+ isCompact?: boolean;
23
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import cn from 'bem-cn-lite';
4
+ import {Breadcrumbs as BreadcrumbsUiKit} from '@yandex-cloud/uikit';
5
+
6
+ import './Breadcrumbs.scss';
7
+
8
+ const b = cn('kv-breadcrumbs');
9
+
10
+ class Breadcrumbs extends React.Component {
11
+ static propTypes = {
12
+ items: PropTypes.array,
13
+ };
14
+
15
+ static defaultProps = {
16
+ items: [],
17
+ };
18
+
19
+ render() {
20
+ const {items} = this.props;
21
+ return <BreadcrumbsUiKit items={items} firstDisplayedItemsCount={1} className={b()} />;
22
+ }
23
+ }
24
+
25
+ export default Breadcrumbs;
@@ -0,0 +1,4 @@
1
+ .kv-breadcrumbs {
2
+ font-size: var(--yc-text-body2-font-size);
3
+ padding: 20px 0;
4
+ }