@cloud-ru/uikit-product-page-layout 0.21.6 → 0.22.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 (46) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/components/TreeNavigation/TreeNavigation.d.ts +42 -0
  3. package/dist/cjs/components/TreeNavigation/TreeNavigation.js +23 -0
  4. package/dist/cjs/components/TreeNavigation/helper-components/ConditionalPopover/ConditionalPopover.d.ts +10 -0
  5. package/dist/cjs/components/TreeNavigation/helper-components/ConditionalPopover/ConditionalPopover.js +18 -0
  6. package/dist/cjs/components/TreeNavigation/helper-components/ConditionalPopover/styles.module.css +6 -0
  7. package/dist/cjs/components/TreeNavigation/helper-components/Menu/Menu.d.ts +9 -0
  8. package/dist/cjs/components/TreeNavigation/helper-components/Menu/Menu.js +25 -0
  9. package/dist/cjs/components/TreeNavigation/helper-components/Menu/styles.module.css +12 -0
  10. package/dist/cjs/components/TreeNavigation/helper-components/Menu/utils.d.ts +7 -0
  11. package/dist/cjs/components/TreeNavigation/helper-components/Menu/utils.js +20 -0
  12. package/dist/cjs/components/TreeNavigation/helper-components/index.d.ts +2 -0
  13. package/dist/cjs/components/TreeNavigation/helper-components/index.js +7 -0
  14. package/dist/cjs/components/TreeNavigation/index.d.ts +1 -0
  15. package/dist/cjs/components/TreeNavigation/index.js +17 -0
  16. package/dist/cjs/components/TreeNavigation/styles.module.css +82 -0
  17. package/dist/cjs/components/index.d.ts +1 -0
  18. package/dist/cjs/components/index.js +1 -0
  19. package/dist/esm/components/TreeNavigation/TreeNavigation.d.ts +42 -0
  20. package/dist/esm/components/TreeNavigation/TreeNavigation.js +17 -0
  21. package/dist/esm/components/TreeNavigation/helper-components/ConditionalPopover/ConditionalPopover.d.ts +10 -0
  22. package/dist/esm/components/TreeNavigation/helper-components/ConditionalPopover/ConditionalPopover.js +12 -0
  23. package/dist/esm/components/TreeNavigation/helper-components/ConditionalPopover/styles.module.css +6 -0
  24. package/dist/esm/components/TreeNavigation/helper-components/Menu/Menu.d.ts +9 -0
  25. package/dist/esm/components/TreeNavigation/helper-components/Menu/Menu.js +19 -0
  26. package/dist/esm/components/TreeNavigation/helper-components/Menu/styles.module.css +12 -0
  27. package/dist/esm/components/TreeNavigation/helper-components/Menu/utils.d.ts +7 -0
  28. package/dist/esm/components/TreeNavigation/helper-components/Menu/utils.js +16 -0
  29. package/dist/esm/components/TreeNavigation/helper-components/index.d.ts +2 -0
  30. package/dist/esm/components/TreeNavigation/helper-components/index.js +2 -0
  31. package/dist/esm/components/TreeNavigation/index.d.ts +1 -0
  32. package/dist/esm/components/TreeNavigation/index.js +1 -0
  33. package/dist/esm/components/TreeNavigation/styles.module.css +82 -0
  34. package/dist/esm/components/index.d.ts +1 -0
  35. package/dist/esm/components/index.js +1 -0
  36. package/package.json +5 -2
  37. package/src/components/TreeNavigation/TreeNavigation.tsx +128 -0
  38. package/src/components/TreeNavigation/helper-components/ConditionalPopover/ConditionalPopover.tsx +34 -0
  39. package/src/components/TreeNavigation/helper-components/ConditionalPopover/styles.module.scss +7 -0
  40. package/src/components/TreeNavigation/helper-components/Menu/Menu.tsx +50 -0
  41. package/src/components/TreeNavigation/helper-components/Menu/styles.module.scss +14 -0
  42. package/src/components/TreeNavigation/helper-components/Menu/utils.ts +22 -0
  43. package/src/components/TreeNavigation/helper-components/index.ts +2 -0
  44. package/src/components/TreeNavigation/index.ts +1 -0
  45. package/src/components/TreeNavigation/styles.module.scss +86 -0
  46. package/src/components/index.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 0.22.0 (2025-11-24)
