[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/AccordionFilterItem.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Fragment, useState } from 'react'
import PropTypes from 'prop-types'
import { useIntl, FormattedMessage } from 'react-intl'
import classNames from 'classnames'
import { IconCaret } from 'vtex.store-icons'
import { Tag } from 'vtex.styleguide'
import { useCssHandles, applyModifiers } from 'vtex.css-handles'
import { Collapse } from 'react-collapse'

import { getFilterTitle } from '../constants/SearchHelpers'
import { generateSlug } from './FilterNavigator/legacy/hooks/useSelectedFilters'

const CSS_HANDLES = [
  'accordionFilterContainer',
  'accordionFilterContent',
  'accordionFilterItem',
  'filterAccordionItemBox',
  'accordionFilterItemActive',
  'accordionFilterItemHidden',
  'accordionFilterItemTitle',
  'accordionFilterItemTag',
  'accordionFilterItemIcon',
  'accordionSelectedFilters',
]

const AccordionFilterItem = ({
  title,
  show,
  open,
  onOpen,
  selectedFilters = [],
  children,
  appliedFiltersOverview,
  navigationType,
  initiallyCollapsed,
  onClearFilter,
  facetKey,
  showClearByFilter,
  quantity,
}) => {
  const intl = useIntl()
  const handles = useCssHandles(CSS_HANDLES)
  const isNavigationCollapsible = navigationType === 'collapsible'
  const [isCollapsed, setIsCollapsed] = useState(initiallyCollapsed)

  const handleOnOpen = e => {
    if (isNavigationCollapsible) {
      setIsCollapsed(prevIsCollapsed => !prevIsCollapsed)
    }

    onOpen(e)
  }

  const handleKeyDown = e => {
    if (e.key === ' ') {
      handleOnOpen(e)
    }
  }

  const quantitySelected =
    typeof quantity !== 'undefined' ? quantity : selectedFilters.length

  const titleSlug = generateSlug(getFilterTitle(title, intl))

  return (
    <Fragment>
      {(!open || isNavigationCollapsible) && (
        <div
          className={`${applyModifiers(
            handles.accordionFilterContainer,
            titleSlug
          )} pl7`}
        >
          <div
            role="button"
            tabIndex={0}
            className={classNames(
              handles.accordionFilterItem,
              applyModifiers(handles.filterAccordionItemBox, titleSlug),
              't-body pr5 pv3 pointer bb b--muted-5 outline-0',
              {
                [handles.accordionFilterItemActive]: open,
                [`${handles.accordionFilterItemHidden} dn`]: !show,
              }
            )}
            onKeyDown={handleKeyDown}
            onClick={handleOnOpen}
          >
            <div
              className={classNames(
                handles.accordionFilterContent,
                'pv4 c-on-base',
                {
                  't-small': open,
                  't-heading-5': !open,
                }
              )}
            >
              <span className={handles.accordionFilterItemTitle}>
                {getFilterTitle(title, intl)}
              </span>
              {quantitySelected !== 0 && (
                <div
                  className={classNames(
                    handles.accordionFilterItemTag,
                    'dib ml3'
                  )}
                >
                  <Tag>{quantitySelected}</Tag>
                </div>
              )}
              {quantitySelected > 0 && showClearByFilter && (
                <span
                  className={classNames(
                    handles.accordionFilterItemTag,
                    'dib ml3'
                  )}
                >
                  <Tag
                    size="small"
                    onClick={e => {
                      e.stopPropagation()
                      onClearFilter && onClearFilter(facetKey)
                    }}
                  >
                    <FormattedMessage id="store/search-result.filter-button.clear" />
                  </Tag>
                </span>
              )}
              <span className={`${handles.accordionFilterItemIcon} fr`}>
                <IconCaret
                  orientation={
                    !isNavigationCollapsible ||
                    (isNavigationCollapsible && isCollapsed)
                      ? 'down'
                      : 'up'
                  }
                  size={10}
                />
              </span>
              {appliedFiltersOverview === 'show' && quantitySelected > 0 && (
                <div
                  className={classNames(handles.accordionSelectedFilters, 'f6')}
                >
                  {selectedFilters.map(facet => facet.name).join(', ')}
                </div>
              )}
            </div>
          </div>
        </div>
      )}
      {!isNavigationCollapsible ? (
        open && children
      ) : (
        <Collapse isOpened={!isCollapsed && isNavigationCollapsible}>
          <div className="pl8">{children}</div>
        </Collapse>
      )}
    </Fragment>
  )
}

AccordionFilterItem.propTypes = {
  /** Title */
  title: PropTypes.string,
  /** Whether to show any of the content */
  show: PropTypes.bool,
  /** Whether to show the filter options */
  open: PropTypes.bool,
  /** Callback to open event */
  onOpen: PropTypes.func,
  /** List of selected filters */
  selectedFilters: PropTypes.arrayOf(PropTypes.object),
  /** quantity that will be shown besides the filter title. If `undefined` selectedFacets.length will be used */
  quantity: PropTypes.number,
  /** content */
  children: PropTypes.node,
  /** Whether an overview of the applied filters should be displayed (`"show"`) or not (`"hide"`). */
  appliedFiltersOverview: PropTypes.string,
  /** Defines the navigation method: 'page' or 'collapsible' */
  navigationType: PropTypes.oneOf(['page', 'collapsible']),
  /** Makes the search filters start out collapsed (`true`) or open (`false`) */
  initiallyCollapsed: PropTypes.bool,
  /** Clear filter function */
  onClearFilter: PropTypes.func,
  /** Facet's key */
  facetKey: PropTypes.string,
  /** Whether a clear button that clear all options in a specific filter should appear beside the filter's name (true) or not (false). */
  showClearByFilter: PropTypes.bool,
}

export default AccordionFilterItem

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/LoadingOverlay.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { Spinner } from 'vtex.styleguide'

export default class LoadingOverlay extends Component {
  static propTypes = {
    children: PropTypes.node,
    loading: PropTypes.bool,
  }

  static defaultProps = {
    loading: false,
  }

  root_ = React.createRef()

  handleClick = e => {
    e.stopPropagation()
  }

  componentDidMount() {
    if (this.props.loading) this.attachEvent()
  }

  attachEvent = () => {
    if (this.root_.current) {
      this.root_.current.addEventListener('click', this.handleClick, true)
    }
  }

  dettachEvent = () => {
    if (this.root_.current) {
      this.root_.current.removeEventListener('click', this.handleClick, true)
    }
  }

  componentDidUpdate(prevProps) {
    if (this.props.loading) {
      this.attachEvent()
    } else if (prevProps.loading && !this.props.loading) {
      this.dettachEvent()
    }
  }

