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 91 | 1x 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>
);
}
|