All files / src/components/SearchPanel/Inputs RangeColumnFilter.tsx

100% Statements 58/58
55.55% Branches 5/9
100% Functions 4/4
100% Lines 58/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 911x 1x 1x 1x   1x 1x   1x                                       1x 1x 5x 8x 8x 8x 8x 8x 8x 8x   8x     8x   5x 5x 5x   5x 5x 5x 5x 5x 5x 3x 5x   5x 1x 1x 1x   5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x   5x 5x 5x 5x 5x 5x 5x 5x 5x 5x       5x  
import { i18n } from '@/helpers/i18n';
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
import Tooltip from '@mui/material/Tooltip';
import type { SliderValueLabelProps } from '@mui/material/Slider';
import { useEffect, useState } from 'react';
import styles from './RangeColumnFilter.module.scss';
 
/**
 * Compact range (slider) filter for header columns. The column header is
 * rendered directly above, so we no longer show a duplicate header and inline
 * MIN / MAX labels — the `valueLabelDisplay="auto"` tooltip surfaces the live
 * values on hover/focus.
 * @component
 * @param props - Component props.
 * - `column` - A TanStack column with `meta.filterVariant === "range"`
 *   whose `getFullRange()` returns `[min, max]`.
 * @returns A 32px-tall MUI `Slider` bound to the column's debounced filter.
 * @example
 * ```tsx
 * // Price column — getFullRange() returns [0, 1000] from facet data.
 * <RangeColumnFilter column={priceColumn} />
 * // Initial render: two thumbs at 0 and 1000.
 * // Dragging the right thumb to 250 →
 * //   column.setFilterValueDebounced([0, 250])
 * // which narrows the table to prices ≤ $250 via TanStack's inNumberRange.
 * ```
 * @source
 */
export default function RangeColumnFilter({ column }: FilterVariantInputProps) {
  function ValueLabelComponent(props: SliderValueLabelProps) {
    const { children, value } = props;
    return (
      <Tooltip
        enterTouchDelay={0}
        placement="top"
        title={value}
        className={`${styles['range-column-filter-tooltip']} no-padding`}
      >
        {children}
      </Tooltip>
    );
  }
 
  const [MIN, MAX] = column.getFullRange();
  const filterValue = column.getFilterValue() as number[] | undefined;
  const [columnFilterRange, setColumnFilterRange] = useState<number[]>(filterValue || [MIN, MAX]);
 
  // Content-based key so the effect only fires when the external filter
  // actually changes — not on every render, where `getFilterValue()` may
  // return a fresh array reference with the same contents and would otherwise
  // clobber in-progress local state.
  const filterKey = filterValue ? filterValue.join('') : '';
  useEffect(() => {
    setColumnFilterRange(filterValue || [MIN, MAX]);
  }, [filterKey, MIN, MAX]);
 
  const handleColumnFilterChange = (_event: Event, newValue: number[]) => {
    setColumnFilterRange(newValue);
    column.setFilterValueDebounced(newValue);
  };
 
  return (
    <Box
      className={styles['range-column-filter']}
      // Center the slider vertically in the filter cell and leave a right-side
      // gap so it doesn't kiss the next filter's input.
      sx={{
        m: 0,
        mr: 0.5,
        px: 1,
        display: 'flex',
        alignItems: 'center',
        height: 32,
      }}
    >
      <Slider
        value={columnFilterRange}
        valueLabelDisplay="auto"
        min={MIN}
        max={MAX}
        size="small"
        onChange={handleColumnFilterChange}
        getAriaLabel={() => i18n('filter_range_aria', [column.getHeaderText() ?? ''])}
        slots={{ valueLabel: ValueLabelComponent }}
        sx={{ p: 0, m: 0 }}
      />
    </Box>
  );
}