  render() {
    const { children, loading } = this.props

    return (
      <div className="relative justify-center flex" ref={this.root_}>
        {loading && (
          <div
            className="absolute w-100 h-100 z-2"
            style={{ background: 'rgba(255, 255, 255, .6)' }}
          >
            <div className="absolute top-0 bottom-0 left-0 right-0 flex justify-center pt11">
              <Spinner />
            </div>
          </div>
        )}
        {children}
      </div>
    )
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/QueryContext.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { createContext, useContext } from 'react'

const QueryContext = createContext({ lol: 'a' })

export default QueryContext

export const useQuery = () => useContext(QueryContext)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/SearchFilterBar.tsx from the search-result VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { Input } from 'vtex.styleguide'
import { useCssHandles } from 'vtex.css-handles'
import { defineMessages, useIntl } from 'react-intl'

const CSS_HANDLES = ['searchFilterBar']

const messages = defineMessages({
  placeholder: {
    id: 'store/search.filter.placeholder',
    defaultMessage: '',
  },
})

interface Props {
  name: string
  handleChange: (value: string) => void
}

export const SearchFilterBar = ({ name, handleChange }: Props) => {
  const handles = useCssHandles(CSS_HANDLES)
  const intl = useIntl()

  return (
    <div className={`${handles.searchFilterBar} mb3`}>
      <Input
        data-testid="filter-search-bar"
        onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
          handleChange(e.target.value)
        }
        placeholder={intl.formatMessage(messages.placeholder, {
          filterName: name,
        })}
      />
    </div>
  )
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/AccordionFilterContainer.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { useIntl } from 'react-intl'
import classNames from 'classnames'
import { IconCaret } from 'vtex.store-icons'
import { useRuntime } from 'vtex.render-runtime'
import { useCssHandles } from 'vtex.css-handles'
import { Spinner } from 'vtex.styleguide'

import AccordionFilterItem from './AccordionFilterItem'
import DepartmentFilters from './DepartmentFilters'
import AccordionFilterGroup from './AccordionFilterGroup'
import AccordionFilterPriceRange from './AccordionFilterPriceRange'
import styles from '../searchResult.css'

const CSS_HANDLES = [
  'filterBreadcrumbsItem',
  'filterBreadcrumbsItemName',
  'filterBreadcrumbsContent',
  'filterBreadcrumbsText',
  'filterBreadcrumbsList',
  'filterLoadingOverlay',
]

const CATEGORIES_TITLE = 'store/search.filter.title.categories'

const AccordionFilterContainer = ({
  filters,
  onFilterCheck,
  tree,
  onCategorySelect,
  priceRange,
  onChangePriceRange,
  appliedFiltersOverview,
  navigationType,
  initiallyCollapsed,
  truncateFilters,
  truncatedFacetsFetched,
  setTruncatedFacetsFetched,
  categoryFiltersMode,
  loading,
  onClearFilter,
  showClearByFilter,
  updateOnFilterSelectionOnMobile,
  priceRangeLayout,
  onOpenPostalCodeModal,
  onOpenPickupModal,
}) => {
  const intl = useIntl()
  const { getSettings, setQuery } = useRuntime()
  const [openItem, setOpenItem] = useState(null)
  const handles = useCssHandles(CSS_HANDLES)
  const isLazyFacetsFetchEnabled =
    getSettings('vtex.store')?.enableFiltersFetchOptimization

  const handleOpen = id => e => {
    e.preventDefault()

    if (navigationType === 'collapsible') {
      return
    }

    if (isLazyFacetsFetchEnabled && !truncatedFacetsFetched) {
      setTruncatedFacetsFetched(true)
    }

    if (openItem === id) {
      setOpenItem(null)
    } else {
      setOpenItem(id)
    }
  }

  const handleKeyDown = e => {
    if (e.key === 'Enter') {
      setOpenItem(null)
    }
  }

  const nonEmptyFilters = filters.filter(spec => spec.facets.length > 0)

  const departmentsOpen = openItem === CATEGORIES_TITLE

  const itemClassName = classNames(
    styles.accordionFilterItemOptions,
    'ph5 pt3 h-100 overflow-scroll',
    { pb9: navigationType !== 'collapsible' }
  )

  const showOverlay = updateOnFilterSelectionOnMobile && loading

  return (
    <div
      className={classNames(styles.accordionFilter, 'h-100 pb9', {
        'overflow-scroll': !openItem,
      })}
    >
      <div
        className={classNames(
          styles.filterAccordionBreadcrumbs,
          'pointer flex flex-row items-center pa5 bg-base w-100 z-max bb b--muted-4'
        )}
      >
        <div
          role="button"
          tabIndex={0}
          className={`${handles.filterBreadcrumbsContent} pv4 flex items-center`}
          onClick={() => setOpenItem(null)}
          onKeyDown={handleKeyDown}
        >
          <div
            className={classNames(
              `${handles.filterBreadcrumbsText} t-heading-4`,
              {
                'c-muted-2': openItem,
                'c-on-base': !openItem,
              }
            )}
          >
            {intl.formatMessage({
              id: 'store/search-result.filter-breadcrumbs.primary',
            })}
          </div>
        </div>
        {openItem && (
          <div
            className={`${handles.filterBreadcrumbsItem} pv4 flex items-center`}
          >
            <IconCaret orientation="right" size={13} />
            <div
              className={`${handles.filterBreadcrumbsItemName} pl3 t-heading-4 c-on-base`}
            >
              {intl.formatMessage({ id: openItem })}
            </div>
          </div>
        )}
      </div>

      {tree.length > 0 && (
        <AccordionFilterItem
          title={CATEGORIES_TITLE}
          open={departmentsOpen}
          show={!openItem || departmentsOpen}
          onOpen={handleOpen(CATEGORIES_TITLE)}
          appliedFiltersOverview={appliedFiltersOverview}
          navigationType={navigationType}
          initiallyCollapsed={initiallyCollapsed}
          onClearFilter={onClearFilter}
        >
          <div className={itemClassName}>
            <DepartmentFilters
              tree={tree}
              isVisible={tree.length > 0}
              onCategorySelect={onCategorySelect}
              categoryFiltersMode={categoryFiltersMode}
              hideBorder
            />
          </div>
        </AccordionFilterItem>
      )}

      {nonEmptyFilters.map(filter => {
        const { type, title } = filter
        const isOpen = openItem === filter.title

        switch (type) {
          case 'PriceRanges':
            return (
              <AccordionFilterPriceRange
                title={filter.title}
                facets={filter.facets}
                key={title}
                className={itemClassName}
                open={isOpen}
                show={!openItem || isOpen}
                onOpen={handleOpen(title)}
                onFilterCheck={onFilterCheck}
                priceRange={priceRange}
                navigationType={navigationType}
                initiallyCollapsed={initiallyCollapsed}
                priceRangeLayout={priceRangeLayout}
                onClearFilter={() => {
                  setQuery({ priceRange: undefined })
                }}
                onChangePriceRange={onChangePriceRange}
                showClearByFilter={showClearByFilter}
              />
            )

          default:
            return (
              <AccordionFilterGroup
                title={filter.title}
                facets={filter.facets}
                quantity={filter.quantity}
                key={title}
                className={itemClassName}
                open={isOpen}
                show={!openItem || isOpen}
                onOpen={handleOpen(title)}
                onFilterCheck={onFilterCheck}
                appliedFiltersOverview={appliedFiltersOverview}
                navigationType={navigationType}
                initiallyCollapsed={initiallyCollapsed}
                truncateFilters={truncateFilters}
                truncatedFacetsFetched={truncatedFacetsFetched}
                setTruncatedFacetsFetched={setTruncatedFacetsFetched}
                onClearFilter={onClearFilter}
                showClearByFilter={showClearByFilter}
                onOpenPostalCodeModal={onOpenPostalCodeModal}
                onOpenPickupModal={onOpenPickupModal}
              />
            )
        }
      })}
      {showOverlay && (
        <div
          style={{ background: 'rgba(3, 4, 78, 0.4)' }}
          className={classNames(
            handles.filterLoadingOverlay,
            'fixed dim top-0 w-100 vh-100 left-0 z-999 justify-center items-center justify-center items-center flex'
          )}
        >
          <Spinner />
        </div>
      )}
    </div>
  )
}

AccordionFilterContainer.propTypes = {
  /** Current available filters */
  filters: PropTypes.arrayOf(PropTypes.object),
  /** Filters mapped for checkbox */
  filtersChecks: PropTypes.object,
  /** Checkbox hit callback function */
  onFilterCheck: PropTypes.func,
  /** Current price range filter query parameter */
  priceRange: PropTypes.string,
  tree: PropTypes.any,
  onCategorySelect: PropTypes.func,
  /** Whether an overview of the applied filters should be displayed (`"show"`) or not (`"hide"`). */
  appliedFiltersOverview: PropTypes.string,
  /** Defines the navigation method: 'page' or 'collapsible' */
  navigationType: PropTypes.oneOf(['page', 'collapsible']),
  /** Makes the search filters start out collapsed (`true`) or open (`false`) */
  initiallyCollapsed: PropTypes.bool,
  /** If filters start truncated */
  truncateFilters: PropTypes.bool,
  /** If the truncated facets were fetched */
  truncatedFacetsFetched: PropTypes.bool,
  /** Sets if the truncated facets were fetched */
  setTruncatedFacetsFetched: PropTypes.func,
  categoryFiltersMode: PropTypes.oneOf(['href', 'default']),
  loading: PropTypes.bool,
  /** Clear filter function */
  onClearFilter: PropTypes.func,
  /** Whether a clear button that clear all options in a specific filter should appear beside the filter's name (true) or not (false). */
  showClearByFilter: PropTypes.bool,
  /** Wether the search will be updated on facet selection (`true`) or not (`false`) when the user is on mobile. */
  updateOnFilterSelectionOnMobile: PropTypes.bool,
  /** Price range layout (default or inputAndSlider) */
  priceRangeLayout: PropTypes.string,
  /** Defines whether the price range should be cleared or not */
  clearPriceRange: PropTypes.bool,
  /** Set the value of clearPriceRange prop */
  setClearPriceRange: PropTypes.func,
  onChangePriceRange: PropTypes.func,
  onOpenPostalCodeModal: PropTypes.func,
  onOpenPickupModal: PropTypes.func,
}

export default AccordionFilterContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/SearchResult.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Component } from 'react'
import { compose } from 'react-apollo'
import { Spinner } from 'vtex.styleguide'
import { ExtensionPoint, withRuntimeContext } from 'vtex.render-runtime'
import { withDevice } from 'vtex.device-detector'
import { isEmpty } from 'ramda'
import { generateBlockClass } from '@vtex/css-handles'

import LoadingOverlay from './LoadingOverlay'
import { searchResultPropTypes } from '../constants/propTypes'
import LayoutModeSwitcher, { LAYOUT_MODE } from './LayoutModeSwitcher'
import FetchPreviousButton from './loaders/FetchPreviousButton'
import FetchMoreButton from './loaders/FetchMoreButton'
import LoadingSpinner from './loaders/LoadingSpinner'
import { PAGINATION_TYPE } from '../constants/paginationType'
import FilterNavigatorContext from './FilterNavigatorContext'
import getFilters from '../utils/getFilters'
import styles from '../searchResult.css'

/**
 * Search Result Component.
 */
class SearchResult extends Component {
  static propTypes = searchResultPropTypes

  static defaultProps = {
    mobileLayout: {
      mode1: LAYOUT_MODE[0].value,
      mode2: LAYOUT_MODE[1].value,
    },
  }

  state = {
    mobileLayoutMode: this.props.mobileLayout.mode1,
    showLoadingAsOverlay: false,
    // The definitions bellow are required because
    // on SSR the getDerivedStateFromProps isn't called
    products: this.props.products,
    recordsFiltered: this.props.recordsFiltered,
    brands: this.props.brands,
    params: this.props.params,
    priceRange: this.props.priceRange,
    priceRanges: this.props.priceRanges,
    specificationFilters: this.props.specificationFilters,
    tree: this.props.tree,
    hiddenFacets: this.props.hiddenFacets,
  }

  handleMobileLayoutChange = e => {
    e.preventDefault()

    const modes = [this.props.mobileLayout.mode1, this.props.mobileLayout.mode2]
    const modeIndex = (modes.indexOf(this.state.mobileLayoutMode) + 1) % 2
    const currentMode = modes[modeIndex]

    this.setState({
      mobileLayoutMode: currentMode,
    })
  }

  static getDerivedStateFromProps(props) {
    // Do not use the props when the query is loading
    // so we can show the previous products when the
    // overlay is on the screen
    if (!props.loading) {
      if (!props.mobileLayout.mode1 && !props.mobileLayout.mode2) {
        props.mobileLayout.mode1 = LAYOUT_MODE[0].value
        props.mobileLayout.mode2 = LAYOUT_MODE[1].value
      }

      const {
        products,
        recordsFiltered,
        brands,
        params,
        priceRange,
        priceRanges,
        specificationFilters,
        tree,
        hiddenFacets,
      } = props

      return {
        products,
        recordsFiltered,
        brands,
        params,
        priceRange,
        priceRanges,
        specificationFilters,
        tree,
        hiddenFacets,
      }
    }

    return null
  }

  componentDidMount() {
    if (!this.props.loading) {
      this.setState({
        showLoadingAsOverlay: true,
      })
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.loading && !this.props.loading) {
      this.setState({
        showLoadingAsOverlay: true,
      })
    }
  }

