Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/components/data-table/data-table-date-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,21 @@ export function DataTableDateFilter<TData>({
className="border-dashed font-normal"
>
{hasValue ? (
<div
<span
role="button"
aria-label={`Clear ${title} filter`}
tabIndex={0}
aria-label={`Clear ${title} filter`}
onClick={onReset}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onReset(e as unknown as React.MouseEvent);
}
}}
className="rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
<XCircle />
</div>
</span>
) : (
<CalendarIcon />
)}
Expand Down
17 changes: 12 additions & 5 deletions src/components/data-table/data-table-faceted-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ export function DataTableFacetedFilter<TData, TValue>({
const [open, setOpen] = React.useState(false);

const columnFilterValue = column?.getFilterValue();
const selectedValues = new Set(
Array.isArray(columnFilterValue) ? columnFilterValue : [],
const selectedValues = React.useMemo(
() => new Set(Array.isArray(columnFilterValue) ? columnFilterValue : []),
[columnFilterValue]
);

const onItemSelect = React.useCallback(
Expand Down Expand Up @@ -82,15 +83,21 @@ export function DataTableFacetedFilter<TData, TValue>({
className="border-dashed font-normal"
>
{selectedValues?.size > 0 ? (
<div
<span
role="button"
aria-label={`Clear ${title} filter`}
tabIndex={0}
aria-label={`Clear ${title} filter`}
className="rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
onClick={onReset}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onReset();
}
}}
>
<XCircle />
</div>
</span>
) : (
<PlusCircle />
)}
Expand Down
5 changes: 3 additions & 2 deletions src/components/data-table/data-table-pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ export function DataTablePagination<TData>({
</Select>
</div>
<div className="flex items-center justify-center font-medium text-sm">
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
{table.getPageCount() > 0
? `Page ${table.getState().pagination.pageIndex + 1} of ${table.getPageCount()}`
: "No pages"}
</div>
<div className="flex items-center space-x-2">
<Button
Expand Down
18 changes: 11 additions & 7 deletions src/components/data-table/data-table-slider-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,8 @@ export function DataTableSliderFilter<TData>({
);

const onReset = React.useCallback(
(event: React.MouseEvent) => {
if (event.target instanceof HTMLDivElement) {
event.stopPropagation();
}
(event?: React.MouseEvent) => {
event?.stopPropagation();
column.setFilterValue(undefined);
},
[column],
Expand All @@ -149,15 +147,21 @@ export function DataTableSliderFilter<TData>({
className="border-dashed font-normal"
>
{columnFilterValue ? (
<div
<span
role="button"
aria-label={`Clear ${title} filter`}
tabIndex={0}
aria-label={`Clear ${title} filter`}
className="rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
onClick={onReset}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onReset();
}
}}
>
<XCircle />
</div>
</span>
) : (
<PlusCircle />
)}
Expand Down
136 changes: 65 additions & 71 deletions src/components/data-table/data-table-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,76 +74,70 @@ interface DataTableToolbarFilterProps<TData> {
function DataTableToolbarFilter<TData>({
column,
}: DataTableToolbarFilterProps<TData>) {
{
const columnMeta = column.columnDef.meta;

const onFilterRender = React.useCallback(() => {
if (!columnMeta?.variant) return null;

switch (columnMeta.variant) {
case "text":
return (
<Input
placeholder={columnMeta.placeholder ?? columnMeta.label}
value={(column.getFilterValue() as string) ?? ""}
onChange={(event) => column.setFilterValue(event.target.value)}
className="h-8 w-40 lg:w-56"
/>
);

case "number":
return (
<div className="relative">
<Input
type="number"
inputMode="numeric"
placeholder={columnMeta.placeholder ?? columnMeta.label}
value={(column.getFilterValue() as string) ?? ""}
onChange={(event) => column.setFilterValue(event.target.value)}
className={cn("h-8 w-[120px]", columnMeta.unit && "pr-8")}
/>
{columnMeta.unit && (
<span className="absolute top-0 right-0 bottom-0 flex items-center rounded-r-md bg-accent px-2 text-muted-foreground text-sm">
{columnMeta.unit}
</span>
)}
</div>
);

case "range":
return (
<DataTableSliderFilter
column={column}
title={columnMeta.label ?? column.id}
/>
);

case "date":
case "dateRange":
return (
<DataTableDateFilter
column={column}
title={columnMeta.label ?? column.id}
multiple={columnMeta.variant === "dateRange"}
/>
);

case "select":
case "multiSelect":
return (
<DataTableFacetedFilter
column={column}
title={columnMeta.label ?? column.id}
options={columnMeta.options ?? []}
multiple={columnMeta.variant === "multiSelect"}
/>
);

default:
return null;
}
}, [column, columnMeta]);

return onFilterRender();
const columnMeta = column.columnDef.meta;

if (!columnMeta?.variant) return null;

switch (columnMeta.variant) {
case "text":
return (
<Input
placeholder={columnMeta.placeholder ?? columnMeta.label}
value={(column.getFilterValue() as string) ?? ""}
onChange={(event) => column.setFilterValue(event.target.value)}
className="h-8 w-40 lg:w-56"
/>
);

case "number":
return (
<div className="relative">
<Input
type="number"
inputMode="numeric"
placeholder={columnMeta.placeholder ?? columnMeta.label}
value={(column.getFilterValue() as string) ?? ""}
onChange={(event) => column.setFilterValue(event.target.value)}
className={cn("h-8 w-[120px]", columnMeta.unit && "pr-8")}
/>
{columnMeta.unit && (
<span className="absolute top-0 right-0 bottom-0 flex items-center rounded-r-md bg-accent px-2 text-muted-foreground text-sm">
{columnMeta.unit}
</span>
)}
</div>
);

case "range":
return (
<DataTableSliderFilter
column={column}
title={columnMeta.label ?? column.id}
/>
);

case "date":
case "dateRange":
return (
<DataTableDateFilter
column={column}
title={columnMeta.label ?? column.id}
multiple={columnMeta.variant === "dateRange"}
/>
);

case "select":
case "multiSelect":
return (
<DataTableFacetedFilter
column={column}
title={columnMeta.label ?? column.id}
options={columnMeta.options ?? []}
multiple={columnMeta.variant === "multiSelect"}
/>
);

default:
return null;
}
}
6 changes: 1 addition & 5 deletions src/hooks/use-data-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,7 @@ export function useDataTable<TData>(props: UseDataTableProps<TData>) {
return Object.entries(filterValues).reduce<ColumnFiltersState>(
(filters, [key, value]) => {
if (value !== null) {
const processedValue = Array.isArray(value)
? value
: typeof value === "string" && /[^a-zA-Z0-9]/.test(value)
? value.split(/[^a-zA-Z0-9]+/).filter(Boolean)
: [value];
const processedValue = Array.isArray(value) ? value : [value];

filters.push({
id: key,
Expand Down
23 changes: 16 additions & 7 deletions src/lib/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,21 @@ export const getFiltersStateParser = <TData>(
serialize: (value) => JSON.stringify(value),
eq: (a, b) =>
a.length === b.length &&
a.every(
(filter, index) =>
filter.id === b[index]?.id &&
filter.value === b[index]?.value &&
filter.variant === b[index]?.variant &&
filter.operator === b[index]?.operator,
),
a.every((filter, index) => {
const otherFilter = b[index];
if (!otherFilter) return false;

const valueEqual = Array.isArray(filter.value) && Array.isArray(otherFilter.value)
? filter.value.length === otherFilter.value.length &&
filter.value.every((v, i) => v === otherFilter.value[i])
: filter.value === otherFilter.value;

return (
filter.id === otherFilter.id &&
valueEqual &&
filter.variant === otherFilter.variant &&
filter.operator === otherFilter.operator
);
}),
});
};