saagie-ui
Version:
Saagie UI from Saagie Design System
198 lines (170 loc) • 4.58 kB
JavaScript
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;