  render() {
    const {
      to,
      from,
      onFetchMore,
      onFetchPrevious,
      showProductsCount,
      breadcrumbsProps,
      loading,
      fetchMoreLoading,
      summary,
      orderBy,
      mobileLayout,
      isMobile,
      pagination,
      infiniteScrollError,
      facetsLoading,
    } = this.props

    const {
      mobileLayoutMode,
      recordsFiltered,
      products = [],
      brands,
      params,
      priceRange,
      priceRanges,
      specificationFilters,
      tree,
      hiddenFacets,
      showLoadingAsOverlay,
    } = this.state

    const queryArgs =
      this.props.searchQuery.facets && this.props.searchQuery.facets.queryArgs
        ? this.props.searchQuery.facets.queryArgs
        : { query: '', map: '' }

    const { map } = queryArgs

    const hideFacets = !map || !map.length
    const showLoading = loading && !fetchMoreLoading
    const showFacetsContentLoader = facetsLoading && !fetchMoreLoading
    const showContentLoader = showLoading && !showLoadingAsOverlay
    const shouldDisplayLayoutSwitcher = !!mobileLayout.mode2

    const filters = getFilters({
      specificationFilters,
      priceRanges,
      brands,
      hiddenFacets,
    })

    const showCategories =
      hiddenFacets && !hiddenFacets.categories && tree && tree.length > 0

    const showFacets = showCategories || (!hideFacets && !isEmpty(filters))

    return (
      <LoadingOverlay loading={showLoading && showLoadingAsOverlay}>
        <div
          className={`${generateBlockClass(
            styles.container,
            this.props.blockClass
          )} w-100 mw9`}
        >
          <div data-testid="breadcrumb" className={styles.breadcrumb}>
            <ExtensionPoint id="breadcrumb" {...breadcrumbsProps} />
          </div>
          <div className={styles.richTitle}>
            <ExtensionPoint id="rich-text" />
          </div>
          <ExtensionPoint
            id="search-title"
            breadcrumb={breadcrumbsProps.breadcrumb}
            wrapperClass={styles.galleryTitle}
          />
          {showFacets && !!map && (
            <div className={styles.filters}>
              <FilterNavigatorContext.Provider value={queryArgs}>
                <ExtensionPoint
                  id="filter-navigator"
                  brands={brands}
                  params={params}
                  priceRange={priceRange}
                  priceRanges={priceRanges}
                  specificationFilters={specificationFilters}
                  tree={tree}
                  loading={showFacetsContentLoader}
                  filters={filters}
                  hiddenFacets={hiddenFacets}
                />
              </FilterNavigatorContext.Provider>
            </div>
          )}
          <ExtensionPoint
            id="total-products"
            recordsFiltered={recordsFiltered}
            wrapperClass={styles.totalProducts}
          />
          <div className={styles.resultGallery}>
            <FetchPreviousButton
              products={products}
              from={from}
              onFetchPrevious={onFetchPrevious}
              loading={fetchMoreLoading}
            />
            {showContentLoader ? (
              <div className="w-100 flex justify-center">
                <div className="w3 ma0">
                  <Spinner />
                </div>
              </div>
            ) : products.length > 0 ? (
              <ExtensionPoint
                id="gallery"
                products={products}
                summary={summary}
                className="bn"
                mobileLayoutMode={mobileLayoutMode}
                showingFacets={showFacets}
              />
            ) : (
              <div className={styles.gallery}>
                <ExtensionPoint id="not-found" />
              </div>
            )}
            {pagination === PAGINATION_TYPE.SHOW_MORE || infiniteScrollError ? (
              <>
                <FetchMoreButton
                  products={products}
                  to={to}
                  recordsFiltered={recordsFiltered}
                  onFetchMore={onFetchMore}
                  loading={fetchMoreLoading}
                  showProductsCount={showProductsCount}
                />
                <ExtensionPoint
                  id="search-products-progress-bar"
                  recordsFiltered={recordsFiltered}
                  products={products}
                />
              </>
            ) : (
              <LoadingSpinner loading={fetchMoreLoading} />
            )}
          </div>
          <div className={styles.orderBy}>
            <ExtensionPoint
              id="order-by"
              orderBy={orderBy}
              wrapperClass={styles.orderBy}
            />
          </div>
          {isMobile && shouldDisplayLayoutSwitcher && (
            <div
              className={`${styles.switch} flex justify-center items-center`}
            >
              <LayoutModeSwitcher
                activeMode={mobileLayoutMode}
                onChange={this.handleMobileLayoutChange}
              />
            </div>
          )}
        </div>
      </LoadingOverlay>
    )
  }
}

export default compose(withRuntimeContext, withDevice)(SearchResult)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigatorTitleTag.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
import { FormattedMessage } from 'react-intl'

const CSS_HANDLES = ['filterMessage']

const FilterNavigatorTitleTags = ({ filtersTitleHtmlTag = 'h5' }) => {
  const handles = useCssHandles(CSS_HANDLES)
  const CustomTag = filtersTitleHtmlTag

  return (
    <CustomTag
      className={`${handles.filterMessage} ${
        filtersTitleHtmlTag === 'h5' ? 't-heading-5' : ''
      } mv5`}
    >
      <FormattedMessage id="store/search-result.filter-button.title" />
    </CustomTag>
  )
}

export default FilterNavigatorTitleTags

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/DepartmentFilters.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import classNames from 'classnames'
import React from 'react'
import { FormattedMessage } from 'react-intl'
import { useCssHandles, applyModifiers } from 'vtex.css-handles'

import Collapsible from './Collapsible'
import CategoryFilter from './CategoryFilter'

const CSS_HANDLES = [
  'filter__container',
  'filter',
  'filterTitle',
  'categoriesContainer',
]

const DepartmentFilters = ({
  title,
  isVisible,
  tree,
  onCategorySelect,
  hideBorder = false,
  preventRouteChange,
  maxItemsDepartment,
  maxItemsCategory,
  categoryFiltersMode,
}) => {
  const handles = useCssHandles(CSS_HANDLES)

  if (!isVisible || tree.length === 0) {
    return null
  }

  const showAllDepartments = tree.every(category => !category.selected)

  const containerClassName = classNames(
    applyModifiers(handles.filter__container, 'c'),
    { 'bb b--muted-4': !hideBorder }
  )

  return (
    <div className={containerClassName}>
      {title && (
        <div className={classNames(handles.filter, 'pt4')}>
          <div
            className={classNames(
              handles.filterTitle,
              't-mini c-muted-2 flex items-center justify-between'
            )}
          >
            <FormattedMessage id={title} />
          </div>
        </div>
      )}
      <div
        className={classNames(
          handles.categoriesContainer,
          'pb5 flex flex-column'
        )}
      >
        {showAllDepartments ? (
          <Collapsible
            maxItems={maxItemsDepartment}
            threshold={2}
            items={tree}
            openLabel="store/filter.more-departments"
            render={category => (
              <CategoryFilter
                key={category.id}
                category={category}
                shallow
                onCategorySelect={onCategorySelect}
                preventRouteChange={preventRouteChange}
                maxItemsCategory={maxItemsCategory}
                categoryFiltersMode={categoryFiltersMode}
              />
            )}
          />
        ) : (
          <CategoryFilter
            category={tree.find(category => category.selected)}
            onCategorySelect={onCategorySelect}
            preventRouteChange={preventRouteChange}
            maxItemsCategory={maxItemsCategory}
            categoryFiltersMode={categoryFiltersMode}
          />
        )}
      </div>
    </div>
  )
}

export default DepartmentFilters

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/GalleryItem.tsx from the search-result VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { ComponentType } from 'react'
import React, { useMemo, useCallback, memo } from 'react'
import { ExtensionPoint } from 'vtex.render-runtime'
import { usePixel } from 'vtex.pixel-manager'
import ProductSummary from 'vtex.product-summary/ProductSummaryCustom'
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'

import type { Product } from '../Gallery'
import type { MobileLayoutMode } from '../GalleryLegacy'
import type { PreferredSKU } from '../GalleryLayout'

interface GalleryItemProps {
  /** Item info that will be rendered. */
  item: Product
  /** Display mode of the product summary */
  displayMode: MobileLayoutMode
  /** ProductSummary props. */
  summary: any
  /** Item position in the gallery */
  position: number
  /** Name of the list of products. This name will be used when sending pixel events */
  listName: string
  CustomSummary?: ComponentType
  /** Logic to enable which SKU will be the selected item */
  preferredSKU?: PreferredSKU
}

/**
 * Normalizes the item received in the props to adapt to the extension point prop.
 */
function GalleryItem({
  item,
  displayMode,
  position,
  listName,
  summary,
  CustomSummary,
  preferredSKU,
}: GalleryItemProps) {
  const { push } = usePixel()
  const { searchQuery } = useSearchPage()

  const product = useMemo(
    () => ProductSummary.mapCatalogProductToProductSummary(item, preferredSKU),
    [item, preferredSKU]
  )

  const handleClick = useCallback(() => {
    push({
      event: 'productClick',
      product,
      query: searchQuery?.variables?.query,
      map: searchQuery?.variables?.map,
      position,
      list: listName,
    })
  }, [
    product,
    push,
    searchQuery?.variables?.map,
    searchQuery?.variables?.query,
    position,
    listName,
  ])

  const productSummaryProps = {
    ...summary,
    product,
    displayMode,
    listName,
    actionOnClick: handleClick,
    position,
  }

  if (CustomSummary) {
    return <CustomSummary {...productSummaryProps} />
  }

  return <ExtensionPoint id="product-summary" {...productSummaryProps} />
}

export default memo(GalleryItem)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/LocalQuery.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { path } from 'ramda'
import React from 'react'
import { useRuntime } from 'vtex.render-runtime'

import SearchQuery from './SearchQuery'
import { SORT_OPTIONS } from '../OrderBy'

const DEFAULT_MAX_ITEMS_PER_PAGE = 10
const DEFAULT_INSTALLMENT_CRITERIA = 'MAX_WITHOUT_INTEREST'

const LocalQuery = props => {
  const {
    maxItemsPerPage = DEFAULT_MAX_ITEMS_PER_PAGE,
    installmentCriteria = DEFAULT_INSTALLMENT_CRITERIA,
    queryField = '',
    mapField = '',
    priceRangeField = '',
    orderByField = SORT_OPTIONS[0].value,
    hideUnavailableItems,
    facetsBehavior,
    skusFilter,
    simulationBehavior,
    query: {
      order: orderBy = orderByField,
      page: pageQuery,
      priceRange = priceRangeField,
      map = mapField,
    } = {},
    render,
    __unstableProductOriginVtex,
  } = props

  const { page: runtimePage } = useRuntime()

  return (
    <SearchQuery
      maxItemsPerPage={maxItemsPerPage}
      query={queryField}
      map={mapField}
      orderBy={orderBy}
      priceRange={priceRange}
      hideUnavailableItems={hideUnavailableItems}
      facetsBehavior={facetsBehavior}
      pageQuery={pageQuery}
      skusFilter={skusFilter}
      simulationBehavior={simulationBehavior}
      lazyItemsQuery={false}
      installmentCriteria={installmentCriteria}
      __unstableProductOriginVtex={__unstableProductOriginVtex}
    >
      {(searchQuery, extraParams) => {
        return render({
          ...props,
          searchQuery: {
            ...searchQuery,
            data: {
              ...(searchQuery.data || {}),
              products: path(
                ['data', 'productSearch', 'products'],
                searchQuery
              ),
            },
            // backwards-compatibility with search-result <= 3.13.x
            facets: path(['data', 'facets'], searchQuery),
            products: path(['data', 'products'], searchQuery),
            recordsFiltered: path(
              ['data', 'facets', 'recordsFiltered'],
              searchQuery
            ),
          },
          searchContext: runtimePage,
          pagesPath: runtimePage,
          map,
          orderBy,
          priceRange,
          page: extraParams.page,
          from: extraParams.from,
          to: extraParams.to,
          facetsLoading: extraParams.facetsLoading,
          maxItemsPerPage,
        })
      }}
    </SearchQuery>
  )
}

export default LocalQuery

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/RadioFilters.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useEffect, useState } from 'react'
import { useIntl } from 'react-intl'
import { RadioGroup } from 'vtex.styleguide'

import ShippingActionButton from './ShippingActionButton'
import useShippingActions from '../hooks/useShippingActions'

const RadioItem = ({ facet, onOpenPostalCodeModal, onOpenPickupModal }) => {
  const intl = useIntl()

  const { actionLabel, actionType } = useShippingActions(facet)

  return (
    <div>
      <div>{facet.name}</div>
      {actionType ? (
        <ShippingActionButton
          label={intl.formatMessage({ id: actionLabel ?? 'none' })}
          openDrawer={
            actionType === 'DELIVERY'
              ? onOpenPostalCodeModal
              : onOpenPickupModal
          }
        />
      ) : undefined}
    </div>
  )
}

