diff --git a/public/r/data-table.json b/public/r/data-table.json index 305fe987..64a44947 100644 --- a/public/r/data-table.json +++ b/public/r/data-table.json @@ -55,7 +55,7 @@ }, { "path": "src/components/data-table-slider-filter.tsx", - "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { cn } from \"@/lib/utils\";\nimport { PlusCircle, XCircle } from \"lucide-react\";\n\ninterface Range {\n min: number;\n max: number;\n}\n\ntype RangeValue = [number, number];\n\nfunction getIsValidRange(value: unknown): value is RangeValue {\n return (\n Array.isArray(value) &&\n value.length === 2 &&\n typeof value[0] === \"number\" &&\n typeof value[1] === \"number\"\n );\n}\n\ninterface DataTableSliderFilterProps {\n column: Column;\n title?: string;\n}\n\ninterface RangeInputProps {\n id: string;\n value: number;\n min: number;\n max: number;\n unit?: string;\n onChange: (event: React.ChangeEvent) => void;\n label: string;\n}\n\nconst RangeInput = React.memo(function RangeInput({\n id,\n value,\n min,\n max,\n unit,\n onChange,\n label,\n}: RangeInputProps) {\n return (\n <>\n \n
\n \n {unit && (\n \n {unit}\n \n )}\n
\n \n );\n});\n\nexport function DataTableSliderFilter({\n column,\n title,\n}: DataTableSliderFilterProps) {\n const id = React.useId();\n\n const columnFilterValue = getIsValidRange(column.getFilterValue())\n ? (column.getFilterValue() as RangeValue)\n : undefined;\n\n const defaultRange = column.columnDef.meta?.range;\n const unit = column.columnDef.meta?.unit;\n\n const { min, max, step } = React.useMemo(() => {\n let minValue = 0;\n let maxValue = 100;\n\n if (defaultRange && getIsValidRange(defaultRange)) {\n [minValue, maxValue] = defaultRange;\n } else {\n const values = column.getFacetedMinMaxValues();\n if (values && Array.isArray(values) && values.length === 2) {\n const [facetMinValue, facetMaxValue] = values;\n if (\n typeof facetMinValue === \"number\" &&\n typeof facetMaxValue === \"number\"\n ) {\n minValue = facetMinValue;\n maxValue = facetMaxValue;\n }\n }\n }\n\n const rangeSize = maxValue - minValue;\n const step =\n rangeSize <= 20\n ? 1\n : rangeSize <= 100\n ? Math.ceil(rangeSize / 20)\n : Math.ceil(rangeSize / 50);\n\n return { min: minValue, max: maxValue, step };\n }, [column, defaultRange]);\n\n const range = React.useMemo((): RangeValue => {\n return columnFilterValue ?? [min, max];\n }, [columnFilterValue, min, max]);\n\n const formatValue = React.useCallback((value: number) => {\n return value.toLocaleString(undefined, { maximumFractionDigits: 0 });\n }, []);\n\n const onFromInputChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const numValue = Number(event.target.value);\n if (!Number.isNaN(numValue) && numValue >= min && numValue <= range[1]) {\n column.setFilterValue([numValue, range[1]]);\n }\n },\n [column, min, range],\n );\n\n const onToInputChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const numValue = Number(event.target.value);\n if (!Number.isNaN(numValue) && numValue <= max && numValue >= range[0]) {\n column.setFilterValue([range[0], numValue]);\n }\n },\n [column, max, range],\n );\n\n const onSliderValueChange = React.useCallback(\n (value: RangeValue) => {\n if (Array.isArray(value) && value.length === 2) {\n column.setFilterValue(value);\n }\n },\n [column],\n );\n\n const onReset = React.useCallback(\n (event?: React.MouseEvent) => {\n event?.stopPropagation();\n column.setFilterValue(undefined);\n },\n [column],\n );\n\n const FilterButton = React.useMemo(\n () => (\n \n ),\n [columnFilterValue, title, unit, onReset, formatValue],\n );\n\n return (\n \n {FilterButton}\n \n
\n

\n {title}\n

\n
\n \n \n
\n \n \n
\n \n Clear\n \n
\n
\n );\n}\n", + "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { cn } from \"@/lib/utils\";\nimport { PlusCircle, XCircle } from \"lucide-react\";\n\ninterface Range {\n min: number;\n max: number;\n}\n\ntype RangeValue = [number, number];\n\nfunction getIsValidRange(value: unknown): value is RangeValue {\n return (\n Array.isArray(value) &&\n value.length === 2 &&\n typeof value[0] === \"number\" &&\n typeof value[1] === \"number\"\n );\n}\n\ninterface DataTableSliderFilterProps {\n column: Column;\n title?: string;\n}\n\nexport function DataTableSliderFilter({\n column,\n title,\n}: DataTableSliderFilterProps) {\n const id = React.useId();\n\n const columnFilterValue = getIsValidRange(column.getFilterValue())\n ? (column.getFilterValue() as RangeValue)\n : undefined;\n\n const defaultRange = column.columnDef.meta?.range;\n const unit = column.columnDef.meta?.unit;\n\n const { min, max, step } = React.useMemo(() => {\n let minValue = 0;\n let maxValue = 100;\n\n if (defaultRange && getIsValidRange(defaultRange)) {\n [minValue, maxValue] = defaultRange;\n } else {\n const values = column.getFacetedMinMaxValues();\n if (values && Array.isArray(values) && values.length === 2) {\n const [facetMinValue, facetMaxValue] = values;\n if (\n typeof facetMinValue === \"number\" &&\n typeof facetMaxValue === \"number\"\n ) {\n minValue = facetMinValue;\n maxValue = facetMaxValue;\n }\n }\n }\n\n const rangeSize = maxValue - minValue;\n const step =\n rangeSize <= 20\n ? 1\n : rangeSize <= 100\n ? Math.ceil(rangeSize / 20)\n : Math.ceil(rangeSize / 50);\n\n return { min: minValue, max: maxValue, step };\n }, [column, defaultRange]);\n\n const range = React.useMemo((): RangeValue => {\n return columnFilterValue ?? [min, max];\n }, [columnFilterValue, min, max]);\n\n const formatValue = React.useCallback((value: number) => {\n return value.toLocaleString(undefined, { maximumFractionDigits: 0 });\n }, []);\n\n const onFromInputChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const numValue = Number(event.target.value);\n if (!Number.isNaN(numValue) && numValue >= min && numValue <= range[1]) {\n column.setFilterValue([numValue, range[1]]);\n }\n },\n [column, min, range],\n );\n\n const onToInputChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const numValue = Number(event.target.value);\n if (!Number.isNaN(numValue) && numValue <= max && numValue >= range[0]) {\n column.setFilterValue([range[0], numValue]);\n }\n },\n [column, max, range],\n );\n\n const onSliderValueChange = React.useCallback(\n (value: RangeValue) => {\n if (Array.isArray(value) && value.length === 2) {\n column.setFilterValue(value);\n }\n },\n [column],\n );\n\n const onReset = React.useCallback(\n (event: React.MouseEvent) => {\n if (event.target instanceof HTMLDivElement) {\n event.stopPropagation();\n }\n column.setFilterValue(undefined);\n },\n [column],\n );\n\n return (\n \n \n \n \n \n
\n

\n {title}\n

\n
\n \n
\n \n {unit && (\n \n {unit}\n \n )}\n
\n \n
\n \n {unit && (\n \n {unit}\n \n )}\n
\n
\n \n \n
\n \n Clear\n \n
\n
\n );\n}\n", "type": "registry:component" }, { diff --git a/src/components/data-table-slider-filter.tsx b/src/components/data-table-slider-filter.tsx index c854168d..c165b17f 100644 --- a/src/components/data-table-slider-filter.tsx +++ b/src/components/data-table-slider-filter.tsx @@ -37,55 +37,6 @@ interface DataTableSliderFilterProps { title?: string; } -interface RangeInputProps { - id: string; - value: number; - min: number; - max: number; - unit?: string; - onChange: (event: React.ChangeEvent) => void; - label: string; -} - -const RangeInput = React.memo(function RangeInput({ - id, - value, - min, - max, - unit, - onChange, - label, -}: RangeInputProps) { - return ( - <> - -
- - {unit && ( - - {unit} - - )} -
- - ); -}); - export function DataTableSliderFilter({ column, title, @@ -168,73 +119,100 @@ export function DataTableSliderFilter({ ); const onReset = React.useCallback( - (event?: React.MouseEvent) => { - event?.stopPropagation(); + (event: React.MouseEvent) => { + if (event.target instanceof HTMLDivElement) { + event.stopPropagation(); + } column.setFilterValue(undefined); }, [column], ); - const FilterButton = React.useMemo( - () => ( - - ), - [columnFilterValue, title, unit, onReset, formatValue], - ); - return ( - {FilterButton} + + +

{title}

- - + +
+ + {unit && ( + + {unit} + + )} +
+ +
+ + {unit && ( + + {unit} + + )} +