UNPKG

saagie-ui

Version:

Saagie UI from Saagie Design System

198 lines (170 loc) 4.58 kB
import React, { Children, forwardRef, useEffect, useRef, } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useTabContext } from './TabsProvider'; import { modifierCSS } from '../../../helpers'; const propTypes = { /** * Children to set inside the TabList * * @see Tab */ children: PropTypes.node, /** * The default class for the TabList. */ defaultClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]), /** * Callback function trigerred on tab change. The object given is of form * { index, name }. */ onChangeTab: PropTypes.func, /** * Callback function trigerred on tab click. The event is given as * parameter. */ onClick: PropTypes.func, /** * Callback function trigerred on panel focus. This is called on ArrowKeyDown * press. */ onFocusPanel: PropTypes.func, /** * Callback function trigerred on key down. The event is given as a parameter. */ onKeyDown: PropTypes.func, /** * Size of the tab. */ size: PropTypes.oneOf(['', 'sm', 'lg']), /** * The custom tag for the root of the TabList */ tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), }; const defaultProps = { children: '', defaultClassName: 'sui-m-tabs__wrapper', onChangeTab: () => {}, onClick: () => {}, onFocusPanel: () => {}, onKeyDown: () => {}, size: '', tag: 'div', }; export const TabList = forwardRef(({ children, defaultClassName, onChangeTab, onClick, onFocusPanel, onKeyDown, size, tag: Tag, ...attributes }, ref) => { const { selectedName, handleChangeTab, } = useTabContext(); const focusableElements = Children.map( children, (child, index) => (child.props.isDisabled ? null : { index, name: child.props.name }) ).filter((element) => element !== null); const focusedElementIndexRef = useRef(0); useEffect(() => { focusedElementIndexRef.current = focusableElements .map((element) => element.name) .indexOf(selectedName); }, [selectedName]); useEffect(() => { React.Children.forEach(children, (child, index) => { if (selectedName === '' && index === 0) { if (child.props && child.props.name) { handleChangeTab(child.props.name); } } }); }, []); const allNodesRef = useRef([]); const classes = classnames( defaultClassName, modifierCSS(size), ); const count = focusableElements.length; const updateIndex = (index) => { const child = focusableElements[index]; allNodesRef.current[child.index].node.focus(); focusedElementIndexRef.current = index; if (onChangeTab) { onChangeTab(child); } }; const handleKeyDown = (event) => { if (event.key === 'ArrowRight') { event.preventDefault(); const nextIndex = (focusedElementIndexRef.current + 1) % count; updateIndex(nextIndex); } if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { event.preventDefault(); const nextIndex = (focusedElementIndexRef.current - 1 + count) % count; updateIndex(nextIndex); } if (event.key === 'Home') { event.preventDefault(); updateIndex(0); } if (event.key === 'End') { event.preventDefault(); updateIndex(count - 1); } if (event.key === 'ArrowDown') { event.preventDefault(); if (onFocusPanel) { onFocusPanel(); } } if (onKeyDown) { onKeyDown(event); } }; const clones = React.Children.map(children, (child, index) => { const isSelected = child.props.name === selectedName; const handleClick = (event) => { // Hack for Safari. Buttons don't receive focus on click on Safari // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus allNodesRef.current[index].node.focus(); handleChangeTab(child.props.name); if (child.props.onClick) { child.props.onClick(event); } if (onClick) { onClick(event); } }; return React.cloneElement(child, { ref: (node) => { allNodesRef.current[index] = { node, child }; return allNodesRef.current[index].node; }, isSelected, onClick: handleClick, }); }); return ( <Tag className={classes} ref={ref} role="tablist" onKeyDown={handleKeyDown} {...attributes} > {clones} </Tag> ); }); TabList.propTypes = propTypes; TabList.defaultProps = defaultProps;