const RadioFilters = ({
  facets,
  onChange,
  onOpenPostalCodeModal,
  onOpenPickupModal,
}) => {
  const selectedOption = facets.find(facet => facet.selected)
  const lastValue = selectedOption ? selectedOption.value : undefined

  const [selectedValue, setSelectedValue] = useState(lastValue)

  useEffect(() => {
    setSelectedValue(lastValue)
  }, [lastValue])

  const onRadioSelect = e => {
    const { value } = e.currentTarget

    setSelectedValue(value)

    const clickedFacet = facets.find(facet => facet.value === value)

    if (clickedFacet.selected) {
      return
    }

    onChange(clickedFacet)
  }

  return (
    <RadioGroup
      data-testid="radio-filters"
      hideBorder
      size="small"
      name={facets[0]?.key || 'radio-group'}
      options={facets.map(facet => ({
        id: facet.value,
        value: facet.value,
        label: (
          <RadioItem
            facet={facet}
            onOpenPostalCodeModal={onOpenPostalCodeModal}
            onOpenPickupModal={onOpenPickupModal}
          />
        ),
        disabled: facet.quantity === 0,
      }))}
      value={selectedValue}
      onChange={onRadioSelect}
    />
  )
}

export default RadioFilters

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterOptionTemplate.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import PropTypes from 'prop-types'
import React, {
  useState,
  useCallback,
  useMemo,
  useRef,
  useEffect,
  useContext,
} from 'react'
import { useShippingOptionState } from 'vtex.shipping-option-components/ShippingOptionContext'
import { FormattedMessage, useIntl } from 'react-intl'
import { Collapse } from 'react-collapse'
import classNames from 'classnames'
import { useRuntime } from 'vtex.render-runtime'
import { IconCaret } from 'vtex.store-icons'
import { useCssHandles } from 'vtex.css-handles'
import { Tag } from 'vtex.styleguide'

import styles from '../searchResult.css'
import { SearchFilterBar } from './SearchFilterBar'
import SettingsContext from './SettingsContext'
import useOutsideClick from '../hooks/useOutsideClick'
import ShowMoreFilterButton from './ShowMoreFilterButton'
import { useRenderOnView } from '../hooks/useRenderOnView'
import {
  FACETS_RENDER_THRESHOLD,
  toggleFiltersValue,
} from '../constants/filterConstants'
import { isRadioFilter, isToggleFilter } from '../constants/filterTypes'
import RadioFilters from './RadioFilters'
import ToggleFilters from './ToggleFilters'

/** Returns true if elementRef has ever been scrolled */
const useHasScrolled = elementRef => {
  const [hasScrolled, setHasScrolled] = useState(false)

  useEffect(() => {
    const scrollableElement = elementRef.current

    if (hasScrolled || !scrollableElement) {
      return
    }

    const handleScroll = () => {
      setHasScrolled(true)
    }

    scrollableElement.addEventListener('scroll', handleScroll)

    return () => {
      scrollableElement.removeEventListener('scroll', handleScroll)
    }
  }, [hasScrolled, elementRef])

  return hasScrolled
}

const CSS_HANDLES = [
  'filter__container',
  'filter',
  'filterSelected',
  'filterAvailable',
  'filterIsOpen',
  'filterTitle',
  'filterTitleSpan',
  'filterIcon',
  'filterContent',
  'filterTemplateOverflow',
  'seeMoreButton',
  'filterSelectedFilters',
]

const useSettings = () => useContext(SettingsContext)
/**
 * Collapsable filters container
 */
const FilterOptionTemplate = ({
  id,
  selected = false,
  title,
  quantity,
  collapsable = true,
  children,
  filters,
  initiallyCollapsed = false,
  lazyRender = false,
  truncateFilters = false,
  lastOpenFilter,
  setLastOpenFilter,
  openFiltersMode,
  truncatedFacetsFetched,
  setTruncatedFacetsFetched,
  closeOnOutsideClick = false,
  appliedFiltersOverview,
  navigateToFacet,
  showClearByFilter,
  preventRouteChange,
  handleClear,
  isSelectedFiltersSection = false,
  onOpenPostalCodeModal,
  onOpenPickupModal,
}) => {
  const [open, setOpen] = useState(!initiallyCollapsed)
  const { getSettings } = useRuntime()
  const intl = useIntl()

  const { shippingOption } = useShippingOptionState()

  const isDeliverySelected = shippingOption === 'delivery'
  const isPickupSelected = shippingOption === 'pickup-in-point'

  // Function to apply translation to toggle filter facets only
  const applyToggleFilterTranslation = useCallback(
    facets => {
      // Safety check: ensure facets is an array
      if (!Array.isArray(facets)) {
        return []
      }

      return facets.map(facet => {
        // Only apply translation for facets that:
        // 1. Belong to a toggle filter (check filter.key)
        // 2. Have a mapping in toggleFiltersValue (check facet.name)
        const belongsToToggleFilter = filters.some(
          filter => filter.key === facet.key && isToggleFilter(filter.key)
        )

        if (belongsToToggleFilter) {
          // Build translation key based on shipping state (for shipping filters)
          const getTranslationKey = () => {
            if (isDeliverySelected) {
              return `delivery-${facet.name}`
            }

            if (isPickupSelected) {
              return `pickup-${facet.name}`
            }

            // Default fallback (non-shipping filters or nothing selected)
            return facet.name
          }

          const translationKey = getTranslationKey()
          const translationId = toggleFiltersValue[translationKey]

          // Single unified return - only the translation key changes
          return {
            ...facet,
            name: translationId
              ? intl.formatMessage({ id: translationId })
              : facet.name,
          }
        }

        return facet
      })
    },
    [intl, filters, isDeliverySelected, isPickupSelected]
  )

  const scrollable = useRef()
  const filterRef = useRef()
  const handles = useCssHandles(CSS_HANDLES)
  const { thresholdForFacetSearch } = useSettings()
  const [searchTerm, setSearchTerm] = useState('')
  const [truncated, setTruncated] = useState(true)

  const isLazyRenderEnabled =
    getSettings('vtex.store')?.enableSearchRenderingOptimization

  const isLazyFacetsFetchEnabled =
    getSettings('vtex.store')?.enableFiltersFetchOptimization

  const { hasBeenViewed, dummyElement } = useRenderOnView({
    lazyRender: isLazyRenderEnabled && lazyRender,
    waitForUserInteraction: false,
  })

  const hasScrolled = useHasScrolled(scrollable)
  const isOpen = openFiltersMode === 'many' ? open : lastOpenFilter === title

  const filteredFacets = useMemo(() => {
    if (thresholdForFacetSearch === undefined || searchTerm === '') {
      return filters
    }

    return filters.filter(
      filter => filter.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
    )
  }, [filters, searchTerm, thresholdForFacetSearch])

  // Apply translation to all filtered facets regardless of section type
  const translatedFacets = useMemo(
    () => applyToggleFilterTranslation(filteredFacets),
    [applyToggleFilterTranslation, filteredFacets]
  )

  const openTruncated = value => {
    if (isLazyFacetsFetchEnabled && !truncatedFacetsFetched) {
      setTruncatedFacetsFetched(true)
    }

    setTruncated(value)
  }

  const onClear = useCallback(
    () =>
      navigateToFacet(
        filters.filter(filter => filter.selected),
        preventRouteChange
      ),
    [navigateToFacet, filters, preventRouteChange]
  )

  const renderChildren = () => {
    if (typeof children !== 'function') {
      return children
    }

    const shouldTruncate =
      (truncateFilters || isLazyFacetsFetchEnabled) &&
      quantity > FACETS_RENDER_THRESHOLD + 1

    const shouldLazyRender =
      !shouldTruncate && !hasScrolled && isLazyRenderEnabled

    /** Inexact measure but good enough for displaying a properly sized scrollbar */
    const placeholderSize = shouldLazyRender
      ? (filters.length - FACETS_RENDER_THRESHOLD) * 34
      : 0

    const endSlice =
      shouldLazyRender || (shouldTruncate && truncated)
        ? FACETS_RENDER_THRESHOLD
        : translatedFacets.length

    const isRadio =
      !isSelectedFiltersSection &&
      filters.some(filter => isRadioFilter(filter.key))

    const isToggle =
      !isSelectedFiltersSection &&
      filters.some(filter => isToggleFilter(filter.key))

    return (
      <>
        {isRadio ? (
          <RadioFilters
            facets={translatedFacets}
            onChange={facet =>
              navigateToFacet({ ...facet, title }, preventRouteChange)
            }
            onOpenPostalCodeModal={onOpenPostalCodeModal}
            onOpenPickupModal={onOpenPickupModal}
          />
        ) : isToggle ? (
          <ToggleFilters
            facets={translatedFacets}
            onChange={facet =>
              navigateToFacet({ ...facet, title }, preventRouteChange)
            }
          />
        ) : (
          translatedFacets.slice(0, endSlice).map(children)
        )}
        {placeholderSize > 0 && <div style={{ height: placeholderSize }} />}
        {shouldTruncate && (
          <ShowMoreFilterButton
            quantity={quantity - FACETS_RENDER_THRESHOLD}
            truncated={truncated}
            toggleTruncate={() => openTruncated(isTruncated => !isTruncated)}
          />
        )}
      </>
    )
  }

  const handleCollapse = useCallback(() => {
    if (openFiltersMode === 'many') {
      setOpen(!open)
    } else if (openFiltersMode === 'one') {
      setLastOpenFilter(lastOpenFilter === title ? null : title)
    } else {
      console.error(
        `Invalid openFiltersMode value: ${openFiltersMode}\nCheck the documentation for the values available`
      )
    }
  }, [lastOpenFilter, open, openFiltersMode, setLastOpenFilter, title])

  useOutsideClick(
    filterRef,
    () => {
      // closeOnOutsideClick only works with openFiltersMode == 'one'
      if (closeOnOutsideClick && openFiltersMode === 'one') {
        handleCollapse()
      }
    },
    isOpen
  )
  const isClearButtonVisible =
    showClearByFilter &&
    (id === 'priceRange' ||
      (!selected && filters && filters.some(filter => filter.selected)))

  const handleKeyDown = useCallback(
    e => {
      if (e.key === ' ' && collapsable) {
        e.preventDefault()
        handleCollapse()
      }
    },
    [collapsable, handleCollapse]
  )

  const containerClassName = classNames(
    handles.filter__container,
    { [`${styles.filter__container}--${id}`]: id },
    'bb b--muted-4'
  )

  const titleContainerClassName = classNames(handles.filter, 'pv5', {
    [handles.filterSelected]: selected,
    [handles.filterAvailable]: !selected,
    [handles.filterIsOpen]: isOpen,
  })

  const titleClassName = classNames(
    handles.filterTitle,
    'f5 flex items-center justify-between',
    {
      ttu: selected,
    }
  )

  return (
    <div className={containerClassName} ref={filterRef}>
      <div className={titleContainerClassName}>
        <div
          role="button"
          tabIndex={collapsable ? 0 : undefined}
          className={collapsable ? 'pointer' : ''}
          onClick={() => collapsable && handleCollapse()}
          onKeyDown={handleKeyDown}
          aria-disabled={!collapsable}
        >
          <div className={titleClassName}>
            <span className={`${handles.filterTitleSpan}`}>
              {title}
              {isClearButtonVisible && (
                <span className="ml2">
                  <Tag
                    size="small"
                    onClick={e => {
                      e.stopPropagation()

                      handleClear ? handleClear() : onClear()
                    }}
                  >
                    <FormattedMessage id="store/search-result.filter-button.clear" />
                  </Tag>
                </span>
              )}
            </span>
            {collapsable && (
              <span
                className={classNames(
                  handles.filterIcon,
                  'flex items-center ph5 c-muted-3'
                )}
              >
                <IconCaret orientation={isOpen ? 'up' : 'down'} size={14} />
              </span>
            )}
          </div>
        </div>
        {appliedFiltersOverview === 'show' && filters && !selected && (
          <div className={classNames(handles.filterSelectedFilters, 'f6')}>
            {filters
              .filter(facet => facet.selected)
              .map(facet => facet.name)
              .join(', ')}
          </div>
        )}
      </div>
      <div
        className={classNames(handles.filterTemplateOverflow, {
          'overflow-y-auto': collapsable,
          pb5: !collapsable || isOpen,
        })}
        ref={scrollable}
        data-testid="scrollable-element"
        style={
          !selected &&
          (!(truncateFilters || isLazyFacetsFetchEnabled) ||
            isLazyRenderEnabled)
            ? { maxHeight: '200px' }
            : {}
        }
        aria-hidden={!isOpen}
      >
        {!hasBeenViewed ? (
          dummyElement
        ) : collapsable ? (
          <Collapse
            isOpened={isOpen}
            theme={{ content: handles.filterContent }}
            initialStyle={
              isOpen
                ? {
                    height: 'auto',
                    overflow: 'initial',
                    transition: 'height 500ms',
                  }
                : {
                    height: '0px',
                    overflow: 'hidden',
                    transition: 'height 500ms',
                  }
            }
          >
            {thresholdForFacetSearch !== undefined &&
            thresholdForFacetSearch < filters.length ? (
              <SearchFilterBar name={title} handleChange={setSearchTerm} />
            ) : null}
            {renderChildren()}
          </Collapse>
        ) : (
          renderChildren()
        )}
      </div>
    </div>
  )
}

