@gravity-ui/page-constructor 1.6.1 → 1.7.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/build/cjs/blocks/ExtendedFeatures/ExtendedFeatures.css +6 -1
- package/build/cjs/blocks/ExtendedFeatures/ExtendedFeatures.js +26 -12
- package/build/cjs/components/RouterLink/RouterLink.d.ts +1 -0
- package/build/cjs/components/navigation/components/Header/Header.css +85 -0
- package/build/cjs/components/navigation/components/Header/Header.d.ts +28 -0
- package/build/cjs/components/navigation/components/Header/Header.js +81 -0
- package/build/cjs/components/navigation/components/Logo/Logo.css +18 -0
- package/build/cjs/components/navigation/components/Logo/Logo.d.ts +7 -0
- package/build/cjs/components/navigation/components/Logo/Logo.js +13 -0
- package/build/cjs/components/navigation/components/MobileNavigation/MobileNavigation.css +58 -0
- package/build/cjs/components/navigation/components/MobileNavigation/MobileNavigation.d.ts +13 -0
- package/build/cjs/components/navigation/components/MobileNavigation/MobileNavigation.js +45 -0
- package/build/cjs/components/navigation/components/Navigation/Navigation.css +43 -0
- package/build/cjs/components/navigation/components/Navigation/Navigation.d.ts +11 -0
- package/build/cjs/components/navigation/components/Navigation/Navigation.js +70 -0
- package/build/cjs/components/navigation/components/NavigationItem/NavigationItem.css +40 -0
- package/build/cjs/components/navigation/components/NavigationItem/NavigationItem.d.ts +12 -0
- package/build/cjs/components/navigation/components/NavigationItem/NavigationItem.js +47 -0
- package/build/cjs/components/navigation/components/NavigationPopup/NavigationPopup.css +33 -0
- package/build/cjs/components/navigation/components/NavigationPopup/NavigationPopup.d.ts +24 -0
- package/build/cjs/components/navigation/components/NavigationPopup/NavigationPopup.js +57 -0
- package/build/cjs/components/navigation/components/SocialIcon/SocialIcon.css +20 -0
- package/build/cjs/components/navigation/components/SocialIcon/SocialIcon.d.ts +7 -0
- package/build/cjs/components/navigation/components/SocialIcon/SocialIcon.js +10 -0
- package/build/cjs/containers/PageConstructor/PageConstructor.d.ts +2 -0
- package/build/cjs/containers/PageConstructor/PageConstructor.js +4 -2
- package/build/cjs/context/locationContext/locationContext.d.ts +1 -0
- package/build/cjs/icons/NavigationArrow.d.ts +2 -0
- package/build/cjs/icons/NavigationArrow.js +9 -0
- package/build/cjs/icons/NavigationClose.d.ts +2 -0
- package/build/cjs/icons/NavigationClose.js +9 -0
- package/build/cjs/icons/NavigationOpen.d.ts +2 -0
- package/build/cjs/icons/NavigationOpen.js +11 -0
- package/build/cjs/icons/index.d.ts +3 -0
- package/build/cjs/icons/index.js +3 -0
- package/build/cjs/models/constructor-items/blocks.d.ts +3 -1
- package/build/cjs/models/navigation.d.ts +59 -0
- package/build/cjs/models/navigation.js +10 -0
- package/build/cjs/text-transform/blocks.js +6 -1
- package/build/esm/blocks/ExtendedFeatures/ExtendedFeatures.css +6 -1
- package/build/esm/blocks/ExtendedFeatures/ExtendedFeatures.js +28 -14
- package/build/esm/components/RouterLink/RouterLink.d.ts +1 -0
- package/build/esm/components/navigation/components/Header/Header.css +85 -0
- package/build/esm/components/navigation/components/Header/Header.d.ts +29 -0
- package/build/esm/components/navigation/components/Header/Header.js +79 -0
- package/build/esm/components/navigation/components/Logo/Logo.css +18 -0
- package/build/esm/components/navigation/components/Logo/Logo.d.ts +8 -0
- package/build/esm/components/navigation/components/Logo/Logo.js +11 -0
- package/build/esm/components/navigation/components/MobileNavigation/MobileNavigation.css +58 -0
- package/build/esm/components/navigation/components/MobileNavigation/MobileNavigation.d.ts +14 -0
- package/build/esm/components/navigation/components/MobileNavigation/MobileNavigation.js +43 -0
- package/build/esm/components/navigation/components/Navigation/Navigation.css +43 -0
- package/build/esm/components/navigation/components/Navigation/Navigation.d.ts +12 -0
- package/build/esm/components/navigation/components/Navigation/Navigation.js +69 -0
- package/build/esm/components/navigation/components/NavigationItem/NavigationItem.css +40 -0
- package/build/esm/components/navigation/components/NavigationItem/NavigationItem.d.ts +13 -0
- package/build/esm/components/navigation/components/NavigationItem/NavigationItem.js +46 -0
- package/build/esm/components/navigation/components/NavigationPopup/NavigationPopup.css +33 -0
- package/build/esm/components/navigation/components/NavigationPopup/NavigationPopup.d.ts +25 -0
- package/build/esm/components/navigation/components/NavigationPopup/NavigationPopup.js +54 -0
- package/build/esm/components/navigation/components/SocialIcon/SocialIcon.css +20 -0
- package/build/esm/components/navigation/components/SocialIcon/SocialIcon.d.ts +8 -0
- package/build/esm/components/navigation/components/SocialIcon/SocialIcon.js +8 -0
- package/build/esm/containers/PageConstructor/PageConstructor.d.ts +2 -0
- package/build/esm/containers/PageConstructor/PageConstructor.js +4 -2
- package/build/esm/context/locationContext/locationContext.d.ts +1 -0
- package/build/esm/icons/NavigationArrow.d.ts +2 -0
- package/build/esm/icons/NavigationArrow.js +4 -0
- package/build/esm/icons/NavigationClose.d.ts +2 -0
- package/build/esm/icons/NavigationClose.js +4 -0
- package/build/esm/icons/NavigationOpen.d.ts +2 -0
- package/build/esm/icons/NavigationOpen.js +6 -0
- package/build/esm/icons/index.d.ts +3 -0
- package/build/esm/icons/index.js +3 -0
- package/build/esm/models/constructor-items/blocks.d.ts +3 -1
- package/build/esm/models/navigation.d.ts +59 -0
- package/build/esm/models/navigation.js +7 -0
- package/build/esm/text-transform/blocks.js +6 -1
- package/package.json +4 -1
- package/server/models/constructor-items/blocks.d.ts +3 -1
- package/server/text-transform/blocks.js +6 -1
- package/styles/mixins.scss +38 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { createRef } from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import { Col, Grid, Row } from '../../../../grid';
|
|
4
|
+
import OutsideClick from '../../../OutsideClick/OutsideClick';
|
|
5
|
+
import Control from '../../../Control/Control';
|
|
6
|
+
import Logo from '../Logo/Logo';
|
|
7
|
+
import Navigation from '../Navigation/Navigation';
|
|
8
|
+
import MobileNavigation from '../MobileNavigation/MobileNavigation';
|
|
9
|
+
import NavigationItem from '../NavigationItem/NavigationItem';
|
|
10
|
+
import { NavigationClose, NavigationOpen } from '../../../../icons';
|
|
11
|
+
import './Header.css';
|
|
12
|
+
const b = block('header');
|
|
13
|
+
class Header extends React.Component {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.ref = createRef();
|
|
17
|
+
this.state = {
|
|
18
|
+
isSidebarOpened: false,
|
|
19
|
+
activeItemIndex: -1,
|
|
20
|
+
};
|
|
21
|
+
this.onActiveItemChange = (index) => {
|
|
22
|
+
this.setState({ activeItemIndex: index });
|
|
23
|
+
};
|
|
24
|
+
this.onSidebarOpenedChange = (isSidebarOpened) => {
|
|
25
|
+
this.setState({ isSidebarOpened });
|
|
26
|
+
};
|
|
27
|
+
this.hideSidebar = () => {
|
|
28
|
+
this.setState({ isSidebarOpened: false });
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
render() {
|
|
32
|
+
return (React.createElement(Grid, { className: b() },
|
|
33
|
+
React.createElement(Row, null,
|
|
34
|
+
React.createElement(Col, null,
|
|
35
|
+
React.createElement("header", { className: b('wrapper') },
|
|
36
|
+
this.renderLogo(),
|
|
37
|
+
this.renderLeft(),
|
|
38
|
+
this.renderRight(),
|
|
39
|
+
this.renderMobileNavigation())))));
|
|
40
|
+
}
|
|
41
|
+
renderLeft() {
|
|
42
|
+
const { activeItemIndex } = this.state;
|
|
43
|
+
const { leftItems } = this.props.data;
|
|
44
|
+
return (leftItems && (React.createElement("div", { className: b('navigation-container') },
|
|
45
|
+
React.createElement(Navigation, { className: b('navigation'), links: leftItems, activeItemIndex: activeItemIndex, onActiveItemChange: this.onActiveItemChange }))));
|
|
46
|
+
}
|
|
47
|
+
renderLogo() {
|
|
48
|
+
const { logo } = this.props;
|
|
49
|
+
if (!logo) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return (React.createElement("div", { className: b('left') },
|
|
53
|
+
React.createElement(Logo, Object.assign({}, logo, { className: b('logo') }))));
|
|
54
|
+
}
|
|
55
|
+
renderRight() {
|
|
56
|
+
return (React.createElement("div", { className: b('right') },
|
|
57
|
+
this.renderMobileMenuButton(),
|
|
58
|
+
this.renderRightItems()));
|
|
59
|
+
}
|
|
60
|
+
renderRightItems() {
|
|
61
|
+
const { rightItems } = this.props.data;
|
|
62
|
+
return (rightItems && (React.createElement("div", { className: b('buttons') }, rightItems.map((button) => (React.createElement(NavigationItem, { key: button.text, data: button, className: b('button') }))))));
|
|
63
|
+
}
|
|
64
|
+
renderMobileMenuButton() {
|
|
65
|
+
const { isSidebarOpened } = this.state;
|
|
66
|
+
const iconProps = { icon: isSidebarOpened ? NavigationClose : NavigationOpen, iconSize: 36 };
|
|
67
|
+
return (React.createElement(Control, Object.assign({ className: b('mobile-menu-button'), onClick: (e) => {
|
|
68
|
+
e.stopPropagation();
|
|
69
|
+
this.onSidebarOpenedChange(!isSidebarOpened);
|
|
70
|
+
}, size: "l" }, iconProps)));
|
|
71
|
+
}
|
|
72
|
+
renderMobileNavigation() {
|
|
73
|
+
const { leftItems, rightItems } = this.props.data;
|
|
74
|
+
const { isSidebarOpened, activeItemIndex } = this.state;
|
|
75
|
+
return (React.createElement(OutsideClick, { onOutsideClick: () => this.onSidebarOpenedChange(false) },
|
|
76
|
+
React.createElement(MobileNavigation, { topItems: leftItems, bottomItems: rightItems, isOpened: isSidebarOpened, activeItemIndex: activeItemIndex, onActiveItemChange: this.onActiveItemChange, onClose: this.hideSidebar })));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export default Header;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* use this for style redefinitions to awoid problems with
|
|
2
|
+
unpredictable css rules order in build */
|
|
3
|
+
.logo {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
font-weight: 500;
|
|
7
|
+
font-size: var(--yc-text-header-2-font-size);
|
|
8
|
+
line-height: var(--yc-text-header-2-line-height);
|
|
9
|
+
}
|
|
10
|
+
.logo__icon {
|
|
11
|
+
width: 178px;
|
|
12
|
+
height: 36px;
|
|
13
|
+
margin-right: 8px;
|
|
14
|
+
object-fit: contain;
|
|
15
|
+
}
|
|
16
|
+
.logo__text {
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import { Image } from '../../../index';
|
|
4
|
+
import RouterLink from '../../../RouterLink/RouterLink';
|
|
5
|
+
import './Logo.css';
|
|
6
|
+
const b = block('logo');
|
|
7
|
+
const Logo = ({ icon, text, className }) => (React.createElement(RouterLink, { href: "/", passHref: true },
|
|
8
|
+
React.createElement("div", { className: b(null, className) },
|
|
9
|
+
icon && React.createElement(Image, { className: b('icon'), src: icon }),
|
|
10
|
+
React.createElement("span", { className: b('text') }, text))));
|
|
11
|
+
export default Logo;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* use this for style redefinitions to awoid problems with
|
|
2
|
+
unpredictable css rules order in build */
|
|
3
|
+
.mobile-navigation {
|
|
4
|
+
position: fixed;
|
|
5
|
+
z-index: 100;
|
|
6
|
+
top: var(--header-height);
|
|
7
|
+
left: 0;
|
|
8
|
+
width: 100%;
|
|
9
|
+
border-bottom-right-radius: var(--pc-border-radius);
|
|
10
|
+
border-bottom-left-radius: var(--pc-border-radius);
|
|
11
|
+
background-color: var(--yc-color-base-background);
|
|
12
|
+
box-shadow: 0px 3px 10px var(--yc-color-sfx-shadow);
|
|
13
|
+
font-size: var(--yc-text-body-2-font-size);
|
|
14
|
+
line-height: var(--yc-text-body-2-line-height);
|
|
15
|
+
}
|
|
16
|
+
@media (min-width: 769px) {
|
|
17
|
+
.mobile-navigation {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
.mobile-navigation__wrapper {
|
|
22
|
+
padding: 32px 20px;
|
|
23
|
+
}
|
|
24
|
+
.mobile-navigation__button {
|
|
25
|
+
margin-top: 24px;
|
|
26
|
+
}
|
|
27
|
+
.mobile-navigation__links {
|
|
28
|
+
position: relative;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
padding-bottom: 24px;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
list-style: none;
|
|
35
|
+
}
|
|
36
|
+
.mobile-navigation__links-item:not(:last-child) {
|
|
37
|
+
margin-bottom: 24px;
|
|
38
|
+
}
|
|
39
|
+
.mobile-navigation__dropdown-item:not(:last-child) {
|
|
40
|
+
margin-bottom: 16px;
|
|
41
|
+
}
|
|
42
|
+
.mobile-navigation__popup {
|
|
43
|
+
z-index: 101;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
min-width: 220px;
|
|
47
|
+
padding: 16px;
|
|
48
|
+
border: 1px solid var(--yc-color-line-generic);
|
|
49
|
+
border-top-width: 0;
|
|
50
|
+
border-radius: calc(var(--pc-border-radius) / 2);
|
|
51
|
+
background: var(--yc-color-base-float);
|
|
52
|
+
box-shadow: 0 3px 10px var(--yc-color-sfx-shadow);
|
|
53
|
+
}
|
|
54
|
+
@media (min-width: 769px) {
|
|
55
|
+
.mobile-navigation__popup {
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NavigationItem as NavigationItemModel } from '../../../../models/navigation';
|
|
3
|
+
import './MobileNavigation.css';
|
|
4
|
+
export interface MobileNavigationProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
isOpened?: boolean;
|
|
7
|
+
topItems?: NavigationItemModel[];
|
|
8
|
+
bottomItems?: NavigationItemModel[];
|
|
9
|
+
activeItemIndex: number;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
onActiveItemChange: (index: number) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const MobileNavigation: React.FC<MobileNavigationProps>;
|
|
14
|
+
export default MobileNavigation;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import block from 'bem-cn-lite';
|
|
2
|
+
import React, { useRef, useCallback } from 'react';
|
|
3
|
+
import Foldable from '../../../Foldable/Foldable';
|
|
4
|
+
import { Popup, Portal } from '@gravity-ui/uikit';
|
|
5
|
+
import { NavigationItemType, } from '../../../../models/navigation';
|
|
6
|
+
import NavigationItem from '../NavigationItem/NavigationItem';
|
|
7
|
+
import './MobileNavigation.css';
|
|
8
|
+
const b = block('mobile-navigation');
|
|
9
|
+
const MobileNavigationDropdown = ({ data, onItemClick, onToggle, isOpened = false, }) => {
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
return (React.createElement("div", { ref: ref, className: b('dropdown') },
|
|
12
|
+
React.createElement(NavigationItem, { data: data, onClick: onToggle, isOpened: isOpened }),
|
|
13
|
+
isOpened && (React.createElement(Popup, { anchorRef: ref, open: isOpened, className: b('popup') }, data.items.map((item) => (React.createElement(NavigationItem, { key: item.text, data: item, className: b('dropdown-item'), onClick: onItemClick })))))));
|
|
14
|
+
};
|
|
15
|
+
const MobileNavigationItem = ({ link, index, isActive, onActiveItemChange, onClose, }) => {
|
|
16
|
+
const toggleActive = useCallback((e) => {
|
|
17
|
+
e.stopPropagation();
|
|
18
|
+
if (onActiveItemChange) {
|
|
19
|
+
onActiveItemChange(isActive ? -1 : index);
|
|
20
|
+
}
|
|
21
|
+
}, [isActive, index, onActiveItemChange]);
|
|
22
|
+
const onItemClick = useCallback((e) => {
|
|
23
|
+
toggleActive(e);
|
|
24
|
+
onClose();
|
|
25
|
+
}, [toggleActive, onClose]);
|
|
26
|
+
return (React.createElement("li", { key: index, className: b('links-item') }, link.type === NavigationItemType.Dropdown ? (React.createElement(MobileNavigationDropdown, { data: link, onToggle: toggleActive, isOpened: isActive, onItemClick: onItemClick })) : (React.createElement(NavigationItem, { data: link, onClick: onItemClick }))));
|
|
27
|
+
};
|
|
28
|
+
const MobileNavigation = (props) => {
|
|
29
|
+
if (typeof window === 'undefined') {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const { isOpened, topItems, bottomItems, activeItemIndex, onActiveItemChange, onClose } = props;
|
|
33
|
+
return (React.createElement(Portal, null,
|
|
34
|
+
React.createElement(Foldable, { key: topItems === null || topItems === void 0 ? void 0 : topItems.length, className: b(), isOpened: Boolean(isOpened) },
|
|
35
|
+
React.createElement("div", { className: b('wrapper') },
|
|
36
|
+
React.createElement("nav", null,
|
|
37
|
+
React.createElement("ul", { className: b('links') }, topItems === null || topItems === void 0 ? void 0 : topItems.map((link, index) => {
|
|
38
|
+
const isActive = index === activeItemIndex;
|
|
39
|
+
return (React.createElement(MobileNavigationItem, { key: index, link: link, index: index, isActive: isOpened && isActive, onClose: onClose, onActiveItemChange: onActiveItemChange }));
|
|
40
|
+
}))), bottomItems === null || bottomItems === void 0 ? void 0 :
|
|
41
|
+
bottomItems.map((item) => (React.createElement(NavigationItem, { key: item.text, data: item, className: b('button') })))))));
|
|
42
|
+
};
|
|
43
|
+
export default MobileNavigation;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* use this for style redefinitions to awoid problems with
|
|
2
|
+
unpredictable css rules order in build */
|
|
3
|
+
.navigation {
|
|
4
|
+
font-size: var(--yc-text-body-2-font-size);
|
|
5
|
+
line-height: var(--yc-text-body-2-line-height);
|
|
6
|
+
}
|
|
7
|
+
.navigation__links {
|
|
8
|
+
position: relative;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
list-style: none;
|
|
14
|
+
}
|
|
15
|
+
.navigation__links-item {
|
|
16
|
+
position: relative;
|
|
17
|
+
height: var(--header-height);
|
|
18
|
+
line-height: var(--header-height);
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
outline: none;
|
|
21
|
+
color: inherit;
|
|
22
|
+
text-decoration: none;
|
|
23
|
+
}
|
|
24
|
+
.utilityfocus .navigation__links-item:focus {
|
|
25
|
+
outline: 2px solid #ffdb4d;
|
|
26
|
+
}
|
|
27
|
+
.navigation__links-item:hover, .navigation__links-item:active {
|
|
28
|
+
color: var(--yc-color-text-link);
|
|
29
|
+
}
|
|
30
|
+
.navigation__links-item:not(:last-child) {
|
|
31
|
+
margin-right: 20px;
|
|
32
|
+
}
|
|
33
|
+
.navigation__slider-container {
|
|
34
|
+
position: absolute;
|
|
35
|
+
right: 0;
|
|
36
|
+
bottom: 0;
|
|
37
|
+
left: 0;
|
|
38
|
+
}
|
|
39
|
+
.navigation__slider {
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 2px;
|
|
42
|
+
background-color: var(--yc-color-text-link);
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NavigationItem as NavigationItemModel } from '../../../../models/navigation';
|
|
3
|
+
import './Navigation.css';
|
|
4
|
+
export interface NavigationProps {
|
|
5
|
+
links: NavigationItemModel[];
|
|
6
|
+
activeItemIndex: number;
|
|
7
|
+
onActiveItemChange: (index: number) => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
highlightActiveItem?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare const Navigation: React.FC<NavigationProps>;
|
|
12
|
+
export default Navigation;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { __rest } from "tslib";
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import block from 'bem-cn-lite';
|
|
4
|
+
import React, { Fragment, useState, useEffect, useCallback, useContext, useRef, } from 'react';
|
|
5
|
+
import OverflowScroller from '../../../OverflowScroller/OverflowScroller';
|
|
6
|
+
import { NavigationItemType, } from '../../../../models/navigation';
|
|
7
|
+
import NavigationPopup from '../NavigationPopup/NavigationPopup';
|
|
8
|
+
import NavigationItem from '../NavigationItem/NavigationItem';
|
|
9
|
+
import { LocationContext } from '../../../../context/locationContext';
|
|
10
|
+
import './Navigation.css';
|
|
11
|
+
const b = block('navigation');
|
|
12
|
+
const Navigation = ({ className, onActiveItemChange, links, activeItemIndex, highlightActiveItem, }) => {
|
|
13
|
+
const { asPath, pathname } = useContext(LocationContext);
|
|
14
|
+
const itemRefs = useRef([]);
|
|
15
|
+
const [itemPositions, setItemPosition] = useState([]);
|
|
16
|
+
const [lastLeftScroll, setLastLeftScroll] = useState(0);
|
|
17
|
+
const hidePopup = useCallback(() => {
|
|
18
|
+
onActiveItemChange(-1);
|
|
19
|
+
}, [onActiveItemChange]);
|
|
20
|
+
const getItemClickHandler = useCallback((index) => (e) => {
|
|
21
|
+
e.stopPropagation();
|
|
22
|
+
onActiveItemChange(index === activeItemIndex ? -1 : index);
|
|
23
|
+
}, [activeItemIndex, onActiveItemChange]);
|
|
24
|
+
const renderNavDropdown = (data, onClick, isActive, position) => {
|
|
25
|
+
const { text, items } = data, popupProps = __rest(data, ["text", "items"]);
|
|
26
|
+
return (React.createElement(Fragment, null,
|
|
27
|
+
React.createElement(NavigationItem, { className: b('link'), onClick: onClick, isOpened: isActive, data: { text, type: NavigationItemType.Dropdown } }),
|
|
28
|
+
isActive && (React.createElement(NavigationPopup, Object.assign({ left: position, onClose: hidePopup, items: items }, popupProps)))));
|
|
29
|
+
};
|
|
30
|
+
const slider = (React.createElement("div", { className: b('slider-container') },
|
|
31
|
+
React.createElement("div", { className: b('slider') })));
|
|
32
|
+
const content = (React.createElement("nav", null,
|
|
33
|
+
React.createElement("ul", { className: b('links') }, links.map((link, index) => {
|
|
34
|
+
const isActive = index === activeItemIndex;
|
|
35
|
+
const onClick = getItemClickHandler(index);
|
|
36
|
+
return (React.createElement("li", { ref: (el) => itemRefs.current.push(el), key: index, className: b('links-item') },
|
|
37
|
+
link.type === NavigationItemType.Dropdown ? (renderNavDropdown(link, onClick, isActive, itemPositions[index])) : (React.createElement(NavigationItem, { data: link, onClick: onClick })),
|
|
38
|
+
highlightActiveItem && isActive && slider));
|
|
39
|
+
}))));
|
|
40
|
+
const calculateItemPositions = useCallback(() => {
|
|
41
|
+
if (itemRefs.current.length) {
|
|
42
|
+
const currentItemPositions = itemRefs.current.map((itemRef) => (itemRef && itemRef.getBoundingClientRect().left) || 0);
|
|
43
|
+
setItemPosition(currentItemPositions);
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const debouncedCalculateItemPositions = _.debounce(calculateItemPositions, 100);
|
|
48
|
+
const calculateOnScroll = _.debounce(() => {
|
|
49
|
+
const curLeftScroll = window.pageXOffset;
|
|
50
|
+
if (curLeftScroll !== lastLeftScroll) {
|
|
51
|
+
setLastLeftScroll(window.pageXOffset);
|
|
52
|
+
calculateItemPositions();
|
|
53
|
+
}
|
|
54
|
+
}, 100);
|
|
55
|
+
calculateItemPositions();
|
|
56
|
+
setLastLeftScroll(window.pageXOffset);
|
|
57
|
+
window.addEventListener('resize', debouncedCalculateItemPositions);
|
|
58
|
+
window.addEventListener('scroll', calculateOnScroll);
|
|
59
|
+
return () => {
|
|
60
|
+
window.removeEventListener(`resize`, calculateItemPositions);
|
|
61
|
+
window.removeEventListener('scroll', calculateOnScroll);
|
|
62
|
+
};
|
|
63
|
+
}, [calculateItemPositions, itemRefs, lastLeftScroll]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
hidePopup();
|
|
66
|
+
}, [hidePopup, asPath, pathname]);
|
|
67
|
+
return (React.createElement(OverflowScroller, { className: b(null, className), onScrollStart: hidePopup, onScrollEnd: calculateItemPositions }, content));
|
|
68
|
+
};
|
|
69
|
+
export default Navigation;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* use this for style redefinitions to awoid problems with
|
|
2
|
+
unpredictable css rules order in build */
|
|
3
|
+
.navigation-item {
|
|
4
|
+
position: relative;
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
}
|
|
8
|
+
.navigation-item_type_link {
|
|
9
|
+
color: var(--yc-color-text-primary);
|
|
10
|
+
color: inherit;
|
|
11
|
+
text-decoration: none;
|
|
12
|
+
outline: none;
|
|
13
|
+
}
|
|
14
|
+
.utilityfocus .navigation-item_type_link:focus {
|
|
15
|
+
outline: 2px solid #ffdb4d;
|
|
16
|
+
}
|
|
17
|
+
.navigation-item_type_link:hover, .navigation-item_type_link_active {
|
|
18
|
+
color: var(--yc-color-text-link);
|
|
19
|
+
}
|
|
20
|
+
.navigation-item_type_button {
|
|
21
|
+
display: inline-block;
|
|
22
|
+
}
|
|
23
|
+
.navigation-item__arrow {
|
|
24
|
+
position: relative;
|
|
25
|
+
top: -2px;
|
|
26
|
+
width: 9px;
|
|
27
|
+
height: 9px;
|
|
28
|
+
margin-left: 5px;
|
|
29
|
+
}
|
|
30
|
+
.navigation-item__icon {
|
|
31
|
+
width: 20px;
|
|
32
|
+
height: 20px;
|
|
33
|
+
margin-right: 8px;
|
|
34
|
+
border-radius: 50%;
|
|
35
|
+
object-fit: cover;
|
|
36
|
+
}
|
|
37
|
+
.navigation-item__dropdown {
|
|
38
|
+
margin-left: 7px;
|
|
39
|
+
color: var(--yc-color-text-secondary);
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { MouseEventHandler } from 'react';
|
|
2
|
+
import { NavigationButtonItem, NavigationDropdownItem, NavigationSocialItem, NavigationLinkItem } from '../../../../models/navigation';
|
|
3
|
+
import './NavigationItem.css';
|
|
4
|
+
declare type DropdownItemData = Omit<NavigationDropdownItem, 'items'>;
|
|
5
|
+
export declare type NavigationItemData = NavigationLinkItem | NavigationButtonItem | NavigationSocialItem | DropdownItemData;
|
|
6
|
+
export interface NavigationItemProps {
|
|
7
|
+
data: NavigationItemData;
|
|
8
|
+
className?: string;
|
|
9
|
+
onClick?: MouseEventHandler;
|
|
10
|
+
isOpened?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const NavigationItem: React.FC<NavigationItemProps>;
|
|
13
|
+
export default NavigationItem;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { __rest } from "tslib";
|
|
2
|
+
import React, { Fragment, useContext, useMemo } from 'react';
|
|
3
|
+
import block from 'bem-cn-lite';
|
|
4
|
+
import { RouterLink, ToggleArrow } from '../../../index';
|
|
5
|
+
import { getLinkProps } from '../../../../utils';
|
|
6
|
+
import { LocationContext } from '../../../../context/locationContext';
|
|
7
|
+
import { NavigationItemType, } from '../../../../models/navigation';
|
|
8
|
+
import { NavigationArrow } from '../../../../icons';
|
|
9
|
+
import SocialIcon from '../SocialIcon/SocialIcon';
|
|
10
|
+
import './NavigationItem.css';
|
|
11
|
+
const b = block('navigation-item');
|
|
12
|
+
const Content = ({ text, icon }) => (React.createElement(Fragment, null,
|
|
13
|
+
icon && React.createElement("img", { className: b('icon'), src: icon }),
|
|
14
|
+
React.createElement("span", { className: b('text') }, text)));
|
|
15
|
+
const NavigationDropdown = (_a) => {
|
|
16
|
+
var { text, icon, isOpened } = _a, props = __rest(_a, ["text", "icon", "isOpened"]);
|
|
17
|
+
return (React.createElement("span", Object.assign({}, props),
|
|
18
|
+
React.createElement(Content, { text: text, icon: icon }),
|
|
19
|
+
React.createElement(ToggleArrow, { className: b('dropdown'), size: 12, type: 'vertical', iconType: "navigation", open: isOpened })));
|
|
20
|
+
};
|
|
21
|
+
const NavigationLink = (props) => {
|
|
22
|
+
const { hostname } = useContext(LocationContext);
|
|
23
|
+
const { url, text, icon, arrow, target } = props, rest = __rest(props, ["url", "text", "icon", "arrow", "target"]);
|
|
24
|
+
const linkExtraProps = getLinkProps(url, hostname, target);
|
|
25
|
+
const content = (React.createElement(Fragment, null,
|
|
26
|
+
React.createElement(Content, { text: text, icon: icon }),
|
|
27
|
+
arrow && React.createElement(NavigationArrow, { className: b('arrow') })));
|
|
28
|
+
return (linkExtraProps === null || linkExtraProps === void 0 ? void 0 : linkExtraProps.target) ? (React.createElement("a", Object.assign({ href: url, title: text }, rest, linkExtraProps), content)) : (React.createElement(RouterLink, { href: url, passHref: true },
|
|
29
|
+
React.createElement("a", Object.assign({}, rest), content)));
|
|
30
|
+
};
|
|
31
|
+
//todo: add types support form component in map
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
const NavigationItemsMap = {
|
|
34
|
+
[NavigationItemType.Button]: RouterLink,
|
|
35
|
+
[NavigationItemType.Social]: SocialIcon,
|
|
36
|
+
[NavigationItemType.Dropdown]: NavigationDropdown,
|
|
37
|
+
[NavigationItemType.Link]: NavigationLink,
|
|
38
|
+
};
|
|
39
|
+
const NavigationItem = (_a) => {
|
|
40
|
+
var { data, className } = _a, props = __rest(_a, ["data", "className"]);
|
|
41
|
+
const { type = NavigationItemType.Link } = data;
|
|
42
|
+
const Component = NavigationItemsMap[type];
|
|
43
|
+
const componentProps = useMemo(() => (Object.assign(Object.assign({ className: b({ type }, className) }, data), props)), [className, data, props, type]);
|
|
44
|
+
return React.createElement(Component, Object.assign({}, componentProps));
|
|
45
|
+
};
|
|
46
|
+
export default NavigationItem;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* use this for style redefinitions to awoid problems with
|
|
2
|
+
unpredictable css rules order in build */
|
|
3
|
+
.navigation-popup {
|
|
4
|
+
position: fixed;
|
|
5
|
+
top: calc(var(--header-height) - 16px);
|
|
6
|
+
padding-right: 4px;
|
|
7
|
+
padding-left: 4px;
|
|
8
|
+
transform: translateX(calc(calc(8px * 2) * -1));
|
|
9
|
+
z-index: 101;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
min-width: 220px;
|
|
13
|
+
padding: 16px;
|
|
14
|
+
border: 1px solid var(--yc-color-line-generic);
|
|
15
|
+
border-top-width: 0;
|
|
16
|
+
border-radius: calc(var(--pc-border-radius) / 2);
|
|
17
|
+
background: var(--yc-color-base-float);
|
|
18
|
+
box-shadow: 0 3px 10px var(--yc-color-sfx-shadow);
|
|
19
|
+
}
|
|
20
|
+
@media (max-width: 768px) {
|
|
21
|
+
.navigation-popup {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
.navigation-popup__link {
|
|
26
|
+
height: 36px;
|
|
27
|
+
padding: 8px 12px;
|
|
28
|
+
border-radius: 8px;
|
|
29
|
+
}
|
|
30
|
+
.navigation-popup__link:hover {
|
|
31
|
+
color: var(--yc-color-text-primary);
|
|
32
|
+
background-color: var(--yc-color-base-simple-hover);
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { RefObject } from 'react';
|
|
2
|
+
import { NavigationLinkItem } from '../../../../models/navigation';
|
|
3
|
+
import './NavigationPopup.css';
|
|
4
|
+
export interface NavigationPopupProps {
|
|
5
|
+
items: NavigationLinkItem[];
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
left?: number;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
interface NavigationPopupState {
|
|
11
|
+
calculatedLeft?: number;
|
|
12
|
+
}
|
|
13
|
+
export default class NavigationPopup extends React.Component<NavigationPopupProps, NavigationPopupState> {
|
|
14
|
+
ref: RefObject<HTMLDivElement>;
|
|
15
|
+
state: {
|
|
16
|
+
calculatedLeft: number | undefined;
|
|
17
|
+
};
|
|
18
|
+
private calculateLeft;
|
|
19
|
+
componentDidMount(): void;
|
|
20
|
+
componentDidUpdate(prevProps: NavigationPopupProps): void;
|
|
21
|
+
componentWillUnmount(): void;
|
|
22
|
+
render(): JSX.Element | null;
|
|
23
|
+
private renderDefaultPopup;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import React, { Fragment, createRef } from 'react';
|
|
4
|
+
import { Portal } from '@gravity-ui/uikit';
|
|
5
|
+
import { OutsideClick } from '../../../index';
|
|
6
|
+
import NavigationItem from '../NavigationItem/NavigationItem';
|
|
7
|
+
import './NavigationPopup.css';
|
|
8
|
+
const b = block('navigation-popup');
|
|
9
|
+
export default class NavigationPopup extends React.Component {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.ref = createRef();
|
|
13
|
+
this.state = {
|
|
14
|
+
calculatedLeft: this.props.left,
|
|
15
|
+
};
|
|
16
|
+
this.calculateLeft = _.debounce(() => {
|
|
17
|
+
const { left } = this.props;
|
|
18
|
+
if (this.ref && this.ref.current && left) {
|
|
19
|
+
const right = left + this.ref.current.offsetWidth;
|
|
20
|
+
const docWidth = document.body.clientWidth;
|
|
21
|
+
const calculatedLeft = right > docWidth ? left - (right - docWidth) : left;
|
|
22
|
+
this.setState({ calculatedLeft });
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
this.setState({ calculatedLeft: left });
|
|
26
|
+
}
|
|
27
|
+
}, 100);
|
|
28
|
+
}
|
|
29
|
+
componentDidMount() {
|
|
30
|
+
this.calculateLeft();
|
|
31
|
+
window.addEventListener('resize', this.calculateLeft);
|
|
32
|
+
}
|
|
33
|
+
componentDidUpdate(prevProps) {
|
|
34
|
+
if (prevProps.left !== this.props.left) {
|
|
35
|
+
this.calculateLeft();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
componentWillUnmount() {
|
|
39
|
+
window.removeEventListener('resize', this.calculateLeft);
|
|
40
|
+
}
|
|
41
|
+
render() {
|
|
42
|
+
if (!document || !document.body) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const { onClose } = this.props;
|
|
46
|
+
const { calculatedLeft } = this.state;
|
|
47
|
+
return (React.createElement(Portal, null,
|
|
48
|
+
React.createElement("div", { ref: this.ref, className: b(), style: { left: calculatedLeft } },
|
|
49
|
+
React.createElement(OutsideClick, { onOutsideClick: onClose }, this.renderDefaultPopup()))));
|
|
50
|
+
}
|
|
51
|
+
renderDefaultPopup() {
|
|
52
|
+
return (React.createElement(Fragment, null, this.props.items.map((item) => (React.createElement(NavigationItem, { key: item.text, className: b('link'), data: item })))));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* use this for style redefinitions to awoid problems with
|
|
2
|
+
unpredictable css rules order in build */
|
|
3
|
+
.social-icon {
|
|
4
|
+
display: flex;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
align-items: center;
|
|
7
|
+
width: 40px;
|
|
8
|
+
height: 40px;
|
|
9
|
+
margin-right: 8px;
|
|
10
|
+
color: var(--yc-color-base-background);
|
|
11
|
+
border-radius: 50%;
|
|
12
|
+
background-color: var(--yc-color-base-generic);
|
|
13
|
+
}
|
|
14
|
+
.social-icon:hover {
|
|
15
|
+
background-color: var(--yc-color-base-generic-hover);
|
|
16
|
+
}
|
|
17
|
+
.social-icon__icon {
|
|
18
|
+
width: 16px;
|
|
19
|
+
height: 16px;
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NavigationSocialItem } from '../../../../models/navigation';
|
|
3
|
+
import './SocialIcon.css';
|
|
4
|
+
export interface NavigationSocialItemProps extends NavigationSocialItem {
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const SocialIcon: React.FC<NavigationSocialItemProps>;
|
|
8
|
+
export default SocialIcon;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import { Image } from '../../../index';
|
|
4
|
+
import './SocialIcon.css';
|
|
5
|
+
const b = block('social-icon');
|
|
6
|
+
const SocialIcon = ({ icon, url, className }) => (React.createElement("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: b(null, className) },
|
|
7
|
+
React.createElement(Image, { className: b('icon'), src: icon })));
|
|
8
|
+
export default SocialIcon;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ShouldRenderBlock, CustomConfig, PageContent, CustomItems } from '../../models';
|
|
3
3
|
import { blockMap, subBlockMap } from '../../constructor-items';
|
|
4
|
+
import { NavigationData } from '../../models/navigation';
|
|
4
5
|
import '@doc-tools/transform/dist/js/yfm';
|
|
5
6
|
import './PageConstructor.css';
|
|
6
7
|
export declare type ItemMap = typeof blockMap & typeof subBlockMap & CustomItems;
|
|
@@ -9,6 +10,7 @@ export interface PageConstructorProps {
|
|
|
9
10
|
shouldRenderBlock?: ShouldRenderBlock;
|
|
10
11
|
custom?: CustomConfig;
|
|
11
12
|
renderMenu?: () => React.ReactNode;
|
|
13
|
+
navigationData?: NavigationData;
|
|
12
14
|
}
|
|
13
15
|
export declare const Constructor: (props: PageConstructorProps) => JSX.Element;
|
|
14
16
|
export declare const PageConstructor: (props: PageConstructorProps) => JSX.Element;
|