UNPKG

@gravityforms/components

Version:

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

231 lines (204 loc) 6.08 kB
import { React, PropTypes, classnames } from '@gravityforms/libraries'; import { useIdContext, useStoreContext } from '@gravityforms/react-utils'; import { getId } from './utils'; import Pill from '../../elements/Pill'; import { BACKSPACE, DELETE } from '../../utils/keymap'; const { forwardRef, useRef } = React; const COUNT_PLACEHOLDER = '{{count}}'; /** * @module CondensedDropdownPill * @description The condensed pill component for the dropdown. * * @since 5.6.3 * * @param {object} props The component props. * @param {Function} props.onChange Callback for when the dropdown changes. * * @return {JSX.Element} The condensed pill component. */ const CondensedDropdownPill = ( { onChange = () => {}, } ) => { const triggerRef = useStoreContext( ( state ) => state.triggerRef ); const selectedItem = useStoreContext( ( state ) => state.selectedItem ); const setSelectedItem = useStoreContext( ( state ) => state.setSelectedItem ); const i18n = useStoreContext( ( state ) => state.i18n ); if ( ! selectedItem.length ) { return null; } /** * @function getCondensedPillLabel * @description Get the label for the condensed pill. * * @since 5.6.3 * * @return {string|JSX.Element} The label for the condensed pill. */ const getCondensedPillLabel = () => { const label = i18n?.condensedPillLabel; if ( typeof label === 'string' ) { if ( label.includes( COUNT_PLACEHOLDER ) ) { return label.replaceAll( COUNT_PLACEHOLDER, `${ selectedItem.length }` ); } return label; } return `${ selectedItem.length }`; }; return <DropdownPill content={ getCondensedPillLabel() } removeItem={ ( event ) => { onChange( event, [] ); setSelectedItem( [] ); triggerRef.current.focus(); } } />; }; CondensedDropdownPill.propTypes = { onChange: PropTypes.func, }; /** * @module DefaultDropdownPill * @description The default pill component for the dropdown. * * @since 4.6.3 * * @param {object} props The component props. * @param {object} props.item The item object. * @param {Function} props.selectItem Callback to select an item. * * @return {JSX.Element} The default pill component. */ const DefaultDropdownPill = ( { item = {}, selectItem = () => {}, } ) => { const selectedItem = useStoreContext( ( state ) => state.selectedItem ); const triggerRef = useStoreContext( ( state ) => state.triggerRef ); const pillRef = useRef( null ); /** * @function removeItem * @description Removes the item from the dropdown. * * @since 4.6.3 * * @param {Event} event The event object. */ const removeItem = ( event ) => { // Find index of removed item. const index = selectedItem.findIndex( ( selItem ) => selItem.value === item.value ); const length = selectedItem.length; // Remove item from selected items. selectItem( event, item ); // If current item is last one and there are more than 1 item, set focus on previous one. if ( pillRef.current && length > 1 && index === length - 1 ) { pillRef.current.previousSibling.focus(); } // If current item is last one and there is only 1 item, set focus on trigger. if ( length === 1 ) { triggerRef.current.focus(); } }; return <DropdownPill content={ item.label } removeItem={ removeItem } ref={ pillRef } />; }; DefaultDropdownPill.propTypes = { item: PropTypes.object, selectItem: PropTypes.func, }; /** * @module DropdownPill * @description The pill component for the dropdown. * * @param {object} props The component props. * @param {string} props.content The content of the pill. * @param {Function} props.removeItem Callback to remove the item. * @param {object} ref The ref object. * * @return {JSX.Element} The pill component. */ const DropdownPill = forwardRef( ( { content = '', removeItem = () => {}, }, ref ) => { const pillProps = { content, customClasses: [ 'gform-dropdown__pill' ], customAttributes: { role: 'option', tabIndex: '0', 'aria-keyshortcuts': 'Backspace Delete', onKeyDown: ( event ) => { // If not backspace or delete, return early. if ( ! [ BACKSPACE, DELETE ].includes( event.key ) ) { return; } removeItem( event ); }, }, tagName: 'div', onClick: removeItem, }; return <Pill { ...pillProps } ref={ ref } />; } ); DropdownPill.propTypes = { content: PropTypes.string, removeItem: PropTypes.func, }; /** * @module DropdownPills * @description The pills component for the dropdown. * * @since 4.5.0 * * @param {object} props The component props. * @param {boolean} props.condensePills Whether to condense pills. * @param {boolean} props.multi Whether the dropdown is multi-select. * @param {Function} props.onChange Callback for when the dropdown changes. * @param {Function} props.selectItem Callback for when an item is selected. * @param {object} ref The ref object. * * @return {JSX.Element} The pills component. */ const DropdownPills = forwardRef( ( { condensePills = false, multi = false, onChange = () => {}, selectItem = () => {}, }, ref ) => { const id = useIdContext(); const selectedItem = useStoreContext( ( state ) => state.selectedItem ); // If not multi, or selected item is not array, return early. if ( ! multi || ! Array.isArray( selectedItem ) ) { return null; } const pillsId = getId( id, 'pills' ); const pillsProps = { className: classnames( { 'gform-dropdown__pills': true, 'gform-dropdown__pills--condensed': condensePills, } ), id: pillsId, role: 'listbox', }; return ( <div { ...pillsProps } ref={ ref }> { condensePills ? <CondensedDropdownPill onChange={ onChange } /> : selectedItem.map( ( item, index ) => ( <DefaultDropdownPill key={ index } item={ item } selectItem={ selectItem } /> ) ) } </div> ); } ); DropdownPills.propTypes = { condensePills: PropTypes.bool, multi: PropTypes.bool, onChange: PropTypes.func, selectItem: PropTypes.func, }; export default DropdownPills;