UNPKG

choerodon-ui

Version:

An enterprise-class UI design language and React-based implementation

607 lines (536 loc) 21.6 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _extends from "@babel/runtime/helpers/extends"; import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import classnames from 'classnames'; import noop from 'lodash/noop'; import debounce from 'lodash/debounce'; import Button from '../../pro/es/button'; import { FuncType } from '../../pro/es/button/enum'; import { useModal } from '../../pro/es/modal-provider/ModalProvider'; import { $l } from '../../pro/es/locale-context'; import { getActiveKeyByGroupKey, getDataAttr, getHeader, getLeft, getTop, isTransformSupported, isVertical, setTransform } from './utils'; import warning from '../_util/warning'; import Ripple from '../ripple'; import TabBarInner from './TabBarInner'; import EventManager from '../_util/EventManager'; import { TabsType } from './enum'; import Icon from '../icon'; import Menu from '../menu'; import MenuItem from '../menu/MenuItem'; import Badge from '../badge'; import TabsContext from './TabsContext'; import KeyCode from '../_util/KeyCode'; import { Size } from '../_util/enum'; import CustomizationSettings from './customization-settings'; import Count from './Count'; var TabBar = function TabBar(props) { var scrollAnimated = props.scrollAnimated, className = props.className, style = props.style, inkBarStyle = props.inkBarStyle, extraContent = props.extraContent, tabBarGutter = props.tabBarGutter, inkBarAnimated = props.inkBarAnimated, type = props.type, onRemoveTab = props.onRemoveTab, restProps = _objectWithoutProperties(props, ["scrollAnimated", "className", "style", "inkBarStyle", "extraContent", "tabBarGutter", "inkBarAnimated", "type", "onRemoveTab"]); var _useContext = useContext(TabsContext), keyboard = _useContext.keyboard, customizable = _useContext.customizable, prefixCls = _useContext.prefixCls, activeKey = _useContext.activeKey, activeGroupKey = _useContext.activeGroupKey, tabBarPosition = _useContext.tabBarPosition, _useContext$hideOnlyG = _useContext.hideOnlyGroup, hideOnlyGroup = _useContext$hideOnlyG === void 0 ? false : _useContext$hideOnlyG, groupedPanelsMap = _useContext.groupedPanelsMap, currentPanelMap = _useContext.currentPanelMap, onTabClick = _useContext.onTabClick, _useContext$onPrevCli = _useContext.onPrevClick, onPrevClick = _useContext$onPrevCli === void 0 ? noop : _useContext$onPrevCli, _useContext$onNextCli = _useContext.onNextClick, onNextClick = _useContext$onNextCli === void 0 ? noop : _useContext$onNextCli, changeActiveKey = _useContext.changeActiveKey; var modal = useModal(); var openCustomizationModal = useCallback(function () { if (customizable) { var modalProps = { drawer: true, size: Size.small, title: $l('Tabs', 'customization_settings'), children: React.createElement(CustomizationSettings, null), bodyStyle: { overflow: 'hidden auto', padding: 0 } }; modalProps.okText = $l('Tabs', 'save'); modal.open(modalProps); } }, [customizable, modal]); var getNextActiveKey = useCallback(function (next) { var list = []; currentPanelMap.forEach(function (c, key) { if (!c.disabled) { if (next) { list.push(key); } else { list.unshift(key); } } }); var length = list.length; if (activeKey && length) { var i = list.indexOf(activeKey); var itemIndex = i === length - 1 ? 0 : i + 1; return list[itemIndex] || list[0]; } return undefined; }, [activeKey, currentPanelMap]); var handleKeyDown = useCallback(function (e) { if (keyboard === false) { return noop; } var keyCode = e.keyCode; if (keyCode === KeyCode.RIGHT || keyCode === KeyCode.DOWN) { e.preventDefault(); var nextKey = getNextActiveKey(true); if (nextKey) { changeActiveKey(nextKey); } } else if (keyCode === KeyCode.LEFT || keyCode === KeyCode.UP) { e.preventDefault(); var previousKey = getNextActiveKey(false); if (previousKey) { changeActiveKey(previousKey); } } }, [keyboard, changeActiveKey, getNextActiveKey]); var handleTabClick = useCallback(function (key) { if (onTabClick) { onTabClick(key); } changeActiveKey(key); }, [changeActiveKey, onTabClick]); var handleGroupSelect = useCallback(function (param) { var key = param.key; if (activeGroupKey !== key) { var newActiveKey = getActiveKeyByGroupKey(groupedPanelsMap, key); if (newActiveKey) { changeActiveKey(newActiveKey, true); } } }, [changeActiveKey, activeGroupKey, groupedPanelsMap]); var resizeEvent = useMemo(function () { return new EventManager(typeof window === 'undefined' ? undefined : window); }, []); var lastNextPrevShownRef = useRef(); var offsetRef = useRef(0); var containerRef = useRef(null); var navWrapRef = useRef(null); var navRef = useRef(null); var rootRef = useRef(null); var activeTabRef = useRef(null); var inkBarRef = useRef(null); var _useState = useState(false), _useState2 = _slicedToArray(_useState, 2), next = _useState2[0], setNext = _useState2[1]; var _useState3 = useState(false), _useState4 = _slicedToArray(_useState3, 2), prev = _useState4[0], setPrev = _useState4[1]; var _useState5 = useState(activeKey), _useState6 = _slicedToArray(_useState5, 2), prevActiveKey = _useState6[0], setActiveKey = _useState6[1]; var getTabs = function getTabs() { return _toConsumableArray(currentPanelMap.entries()).reduce(function (rst, _ref, index, list) { var _ref2 = _slicedToArray(_ref, 2), key = _ref2[0], child = _ref2[1]; var disabled = child.disabled, _child$closable = child.closable, closable = _child$closable === void 0 ? true : _child$closable, count = child.count, overflowCount = child.overflowCount, showCount = child.showCount; var classes = ["".concat(prefixCls, "-tab")]; var tabProps = { tabKey: key, role: 'tab', 'aria-disabled': 'false', 'aria-selected': 'false', style: { marginRight: tabBarGutter && index === list.length - 1 ? 0 : tabBarGutter } }; if (disabled) { classes.push("".concat(prefixCls, "-tab-disabled")); tabProps['aria-disabled'] = 'true'; } else { tabProps.onTabClick = handleTabClick; } if (activeKey === key) { classes.push("".concat(prefixCls, "-tab-active")); tabProps.ref = activeTabRef; tabProps['aria-selected'] = 'true'; } tabProps.className = classes.join(' '); warning('tab' in child || 'title' in child, 'There must be `tab` or `title` property on children of Tabs.'); var title = React.createElement(React.Fragment, null, getHeader(child), showCount && React.createElement(Count, { prefixCls: prefixCls, count: count, overflowCount: overflowCount })); rst.push(React.createElement(Ripple, { disabled: disabled, key: key }, React.createElement(TabBarInner, _extends({}, tabProps), type === TabsType['editable-card'] ? React.createElement("div", { className: closable ? undefined : "".concat(prefixCls, "-tab-unclosable") }, title, closable && React.createElement(Icon, { type: "close", onClick: function onClick(e) { return onRemoveTab(key, e); } })) : title))); return rst; }, []); }; var getContent = function getContent(contents) { if (extraContent || customizable) { return [contents, React.createElement("div", { key: "extra", className: "".concat(prefixCls, "-extra-content") }, customizable && React.createElement(Button, { className: "".concat(prefixCls, "-hover-button"), funcType: FuncType.flat, icon: "predefine", size: Size.small, onClick: openCustomizationModal }), extraContent)]; } return [contents]; }; var getGroupNode = function getGroupNode() { if (groupedPanelsMap.size > Number(hideOnlyGroup)) { return React.createElement(Menu, { prefixCls: "".concat(prefixCls, "-group"), selectedKeys: activeGroupKey ? [activeGroupKey] : [], onSelect: handleGroupSelect, mode: isVertical(tabBarPosition) ? 'vertical' : 'horizontal' }, _toConsumableArray(groupedPanelsMap.entries()).map(function (_ref3) { var _ref4 = _slicedToArray(_ref3, 2), key = _ref4[0], _ref4$1$group = _ref4[1].group, tab = _ref4$1$group.tab, disabled = _ref4$1$group.disabled, dot = _ref4$1$group.dot; return React.createElement(MenuItem, { key: key, disabled: disabled }, React.createElement(Badge, { dot: dot }, tab)); })); } return undefined; }; var getInkBarNode = function getInkBarNode() { var inkBarClassName = "".concat(prefixCls, "-ink-bar"); var classes = classnames(inkBarClassName, inkBarAnimated ? "".concat(inkBarClassName, "-animated") : "".concat(inkBarClassName, "-no-animated")); return React.createElement("div", { style: inkBarStyle, className: classes, key: "inkBar", ref: inkBarRef }); }; var getOffsetWH = useCallback(function (node) { return node[isVertical(tabBarPosition) ? 'offsetHeight' : 'offsetWidth']; }, [tabBarPosition]); var getScrollWH = useCallback(function (node) { return node[isVertical(tabBarPosition) ? 'scrollHeight' : 'scrollWidth']; }, [tabBarPosition]); var getOffsetLT = useCallback(function (node) { return node.getBoundingClientRect()[isVertical(tabBarPosition) ? 'top' : 'left']; }, [tabBarPosition]); var setOffset = useCallback(function (offset, callback) { var nav = navRef.current; if (nav) { var target = Math.min(0, offset); if (offsetRef.current !== target) { offsetRef.current = target; var navOffset = {}; var navStyle = nav.style; var transformSupported = isTransformSupported(navStyle); if (isVertical(tabBarPosition)) { if (transformSupported) { navOffset.value = "translate3d(0,".concat(target, "px,0)"); } else { navOffset.name = 'top'; navOffset.value = "".concat(target, "px"); } } else if (transformSupported) { navOffset.value = "translate3d(".concat(target, "px,0,0)"); } else { navOffset.name = 'left'; navOffset.value = "".concat(target, "px"); } if (transformSupported) { setTransform(navStyle, navOffset.value); } else if (navOffset.name) { navStyle[navOffset.name] = navOffset.value; } if (callback) { callback(); } } } }, [offsetRef, navRef, tabBarPosition]); var setNextPrev = useCallback(function () { var navNode = navRef.current; var container = containerRef.current; var navWrap = navWrapRef.current; if (navNode && container && navWrap) { var navNodeWH = getScrollWH(navNode); var containerWH = getOffsetWH(container); var navWrapNodeWH = getOffsetWH(navWrap); var offset = offsetRef.current; // 当容器小于tab的时候使用最小值才可以防止回弹问题。 var navNodeWHValue = Math.min(containerWH, navWrapNodeWH); var minOffset = navNodeWHValue - navNodeWH; // -165 var $next = next; var $prev = prev; if (minOffset >= 0) { $next = false; setOffset(0); offset = 0; } else if (minOffset < offset) { $next = true; } else { $next = false; // Test with container offset which is stable // and set the offset of the nav wrap node var realOffset = navWrapNodeWH - navNodeWH; setOffset(realOffset); offset = realOffset; } if (offset < 0) { $prev = true; } else { $prev = false; } if (prev !== $prev) { setPrev($prev); } if (next !== $next) { setNext($next); } return { next: $next, prev: $prev }; } }, [next, prev, navRef, containerRef, navWrapRef, offsetRef, getScrollWH, getOffsetWH, setOffset]); var isNextPrevShown = useCallback(function (state) { if (state) { return state.next || state.prev; } return next || prev; }, [next, prev]); var toPrev = useCallback(function (e) { onPrevClick(e); var navWrapNode = navWrapRef.current; if (navWrapNode) { var navWrapNodeWH = getOffsetWH(navWrapNode); setOffset(offsetRef.current + navWrapNodeWH, setNextPrev); } }, [getOffsetWH, setOffset, navWrapRef, onPrevClick, setNextPrev]); var toNext = useCallback(function (e) { onNextClick(e); var navWrapNode = navWrapRef.current; if (navWrapNode) { var navWrapNodeWH = getOffsetWH(navWrapNode); var offset = offsetRef.current; setOffset(offset - navWrapNodeWH, setNextPrev); } }, [getOffsetWH, setOffset, navWrapRef, onNextClick, setNextPrev]); var scrollToActiveTab = useCallback(function (e) { var activeTab = activeTabRef.current; var navWrap = navWrapRef.current; if (e && e.target !== e.currentTarget || !activeTab || !navWrap) { return; } // when not scrollable or enter scrollable first time, don't emit scrolling var needToSroll = isNextPrevShown() && lastNextPrevShownRef.current; lastNextPrevShownRef.current = isNextPrevShown(); if (!needToSroll) { return; } var activeTabWH = getScrollWH(activeTab); var navWrapNodeWH = getOffsetWH(navWrap); var offset = offsetRef.current; var wrapOffset = getOffsetLT(navWrap); var activeTabOffset = getOffsetLT(activeTab); if (wrapOffset > activeTabOffset) { offset += wrapOffset - activeTabOffset; setOffset(offset, setNextPrev); } else if (wrapOffset + navWrapNodeWH < activeTabOffset + activeTabWH) { offset -= activeTabOffset + activeTabWH - (wrapOffset + navWrapNodeWH); setOffset(offset, setNextPrev); } }, [activeTabRef, navWrapRef, lastNextPrevShownRef, getScrollWH, getOffsetWH, getOffsetLT, setOffset, setNextPrev, isNextPrevShown]); var prevTransitionEnd = useCallback(function (e) { if (e.propertyName !== 'opacity') { return; } var current = containerRef.current; if (current) { scrollToActiveTab({ target: current, currentTarget: current }); } }, [scrollToActiveTab, containerRef]); var getScrollBarNode = function getScrollBarNode(content) { var _classnames, _classnames2, _classnames3; var showNextPrev = prev || next; var prevButton = React.createElement("span", { onClick: prev ? toPrev : undefined, unselectable: "on", className: classnames("".concat(prefixCls, "-tab-prev"), (_classnames = {}, _defineProperty(_classnames, "".concat(prefixCls, "-tab-btn-disabled"), !prev), _defineProperty(_classnames, "".concat(prefixCls, "-tab-arrow-show"), showNextPrev), _classnames)), onTransitionEnd: prevTransitionEnd }, React.createElement("span", { className: "".concat(prefixCls, "-tab-prev-icon") })); var nextButton = React.createElement("span", { onClick: next ? toNext : undefined, unselectable: "on", className: classnames((_classnames2 = {}, _defineProperty(_classnames2, "".concat(prefixCls, "-tab-next"), 1), _defineProperty(_classnames2, "".concat(prefixCls, "-tab-btn-disabled"), !next), _defineProperty(_classnames2, "".concat(prefixCls, "-tab-arrow-show"), showNextPrev), _classnames2)) }, React.createElement("span", { className: "".concat(prefixCls, "-tab-next-icon") })); var navClassName = "".concat(prefixCls, "-nav"); var navClasses = classnames(navClassName, scrollAnimated ? "".concat(navClassName, "-animated") : "".concat(navClassName, "-no-animated")); return React.createElement("div", { className: classnames((_classnames3 = {}, _defineProperty(_classnames3, "".concat(prefixCls, "-nav-container"), 1), _defineProperty(_classnames3, "".concat(prefixCls, "-nav-container-scrolling"), showNextPrev), _classnames3)), key: "container", ref: containerRef }, prevButton, nextButton, React.createElement("div", { className: "".concat(prefixCls, "-nav-wrap"), ref: navWrapRef }, React.createElement("div", { className: "".concat(prefixCls, "-nav-scroll") }, React.createElement("div", { className: navClasses, ref: navRef }, content)))); }; useLayoutEffect(function () { var inkBarNode = inkBarRef.current; if (inkBarNode) { var inkBarNodeStyle = inkBarNode.style; var activeTab = activeTabRef.current; var rootNode = rootRef.current; if (activeTab && rootNode) { var wrapNode = navRef.current || rootNode; var transformSupported = isTransformSupported(inkBarNodeStyle); if (!isVertical(tabBarPosition)) { var left = getLeft(activeTab, wrapNode); var width = activeTab.offsetWidth; // If tabNode'width width equal to wrapNode'width when tabBarPosition is top or bottom // It means no css working, then ink bar should not have width until css is loaded if (width === rootNode.offsetWidth) { width = 0; } else if (inkBarStyle && inkBarStyle.width !== undefined) { width = parseFloat(inkBarStyle.width); if (width) { left += (activeTab.offsetWidth - width) / 2; } } // use 3d gpu to optimize render if (transformSupported) { setTransform(inkBarNodeStyle, "translate3d(".concat(left, "px,0,0)")); inkBarNodeStyle.width = "".concat(width, "px"); inkBarNodeStyle.height = ''; } else { inkBarNodeStyle.left = "".concat(left, "px"); inkBarNodeStyle.top = ''; inkBarNodeStyle.bottom = ''; inkBarNodeStyle.right = "".concat(wrapNode.offsetWidth - left - width, "px"); } } else { var top = getTop(activeTab, wrapNode); var height = activeTab.offsetHeight; if (inkBarStyle && inkBarStyle.height !== undefined) { height = parseFloat(inkBarStyle.height); if (height) { top += (activeTab.offsetHeight - height) / 2; } } if (transformSupported) { setTransform(inkBarNodeStyle, "translate3d(0,".concat(top, "px,0)")); inkBarNodeStyle.height = "".concat(height, "px"); inkBarNodeStyle.width = ''; } else { inkBarNodeStyle.left = ''; inkBarNodeStyle.right = ''; inkBarNodeStyle.top = "".concat(top, "px"); inkBarNodeStyle.bottom = "".concat(wrapNode.offsetHeight - top - height, "px"); } } } inkBarNodeStyle.visibility = activeTab ? 'visible' : 'hidden'; } }); useEffect(function () { setOffset(0, setNextPrev); }, [tabBarPosition]); useLayoutEffect(function () { var currentNextPrev = { prev: prev, next: next }; var nextPrev = setNextPrev(); // wait next, prev show hide /* eslint react/no-did-update-set-state:0 */ if (isNextPrevShown(currentNextPrev) !== isNextPrevShown(nextPrev)) { scrollToActiveTab(); } else if (activeKey !== prevActiveKey) { setActiveKey(activeKey); // can not use props.activeKey scrollToActiveTab(); } }, [setNextPrev, isNextPrevShown, prev, next, activeKey, prevActiveKey]); useEffect(function () { var debouncedResize = debounce(function () { setNextPrev(); scrollToActiveTab(); }, 200); resizeEvent.addEventListener('resize', debouncedResize); return function () { resizeEvent.removeEventListener('resize', debouncedResize); debouncedResize.cancel(); }; }, [setNextPrev, scrollToActiveTab, resizeEvent]); var inkBarNode = getInkBarNode(); var tabs = getTabs(); var groupNode = getGroupNode(); var scrollbarNode = getScrollBarNode([inkBarNode, tabs]); return React.createElement("div", _extends({ role: "tablist", className: classnames("".concat(prefixCls, "-bar"), _defineProperty({}, "".concat(prefixCls, "-bar-with-groups"), groupNode), className), tabIndex: 0, ref: rootRef, onKeyDown: handleKeyDown, style: style }, getDataAttr(restProps)), React.createElement("div", { className: "".concat(prefixCls, "-bar-inner") }, groupNode, groupNode && React.createElement("div", { className: "".concat(prefixCls, "-bar-divider") }), getContent(scrollbarNode))); }; TabBar.displayName = 'TabBar'; TabBar.defaultProps = { inkBarAnimated: true, scrollAnimated: true }; export default TabBar; //# sourceMappingURL=TabBar.js.map