UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

257 lines (241 loc) 7.12 kB
import { React, PropTypes, classnames } from '@gravityforms/libraries'; import { CrossFade, useId, useStateWithDep } from '@gravityforms/react-utils'; import { spacerClasses } from '@gravityforms/utils'; import Button from '../../elements/Button'; import StatusIndicator from '../../elements/StatusIndicator'; import { ARROW_LEFT, ARROW_RIGHT, HOME, END } from '../../utils/keymap'; const { forwardRef, useState } = React; /** * @module Tabs * @description A tabs component. * * @since 4.7.2 * * @param {object} props The component props. * @param {number} props.activeTab The active tab index. * @param {boolean} props.automatic Whether to automatically switch tabs using arrow keys. * @param {boolean} props.controlled Whether the component is controlled. * @param {object} props.customAttributes Custom attributes for the component. * @param {string|Array|object} props.customClasses Custom classes for the component. * @param {string} props.iconPrefix The icon prefix for the component. * @param {string} props.id The id for the component. * @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object. * @param {Array} props.tabs An array of objects for the tabs content. * @param {string} props.tabWidth The tab width for the component, one of `auto` or `even`. * * @return {JSX.Element} The tabs component. * * @example * import Tabs from '@gravityforms/components/react/admin/modules/Tabs'; * * return ( * <Tabs * automatic={ true } * tabs={ [ * { * label: 'Tab 1', * panel: 'Tab 1 Content', * }, * { * icon: 'ellipsis', * label: 'Tab 2', * panel: 'Tab 2 Content', * }, * { * label: 'Tab 3', * status: '2', * panel: 'Tab 3 Content', * }, * { * icon: 'trash', * label: 'Tab 4', * status: '97', * panel: 'Tab 4 Content', * } * ] } * /> * ); * */ const Tabs = forwardRef( ( { activeTab: defaultActiveTab = 0, automatic = true, controlled = false, customAttributes = {}, customClasses = [], iconPrefix = 'gravity-component-icon', id: defaultId = '', spacing = '', tabs = [], tabWidth = 'auto', }, ref ) => { const id = useId( defaultId ); const [ stateActiveTab, setStateActiveTab ] = useState( defaultActiveTab ); const [ controlActiveTab, setControlActiveTab ] = useStateWithDep( defaultActiveTab ); const activeTab = controlled ? controlActiveTab : stateActiveTab; const lastTab = tabs.length - 1; /** * @function getTablist * @description Get the tablist. * * @since 4.7.2 * * @return {JSX.Element} The tablist. */ const getTablist = () => { const tablistProps = { className: 'gform-tabs__tablist', role: 'tablist', }; return ( <div { ...tablistProps }> { tabs.map( ( tab, index ) => { const tabId = `${ id }-tab-${ index }`; const tabPanelId = `${ id }-tabpanel-${ index }`; const isActive = activeTab === index; const tabProps = { customAttributes: { 'aria-controls': tabPanelId, 'aria-selected': isActive ? 'true' : 'false', id: tabId, onKeyDown: ( event ) => { if ( ! [ ARROW_LEFT, ARROW_RIGHT, HOME, END ].includes( event.key ) ) { return; } let nextIndex; switch ( event.key ) { case ARROW_LEFT: nextIndex = index === 0 ? lastTab : index - 1; break; case ARROW_RIGHT: nextIndex = index === lastTab ? 0 : index + 1; break; case HOME: nextIndex = 0; break; case END: nextIndex = lastTab; break; default: break; } document.getElementById( `${ id }-tab-${ nextIndex }` ).focus(); if ( activeTab === nextIndex ) { return; } if ( automatic ) { setStateActiveTab( nextIndex ); setControlActiveTab( nextIndex ); } }, role: 'tab', tabIndex: ! isActive ? '-1' : undefined, }, customClasses: classnames( { 'gform-tabs__tab': true, 'gform-tabs__tab--active': isActive, } ), onClick: () => { if ( activeTab === index ) { return; } setStateActiveTab( index ); // setControlActiveTab( index ); }, label: tab.label, size: 'size-height-m', type: 'simplified', }; if ( tab.icon ) { tabProps.icon = tab.icon; tabProps.iconPrefix = iconPrefix; tabProps.iconPosition = 'leading'; } if ( tab.status ) { const statusProps = { hasDot: false, label: tab.status, size: 'sm', status: 'gray', }; tabProps.children = <StatusIndicator { ...statusProps } />; } return <Button key={ tabId } { ...tabProps } />; } ) } </div> ); }; /** * @function getTabPanels * @description Get the tab panels. * * @since 4.7.2 * * @return {JSX.Element} The tab panels. */ const getTabPanels = () => { const tabPanelsProps = { activeIndex: activeTab, customClasses: [ 'gform-tabs__tabpanels' ], duration: 150, id: `${ id }-tabpanels`, }; return ( <CrossFade { ...tabPanelsProps }> { tabs.map( ( tab, index ) => { const tabId = `${ id }-tab-${ index }`; const tabPanelId = `${ id }-tabpanel-${ index }`; const tabPanelProps = { 'aria-labelledby': tabId, className: 'gform-tabs__tabpanel', id: tabPanelId, role: 'tabpanel', tabIndex: '0', }; return ( <div key={ tabPanelId } { ...tabPanelProps }> { tab.panel } </div> ); } ) } </CrossFade> ); }; const componentProps = { className: classnames( { 'gform-tabs': true, 'gform-tabs--automatic': automatic, [ `gform-tabs--tab-width-${ tabWidth }` ]: tabWidth, ...spacerClasses( spacing ), }, customClasses ), ...customAttributes, }; return ( <div { ...componentProps } ref={ ref }> { getTablist() } { getTabPanels() } </div> ); } ); Tabs.propTypes = { activeTab: PropTypes.number, automatic: PropTypes.bool, controlled: PropTypes.bool, customAttributes: PropTypes.object, customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), iconPrefix: PropTypes.string, id: PropTypes.string, spacing: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object, ] ), tabs: PropTypes.array, tabWidth: PropTypes.oneOf( [ 'auto', 'even' ] ), }; export default Tabs;