UNPKG

@zohodesk/components

Version:

In this Package, we Provide Some Basic Components to Build Web App

619 lines (566 loc) • 19.3 kB
import React, { useState, useRef, useEffect, useCallback } from 'react'; import useEffectCallOnlyAfterState from '@zohodesk/hooks/es/utils/useEffectCallOnlyAfterState'; import TextBoxIcon from "../TextBoxIcon/TextBoxIcon"; import EmptySearch from '@zohodesk/svg/lib/emptystate/version3/EmptySearch'; import Tab from "./Tab"; import { Tabs_defaultProps } from "./props/defaultProps"; import { Tabs_propTypes } from "./props/propTypes"; import { remConvert, getTotalDimension, cs, throttle, cancelBubblingEffect } from "../../utils/Common"; import { Box, Container } from "../Layout"; import ResizeObserver from "../../Responsive/ResizeObserver"; import ListItem from "../ListItem/ListItem"; import { Icon } from '@zohodesk/icons'; import tabsStyle from "./v1Tabs.module.css"; import Popup from "../Popup/Popup"; import ResponsiveDropBox from "../ResponsiveDropBox/ResponsiveDropBox"; import { ResponsiveReceiver } from "../../Responsive/CustomResponsive"; import btnstyle from "../../semantic/Button/semanticButton.module.css"; const Tabs = props => { const [totalDimension, setTotalDimension] = useState(null); const [tabDimensions, setTabDimensions] = useState({}); const highlightInitialDimension = useRef(null); const [renderVirtualTabs, setRenderVirtualTabs] = useState(true); const [tabKeys, setTabKeys] = useState([]); const [searchValue, setSearchValue] = useState(''); const tabsObserver = useRef(new ResizeObserver(onResize)); const tabObserver = useRef({}); const highlight = useRef(null); const tabsEle = useRef(null); const tabElement = useRef({}); let { style, className, dataId, highlightClass, type, isAnimate, needTabBorder, needBorder, needPadding, align, boxPosition, removeClose, togglePopup, needAppearance, children, onSelect, onScroll, isResponsive, maxTabsCount, minTabsCount, childType, containerClass, dataSelectorId, selectedTab, showTitleInMoreOptions, searchErrorText, placeHolderText, searchBoxSize, isPopupOpen, closePopupOnly } = props; function handleChange(value, e) { setSearchValue(value); } function handleSearchValueClear(e) { setSearchValue(''); } function getMoreList(_ref) { let { moreTabs } = _ref; const validElements = []; React.Children.map(moreTabs, child => { const isValidElement = /*#__PURE__*/React.isValidElement(child); if (isValidElement) { validElements.push(child.props); } }); return validElements; } function handleFilterSuggestions(_ref2) { let { moreTabs } = _ref2; const options = getMoreList({ moreTabs }); const filteredOptions = options.filter(list => { const { text, title } = list; const value = text ? text : showTitleInMoreOptions ? title : ''; return value.toLowerCase().includes(searchValue.toLowerCase()); }); return filteredOptions; } function renderEmptyState() { return /*#__PURE__*/React.createElement("div", { className: tabsStyle.emptyStateContainer }, /*#__PURE__*/React.createElement(EmptySearch, { size: "small" }), /*#__PURE__*/React.createElement("div", { className: tabsStyle.emptyStateTitle }, searchErrorText)); } function onTabResize(size, target) { let newDim = Object.assign({}, tabDimensions); let elemDim = getTotalDimension(target, align); if (elemDim !== 0) { newDim[target.dataset.key] = elemDim; } setTabDimensions(newDim); } useEffect(() => { let totalDimension = getTotalDimensionLocal(); let tabDimensions = getTabDimensions(); let tabKeys = []; let restrictedKeys = ['state', 'props', 'refs', 'context']; React.Children.toArray(children).forEach(child => { if (child && child.props.id && child.type === childType) { if (restrictedKeys.includes(child.props.id)) { throw new Error('Restricted id name found in Tabs, Please avoid these names => ' + restrictedKeys.toString()); } else { tabKeys.push(child.props.id); } } }); setTotalDimension(totalDimension); setTabDimensions(tabDimensions); setRenderVirtualTabs(false); setTabKeys(tabKeys); if (highlight.current) { highlightInitialDimension.current = getHighlightDim(); moveHighlight(); } return () => { if (tabsObserver.current) { tabsObserver.current.disconnect(); tabsObserver.current = null; } if (tabObserver.current) { Object.keys(tabObserver.current).forEach(observer => { tabObserver.current[observer] && tabObserver.current[observer].disconnect(); }); tabObserver.current = null; } }; }, []); useEffect(() => { if (searchValue.length) { handleSearchValueClear(); } }, [isPopupOpen]); function setMaxDim() { let totalDimension = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; // let actual = Object.keys(tabDimensions).reduce( // (sum, tab) => sum + (tabDimensions[tab] || 0), // 0 // ); if (totalDimension && tabElement.current[selectedTab]) { let newDim = `${remConvert(totalDimension)}rem`; align === 'vertical' ? tabElement.current[selectedTab].style.maxWidth = newDim : tabElement.current[selectedTab].style.maxHeight = newDim; } } function getHighlightDim() { return align === 'vertical' ? highlight.current.offsetLeft : highlight.current.offsetTop; } useEffectCallOnlyAfterState(() => { if (children) { /** * To recalculate the dimensions of tabs we are checking the children length. * and also checking the tab id and their order. */ let newTabKeys = []; React.Children.toArray(children).forEach(child => { if (child && child.props.id && child.type === childType) { newTabKeys.push(child.props.id); } }); let isSameTabKeys = newTabKeys.every((current, index) => tabKeys[index] === current); if (newTabKeys.length !== tabKeys.length || !isSameTabKeys) { setRenderVirtualTabs(true); setTabKeys(newTabKeys); setTimeout(() => { setTabDimensions(getTabDimensions()); setTotalDimension(getTotalDimensionLocal()); setRenderVirtualTabs(false); }); } if (newTabKeys.length === 1) { moveHighlight(); } } }); useEffectCallOnlyAfterState(() => { tabsObserver.current && tabsObserver.current.replaceResizeHandler(onResize); if (children) { moveHighlight(); } }, [selectedTab]); function moveHighlight() { let node = highlight.current; if (isAnimate && needTabBorder && node) { let tabActive = tabElement.current[selectedTab]; if (tabActive) { let position, dimension; if (align === 'vertical') { position = tabActive.offsetLeft; dimension = tabActive.offsetWidth; node.style.transform = `translateX(${remConvert(position - highlightInitialDimension.current)}rem)`; node.style.width = `${remConvert(dimension)}rem`; } else if (align === 'horizontal') { position = tabActive.offsetTop; dimension = tabActive.offsetHeight; node.style.transform = `translateY(${remConvert(position - highlightInitialDimension.current)}rem)`; node.style.height = `${remConvert(dimension)}rem`; } } } } const getHighlightRef = useCallback(ele => { highlight.current = ele; }, []); const getTabsRef = useCallback(ele => { tabsEle.current = ele; if (ele) { tabsObserver.current.observe(ele); } else { tabsObserver.current && tabsObserver.current.disconnect(); } }, []); function onSizeChange() { let totalDimension = getTotalDimensionLocal(); setTotalDimension(totalDimension); if (highlight.current) { highlightInitialDimension.current = getHighlightDim(); moveHighlight(); } } function onResize(timer) { if (tabsEle.current && isResponsive) { if (!timer) { onSizeChange(); } else { setTimeout(() => { onSizeChange(); }, timer); } } } function getTotalDimensionLocal() { let totalDimension = tabsEle.current && getTotalDimension(tabsEle.current, align); return totalDimension; } function getTabDimensions() { let tabDimensions = {}; React.Children.forEach(children, child => /*#__PURE__*/React.isValidElement(child) && child.type === childType && tabElement.current[child.props.id] && Object.assign(tabDimensions, { [child.props.id]: tabElement.current[child.props.id] && getTotalDimension(tabElement.current[child.props.id], align) })); return tabDimensions; } function responsiveTab(totalDimension, tabDimensions) { let mainTabs = [], moreTabs = [], otherTabs = []; if (totalDimension && isResponsive) { let selectedTabDimension = tabDimensions[selectedTab] || 0; let remainingDimension = totalDimension - selectedTabDimension; let maxTabsTobeVisible = maxTabsCount - 1; let minTabsToBeVisible = 1; React.Children.forEach(children, child => { if (child && child.type === childType && child.props.id) { const elemDimension = tabDimensions[child.props.id]; let isMaxCountExceeded = maxTabsTobeVisible <= 0 ? true : false; let isMinCountNotExceeded = minTabsToBeVisible < minTabsCount ? true : false; if (child.props.id === selectedTab) { mainTabs.push(child); } else if (isMinCountNotExceeded) { mainTabs.push(child); remainingDimension -= tabDimensions[child.props.id]; minTabsToBeVisible++; maxTabsTobeVisible--; } else if (remainingDimension - elemDimension >= 20 && !moreTabs.length && !isMaxCountExceeded) { mainTabs.push(child); remainingDimension -= tabDimensions[child.props.id]; maxTabsTobeVisible--; } else { moreTabs.push(child); } } else if ( /*#__PURE__*/React.isValidElement(child)) { otherTabs.push(child); } }); if (selectedTabDimension > totalDimension) { /* let { align } = this.props; let newDim = `${remConvert(totalDimension)}rem`; let newTabs; if (align === 'vertical') { newTabs = mainTabs.map(tab => React.cloneElement(tab, { style: { maxWidth: newDim } })); } else { newTabs = mainTabs.map(tab => React.cloneElement(tab, { style: { maxHeight: newDim } })); } mainTabs = newTabs; */ setMaxDim(totalDimension); } } else if (!isResponsive) { React.Children.forEach(children, child => { if (child && child.type === childType && child.props.id) { mainTabs.push(child); } else { otherTabs.push(child); } }); } else { moreTabs = children; } return { mainTabs, moreTabs, otherTabs }; } function getTabRef(refName, ele, isVirtual) { if (ele) { tabElement.current[refName] = ele; } // tabElement.current[refName] = ele let key = isVirtual ? `virtual_${refName}` : refName; if (ele) { tabObserver.current[key] = new ResizeObserver(onTabResize); tabObserver.current[key].observe(ele); } else { if (tabObserver.current && tabObserver.current[key]) { tabObserver.current[key].disconnect(); } } } function moreTabSelect(tab, value, index, e) { if (e && e.target && e.target.parentElement.tagName === 'A' && (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)) { cancelBubblingEffect(e); return; } e.preventDefault(); onSelect(tab); closePopupOnly(); } function onScrollLocal(e) { e.persist(); onScroll && throttle(onScroll, 100, e); } function togglePopupLocal(e) { removeClose && removeClose(e); let popupPosition = align === 'vertical' ? 'bottomLeft' : 'rightTop'; boxPosition = boxPosition ? boxPosition : popupPosition; togglePopup(e, boxPosition); } function responsiveFunc(_ref3) { let { mediaQueryOR } = _ref3; return { tabletMode: mediaQueryOR([{ maxWidth: 700 }]) }; } function renderTabs(mainTabs, moreTabs, isVirtual) { let { moreButtonClass, moreButtonActiveClass, moreBoxClass, menuItemClass, itemClass, itemActiveClass, iconName, isPopupReady, isPopupOpen, getContainerRef, showTitleInMoreOptions, moreContainerClass, align, type, isAnimate, needTabBorder, needAppearance, iconSize, getTargetRef, position, customProps, getCustomDropBoxHeaderPlaceHolder, dataId, isAbsolutePositioningNeeded, isRestrictScroll, positionsOffset, targetOffset } = props; let { DropBoxProps = {}, ListItemProps = {}, MoreButtonProps = {}, TextBoxIconProps = {} } = customProps; let popupClass = align === 'vertical' ? tabsStyle[position] ? tabsStyle[position] : '' : ''; const moreTabsListItems = handleFilterSuggestions({ moreTabs }); const hasSearch = moreTabs.length > 4; return /*#__PURE__*/React.createElement(React.Fragment, null, React.Children.map(mainTabs, child => { if (!child) { return null; } let MainTab = child.type; let classProps = {}; if (itemActiveClass) { classProps.activeClass = itemActiveClass; } if (itemClass) { classProps.className = itemClass; } return /*#__PURE__*/React.isValidElement(child) && /*#__PURE__*/React.createElement(MainTab, { ...child.props, key: child.props.id, getTabRef: getTabRef, onSelect: onSelect // recalculateDimension={tabElement.current[recalculateDimension]} , isActive: child.props.id === selectedTab, type: type, isAnimate: isAnimate, needBorder: needTabBorder, needAppearance: needAppearance, align: align, isVirtual: isVirtual, ...classProps }); }), React.Children.count(moreTabs) ? /*#__PURE__*/React.createElement(Box, { className: `${tabsStyle.menu} `, dataSelectorId: `${dataSelectorId}_moreIcon` }, /*#__PURE__*/React.createElement(Container, { className: `${btnstyle.buttonReset} ${moreButtonClass} ${isPopupOpen ? moreButtonActiveClass : ''}`, align: "both", onClick: togglePopupLocal, dataId: "moreTabs", eleRef: getTargetRef, ...MoreButtonProps, "aria-label": "MoreTabs", role: "link", tagName: "button" }, /*#__PURE__*/React.createElement(Icon, { name: iconName, size: iconSize })), isPopupOpen && /*#__PURE__*/React.createElement(ResponsiveReceiver, { query: responsiveFunc, responsiveId: "Helmet" }, _ref4 => { let { tabletMode } = _ref4; return /*#__PURE__*/React.createElement(ResponsiveDropBox, { isActive: isPopupReady, isAnimate: true, size: "medium", customClass: { customDropBoxWrap: `${moreContainerClass} ${popupClass}` }, boxPosition: position, getRef: getContainerRef, isBoxPaddingNeed: false, isArrow: false, isAbsolutePositioningNeeded: isAbsolutePositioningNeeded, isRestrictScroll: isRestrictScroll, positionsOffset: positionsOffset, targetOffset: targetOffset, ...DropBoxProps, isResponsivePadding: true, needFocusScope: true, onClose: togglePopupLocal, onClick: removeClose, dataId: `${dataId}_dropbox` }, getCustomDropBoxHeaderPlaceHolder && getCustomDropBoxHeaderPlaceHolder(props), hasSearch ? /*#__PURE__*/React.createElement(Box, { className: tabsStyle.search }, /*#__PURE__*/React.createElement(TextBoxIcon, { placeHolder: placeHolderText, onChange: handleChange, value: searchValue, onClear: handleSearchValueClear, size: searchBoxSize, customProps: { TextBoxProps: { 'data-a11y-autofocus': true } }, dataId: `${dataId}_search`, autoComplete: false, name: "search", ...TextBoxIconProps })) : null, /*#__PURE__*/React.createElement(Box, { flexible: true, shrink: true, scroll: "vertical", className: `${tabsStyle.listWrapper} ${tabletMode ? '' : tabsStyle.menuBox} ${moreBoxClass}`, onScroll: onScrollLocal, dataId: `${dataId}_Tabs` }, moreTabsListItems.length ? moreTabsListItems.map(data => { let { text, id, title, isLink, href, children, dataId } = data; const value = text ? text : showTitleInMoreOptions ? title : null; return /*#__PURE__*/React.createElement(ListItem, { key: id, value: value, onClick: moreTabSelect, id: id, title: title || text, isLink: isLink, href: href, autoHover: true, customClass: { customListItem: menuItemClass }, target: "self", dataId: `${dataId}_Tab`, ...ListItemProps }, !showTitleInMoreOptions ? children : null); }) : renderEmptyState())); })) : null); } let { mainTabs, moreTabs, otherTabs } = responsiveTab(totalDimension, tabDimensions); let appearanceClass = cs([tabsStyle[type], needBorder && tabsStyle[`${type}_border`], needPadding && tabsStyle[`${type}_padding`]]); let tabsClass = cs([tabsStyle.tab, className, needAppearance && appearanceClass]); let hlClass = cs([tabsStyle.highlight, highlightClass, isAnimate && tabsStyle.lineAnimate]); return /*#__PURE__*/React.createElement(Box, { className: containerClass, dataSelectorId: dataSelectorId }, /*#__PURE__*/React.createElement(Container, { alignBox: align === 'vertical' ? 'row' : 'column', className: tabsClass, style: style }, /*#__PURE__*/React.createElement(Box, { eleRef: getTabsRef, flexible: true }, renderVirtualTabs && /*#__PURE__*/React.createElement(Container, { alignBox: align === 'vertical' ? 'row' : 'column', className: tabsStyle.hidden }, renderTabs(children, [], true)), /*#__PURE__*/React.createElement(Container, { alignBox: align === 'vertical' ? 'row' : 'column' }, renderTabs(mainTabs, moreTabs, false))), otherTabs.length ? /*#__PURE__*/React.createElement(Box, null, otherTabs) : null, isAnimate && needTabBorder && needAppearance && /*#__PURE__*/React.createElement("div", { className: hlClass, ref: getHighlightRef, "data-id": dataId, "data-test-id": dataId }))); }; Tabs.propTypes = Tabs_propTypes; Tabs.defaultProps = { ...Tabs_defaultProps, childType: Tab }; export default Popup(Tabs);