FilterOptionTemplate.propTypes = {
  /** Identifier to be used by CSS handles */
  id: PropTypes.string,
  /** Filters to be shown, if no filter is provided, treat the children as simple node */
  filters: PropTypes.arrayOf(PropTypes.object),
  /** Function to handle filter rendering or node if no filter is provided */
  children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
  /** Title */
  title: PropTypes.node,
  /** Whether collapsing is enabled */
  collapsable: PropTypes.bool,
  /** Whether it represents the selected filters */
  selected: PropTypes.bool,
  initiallyCollapsed: PropTypes.bool,
  /** Internal prop, whether this component should be rendered only on view */
  lazyRender: PropTypes.bool,
  /** When `true`, truncates filters with more than 10 options displaying a button to see all */
  truncateFilters: PropTypes.bool,
  /** Last open filter */
  lastOpenFilter: PropTypes.string,
  /** Sets the last open filter */
  setLastOpenFilter: PropTypes.func,
  /** Dictates how many filters can be open at the same time */
  openFiltersMode: PropTypes.string,
  /** If the truncated facets were fetched */
  truncatedFacetsFetched: PropTypes.bool,
  /** Sets if the truncated facets were fetched */
  setTruncatedFacetsFetched: PropTypes.func,
  /** Quantity of facets of the current filter */
  quantity: PropTypes.number,
  closeOnOutsideClick: PropTypes.bool,
  /** Whether an overview of the applied filters should be displayed (`"show"`) or not (`"hide"`). */
  appliedFiltersOverview: PropTypes.string,
  navigateToFacet: PropTypes.func,
  showClearByFilter: PropTypes.bool,
  preventRouteChange: PropTypes.bool,
  /** Custom clear handler */
  handleClear: PropTypes.func,
  isSelectedFiltersSection: PropTypes.bool,
  onOpenPostalCodeModal: PropTypes.func,
  onOpenPickupModal: PropTypes.func,
}

export default FilterOptionTemplate

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/SearchFooter.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import PropTypes from 'prop-types'
import { range } from 'ramda'
import React, { Component, Fragment } from 'react'
import { IconCaret } from 'vtex.store-icons'
import { withRuntimeContext } from 'vtex.render-runtime'

import { HEADER_SCROLL_OFFSET } from '../constants/SearchHelpers'

class SearchFooter extends Component {
  static propTypes = {
    /** Amount of products matched with the filters. */
    recordsFiltered: PropTypes.number.isRequired,
    /** Page number */
    page: PropTypes.number.isRequired,
    /** Maximum number of items per page. */
    maxItemsPerPage: PropTypes.number.isRequired,
    /** Returns the link props. */
    getLinkProps: PropTypes.func.isRequired,
    /** Render Runtime Context */
    runtime: PropTypes.shape({
      navigate: PropTypes.func.isRequired,
    }).isRequired,
  }

  handleClick = pageNumber => this.navigateTo(pageNumber)

  navigateTo = pageNumber => {
    const {
      page,
      params,
      queryString: query,
    } = this.props.getLinkProps({
      pageNumber,
    })

    return this.props.runtime.navigate({
      page,
      params,
      query,
      scrollOptions: {
        baseElementId: 'search-result-anchor',
        top: -HEADER_SCROLL_OFFSET,
      },
    })
  }

  getNumberButtonsFromRange = (begin, end) =>
    range(begin, end).map(pageNumber => (
      <div
        className={`ph2 pointer ${
          pageNumber === this.props.page ? 'c-on-base' : 'c-disabled'
        }`}
        data-testid="page-button"
        onClick={() => this.handleClick(pageNumber)}
        key={pageNumber}
      >
        {pageNumber}
      </div>
    ))

  render() {
    const { recordsFiltered, page, maxItemsPerPage } = this.props
    const lastPage = Math.ceil(recordsFiltered / maxItemsPerPage)
    const maxNumberButtons = 5

    return (
      <div className="flex justify-center b">
        {page > 1 && (
          <div
            className="ph2 pointer"
            onClick={() => this.handleClick(page - 1)}
          >
            <IconCaret orientation="left" />
          </div>
        )}
        {page > maxNumberButtons ? (
          <Fragment>
            <div className="ph2 c-disabled pointer">1</div>
            <div className="ph2 c-disabled">…</div>
            {page <= lastPage - maxNumberButtons ? (
              <Fragment>
                <div
                  className="ph2 pointer c-disabled"
                  onClick={() => this.handleClick(page - 1)}
                >
                  {page - 1}
                </div>
                <div
                  className="ph2 pointer c-on-base"
                  onClick={() => this.handleClick(page)}
                >
                  {page}
                </div>
                <div
                  className="ph2 pointer c-disabled"
                  onClick={() => this.handleClick(page + 1)}
                >
                  {page + 1}
                </div>
                <div className="ph2 c-disabled">…</div>
                <div
                  className="ph2 pointer c-disabled"
                  onClick={() => this.handleClick(lastPage)}
                >
                  {lastPage}
                </div>
              </Fragment>
            ) : (
              this.getNumberButtonsFromRange(
                lastPage - maxNumberButtons + 1,
                lastPage + 1
              )
            )}
          </Fragment>
        ) : (
          <Fragment>
            {this.getNumberButtonsFromRange(
              1,
              Math.min(lastPage, maxNumberButtons) + 1
            )}
            {lastPage > maxNumberButtons && (
              <div className="ph2 c-disabled">…</div>
            )}
            {page !== lastPage && lastPage > maxNumberButtons && (
              <div
                className="ph2 pointer c-disabled"
                onClick={() => this.handleClick(lastPage)}
              >
                {lastPage}
              </div>
            )}
          </Fragment>
        )}
        {page < lastPage && (
          <div
            className="ph2 pointer"
            onClick={() => this.handleClick(page + 1)}
          >
            <IconCaret orientation="right" />
          </div>
        )}
      </div>
    )
  }
}

export default withRuntimeContext(SearchFooter)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/ToggleFilters.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useEffect, useState } from 'react'
import { Toggle } from 'vtex.styleguide'

const ToggleFilters = ({ facets, onChange }) => {
  const [toggleStates, setToggleStates] = useState({})

  useEffect(() => {
    const initialStates = {}

    facets.forEach(facet => {
      initialStates[facet.value] = Boolean(facet.selected)
    })
    setToggleStates(initialStates)
  }, [facets])

  const onToggleChange = (facetValue, isChecked) => {
    const clickedFacet = facets.find(facet => facet.value === facetValue)

    if (!clickedFacet) return

    const currentSelected = Boolean(clickedFacet.selected)

    if (currentSelected === isChecked) {
      return
    }

    if (isChecked) {
      const newStates = {}

      facets.forEach(facet => {
        newStates[facet.value] = facet.value === facetValue
      })
      setToggleStates(newStates)
    } else {
      setToggleStates(prev => ({
        ...prev,
        [facetValue]: false,
      }))
    }

    const updatedFacet = { ...clickedFacet, selected: isChecked }

    onChange(updatedFacet)
  }

  return (
    <div data-testid="toggle-filters">
      {facets.map(facet => (
        <div key={facet.value} className="mb3">
          <Toggle
            checked={toggleStates[facet.value] || false}
            disabled={facet.quantity === 0}
            label={facet.name}
            onChange={eventOrValue => {
              const isChecked =
                typeof eventOrValue === 'boolean'
                  ? eventOrValue
                  : eventOrValue?.target?.checked ?? false

              onToggleChange(facet.value, isChecked)
            }}
            size="small"
          />
        </div>
      ))}
    </div>
  )
}

export default ToggleFilters

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/AccordionFilterGroup.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import { useIntl } from 'react-intl'
import classNames from 'classnames'
import { useCssHandles, applyModifiers } from 'vtex.css-handles'
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'
import { usePixel } from 'vtex.pixel-manager'

import AccordionFilterItem from './AccordionFilterItem'
import FacetCheckboxList from './FacetCheckboxList'
import useSelectedFilters from '../hooks/useSelectedFilters'
import { getFilterTitle } from '../constants/SearchHelpers'
import { searchSlugify } from '../utils/slug'
import RadioFilters from './RadioFilters'
import ToggleFilters from './ToggleFilters'
import { pushFilterManipulationPixelEvent } from '../utils/filterManipulationPixelEvents'
import { isRadioFilter, isToggleFilter } from '../constants/filterTypes'

const CSS_HANDLES = ['accordionFilterOpen']

