All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.components.SearchInput.AdvancedSearchMenu.tsx Maven / Gradle / Ivy

Go to download

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 ? (
    
      
        
          
{buildFormGroups()} {formAdditionalItems ? formAdditionalItems : null} {!!onClear && ( )}
) : null; }; AdvancedSearchMenu.displayName = 'SearchInput';




© 2015 - 2024 Weber Informatics LLC | Privacy Policy