@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
389 lines (388 loc) • 14.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _get2 = _interopRequireDefault(require("lodash/get"));
var _times2 = _interopRequireDefault(require("lodash/times"));
var _baseComponent = _interopRequireDefault(require("../_base/baseComponent"));
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _classnames = _interopRequireDefault(require("classnames"));
require("@douyinfe/semi-foundation/lib/cjs/navigation/navigation.css");
var _isNullOrUndefined = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/utils/isNullOrUndefined"));
var _subNavFoundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/navigation/subNavFoundation"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/navigation/constants");
var _semiIcons = require("@douyinfe/semi-icons");
var _Item = _interopRequireDefault(require("./Item"));
var _dropdown = _interopRequireDefault(require("../dropdown"));
var _navContext = _interopRequireDefault(require("./nav-context"));
var _collapsible = _interopRequireDefault(require("../collapsible"));
var _cssAnimation = _interopRequireDefault(require("../_cssAnimation"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
class SubNav extends _baseComponent.default {
constructor(props) {
super(props);
this.setItemRef = ref => {
if (ref && ref.current) {
this.itemRef = ref;
} else {
this.itemRef = {
current: ref
};
}
};
this.setTitleRef = ref => {
if (ref && ref.current) {
this.titleRef = ref;
} else {
this.titleRef = {
current: ref
};
}
};
this.handleClick = e => {
this.foundation.handleClick(e && e.nativeEvent, this.titleRef && this.titleRef.current);
};
this.handleKeyPress = e => {
this.foundation.handleKeyPress(e && e.nativeEvent, this.titleRef && this.titleRef.current);
};
this.handleDropdownVisible = visible => this.foundation.handleDropdownVisibleChange(visible);
this.state = {
isHovered: false
};
this.adapter.setCache('firstMounted', true);
this.titleRef = /*#__PURE__*/_react.default.createRef();
this.itemRef = /*#__PURE__*/_react.default.createRef();
this.foundation = new _subNavFoundation.default(this.adapter);
}
_invokeContextFunc(funcName) {
if (funcName && this.context && typeof this.context[funcName] === 'function') {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return this.context[funcName](...args);
}
return null;
}
get adapter() {
var _this = this;
return Object.assign(Object.assign({}, super.adapter), {
updateIsHovered: isHovered => this.setState({
isHovered
}),
getOpenKeys: () => this.context && this.context.openKeys,
getOpenKeysIsControlled: () => this.context && this.context.openKeysIsControlled,
getCanUpdateOpenKeys: () => this.context && this.context.canUpdateOpenKeys,
updateOpen: isOpen => this._invokeContextFunc(isOpen ? 'addOpenKeys' : 'removeOpenKeys', this.props.itemKey),
notifyGlobalOpenChange: function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _this._invokeContextFunc('onOpenChange', ...args);
},
notifyGlobalOnSelect: function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return _this._invokeContextFunc('onSelect', ...args);
},
notifyGlobalOnClick: function () {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return _this._invokeContextFunc('onClick', ...args);
},
getIsSelected: itemKey => Boolean(!(0, _isNullOrUndefined.default)(itemKey) && (0, _get2.default)(this.context, 'selectedKeys', []).includes(String(itemKey))),
getIsOpen: () => {
const {
itemKey
} = this.props;
return Boolean(this.context && this.context.openKeys && this.context.openKeys.includes(this.props.itemKey));
}
});
}
renderIcon(icon, pos, withTransition) {
let isToggleIcon = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
let key = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
const {
prefixCls
} = this.context;
let iconSize = 'large';
if (pos === _constants.strings.ICON_POS_RIGHT) {
iconSize = 'default';
}
const className = (0, _classnames.default)(`${prefixCls}-item-icon`, {
[`${prefixCls}-item-icon-toggle-${this.context.toggleIconPosition}`]: isToggleIcon,
[`${prefixCls}-item-icon-info`]: !isToggleIcon
});
const isOpen = this.adapter.getIsOpen();
const iconElem = /*#__PURE__*/_react.default.isValidElement(icon) ? withTransition ? (/*#__PURE__*/_react.default.createElement(_cssAnimation.default, {
animationState: isOpen ? "enter" : "leave",
startClassName: `${_constants.cssClasses.PREFIX}-icon-rotate-${isOpen ? "180" : "0"}`
}, _ref => {
let {
animationClassName
} = _ref;
// @ts-ignore
return /*#__PURE__*/_react.default.cloneElement(icon, {
size: iconSize,
className: animationClassName
});
})
// @ts-ignore
) : /*#__PURE__*/_react.default.cloneElement(icon, {
size: iconSize
}) : null;
return /*#__PURE__*/_react.default.createElement("i", {
key: key,
className: className
}, iconElem);
}
renderTitleDiv() {
const {
text,
icon,
itemKey,
indent,
disabled,
level,
expandIcon
} = this.props;
const {
mode,
isInSubNav,
isCollapsed,
prefixCls,
subNavMotion,
limitIndent
} = this.context;
const isOpen = this.adapter.getIsOpen();
const titleCls = (0, _classnames.default)(`${prefixCls}-sub-title`, {
[`${prefixCls}-sub-title-selected`]: this.adapter.getIsSelected(itemKey),
[`${prefixCls}-sub-title-disabled`]: disabled
});
let withTransition = false;
let toggleIconType = '';
if (isCollapsed) {
if (isInSubNav) {
toggleIconType = /*#__PURE__*/_react.default.createElement(_semiIcons.IconChevronRight, null);
} else {
toggleIconType = null;
}
} else if (mode === _constants.strings.MODE_HORIZONTAL) {
if (isInSubNav) {
toggleIconType = /*#__PURE__*/_react.default.createElement(_semiIcons.IconChevronRight, {
"aria-hidden": true
});
} else {
toggleIconType = expandIcon ? expandIcon : /*#__PURE__*/_react.default.createElement(_semiIcons.IconChevronDown, {
"aria-hidden": true
});
// Horizontal mode does not require animation fix#1198
// withTransition = true;
}
} else {
if (subNavMotion) {
withTransition = true;
}
toggleIconType = expandIcon ? expandIcon : /*#__PURE__*/_react.default.createElement(_semiIcons.IconChevronDown, {
"aria-hidden": true
});
}
let placeholderIcons = null;
if (mode === _constants.strings.MODE_VERTICAL && !limitIndent && !isCollapsed) {
/* Different icons' amount means different indents.*/
const iconAmount = icon && !indent ? level : level - 1;
placeholderIcons = (0, _times2.default)(iconAmount, index => this.renderIcon(null, _constants.strings.ICON_POS_RIGHT, false, false, index));
}
const isIconChevronRightShow = !isCollapsed && isInSubNav && mode === _constants.strings.MODE_HORIZONTAL || isCollapsed && isInSubNav;
const titleDiv = /*#__PURE__*/_react.default.createElement("div", {
role: "menuitem",
// to avoid nested horizontal navigation be focused
tabIndex: isIconChevronRightShow ? -1 : 0,
ref: this.setTitleRef,
className: titleCls,
onClick: this.handleClick,
onKeyPress: this.handleKeyPress,
"aria-expanded": isOpen ? 'true' : 'false'
}, /*#__PURE__*/_react.default.createElement("div", {
className: `${prefixCls}-item-inner`
}, placeholderIcons, this.context.toggleIconPosition === _constants.strings.TOGGLE_ICON_LEFT && this.renderIcon(toggleIconType, _constants.strings.ICON_POS_RIGHT, withTransition, true, 'key-toggle-position-left'), icon || indent || isInSubNav && mode !== _constants.strings.MODE_HORIZONTAL ? this.renderIcon(icon, _constants.strings.ICON_POS_LEFT, false, false, 'key-inSubNav-position-left') : null, /*#__PURE__*/_react.default.createElement("span", {
className: `${prefixCls}-item-text`
}, text), this.context.toggleIconPosition === _constants.strings.TOGGLE_ICON_RIGHT && this.renderIcon(toggleIconType, _constants.strings.ICON_POS_RIGHT, withTransition, true, 'key-toggle-position-right')));
return titleDiv;
}
renderSubUl() {
const {
children,
maxHeight
} = this.props;
const {
isCollapsed,
mode,
subNavMotion,
prefixCls
} = this.context;
const isOpen = this.adapter.getIsOpen();
const isHorizontal = mode === _constants.strings.MODE_HORIZONTAL;
const subNavCls = (0, _classnames.default)(`${prefixCls}-sub`, {
[`${prefixCls}-sub-open`]: isOpen,
[`${prefixCls}-sub-popover`]: isCollapsed || isHorizontal
});
const ulWithMotion = /*#__PURE__*/_react.default.createElement(_collapsible.default, {
motion: subNavMotion,
isOpen: isOpen,
keepDOM: false,
fade: true
}, !isCollapsed ? /*#__PURE__*/_react.default.createElement("ul", {
className: subNavCls
}, children) : null);
const finalDom = isHorizontal ? null : subNavMotion ? ulWithMotion : isOpen && !isCollapsed ? (/*#__PURE__*/_react.default.createElement("ul", {
className: subNavCls
}, children)) : null;
return finalDom;
}
wrapDropdown() {
let elem = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
let _elem = elem;
const {
children,
dropdownStyle,
disabled,
subDropdownProps,
dropdownProps: userDropdownProps
} = this.props;
const {
mode,
isInSubNav,
isCollapsed,
subNavCloseDelay,
subNavOpenDelay,
prefixCls,
getPopupContainer
} = this.context;
const isOpen = this.adapter.getIsOpen();
const openKeysIsControlled = this.adapter.getOpenKeysIsControlled();
const subNavCls = (0, _classnames.default)({
[`${prefixCls}-popover`]: isCollapsed
});
const dropdownProps = {
trigger: 'hover',
style: dropdownStyle
};
if (openKeysIsControlled) {
dropdownProps.trigger = 'custom';
dropdownProps.visible = isOpen;
}
if (getPopupContainer) {
dropdownProps.getPopupContainer = getPopupContainer;
}
if (isCollapsed || mode === _constants.strings.MODE_HORIZONTAL) {
// Do not show dropdown when disabled
_elem = !disabled ? (/*#__PURE__*/_react.default.createElement(_dropdown.default, Object.assign({
className: subNavCls,
render: (/*#__PURE__*/_react.default.createElement(_dropdown.default.Menu, null, children)),
position: mode === _constants.strings.MODE_HORIZONTAL && !isInSubNav ? 'bottomLeft' : 'rightTop',
mouseEnterDelay: subNavOpenDelay,
mouseLeaveDelay: subNavCloseDelay,
onVisibleChange: this.handleDropdownVisible
}, userDropdownProps ? userDropdownProps : subDropdownProps, dropdownProps), _elem)) : _elem;
}
return _elem;
}
render() {
const {
itemKey,
style,
onMouseEnter,
onMouseLeave,
disabled,
text
} = this.props;
const {
mode,
isCollapsed,
prefixCls
} = this.context;
let titleDiv = this.renderTitleDiv();
const subUl = this.renderSubUl();
// When mode=horizontal, it is displayed in Dropdown
if (isCollapsed || mode === _constants.strings.MODE_HORIZONTAL) {
titleDiv = this.wrapDropdown(titleDiv);
}
return (
/*#__PURE__*/
// Children is not a recommended usage and may cause some bug-like performance, but some users have already used it, so here we only delete the ts definition instead of deleting the actual code
// children 并不是我们推荐的用法,可能会导致一些像 bug的表现,但是有些用户已经用了,所以此处仅作删除 ts 定义而非删除实际代码的操作
// refer https://github.com/DouyinFE/semi-design/issues/2710
// @ts-ignore
_react.default.createElement(_Item.default, {
style: style,
isSubNav: true,
itemKey: itemKey,
forwardRef: this.setItemRef,
isCollapsed: isCollapsed,
className: `${prefixCls}-sub-wrap`,
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
disabled: disabled,
text: text
}, /*#__PURE__*/_react.default.createElement(_navContext.default.Provider, {
value: Object.assign(Object.assign({}, this.context), {
isInSubNav: true
})
}, titleDiv, subUl))
);
}
}
exports.default = SubNav;
SubNav.contextType = _navContext.default;
SubNav.propTypes = {
/**
* Unique identification
*/
itemKey: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
/**
* Copywriting
*/
text: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node]),
/**
* Whether child navigation is expanded
*/
isOpen: _propTypes.default.bool,
/**
* Whether it is in the state of being stowed to the sidebar
*/
isCollapsed: _propTypes.default.bool,
/**
* Whether to keep the left Icon placeholder
*/
indent: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.number]),
/**
* Nested child elements
*/
children: _propTypes.default.node,
style: _propTypes.default.object,
/**
* Icon name on the left
*/
icon: _propTypes.default.node,
/**
* Maximum height (for animation)
*/
maxHeight: _propTypes.default.number,
onMouseEnter: _propTypes.default.func,
onMouseLeave: _propTypes.default.func,
// Is it disabled
disabled: _propTypes.default.bool,
level: _propTypes.default.number
};
SubNav.defaultProps = {
level: 0,
indent: false,
isCollapsed: false,
isOpen: false,
maxHeight: _constants.numbers.DEFAULT_SUBNAV_MAX_HEIGHT,
disabled: false
};