@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.
266 lines • 10.5 kB
JavaScript
import _times from "lodash/times";
import _noop from "lodash/noop";
import BaseComponent from '../_base/baseComponent';
import React from 'react';
import PropTypes from 'prop-types';
import cls from 'classnames';
import isNullOrUndefined from '@douyinfe/semi-foundation/lib/es/utils/isNullOrUndefined';
import { cloneDeep, isSemiIcon } from '../_utils';
import ItemFoundation from '@douyinfe/semi-foundation/lib/es/navigation/itemFoundation';
import { cssClasses, strings } from '@douyinfe/semi-foundation/lib/es/navigation/constants';
import Tooltip from '../tooltip';
import NavContext from './nav-context';
import Dropdown from '../dropdown';
const clsPrefix = `${cssClasses.PREFIX}-item`;
export default class NavItem extends BaseComponent {
constructor(props) {
super(props);
this.setItemRef = ref => {
// console.log('Item - setItemRef()', ref);
this.props.forwardRef && this.props.forwardRef(ref);
};
this.wrapTooltip = node => {
const {
text,
tooltipHideDelay,
tooltipShowDelay
} = this.props;
const hideDelay = tooltipHideDelay !== null && tooltipHideDelay !== void 0 ? tooltipHideDelay : this.context.tooltipHideDelay;
const showDelay = tooltipShowDelay !== null && tooltipShowDelay !== void 0 ? tooltipShowDelay : this.context.tooltipShowDelay;
return /*#__PURE__*/React.createElement(Tooltip, {
content: text,
wrapWhenSpecial: false,
position: "right",
trigger: 'hover',
mouseEnterDelay: showDelay,
mouseLeaveDelay: hideDelay
}, node);
};
this.handleClick = e => this.foundation.handleClick(e);
this.handleKeyPress = e => this.foundation.handleKeyPress(e);
this.state = {
tooltipShow: false
};
this.foundation = new ItemFoundation(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), {
cloneDeep,
updateTooltipShow: tooltipShow => this.setState({
tooltipShow
}),
updateSelected: _selected => this._invokeContextFunc('updateSelectedKeys', [this.props.itemKey]),
updateGlobalSelectedKeys: keys => this._invokeContextFunc('updateSelectedKeys', [...keys]),
getSelectedKeys: () => this.context && this.context.selectedKeys,
getSelectedKeysIsControlled: () => this.context && this.context.selectedKeysIsControlled,
notifyGlobalOnSelect: function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _this._invokeContextFunc('onSelect', ...args);
},
notifyGlobalOnClick: function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return _this._invokeContextFunc('onClick', ...args);
},
notifyClick: function () {
return _this.props.onClick(...arguments);
},
notifyMouseEnter: function () {
return _this.props.onMouseEnter(...arguments);
},
notifyMouseLeave: function () {
return _this.props.onMouseLeave(...arguments);
},
getIsCollapsed: () => this.props.isCollapsed || Boolean(this.context && this.context.isCollapsed) || false,
getSelected: () => Boolean(this.context && this.context.selectedKeys && this.context.selectedKeys.includes(this.props.itemKey)),
getIsOpen: () => Boolean(this.context && this.context.openKeys && this.context.openKeys.includes(this.props.itemKey))
});
}
renderIcon(icon, pos) {
let isToggleIcon = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
let key = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
if (this.props.isSubNav) {
return null;
}
if (!icon && this.context.mode === strings.MODE_HORIZONTAL) {
return null;
}
let iconSize = 'large';
if (pos === strings.ICON_POS_RIGHT) {
iconSize = 'default';
}
const className = cls(`${clsPrefix}-icon`, {
[`${clsPrefix}-icon-toggle-${this.context.toggleIconPosition}`]: isToggleIcon,
[`${clsPrefix}-icon-info`]: !isToggleIcon
});
return /*#__PURE__*/React.createElement("i", {
className: className,
key: key
}, isSemiIcon(icon) ? /*#__PURE__*/React.cloneElement(icon, {
size: icon.props.size || iconSize
}) : icon);
}
render() {
var _a;
const {
text,
icon,
toggleIcon,
className,
isSubNav,
style,
indent,
onMouseEnter,
onMouseLeave,
link,
linkOptions,
disabled,
level = 0,
tabIndex
} = this.props;
const {
mode,
isInSubNav,
prefixCls,
limitIndent
} = this.context;
const isCollapsed = this.adapter.getIsCollapsed();
const selected = this.adapter.getSelected();
let itemChildren = null;
// 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
const children = (_a = this.props) === null || _a === void 0 ? void 0 : _a.children;
if (!isNullOrUndefined(children)) {
itemChildren = children;
} else {
let placeholderIcons = null;
if (mode === strings.MODE_VERTICAL && !limitIndent && !isCollapsed) {
const iconAmount = icon && !indent ? level : level - 1;
placeholderIcons = _times(iconAmount, index => this.renderIcon(null, strings.ICON_POS_RIGHT, false, index));
}
itemChildren = /*#__PURE__*/React.createElement(React.Fragment, null, placeholderIcons, this.context.toggleIconPosition === strings.TOGGLE_ICON_LEFT && this.renderIcon(toggleIcon, strings.ICON_POS_RIGHT, true, 'key-toggle-pos-right'), icon || indent || isInSubNav ? this.renderIcon(icon, strings.ICON_POS_LEFT, false, 'key-position-left') : null, !isNullOrUndefined(text) ? /*#__PURE__*/React.createElement("span", {
className: `${cssClasses.PREFIX}-item-text`
}, text) : '', this.context.toggleIconPosition === strings.TOGGLE_ICON_RIGHT && this.renderIcon(toggleIcon, strings.ICON_POS_RIGHT, true, 'key-toggle-pos-right'));
}
if (typeof link === 'string') {
itemChildren = /*#__PURE__*/React.createElement("a", Object.assign({
className: `${prefixCls}-item-link`,
href: link,
tabIndex: -1
}, linkOptions), itemChildren);
}
let itemDom = '';
if (isInSubNav && (isCollapsed || mode === strings.MODE_HORIZONTAL)) {
const popoverItemCls = cls({
[clsPrefix]: true,
[`${clsPrefix}-sub`]: isSubNav,
[`${clsPrefix}-selected`]: selected,
[`${clsPrefix}-collapsed`]: isCollapsed,
[`${clsPrefix}-disabled`]: disabled
});
itemDom = /*#__PURE__*/React.createElement(Dropdown.Item, {
selected: selected,
active: selected,
forwardRef: this.setItemRef,
className: popoverItemCls,
onClick: this.handleClick,
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
disabled: disabled,
onKeyDown: this.handleKeyPress
}, itemChildren);
} else {
// Items are divided into normal and sub-wrap
const popoverItemCls = cls(`${className || `${clsPrefix}-normal`}`, {
[clsPrefix]: true,
[`${clsPrefix}-sub`]: isSubNav,
[`${clsPrefix}-selected`]: selected && !isSubNav,
[`${clsPrefix}-collapsed`]: isCollapsed,
[`${clsPrefix}-disabled`]: disabled,
[`${clsPrefix}-has-link`]: typeof link === 'string'
});
const ariaProps = {
'aria-disabled': disabled
};
if (isSubNav) {
const isOpen = this.adapter.getIsOpen();
ariaProps['aria-expanded'] = isOpen;
}
itemDom =
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
React.createElement("li", Object.assign({
// if role = menuitem, the narration will read all expanded li
role: isSubNav ? null : "menuitem",
tabIndex: isSubNav ? -1 : tabIndex
}, ariaProps, {
style: style,
ref: this.setItemRef,
className: popoverItemCls,
onClick: this.handleClick,
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onKeyPress: this.handleKeyPress
}, this.getDataAttr(this.props)), itemChildren);
}
// Display Tooltip when disabled and SubNav
if (isCollapsed && !isInSubNav && !isSubNav || isCollapsed && isSubNav && disabled) {
itemDom = this.wrapTooltip(itemDom);
}
if (typeof this.context.renderWrapper === 'function') {
return this.context.renderWrapper({
itemElement: itemDom,
isSubNav: isSubNav,
isInSubNav: isInSubNav,
props: this.props
});
}
return itemDom;
}
}
NavItem.contextType = NavContext;
NavItem.propTypes = {
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
itemKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onClick: PropTypes.func,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
icon: PropTypes.oneOfType([PropTypes.node]),
className: PropTypes.string,
toggleIcon: PropTypes.string,
style: PropTypes.object,
forwardRef: PropTypes.func,
indent: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
isCollapsed: PropTypes.bool,
isSubNav: PropTypes.bool,
link: PropTypes.string,
linkOptions: PropTypes.object,
disabled: PropTypes.bool,
tabIndex: PropTypes.number
};
NavItem.defaultProps = {
isSubNav: false,
indent: false,
forwardRef: _noop,
isCollapsed: false,
onClick: _noop,
onMouseEnter: _noop,
onMouseLeave: _noop,
disabled: false,
tabIndex: 0
};