UNPKG

@gravityforms/components

Version:

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

246 lines (229 loc) 8.3 kB
import { React, PropTypes, classnames } from '@gravityforms/libraries'; import Label from '../Label'; import HelpText from '../HelpText'; import { uniqueId, isObject, isEmptyObject, slugify, spacerClasses } from '@gravityforms/utils'; import { useStateWithDep } from '@gravityforms/react-utils'; const { useState, forwardRef } = React; /** * @module Select * @description A select component to display a list of items. * * @since 1.1.15 * * @param {object} props Component props. * @param {string} props.ariaLabel The aria-label text for the select element. * @param {boolean} props.controlled Whether the select is controlled or not. * @param {object} props.customAttributes Custom attributes for the component. * @param {string|Array|object} props.customClasses Custom classes for the component. * @param {boolean} props.disabled If select is disabled. * @param {string} props.helpTextAttributes Custom attribute for the help text. * @param {string} props.helpTextPosition The position of the help text. Above or below. * @param {string} props.id Optional id. Auto generated if not passed. * @param {string} props.initialValue Initial value for the select. * @param {string} props.labelAttributes Any custom attributes for the label. * @param {string} props.name The name attribute for the select. * @param {Function} props.onBlur On blur function handler. * @param {Function} props.onChange On change function handler. * @param {Function} props.onFocus On focus function handler. * @param {Array} props.options An array of options, each option as an object in the following structure: * { * customOptionAttrs: {}, // Key-value pairs of custom attributes. * customOptionClasses: [], // Array of strings of custom classes. * label: '', // Label as a string. * value: '', // Value as a string. * } * @param {string} props.size The select size. Regular (`size-r`), large (`size-l`), or extra large (`size-xl`). * @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object. * @param {string} props.theme The theme of the select. * @param {object} props.wrapperAttributes Custom attributes for the wrapper element. * @param {string|Array|object} props.wrapperClasses Custom classes for the wrapper element. * @param {object} props.wrapperTagName Tag to use for the textarea wrapper. Defaults to `div`. * @param {object|null} ref Ref to the component. * * @return {JSX.Element} The select component. * * @example * import Select from '@gravityforms/components/react/admin/elements/Select'; * * return ( * <Select * name="select-name" * onChange={ () => {} } * options={ [ * { * label: 'Option 1', * value: 'option-1', * }, * { * label: 'Option 2', * value: 'option-2', * }, * { * label: 'Option 3', * value: 'option-3', * }, * ] } * /> * ); * */ const Select = forwardRef( ( { ariaLabel = '', controlled = false, customAttributes = {}, customClasses = [], disabled = false, helpTextAttributes = {}, helpTextPosition = 'below', id = '', initialValue = '', labelAttributes = {}, name = '', onBlur = () => {}, onChange = () => {}, onFocus = () => {}, options = [], size = 'size-r', spacing = '', theme = 'cosmos', wrapperAttributes = {}, wrapperClasses = [], wrapperTagName = 'div', }, ref ) => { const [ stateSelectValue, setStateSelectValue ] = useState( initialValue ); const [ controlSelectValue, setControlSelectValue ] = useStateWithDep( initialValue ); const selectValue = controlled ? controlSelectValue : stateSelectValue; const inputId = id || uniqueId( 'gform-select' ); const helpTextId = `${ inputId }-help-text`; const wrapperProps = { ...wrapperAttributes, className: classnames( { 'gform-input-wrapper': true, [ `gform-input-wrapper--theme-${ theme }` ]: true, 'gform-input-wrapper--select': true, 'gform-input-wrapper--disabled': disabled, [ `gform-input-wrapper--${ size }` ]: true, ...spacerClasses( spacing ), }, wrapperClasses ), ref, }; const selectProps = { ...customAttributes, className: classnames( [ 'gform-select', ], customClasses ), disabled: disabled || labelAttributes?.locked === true, id: inputId, name, onBlur, onChange: ( e ) => { const { value: newSelectValue } = e.target; setStateSelectValue( newSelectValue ); setControlSelectValue( newSelectValue ); onChange( newSelectValue, e ); }, onFocus, value: selectValue, }; if ( helpTextAttributes.content ) { selectProps[ 'aria-describedby' ] = helpTextId; } if ( ariaLabel ) { selectProps[ 'aria-label' ] = ariaLabel; } const labelProps = { ...labelAttributes, htmlFor: inputId, }; const helpTextProps = { ...helpTextAttributes, id: helpTextId, }; const getSubOptions = ( choices = [] ) => { if ( isObject( choices ) && ! isEmptyObject( choices ) ) { return Object.entries( choices ).map( ( [ key ] ) => choices[ key ] ); } return choices; }; const Container = wrapperTagName; const getOption = ( ( { customOptionAttributes = {}, customOptionClasses = [], label = '', value = '', }, index ) => { return ( <option className={ classnames( [ 'gform-select__option', ], customOptionClasses ) } key={ `${ slugify( label ) }-${ index }` } value={ value } { ...customOptionAttributes } > { label } </option> ); } ); const getOptions = options.map( ( data, index ) => { const subOptions = getSubOptions( data.choices ); return subOptions.length ? ( <optgroup label={ data.label } key={ `${ slugify( data.label ) }-${ index }` }> { subOptions.map( ( subData, subIndex ) => getOption( subData, subIndex ) ) } </optgroup> ) : getOption( data, index ); } ); return ( <Container { ...wrapperProps }> <Label { ...labelProps } /> { helpTextPosition === 'above' && <HelpText { ...helpTextProps } /> } <div className="gform-select__wrapper"> <select { ...selectProps }> { getOptions } </select> </div> { helpTextPosition === 'below' && <HelpText { ...helpTextProps } /> } </Container> ); } ); Select.propTypes = { ariaLabel: PropTypes.string, controlled: PropTypes.bool, customAttributes: PropTypes.object, customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), disabled: PropTypes.bool, helpTextAttributes: PropTypes.object, helpTextPosition: PropTypes.string, id: PropTypes.string, initialValue: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, ] ), labelAttributes: PropTypes.object, name: PropTypes.string, onBlur: PropTypes.func, onChange: PropTypes.func, onFocus: PropTypes.func, options: PropTypes.array, size: PropTypes.string, spacing: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object, ] ), theme: PropTypes.string, wrapperAttributes: PropTypes.object, wrapperClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), wrapperTagName: PropTypes.string, }; Select.displayName = 'Select'; export default Select;