@plone/volto
Version:
Volto
196 lines (176 loc) • 4.81 kB
JSX
/**
* 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);