import { useTranslation } from 'react-i18next'; import { FieldValueFilter, Matcher, NumberRangeValue } from '@yext/search-headless-react'; import React, { ChangeEvent, useCallback, useEffect, useMemo } from 'react'; import { useFiltersContext } from './FiltersContext'; import { useFilterGroupContext } from './FilterGroupContext'; import { useComposedCssClasses } from '../../hooks'; import { findSelectableFieldValueFilter, isNumberRangeValue, getDefaultFilterDisplayName } from '../../utils/filterutils'; import classNames from 'classnames'; import { useId } from '../../hooks/useId'; /** * The configuration data for a field value filter option. * * @public */ export interface FilterOptionConfig { /** The value used to perform filtering. */ value: string | number | boolean | NumberRangeValue, /** The type of filtering operation used. Defaults to an equals comparison. */ matcher?: Matcher, /** If this particular filter should be selected by default. */ selectedByDefault?: boolean, /** The display name. Defaults to the value prop. */ displayName?: string, /** The number of results associated with this filter. */ resultsCount?: number } /** * Props for the {@link Filters.CheckboxOption}. * * @public */ export interface CheckboxOptionProps extends FilterOptionConfig { /** CSS classes for customizing the component styling defined by {@link Filters.CheckboxCssClasses}. */ customCssClasses?: CheckboxCssClasses } /** * The CSS class interface for {@link Filters.CheckboxOption}. * * @public */ export interface CheckboxCssClasses { input?: string, input___disabled?: string, label?: string, label___disabled?: string, container?: string, optionContainer?: string, tooltipContainer?: string, tooltip?: string } const builtInCssClasses: Readonly = { label: 'text-neutral text-sm font-normal cursor-pointer', label___disabled: 'opacity-50 cursor-not-allowed', input: 'w-3.5 h-3.5 form-checkbox cursor-pointer border border-gray-300 rounded-sm text-primary focus:ring-primary', input___disabled: 'border-gray-200 bg-gray-50 cursor-not-allowed', container: 'flex items-center', optionContainer: 'flex items-center space-x-3 peer', tooltipContainer: 'invisible peer-hover:visible relative -right-5 -top-5', tooltip: 'absolute z-10 left-0 -top-0.5 whitespace-nowrap rounded shadow-lg p-3 text-sm bg-neutral-dark text-white' }; /** * A checkbox component that represents a single FieldValueFilter. * * @public * * @param props - {@link Filters.CheckboxOptionProps} */ export function CheckboxOption(props: CheckboxOptionProps): React.JSX.Element | null { const { t } = useTranslation(); const { fieldId, isOptionsDisabled } = useFilterGroupContext(); const { value, matcher = Matcher.Equals, selectedByDefault = false, displayName = props.value, resultsCount } = props; const cssClasses = useComposedCssClasses(builtInCssClasses, props.customCssClasses); const optionId = useId('facet'); const { selectFilter, filters, applyFilters } = useFiltersContext(); const handleClick = useCallback((checked: boolean) => { selectFilter({ matcher, fieldId, value, displayName: typeof displayName === 'string' ? displayName : undefined, selected: checked }); applyFilters(); }, [applyFilters, fieldId, displayName, selectFilter, value, matcher]); const handleChange = useCallback((evt: ChangeEvent) => { handleClick(evt.target.checked); }, [handleClick]); const optionFilter: FieldValueFilter = useMemo(() => { return { fieldId, matcher, value }; }, [fieldId, value, matcher]); const existingStoredFilter = findSelectableFieldValueFilter(optionFilter, filters); useEffect(() => { if (!existingStoredFilter && selectedByDefault) { selectFilter({ ...optionFilter, displayName: typeof displayName === 'string' ? displayName : undefined, selected: true }); } }, [displayName, selectFilter, selectedByDefault, existingStoredFilter, optionFilter]); const isSelected = existingStoredFilter ? existingStoredFilter.selected : false; const displayNameString = isNumberRangeValue(displayName) ? getDefaultFilterDisplayName(displayName) : displayName.toString(); const labelText = resultsCount ? `${displayNameString} (${resultsCount})` : displayNameString; const inputClasses = classNames(cssClasses.input, { [cssClasses.input___disabled ?? '']: isOptionsDisabled }); const labelClasses = classNames(cssClasses.label, { [cssClasses.label___disabled ?? '']: isOptionsDisabled }); return (
{isOptionsDisabled &&
{t('clearTheRangeToSelectOptions')}
}
); }