department-of-veterans-affairs/vets-website

View on GitHub
src/platform/site-wide/user-nav/components/SearchMenu.jsx

Summary

Maintainability
B
6 hrs
Test Coverage
import PropTypes from 'prop-types';
import React from 'react';
import {
  PAGE_PATH,
  SEARCH_LOCATION,
  SEARCH_APP_USED,
  SEARCH_SELECTION,
  SEARCH_TYPEAHEAD_ENABLED,
  TYPEAHEAD_CLICKED,
  TYPEAHEAD_LIST,
  addSearchGADataToStorage,
} from 'platform/site-wide/search-analytics';
import classNames from 'classnames';
import recordEvent from 'platform/monitoring/record-event';

import SearchDropdownComponent from './SearchDropdownComponent';
import DropDownPanel from './DropDownPanel/DropDownPanel';
import { replaceWithStagingDomain } from '../../../utilities/environment/stagingDomains';

export class SearchMenu extends React.Component {
  componentDidUpdate(prevProps) {
    const { isOpen } = this.props;

    // focus the query input when the search menu is opened
    const inputField = document.getElementById('query');
    if (isOpen && !prevProps.isOpen && inputField) {
      inputField.focus();
    }

    // focus the search dropdown input field when the search menu is opened
    const dropdownInputField = document.getElementById(
      'search-header-dropdown-input-field',
    );

    if (isOpen && !prevProps.isOpen && dropdownInputField) {
      dropdownInputField.focus();
    }
  }

  onInputSubmit = componentState => {
    const savedSuggestions = componentState?.savedSuggestions || [];
    const suggestions = componentState?.suggestions || [];
    const inputValue = componentState?.inputValue;
    const validSuggestions =
      savedSuggestions.length > 0 ? savedSuggestions : suggestions;

    addSearchGADataToStorage({
      [PAGE_PATH]: document.location.pathname,
      [SEARCH_LOCATION]: 'Desktop Header Search',
      [SEARCH_APP_USED]: false,
      [SEARCH_SELECTION]: 'All VA.gov',
      [SEARCH_TYPEAHEAD_ENABLED]: true,
      [TYPEAHEAD_LIST]: validSuggestions,
    });

    // create a search url
    const searchUrl = replaceWithStagingDomain(
      `https://www.va.gov/search/?query=${encodeURIComponent(
        inputValue,
      )}&t=${false}`,
    );

    // relocate to search results, preserving history
    window.location.assign(searchUrl);
  };

  onSuggestionSubmit = (index, componentState) => {
    const savedSuggestions = componentState?.savedSuggestions || [];
    const suggestions = componentState?.suggestions || [];
    const validSuggestions =
      savedSuggestions?.length > 0 ? savedSuggestions : suggestions;

    const analyticsData = {
      [PAGE_PATH]: document.location.pathname,
      [SEARCH_LOCATION]: 'Desktop Header Search',
      [SEARCH_APP_USED]: false,
      [SEARCH_SELECTION]: 'All VA.gov',
      [SEARCH_TYPEAHEAD_ENABLED]: true,
      [TYPEAHEAD_CLICKED]: true,
      [TYPEAHEAD_LIST]: validSuggestions,
    };

    addSearchGADataToStorage(analyticsData);

    // create a search url
    const searchUrl = replaceWithStagingDomain(
      `https://www.va.gov/search/?query=${encodeURIComponent(
        validSuggestions[index],
      )}&t=${true}`,
    );

    // relocate to search results, preserving history
    window.location.assign(searchUrl);
  };

  makeForm = () => {
    return (
      <SearchDropdownComponent
        buttonText=""
        canSubmit
        id="search-header-dropdown"
        componentClassName=""
        containerClassName=""
        inputClassName=""
        suggestionsListClassName=""
        suggestionClassName=""
        fullWidthSuggestions
        formatSuggestions
        startingValue=""
        submitOnClick
        submitOnEnter
        onInputSubmit={this.onInputSubmit}
        onSuggestionSubmit={this.onSuggestionSubmit}
      />
    );
  };

  render() {
    const { makeForm } = this;
    const { cssClass, clickHandler, isOpen } = this.props;

    const buttonClasses = classNames(
      'sitewide-search-drop-down-panel-button',
      cssClass,
      'va-btn-withicon',
      'va-dropdown-trigger',
    );

    const icon = (
      <>
        {/* search icon on the header (next to "Search") */}
        {/* Convert to va-icon when injected header/footer split is in prod: https://github.com/department-of-veterans-affairs/vets-website/pull/27590 */}
        <svg
          aria-hidden="true"
          className="vads-u-margin-right--0p5"
          focusable="false"
          width="24"
          viewBox="2 0 20 20"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            fill="#fff"
            fillRule="evenodd"
            clipRule="evenodd"
            d="M15.5 14H14.71L14.43 13.73C15.41 12.59 16 11.11 16 9.5C16 5.91 13.09 3 9.5 3C5.91 3 3 5.91 3 9.5C3 13.09 5.91 16 9.5 16C11.11 16 12.59 15.41 13.73 14.43L14 14.71V15.5L19 20.49L20.49 19L15.5 14ZM9.5 14C7.01 14 5 11.99 5 9.5C5 7.01 7.01 5 9.5 5C11.99 5 14 7.01 14 9.5C14 11.99 11.99 14 9.5 14Z"
          />
        </svg>
      </>
    );

    return (
      <DropDownPanel
        buttonText="Search"
        clickHandler={clickHandler}
        cssClass={buttonClasses}
        dropdownPanelClassNames="vads-u-padding--0 vads-u-margin--0"
        icon={icon}
        id="search"
        isOpen={isOpen}
        onClick={() => recordEvent({ event: 'nav-jumplink-click' })}
      >
        {makeForm()}
      </DropDownPanel>
    );
  }
}

SearchMenu.propTypes = {
  isOpen: PropTypes.bool.isRequired,
  clickHandler: PropTypes.func,
  cssClass: PropTypes.string,
  debounceRate: PropTypes.number,
};

SearchMenu.defaultProps = {
  debounceRate: 200,
};

export default SearchMenu;