const AccordionFilterGroup = ({
  className,
  title,
  facets,
  quantity,
  show,
  open,
  onOpen,
  onFilterCheck,
  appliedFiltersOverview,
  navigationType,
  initiallyCollapsed,
  truncateFilters,
  truncatedFacetsFetched,
  setTruncatedFacetsFetched,
  onClearFilter,
  showClearByFilter,
  onOpenPostalCodeModal,
  onOpenPickupModal,
}) => {
  const { searchQuery } = useSearchPage()
  const handles = useCssHandles(CSS_HANDLES)
  const filters = useSelectedFilters(facets)
  const selectedFilters = filters.filter(facet => facet.selected)
  const intl = useIntl()
  const facetTitle = getFilterTitle(title, intl)
  const slugifiedFacetTitle = searchSlugify(facetTitle)
  const facetKey = filters.length > 0 ? filters[0].key : null
  const { push } = usePixel()

  const isRadio = filters.some(filter => isRadioFilter(filter.key))
  const isToggle = filters.some(filter => isToggleFilter(filter.key))

  return (
    <AccordionFilterItem
      facetKey={facetKey}
      title={title}
      open={open}
      show={show}
      onOpen={onOpen}
      selectedFilters={selectedFilters}
      appliedFiltersOverview={appliedFiltersOverview}
      navigationType={navigationType}
      initiallyCollapsed={initiallyCollapsed}
      onFilterCheck={onFilterCheck}
      onClearFilter={onClearFilter}
      showClearByFilter={showClearByFilter}
    >
      <div
        className={classNames(
          applyModifiers(handles.accordionFilterOpen, slugifiedFacetTitle),
          className
        )}
      >
        {isRadio ? (
          <RadioFilters
            facets={filters}
            onChange={facet => {
              pushFilterManipulationPixelEvent({
                name: facetTitle,
                value: facet.name,
                products: searchQuery?.products ?? [],
                push,
              })
              onFilterCheck({ ...facet, title: facetTitle }, true)
            }}
            onOpenPostalCodeModal={onOpenPostalCodeModal}
            onOpenPickupModal={onOpenPickupModal}
          />
        ) : isToggle ? (
          <ToggleFilters
            facets={filters}
            onChange={facet => {
              pushFilterManipulationPixelEvent({
                name: facetTitle,
                value: facet.name,
                products: searchQuery?.products ?? [],
                push,
              })
              // Para toggle, usar comportamento similar ao radio (excludente)
              onFilterCheck({ ...facet, title: facetTitle }, true)
            }}
          />
        ) : (
          <FacetCheckboxList
            onFilterCheck={onFilterCheck}
            facets={filters}
            quantity={quantity}
            facetTitle={facetTitle}
            truncateFilters={truncateFilters}
            navigationType={navigationType}
            truncatedFacetsFetched={truncatedFacetsFetched}
            setTruncatedFacetsFetched={setTruncatedFacetsFetched}
          />
        )}
      </div>
    </AccordionFilterItem>
  )
}

export default AccordionFilterGroup

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/SearchFilter.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import PropTypes from 'prop-types'
import React from 'react'
import { useIntl } from 'react-intl'

import FilterOptionTemplate from './FilterOptionTemplate'
import FacetItem from './FacetItem'
import { facetOptionShape } from '../constants/propTypes'
import { getFilterTitle } from '../constants/SearchHelpers'

/**
 * Search Filter Component.
 */
const SearchFilter = ({
  title = 'Default Title',
  facets = [],
  quantity = 0,
  preventRouteChange = false,
  initiallyCollapsed = false,
  navigateToFacet,
  lazyRender = false,
  truncateFilters = false,
  lastOpenFilter,
  setLastOpenFilter,
  openFiltersMode,
  truncatedFacetsFetched,
  setTruncatedFacetsFetched,
  closeOnOutsideClick,
  appliedFiltersOverview,
  showClearByFilter,
  onOpenPostalCodeModal,
  onOpenPickupModal,
}) => {
  const intl = useIntl()
  const sampleFacet = facets && facets.length > 0 ? facets[0] : null
  const facetTitle = getFilterTitle(title, intl)

  return (
    <FilterOptionTemplate
      id={sampleFacet ? sampleFacet.map : null}
      title={facetTitle}
      filters={facets}
      quantity={quantity}
      initiallyCollapsed={initiallyCollapsed}
      lazyRender={lazyRender}
      truncateFilters={truncateFilters}
      lastOpenFilter={lastOpenFilter}
      setLastOpenFilter={setLastOpenFilter}
      openFiltersMode={openFiltersMode}
      truncatedFacetsFetched={truncatedFacetsFetched}
      setTruncatedFacetsFetched={setTruncatedFacetsFetched}
      closeOnOutsideClick={closeOnOutsideClick}
      appliedFiltersOverview={appliedFiltersOverview}
      navigateToFacet={navigateToFacet}
      showClearByFilter={showClearByFilter}
      preventRouteChange={preventRouteChange}
      onOpenPostalCodeModal={onOpenPostalCodeModal}
      onOpenPickupModal={onOpenPickupModal}
    >
      {facet => (
        <FacetItem
          key={facet.name}
          facetTitle={facetTitle}
          facet={facet}
          preventRouteChange={preventRouteChange}
          navigateToFacet={navigateToFacet}
          onOpenPostalCodeModal={onOpenPostalCodeModal}
          onOpenPickupModal={onOpenPickupModal}
        />
      )}
    </FilterOptionTemplate>
  )
}

SearchFilter.propTypes = {
  /** SearchFilter's title. */
  title: PropTypes.string.isRequired,
  /** SearchFilter's options. */
  facets: PropTypes.arrayOf(facetOptionShape),
  /** Prevent route changes */
  preventRouteChange: PropTypes.bool,
  initiallyCollapsed: PropTypes.bool,
  navigateToFacet: PropTypes.func,
  lazyRender: PropTypes.bool,
  /** When `true`, truncates filters with more than 10 options displaying a button to see all */
  truncateFilters: PropTypes.bool,
  /** Last open filter */
  lastOpenFilter: PropTypes.string,
  /** Sets the last open filter */
  setLastOpenFilter: PropTypes.func,
  /** Dictates how many filters can be open at the same time */
  openFiltersMode: PropTypes.string,
  /** If the truncated facets were fetched */
  truncatedFacetsFetched: PropTypes.bool,
  /** Sets if the truncated facets were fetched */
  setTruncatedFacetsFetched: PropTypes.func,
  /** Quantity of facets of the current filter */
  quantity: PropTypes.number,
  closeOnOutsideClick: PropTypes.bool,
  /** Whether an overview of the applied filters should be displayed (`"show"`) or not (`"hide"`). */
  appliedFiltersOverview: PropTypes.string,
  showClearByFilter: PropTypes.bool,
  type: PropTypes.string,
  onOpenPostalCodeModal: PropTypes.func,
  onOpenPickupModal: PropTypes.func,
}

export default SearchFilter

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FacetCheckboxList.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useContext, useState, useMemo } from 'react'
import { useRuntime } from 'vtex.render-runtime'
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'

import SettingsContext from './SettingsContext'
import { SearchFilterBar } from './SearchFilterBar'
import { FACETS_RENDER_THRESHOLD } from '../constants/filterConstants'
import ShowMoreFilterButton from './ShowMoreFilterButton'
import FacetCheckboxListItem from './FacetCheckboxListItem'

const useSettings = () => useContext(SettingsContext)

const FacetCheckboxList = ({
  facets,
  onFilterCheck,
  facetTitle,
  quantity,
  truncateFilters,
  navigationType,
  truncatedFacetsFetched,
  setTruncatedFacetsFetched,
}) => {
  const { searchQuery } = useSearchPage()
  const { showFacetQuantity } = useContext(SettingsContext)
  const { getSettings } = useRuntime()
  const { thresholdForFacetSearch } = useSettings()
  const [searchTerm, setSearchTerm] = useState('')
  const [truncated, setTruncated] = useState(true)
  const isLazyFacetsFetchEnabled =
    getSettings('vtex.store')?.enableFiltersFetchOptimization

  const sampling = searchQuery?.facets?.sampling

  const filteredFacets = useMemo(() => {
    if (thresholdForFacetSearch === undefined || searchTerm === '') {
      return facets
    }

    return facets.filter(
      facet => facet.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
    )
  }, [facets, searchTerm, thresholdForFacetSearch])

  const shouldTruncate =
    navigationType === 'collapsible' &&
    truncateFilters &&
    // The "+ 1" prevents from truncating a single value
    quantity > FACETS_RENDER_THRESHOLD + 1

  const endSlice =
    shouldTruncate && truncated
      ? FACETS_RENDER_THRESHOLD
      : filteredFacets.length

  const showSearchBar =
    thresholdForFacetSearch !== undefined &&
    thresholdForFacetSearch < facets.length

  const openTruncated = value => {
    if (isLazyFacetsFetchEnabled && !truncatedFacetsFetched) {
      setTruncatedFacetsFetched(true)
    }

    setTruncated(value)
  }

  return (
    <>
      {showSearchBar ? (
        <SearchFilterBar name={facetTitle} handleChange={setSearchTerm} />
      ) : null}
      {filteredFacets.slice(0, endSlice).map(facet => {
        return (
          <FacetCheckboxListItem
            facet={facet}
            facetTitle={facetTitle}
            onFilterCheck={onFilterCheck}
            sampling={sampling}
            searchQuery={searchQuery}
            showFacetQuantity={showFacetQuantity}
            key={facet.name}
          />
        )
      })}
      {shouldTruncate && (
        <ShowMoreFilterButton
          quantity={quantity - FACETS_RENDER_THRESHOLD}
          truncated={truncated}
          toggleTruncate={() => openTruncated(prevTruncated => !prevTruncated)}
        />
      )}
    </>
  )
}

export default FacetCheckboxList

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/__tests__/SearchFooter.test.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
/* eslint-env jest */
import { render, fireEvent } from '@vtex/test-tools/react'
import React from 'react'

import SearchFooter from '../components/SearchFooter'