7
+
8
+
9
+ ### Features
10
+
11
+ * **PDS-2935:** add new component TreeNavigation for Page Layout ([ef1a595](https://gitverse.ru/cloud-ru-tech/uikit-product/commits/ef1a595fb34619087d5930de5affe72bff4e867b))
12
+
13
+
14
+
15
+
16
+
6
17
  ## 0.21.6 (2025-11-13)
7
18
 
8
19
 
@@ -0,0 +1,42 @@
1
+ import { ReactNode } from 'react';
2
+ import { StatusProps } from '@snack-uikit/status';
3
+ import { TreeNodeProps } from '@snack-uikit/tree';
4
+ export type TreeNavigationProps = {
5
+ header: {
6
+ /** Текст заголовка */
7
+ title: string;
8
+ /** Иконка */
9
+ icon?: ReactNode;
10
+ /** Текст описания */
11
+ description?: string;
12
+ /** Статус (цвет, иконка и т.п.) – любой тип, который принимает ваш <Status/> */
13
+ status?: StatusProps;
14
+ /** Раздел для действий */
15
+ actions?: ReactNode;
16
+ };
17
+ menu: {
18
+ /** Заголовок меню */
19
+ menuTitle?: string;
20
+ /** Данные для дерева меню */
21
+ items: TreeNodeProps[];
22
+ /** Управляемый режим: если передан, меню открывается как popover. */
23
+ isMenuOpen?: boolean;
24
+ /** Колбэк, вызываемый при попытке изменить состояние меню.
25
+ * В контролируемом режиме обязателен, в неконтролируемом – опционален.
26
+ */
27
+ onMenuToggle?: (open: boolean) => void;
28
+ /** Открывать меню по умолчанию */
29
+ defaultMenuOpened?: boolean;
30
+ /** Позляет отключить кнопку "Свернуть все"*/
31
+ enableShrinkMenuButton?: boolean;
32
+ /** Открывать пункты меню по умолчанию */
33
+ withDefaultOpenedMenuList?: boolean;
34
+ };
35
+ /** Контентная часть страницы */
36
+ content: ReactNode;
37
+ /** Вариант отображения */
38
+ mode: 'popover' | 'aside';
39
+ /** Класс для контейнера контентной части */
40
+ contentClassName?: string;
41
+ };
42
+ export declare function TreeNavigation({ header: { title, icon, description, status, actions }, menu: { menuTitle, items, enableShrinkMenuButton, withDefaultOpenedMenuList, isMenuOpen, defaultMenuOpened, onMenuToggle, }, content, mode, contentClassName, }: TreeNavigationProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TreeNavigation = TreeNavigation;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const classnames_1 = __importDefault(require("classnames"));
9
+ const react_1 = require("react");
10
+ const uncontrollable_1 = require("uncontrollable");
11
+ const uikit_product_icons_1 = require("@cloud-ru/uikit-product-icons");
12
+ const uikit_product_locale_1 = require("@cloud-ru/uikit-product-locale");
13
+ const button_1 = require("@snack-uikit/button");
14
+ const status_1 = require("@snack-uikit/status");
15
+ const typography_1 = require("@snack-uikit/typography");
16
+ const helper_components_1 = require("./helper-components");
17
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
18
+ function TreeNavigation({ header: { title, icon, description, status, actions }, menu: { menuTitle, items, enableShrinkMenuButton, withDefaultOpenedMenuList, isMenuOpen, defaultMenuOpened, onMenuToggle, }, content, mode, contentClassName, }) {
19
+ const [open, setOpen] = (0, uncontrollable_1.useUncontrolledProp)(isMenuOpen, defaultMenuOpened, onMenuToggle);
20
+ const { t } = (0, uikit_product_locale_1.useLocale)('PageLayout');
21
+ const menu = (0, react_1.useMemo)(() => ((0, jsx_runtime_1.jsx)(helper_components_1.Menu, { menuItems: items, menuTitle: menuTitle, enableShrinkMenuButton: enableShrinkMenuButton, withDefaultOpenedMenuList: withDefaultOpenedMenuList })), [items, menuTitle, enableShrinkMenuButton, withDefaultOpenedMenuList]);
22
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.root, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.header, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.titleWrapper, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.titleInner, children: [(0, jsx_runtime_1.jsx)(helper_components_1.ConditionalPopover, { isOpen: Boolean(open), onOpenChange: setOpen, tip: menu, withPopover: mode === 'popover', children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.innerElement, children: (0, jsx_runtime_1.jsx)(button_1.ButtonSimple, { size: 'xs', "aria-label": open ? t('TreeNavigation.closeMenu') : t('TreeNavigation.openMenu'), icon: open ? (0, jsx_runtime_1.jsx)(uikit_product_icons_1.CloseSVG, {}) : (0, jsx_runtime_1.jsx)(uikit_product_icons_1.BurgerSVG, {}), onClick: () => setOpen(!open) }) }) }), icon && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.innerElement, children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.icon, children: icon }) })), (0, jsx_runtime_1.jsx)(typography_1.Typography.SansTitleL, { className: styles_module_scss_1.default.title, children: title }), status && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.innerElement, children: (0, jsx_runtime_1.jsx)(status_1.Status, Object.assign({}, status)) }))] }), description && (0, jsx_runtime_1.jsx)(typography_1.Typography.SansBodyS, { className: styles_module_scss_1.default.description, children: description })] }), actions] }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.body, children: [mode === 'aside' && open && (0, jsx_runtime_1.jsx)("aside", { className: styles_module_scss_1.default.sidebar, children: menu }), (0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.main, contentClassName), children: content })] })] }));
23
+ }
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ type ConditionalPopoverProps = {
3
+ isOpen: boolean;
4
+ onOpenChange: (value: boolean) => void;
5
+ tip: ReactNode;
6
+ withPopover?: boolean;
7
+ children: ReactNode;
8
+ };
9
+ export declare function ConditionalPopover({ tip, withPopover, isOpen, onOpenChange, children }: ConditionalPopoverProps): string | number | boolean | Iterable<ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
10
+ export {};
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConditionalPopover = ConditionalPopover;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const popover_1 = require("@snack-uikit/popover");
9
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
10
+ function ConditionalPopover({ tip, withPopover, isOpen, onOpenChange, children }) {
11
+ if (withPopover) {
12
+ return ((0, jsx_runtime_1.jsx)(popover_1.Popover, { className: styles_module_scss_1.default.popover, open: isOpen, onOpenChange: () => {
13
+ if (!open)
14
+ onOpenChange(false);
15
+ }, tip: tip, trigger: 'click', placement: 'bottom-start', children: children }));
16
+ }
17
+ return children;
18
+ }
@@ -0,0 +1,6 @@
1
+ .popover{
2
+ max-height:80%;
3
+ }
4
+ .popover > div{
5
+ overflow:auto;
6
+ }
@@ -0,0 +1,9 @@
1
+ import { TreeNodeProps } from '@snack-uikit/tree';
2
+ type MenuProps = {
3
+ menuTitle?: string;
4
+ menuItems: TreeNodeProps[];
5
+ enableShrinkMenuButton?: boolean;
6
+ withDefaultOpenedMenuList?: boolean;
7
+ };
8
+ export declare function Menu({ menuTitle, menuItems, enableShrinkMenuButton, withDefaultOpenedMenuList }: MenuProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Menu = Menu;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const uikit_product_icons_1 = require("@cloud-ru/uikit-product-icons");
10
+ const uikit_product_locale_1 = require("@cloud-ru/uikit-product-locale");
11
+ const button_1 = require("@snack-uikit/button");
12
+ const tree_1 = require("@snack-uikit/tree");
13
+ const typography_1 = require("@snack-uikit/typography");
14
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
15
+ const utils_1 = require("./utils");
16
+ function Menu({ menuTitle, menuItems, enableShrinkMenuButton = true, withDefaultOpenedMenuList }) {
17
+ const { t } = (0, uikit_product_locale_1.useLocale)('PageLayout');
18
+ const allExpandedNodes = (0, react_1.useMemo)(() => (0, utils_1.getExpandedNodes)(menuItems), [menuItems]);
19
+ const [expandedNodes, setExpandedNodes] = (0, react_1.useState)(withDefaultOpenedMenuList ? allExpandedNodes : []);
20
+ const isExpanded = expandedNodes.length > 0;
21
+ const handleExpandAll = () => setExpandedNodes((0, utils_1.getExpandedNodes)(menuItems));
22
+ const handleCollapseAll = () => setExpandedNodes([]);
23
+ const showSubheader = Boolean(menuTitle || enableShrinkMenuButton);
24
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.sidebar, children: [showSubheader && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.subheader, children: [(0, jsx_runtime_1.jsx)(typography_1.Typography.SansTitleM, { children: menuTitle }), enableShrinkMenuButton && ((0, jsx_runtime_1.jsx)(button_1.ButtonSimple, { label: isExpanded ? t('TreeNavigation.collapseAll') : t('TreeNavigation.expandAll'), icon: (0, jsx_runtime_1.jsx)(uikit_product_icons_1.HorizontalMenuCloseSVG, {}), onClick: isExpanded ? handleCollapseAll : handleExpandAll }))] })), (0, jsx_runtime_1.jsx)(tree_1.Tree, { data: menuItems, selectionMode: 'single', expandedNodes: expandedNodes, onExpand: setExpandedNodes })] }));
25
+ }
@@ -0,0 +1,12 @@
1
+ .sidebar{
2
+ min-width:292px;
3
+ display:flex;
4
+ flex-direction:column;
5
+ gap:12px;
6
+ }
7
+
8
+ .subheader{
9
+ display:flex;
10
+ align-items:center;
11
+ justify-content:space-between;
12
+ }
@@ -0,0 +1,7 @@
1
+ import { TreeNodeProps } from '@snack-uikit/tree';
2
+ /**
3
+ * Рекурсивно собирает все ID узлов дерева для их разворачивания
4
+ * @param nodes - Массив узлов дерева
5
+ * @returns Массив ID всех узлов (включая вложенные)
6
+ */
7
+ export declare const getExpandedNodes: (nodes: TreeNodeProps[]) => string[];
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getExpandedNodes = void 0;
4
+ /**
5
+ * Рекурсивно собирает все ID узлов дерева для их разворачивания
6
+ * @param nodes - Массив узлов дерева
7
+ * @returns Массив ID всех узлов (включая вложенные)
8
+ */
9
+ const getExpandedNodes = (nodes) => {
10
+ if (!nodes || nodes.length === 0) {
11
+ return [];
12
+ }
13
+ const ids = [];
14
+ nodes.forEach(el => {
15
+ const children = el.nested ? (0, exports.getExpandedNodes)(el.nested) : [];
16
+ ids.push(el.id, ...children);
17
+ });
18
+ return ids;
19
+ };
20
+ exports.getExpandedNodes = getExpandedNodes;
@@ -0,0 +1,2 @@
1
+ export { Menu } from './Menu/Menu';
2
+ export { ConditionalPopover } from './ConditionalPopover/ConditionalPopover';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConditionalPopover = exports.Menu = void 0;
4
+ var Menu_1 = require("./Menu/Menu");
5
+ Object.defineProperty(exports, "Menu", { enumerable: true, get: function () { return Menu_1.Menu; } });
6
+ var ConditionalPopover_1 = require("./ConditionalPopover/ConditionalPopover");
7
+ Object.defineProperty(exports, "ConditionalPopover", { enumerable: true, get: function () { return ConditionalPopover_1.ConditionalPopover; } });
@@ -0,0 +1 @@
1
+ export * from './TreeNavigation';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./TreeNavigation"), exports);
@@ -0,0 +1,82 @@
1
+ .root{
2
+ display:flex;
3
+ flex-direction:column;
4
+ height:100%;
5
+ width:100%;
6
+ }
7
+
8
+ .header{
9
+ width:100%;
10
+ display:flex;
11
+ justify-content:space-between;
12
+ align-items:flex-start;
13
+ gap:40px;
14
+ padding:8px 24px;
15
+ background-color:var(--sys-neutral-background, #eeeff3);
16
+ border-bottom:1px solid var(--sys-neutral-decor-default, #dde0ea);
17
+ box-sizing:border-box;
18
+ }
19
+
20
+ .description{
21
+ color:var(--sys-neutral-text-support, #6d707f);
22
+ }
23
+
24
+ .body{
25
+ display:flex;
26
+ gap:16px;
27
+ padding:0 24px;
28
+ box-sizing:border-box;
29
+ width:100%;
30
+ }
31
+
32
+ .sidebar{
33
+ border-radius:12px;
34
+ padding:16px;
35
+ height:-moz-max-content;
36
+ height:max-content;
37
+ background:var(--sys-neutral-background1-level, #fdfdfd);
38
+ }
39
+
40
+ .main{
41
+ padding-top:16px;
42
+ width:100%;
43
+ box-sizing:border-box;
44
+ }
45
+
46
+ .titleWrapper{
47
+ display:flex;
48
+ align-items:center;
49
+ flex-wrap:wrap;
50
+ -moz-column-gap:16px;
51
+ column-gap:16px;
52
+ row-gap:8px;
53
+ }
54
+
55
+ .titleInner{
56
+ display:flex;
57
+ gap:8px;
58
+ }
59
+
60
+ .title{
61
+ box-sizing:border-box;
62
+ max-width:100%;
63
+ margin:0;
64
+ padding:0;
65
+ overflow-wrap:break-word;
66
+ }
67
+
68
+ .innerElement{
69
+ display:flex;
70
+ align-items:center;
71
+ justify-content:center;
72
+ height:var(--sans-title-l-line-height, 28px);
73
+ }
74
+
75
+ .icon{
76
+ display:flex;
77
+ align-items:center;
78
+ justify-content:center;
79
+ width:24px;
80
+ height:24px;
81
+ flex-shrink:0;
82
+ }
@@ -4,3 +4,4 @@ export * from './PageLoading';
4
4
  export * from './PageServices';
5
5
  export * from './DefaultSubHeader';
6
6
  export * from './PageSidebar';
7
+ export * from './TreeNavigation';
@@ -20,3 +20,4 @@ __exportStar(require("./PageLoading"), exports);
20
20
  __exportStar(require("./PageServices"), exports);
21
21
  __exportStar(require("./DefaultSubHeader"), exports);
22
22
  __exportStar(require("./PageSidebar"), exports);
23
+ __exportStar(require("./TreeNavigation"), exports);
@@ -0,0 +1,42 @@
1
+ import { ReactNode } from 'react';
2
+ import { StatusProps } from '@snack-uikit/status';
3
+ import { TreeNodeProps } from '@snack-uikit/tree';
4
+ export type TreeNavigationProps = {
5
+ header: {
6
+ /** Текст заголовка */
7
+ title: string;
8
+ /** Иконка */
9
+ icon?: ReactNode;
10
+ /** Текст описания */
11
+ description?: string;
12
+ /** Статус (цвет, иконка и т.п.) – любой тип, который принимает ваш <Status/> */
13
+ status?: StatusProps;
14
+ /** Раздел для действий */
15
+ actions?: ReactNode;
16
+ };
17
+ menu: {
18
+ /** Заголовок меню */
19
+ menuTitle?: string;
20
+ /** Данные для дерева меню */
21
+ items: TreeNodeProps[];
22
+ /** Управляемый режим: если передан, меню открывается как popover. */
23
+ isMenuOpen?: boolean;
24
+ /** Колбэк, вызываемый при попытке изменить состояние меню.
25
+ * В контролируемом режиме обязателен, в неконтролируемом – опционален.
26
+ */
27
+ onMenuToggle?: (open: boolean) => void;
28
+ /** Открывать меню по умолчанию */
29
+ defaultMenuOpened?: boolean;
30
+ /** Позляет отключить кнопку "Свернуть все"*/
31
+ enableShrinkMenuButton?: boolean;
32
+ /** Открывать пункты меню по умолчанию */
33
+ withDefaultOpenedMenuList?: boolean;
34
+ };
35
+ /** Контентная часть страницы */
36
+ content: ReactNode;
37
+ /** Вариант отображения */
38
+ mode: 'popover' | 'aside';
39
+ /** Класс для контейнера контентной части */
40
+ contentClassName?: string;
41
+ };
42
+ export declare function TreeNavigation({ header: { title, icon, description, status, actions }, menu: { menuTitle, items, enableShrinkMenuButton, withDefaultOpenedMenuList, isMenuOpen, defaultMenuOpened, onMenuToggle, }, content, mode, contentClassName, }: TreeNavigationProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import cn from 'classnames';
3
+ import { useMemo } from 'react';
4
+ import { useUncontrolledProp } from 'uncontrollable';
5
+ import { BurgerSVG, CloseSVG } from '@cloud-ru/uikit-product-icons';
6
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
7
+ import { ButtonSimple } from '@snack-uikit/button';
8
+ import { Status } from '@snack-uikit/status';
9
+ import { Typography } from '@snack-uikit/typography';
10
+ import { ConditionalPopover, Menu } from './helper-components';
11
+ import styles from './styles.module.css';
12
+ export function TreeNavigation({ header: { title, icon, description, status, actions }, menu: { menuTitle, items, enableShrinkMenuButton, withDefaultOpenedMenuList, isMenuOpen, defaultMenuOpened, onMenuToggle, }, content, mode, contentClassName, }) {
13
+ const [open, setOpen] = useUncontrolledProp(isMenuOpen, defaultMenuOpened, onMenuToggle);
14
+ const { t } = useLocale('PageLayout');
15
+ const menu = useMemo(() => (_jsx(Menu, { menuItems: items, menuTitle: menuTitle, enableShrinkMenuButton: enableShrinkMenuButton, withDefaultOpenedMenuList: withDefaultOpenedMenuList })), [items, menuTitle, enableShrinkMenuButton, withDefaultOpenedMenuList]);
16
+ return (_jsxs("div", { className: styles.root, children: [_jsxs("div", { className: styles.header, children: [_jsxs("div", { className: styles.titleWrapper, children: [_jsxs("div", { className: styles.titleInner, children: [_jsx(ConditionalPopover, { isOpen: Boolean(open), onOpenChange: setOpen, tip: menu, withPopover: mode === 'popover', children: _jsx("div", { className: styles.innerElement, children: _jsx(ButtonSimple, { size: 'xs', "aria-label": open ? t('TreeNavigation.closeMenu') : t('TreeNavigation.openMenu'), icon: open ? _jsx(CloseSVG, {}) : _jsx(BurgerSVG, {}), onClick: () => setOpen(!open) }) }) }), icon && (_jsx("div", { className: styles.innerElement, children: _jsx("div", { className: styles.icon, children: icon }) })), _jsx(Typography.SansTitleL, { className: styles.title, children: title }), status && (_jsx("div", { className: styles.innerElement, children: _jsx(Status, Object.assign({}, status)) }))] }), description && _jsx(Typography.SansBodyS, { className: styles.description, children: description })] }), actions] }), _jsxs("div", { className: styles.body, children: [mode === 'aside' && open && _jsx("aside", { className: styles.sidebar, children: menu }), _jsx("div", { className: cn(styles.main, contentClassName), children: content })] })] }));
17
+ }
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ type ConditionalPopoverProps = {
3
+ isOpen: boolean;
4
+ onOpenChange: (value: boolean) => void;
5
+ tip: ReactNode;
6
+ withPopover?: boolean;
7
+ children: ReactNode;
8
+ };
9
+ export declare function ConditionalPopover({ tip, withPopover, isOpen, onOpenChange, children }: ConditionalPopoverProps): string | number | boolean | Iterable<ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
10
+ export {};
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Popover } from '@snack-uikit/popover';
3
+ import styles from './styles.module.css';
4
+ export function ConditionalPopover({ tip, withPopover, isOpen, onOpenChange, children }) {
5
+ if (withPopover) {
6
+ return (_jsx(Popover, { className: styles.popover, open: isOpen, onOpenChange: () => {
7
+ if (!open)
8
+ onOpenChange(false);
9
+ }, tip: tip, trigger: 'click', placement: 'bottom-start', children: children }));
10
+ }
11
+ return children;
12
+ }
@@ -0,0 +1,6 @@
1
+ .popover{
2
+ max-height:80%;
3
+ }
4
+ .popover > div{
5
+ overflow:auto;
6
+ }
@@ -0,0 +1,9 @@
1
+ import { TreeNodeProps } from '@snack-uikit/tree';
2
+ type MenuProps = {
3
+ menuTitle?: string;
4
+ menuItems: TreeNodeProps[];
5
+ enableShrinkMenuButton?: boolean;
6
+ withDefaultOpenedMenuList?: boolean;
7
+ };
8
+ export declare function Menu({ menuTitle, menuItems, enableShrinkMenuButton, withDefaultOpenedMenuList }: MenuProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { HorizontalMenuCloseSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { ButtonSimple } from '@snack-uikit/button';
6
+ import { Tree } from '@snack-uikit/tree';
7
+ import { Typography } from '@snack-uikit/typography';
8
+ import styles from './styles.module.css';
9
+ import { getExpandedNodes } from './utils';
10
+ export function Menu({ menuTitle, menuItems, enableShrinkMenuButton = true, withDefaultOpenedMenuList }) {
11
+ const { t } = useLocale('PageLayout');
12
+ const allExpandedNodes = useMemo(() => getExpandedNodes(menuItems), [menuItems]);
13
+ const [expandedNodes, setExpandedNodes] = useState(withDefaultOpenedMenuList ? allExpandedNodes : []);
14
+ const isExpanded = expandedNodes.length > 0;
15
+ const handleExpandAll = () => setExpandedNodes(getExpandedNodes(menuItems));
16
+ const handleCollapseAll = () => setExpandedNodes([]);
17
+ const showSubheader = Boolean(menuTitle || enableShrinkMenuButton);
18
+ return (_jsxs("div", { className: styles.sidebar, children: [showSubheader && (_jsxs("div", { className: styles.subheader, children: [_jsx(Typography.SansTitleM, { children: menuTitle }), enableShrinkMenuButton && (_jsx(ButtonSimple, { label: isExpanded ? t('TreeNavigation.collapseAll') : t('TreeNavigation.expandAll'), icon: _jsx(HorizontalMenuCloseSVG, {}), onClick: isExpanded ? handleCollapseAll : handleExpandAll }))] })), _jsx(Tree, { data: menuItems, selectionMode: 'single', expandedNodes: expandedNodes, onExpand: setExpandedNodes })] }));
19
+ }
@@ -0,0 +1,12 @@
1
+ .sidebar{
2
+ min-width:292px;
3
+ display:flex;
4
+ flex-direction:column;
5
+ gap:12px;
6
+ }
7
+
8
+ .subheader{
9
+ display:flex;
10
+ align-items:center;
11
+ justify-content:space-between;
12
+ }
@@ -0,0 +1,7 @@
1
+ import { TreeNodeProps } from '@snack-uikit/tree';
2
+ /**
3
+ * Рекурсивно собирает все ID узлов дерева для их разворачивания
4
+ * @param nodes - Массив узлов дерева
5
+ * @returns Массив ID всех узлов (включая вложенные)
6
+ */
7
+ export declare const getExpandedNodes: (nodes: TreeNodeProps[]) => string[];
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Рекурсивно собирает все ID узлов дерева для их разворачивания
3
+ * @param nodes - Массив узлов дерева
4
+ * @returns Массив ID всех узлов (включая вложенные)
5
+ */
6
+ export const getExpandedNodes = (nodes) => {
7
+ if (!nodes || nodes.length === 0) {
8
+ return [];
9
+ }
10
+ const ids = [];
11
+ nodes.forEach(el => {
12
+ const children = el.nested ? getExpandedNodes(el.nested) : [];
13
+ ids.push(el.id, ...children);
14
+ });
15
+ return ids;
16
+ };
@@ -0,0 +1,2 @@
1
+ export { Menu } from './Menu/Menu';
2
+ export { ConditionalPopover } from './ConditionalPopover/ConditionalPopover';
@@ -0,0 +1,2 @@
1
+ export { Menu } from './Menu/Menu';
2
+ export { ConditionalPopover } from './ConditionalPopover/ConditionalPopover';
@@ -0,0 +1 @@
1
+ export * from './TreeNavigation';
@@ -0,0 +1 @@
1
+ export * from './TreeNavigation';
@@ -0,0 +1,82 @@
1
+ .root{
2
+ display:flex;
3
+ flex-direction:column;
4
+ height:100%;
5
+ width:100%;
6
+ }
7
+
8
+ .header{
9
+ width:100%;
10
+ display:flex;
11
+ justify-content:space-between;
12
+ align-items:flex-start;
13
+ gap:40px;
14
+ padding:8px 24px;
15
+ background-color:var(--sys-neutral-background, #eeeff3);
16
+ border-bottom:1px solid var(--sys-neutral-decor-default, #dde0ea);
17
+ box-sizing:border-box;
18
+ }
19
+
20
+ .description{
21
+ color:var(--sys-neutral-text-support, #6d707f);
22
+ }
23
+
24
+ .body{
25
+ display:flex;
26
+ gap:16px;
27
+ padding:0 24px;
28
+ box-sizing:border-box;
29
+ width:100%;
30
+ }
31
+
32
+ .sidebar{
33
+ border-radius:12px;
34
+ padding:16px;
35
+ height:-moz-max-content;
36
+ height:max-content;
37
+ background:var(--sys-neutral-background1-level, #fdfdfd);
38
+ }
39
+
40
+ .main{
41
+ padding-top:16px;
42
+ width:100%;
43
+ box-sizing:border-box;
44
+ }
45
+
46
+ .titleWrapper{
47
+ display:flex;
48
+ align-items:center;
49
+ flex-wrap:wrap;
50
+ -moz-column-gap:16px;
51
+ column-gap:16px;
52
+ row-gap:8px;
53
+ }
54
+
55
+ .titleInner{
56
+ display:flex;
57
+ gap:8px;
58
+ }
59
+
60
+ .title{
61
+ box-sizing:border-box;
62
+ max-width:100%;
63
+ margin:0;
64
+ padding:0;
65
+ overflow-wrap:break-word;
66
+ }
67
+
68
+ .innerElement{
69
+ display:flex;
70
+ align-items:center;
71
+ justify-content:center;
72
+ height:var(--sans-title-l-line-height, 28px);
73
+ }
74
+
75
+ .icon{
76
+ display:flex;
77
+ align-items:center;
78
+ justify-content:center;
79
+ width:24px;
80
+ height:24px;
81
+ flex-shrink:0;
82
+ }
@@ -4,3 +4,4 @@ export * from './PageLoading';
4
4
  export * from './PageServices';
5
5
  export * from './DefaultSubHeader';
6
6
  export * from './PageSidebar';
7
+ export * from './TreeNavigation';
@@ -4,3 +4,4 @@ export * from './PageLoading';
4
4
  export * from './PageServices';
5
5
  export * from './DefaultSubHeader';
6
6
  export * from './PageSidebar';
7
+ export * from './TreeNavigation';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloud-ru/uikit-product-page-layout",
3
3
  "title": "Page Layout",
4
- "version": "0.21.6",
4
+ "version": "0.22.0",
5
5
  "sideEffects": [
6
6
  "*.css",
7
7
  "*.woff",
@@ -43,8 +43,11 @@
43
43
  "@snack-uikit/icon-predefined": "0.7.3",
44
44
  "@snack-uikit/list": "0.26.0",
45
45
  "@snack-uikit/loaders": "0.9.4",
46
+ "@snack-uikit/popover": "0.10.3",
46
47
  "@snack-uikit/search": "0.12.13",
48
+ "@snack-uikit/status": "0.10.4",
47
49
  "@snack-uikit/tooltip": "0.16.2",
50
+ "@snack-uikit/tree": "0.9.30",
48
51
  "@snack-uikit/truncate-string": "0.6.9",
49
52
  "@snack-uikit/typography": "0.8.4",
50
53
  "@snack-uikit/utils": "3.7.0",
@@ -54,5 +57,5 @@
54
57
  "peerDependencies": {
55
58
  "@cloud-ru/uikit-product-locale": "*"
56
59
  },
57
- "gitHead": "ce69097516055b330a0b05da5e29ac5b66e56284"
60
+ "gitHead": "0c7a2375e56560d287c95b9ef47c5b424cb004d6"
58
61
  }
@@ -0,0 +1,128 @@
1
+ import cn from 'classnames';
2
+ import { ReactNode, useMemo } from 'react';
3
+ import { useUncontrolledProp } from 'uncontrollable';
4
+
5
+ import { BurgerSVG, CloseSVG } from '@cloud-ru/uikit-product-icons';
6
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
7
+ import { ButtonSimple } from '@snack-uikit/button';
8
+ import { Status, StatusProps } from '@snack-uikit/status';
9
+ import { TreeNodeProps } from '@snack-uikit/tree';
10
+ import { Typography } from '@snack-uikit/typography';
11
+
12
+ import { ConditionalPopover, Menu } from './helper-components';
13
+ import styles from './styles.module.scss';
14
+
15
+ export type TreeNavigationProps = {
16
+ header: {
17
+ /** Текст заголовка */
18
+ title: string;
19
+ /** Иконка */
20
+ icon?: ReactNode;
21
+ /** Текст описания */
22
+ description?: string;
23
+ /** Статус (цвет, иконка и т.п.) – любой тип, который принимает ваш <Status/> */
24
+ status?: StatusProps;
25
+ /** Раздел для действий */
26
+ actions?: ReactNode;
27
+ };
28
+ menu: {
29
+ /** Заголовок меню */
30
+ menuTitle?: string;
31
+ /** Данные для дерева меню */
32
+ items: TreeNodeProps[];
33
+ /** Управляемый режим: если передан, меню открывается как popover. */
34
+ isMenuOpen?: boolean;
35
+ /** Колбэк, вызываемый при попытке изменить состояние меню.
36
+ * В контролируемом режиме обязателен, в неконтролируемом – опционален.
37
+ */
38
+ onMenuToggle?: (open: boolean) => void;
39
+ /** Открывать меню по умолчанию */
40
+ defaultMenuOpened?: boolean;
41
+ /** Позляет отключить кнопку "Свернуть все"*/
42
+ enableShrinkMenuButton?: boolean;
43
+ /** Открывать пункты меню по умолчанию */
44
+ withDefaultOpenedMenuList?: boolean;
45
+ };
46
+ /** Контентная часть страницы */
47
+ content: ReactNode;
48
+ /** Вариант отображения */
49
+ mode: 'popover' | 'aside';
50
+ /** Класс для контейнера контентной части */
51
+ contentClassName?: string;
52
+ };
53
+
54
+ export function TreeNavigation({
55
+ header: { title, icon, description, status, actions },
56
+ menu: {
57
+ menuTitle,
58
+ items,
59
+ enableShrinkMenuButton,
60
+ withDefaultOpenedMenuList,
61
+ isMenuOpen,
62
+ defaultMenuOpened,
63
+ onMenuToggle,
64
+ },
65
+ content,
66
+ mode,
67
+ contentClassName,
68
+ }: TreeNavigationProps) {
69
+ const [open, setOpen] = useUncontrolledProp(isMenuOpen, defaultMenuOpened, onMenuToggle);
70
+ const { t } = useLocale('PageLayout');
71
+ const menu = useMemo(
72
+ () => (
73
+ <Menu
74
+ menuItems={items}
75
+ menuTitle={menuTitle}
76
+ enableShrinkMenuButton={enableShrinkMenuButton}
77
+ withDefaultOpenedMenuList={withDefaultOpenedMenuList}
78
+ />
79
+ ),
80
+ [items, menuTitle, enableShrinkMenuButton, withDefaultOpenedMenuList],
81
+ );
82
+
83
+ return (
84
+ <div className={styles.root}>
85
+ <div className={styles.header}>
86
+ <div className={styles.titleWrapper}>
87
+ <div className={styles.titleInner}>
88
+ <ConditionalPopover
89
+ isOpen={Boolean(open)}
90
+ onOpenChange={setOpen}
91
+ tip={menu}
92
+ withPopover={mode === 'popover'}
93
+ >
94
+ <div className={styles.innerElement}>
95
+ <ButtonSimple
96
+ size='xs'
97
+ aria-label={open ? t('TreeNavigation.closeMenu') : t('TreeNavigation.openMenu')}
98
+ icon={open ? <CloseSVG /> : <BurgerSVG />}
99
+ onClick={() => setOpen(!open)}
100
+ />
101
+ </div>
102
+ </ConditionalPopover>
103
+ {icon && (
104
+ <div className={styles.innerElement}>
105
+ <div className={styles.icon}>{icon}</div>
106
+ </div>
107
+ )}
108
+ <Typography.SansTitleL className={styles.title}>{title}</Typography.SansTitleL>
109
+
110
+ {status && (
111
+ <div className={styles.innerElement}>
112
+ <Status {...status} />
113
+ </div>
114
+ )}
115
+ </div>
116
+ {description && <Typography.SansBodyS className={styles.description}>{description}</Typography.SansBodyS>}
117
+ </div>
118
+
119
+ {actions}
120
+ </div>
121
+
122
+ <div className={styles.body}>
123
+ {mode === 'aside' && open && <aside className={styles.sidebar}>{menu}</aside>}
124
+ <div className={cn(styles.main, contentClassName)}>{content}</div>
125
+ </div>
126
+ </div>
127
+ );
128
+ }
@@ -0,0 +1,34 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ import { Popover } from '@snack-uikit/popover';
4
+
5
+ import styles from './styles.module.scss';
6
+
7
+ type ConditionalPopoverProps = {
8
+ isOpen: boolean;
9
+ onOpenChange: (value: boolean) => void;
10
+ tip: ReactNode;
11
+ withPopover?: boolean;
12
+ children: ReactNode;
13
+ };
14
+
15
+ export function ConditionalPopover({ tip, withPopover, isOpen, onOpenChange, children }: ConditionalPopoverProps) {
16
+ if (withPopover) {
17
+ return (
18
+ <Popover
19
+ className={styles.popover}
20
+ open={isOpen}
21
+ onOpenChange={() => {
22
+ if (!open) onOpenChange(false);
23
+ }}
24
+ tip={tip}
25
+ trigger={'click'}
26
+ placement={'bottom-start'}
27
+ >
28
+ {children}
29
+ </Popover>
30
+ );
31
+ }
32
+
33
+ return children;
34
+ }
@@ -0,0 +1,7 @@
1
+ .popover {
2
+ max-height: 80%;
3
+
4
+ & > div {
5
+ overflow: auto;
6
+ }
7
+ }
@@ -0,0 +1,50 @@
1
+ import { useMemo, useState } from 'react';
2
+
3
+ import { HorizontalMenuCloseSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { ButtonSimple } from '@snack-uikit/button';
6
+ import { Tree, TreeNodeProps } from '@snack-uikit/tree';
7
+ import { Typography } from '@snack-uikit/typography';
8
+
9
+ import styles from './styles.module.scss';
10
+ import { getExpandedNodes } from './utils';
11
+
12
+ type MenuProps = {
13
+ menuTitle?: string;
14
+ menuItems: TreeNodeProps[];
15
+ enableShrinkMenuButton?: boolean;
16
+ withDefaultOpenedMenuList?: boolean;
17
+ };
18
+
19
+ export function Menu({ menuTitle, menuItems, enableShrinkMenuButton = true, withDefaultOpenedMenuList }: MenuProps) {
20
+ const { t } = useLocale('PageLayout');
21
+ const allExpandedNodes = useMemo(() => getExpandedNodes(menuItems), [menuItems]);
22
+
23
+ const [expandedNodes, setExpandedNodes] = useState<string[]>(withDefaultOpenedMenuList ? allExpandedNodes : []);
24
+
25
+ const isExpanded = expandedNodes.length > 0;
26
+
27
+ const handleExpandAll = () => setExpandedNodes(getExpandedNodes(menuItems));
28
+ const handleCollapseAll = () => setExpandedNodes([]);
29
+
30
+ const showSubheader = Boolean(menuTitle || enableShrinkMenuButton);
31
+
32
+ return (
33
+ <div className={styles.sidebar}>
34
+ {showSubheader && (
35
+ <div className={styles.subheader}>
36
+ <Typography.SansTitleM>{menuTitle}</Typography.SansTitleM>
37
+ {enableShrinkMenuButton && (
38
+ <ButtonSimple
39
+ label={isExpanded ? t('TreeNavigation.collapseAll') : t('TreeNavigation.expandAll')}
40
+ icon={<HorizontalMenuCloseSVG />}
41
+ onClick={isExpanded ? handleCollapseAll : handleExpandAll}
42
+ />
43
+ )}
44
+ </div>
45
+ )}
46
+
47
+ <Tree data={menuItems} selectionMode='single' expandedNodes={expandedNodes} onExpand={setExpandedNodes} />
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,14 @@
1
+ $sidebar-width: 292px; // Общая ширина 324пкс из-за 16пкс paddings
2
+
3
+ .sidebar {
4
+ min-width: $sidebar-width;
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: 12px;
8
+ }
9
+
10
+ .subheader {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: space-between;
14
+ }
@@ -0,0 +1,22 @@
1
+ import { TreeNodeProps } from '@snack-uikit/tree';
2
+
3
+ /**
4
+ * Рекурсивно собирает все ID узлов дерева для их разворачивания
5
+ * @param nodes - Массив узлов дерева
6
+ * @returns Массив ID всех узлов (включая вложенные)
7
+ */
8
+ export const getExpandedNodes = (nodes: TreeNodeProps[]): string[] => {
9
+ if (!nodes || nodes.length === 0) {
10
+ return [];
11
+ }
12
+
13
+ const ids: string[] = [];
14
+
15
+ nodes.forEach(el => {
16
+ const children = el.nested ? getExpandedNodes(el.nested) : [];
17
+
18
+ ids.push(el.id, ...children);
19
+ });
20
+
21
+ return ids;
22
+ };
@@ -0,0 +1,2 @@
1
+ export { Menu } from './Menu/Menu';
2
+ export { ConditionalPopover } from './ConditionalPopover/ConditionalPopover';
@@ -0,0 +1 @@
1
+ export * from './TreeNavigation';
@@ -0,0 +1,86 @@
1
+ @import '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ $page-padding: 24px;
4
+ $main-content-padding: 8px;
5
+ $content-gap: 16px;
6
+
7
+ .root {
8
+ display: flex;
9
+ flex-direction: column;
10
+ height: 100%;
11
+ width: 100%;
12
+ }
13
+
14
+ .header {
15
+ width: 100%;
16
+ display: flex;
17
+ justify-content: space-between;
18
+ align-items: flex-start;
19
+ gap: 40px;
20
+ padding: $main-content-padding $page-padding;
21
+ background-color: $sys-neutral-background;
22
+ border-bottom: 1px solid $sys-neutral-decor-default;
23
+ box-sizing: border-box;
24
+ }
25
+
26
+ .description {
27
+ color: $sys-neutral-text-support;
28
+ }
29
+
30
+ .body {
31
+ display: flex;
32
+ gap: $content-gap;
33
+ padding: 0 $page-padding;
34
+ box-sizing: border-box;
35
+ width: 100%;
36
+ }
37
+
38
+ .sidebar {
39
+ border-radius: 12px;
40
+ padding: $content-gap;
41
+ height: max-content;
42
+ background: $sys-neutral-background1-level;
43
+ }
44
+
45
+ .main {
46
+ padding-top: $content-gap;
47
+ width: 100%;
48
+ box-sizing: border-box;
49
+ }
50
+
51
+ .titleWrapper {
52
+ display: flex;
53
+ align-items: center;
54
+ flex-wrap: wrap;
55
+ column-gap: $content-gap;
56
+ row-gap: $main-content-padding;
57
+ }
58
+
59
+ .titleInner {
60
+ display: flex;
61
+ gap: $main-content-padding;
62
+ }
63
+
64
+ .title {
65
+ box-sizing: border-box;
66
+ max-width: 100%;
67
+ margin: 0;
68
+ padding: 0;
69
+ overflow-wrap: break-word;
70
+ }
71
+
72
+ .innerElement {
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ height: $sans-title-l-line-height;
77
+ }
78
+
79
+ .icon {
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ width: 24px;
84
+ height: 24px;
85
+ flex-shrink: 0;
86
+ }
@@ -4,3 +4,4 @@ export * from './PageLoading';
4
4
  export * from './PageServices';
5
5
  export * from './DefaultSubHeader';
6
6
  export * from './PageSidebar';
7
+ export * from './TreeNavigation';