wix-style-react
Version:
wix-style-react
366 lines • 18.1 kB
JavaScript
import React, { Component, cloneElement } from 'react';
import PropTypes from 'prop-types';
import { st, classes } from './Sidebar.st.css';
import { SidebarItem } from './SidebarItem';
import { SidebarPersistentHeader } from './SidebarPersistentHeader';
import { SidebarPersistentFooter } from './SidebarPersistentFooter';
import { SidebarBackButton } from './SidebarBackButton';
import { SidebarContext } from './SidebarAPI';
import { dataHooks, sidebarSkins } from './constants';
import { SidebarContentWrapper } from './SidebarContentWrapper';
import { WixStyleReactContext } from '../WixStyleReactProvider/context';
const CollapsibeInnerMenuOpenChildren = props => {
const { children, waitForOtherMenuToClose, skin, isAlreadyOpen } = props;
return (React.createElement("div", { "data-hook": "open-inner-menu-children", className: isAlreadyOpen
? ''
: st(waitForOtherMenuToClose
? classes.innerMenuWrapperInToPlaceAfterClosingOther
: classes.innerMenuWrapperInToPlace) },
React.createElement(SidebarContentWrapper, { containerClasses: st(classes.innerMenu), containerDataHook: dataHooks.drivenInChildren, skin: skin }, children)));
};
const CollapsibeInnerMenuCloseChildren = props => {
const { children } = props;
return (React.createElement("div", { "data-hook": "closed-inner-menu-children", className: st(classes.innerMenuWrapperOutOfPlace) },
React.createElement("div", { className: st(classes.innerMenu), "data-hook": dataHooks.drivenOutChildren }, children)));
};
const CollapsibeInnerMenuCloseParent = props => {
const { isAlreadyClosed, children } = props;
const closeParentClass = st(isAlreadyClosed ? classes.closedInnerMenu : classes.closingInnerMenu);
return (React.createElement("div", { "data-hook": "closed-inner-menu", className: closeParentClass }, children));
};
const CollapsibeInnerMenuOpenParent = props => {
const { isAlreadyOpen, children, waitForOtherMenuToClose } = props;
const openParentClass = waitForOtherMenuToClose
? st(classes.openingInnerMenuAfterClosingOther)
: st(isAlreadyOpen ? classes.openInnerMenu : classes.openingInnerMenu);
return (React.createElement("div", { "data-hook": "open-inner-menu", className: openParentClass }, children));
};
/** A sidebar navigation component */
class Sidebar extends Component {
constructor(props) {
super(props);
this._getInnerMenuCollapsibleState = options => {
const { menuToClose, menuToOpen, selected } = options;
let expandedInnerMenu = undefined;
const openMenuChildren = this.itemKeyToChildren[menuToOpen] &&
(this.itemKeyToChildren[menuToOpen].children || []);
const closeMenuChildren = this.itemKeyToChildren[menuToClose] &&
(this.itemKeyToChildren[menuToClose].children || []);
const collapsibleOnScreenChildren = this.firstLevelItems.reduce((accumalator, child) => {
const shouldExpand = menuToOpen &&
child.props.itemKey === menuToOpen &&
openMenuChildren.length > 0;
const shouldCollapse = this.state.expandedInnerMenu &&
menuToClose &&
closeMenuChildren.length > 0 &&
child.props.itemKey === menuToClose &&
this.state.expandedInnerMenu === menuToClose;
const waitForOtherMenuToClose = this.state.expandedInnerMenu &&
menuToClose &&
this.state.expandedInnerMenu === menuToClose &&
menuToClose !== menuToOpen;
if (shouldExpand) {
expandedInnerMenu = menuToOpen;
return [
...accumalator,
React.createElement(CollapsibeInnerMenuOpenParent, { key: `open-parent-${menuToOpen}`, isAlreadyOpen: this._isAlreadyOpen(menuToOpen), waitForOtherMenuToClose: waitForOtherMenuToClose }, child),
React.createElement(CollapsibeInnerMenuOpenChildren, { key: `open-children-${menuToOpen}`, waitForOtherMenuToClose: waitForOtherMenuToClose, skin: this.props.skin, isAlreadyOpen: this._isAlreadyOpen(menuToOpen) }, openMenuChildren),
];
}
if (shouldCollapse) {
return [
...accumalator,
React.createElement(CollapsibeInnerMenuCloseParent, { key: `closed-parent-${menuToClose}`, isAlreadyClosed: this._isAlreadyClosed(selected, menuToClose) }, child),
React.createElement(CollapsibeInnerMenuCloseChildren, { key: `closed-children-${menuToClose}` }, closeMenuChildren),
];
}
return [...accumalator, child];
}, []);
return {
collapsibleOnScreenChildren,
expandedInnerMenu,
};
};
this._shouldCollapseInnerMenu = itemKey => this.state.expandedInnerMenu === itemKey &&
((itemKey === this.state.selectedKey && this.state.expandedInnerMenu) ||
itemKey !== this.state.selectedKey);
this._shouldExpandInnerMenu = (parentKey, itemKey) => (parentKey === this.state.lastSelectedKey &&
!this.state.expandedInnerMenu) ||
this.itemKeyToParentKey[itemKey] === parentKey ||
parentKey !== this.state.lastSelectedKey;
this._navigateTo = itemKey => {
if (this._isChild(itemKey)) {
this._selectItem(itemKey);
this.sidebarContext = this._getSidebarContext();
return;
}
if (this._isParent(itemKey)) {
this._openInnerMenu(itemKey);
this.sidebarContext = this._getSidebarContext();
return;
}
if (itemKey) {
this._closeInnerMenu(itemKey);
this.sidebarContext = this._getSidebarContext();
return;
}
};
this._getSidebarContext = () => {
return {
itemClicked: this._navigateTo,
backClicked: () => {
this._closeInnerMenu();
this.sidebarContext = this._getSidebarContext();
},
getSelectedKey: () => this.state.selectedKey,
getSkin: () => this.props.skin,
getIsMenuExpanded: () => this.state.expandedInnerMenu,
};
};
this.sidebarContext = this._getSidebarContext();
this._getInnerChildSelectedState = itemKey => {
const collapsibleInnerMenuState = this._getInnerMenuCollapsibleState({
menuToClose: this.state.lastSelectedKey,
menuToOpen: this.itemKeyToParentKey[itemKey],
selected: itemKey,
});
if (this.itemKeyToParentKey[itemKey] !== this.state.lastSelectedKey) {
return {
drivenInChildren: this.itemKeyToChildren[this.itemKeyToParentKey[itemKey]].children,
selectedKey: itemKey,
lastSelectedKey: this.itemKeyToParentKey[itemKey],
...collapsibleInnerMenuState,
};
}
else {
return {
...collapsibleInnerMenuState,
selectedKey: itemKey,
};
}
};
this._getInnerMenuOpenState = itemKey => {
const { children, selectedKey } = this.itemKeyToChildren[itemKey];
const selected = this.itemKeyToParentKey[this.state.lastSelectedKey] === itemKey
? this.state.lastSelectedKey
: selectedKey;
const parentKey = this.itemKeyToParentKey[selected];
const parentItemKeyToOpen = this._getItemToOpenKey(parentKey, itemKey);
const parentItemKeyToClose = this._getItemToCloseKey(this.state.lastSelectedKey);
return {
...this._getInnerMenuCollapsibleState({
menuToClose: parentItemKeyToClose,
menuToOpen: parentItemKeyToOpen,
selected,
}),
drivenInChildren: children,
drivenOutChildren: [],
selectedKey: selected,
lastSelectedKey: itemKey,
};
};
this._getItemToCloseKey = itemKey => {
if (this._shouldCollapseInnerMenu(itemKey)) {
return this.itemKeyToParentKey[itemKey] || itemKey;
}
if (this._shouldCollapseInnerMenu(this.state.lastSelectedKey)) {
return (this.itemKeyToParentKey[this.state.lastSelectedKey] ||
this.state.lastSelectedKey);
}
return undefined;
};
this._getItemToOpenKey = (parentKey, itemKey) => this._shouldExpandInnerMenu(parentKey, itemKey) ? parentKey : undefined;
this._getInnerMenuCloseState = (itemKey, updateCollapsibleOnlyOnChange) => {
const selectedKey = this.state.lastSelectedKey || itemKey;
const parentItemKeyToClose = this._getItemToCloseKey(selectedKey);
return {
...this._getInnerMenuCollapsibleState({
menuToClose: parentItemKeyToClose,
selected: selectedKey,
}),
selectedKey: itemKey || this.state.lastSelectedKey,
lastSelectedKey: selectedKey,
drivenInChildren: [],
drivenOutChildren: this.state.drivenInChildren,
};
};
this._getItemWithKey = (item, itemKey) => item.props.itemKey ? item : cloneElement(item, { ...item.props, itemKey });
this._getChildrenWithKeys = child => (child.props.innerMenu &&
child.props.innerMenu.map((innerChild, index) => this._getItemWithKey(innerChild, child.props.itemKey + index))) ||
[];
this._isParent = itemKey => this.itemKeyToChildren[itemKey];
this._isChild = itemKey => this.itemKeyToParentKey[itemKey];
this._isAlreadyOpen = menuToOpen => this.state.lastSelectedKey === menuToOpen &&
this.itemKeyToParentKey[this.props.selectedKey] === menuToOpen &&
this.state.expandedInnerMenu === menuToOpen;
this._isAlreadyClosed = (selected, menuToClose) => this.itemKeyToParentKey[selected] !== menuToClose;
this._selectItem = itemKey => this.setState(this._getInnerChildSelectedState(itemKey));
this._openInnerMenu = itemKey => this.setState(this._getInnerMenuOpenState(itemKey));
this._closeInnerMenu = itemKey => this.setState(this._getInnerMenuCloseState(itemKey));
this.itemKeyToChildren = {};
this.itemKeyToParentKey = {};
this.firstLevelItems = [];
this.state = {
persistentTopChildren: [],
drivenOutChildren: [],
onScreenChildren: [],
collapsibleOnScreenChildren: [],
drivenInChildren: [],
persistentBottomChildren: [],
selectedKey: '',
lastSelectedKey: '',
expandedInnerMenu: undefined,
};
}
UNSAFE_componentWillMount() {
this._setInnerMenus(this.props);
}
UNSAFE_componentWillReceiveProps(props) {
this._setInnerMenus(props);
}
_setInnerMenus(props) {
const persistentTopChildren = [];
const persistentBottomChildren = [];
const onScreenChildren = [];
const findEnabledChild = item => item.props.innerMenu &&
item.props.innerMenu.find(c => c.type === SidebarItem && !c.props.disable);
const handleChild = child => {
if (child.type === SidebarItem) {
const enabledChild = findEnabledChild(child);
const innerChildrenWithKeys = this._getChildrenWithKeys(child);
this.itemKeyToChildren[child.props.itemKey] = {
selectedKey: enabledChild
? enabledChild.props.itemKey
: child.props.itemKey,
children: innerChildrenWithKeys,
};
if (child.props.innerMenu) {
innerChildrenWithKeys.forEach(innerChild => {
if (innerChild.type !== SidebarBackButton) {
this.itemKeyToParentKey[innerChild.props.itemKey] =
child.props.itemKey;
}
});
}
onScreenChildren.push(child);
}
else if (child.type === SidebarPersistentHeader) {
persistentTopChildren.push(child);
}
else if (child.type === SidebarPersistentFooter) {
persistentBottomChildren.push(child);
}
else {
onScreenChildren.push(child);
}
};
if (props.children) {
if (props.children.length) {
props.children.forEach(child => {
if (child) {
if (child.length > 0) {
child.forEach(handleChild);
}
else {
handleChild(child);
}
}
});
}
else {
handleChild(props.children);
}
}
this.firstLevelItems = onScreenChildren.slice();
const newState = {
persistentTopChildren,
persistentBottomChildren,
onScreenChildren,
selectedKey: props.selectedKey,
};
const selectedItemParentKey = this.itemKeyToParentKey[props.selectedKey];
if (selectedItemParentKey) {
this.setState({
...newState,
drivenInChildren: this.itemKeyToChildren[selectedItemParentKey].children,
lastSelectedKey: selectedItemParentKey,
...this._getInnerMenuCollapsibleState({
menuToClose: this.itemKeyToParentKey[this.props.selectedKey] ||
this.props.selctedKey,
menuToOpen: selectedItemParentKey,
selected: props.selectedKey,
}),
});
}
else {
const updateCollapsibleOnlyOnChange = true;
this.setState({
...newState,
drivenInChildren: [],
...this._getInnerMenuCloseState(props.selectedKey, updateCollapsibleOnlyOnChange),
});
}
}
render() {
const { isHidden, skin } = this.props;
const css = { ...classes, ...this.props.classNames };
const sliderClasses = st(css.slider, {
skin,
}, this.state.drivenInChildren.length !== 0 && css.sliderOutToLeft, this.state.drivenInChildren.length === 0 &&
this.state.drivenOutChildren.length !== 0 &&
css.sliderInFromLeft);
const collapsibleSliderClasses = st(css.slider, {
skin,
});
const sliderOutToRightClasses = st(css.slider, {
skin,
}, !this.props.isHidden && css.sliderOutToRight);
const sliderInFromRightClasses = st(css.slider, {
skin,
}, !this.props.isHidden && css.sliderInFromRight);
const rootClasses = st(css.sideBar || classes.root, {
hidden: isHidden,
skin,
});
return (React.createElement(WixStyleReactContext.Consumer, null, ({ sidebarExperimentCollapsible }) => {
return (React.createElement(SidebarContext.Provider, { value: this.sidebarContext },
React.createElement("div", { className: rootClasses, "data-hook": this.props.dataHook, "data-skin": skin },
this.state.persistentTopChildren,
React.createElement("div", { className: st(css.content) },
!sidebarExperimentCollapsible &&
this.state.drivenInChildren.length === 0 &&
this.state.drivenOutChildren.length !== 0 && (React.createElement("div", { className: sliderOutToRightClasses, "data-hook": dataHooks.drivenOutChildren }, this.state.drivenOutChildren)),
React.createElement(SidebarContentWrapper, { containerClasses: sidebarExperimentCollapsible
? collapsibleSliderClasses
: sliderClasses, skin: skin }, sidebarExperimentCollapsible
? this.state.collapsibleOnScreenChildren
: this.state.onScreenChildren),
!sidebarExperimentCollapsible &&
this.state.drivenInChildren.length !== 0 && (React.createElement(SidebarContentWrapper, { key: "collapsiblle", containerClasses: sliderInFromRightClasses, containerDataHook: dataHooks.drivenInChildren, skin: skin }, this.state.drivenInChildren))),
this.state.persistentBottomChildren)));
}));
}
}
Sidebar.displayName = 'Sidebar';
Sidebar.Item = SidebarItem;
Sidebar.PersistentHeader = SidebarPersistentHeader;
Sidebar.PersistentFooter = SidebarPersistentFooter;
Sidebar.BackButton = SidebarBackButton;
Sidebar.propTypes = {
/** classNames overrides */
classNames: PropTypes.string,
/** The dataHook of the Sidebar */
dataHook: PropTypes.string,
/** Sidebar menu children */
children: PropTypes.node,
/** Sidebar indicator for animating out or in */
isHidden: PropTypes.bool,
/** Sets the skin of the Sidebar */
skin: PropTypes.oneOf(['dark', 'light']),
};
Sidebar.defaultProps = {
skin: sidebarSkins.dark,
isHidden: false,
};
export default Sidebar;
//# sourceMappingURL=Sidebar.js.map