import React, { FC, memo } from 'react'; import { cn } from '../../util/bem'; import { useEventCallback } from '../../util/event-callback'; import { ChildrenOf } from '../../util/types'; import { ButtonPropsType } from '../button/button.component'; import { TabsItem } from './tabs-item.component'; import { TabsScrollButton } from './tabs-scroll-button.component'; import './tabs.component.scss'; export type TabsPropsType = { children: ChildrenOf | ChildrenOf[]; onChange?: (e: React.MouseEvent, value: string) => void; orientation?: 'horizontal' | 'vertical'; viewType?: 'radio-button' | 'tabs' | 'vertical-tabs'; /** * `true` will show scroll buttons on sides in case of overflow * `false` will hide scroll buttons */ hasScrollButtons?: boolean; stretch?: boolean; /** * Id of currently selected `Tab`, to deselect all tabs use `false` */ activeTab?: string | boolean; }; const className = cn('tabs'); type Ref = HTMLDivElement; export const Tabs: FC = memo(React.forwardRef((props, ref) => { const tabsRef = React.useRef(null); const tabsPanelContainerRef = React.useRef(null); const [ translateValue, setTranslateValue ] = React.useState(0); const [ displayScrollButtons, setDisplayScrollButtons ] = React.useState({ start: false, end: false }); const scroll = (scrollValue: number) => { if (tabsPanelContainerRef.current) { const normalizeScroll = scrollValue > 0 ? scrollValue : 0; tabsPanelContainerRef.current.style.transform = (props.orientation === 'vertical') ? `translateY(-${ normalizeScroll }px)` : `translateX(-${ normalizeScroll }px)`; } }; // scroll < > helper const moveTabsScroll = (direction: 'next' | 'back', orientation: TabsPropsType['orientation']) => { if (!tabsRef.current) { return; } const multiplier = direction === 'next' ? 1 : -1; const newTranslateValue = translateValue + tabsRef.current[ orientation === 'vertical' ? 'clientHeight' : 'clientWidth' ] * multiplier; if (tabsPanelContainerRef.current && newTranslateValue < tabsPanelContainerRef.current[ orientation === 'vertical' ? 'scrollHeight' : 'scrollWidth' ]) { setTranslateValue(newTranslateValue); } }; // scroll <-- const handleStartScrollClick = () => { moveTabsScroll('back', props.orientation); }; // scroll --> const handleEndScrollClick = () => { moveTabsScroll('next', props.orientation); }; const getScrollButtons = () => { const scrollButtonsActive = displayScrollButtons.start || displayScrollButtons.end; const showScrollButtons = props.hasScrollButtons && scrollButtonsActive; return { buttonStart: showScrollButtons ? ( ) : null, buttonEnd: showScrollButtons ? ( ) : null }; }; const getScrollParams = (tabPanelContainer: HTMLDivElement, tabs: HTMLDivElement) => ( props.orientation === 'vertical' ? { showStartScroll: translateValue > 0 && translateValue - tabs.clientHeight < tabPanelContainer.scrollHeight, showEndScroll: translateValue + tabs.clientHeight < tabPanelContainer.scrollHeight } : { showStartScroll: translateValue > 0 && translateValue - tabs.clientWidth < tabPanelContainer.scrollWidth, showEndScroll: translateValue + tabs.clientWidth < tabPanelContainer.scrollWidth }); // check if any scroll buttons needed, because content is partly invisible const isScrollButtonsVisible = useEventCallback(() => { if (props.hasScrollButtons && tabsPanelContainerRef.current && tabsRef.current) { const { showStartScroll: start, showEndScroll: end } = getScrollParams(tabsPanelContainerRef.current, tabsRef.current); if (start !== displayScrollButtons.start || end !== displayScrollButtons.end) { setDisplayScrollButtons({ start, end }); } } }); React.useEffect(() => { isScrollButtonsVisible(); }, [ translateValue, props.children ]); React.useLayoutEffect(() => { scroll(translateValue); }, [ translateValue ]); const scrollButtons = getScrollButtons(); const getItemStyle = (child: any) => { let computedStyles: ButtonPropsType = {}; if (props.orientation === 'horizontal') { computedStyles = { ...{ viewType: 'secondary', align: 'center' } }; } else { // computedStyles = { ...{ viewType: 'secondary', align: 'center' } }; // @ts-ignore computedStyles = { ...{ viewType: 'transparent', align: 'left' } }; } if (props.viewType === 'radio-button') { computedStyles = { ...{ size: 'xs', flex: 1 } }; if (child.props.tabId === props.activeTab) { computedStyles = { ...{ viewType: 'primary' } }; } } return computedStyles; }; return (
{ scrollButtons.buttonStart }
{ React.Children.map(props.children, (child) => ( React.isValidElement(child) ? React.cloneElement(child, { // @ts-ignore selected: child.props.tabId === props.activeTab, // @ts-ignore tabId: child.props.tabId, onChange: props.onChange, viewType: props.orientation === 'horizontal' ? 'secondary' : 'transparent', align: props.orientation === 'horizontal' ? 'center' : 'left', ...getItemStyle(child) }) : null)) }
{ scrollButtons.buttonEnd }
); })); Tabs.defaultProps = { orientation: 'horizontal', viewType: 'tabs' };