@gravityforms/components
Version:
UI components for use in Gravity Forms development. Both React and vanilla js flavors.
265 lines (250 loc) • 8.2 kB
JavaScript
import { React, PropTypes, SimpleBar, classnames } from '@gravityforms/libraries';
import { ConditionalWrapper, useIdContext, useStoreContext } from '@gravityforms/react-utils';
import {
getId,
getComponent,
} from './utils';
import RingLoader from '../Loaders/RingLoader';
const { forwardRef } = React;
/**
* @module DropdownList
* @description The list component for the dropdown.
*
* @since 4.5.0
*
* @param {object} props The component props.
* @param {boolean} props.ajaxSearch Whether to use ajax search for the dropdown.
* @param {Function} props.handleBlur The blur event handler.
* @param {Function} props.handleKeyDownCapture The key down capture event handler.
* @param {Function} props.handleListKeyDown The list key down event handler.
* @param {boolean} props.hasSearch Whether the dropdown has search.
* @param {string} props.label The label of the dropdown.
* @param {Array} props.listAttributes Custom attributes for the list.
* @param {string|Array|object} props.listClasses Custom classes for the list.
* @param {boolean} props.multi Whether the dropdown is multi-select.
* @param {number} props.popoverMaxHeight The popover max height.
* @param {boolean} props.searchIsLoading Whether the dropdown list items are loading from search or not.
* @param {string} props.selectedIcon The selected icon.
* @param {string} props.selectedIconPrefix The selected icon prefix.
* @param {Function} props.selectItem The select item event handler.
* @param {boolean} props.simplebar Whether to use simplebar for the dropdown.
* @param {object} ref The ref object.
*
* @return {JSX.Element} The list component.
*/
const DropdownList = forwardRef( ( {
ajaxSearch = false,
handleBlur = () => {},
handleKeyDownCapture = () => {},
handleListKeyDown = () => {},
hasSearch = false,
label = '',
listAttributes = {},
listClasses = [],
multi = false,
popoverMaxHeight = 0,
searchIsLoading = false,
selectedIcon = 'check-mark-alt',
selectedIconPrefix = 'gravity-component-icon',
selectItem = () => {},
simplebar = true,
}, ref ) => {
const id = useIdContext();
const activeItem = useStoreContext( ( state ) => state.activeItem );
const listItems = useStoreContext( ( state ) => state.listItems );
const selectedItem = useStoreContext( ( state ) => state.selectedItem );
const setActiveItem = useStoreContext( ( state ) => state.setActiveItem );
const baseElRef = useStoreContext( ( state ) => state.baseElRef );
/**
* @function getListItems
* @description Gets the list items for the dropdown.
*
* @since 4.5.0
*
* @param {Array} items The list items.
*
* @return {Array} An array of list items.
*/
const getListItems = ( items ) => {
return items.map( ( item ) => {
// Return null if search.
if ( item.type === 'search' ) {
return null;
}
// Recursively get group items.
if ( item.type === 'group' ) {
const hasLabel = item.items.some( ( subItem ) => subItem.type === 'groupLabel' );
const groupProps = {
className: classnames( {
'gform-dropdown__list-group': true,
'gform-dropdown__list-group--has-label': hasLabel,
} ),
id: item.id,
role: 'group',
};
return (
<div { ...groupProps } key={ item.id }>
{ getListItems( item.items ) }
</div>
);
}
// Get the group label.
if ( item.type === 'groupLabel' ) {
const groupLabelProps = {
className: classnames( [
'gform-dropdown__list-group-label',
'gform-text--color-comet',
'gform-typography--size-text-xs',
'gform-typography--weight-regular',
] ),
id: item.id,
role: 'presentation',
};
return (
<div { ...groupLabelProps } key={ item.id }>
{ item.label }
</div>
);
}
// Get the list item.
let selected = selectedItem.value === item.value;
if ( multi && Array.isArray( selectedItem ) ) {
selected = selectedItem.some( ( selItem ) => selItem.value === item.value );
}
const isDisabled = ! ! item.disabled;
const itemProps = {
className: classnames( 'gform-dropdown__list-item', {
'gform-dropdown__list-item--disabled': isDisabled,
} ),
id: item.id,
'aria-selected': selected ? 'true' : 'false',
'aria-disabled': isDisabled ? 'true' : 'false',
tabIndex: '-1',
role: 'option',
onMouseMove: isDisabled ? null : () => {
setActiveItem( item );
},
onClick: isDisabled ? null : ( event ) => selectItem( event, item ),
onFocus: () => baseElRef?.current?.focus(),
};
if ( ! isDisabled && activeItem.value === item.value ) {
itemProps[ 'data-active-item' ] = 'true';
}
const itemInnerProps = {
className: classnames( [
'gform-dropdown__list-item-inner',
'gform-text',
'gform-text--color-port',
'gform-typography--size-text-sm',
'gform-typography--weight-regular',
] ),
};
return (
<div { ...itemProps } key={ item.id }>
<div { ...itemInnerProps }>
{ item.beforeLabel && (
<span className="gform-dropdown__list-item-before-label">
{ getComponent( item.beforeLabel ) }
</span>
) }
<span className="gform-dropdown__list-item-label">{ item.label }</span>
{ ( ( item.afterLabel && ! multi ) || ( multi && selected ) ) && (
<span className="gform-dropdown__list-item-after-label">
{ multi && selected
? getComponent( {
component: 'Icon',
props: {
iconPrefix: selectedIconPrefix,
icon: selectedIcon,
},
} )
: getComponent( item.afterLabel )
}
</span>
) }
</div>
</div>
);
} );
};
const listId = getId( id, 'list' );
const listProps = {
className: classnames( [ 'gform-dropdown__list' ], listClasses ),
...listAttributes,
id: listId,
onBlur: handleBlur,
onKeyDown: ( event ) => {
handleListKeyDown( event );
},
onKeyDownCapture: ( event ) => handleKeyDownCapture( event ),
role: 'listbox',
};
const simpleBarProps = {
className: 'gform-dropdown__list-simplebar',
};
if ( ! hasSearch ) {
listProps.tabIndex = '0';
if ( label ) {
const labelId = getId( id, 'label' );
listProps[ 'aria-labelledby' ] = labelId;
}
if ( listItems.flatItems.length ) {
listProps[ 'aria-activedescendant' ] = activeItem.id;
}
}
if ( popoverMaxHeight ) {
const listMaxHeight = hasSearch ? Math.max( popoverMaxHeight - 58, 0 ) : popoverMaxHeight;
if ( listMaxHeight > 0 ) {
const listMaxHeightPx = `${ listMaxHeight }px`;
listProps.style = {
maxHeight: listMaxHeightPx,
};
simpleBarProps.style = {
height: listMaxHeightPx,
};
}
}
return (
<div { ...listProps } ref={ ref }>
<ConditionalWrapper
condition={ simplebar && popoverMaxHeight > 0 }
wrapper={ ( ch ) => (
<div { ...simpleBarProps } >
<SimpleBar>
{ ch }
</SimpleBar>
</div>
) }
>
{ getListItems( listItems.items ) }
</ConditionalWrapper>
{ ajaxSearch && searchIsLoading && (
<div className="gform-dropdown__list-loader-wrapper">
<RingLoader customClasses={ [ 'gform-dropdown__list-loader' ] } foreground="#242748" />
</div>
) }
</div>
);
} );
DropdownList.propTypes = {
ajaxSearch: PropTypes.bool,
handleBlur: PropTypes.func,
handleKeyDownCapture: PropTypes.func,
handleListKeyDown: PropTypes.func,
hasSearch: PropTypes.bool,
label: PropTypes.string,
listAttributes: PropTypes.object,
listClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
multi: PropTypes.bool,
popoverMaxHeight: PropTypes.number,
searchIsLoading: PropTypes.bool,
selectedIcon: PropTypes.string,
selectedIconPrefix: PropTypes.string,
selectItem: PropTypes.func,
simplebar: PropTypes.bool,
};
export default DropdownList;