@gravityforms/components
Version:
UI components for use in Gravity Forms development. Both React and vanilla js flavors.
231 lines (204 loc) • 6.08 kB
JavaScript
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;