From 2646efd0a3601bf33e05feb8cdcd54b8cc8aefc4 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Tue, 17 Sep 2024 11:03:45 +0200 Subject: [PATCH 01/12] :sparkles: [#4524] Using react-select for the VariableSelection Marked SelectWithoutFormik as deprecated --- .../admin/form_design/variables/constants.js | 7 ++ .../js/components/admin/forms/ReactSelect.js | 79 +++++++++++++++++-- .../admin/forms/VariableSelection.js | 56 +++++++++---- src/openforms/scss/_vars.scss | 1 + .../scss/components/admin/_select.scss | 32 ++++++++ src/openforms/scss/vendor/_react-select.scss | 6 +- 6 files changed, 159 insertions(+), 22 deletions(-) diff --git a/src/openforms/js/components/admin/form_design/variables/constants.js b/src/openforms/js/components/admin/form_design/variables/constants.js index f0dfb98150..9d8b8b75dd 100644 --- a/src/openforms/js/components/admin/form_design/variables/constants.js +++ b/src/openforms/js/components/admin/form_design/variables/constants.js @@ -85,6 +85,12 @@ const VARIABLE_SOURCES = { userDefined: 'user_defined', }; +const VARIABLE_SOURCES_GROUP_LABELS = { + userDefined: 'user variables', + component: 'component variables', + static: 'static variables', +}; + const EMPTY_VARIABLE = { name: '', key: '', @@ -113,6 +119,7 @@ const IDENTIFIER_ROLE_CHOICES = { export { COMPONENT_DATATYPES, VARIABLE_SOURCES, + VARIABLE_SOURCES_GROUP_LABELS, DATATYPES_CHOICES, EMPTY_VARIABLE, IDENTIFIER_ROLE_CHOICES, diff --git a/src/openforms/js/components/admin/forms/ReactSelect.js b/src/openforms/js/components/admin/forms/ReactSelect.js index 92f06a7174..e3a20d16f7 100644 --- a/src/openforms/js/components/admin/forms/ReactSelect.js +++ b/src/openforms/js/components/admin/forms/ReactSelect.js @@ -1,4 +1,5 @@ import {getReactSelectStyles} from '@open-formulieren/formio-builder/esm/components/formio/select'; +import classNames from 'classnames'; import {useField} from 'formik'; import PropTypes from 'prop-types'; import ReactSelect from 'react-select'; @@ -31,25 +32,84 @@ const styles = { }), }; +const getValueRecursively = (options, value) => { + if (!value) { + return null; + } + + const option = options.find(opt => opt.value === value); + if (option) { + return option; + } + + // Search for the option recursively + for (let i = 0; i < options.length; i++) { + const opt = options[i]; + if (!opt.options) { + continue; + } + + const option = getValueRecursively(opt.options, value); + if (option) { + return option; + } + } + + return null; +}; + +/** + * A select dropdown backed by react-select for legacy usage. + * + * Any additional props are forwarded to the underlying ReactSelect component. + * + * @deprecated - if possible, refactor the form to use Formik and use the Formik-enabled + * variant. + */ +const SelectWithoutFormik = ({name, options, value, className, onChange, ...props}) => { + const classes = classNames('admin-react-select', { + [`${className}`]: className, + }); + return ( + { + onChange(selectedOption === null ? undefined : selectedOption.value); + }} + {...props} + /> + ); +}; + /** * A select dropdown backed by react-select for Formik forms. * - * Any additional props are forwarded to the underlyng ReactSelect component. + * Any additional props are forwarded to the underlying ReactSelect component. */ -const Select = ({name, options, ...props}) => { +const SelectWithFormik = ({name, options, className, ...props}) => { const [fieldProps, , fieldHelpers] = useField(name); const {value} = fieldProps; const {setValue} = fieldHelpers; + const classes = classNames('admin-react-select', { + [`${className}`]: className, + }); return ( opt.value === value) || null} + value={getValueRecursively(options, value)} onChange={selectedOption => { // clear the value if (selectedOption == null) { @@ -63,9 +123,16 @@ const Select = ({name, options, ...props}) => { ); }; -Select.propTypes = { +SelectWithoutFormik.propTypes = { + name: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + options: PropTypes.arrayOf(PropTypes.object), +}; + +SelectWithFormik.propTypes = { name: PropTypes.string.isRequired, options: PropTypes.arrayOf(PropTypes.object), }; -export default Select; +export default SelectWithFormik; +export {SelectWithFormik, SelectWithoutFormik}; diff --git a/src/openforms/js/components/admin/forms/VariableSelection.js b/src/openforms/js/components/admin/forms/VariableSelection.js index 1d8c8264ae..38c086f06f 100644 --- a/src/openforms/js/components/admin/forms/VariableSelection.js +++ b/src/openforms/js/components/admin/forms/VariableSelection.js @@ -3,7 +3,8 @@ import React, {useContext} from 'react'; import {FormContext} from 'components/admin/form_design/Context'; -import Select from './Select'; +import {VARIABLE_SOURCES, VARIABLE_SOURCES_GROUP_LABELS} from '../form_design/variables/constants'; +import {SelectWithoutFormik} from './ReactSelect'; const allowAny = () => true; @@ -24,26 +25,51 @@ const VariableSelection = ({ }); const allFormVariables = (includeStaticVariables ? staticVariables : []).concat(formVariables); + + const getVariableSource = variable => { + if (variable.source === VARIABLE_SOURCES.userDefined) { + return VARIABLE_SOURCES_GROUP_LABELS.userDefined; + } + if (variable.source === VARIABLE_SOURCES.component) { + return VARIABLE_SOURCES_GROUP_LABELS.component; + } + return VARIABLE_SOURCES_GROUP_LABELS.static; + }; + const choices = allFormVariables .filter(variable => filter(variable)) - .map(variable => { - const label = formDefinitionsNames[variable.formDefinition] - ? `${formDefinitionsNames[variable.formDefinition]}: ${variable.name} (${variable.key})` - : `${variable.name} (${variable.key})`; - return [variable.key, label]; - }); - - { - /*TODO: This should be a searchable select for when there are a billion variables -> react-select */ - } + .reduce( + (variableGroups, variable) => { + let label = `${variable.name} (${variable.key})`; + if (formDefinitionsNames[variable.formDefinition]) { + label += `${formDefinitionsNames[variable.formDefinition]}`; + } + + variableGroups + .find(group => group.label === getVariableSource(variable)) + .options.push({value: variable.key, label}); + return variableGroups; + }, + [ + {label: VARIABLE_SOURCES_GROUP_LABELS.userDefined, options: []}, + {label: VARIABLE_SOURCES_GROUP_LABELS.component, options: []}, + {label: VARIABLE_SOURCES_GROUP_LABELS.static, options: []}, + ] + ); + return ( -