package.src.components.SearchInput.AdvancedSearchMenu.tsx Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of react-core Show documentation
Show all versions of react-core Show documentation
This library provides a set of common React components for use with the PatternFly reference implementation.
The newest version!
import * as React from 'react';
import { Button } from '../Button';
import { ActionGroup, Form, FormGroup } from '../Form';
import { TextInput } from '../TextInput';
import { GenerateId, KeyTypes } from '../../helpers';
import { SearchInputSearchAttribute } from './SearchInput';
import { Panel, PanelMain, PanelMainBody } from '../Panel';
import { css } from '@patternfly/react-styles';
export interface AdvancedSearchMenuProps extends Omit, 'onChange'> {
/** Delimiter in the query string for pairing attributes with search values.
* Required whenever attributes are passed as props.
*/
advancedSearchDelimiter?: string;
/** Array of attribute values used for dynamically generated advanced search. */
attributes?: string[] | SearchInputSearchAttribute[];
/** Additional classes added to the advanced search menu. */
className?: string;
/* Additional elements added after the attributes in the form.
* The new form elements can be wrapped in a form group component for automatic formatting. */
formAdditionalItems?: React.ReactNode;
/** Function which builds an attribute-value map by parsing the value in the search input. */
getAttrValueMap?: () => { [key: string]: string };
/** Attribute label for strings unassociated with one of the provided listed attributes. */
hasWordsAttrLabel?: React.ReactNode;
/** Flag for toggling the open/close state of the advanced search menu. */
isSearchMenuOpen?: boolean;
/** A callback for when the input value changes. */
onChange?: (event: React.FormEvent, value: string) => void;
/** A callback for when the user clicks the clear button. */
onClear?: (event: React.SyntheticEvent) => void;
/** A callback for when the search button is clicked. */
onSearch?: (
event: React.SyntheticEvent,
value: string,
attrValueMap: { [key: string]: string }
) => void;
/** A callback for when the open advanced search button is clicked. */
onToggleAdvancedMenu?: (e: React.SyntheticEvent) => void;
/** Ref of the input element within the search input. */
parentInputRef?: React.RefObject;
/** Ref of the div wrapping the whole search input. */
parentRef?: React.RefObject;
/** Label for the button which resets the advanced search form and clears the search input. */
resetButtonLabel?: string;
/** Label for the button which calls the onSearch event handler. */
submitSearchButtonLabel?: string;
/** Value of the search input. */
value?: string;
}
export const AdvancedSearchMenu: React.FunctionComponent = ({
className,
parentRef,
parentInputRef,
value = '',
attributes = [] as string[],
formAdditionalItems,
hasWordsAttrLabel = 'Has words',
advancedSearchDelimiter,
getAttrValueMap,
onChange,
onSearch,
onClear,
resetButtonLabel = 'Reset',
submitSearchButtonLabel = 'Search',
isSearchMenuOpen,
onToggleAdvancedMenu
}: AdvancedSearchMenuProps) => {
const firstAttrRef = React.useRef(null);
const [putFocusBackOnInput, setPutFocusBackOnInput] = React.useState(false);
React.useEffect(() => {
if (attributes.length > 0 && !advancedSearchDelimiter) {
// eslint-disable-next-line no-console
console.error(
'AdvancedSearchMenu: An advancedSearchDelimiter prop is required when advanced search attributes are provided using the attributes prop'
);
}
});
React.useEffect(() => {
if (isSearchMenuOpen && firstAttrRef && firstAttrRef.current) {
firstAttrRef.current.focus();
setPutFocusBackOnInput(true);
} else if (!isSearchMenuOpen && putFocusBackOnInput && parentInputRef && parentInputRef.current) {
parentInputRef.current.focus();
}
}, [isSearchMenuOpen]);
React.useEffect(() => {
document.addEventListener('mousedown', onDocClick);
document.addEventListener('touchstart', onDocClick);
document.addEventListener('keydown', onEscPress);
return function cleanup() {
document.removeEventListener('mousedown', onDocClick);
document.removeEventListener('touchstart', onDocClick);
document.removeEventListener('keydown', onEscPress);
};
});
const onDocClick = (event: Event) => {
const clickedWithinSearchInput = parentRef && parentRef.current.contains(event.target as Node);
if (isSearchMenuOpen && !clickedWithinSearchInput) {
onToggleAdvancedMenu(event as any);
}
};
const onEscPress = (event: KeyboardEvent) => {
if (
isSearchMenuOpen &&
event.key === KeyTypes.Escape &&
parentRef &&
parentRef.current.contains(event.target as Node)
) {
onToggleAdvancedMenu(event as any);
if (parentInputRef) {
parentInputRef.current.focus();
}
}
};
const onSearchHandler = (event: React.SyntheticEvent) => {
event.preventDefault();
if (onSearch) {
onSearch(event, value, getAttrValueMap());
}
if (isSearchMenuOpen) {
onToggleAdvancedMenu(event as any);
}
};
const handleValueChange = (attribute: string, newValue: string, event: React.FormEvent) => {
const newMap = getAttrValueMap();
newMap[attribute] = newValue;
let updatedValue = '';
Object.entries(newMap).forEach(([k, v]) => {
if (v.trim() !== '') {
/* Wrap the value in quotes if it contains spaces */
const quoteWrappedValue = v.includes(' ') ? `'${v.replace(/(^'|'$)/g, '')}'` : v;
if (k !== 'haswords') {
updatedValue = `${updatedValue} ${k}${advancedSearchDelimiter}${quoteWrappedValue}`;
} else {
updatedValue = `${updatedValue} ${quoteWrappedValue}`;
}
}
});
if (onChange) {
onChange(event, updatedValue.replace(/^\s+/g, ''));
}
};
const getValue = (attribute: string) => {
const map = getAttrValueMap();
return map.hasOwnProperty(attribute) ? map[attribute] : '';
};
const buildFormGroups = () => {
const formGroups = [] as React.ReactNode[];
attributes.forEach((attribute: string | SearchInputSearchAttribute, index: number) => {
const display = typeof attribute === 'string' ? attribute : attribute.display;
const queryAttr = typeof attribute === 'string' ? attribute : attribute.attr;
if (index === 0) {
formGroups.push(
handleValueChange(queryAttr, value, evt)}
/>
);
} else {
formGroups.push(
handleValueChange(queryAttr, value, evt)}
/>
);
}
});
formGroups.push(
{(randomId) => (
handleValueChange('haswords', value, evt)}
/>
)}
);
return formGroups;
};
return isSearchMenuOpen ? (
) : null;
};
AdvancedSearchMenu.displayName = 'SearchInput';