diff --git a/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.js b/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.js index 69cfbcb4f..c03e8a530 100644 --- a/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.js +++ b/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import Input from '../Input/Input'; @@ -7,7 +7,6 @@ import { Container, Dropdown, DropdownList, - DropdownItem, DropdownItemSelectable, TextItalic } from './TextInputWithDropdown.style'; @@ -37,16 +36,44 @@ const TextInputWithDropdown = React.forwardRef( label, dropdownInstruction = null, className = '', + hideBorder = false, ...otherInputProps }, - ref + forwardedRef ) => { const [activeOption, setActiveOption] = useState(-1); const [forceClosed, setForceClosed] = useState(false); + const dropdownRef = useRef(null); + const containerRef = useRef(null); + useEffect(() => { - // reset if options change + const handleClickOutside = event => { + if (dropdownRef.current + && !dropdownRef.current.contains(event.target) + && !containerRef.current.contains(event.target)) { + setForceClosed(true); + } + }; + + // Only add the listeners if we have options showing + if (options.length > 0 && !forceClosed) { + ['mousedown', 'touchstart'].forEach(event => document.addEventListener(event, handleClickOutside)); + } + + return () => { + ['mousedown', 'touchstart'].forEach(event => document.removeEventListener(event, handleClickOutside)); + }; + }, [options.length, forceClosed, onChange]); + + const closeDropdown = () => { + setForceClosed(true); setActiveOption(-1); + }; + + // Reset forceClosed when options change + useEffect(() => { setForceClosed(false); + setActiveOption(-1); }, [options]); const down = () => (activeOption < options.length - 1 @@ -89,6 +116,7 @@ const TextInputWithDropdown = React.forwardRef( onSelect, dropdownInstruction, activeOption, + closeDropdown, resetActiveOption: () => setActiveOption(-1) }; @@ -96,16 +124,19 @@ const TextInputWithDropdown = React.forwardRef( - {options.length > 0 && forceClosed === false && ( + {options.length > 0 && !forceClosed && ( )} @@ -113,14 +144,15 @@ const TextInputWithDropdown = React.forwardRef( } ); -const Options = ({ +const Options = React.forwardRef(({ options, dropdownInstruction, onSelect, activeOption, + closeDropdown, resetActiveOption, ...rest -}) => { +}, ref) => { // Reset 'activeOption' when the list is unfocused. const onBlur = e => { const { target } = e; @@ -136,6 +168,7 @@ const Options = ({ return ( {dropdownInstruction && ( - - {dropdownInstruction} - + + + {dropdownInstruction} + + )} {options.map((option, index) => ( ); -}; +}); TextInputWithDropdown.propTypes = { options: PropTypes.arrayOf(PropTypes.string).isRequired, @@ -183,7 +223,8 @@ TextInputWithDropdown.propTypes = { name: PropTypes.string.isRequired, label: PropTypes.string.isRequired, className: PropTypes.string, - dropdownInstruction: PropTypes.string + dropdownInstruction: PropTypes.string, + hideBorder: PropTypes.bool }; Options.propTypes = { @@ -191,7 +232,11 @@ Options.propTypes = { onSelect: PropTypes.func.isRequired, dropdownInstruction: PropTypes.string, activeOption: PropTypes.number.isRequired, - resetActiveOption: PropTypes.func.isRequired + resetActiveOption: PropTypes.func.isRequired, + hideBorder: PropTypes.bool, + closeDropdown: PropTypes.func }; +TextInputWithDropdown.displayName = 'TextInputWithDropdown'; + export default TextInputWithDropdown; diff --git a/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.style.js b/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.style.js index 92cd0614f..d2e4ad0bf 100644 --- a/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.style.js +++ b/src/components/Atoms/TextInputWithDropdown/TextInputWithDropdown.style.js @@ -16,7 +16,7 @@ const Dropdown = styled.div` max-height: 300px; overflow: auto; background-color: ${({ theme }) => theme.color('white')}; - border: 1px solid; + border: ${({ hideBorder }) => (hideBorder ? 'none' : '1px solid')}; margin-top: -1px; width: 100%; diff --git a/src/components/Molecules/SchoolLookup/SchoolLookup.js b/src/components/Molecules/SchoolLookup/SchoolLookup.js index a2164b80e..192371d5f 100644 --- a/src/components/Molecules/SchoolLookup/SchoolLookup.js +++ b/src/components/Molecules/SchoolLookup/SchoolLookup.js @@ -25,6 +25,7 @@ const SchoolLookup = React.forwardRef( dropdownInstruction = 'Please select a school from the list below', notFoundMessage = "Sorry, we can't find this school", onSelect, + hideBorder = false, ...rest }, ref @@ -39,6 +40,7 @@ const SchoolLookup = React.forwardRef( placeholder, notFoundMessage, dropdownInstruction, + hideBorder, ...rest }; @@ -52,7 +54,10 @@ SchoolLookup.propTypes = { label: PropTypes.string, placeholder: PropTypes.string, dropdownInstruction: PropTypes.string, - notFoundMessage: PropTypes.string + notFoundMessage: PropTypes.string, + hideBorder: PropTypes.bool }; +SchoolLookup.displayName = 'SchoolLookup'; + export default SchoolLookup;