diff --git a/app/javascript/components/ComboMultipleDropdownList.js b/app/javascript/components/ComboMultipleDropdownList.js new file mode 100644 index 000000000..4665fe2a6 --- /dev/null +++ b/app/javascript/components/ComboMultipleDropdownList.js @@ -0,0 +1,217 @@ +import React, { + useMemo, + useState, + useRef, + useContext, + createContext, + useEffect, + useLayoutEffect +} from 'react'; +import PropTypes from 'prop-types'; +import { + Combobox, + ComboboxInput, + ComboboxList, + ComboboxOption, + ComboboxPopover +} from '@reach/combobox'; +import '@reach/combobox/styles.css'; +import matchSorter from 'match-sorter'; +import { fire } from '@utils'; + +const Context = createContext(); + +function ComboMultipleDropdownList({ + options, + hiddenFieldId, + selected, + label +}) { + if (label == undefined) { + label = 'Choisir une option'; + } + if (Array.isArray(options[0]) == false) { + options = options.map((o) => [o, o]); + } + const inputRef = useRef(); + const [term, setTerm] = useState(''); + const [selections, setSelections] = useState(selected); + const results = useMemo( + () => + (term + ? matchSorter( + options.filter((o) => !o[0].startsWith('--')), + term + ) + : options + ).filter((o) => o[0] && !selections.includes(o[1])), + [term, selections] + ); + const hiddenField = useMemo( + () => document.querySelector(`input[data-uuid="${hiddenFieldId}"]`), + [hiddenFieldId] + ); + + const handleChange = (event) => { + setTerm(event.target.value); + }; + + const saveSelection = (selections) => { + setSelections(selections); + if (hiddenField) { + hiddenField.setAttribute('value', JSON.stringify(selections)); + fire(hiddenField, 'autosave:trigger'); + } + }; + + const onSelect = (value) => { + let sel = options.find((o) => o[0] == value)[1]; + saveSelection([...selections, sel]); + setTerm(''); + }; + + const onRemove = (value) => { + saveSelection( + selections.filter((s) => s !== options.find((o) => o[0] == value)[1]) + ); + inputRef.current.focus(); + }; + + return ( + + + + + + {results && ( + + {results.length === 0 && ( +

+ Aucun résultat{' '} + +

+ )} + + {results.map((value, index) => { + if (value[0].startsWith('--')) { + return ; + } + return ; + })} + +
+ )} +
+ ); +} + +function ComboboxTokenLabel({ onRemove, ...props }) { + const selectionsRef = useRef([]); + + useLayoutEffect(() => { + selectionsRef.current = []; + return () => (selectionsRef.current = []); + }); + + const context = { + onRemove, + selectionsRef + }; + + return ( + +
+ + ); +} + +ComboboxTokenLabel.propTypes = { + onRemove: PropTypes.func +}; + +function ComboboxSeparator({ value }) { + return ( +
  • + {value.slice(2, -2)} +
  • + ); +} + +ComboboxSeparator.propTypes = { + value: PropTypes.string +}; + +function ComboboxToken({ value, ...props }) { + const { selectionsRef, onRemove } = useContext(Context); + useEffect(() => { + selectionsRef.current.push(value); + }); + + return ( +
  • { + if (event.key === 'Backspace') { + onRemove(value); + } + }} + {...props} + > + { + onRemove(value); + }} + > + x + + {value} +
  • + ); +} + +ComboboxToken.propTypes = { + value: PropTypes.string, + label: PropTypes.string +}; + +ComboMultipleDropdownList.propTypes = { + options: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.arrayOf( + PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + ) + ) + ]), + hiddenFieldId: PropTypes.string, + selected: PropTypes.arrayOf(PropTypes.string), + arraySelected: PropTypes.arrayOf(PropTypes.array), + label: PropTypes.string +}; + +export default ComboMultipleDropdownList; diff --git a/app/javascript/loaders/ComboMultipleDropdownList.js b/app/javascript/loaders/ComboMultipleDropdownList.js new file mode 100644 index 000000000..db778b339 --- /dev/null +++ b/app/javascript/loaders/ComboMultipleDropdownList.js @@ -0,0 +1,5 @@ +import Loadable from '../components/Loadable'; + +export default Loadable(() => + import('../components/ComboMultipleDropdownList') +);