@cloud-ru/ds-uikit-product-header 2.0.2 → 2.0.3-preview-1298edee.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/MainMenu/helperComponents/MenuBottom/MenuBottom.js +1 -1
- package/dist/cjs/components/MainMenu/helperComponents/MenuMobile/MenuMobile.js +2 -1
- package/dist/cjs/components/MainMenu/helperComponents/ScrollWithAnimatedStickyPanel/ScrollWithAnimatedStickyPanel.js +18 -6
- package/dist/cjs/components/UserMenu/UserMenu.js +5 -4
- package/dist/cjs/components/UserMenu/hooks/useLogoutItem.d.ts +2 -2
- package/dist/cjs/components/UserMenu/hooks/useLogoutItem.js +12 -9
- package/dist/cjs/components/UserMenu/hooks/useUserMenuItems.js +3 -3
- package/dist/cjs/components/UserMenu/styles.module.css +9 -0
- package/dist/cjs/constants.d.ts +0 -1
- package/dist/cjs/constants.js +1 -3
- package/dist/cjs/style.css +9 -0
- package/dist/esm/components/MainMenu/helperComponents/MenuBottom/MenuBottom.js +2 -2
- package/dist/esm/components/MainMenu/helperComponents/MenuMobile/MenuMobile.js +3 -2
- package/dist/esm/components/MainMenu/helperComponents/ScrollWithAnimatedStickyPanel/ScrollWithAnimatedStickyPanel.js +18 -6
- package/dist/esm/components/UserMenu/UserMenu.js +8 -7
- package/dist/esm/components/UserMenu/hooks/useLogoutItem.d.ts +2 -2
- package/dist/esm/components/UserMenu/hooks/useLogoutItem.js +9 -9
- package/dist/esm/components/UserMenu/hooks/useUserMenuItems.js +3 -3
- package/dist/esm/components/UserMenu/styles.module.css +9 -0
- package/dist/esm/constants.d.ts +0 -1
- package/dist/esm/constants.js +0 -2
- package/dist/esm/style.css +9 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +28 -28
- package/src/components/MainMenu/helperComponents/MenuBottom/MenuBottom.tsx +2 -2
- package/src/components/MainMenu/helperComponents/MenuMobile/MenuMobile.tsx +3 -2
- package/src/components/MainMenu/helperComponents/ScrollWithAnimatedStickyPanel/ScrollWithAnimatedStickyPanel.tsx +21 -10
- package/src/components/UserMenu/UserMenu.tsx +11 -7
- package/src/components/UserMenu/hooks/useLogoutItem.tsx +12 -12
- package/src/components/UserMenu/hooks/useUserMenuItems.ts +4 -4
- package/src/components/UserMenu/styles.module.scss +11 -0
- package/src/constants.ts +0 -3
|
@@ -12,5 +12,5 @@ const constants_1 = require("../../../../constants");
|
|
|
12
12
|
const utils_1 = require("../../utils");
|
|
13
13
|
const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
|
|
14
14
|
function MenuBottom({ settingItems, leftBottom }) {
|
|
15
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.bottom, "data-test-id": constants_1.TEST_IDS.mainMenu.leftBottom, children: [Boolean(settingItems?.length) && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.bottomItems, "data-test-id": constants_1.TEST_IDS.mainMenu.leftBottomItems, children: settingItems?.map(item => ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [item.divider === 'before' && (0, jsx_runtime_1.jsx)(divider_1.Divider, { orientation: 'horizontal', className: styles_module_scss_1.default.bottomDivider }), (0, jsx_runtime_1.jsx)(uikit_product_card_predefined_1.
|
|
15
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.bottom, "data-test-id": constants_1.TEST_IDS.mainMenu.leftBottom, children: [Boolean(settingItems?.length) && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.bottomItems, "data-test-id": constants_1.TEST_IDS.mainMenu.leftBottomItems, children: settingItems?.map(item => ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [item.divider === 'before' && (0, jsx_runtime_1.jsx)(divider_1.Divider, { orientation: 'horizontal', className: styles_module_scss_1.default.bottomDivider }), (0, jsx_runtime_1.jsx)(uikit_product_card_predefined_1.CardNavigation, { ...(item.href ? { href: item.href, as: 'a' } : { as: 'button', type: 'button' }), title: item.label, icon: (0, utils_1.getLinkEmblem)(item), onClick: item.onClick, "data-test-id": `header__drawer-menu__setting-${item.id}` }), item.divider === 'after' && (0, jsx_runtime_1.jsx)(divider_1.Divider, { orientation: 'horizontal', className: styles_module_scss_1.default.bottomDivider })] }, item.id))) })), leftBottom] }));
|
|
16
16
|
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.MenuMobile = MenuMobile;
|
|
7
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
8
|
const divider_1 = require("@cloud-ru/ds-divider");
|
|
9
|
+
const list_1 = require("@cloud-ru/ds-list");
|
|
9
10
|
const react_1 = require("react");
|
|
10
11
|
const constants_1 = require("../../../../constants");
|
|
11
12
|
const mobileOverlays_1 = require("../../../../mobileOverlays");
|
|
@@ -38,5 +39,5 @@ function MenuMobile({ open = false, setOpen, settingItems, platformGroups, segme
|
|
|
38
39
|
const handleClose = (0, react_1.useCallback)(() => {
|
|
39
40
|
setOpen?.(false);
|
|
40
41
|
}, [setOpen]);
|
|
41
|
-
return ((0, jsx_runtime_1.jsx)(mobileOverlays_1.MobileDrawerCustom, { open: open, onClose: handleClose, position: 'bottom', className: styles_module_scss_1.default.drawerMobile, swipeEnabled: false, "data-test-id": constants_1.TEST_IDS.mainMenu.drawerMobile, closeOnPopstate: true, snapPoints:
|
|
42
|
+
return ((0, jsx_runtime_1.jsx)(mobileOverlays_1.MobileDrawerCustom, { open: open, onClose: handleClose, position: 'bottom', className: styles_module_scss_1.default.drawerMobile, swipeEnabled: false, "data-test-id": constants_1.TEST_IDS.mainMenu.drawerMobile, closeOnPopstate: true, snapPoints: list_1.SNAP_POINTS_PRESET.full, disableMotions: true, children: (0, jsx_runtime_1.jsx)(ScrollWithAnimatedStickyPanel_1.ScrollWithAnimatedStickyPanel, { panel: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(MenuHeaderBrand_1.MenuHeaderBrand, { logo: logo, onClose: handleClose, className: styles_module_scss_1.default.menuHeader, isMobile: true }), (0, jsx_runtime_1.jsx)(divider_1.Divider, { orientation: 'horizontal' })] }), children: (0, jsx_runtime_1.jsxs)(MountAnimation_1.MountAnimation, { className: styles_module_scss_1.default.scrollMobile, type: 'fade-slide-up', children: [leftTop, search && (0, jsx_runtime_1.jsx)(Search_1.Search, { ...search, ref: searchRef, isMobile: true }), !isSearching && favorite && (0, jsx_runtime_1.jsx)(Favorites_1.Favorites, { favorite: favorite, servicesById: servicesById, isMobile: true }), hasSegments && ((0, jsx_runtime_1.jsx)(useMainMenuDnd_1.MainMenuDndContext, { children: (0, jsx_runtime_1.jsx)(Content_1.Content, { isMobile: true, searchValue: search && search.value, rightTop: rightTop, favorite: favorite, segments: segments, searchGroups: resultItems, segmentPrefs: segmentPrefs, activeSegmentId: activeSegmentId, onActiveSegmentChange: onActiveSegmentChange, onSegmentOrderChange: onSegmentOrderChange, onSegmentExpandedChange: onSegmentExpandedChange, onSegmentServiceClick: onSegmentServiceClick, loading: loading }) })), !isSearching && hasBottomItems && (0, jsx_runtime_1.jsx)(MenuBottom_1.MenuBottom, { settingItems: settingItems, leftBottom: leftBottom })] }) }) }));
|
|
42
43
|
}
|
|
@@ -8,6 +8,10 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
8
8
|
const scroll_1 = require("@cloud-ru/ds-scroll");
|
|
9
9
|
const react_1 = require("react");
|
|
10
10
|
const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
|
|
11
|
+
// Упругая прокрутка у верхней границы (touch-скролл на мобильных не "упирается" жёстко в 0 —
|
|
12
|
+
// контент можно немного "оттянуть" дальше) даёт scrollTop, не совпадающий с реальным положением
|
|
13
|
+
// контента в этот момент. Диапазон запаса — единицы пикселей; двух хватает с большим запасом.
|
|
14
|
+
const TOP_OVERSCROLL_EPSILON_PX = 2;
|
|
11
15
|
function ScrollWithAnimatedStickyPanel({ panel, children }) {
|
|
12
16
|
const [positions, setPositions] = (0, react_1.useState)({ panelTopShift: 0, containerScroll: 0 });
|
|
13
17
|
const [panelHeight, setPanelHeight] = (0, react_1.useState)(0);
|
|
@@ -20,6 +24,13 @@ function ScrollWithAnimatedStickyPanel({ panel, children }) {
|
|
|
20
24
|
return;
|
|
21
25
|
const panelHeight = panelRef.current.offsetHeight;
|
|
22
26
|
setPanelHeight(panelHeight);
|
|
27
|
+
// У верхней границы панель всегда полностью видна — не доверяем накопленному diff. Иначе
|
|
28
|
+
// "лишний" ход упругой прокрутки у самого верха читается как скролл вниз и на возврате к
|
|
29
|
+
// началу списка панель может остаться скрытой, хотя пользователь как раз скроллит наверх.
|
|
30
|
+
if (target.scrollTop <= TOP_OVERSCROLL_EPSILON_PX) {
|
|
31
|
+
setPositions({ panelTopShift: 0, containerScroll: target.scrollTop });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
23
34
|
setPositions(prev => {
|
|
24
35
|
const diff = prev.containerScroll - target.scrollTop;
|
|
25
36
|
return {
|
|
@@ -28,10 +39,11 @@ function ScrollWithAnimatedStickyPanel({ panel, children }) {
|
|
|
28
39
|
};
|
|
29
40
|
});
|
|
30
41
|
}, []);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
const setPanelRef = (0, react_1.useCallback)((element) => {
|
|
43
|
+
if (element) {
|
|
44
|
+
setPanelHeight(element.offsetHeight);
|
|
45
|
+
panelRef.current = element;
|
|
46
|
+
}
|
|
47
|
+
}, []);
|
|
48
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.container, style: { '--snack-autohide-panel-height': `${panelHeight}px` }, children: [(0, jsx_runtime_1.jsx)(scroll_1.Scroll, { barHideStrategy: 'never', overflow: { x: 'hidden' }, onScroll: handleScroll, children: children }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.panel, ref: setPanelRef, style: { top: positions.panelTopShift }, children: panel })] }));
|
|
37
49
|
}
|
|
@@ -28,6 +28,9 @@ function UserMenu({ profile = {}, open: openProp, setOpen: setOpenProp, onLogout
|
|
|
28
28
|
const comfortClassName = (0, useDesktopComfortClassName_1.useDesktopComfortClassName)();
|
|
29
29
|
const [open = false, setOpen] = (0, utils_1.useValueControl)({ value: openProp, onChange: setOpenProp });
|
|
30
30
|
const { fullName = '', inviteCount } = profile;
|
|
31
|
+
const handleClose = (0, react_1.useCallback)(() => {
|
|
32
|
+
setOpen(false);
|
|
33
|
+
}, [setOpen]);
|
|
31
34
|
const { pinTop, items: menuItems, pinBottom, } = (0, useUserMenuItems_1.useUserMenuItems)({
|
|
32
35
|
isMobile,
|
|
33
36
|
profile,
|
|
@@ -36,9 +39,7 @@ function UserMenu({ profile = {}, open: openProp, setOpen: setOpenProp, onLogout
|
|
|
36
39
|
organizationItems,
|
|
37
40
|
bottomItems,
|
|
38
41
|
settingItems,
|
|
39
|
-
onClose:
|
|
40
|
-
setOpen(false);
|
|
41
|
-
},
|
|
42
|
+
onClose: handleClose,
|
|
42
43
|
onLogout,
|
|
43
44
|
});
|
|
44
45
|
const trigger = (0, react_1.useMemo)(() => ((0, jsx_runtime_1.jsx)(HeaderButton_1.HeaderButton, { tooltip: { tip: triggerTooltip }, isMobile: isMobile, onClick: () => {
|
|
@@ -50,5 +51,5 @@ function UserMenu({ profile = {}, open: openProp, setOpen: setOpenProp, onLogout
|
|
|
50
51
|
appearance: 'primary',
|
|
51
52
|
}
|
|
52
53
|
: undefined, "data-test-id": constants_1.TEST_IDS.userMenu.button, icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { appearance: 'red', size: 's', name: fullName, showTwoSymbols: true }), "data-pressed": open })), [fullName, inviteCount, onClick, open, setOpen, isMobile, triggerTooltip]);
|
|
53
|
-
return ((0, jsx_runtime_1.jsx)(list_1.Droplist, { open: open, onOpenChange: setOpen, size: 'm', selection: SELECTION_STUB, pinTop: pinTop, items: menuItems, pinBottom: pinBottom, scroll: !isMobile, trigger: 'click', placement: 'bottom-end', className: (0, classnames_1.default)(styles_module_scss_1.default.userMenuDroplist, comfortClassName), closeOnPopstate: true, "data-test-id": constants_1.TEST_IDS.userMenu.root, label: t('user'), snapPoints:
|
|
54
|
+
return ((0, jsx_runtime_1.jsx)(list_1.Droplist, { open: open, onOpenChange: setOpen, size: 'm', selection: SELECTION_STUB, pinTop: pinTop, items: menuItems, pinBottom: pinBottom, scroll: !isMobile, trigger: 'click', placement: 'bottom-end', className: (0, classnames_1.default)(styles_module_scss_1.default.userMenuDroplist, comfortClassName), closeOnPopstate: true, "data-test-id": constants_1.TEST_IDS.userMenu.root, label: t('user'), snapPoints: list_1.SNAP_POINTS_PRESET.full, withDividers: false, triggerClassName: styles_module_scss_1.default.userMenuDroplistTrigger, children: trigger }));
|
|
54
55
|
}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.useLogoutItem = useLogoutItem;
|
|
4
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
8
|
const product_1 = require("@cloud-ru/ds-icons/interface/product");
|
|
6
9
|
const react_1 = require("react");
|
|
7
10
|
const locale_1 = require("../../../locale");
|
|
11
|
+
const styles_module_scss_1 = __importDefault(require('../styles.module.css'));
|
|
8
12
|
function useLogoutItem({ onLogout }) {
|
|
9
13
|
const { t } = locale_1.headerLocale.useTranslations();
|
|
10
|
-
return (0, react_1.useMemo)(() =>
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
label: t('logout'),
|
|
15
|
-
},
|
|
16
|
-
onClick: onLogout,
|
|
17
|
-
'data-test-id': 'header__user-menu__logout',
|
|
14
|
+
return (0, react_1.useMemo)(() => ({
|
|
15
|
+
beforeContent: (0, jsx_runtime_1.jsx)(product_1.ExitSVG, {}),
|
|
16
|
+
content: {
|
|
17
|
+
label: t('logout'),
|
|
18
18
|
},
|
|
19
|
-
|
|
19
|
+
className: styles_module_scss_1.default.logoutItem,
|
|
20
|
+
onClick: onLogout,
|
|
21
|
+
'data-test-id': 'header__user-menu__logout',
|
|
22
|
+
}), [onLogout, t]);
|
|
20
23
|
}
|
|
@@ -38,11 +38,11 @@ function useUserMenuItems({ profile, theme, onLogout, topItems = emptyArray_1.EM
|
|
|
38
38
|
}
|
|
39
39
|
pinTop = pinTop.concat(topItems);
|
|
40
40
|
let pinBottom = bottomItems.concat(logoutItem);
|
|
41
|
-
if (isMobile && Boolean(settingItems?.length)) {
|
|
42
|
-
pinBottom = pinBottom.concat([DIVIDER_ITEM, ...settingItems]);
|
|
43
|
-
}
|
|
44
41
|
// В шторке прокручивается всё меню, закреплять части незачем; разделитель над организациями добавляем сами
|
|
45
42
|
if (isMobile) {
|
|
43
|
+
if (settingItems?.length) {
|
|
44
|
+
pinBottom = [...bottomItems, ...settingItems, DIVIDER_ITEM, logoutItem];
|
|
45
|
+
}
|
|
46
46
|
return {
|
|
47
47
|
pinTop: [],
|
|
48
48
|
items: withClose([...pinTop, DIVIDER_ITEM, ...organizationItems, ...pinBottom]),
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
.userMenuDroplistTrigger{
|
|
2
|
+
display:inline-flex;
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
.userMenuDroplist{
|
|
2
6
|
width:256px;
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
.profileSettingsIcon{
|
|
6
10
|
color:var(--sn-theme-color-neutral-text, var(--sn-brand-color-neutral-40, var(--sn-brand-palette-gray-40, #81869c)));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.logoutItem span,
|
|
14
|
+
.logoutItem svg{
|
|
15
|
+
color:var(--sn-theme-color-red-text, var(--sn-brand-palette-red-40, #a93533));
|
|
7
16
|
}
|
package/dist/cjs/constants.d.ts
CHANGED
package/dist/cjs/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.TEST_IDS = void 0;
|
|
4
4
|
exports.TEST_IDS = {
|
|
5
5
|
headerLayout: {
|
|
6
6
|
root: 'header-layout',
|
|
@@ -29,5 +29,3 @@ exports.TEST_IDS = {
|
|
|
29
29
|
root: 'header__breadcrumbs',
|
|
30
30
|
},
|
|
31
31
|
};
|
|
32
|
-
// Только mobile: в мастере bottomSheet панель открывается на всю высоту экрана.
|
|
33
|
-
exports.BOTTOM_SHEET_FULLSCREEN_SNAP_POINTS = [1];
|
package/dist/cjs/style.css
CHANGED
|
@@ -951,10 +951,19 @@
|
|
|
951
951
|
color:var(--sn-theme-color-neutral-onAccent, var(--sn-brand-color-neutral-99, var(--sn-brand-palette-gray-99, #ffffff)));
|
|
952
952
|
background:var(--sn-theme-color-neutral-accent, var(--sn-brand-color-neutral-50, var(--sn-brand-palette-gray-50, #8b8fa7)));
|
|
953
953
|
}
|
|
954
|
+
.userMenuDroplistTrigger{
|
|
955
|
+
display:inline-flex;
|
|
956
|
+
}
|
|
957
|
+
|
|
954
958
|
.userMenuDroplist{
|
|
955
959
|
width:256px;
|
|
956
960
|
}
|
|
957
961
|
|
|
958
962
|
.profileSettingsIcon{
|
|
959
963
|
color:var(--sn-theme-color-neutral-text, var(--sn-brand-color-neutral-40, var(--sn-brand-palette-gray-40, #81869c)));
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.logoutItem span,
|
|
967
|
+
.logoutItem svg{
|
|
968
|
+
color:var(--sn-theme-color-red-text, var(--sn-brand-palette-red-40, #a93533));
|
|
960
969
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Divider } from '@cloud-ru/ds-divider';
|
|
3
|
-
import {
|
|
3
|
+
import { CardNavigation } from '@cloud-ru/ds-uikit-product-card-predefined';
|
|
4
4
|
import { Fragment } from 'react';
|
|
5
5
|
import { TEST_IDS } from '../../../../constants.js';
|
|
6
6
|
import { getLinkEmblem } from '../../utils/index.js';
|
|
7
7
|
import styles from './styles.module.css';
|
|
8
8
|
export function MenuBottom({ settingItems, leftBottom }) {
|
|
9
|
-
return (_jsxs("div", { className: styles.bottom, "data-test-id": TEST_IDS.mainMenu.leftBottom, children: [Boolean(settingItems?.length) && (_jsx("div", { className: styles.bottomItems, "data-test-id": TEST_IDS.mainMenu.leftBottomItems, children: settingItems?.map(item => (_jsxs(Fragment, { children: [item.divider === 'before' && _jsx(Divider, { orientation: 'horizontal', className: styles.bottomDivider }), _jsx(
|
|
9
|
+
return (_jsxs("div", { className: styles.bottom, "data-test-id": TEST_IDS.mainMenu.leftBottom, children: [Boolean(settingItems?.length) && (_jsx("div", { className: styles.bottomItems, "data-test-id": TEST_IDS.mainMenu.leftBottomItems, children: settingItems?.map(item => (_jsxs(Fragment, { children: [item.divider === 'before' && _jsx(Divider, { orientation: 'horizontal', className: styles.bottomDivider }), _jsx(CardNavigation, { ...(item.href ? { href: item.href, as: 'a' } : { as: 'button', type: 'button' }), title: item.label, icon: getLinkEmblem(item), onClick: item.onClick, "data-test-id": `header__drawer-menu__setting-${item.id}` }), item.divider === 'after' && _jsx(Divider, { orientation: 'horizontal', className: styles.bottomDivider })] }, item.id))) })), leftBottom] }));
|
|
10
10
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Divider } from '@cloud-ru/ds-divider';
|
|
3
|
+
import { SNAP_POINTS_PRESET } from '@cloud-ru/ds-list';
|
|
3
4
|
import { useCallback, useMemo } from 'react';
|
|
4
|
-
import {
|
|
5
|
+
import { TEST_IDS } from '../../../../constants.js';
|
|
5
6
|
import { MobileDrawerCustom } from '../../../../mobileOverlays.js';
|
|
6
7
|
import { MainMenuDndContext } from '../../hooks/useMainMenuDnd.js';
|
|
7
8
|
import { useMenuItems } from '../../hooks/useMenuItems.js';
|
|
@@ -32,5 +33,5 @@ export function MenuMobile({ open = false, setOpen, settingItems, platformGroups
|
|
|
32
33
|
const handleClose = useCallback(() => {
|
|
33
34
|
setOpen?.(false);
|
|
34
35
|
}, [setOpen]);
|
|
35
|
-
return (_jsx(MobileDrawerCustom, { open: open, onClose: handleClose, position: 'bottom', className: styles.drawerMobile, swipeEnabled: false, "data-test-id": TEST_IDS.mainMenu.drawerMobile, closeOnPopstate: true, snapPoints:
|
|
36
|
+
return (_jsx(MobileDrawerCustom, { open: open, onClose: handleClose, position: 'bottom', className: styles.drawerMobile, swipeEnabled: false, "data-test-id": TEST_IDS.mainMenu.drawerMobile, closeOnPopstate: true, snapPoints: SNAP_POINTS_PRESET.full, disableMotions: true, children: _jsx(ScrollWithAnimatedStickyPanel, { panel: _jsxs(_Fragment, { children: [_jsx(MenuHeaderBrand, { logo: logo, onClose: handleClose, className: styles.menuHeader, isMobile: true }), _jsx(Divider, { orientation: 'horizontal' })] }), children: _jsxs(MountAnimation, { className: styles.scrollMobile, type: 'fade-slide-up', children: [leftTop, search && _jsx(Search, { ...search, ref: searchRef, isMobile: true }), !isSearching && favorite && _jsx(Favorites, { favorite: favorite, servicesById: servicesById, isMobile: true }), hasSegments && (_jsx(MainMenuDndContext, { children: _jsx(Content, { isMobile: true, searchValue: search && search.value, rightTop: rightTop, favorite: favorite, segments: segments, searchGroups: resultItems, segmentPrefs: segmentPrefs, activeSegmentId: activeSegmentId, onActiveSegmentChange: onActiveSegmentChange, onSegmentOrderChange: onSegmentOrderChange, onSegmentExpandedChange: onSegmentExpandedChange, onSegmentServiceClick: onSegmentServiceClick, loading: loading }) })), !isSearching && hasBottomItems && _jsx(MenuBottom, { settingItems: settingItems, leftBottom: leftBottom })] }) }) }));
|
|
36
37
|
}
|
|
@@ -2,6 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Scroll } from '@cloud-ru/ds-scroll';
|
|
3
3
|
import { useCallback, useRef, useState } from 'react';
|
|
4
4
|
import styles from './styles.module.css';
|
|
5
|
+
// Упругая прокрутка у верхней границы (touch-скролл на мобильных не "упирается" жёстко в 0 —
|
|
6
|
+
// контент можно немного "оттянуть" дальше) даёт scrollTop, не совпадающий с реальным положением
|
|
7
|
+
// контента в этот момент. Диапазон запаса — единицы пикселей; двух хватает с большим запасом.
|
|
8
|
+
const TOP_OVERSCROLL_EPSILON_PX = 2;
|
|
5
9
|
export function ScrollWithAnimatedStickyPanel({ panel, children }) {
|
|
6
10
|
const [positions, setPositions] = useState({ panelTopShift: 0, containerScroll: 0 });
|
|
7
11
|
const [panelHeight, setPanelHeight] = useState(0);
|
|
@@ -14,6 +18,13 @@ export function ScrollWithAnimatedStickyPanel({ panel, children }) {
|
|
|
14
18
|
return;
|
|
15
19
|
const panelHeight = panelRef.current.offsetHeight;
|
|
16
20
|
setPanelHeight(panelHeight);
|
|
21
|
+
// У верхней границы панель всегда полностью видна — не доверяем накопленному diff. Иначе
|
|
22
|
+
// "лишний" ход упругой прокрутки у самого верха читается как скролл вниз и на возврате к
|
|
23
|
+
// началу списка панель может остаться скрытой, хотя пользователь как раз скроллит наверх.
|
|
24
|
+
if (target.scrollTop <= TOP_OVERSCROLL_EPSILON_PX) {
|
|
25
|
+
setPositions({ panelTopShift: 0, containerScroll: target.scrollTop });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
17
28
|
setPositions(prev => {
|
|
18
29
|
const diff = prev.containerScroll - target.scrollTop;
|
|
19
30
|
return {
|
|
@@ -22,10 +33,11 @@ export function ScrollWithAnimatedStickyPanel({ panel, children }) {
|
|
|
22
33
|
};
|
|
23
34
|
});
|
|
24
35
|
}, []);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
const setPanelRef = useCallback((element) => {
|
|
37
|
+
if (element) {
|
|
38
|
+
setPanelHeight(element.offsetHeight);
|
|
39
|
+
panelRef.current = element;
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
return (_jsxs("div", { className: styles.container, style: { '--snack-autohide-panel-height': `${panelHeight}px` }, children: [_jsx(Scroll, { barHideStrategy: 'never', overflow: { x: 'hidden' }, onScroll: handleScroll, children: children }), _jsx("div", { className: styles.panel, ref: setPanelRef, style: { top: positions.panelTopShift }, children: panel })] }));
|
|
31
43
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Avatar } from '@cloud-ru/ds-avatar';
|
|
3
|
-
import { Droplist } from '@cloud-ru/ds-list';
|
|
3
|
+
import { Droplist, SNAP_POINTS_PRESET } from '@cloud-ru/ds-list';
|
|
4
4
|
import { useValueControl } from '@cloud-ru/ds-utils';
|
|
5
5
|
import cn from 'classnames';
|
|
6
|
-
import { useMemo } from 'react';
|
|
7
|
-
import {
|
|
6
|
+
import { useCallback, useMemo } from 'react';
|
|
7
|
+
import { TEST_IDS } from '../../constants.js';
|
|
8
8
|
import { useDesktopComfortClassName } from '../../hooks/useDesktopComfortClassName.js';
|
|
9
9
|
import { useMobileLayout } from '../../hooks/useMobileLayout.js';
|
|
10
10
|
import { headerLocale } from '../../locale/index.js';
|
|
@@ -22,6 +22,9 @@ export function UserMenu({ profile = {}, open: openProp, setOpen: setOpenProp, o
|
|
|
22
22
|
const comfortClassName = useDesktopComfortClassName();
|
|
23
23
|
const [open = false, setOpen] = useValueControl({ value: openProp, onChange: setOpenProp });
|
|
24
24
|
const { fullName = '', inviteCount } = profile;
|
|
25
|
+
const handleClose = useCallback(() => {
|
|
26
|
+
setOpen(false);
|
|
27
|
+
}, [setOpen]);
|
|
25
28
|
const { pinTop, items: menuItems, pinBottom, } = useUserMenuItems({
|
|
26
29
|
isMobile,
|
|
27
30
|
profile,
|
|
@@ -30,9 +33,7 @@ export function UserMenu({ profile = {}, open: openProp, setOpen: setOpenProp, o
|
|
|
30
33
|
organizationItems,
|
|
31
34
|
bottomItems,
|
|
32
35
|
settingItems,
|
|
33
|
-
onClose:
|
|
34
|
-
setOpen(false);
|
|
35
|
-
},
|
|
36
|
+
onClose: handleClose,
|
|
36
37
|
onLogout,
|
|
37
38
|
});
|
|
38
39
|
const trigger = useMemo(() => (_jsx(HeaderButton, { tooltip: { tip: triggerTooltip }, isMobile: isMobile, onClick: () => {
|
|
@@ -44,5 +45,5 @@ export function UserMenu({ profile = {}, open: openProp, setOpen: setOpenProp, o
|
|
|
44
45
|
appearance: 'primary',
|
|
45
46
|
}
|
|
46
47
|
: undefined, "data-test-id": TEST_IDS.userMenu.button, icon: _jsx(Avatar, { appearance: 'red', size: 's', name: fullName, showTwoSymbols: true }), "data-pressed": open })), [fullName, inviteCount, onClick, open, setOpen, isMobile, triggerTooltip]);
|
|
47
|
-
return (_jsx(Droplist, { open: open, onOpenChange: setOpen, size: 'm', selection: SELECTION_STUB, pinTop: pinTop, items: menuItems, pinBottom: pinBottom, scroll: !isMobile, trigger: 'click', placement: 'bottom-end', className: cn(styles.userMenuDroplist, comfortClassName), closeOnPopstate: true, "data-test-id": TEST_IDS.userMenu.root, label: t('user'), snapPoints:
|
|
48
|
+
return (_jsx(Droplist, { open: open, onOpenChange: setOpen, size: 'm', selection: SELECTION_STUB, pinTop: pinTop, items: menuItems, pinBottom: pinBottom, scroll: !isMobile, trigger: 'click', placement: 'bottom-end', className: cn(styles.userMenuDroplist, comfortClassName), closeOnPopstate: true, "data-test-id": TEST_IDS.userMenu.root, label: t('user'), snapPoints: SNAP_POINTS_PRESET.full, withDividers: false, triggerClassName: styles.userMenuDroplistTrigger, children: trigger }));
|
|
48
49
|
}
|
|
@@ -2,16 +2,16 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { ExitSVG } from '@cloud-ru/ds-icons/interface/product';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
4
|
import { headerLocale } from '../../../locale/index.js';
|
|
5
|
+
import styles from '../styles.module.css';
|
|
5
6
|
export function useLogoutItem({ onLogout }) {
|
|
6
7
|
const { t } = headerLocale.useTranslations();
|
|
7
|
-
return useMemo(() =>
|
|
8
|
-
{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
label: t('logout'),
|
|
12
|
-
},
|
|
13
|
-
onClick: onLogout,
|
|
14
|
-
'data-test-id': 'header__user-menu__logout',
|
|
8
|
+
return useMemo(() => ({
|
|
9
|
+
beforeContent: _jsx(ExitSVG, {}),
|
|
10
|
+
content: {
|
|
11
|
+
label: t('logout'),
|
|
15
12
|
},
|
|
16
|
-
|
|
13
|
+
className: styles.logoutItem,
|
|
14
|
+
onClick: onLogout,
|
|
15
|
+
'data-test-id': 'header__user-menu__logout',
|
|
16
|
+
}), [onLogout, t]);
|
|
17
17
|
}
|
|
@@ -35,11 +35,11 @@ export function useUserMenuItems({ profile, theme, onLogout, topItems = EMPTY_AR
|
|
|
35
35
|
}
|
|
36
36
|
pinTop = pinTop.concat(topItems);
|
|
37
37
|
let pinBottom = bottomItems.concat(logoutItem);
|
|
38
|
-
if (isMobile && Boolean(settingItems?.length)) {
|
|
39
|
-
pinBottom = pinBottom.concat([DIVIDER_ITEM, ...settingItems]);
|
|
40
|
-
}
|
|
41
38
|
// В шторке прокручивается всё меню, закреплять части незачем; разделитель над организациями добавляем сами
|
|
42
39
|
if (isMobile) {
|
|
40
|
+
if (settingItems?.length) {
|
|
41
|
+
pinBottom = [...bottomItems, ...settingItems, DIVIDER_ITEM, logoutItem];
|
|
42
|
+
}
|
|
43
43
|
return {
|
|
44
44
|
pinTop: [],
|
|
45
45
|
items: withClose([...pinTop, DIVIDER_ITEM, ...organizationItems, ...pinBottom]),
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
.userMenuDroplistTrigger{
|
|
2
|
+
display:inline-flex;
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
.userMenuDroplist{
|
|
2
6
|
width:256px;
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
.profileSettingsIcon{
|
|
6
10
|
color:var(--sn-theme-color-neutral-text, var(--sn-brand-color-neutral-40, var(--sn-brand-palette-gray-40, #81869c)));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.logoutItem span,
|
|
14
|
+
.logoutItem svg{
|
|
15
|
+
color:var(--sn-theme-color-red-text, var(--sn-brand-palette-red-40, #a93533));
|
|
7
16
|
}
|
package/dist/esm/constants.d.ts
CHANGED
package/dist/esm/constants.js
CHANGED
package/dist/esm/style.css
CHANGED
|
@@ -951,10 +951,19 @@
|
|
|
951
951
|
color:var(--sn-theme-color-neutral-onAccent, var(--sn-brand-color-neutral-99, var(--sn-brand-palette-gray-99, #ffffff)));
|
|
952
952
|
background:var(--sn-theme-color-neutral-accent, var(--sn-brand-color-neutral-50, var(--sn-brand-palette-gray-50, #8b8fa7)));
|
|
953
953
|
}
|
|
954
|
+
.userMenuDroplistTrigger{
|
|
955
|
+
display:inline-flex;
|
|
956
|
+
}
|
|
957
|
+
|
|
954
958
|
.userMenuDroplist{
|
|
955
959
|
width:256px;
|
|
956
960
|
}
|
|
957
961
|
|
|
958
962
|
.profileSettingsIcon{
|
|
959
963
|
color:var(--sn-theme-color-neutral-text, var(--sn-brand-color-neutral-40, var(--sn-brand-palette-gray-40, #81869c)));
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.logoutItem span,
|
|
967
|
+
.logoutItem svg{
|
|
968
|
+
color:var(--sn-theme-color-red-text, var(--sn-brand-palette-red-40, #a93533));
|
|
960
969
|
}
|