diff --git a/packages/react-core/src/demos/SearchInput/SearchInput.md b/packages/react-core/src/demos/SearchInput/SearchInput.md index e7570ab47e5..764517f576b 100644 --- a/packages/react-core/src/demos/SearchInput/SearchInput.md +++ b/packages/react-core/src/demos/SearchInput/SearchInput.md @@ -3,24 +3,25 @@ id: Search input section: components beta: true --- -import { - Button, - Card, - CardBody, - CardFooter, - DatePicker, - Form, - FormGroup, - Grid, - GridItem, - Menu, - MenuContent, - MenuItem, - MenuList, - MenuToggle, - Popper, - SearchInput, - TextInput + +import { +Button, +Card, +CardBody, +CardFooter, +DatePicker, +Form, +FormGroup, +Grid, +GridItem, +Menu, +MenuContent, +MenuItem, +MenuList, +MenuToggle, +Popper, +SearchInput, +TextInput } from '@patternfly/react-core'; import { words } from './words.js'; @@ -31,17 +32,9 @@ import { words } from './words.js'; This demo handles building the advanced search form using the composable Menu, and the `SearchInput`'s `hint` prop. It also demonstrates wiring up the appropriate keyboard interactions, focus management, and general event handling. - ```js import React from 'react'; -import { - Menu, - MenuContent, - MenuItem, - MenuList, - Popper, - SearchInput -} from '@patternfly/react-core'; +import { Menu, MenuContent, MenuItem, MenuList, Popper, SearchInput } from '@patternfly/react-core'; import { words } from './words.js'; @@ -49,32 +42,52 @@ SearchAutocomplete = () => { const [value, setValue] = React.useState(''); const [hint, setHint] = React.useState(''); const [autocompleteOptions, setAutocompleteOptions] = React.useState([]); - + const [isAutocompleteOpen, setIsAutocompleteOpen] = React.useState(false); - + const searchInputRef = React.useRef(null); const autocompleteRef = React.useRef(null); - + const onClear = () => { setValue(''); }; - - const onChange = (newValue) => { - if (newValue !== '' && searchInputRef && searchInputRef.current && searchInputRef.current.contains(document.activeElement)) { + + const onChange = (_event, newValue) => { + if ( + newValue !== '' && + searchInputRef && + searchInputRef.current && + searchInputRef.current.contains(document.activeElement) + ) { setIsAutocompleteOpen(true); - + // When the value of the search input changes, build a list of no more than 10 autocomplete options. // Options which start with the search input value are listed first, followed by options which contain // the search input value. - let options = words.filter((option) => option.startsWith(newValue.toLowerCase())).map((option) => {option}); + let options = words + .filter((option) => option.startsWith(newValue.toLowerCase())) + .map((option) => ( + + {option} + + )); if (options.length > 10) { - options = options.slice(0,10); + options = options.slice(0, 10); } else { - options = [...options, ...words.filter((option) => !option.startsWith(newValue.toLowerCase()) && option.includes(newValue.toLowerCase())).map((option) => {option})].slice(0, 10) + options = [ + ...options, + ...words + .filter((option) => !option.startsWith(newValue.toLowerCase()) && option.includes(newValue.toLowerCase())) + .map((option) => ( + + {option} + + )) + ].slice(0, 10); } - + // The hint is set whenever there is only one autocomplete option left. - setHint(options.length === 1? options[0].props.itemId : ''); + setHint(options.length === 1 ? options[0].props.itemId : ''); // The menu is hidden if there are no options setIsAutocompleteOpen(options.length > 0); setAutocompleteOptions(options); @@ -83,7 +96,7 @@ SearchAutocomplete = () => { } setValue(newValue); }; - + // Whenever an autocomplete option is selected, set the search input value, close the menu, and put the browser // focus back on the search input const onSelect = (e, itemId) => { @@ -92,53 +105,57 @@ SearchAutocomplete = () => { setIsAutocompleteOpen(false); searchInputRef.current.focus(); }; - - const handleMenuKeys = event => { - // If there is a hint while the browser focus is on the search input, tab or right arrow will 'accept' the hint value + + const handleMenuKeys = (event) => { + // If there is a hint while the browser focus is on the search input, tab or right arrow will 'accept' the hint value // and set it as the search input value if (hint && (event.key === 'Tab' || event.key === 'ArrowRight') && searchInputRef.current === event.target) { setValue(hint); setHint(''); setIsAutocompleteOpen(false); if (event.key === 'ArrowRight') { - event.preventDefault(); + event.preventDefault(); } - // if the autocomplete is open and the browser focus is on the search input, + // if the autocomplete is open and the browser focus is on the search input, } else if (isAutocompleteOpen && searchInputRef.current && searchInputRef.current === event.target) { // the escape key closes the autocomplete menu and keeps the focus on the search input. if (event.key === 'Escape') { setIsAutocompleteOpen(false); searchInputRef.current.focus(); - // the up and down arrow keys move browser focus into the autocomplete menu + // the up and down arrow keys move browser focus into the autocomplete menu } else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { const firstElement = autocompleteRef.current.querySelector('li > button:not(:disabled)'); firstElement && firstElement.focus(); event.preventDefault(); // by default, the up and down arrow keys scroll the window - // the tab, enter, and space keys will close the menu, and the tab key will move browser - // focus forward one element (by default) - } else if (event.key === 'Tab'|| event.key === "Enter" || event.key === 'Space'){ + // the tab, enter, and space keys will close the menu, and the tab key will move browser + // focus forward one element (by default) + } else if (event.key === 'Tab' || event.key === 'Enter' || event.key === 'Space') { setIsAutocompleteOpen(false); - if (event.key === "Enter" || event.key === 'Space') { + if (event.key === 'Enter' || event.key === 'Space') { event.preventDefault(); } } - // If the autocomplete is open and the browser focus is in the autocomplete menu - // hitting tab will close the autocomplete and but browser focus back on the search input. - } else if (isAutocompleteOpen && autocompleteRef.current.contains(event.target) && (event.key === 'Tab')) { + // If the autocomplete is open and the browser focus is in the autocomplete menu + // hitting tab will close the autocomplete and but browser focus back on the search input. + } else if (isAutocompleteOpen && autocompleteRef.current.contains(event.target) && event.key === 'Tab') { event.preventDefault(); setIsAutocompleteOpen(false); searchInputRef.current.focus(); } - }; - + // The autocomplete menu should close if the user clicks outside the menu. - const handleClickOutside = event => { - if (isAutocompleteOpen && autocompleteRef && autocompleteRef.current && !autocompleteRef.current.contains(event.target)) { + const handleClickOutside = (event) => { + if ( + isAutocompleteOpen && + autocompleteRef && + autocompleteRef.current && + !autocompleteRef.current.contains(event.target) + ) { setIsAutocompleteOpen(false); } }; - + React.useEffect(() => { window.addEventListener('keydown', handleMenuKeys); window.addEventListener('click', handleClickOutside); @@ -147,7 +164,7 @@ SearchAutocomplete = () => { window.removeEventListener('click', handleClickOutside); }; }, [isAutocompleteOpen, hint, searchInputRef.current]); - + const searchInput = ( { id="autocomplete-search" /> ); - + const autocomplete = ( - - {autocompleteOptions} - + {autocompleteOptions} ); - return ( { ### Composable advanced search -This demo handles building the advanced search form using the composable Menu, as well as wiring up a +This demo handles building the advanced search form using the composable Menu, as well as wiring up a select using the composable Menu and MenuToggle components. This demo also demonstrates wiring up the appropriate keyboard interactions, focus management, and general event handling. @@ -195,14 +209,14 @@ Note: This demo and its handling of 'date within' and a date picker is modeled a ```js import React from 'react'; -import { +import { ActionGroup, - Button, + Button, DatePicker, - Form, + Form, FormGroup, Grid, - GridItem, + GridItem, isValidDate, Menu, MenuContent, @@ -212,8 +226,8 @@ import { Panel, PanelMain, PanelMainBody, - Popper, - SearchInput, + Popper, + SearchInput, TextInput, yyyyMMddFormat } from '@patternfly/react-core'; @@ -223,25 +237,25 @@ AdvancedComposableSearchInput = () => { const [hasWords, setHasWords] = React.useState(''); const [dateWithin, setDateWithin] = React.useState('1 day'); const [date, setDate] = React.useState(); - + const [isAdvancedSearchOpen, setIsAdvancedSearchOpen] = React.useState(false); const [isDateWithinOpen, setIsDateWithinOpen] = React.useState(false); - + const isInitialMount = React.useRef(true); const firstAttrRef = React.useRef(null); const searchInputRef = React.useRef(null); const advancedSearchPaneRef = React.useRef(null); const dateWithinToggleRef = React.useRef(); const dateWithinMenuRef = React.useRef(); - + const onClear = () => { setValue(''); setHasWords(''); setDateWithin(''); setDate(''); }; - - const onChange = (value) => { + + const onChange = (_event, value) => { if (value.length <= hasWords.length + 1) { setValue(value); setHasWords(value); @@ -249,7 +263,7 @@ AdvancedComposableSearchInput = () => { setValue(hasWords); } }; - + // After initial page load, whenever the advanced search menu is opened, the browser focus should be placed on the // first advanced search form input. Whenever the advanced search menu is closed, the browser focus should // be returned to the search input. @@ -264,38 +278,51 @@ AdvancedComposableSearchInput = () => { } } }, [isAdvancedSearchOpen]); - - // If a menu is open and has browser focus, then the escape key closes them and puts the browser focus onto their + + // If a menu is open and has browser focus, then the escape key closes them and puts the browser focus onto their // respective toggle. The 'date within' menu also needs to close when the 'tab' key is hit. However, hitting tab while // focus is in the advanced search form should move the focus to the next form input, not close the advanced search // menu. - const handleMenuKeys = event => { + const handleMenuKeys = (event) => { if (isDateWithinOpen && dateWithinMenuRef.current && dateWithinMenuRef.current.contains(event.target)) { if (event.key === 'Escape' || event.key === 'Tab') { setIsDateWithinOpen(!isDateWithinOpen); dateWithinToggleRef.current.focus(); } - } + } if (isAdvancedSearchOpen && advancedSearchPaneRef.current && advancedSearchPaneRef.current.contains(event.target)) { - if (event.key === 'Escape' || - (event.key === 'Tab' && !event.shiftKey && advancedSearchPaneRef.current.querySelector('button[type=reset]') === event.target) + if ( + event.key === 'Escape' || + (event.key === 'Tab' && + !event.shiftKey && + advancedSearchPaneRef.current.querySelector('button[type=reset]') === event.target) ) { setIsAdvancedSearchOpen(!isAdvancedSearchOpen); searchInputRef.current.focus(); } - } + } }; - + // If a menu is open and has browser focus, then clicking outside the menu should close it. - const handleClickOutside = event => { - if (isDateWithinOpen && dateWithinMenuRef && dateWithinMenuRef.current && !dateWithinMenuRef.current.contains(event.target)) { + const handleClickOutside = (event) => { + if ( + isDateWithinOpen && + dateWithinMenuRef && + dateWithinMenuRef.current && + !dateWithinMenuRef.current.contains(event.target) + ) { setIsDateWithinOpen(false); - } - if (isAdvancedSearchOpen && advancedSearchPaneRef && advancedSearchPaneRef.current && !advancedSearchPaneRef.current.contains(event.target)) { + } + if ( + isAdvancedSearchOpen && + advancedSearchPaneRef && + advancedSearchPaneRef.current && + !advancedSearchPaneRef.current.contains(event.target) + ) { setIsAdvancedSearchOpen(false); } }; - + React.useEffect(() => { window.addEventListener('keydown', handleMenuKeys); window.addEventListener('click', handleClickOutside); @@ -304,12 +331,11 @@ AdvancedComposableSearchInput = () => { window.removeEventListener('click', handleClickOutside); }; }, [dateWithinMenuRef.current, advancedSearchPaneRef.current, isAdvancedSearchOpen, isDateWithinOpen]); - - + // This demo and its handling of 'date within' and a date picker is modeled after the gmail advanced search form. const onSubmit = (event, value) => { event.preventDefault(); - + if (isValidDate(new Date(date)) && dateWithin) { let afterDate = new Date(date); let toDate = new Date(date); @@ -355,21 +381,21 @@ AdvancedComposableSearchInput = () => { toDate.setDate(toDate.getDate() + 1); break; } - setValue(`${hasWords && (hasWords + " ")}after:${yyyyMMddFormat(afterDate)} to:${yyyyMMddFormat(toDate)}`) + setValue(`${hasWords && hasWords + ' '}after:${yyyyMMddFormat(afterDate)} to:${yyyyMMddFormat(toDate)}`); } else { setValue(hasWords); } - + setIsAdvancedSearchOpen(false); }; - + const searchInput = ( onChange(value)} + onChange={onChange} onToggleAdvancedSearch={(e, isOpen) => { e.stopPropagation(); - setIsAdvancedSearchOpen(isOpen) + setIsAdvancedSearchOpen(isOpen); }} isAdvancedSearchOpen={isAdvancedSearchOpen} onClear={onClear} @@ -378,10 +404,10 @@ AdvancedComposableSearchInput = () => { id="custom-advanced-search" /> ); - - // Clicking the 'date within' toggle should open its associated menu and then place the browser - // focus on the first menu item. - const toggleDateWithinMenu = ev => { + + // Clicking the 'date within' toggle should open its associated menu and then place the browser + // focus on the first menu item. + const toggleDateWithinMenu = (ev) => { ev.stopPropagation(); // Stop handleClickOutside from handling setTimeout(() => { if (dateWithinMenuRef.current) { @@ -391,7 +417,7 @@ AdvancedComposableSearchInput = () => { }, 0); setIsDateWithinOpen(!isDateWithinOpen); }; - + // Selecting a date within option closes the menu, sets the value of date within, and puts browser focus back // on the date within toggle. const onDateWithinSelect = (e, itemId) => { @@ -402,7 +428,7 @@ AdvancedComposableSearchInput = () => { dateWithinToggleRef.current.focus(); } }; - + const dateWithinOptions = ( @@ -419,25 +445,30 @@ AdvancedComposableSearchInput = () => { ); - + const dateWithinToggle = ( - + {dateWithin} ); - + const advancedForm = (
- +
- + { + onChange={(value) => { setHasWords(value); setValue(value); }} @@ -446,26 +477,36 @@ AdvancedComposableSearchInput = () => { - - + + - - document.querySelector("#datePicker")} + + document.querySelector('#datePicker')} /> - + {!!onClear && ( - + )} @@ -484,7 +525,7 @@ AdvancedComposableSearchInput = () => { popperRef={advancedSearchPaneRef} isVisible={isAdvancedSearchOpen} enableFlip={false} - appendTo={() => document.querySelector("#custom-advanced-search")} + appendTo={() => document.querySelector('#custom-advanced-search')} /> ); };