UNPKG

@plone/volto

Version:
196 lines (176 loc) 4.81 kB
/** * TokenWidget component. * @module components/manage/Widgets/TokenWidget */ import React, { useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useSelector, useDispatch } from 'react-redux'; import { defineMessages, useIntl } from 'react-intl'; import { getVocabFromHint, getVocabFromField, getVocabFromItems, } from '@plone/volto/helpers/Vocabularies/Vocabularies'; import { getVocabulary } from '@plone/volto/actions/vocabularies/vocabularies'; import { Option, DropdownIndicator, ClearIndicator, MultiValueContainer, selectTheme, customSelectStyles, } from '@plone/volto/components/manage/Widgets/SelectStyling'; import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; const messages = defineMessages({ select: { id: 'Select…', defaultMessage: 'Select…', }, no_options: { id: 'No options', defaultMessage: 'No options', }, }); /** * TokenWidget component class. * * Because new terms are created through the web by using the widget, the token * widget conflates the meaning of token, label and value and assumes they can * be used interchangeably. * * @class TokenWidget * @extends Component */ const TokenWidget = (props) => { const { id, onChange, value, placeholder, reactSelectCreateable, isDisabled, fieldSet, } = props; const intl = useIntl(); const dispatch = useDispatch(); const vocabBaseUrl = getVocabFromHint(props) || getVocabFromField(props) || getVocabFromItems(props); const lang = useSelector((state) => state.intl.locale); const vocabState = useSelector((state) => { if (!vocabBaseUrl) return null; return state.vocabularies?.[vocabBaseUrl]?.subrequests?.[lang]; }); const choices = useMemo(() => { if (vocabState?.items) { return vocabState.items.map((item) => ({ label: item.label || item.value, value: item.value, })); } return []; }, [vocabState]); const vocabLoading = vocabState?.loading; const vocabLoaded = vocabState?.loaded; useEffect(() => { if ( !choices?.length && vocabLoading === undefined && !vocabLoaded && vocabBaseUrl ) { dispatch( getVocabulary({ vocabNameOrURL: vocabBaseUrl, size: -1, subrequest: lang, }), ); } }, [ choices?.length, vocabLoading, vocabLoaded, vocabBaseUrl, lang, dispatch, ]); const handleChange = (selectedOption) => { onChange( id, selectedOption ? selectedOption.map((item) => item.label) : null, ); }; const selectedOption = value ? value.map((item) => ({ label: item, value: item })) : []; const defaultOptions = (choices || []) .filter((item) => !selectedOption.find(({ label }) => label === item.label)) .map((item) => ({ label: item.label || item.value, value: item.value, })); const CreatableSelect = reactSelectCreateable.default; return ( <FormFieldWrapper {...props}> <CreatableSelect id={`field-${id}`} aria-labelledby={`fieldset-${fieldSet}-field-label-${id}`} key={id} menuShouldScrollIntoView={false} isDisabled={isDisabled} className="react-select-container" classNamePrefix="react-select" defaultOptions={defaultOptions} options={defaultOptions} styles={customSelectStyles} theme={selectTheme} components={{ MultiValueContainer, ClearIndicator, DropdownIndicator, Option, }} isMulti value={selectedOption || []} onChange={handleChange} placeholder={placeholder ?? intl.formatMessage(messages.select)} noOptionsMessage={() => intl.formatMessage(messages.no_options)} /> </FormFieldWrapper> ); }; TokenWidget.propTypes = { id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, description: PropTypes.string, required: PropTypes.bool, error: PropTypes.arrayOf(PropTypes.string), choices: PropTypes.arrayOf(PropTypes.object), items: PropTypes.shape({ vocabulary: PropTypes.object, }), widgetOptions: PropTypes.shape({ vocabulary: PropTypes.object, }), value: PropTypes.arrayOf(PropTypes.string), onChange: PropTypes.func.isRequired, wrapped: PropTypes.bool, placeholder: PropTypes.string, }; TokenWidget.defaultProps = { description: null, required: false, items: { vocabulary: null, }, widgetOptions: { vocabulary: null, }, error: [], choices: [], value: null, }; export default injectLazyLibs(['reactSelectCreateable'])(TokenWidget);