UNPKG

@patternfly/react-core

Version:

This library provides a set of common React components for use with the PatternFly reference implementation.

185 lines (173 loc) 6.29 kB
--- id: Password generator section: patterns --- import { useEffect, useRef, useState } from 'react'; import RedoIcon from '@patternfly/react-icons/dist/esm/icons/redo-icon'; import EyeIcon from '@patternfly/react-icons/dist/esm/icons/eye-icon'; import EyeSlashIcon from '@patternfly/react-icons/dist/esm/icons/eye-slash-icon'; ## Demos ### Provide a generated password ```ts import { useEffect, useRef, useState } from 'react'; import { InputGroup, InputGroupItem, TextInput, Button, Popper, Menu, MenuContent, MenuList, MenuItem, MenuItemAction } from '@patternfly/react-core'; import RedoIcon from '@patternfly/react-icons/dist/esm/icons/redo-icon'; import EyeIcon from '@patternfly/react-icons/dist/esm/icons/eye-icon'; import EyeSlashIcon from '@patternfly/react-icons/dist/esm/icons/eye-slash-icon'; const PasswordGenerator: React.FunctionComponent = () => { const generatePassword = () => { const length = 12; const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@%()_-=+'; let retVal = ''; for (var i = 0, n = charset.length; i < length; ++i) { retVal += charset.charAt(Math.floor(Math.random() * n)); } return retVal; }; const [password, setPassword] = useState<string>(''); const [generatedPassword, setGeneratedPassword] = useState<string>(generatePassword()); const [isAutocompleteOpen, setIsAutocompleteOpen] = useState<boolean>(false); const [passwordHidden, setPasswordHidden] = useState<boolean>(true); const searchInputRef = useRef(null); const autocompleteRef = .useRef(null); useEffect(() => { window.addEventListener('keydown', handleMenuKeys); window.addEventListener('click', handleClickOutside); return () => { window.removeEventListener('keydown', handleMenuKeys); window.removeEventListener('click', handleClickOutside); }; }, [isAutocompleteOpen, searchInputRef.current]); const onChange = (_event: React.FormEvent<HTMLInputElement>, newValue: string) => { if (searchInputRef && searchInputRef.current && searchInputRef.current.contains(document.activeElement)) { setIsAutocompleteOpen(true); } else { setIsAutocompleteOpen(false); } setPassword(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 = (event: React.MouseEvent<HTMLButtonElement>) => { event.stopPropagation(); setPassword(generatedPassword); setIsAutocompleteOpen(false); searchInputRef.current.focus(); }; const handleMenuKeys = (event: KeyboardEvent | React.KeyboardEvent<any>) => { if (!(isAutocompleteOpen && searchInputRef.current && searchInputRef.current.contains(event.target))) { return; } // 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 } 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 } // If the autocomplete is open and the browser focus is in the autocomplete menu // hitting tab will close the autocomplete and put browser focus back on the search input. else if (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: MouseEvent | TouchEvent | KeyboardEvent | React.KeyboardEvent<any> | React.MouseEvent<HTMLButtonElement> ) => { if ( isAutocompleteOpen && autocompleteRef && autocompleteRef.current && !searchInputRef.current.contains(event.target) ) { setIsAutocompleteOpen(false); } if ( !isAutocompleteOpen && searchInputRef && searchInputRef.current && searchInputRef.current.contains(event.target) ) { setIsAutocompleteOpen(true); } }; const textInput = ( <div ref={searchInputRef} id="password-input"> <InputGroup> <InputGroupItem isFill> <TextInput onFocus={() => { setIsAutocompleteOpen(true); }} isRequired type={passwordHidden ? 'password' : 'text'} aria-label="Password input" value={password} onChange={onChange} /> </InputGroupItem> <InputGroupItem> <Button variant="control" onClick={() => setPasswordHidden(!passwordHidden)} aria-label={passwordHidden ? 'Show password' : 'Hide password'} icon={passwordHidden ? <EyeIcon /> : <EyeSlashIcon />} /> </InputGroupItem> </InputGroup> </div> ); const autocomplete = ( <Menu ref={autocompleteRef} onSelect={onSelect}> <MenuContent> <MenuList> <MenuItem itemId={0} actions={ <MenuItemAction icon={<RedoIcon />} onClick={(e) => { setGeneratedPassword(generatePassword()); }} actionId="redo" aria-label="Generate a new suggested password" /> } > Use suggested password: <b>{`${generatedPassword}`}</b> </MenuItem> </MenuList> </MenuContent> </Menu> ); return ( <Popper trigger={textInput} triggerRef={searchInputRef} popper={autocomplete} popperRef={autocompleteRef} isVisible={isAutocompleteOpen} enableFlip={false} // append the autocomplete menu to the search input in the DOM for the sake of the keyboard navigation experience appendTo={() => document.querySelector('#password-input')} /> ); }; ```