import { useTranslation } from 'react-i18next'; import { useSearchUtilities } from '@yext/search-headless-react'; import React, { PropsWithChildren, useMemo, useState } from 'react'; import { CheckboxOption, CollapsibleSection, FilterOptionConfig, SearchInput, FilterGroupProvider, useFilterGroupContext, CheckboxCssClasses } from './Filters'; import { FacetTitle } from './FacetTiltle'; const DEFAULT_CUSTOM_CSS_CLASSES = {}; /** * The CSS class interface for FilterGroup. * * @public */ export interface FilterGroupCssClasses { titleLabel?: string, searchInput?: string, searchInputLabel?: string, optionsContainer?: string, option?: string, optionInput?: string, optionLabel?: string } /** * Props for the FilterGroup component. * * @public */ export interface FilterGroupProps { /** The fieldId corresponding to the filter group. */ fieldId: string, /** {@inheritDoc FilterOptionConfig} */ filterOptions: FilterOptionConfig[], /** The displayed label for the filter group. */ title: string, /** Whether or not the filter is collapsible. Defaults to true. */ collapsible?: boolean, /** * If the filter group is collapsible, whether or not it should start out * expanded. Defaults to true. */ defaultExpanded?: boolean, /** Whether or not to display a text input to search for filter options. */ searchable?: boolean, /** Whether or not to display the visible search input label. Defaults to false. */ showOptionsSearchInputLabel?: boolean, /** CSS classes for customizing the component styling. */ customCssClasses?: FilterGroupCssClasses, /** Limit on the number of options to be displayed. */ showMoreLimit?: number } /** * Renders a group of selectable filters with support for searching and collapsing. */ export function FilterGroup({ fieldId, filterOptions, title, collapsible = true, defaultExpanded = true, searchable, showOptionsSearchInputLabel = false, customCssClasses = DEFAULT_CUSTOM_CSS_CLASSES, showMoreLimit = filterOptions.length, children }: PropsWithChildren) { const { t } = useTranslation(); const cssClasses = useMemo(() => { const { option, optionLabel, optionInput, ...remainingClasses } = customCssClasses; return { ...remainingClasses, ...option && { optionContainer: option }, ...optionLabel && { label: optionLabel }, ...optionInput && { input: optionInput } }; }, [customCssClasses]); const searchInputLabel = showOptionsSearchInputLabel ? t('filterGroupSearchInputLabel', { title }) : undefined; return ( {collapsible ? ( {searchable && } {children} ) : (
{searchable && } {children}
)}
); } function CheckboxOptions({ filterOptions, showMoreLimit, cssClasses }: { filterOptions: FilterOptionConfig[], showMoreLimit: number, cssClasses: CheckboxCssClasses }) { const { t } = useTranslation(); const searchUtilities = useSearchUtilities(); const { searchValue } = useFilterGroupContext(); const shouldRenderOption = ( option: FilterOptionConfig ) => { return searchUtilities.isCloseMatch(option.displayName || option.value.toString(), searchValue); }; let displayedOptions = filterOptions.filter(shouldRenderOption).map(o => { return ( ); }); const isLimited = displayedOptions.length > showMoreLimit; const [showAll, setShowAll] = useState(!isLimited); displayedOptions = displayedOptions.slice(0, showAll ? displayedOptions.length : showMoreLimit); return ( <> {displayedOptions} {isLimited && /* eslint-disable-next-line react-perf/jsx-no-new-function-as-prop */ } ); }