describe('<SearchFooter /> component', () => {
  const getLinkProps = jest.fn().mockImplementation(pageNumber => {
    return {
      page: `mockedPage${pageNumber}`,
      params: 'mockedParams',
    }
  })

  const renderComponent = customProps => {
    const props = {
      recordsFiltered: 20,
      page: 1,
      maxItemsPerPage: 5,
      runtime: { navigate: () => {} },
      getLinkProps,
      ...customProps,
    }

    return { ...render(<SearchFooter {...props} />), getLinkProps }
  }

  it('should be rendered', () => {
    const { asFragment } = renderComponent()

    expect(asFragment()).toBeDefined()
  })

  it('should return link', () => {
    const { getAllByTestId, getLinkProps: props } = renderComponent()
    const [firstLink] = getAllByTestId('page-button')

    fireEvent.click(firstLink)

    // Expect getLinkProps has been called
    expect(props).toBeCalledTimes(1)
  })

  it('should match snapshot', () => {
    const { asFragment } = renderComponent()

    expect(asFragment()).toMatchSnapshot()
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/__tests__/FilterNavigator.test.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
/* eslint-disable jest/no-mocks-import */
/* eslint-env jest */
import React from 'react'
import { render, fireEvent } from '@vtex/test-tools/react'
import categoriesTree from 'categoriesTree'
import { useDevice } from 'vtex.device-detector'

import FilterNavigator from '../FilterNavigator'
import QueryContext from '../components/QueryContext'
import { useRuntime } from '../__mocks__/vtex.render-runtime'
import FilterNavigatorContext from '../components/FilterNavigatorContext'
import specifications from '../__mocks__/specifications'

const mockUseRuntime = useRuntime

const mockNavigate = jest.fn()
const mockSetQuery = jest.fn()

beforeEach(() => {
  jest.clearAllMocks()

  mockUseRuntime.mockImplementation(() => ({
    navigate: mockNavigate,
    setQuery: mockSetQuery,
    getSettings: () => ({}),
    query: {},
  }))

  useDevice.mockImplementation(() => ({ isMobile: false }))
})

describe('<FilterNavigator />', () => {
  const renderComponent = (customProps = { query: 'clothing', map: 'c' }) => {
    const props = {
      map: customProps.map || 'c',
      tree: categoriesTree,
      queryArgs: { query: customProps.query, map: customProps.map || 'c' },
      ...customProps,
    }

    return render(
      <QueryContext.Provider
        value={{ query: customProps.query, map: props.map }}
      >
        <FilterNavigatorContext.Provider
          value={{ query: customProps.query, map: props.map }}
        >
          <FilterNavigator {...props} />
        </FilterNavigatorContext.Provider>
      </QueryContext.Provider>
    )
  }

  it('should be able to render a selected category with no children', () => {
    const tree = [
      {
        name: 'Clothing',
        link: '/Clothing',
        quantity: 0,
        selected: true,
      },
    ]

    const { getByText } = renderComponent({
      query: 'Clothing',
      tree,
    })

    expect(getByText(/Clothing/)).toBeInTheDocument()
  })

  it('should add unselected category on click', () => {
    const { getByText } = renderComponent({ query: 'Eletrônicos' })

    fireEvent.click(getByText(/Smartphones/))

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/eletronicos/smartphones',
        query: '',
      })
    )
  })

  it('should remove selected category on click', () => {
    const tree = JSON.parse(JSON.stringify(categoriesTree))

    const selectedDepartment = tree.find(
      department => department.value === 'eletronicos'
    )

    selectedDepartment.selected = true

    selectedDepartment.children.find(
      category => category.value === 'smartphones'
    ).selected = true

    const { getByText } = renderComponent({
      query: 'eletronicos/smartphones',
      tree,
    })

    fireEvent.click(getByText(/Smartphones/))

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/eletronicos',
        query: '',
      })
    )
  })

  it('should allow to add a root category when there is no categories selected', () => {
    const { getByText } = renderComponent({ query: 'fulltextterm', map: 'ft' })

    fireEvent.click(getByText(/Eletrônicos/))

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/fulltextterm/eletronicos',
      })
    )
  })

  it('should not allow to remove a root category when there is a children selected', () => {
    const tree = JSON.parse(JSON.stringify(categoriesTree))

    const selectedDepartment = tree.find(
      department => department.value === 'eletronicos'
    )

    selectedDepartment.selected = true

    selectedDepartment.children.find(
      category => category.value === 'smartphones'
    ).selected = true

    const { getByText } = renderComponent({
      query: 'eletronicos/smartphones',
      tree,
    })

    fireEvent.click(getByText(/Eletrônicos/))

    expect(mockNavigate).not.toHaveBeenCalled()
  })

  it('should allow to remove a root category when it is a fulltext search', () => {
    const tree = JSON.parse(JSON.stringify(categoriesTree))

    const selectedDepartment = tree.find(
      department => department.value === 'eletronicos'
    )

    selectedDepartment.selected = true

    const { getByText } = renderComponent({
      query: 'fulltextterm',
      map: 'ft',
      tree,
    })

    fireEvent.click(getByText(/Eletrônicos/))

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/fulltextterm',
      })
    )
  })

  it('should add unselected specification on click', () => {
    const { getByTestId } = renderComponent({
      specificationFilters: specifications,
      filters: specifications,
      tree: [],
    })

    fireEvent.click(getByTestId('check-specification-color-pink'))

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/pink',
        query: 'map=color',
      })
    )
  })

  it('should remove selected specification on click', () => {
    const filters = JSON.parse(JSON.stringify(specifications))

    filters
      .find(filter => filter.title === 'store/search.filter.title.color')
      .facets.find(
        filterOption => filterOption.value === 'pink'
      ).selected = true

    const { getAllByTestId } = renderComponent({
      query: 'top/pink',
      map: 'ft,map',
      specificationFilters: filters,
      filters,
      tree: [],
    })

    const [checkBox] = getAllByTestId('check-specification-color-pink')

    fireEvent.click(checkBox)

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/top',
        query: 'map=ft',
      })
    )
  })

  it('should navigate to root category on key down', () => {
    const { getByTestId } = renderComponent({
      query: 'fulltextterm',
      map: 'ft',
    })

    fireEvent.keyDown(getByTestId('root-category-eletronicos'), {
      key: 'Enter',
      code: 'Enter',
    })

    expect(mockNavigate).toHaveBeenCalledTimes(1)
  })

  it('should navigate to child category on key down', () => {
    const tree = JSON.parse(JSON.stringify(categoriesTree))

    const selectedDepartment = tree.find(
      department => department.value === 'eletronicos'
    )

    selectedDepartment.selected = true

    const { getByTestId } = renderComponent({
      query: '/eletronicos',
      map: 'c',
      tree,
    })

    fireEvent.keyDown(getByTestId('categoryItem-Smartphones'), {
      key: 'Enter',
      code: 'Enter',
    })

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/eletronicos/smartphones',
      })
    )
  })

  it('should navigate to child category on key down when categoryFiltersMode is href', () => {
    const tree = JSON.parse(JSON.stringify(categoriesTree))

    const selectedDepartment = tree.find(
      department => department.value === 'eletronicos'
    )

    selectedDepartment.selected = true

    const { getByTestId } = renderComponent({
      query: '/eletronicos',
      map: 'c',
      tree,
      categoryFiltersMode: 'href',
    })

    fireEvent.keyDown(getByTestId('categoryItem-Smartphones'), {
      key: 'Enter',
      code: 'Enter',
    })

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/eletronicos/smartphones',
      })
    )
  })

  it('should remove category on key down', () => {
    const tree = JSON.parse(JSON.stringify(categoriesTree))

    const selectedDepartment = tree.find(
      department => department.value === 'eletronicos'
    )

    selectedDepartment.children.find(
      category => category.value === 'smartphones'
    ).selected = true

    selectedDepartment.selected = true

    const { getByTestId } = renderComponent({
      query: '/eletronicos/smartphones',
      map: 'c,c',
      tree,
    })

    fireEvent.keyDown(getByTestId('selected-category-smartphones'), {
      key: 'Enter',
      code: 'Enter',
    })

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/eletronicos',
      })
    )
  })

  it('should navigate properly when categoryFiltersMode is href', () => {
    const { getByText } = renderComponent({
      query: 'Eletrônicos',
      categoryFiltersMode: 'href',
    })

    fireEvent.click(getByText(/Smartphones/))

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/eletronicos/smartphones',
        query: '',
      })
    )
  })

  it('should be able to clear all selected options from a specification', () => {
    const filters = JSON.parse(JSON.stringify(specifications))

    filters
      .find(filter => filter.title === 'store/search.filter.title.color')
      .facets.find(
        filterOption => filterOption.value === 'green'
      ).selected = true

    filters
      .find(filter => filter.title === 'store/search.filter.title.color')
      .facets.find(
        filterOption => filterOption.value === 'white'
      ).selected = true

    filters
      .find(filter => filter.title === 'Size')
      .facets.find(filterOption => filterOption.value === '40').selected = true

    const { getAllByText } = renderComponent({
      showClearByFilter: true,
      specificationFilters: filters,
      filters,
      tree: [],
      query: '40/green/white',
      map: 'size,color,color',
    })

    fireEvent.click(getAllByText('Clear')[0])

    expect(mockNavigate).toHaveBeenCalledTimes(1)
    expect(mockNavigate).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '/40',
        query: 'map=size',
      })
    )
  })

  it('should open only one filter', () => {
    const { getByTestId, queryByTestId, getByText } = renderComponent({
      openFiltersMode: 'one',
      filters: specifications,
      specificationFilters: specifications,
    })

    fireEvent.click(getByText('Color'))

    expect(getByTestId('check-specification-color-white')).toBeInTheDocument()
    expect(queryByTestId('check-specification-size-40')).toBeNull()

    fireEvent.click(getByText('Size'))

    expect(getByTestId('check-specification-size-40')).toBeInTheDocument()
    expect(queryByTestId('check-specification-color-white')).toBeNull()
  })

  it('should close on outsideClick', () => {
    const { getByTestId, queryByTestId, getByText } = render(
      <div>
        <QueryContext.Provider value={{ query: '/searchterm', map: 'ft' }}>
          <FilterNavigatorContext.Provider
            value={{ query: '/searchterm', map: 'ft' }}
          >
            <FilterNavigator
              openFiltersMode="one"
              filters={specifications}
              specificationFilters={specifications}
              closeOnOutsideClick
            />
          </FilterNavigatorContext.Provider>
        </QueryContext.Provider>
        <span>Outside</span>
      </div>
    )

    fireEvent.click(getByText('Color'))

    expect(getByTestId('check-specification-color-white')).toBeInTheDocument()

    fireEvent.mouseDown(getByText('Outside'))

    expect(queryByTestId('check-specification-color-white')).toBeNull()
  })

  it('should not close on outsideClick if mode is not one', () => {
    const { getByTestId, getByText } = render(
      <div>
        <QueryContext.Provider value={{ query: '/searchterm', map: 'ft' }}>
          <FilterNavigatorContext.Provider
            value={{ query: '/searchterm', map: 'ft' }}
          >
            <FilterNavigator
              openFiltersMode="many"
              filters={specifications}
              specificationFilters={specifications}
              closeOnOutsideClick
            />
          </FilterNavigatorContext.Provider>
        </QueryContext.Provider>
        <span>Outside</span>
      </div>
    )

    fireEvent.mouseDown(getByText('Outside'))

    expect(getByTestId('check-specification-color-white')).toBeInTheDocument()
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/__tests__/Gallery.test.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
/* eslint-disable jest/no-mocks-import */
/* eslint-env jest */
import React, { Fragment } from 'react'
import { render } from '@vtex/test-tools/react'
import { useDevice } from 'vtex.device-detector'
import { products, summary } from 'GalleryMocks'

import Gallery from '../Gallery'
import { ProductList as ProductListStructuredData } from '../__mocks__/vtex.structured-data'
import { useRuntime } from '../__mocks__/vtex.render-runtime'
import { SearchPageContext } from '../__mocks__/vtex.search-page-context'

const { useSearchPageState } = SearchPageContext

const mockUseRuntime = useRuntime
const mockUseSearchPageState = useSearchPageState

beforeEach(() => {
  jest.clearAllMocks()

  mockUseRuntime.mockImplementation(() => ({
    route: { routeId: 'routeId' },
    getSettings: () => ({}),
  }))

  mockUseSearchPageState.mockImplementation(() => ({}))

  useDevice.mockImplementation(() => ({
    isMobile: false,
  }))
})

describe('<Gallery />', () => {
  const renderComponent = customProps => {
    const props = {
      products,
      summary,
      ...customProps,
    }

    return render(
      <Fragment>
        <ProductListStructuredData products={products} />
        <Gallery {...props} />
      </Fragment>
    )
  }

  it('should match snapshot', () => {
    const { asFragment } = renderComponent()

    expect(asFragment()).toMatchSnapshot()
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/__tests__/useFacetNavigation.test.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { renderHook, act } from '@testing-library/react-hooks'

import useFacetNavigation, {
  buildNewQueryMap,
} from '../hooks/useFacetNavigation'
// eslint-disable-next-line jest/no-mocks-import
import { useRuntime } from '../__mocks__/vtex.render-runtime'
import { useFilterNavigator } from '../components/FilterNavigatorContext'

jest.mock('../components/QueryContext')
jest.mock('../components/FilterNavigatorContext')

const mockUseRuntime = useRuntime

const mockNavigate = jest.fn()
const mockSetQuery = jest.fn()

beforeEach(() => {
  jest.clearAllMocks()

  mockUseRuntime.mockImplementation(() => ({
    navigate: mockNavigate,
    setQuery: mockSetQuery,
  }))
})

test('navigating to another category facet', () => {
  // The new idea here is that we event though we pass the map=c,c,c to navigate we dont have it in our response anymore.
  // This state is recordered and sent by search-graphql so we can know where we are in catalog search
  const map = 'c'

  useFilterNavigator.mockImplementation(() => ({
    map,
    query: 'clothing',
  }))

  const { result } = renderHook(() =>
    useFacetNavigation([
      {
        map,
        value: 'clothing',
      },
    ])
  )

  act(() => {
    result.current({ map: 'c', value: 'shorts' })
  })

  const {
    mock: {
      calls: [[navigateCall]],
    },
  } = mockNavigate

  expect(navigateCall.to).toBe('/clothing/shorts')
})

test('joins categories', () => {
  const map = 'c,b'

  useFilterNavigator.mockImplementation(() => ({
    map,
    query: 'clothing/Brand',
  }))

  const selectedFacets = [
    { map: 'c', value: 'clothing' },
    { map: 'b', value: 'Brand' },
  ]

  const { result } = renderHook(() => useFacetNavigation(selectedFacets))

  act(() => {
    result.current({ map: 'c', value: 'shorts' })
  })

  const {
    mock: {
      calls: [[navigateCall]],
    },
  } = mockNavigate

  expect(navigateCall.to).toBe('/clothing/shorts/brand')
  expect(navigateCall.query).toBe('map=b')
})

test('pass array of facets as args work', () => {
  const map = 'c,b'

  useFilterNavigator.mockImplementation(() => ({
    map,
    query: 'clothing/Brand',
  }))

  const selectedFacets = [
    { map: 'c', value: 'clothing' },
    { map: 'b', value: 'Brand' },
  ]

  const facets = [
    {
      map: 'b',
      value: 'OtherBrand',
      title: '',
    },
    {
      map: 'specificationFilter_100',
      value: 'Mens',
      title: 'gender',
    },
    { map: 'c', value: 'shorts', title: '', newQuerySegment: 'shorts' },
  ]

  const { result } = renderHook(() => useFacetNavigation(selectedFacets))

  act(() => {
    result.current(facets)
  })

  const {
    mock: {
      calls: [[navigateCall]],
    },
  } = mockNavigate

  expect(navigateCall.to).toBe('/clothing/shorts/brand/otherbrand/gender_Mens')
  expect(navigateCall.query).toBe('map=b%2Cb')
})

// We've removed the concept of preventRouteChange since the urls should be transformed to the new urls format.

describe('buildNewQueryMap - RadioGroup behavior', () => {
  const radioFacet = {
    key: 'shipping',
    name: 'Delivery',
    value: 'shipping',
    selected: false,
    quantity: 1,
    map: 'shipping',
  }

  const radioFacetSelected = { ...radioFacet, selected: true }
  const otherFacet = {
    key: 'color',
    name: 'Red',
    value: 'red',
    selected: true,
    quantity: 1,
    map: 'color',
  }

  it('removes radio facet from selectedFacets and calls onShouldIgnore(false) when not selected', () => {
    const onShouldIgnore = jest.fn()
    const facets = [radioFacet, otherFacet]
    const selectedFacets = [radioFacet, otherFacet]
    const result = buildNewQueryMap(
      {},
      facets,
      selectedFacets,
      false,
      onShouldIgnore
    )

    expect(result.query).toContain('shipping')
    expect(result.query).not.toContain('red')
    expect(onShouldIgnore).toHaveBeenCalledWith(false)
  })

  it('keeps radio facet and calls onShouldIgnore(true) when selected', () => {
    const onShouldIgnore = jest.fn()
    const facets = [radioFacetSelected, otherFacet]
    const selectedFacets = [radioFacetSelected, otherFacet]
    const result = buildNewQueryMap(
      {},
      facets,
      selectedFacets,
      false,
      onShouldIgnore
    )

    expect(result.query).toContain('ignore')
    expect(onShouldIgnore).toHaveBeenCalledWith(true)
  })

  it('adds ignore to query and radio key to map when shouldIgnore is true and radio selected', () => {
    const onShouldIgnore = jest.fn()
    const facets = [radioFacetSelected, otherFacet]
    const selectedFacets = [radioFacetSelected, otherFacet]
    const result = buildNewQueryMap(
      {},
      facets,
      selectedFacets,
      true,
      onShouldIgnore
    )

    expect(result.query).toContain('ignore')
    expect(result.map).toContain('shipping')
  })

  describe('ToggleGroup behavior', () => {
    const toggleFacetSameDay = {
      key: 'dynamic-estimate',
      value: 'same-day',
      map: 'dynamic-estimate',
      selected: true,
    }

    const toggleFacetNextDay = {
      key: 'dynamic-estimate',
      value: 'next-day',
      map: 'dynamic-estimate',
      selected: true,
    }

    const toggleFacetNotSelected = {
      key: 'dynamic-estimate',
      value: 'same-day',
      map: 'dynamic-estimate',
      selected: false,
    }

    it('should remove other toggle filters of same group when selecting a new one', () => {
      const onShouldIgnore = jest.fn()
      const selectedFacets = [toggleFacetSameDay] // same-day already selected
      const facets = [toggleFacetNextDay] // selecting next-day

      const result = buildNewQueryMap(
        {},
        facets,
        selectedFacets,
        false,
        onShouldIgnore
      )

      // Should contain only next-day, not same-day
      expect(result.query).toContain('next-day')
      expect(result.query).not.toContain('same-day')
      expect(result.map).toContain('dynamic-estimate')
      expect(onShouldIgnore).toHaveBeenCalledWith(false) // toggles don't use ignore
    })

    it('should not use ignore pattern for toggle filters', () => {
      const onShouldIgnore = jest.fn()
      const selectedFacets = []
      const facets = [toggleFacetSameDay]

      const result = buildNewQueryMap(
        {},
        facets,
        selectedFacets,
        false,
        onShouldIgnore
      )

      expect(result.query).not.toContain('ignore')
      expect(result.query).toContain('same-day')
      expect(onShouldIgnore).toHaveBeenCalledWith(false)
    })

    it('should remove toggle filter when deselected', () => {
      const onShouldIgnore = jest.fn()
      const selectedFacets = [toggleFacetSameDay] // same-day already selected
      const facets = [toggleFacetNotSelected] // deselecting same-day

      const result = buildNewQueryMap(
        {},
        facets,
        selectedFacets,
        false,
        onShouldIgnore
      )

      expect(result.query).not.toContain('same-day')
      expect(onShouldIgnore).toHaveBeenCalledWith(false)
    })

    it('should allow radio and toggle filters to coexist', () => {
      const onShouldIgnore = jest.fn()
      const radioPickupFacet = {
        key: 'delivery-options',
        value: 'pickup',
        map: 'delivery-options',
        selected: true,
      }

      const selectedFacets = [radioPickupFacet] // radio already selected
      const facets = [toggleFacetSameDay] // selecting toggle

      const result = buildNewQueryMap(
        {},
        facets,
        selectedFacets,
        false,
        onShouldIgnore
      )

      // Both should be present
      expect(result.query).toContain('pickup')
      expect(result.query).toContain('same-day')
      expect(result.map).toContain('delivery-options')
      expect(result.map).toContain('dynamic-estimate')
      expect(onShouldIgnore).toHaveBeenCalledWith(false) // toggle selected, so no ignore
    })

    it('should select radio when toggle is already selected', () => {
      const onShouldIgnore = jest.fn()
      const radioPickupFacet2 = {
        key: 'delivery-options',
        value: 'pickup',
        map: 'delivery-options',
        selected: true,
      }

      const selectedFacets = [toggleFacetSameDay] // toggle already selected
      const facets = [radioPickupFacet2] // selecting radio

      const result = buildNewQueryMap(
        {},
        facets,
        selectedFacets,
        false,
        onShouldIgnore
      )

      // Both should be present but with ignore for radio
      expect(result.query).toContain('ignore')
      expect(result.query).toContain('same-day')
      expect(result.map).toContain('delivery-options')
      expect(result.map).toContain('dynamic-estimate')
      expect(onShouldIgnore).toHaveBeenCalledWith(true) // radio selected, so use ignore
    })
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/__tests__/AccordionFilterContainer.test.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import { render } from '@vtex/test-tools/react'

import AccordionFilterContainer from '../components/AccordionFilterContainer'
// eslint-disable-next-line jest/no-mocks-import
import { useRuntime } from '../__mocks__/vtex.render-runtime'

const mockUseRuntime = useRuntime

beforeEach(() => {
  jest.clearAllMocks()

  mockUseRuntime.mockImplementation(() => ({
    getSettings: () => ({}),
  }))
})

describe('<AccordionFilterContainer />', () => {
  const renderComponent = () => {
    const filters = [
      {
        title: 'price-range1',
        type: 'PriceRanges',
        facets: [
          {
            name: 'name2',
            quantity: 5,
          },
        ],
      },
    ]

    const priceRange = '1 TO 20000000'
    const tree = []

    return render(
      <AccordionFilterContainer
        filters={filters}
        tree={tree}
        priceRange={priceRange}
      />
    )
  }

  it('check if mobile price range exists', () => {
    const { getByText } = renderComponent()

    expect(getByText('price-range1')).toBeInTheDocument()
  })
})

```