UNPKG

@gravityforms/components

Version:

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

1,029 lines (964 loc) 33.2 kB
import { React, PropTypes, SimpleBar, classnames } from '@gravityforms/libraries'; import { ConditionalWrapper, IdProvider, useIdContext, StoreProvider, useStoreContext } from '@gravityforms/react-utils'; import { spacerClasses } from '@gravityforms/utils'; import useDropdownControl from './hooks/control'; import useDropdownBlur from './hooks/blur'; import useDropdownKeyDown from './hooks/key-down'; import useDropdownTypeahead from './hooks/typeahead'; import { getId, getSearchItem, getListItemsState, getComponent, filterListItems, convertSingleToMultiItem, convertMultiToSingleItem, getSelectedItemFromValue, } from './utils'; import createStore from './store'; import Input from '../../elements/Input'; import Pill from '../../elements/Pill'; import { BACKSPACE, DELETE } from '../../utils/keymap'; const { forwardRef, useEffect, useRef } = React; /** * @module DropdownLabel * @description The label for the dropdown. * * @since 4.5.0 * * @param {object} props Component props. * @param {string} props.label The label for the dropdown. * @param {object} props.labelAttributes Custom attributes for the label. * @param {string|Array|object} props.labelClasses Custom classes for the label. * @param {object} ref Ref to the component. * * @return {JSX.Element} The label component. */ const DropdownLabel = forwardRef( ( { label = '', labelAttributes = {}, labelClasses = [], }, ref ) => { const id = useIdContext(); const triggerRef = useStoreContext( ( state ) => state.triggerRef ); if ( ! label ) { return null; } const labelId = getId( id, 'label' ); const labelProps = { className: classnames( [ 'gform-dropdown__label', 'gform-text', 'gform-text--color-port', 'gform-typography--size-text-sm', 'gform-typography--weight-medium', ], labelClasses ), ...labelAttributes, id: labelId, onClick: () => triggerRef?.current?.focus(), }; return <div { ...labelProps } ref={ ref }>{ label }</div>; } ); /** * @module DropdownTrigger * @description The trigger for the dropdown. * * @since 4.5.0 * * @param {object} props Component props. * @param {boolean} props.disabled Whether the dropdown is disabled. * @param {Function} props.handleBlur The blur event handler. * @param {Function} props.handleEscKeyDown The escape keydown event handler. * @param {Function} props.handleKeyDownCapture The keydown capture event handler. * @param {Function} props.handleTriggerKeyDown The trigger keydown event handler. * @param {object} props.i18n The i18n object. * @param {string} props.label The label for the dropdown. * @param {boolean} props.multi Whether the dropdown is multi-select. * @param {Function} props.open The open function. * @param {Function} props.resetAndClose The reset and close function. * @param {object} props.triggerAttributes Custom attributes for the trigger. * @param {string|Array|object} props.triggerClasses Custom classes for the trigger. * @param {object} ref Ref to the component. * * @return {JSX.Element} The trigger component. */ const DropdownTrigger = forwardRef( ( { disabled = false, handleBlur = () => {}, handleEscKeyDown = () => {}, handleKeyDownCapture = () => {}, handleTriggerKeyDown = () => {}, i18n = {}, label = '', multi = false, open = () => {}, resetAndClose = () => {}, triggerAttributes = {}, triggerClasses = [], }, ref ) => { const id = useIdContext(); const dropdownOpen = useStoreContext( ( state ) => state.open ); const selectedItem = useStoreContext( ( state ) => state.selectedItem ); const triggerHeight = useStoreContext( ( state ) => state.triggerHeight ); const popoverId = getId( id, 'popover' ); const labelId = getId( id, 'label' ); const pillsId = getId( id, 'pills' ); const triggerProps = { className: classnames( [ 'gform-dropdown__trigger', 'gform-text', 'gform-text--color-port', 'gform-typography--size-text-sm', 'gform-typography--weight-regular', ], triggerClasses ), ...triggerAttributes, 'aria-autocomplete': 'none', 'aria-controls': popoverId, 'aria-expanded': dropdownOpen ? 'true' : 'false', 'aria-haspopup': 'listbox', disabled, onBlur: handleBlur, onClick: ( event ) => { const clickHandler = dropdownOpen ? resetAndClose : open; clickHandler( event ); }, onKeyDown: ( event ) => { handleEscKeyDown( event ); handleTriggerKeyDown( event ); }, onKeyDownCapture: ( event ) => handleKeyDownCapture( event ), role: 'combobox', type: 'button', }; if ( label ) { triggerProps[ 'aria-labelledby' ] = labelId; } if ( multi ) { triggerProps[ 'aria-describedby' ] = pillsId; } if ( triggerHeight ) { triggerProps.style = { height: `${ triggerHeight }px`, }; } /** * @function getSingleTriggerLabel * @description Get the label for the single dropdown trigger. * * @since 4.5.0 * * @return {JSX.Element} The label for the single dropdown trigger. */ const getSingleTriggerLabel = () => ( <> { selectedItem.beforeLabel && ( <span className="gform-dropdown__trigger-before-label"> { getComponent( selectedItem.beforeLabel ) } </span> ) } <span className="gform-dropdown__trigger-label">{ selectedItem.label }</span> { selectedItem.afterLabel && ( <span className="gform-dropdown__trigger-after-label"> { getComponent( selectedItem.afterLabel ) } </span> ) } </> ); /** * @function getMultiTriggerLabel * @description Get the label for the multi dropdown trigger. * * @since 4.5.0 * * @return {string|JSX.Element} The label for the multi dropdown trigger. */ const getMultiTriggerLabel = () => { if ( selectedItem.length ) { return ( <span className="gform-visually-hidden"> { selectedItem.map( ( item ) => item.label ).join( ', ' ) } </span> ); } return i18n.multiTriggerLabel || 'Select'; }; return ( <button { ...triggerProps } ref={ ref }> { multi ? getMultiTriggerLabel() : getSingleTriggerLabel() } </button> ); } ); /** * @module DropdownPill * @description The pill component for the dropdown. * * @since 4.5.0 * * @param {object} props The component props. * @param {object} props.item The item object. * * @return {JSX.Element} The pill component. */ const DropdownPill = ( { item } ) => { const selectedItem = useStoreContext( ( state ) => state.selectedItem ); const setSelectedItem = useStoreContext( ( state ) => state.setSelectedItem ); const triggerRef = useStoreContext( ( state ) => state.triggerRef ); const pillRef = useRef( null ); const removeItem = () => { // Find index of removed item. const index = selectedItem.findIndex( ( selItem ) => selItem.value === item.value ); const length = selectedItem.length; // Remove item from selected items. setSelectedItem( selectedItem.filter( ( selItem ) => selItem.value !== item.value ) ); // 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(); } }; const pillProps = { content: item.label, 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(); }, }, tagName: 'div', onClick: removeItem, }; return <Pill { ...pillProps } ref={ pillRef } />; }; /** * @module DropdownPills * @description The pills component for the dropdown. * * @since 4.5.0 * * @param {object} props The component props. * @param {boolean} props.multi Whether the dropdown is multi-select. * @param {object} ref The ref object. * * @return {JSX.Element} The pills component. */ const DropdownPills = forwardRef( ( { multi = false, }, 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: 'gform-dropdown__pills', id: pillsId, role: 'listbox', }; return ( <div { ...pillsProps } ref={ ref }> { selectedItem.map( ( item, index ) => ( <DropdownPill key={ index } item={ item } /> ) ) } </div> ); } ); /** * @module DropdownPopover * @description The popover component for the dropdown. * * @since 4.5.0 * * @param {object} props The component props. * @param {JSX.Element} props.children The children of the component. * @param {Function} props.handleBlur The blur event handler. * @param {Function} props.handleEscKeyDown The escape key down event handler. * @param {Function} props.handleKeyDownCapture The key down capture event handler. * @param {Function} props.handleListKeyDown The list key down event handler. * @param {object} props.popoverAttributes The popover attributes. * @param {string|Array|object} props.popoverClasses The popover classes. * @param {number} props.popoverMaxHeight The popover max height. * @param {object} ref The ref object. * * @return {JSX.Element} The popover component. */ const DropdownPopover = forwardRef( ( { children = null, handleBlur = () => {}, handleEscKeyDown = () => {}, handleKeyDownCapture = () => {}, handleListKeyDown = () => {}, popoverAttributes = {}, popoverClasses = [], popoverMaxHeight = 0, }, ref ) => { const id = useIdContext(); const dropdownOpen = useStoreContext( ( state ) => state.open ); const popoverId = getId( id, 'popover' ); const popoverProps = { className: classnames( { 'gform-dropdown__popover': true, }, popoverClasses ), ...popoverAttributes, 'data-dialog': true, id: popoverId, onBlur: handleBlur, onKeyDown: ( event ) => { handleEscKeyDown( event ); handleListKeyDown( event ); }, onKeyDownCapture: ( event ) => handleKeyDownCapture( event ), role: 'dialog', tabIndex: '-1', }; if ( ! dropdownOpen ) { popoverProps.hidden = true; } if ( popoverMaxHeight ) { popoverProps.style = { maxHeight: `${ popoverMaxHeight }px`, }; } return ( <div className="gform-dropdown__popover-wrapper"> <div { ...popoverProps } ref={ ref }> { children } </div> </div> ); } ); /** * @module DropdownList * @description The list component for the dropdown. * * @since 4.5.0 * * @param {object} props The component props. * @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 {string} props.selectedIcon The selected icon. * @param {string} props.selectedIconPrefix The selected icon prefix. * @param {Function} props.selectItem The select item event handler. * @param {object} ref The ref object. * * @return {JSX.Element} The list component. */ const DropdownList = forwardRef( ( { handleBlur = () => {}, handleKeyDownCapture = () => {}, handleListKeyDown = () => {}, hasSearch = false, label = '', listAttributes = {}, listClasses = [], multi = false, popoverMaxHeight = 0, selectedIcon = 'check-mark-alt', selectedIconPrefix = 'gravity-component-icon', selectItem = () => {}, }, 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 groupProps = { className: 'gform-dropdown__list-group', 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 itemProps = { className: 'gform-dropdown__list-item', id: item.id, 'aria-selected': selected ? 'true' : 'false', tabIndex: '-1', role: 'option', onMouseMove: () => { setActiveItem( item ); }, onClick: selectItem( item ), onFocus: () => baseElRef?.current?.focus(), }; if ( 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', }; 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 ? popoverMaxHeight - 58 : popoverMaxHeight; if ( listMaxHeight > 0 ) { listProps.style = { maxHeight: `${ listMaxHeight }px`, }; } } return ( <div { ...listProps } ref={ ref }> { getListItems( listItems.items ) } </div> ); } ); /** * @module DropdownSearch * @description The search component for the dropdown. * * @since 4.5.0 * * @param {object} props The component props. * @param {boolean} props.hasSearch Whether the dropdown has a search component. * @param {object} props.searchAttributes The search component attributes. * @param {string|Array|object} props.searchClasses The search component classes. * @param {object} ref The ref object. * * @return {JSX.Element} The dropdown search component. */ const DropdownSearch = forwardRef( ( { hasSearch = false, searchAttributes = {}, searchClasses = [], }, ref ) => { const id = useIdContext(); const dropdownOpen = useStoreContext( ( state ) => state.dropdownOpen ); const activeItem = useStoreContext( ( state ) => state.activeItem ); const searchValue = useStoreContext( ( state ) => state.searchValue ); const setActiveItem = useStoreContext( ( state ) => state.setActiveItem ); const setSearchValue = useStoreContext( ( state ) => state.setSearchValue ); if ( ! hasSearch ) { return null; } const listId = getId( id, 'list' ); const searchId = getId( id, 'search' ); const { customAttributes: searchCustomAttributes = {}, wrapperClasses: searchWrapperClasses = [], ...restSearchAttributes } = searchAttributes; const searchProps = { customClasses: classnames( [ 'gform-dropdown__search', ], searchClasses ), ...restSearchAttributes, controlled: true, customAttributes: { ...searchCustomAttributes, autoComplete: 'off', role: 'combobox', 'aria-autocomplete': 'list', 'aria-haspopup': 'listbox', 'aria-controls': listId, 'aria-expanded': dropdownOpen ? 'true' : 'false', onMouseMove: () => { setActiveItem( getSearchItem( id ) ); }, ref, }, id: searchId, wrapperClasses: classnames( [ 'gform-dropdown__search-wrapper', ], searchWrapperClasses ), onChange: ( value ) => { setSearchValue( value ); }, value: searchValue, }; if ( activeItem.type === 'search' ) { searchProps.customAttributes[ 'data-active-item' ] = 'true'; } else { searchProps.customAttributes[ 'aria-activedescendant' ] = activeItem.id; } return <Input { ...searchProps } />; } ); /** * @module DropdownComponent * @description The dropdown component. * * @since 4.5.0 * * @param {object} props The component props. * @param {object} ref The ref object. * * @return {JSX.Element} The dropdown component. */ const DropdownComponent = forwardRef( ( props, ref ) => { const id = useIdContext(); const listItems = useStoreContext( ( state ) => state.listItems ); const dropdownOpen = useStoreContext( ( state ) => state.open ); const dropdownReveal = useStoreContext( ( state ) => state.reveal ); const dropdownHide = useStoreContext( ( state ) => state.hide ); const searchValue = useStoreContext( ( state ) => state.searchValue ); const selectedItem = useStoreContext( ( state ) => state.selectedItem ); const initialTriggerHeight = useStoreContext( ( state ) => state.initialTriggerHeight ); const setListItems = useStoreContext( ( state ) => state.setListItems ); const setActiveItem = useStoreContext( ( state ) => state.setActiveItem ); const setSelectedItem = useStoreContext( ( state ) => state.setSelectedItem ); const setTriggerHeight = useStoreContext( ( state ) => state.setTriggerHeight ); const setInitialTriggerHeight = useStoreContext( ( state ) => state.setInitialTriggerHeight ); const setBaseElRef = useStoreContext( ( state ) => state.setBaseElRef ); const listItemsMounted = useRef( false ); const triggerRef = useStoreContext( ( state ) => state.triggerRef ); const popoverRef = useStoreContext( ( state ) => state.popoverRef ); const listRef = useStoreContext( ( state ) => state.listRef ); const searchRef = useStoreContext( ( state ) => state.searchRef ); const baseElRef = useStoreContext( ( state ) => state.baseElRef ); const pillsRef = useStoreContext( ( state ) => state.pillsRef ); const { controlled = false, customAttributes = {}, customClasses = [], hasSearch = false, listItems: listItemsProp = [], multi = false, popoverMaxHeight = 0, popoverPosition = 'bottom', simplebar = true, size = 'r', spacing = '', width = 0, value = '', } = props; /* Set active item and list items state when id, list items, or search value changes. */ useEffect( () => { if ( ! listItemsMounted.current ) { listItemsMounted.current = true; return; } const newListItems = getListItemsState( filterListItems( listItemsProp, searchValue ), { hasSearch, id }, ); setActiveItem( newListItems.flatItems[ 0 ] ); setListItems( newListItems ); }, [ hasSearch, id, listItemsProp, searchValue, setActiveItem, setListItems ] ); /* Set selected item if controlled and value changes. */ useEffect( () => { if ( controlled ) { setSelectedItem( getSelectedItemFromValue( value, listItems.flatItems, multi ) ); } }, [ controlled, value, listItems.flatItems, setSelectedItem, multi ] ); /* Focus on base element when dropdown opens. */ useEffect( () => { if ( ! dropdownOpen ) { return; } baseElRef?.current?.focus(); }, [ dropdownOpen, baseElRef ] ); /* Set the base element when hasSearch changes. */ useEffect( () => { setBaseElRef( hasSearch ? searchRef : listRef ); }, [ hasSearch, listRef, searchRef, setBaseElRef ] ); /* Convert single to multi value and multi to single value when multi changes. */ useEffect( () => { const newSelectedItem = multi ? convertSingleToMultiItem( selectedItem ) : convertMultiToSingleItem( selectedItem, listItems.flatItems ); setSelectedItem( newSelectedItem ); }, [ listItems.flatItems, multi, selectedItem, setSelectedItem ] ); /* Set initial trigger height when size changes. */ useEffect( () => { if ( ! triggerRef.current ) { return; } setInitialTriggerHeight( triggerRef.current.offsetHeight ); }, [ size, triggerRef, setInitialTriggerHeight ] ); /* Set trigger height when selected item changes in multi. */ useEffect( () => { if ( ! multi ) { setTriggerHeight( 0 ); return; } if ( ! pillsRef.current ) { return; } if ( ! initialTriggerHeight ) { return; } const pillsHeight = pillsRef.current.offsetHeight; if ( pillsHeight <= initialTriggerHeight ) { setTriggerHeight( 0 ); return; } setTriggerHeight( pillsHeight ); }, [ multi, selectedItem, pillsRef, initialTriggerHeight, setTriggerHeight ] ); const dropdownProps = { className: classnames( { 'gform-dropdown': true, 'gform-dropdown--react': true, [ `gform-dropdown--popover-position-${ popoverPosition }` ]: true, [ `gform-dropdown--size-${ size }` ]: size, 'gform-dropdown--open': dropdownOpen, 'gform-dropdown--reveal': dropdownReveal, 'gform-dropdown--hide': dropdownHide, 'gform-dropdown--multi': multi, 'gform-dropdown--has-simplebar': simplebar, 'gform-dropdown--has-search': hasSearch, ...spacerClasses( spacing ), }, customClasses ), style: { width: width ? `${ width }px` : undefined, }, ...customAttributes, }; const listMaxHeight = hasSearch ? popoverMaxHeight - 58 : popoverMaxHeight; return ( <div { ...dropdownProps } ref={ ref }> <DropdownLabel { ...props } /> <div className="gform-dropdown__trigger-wrapper"> <DropdownTrigger { ...props } ref={ triggerRef } /> <DropdownPills { ...props } ref={ pillsRef } /> </div> <DropdownPopover { ...props } ref={ popoverRef }> <DropdownSearch { ...props } ref={ searchRef } /> <ConditionalWrapper condition={ simplebar && popoverMaxHeight > 0 } wrapper={ ( ch ) => <div className="gform-dropdown__popover-simplebar" style={ { height: `${ listMaxHeight }px` } } ><SimpleBar>{ ch }</SimpleBar></div> } > <DropdownList { ...props } ref={ listRef } /> </ConditionalWrapper> </DropdownPopover> </div> ); } ); const useDropdown = ( props ) => { const hooks = [ useDropdownControl, useDropdownBlur, useDropdownKeyDown, useDropdownTypeahead, ]; return hooks.reduce( ( carryProps, hook ) => hook( carryProps, useStoreContext ), props ); }; const StoreProviderWrapper = ( { children, controlled, hasSearch, initialValue, listItems: listItemsProp, multi, value, } ) => { const id = useIdContext(); const triggerRef = useRef(); const popoverRef = useRef(); const listRef = useRef(); const searchRef = useRef(); const pillsRef = useRef(); const listItems = getListItemsState( listItemsProp, { hasSearch, id } ); const firstItem = listItems.flatItems[ 0 ] || {}; let selectedItem = multi ? [] : firstItem; if ( controlled && value ) { selectedItem = getSelectedItemFromValue( value, listItems.flatItems, multi ); } else if ( initialValue ) { selectedItem = getSelectedItemFromValue( initialValue, listItems.flatItems, multi ); } const activeItem = firstItem; const storeProviderProps = { initialState: { listItems, selectedItem, activeItem, triggerRef, popoverRef, listRef, searchRef, baseElRef: hasSearch ? searchRef : listRef, pillsRef, }, createStore, }; return ( <StoreProvider { ...storeProviderProps }> { children } </StoreProvider> ); }; const DropdownWrapper = forwardRef( ( props, ref ) => { const componentProps = useDropdown( props ); return <DropdownComponent { ...componentProps } ref={ ref } />; } ); /** * @module Dropdown * @description Dropdown component with store and id wrapper. * * @since 4.5.0 * * @param {object} props Component props. * @param {boolean} props.controlled Whether the dropdown is controlled or not. * @param {object} props.customAttributes Custom attributes for the component. * @param {object} props.customClasses Custom classes for the component. * @param {boolean} props.disabled Whether the dropdown is disabled or not. * @param {boolean} props.hasSearch Whether the dropdown has search or not. * @param {object} props.i18n i18n strings. * @param {string} props.id The ID of the dropdown. * @param {string|Array|object} props.initialValue Initial value for the dropdown. * @param {string} props.label The label text. * @param {object} props.labelAttributes Custom attributes for the label. * @param {string|Array|object} props.labelClasses Custom classes for the label. * @param {object} props.listAttributes Custom attributes for the list. * @param {string|Array|object} props.listClasses Custom classes for the list. * @param {Array} props.listItems The list items for the dropdown. * @param {boolean} props.multi Whether the dropdown is a multi dropdown or not. * @param {Function} props.onAfterClose Callback for after the dropdown closes. * @param {Function} props.onAfterOpen Callback for after the dropdown opens. * @param {Function} props.onChange Callback for when the dropdown changes. * @param {Function} props.onClose Callback for when the dropdown closes. * @param {Function} props.onOpen Callback for when the dropdown opens. * @param {object} props.popoverAttributes Custom attributes for the popover. * @param {string|Array|object} props.popoverClasses Custom classes for the popover. * @param {number} props.popoverMaxHeight The maximum height of the popover. * @param {string} props.popoverPosition The position of the popover. * @param {object} props.searchAttributes Custom attributes for the search. * @param {string|Array|object} props.searchClasses Custom classes for the search. * @param {string} props.selectedIcon The icon for the selected state in multi dropdown. * @param {string} props.selectedIconPrefix The prefix for the icon library to be used in multi dropdown. * @param {boolean} props.simplebar Whether to use simplebar for the dropdown. * @param {string} props.size The size of the dropdown, one of `r`, `l`, `xl`. * @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object. * @param {object} props.triggerAttributes Custom attributes for the trigger. * @param {string|Array|object} props.triggerClasses Custom classes for the trigger. * @param {number} props.width The width of the dropdown. * @param {string|Array|object} props.value The value of the dropdown. Only works in controlled mode. * @param {Function} ref The ref to the dropdown component. * * @return {JSX.Element} The dropdown component. */ const Dropdown = forwardRef( ( props, ref ) => { const defaultProps = { controlled: false, customAttributes: {}, customClasses: [], disabled: false, hasSearch: false, i18n: {}, id: '', initialValue: '', label: '', labelAttributes: {}, labelClasses: [], listAttributes: {}, listClasses: [], listItems: [], multi: false, onAfterClose: () => {}, onAfterOpen: () => {}, onChange: () => {}, onClose: () => {}, onOpen: () => {}, popoverAttributes: {}, popoverClasses: [], popoverMaxHeight: 0, popoverPosition: 'bottom', searchAttributes: {}, searchClasses: [], selectedIcon: 'check-mark-alt', selectedIconPrefix: 'gravity-component-icon', simplebar: true, size: 'r', spacing: '', triggerAttributes: {}, triggerClasses: [], value: '', width: 0, }; const combinedProps = { ...defaultProps, ...props }; const { id: idProp } = combinedProps; const idProviderProps = { id: idProp }; return ( <IdProvider { ...idProviderProps }> <StoreProviderWrapper { ...combinedProps }> <DropdownWrapper { ...combinedProps } ref={ ref } /> </StoreProviderWrapper> </IdProvider> ); } ); Dropdown.propTypes = { controlled: PropTypes.bool, customAttributes: PropTypes.object, customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), disabled: PropTypes.bool, hasSearch: PropTypes.bool, i18n: PropTypes.object, id: PropTypes.string, label: PropTypes.string, labelAttributes: PropTypes.object, labelClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), listAttributes: PropTypes.object, listClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), listItems: PropTypes.array, multi: PropTypes.bool, onAfterClose: PropTypes.func, onAfterOpen: PropTypes.func, onClose: PropTypes.func, onOpen: PropTypes.func, popoverAttributes: PropTypes.object, popoverClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), popoverMaxHeight: PropTypes.number, popoverPosition: PropTypes.oneOf( [ 'top', 'bottom' ] ), searchAttributes: PropTypes.object, searchClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), selectedIcon: PropTypes.string, selectedIconPrefix: PropTypes.string, simplebar: PropTypes.bool, size: PropTypes.oneOf( [ 'r', 'l', 'xl' ] ), spacing: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object, ] ), triggerAttributes: PropTypes.object, triggerClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), width: PropTypes.number, }; export default Dropdown;