Skip to content
Merged
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
69 changes: 32 additions & 37 deletions src/app/components/tasks-table-columns.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import type { ColumnDef } from "@tanstack/react-table";

import { createColumnHelper } from "@tanstack/react-table";
import {
ArrowUpDown,
CalendarIcon,
Expand Down Expand Up @@ -39,6 +38,8 @@ import {
import { updateTask } from "../lib/actions";
import { getPriorityIcon, getStatusIcon } from "../lib/utils";

const columnHelper = createColumnHelper<DataTableFeatures, Task>();

interface GetTasksTableColumnsProps {
statusCounts: Record<Task["status"], number>;
priorityCounts: Record<Task["priority"], number>;
Expand All @@ -53,35 +54,33 @@ export function getTasksTableColumns({
priorityCounts,
estimatedHoursRange,
setRowAction,
}: GetTasksTableColumnsProps): ColumnDef<DataTableFeatures, Task>[] {
return [
}: GetTasksTableColumnsProps) {
return columnHelper.columns([
getDataTableSelectColumn<Task>(),
{
columnHelper.accessor("code", {
id: "code",
accessorKey: "code",
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Task" />
),
cell: ({ row }) => <div className="w-20">{row.getValue("code")}</div>,
cell: ({ cell }) => <div>{cell.getValue()}</div>,
enableSorting: false,
enableHiding: false,
},
{
}),
columnHelper.accessor("title", {
id: "title",
accessorKey: "title",
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Title" />
),
cell: ({ row }) => {
cell: ({ row, cell }) => {
const label = tasks.label.enumValues.find(
(label) => label === row.original.label,
);

return (
<div className="flex items-center gap-2">
{label && <Badge variant="outline">{label}</Badge>}
<span className="max-w-125 truncate font-medium">
{row.getValue("title")}
<span className="min-w-0 truncate font-medium">
{cell.getValue()}
</span>
</div>
);
Expand All @@ -93,16 +92,16 @@ export function getTasksTableColumns({
icon: Text,
},
enableColumnFilter: true,
},
{
size: 500,
}),
columnHelper.accessor("status", {
id: "status",
accessorKey: "status",
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Status" />
),
cell: ({ cell }) => {
const status = tasks.status.enumValues.find(
(status) => status === cell.getValue<Task["status"]>(),
(status) => status === cell.getValue(),
);

if (!status) return null;
Expand All @@ -128,16 +127,15 @@ export function getTasksTableColumns({
icon: CircleDashed,
},
enableColumnFilter: true,
},
{
}),
columnHelper.accessor("priority", {
id: "priority",
accessorKey: "priority",
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Priority" />
),
cell: ({ cell }) => {
const priority = tasks.priority.enumValues.find(
(priority) => priority === cell.getValue<Task["priority"]>(),
(priority) => priority === cell.getValue(),
);

if (!priority) return null;
Expand All @@ -163,17 +161,15 @@ export function getTasksTableColumns({
icon: ArrowUpDown,
},
enableColumnFilter: true,
},
{
}),
columnHelper.accessor("estimatedHours", {
id: "estimatedHours",
accessorKey: "estimatedHours",
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Est. Hours" />
),
cell: ({ cell }) => {
const estimatedHours = cell.getValue<number>();
return <div className="w-20 text-right">{estimatedHours}</div>;
},
cell: ({ cell }) => (
<div className="w-20 text-right">{cell.getValue()}</div>
),
meta: {
label: "Est. Hours",
variant: "range",
Expand All @@ -182,22 +178,21 @@ export function getTasksTableColumns({
icon: Clock,
},
enableColumnFilter: true,
},
{
}),
columnHelper.accessor("createdAt", {
id: "createdAt",
accessorKey: "createdAt",
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Created At" />
),
cell: ({ cell }) => formatDate(cell.getValue<Date>()),
cell: ({ cell }) => formatDate(cell.getValue()),
meta: {
label: "Created At",
variant: "dateRange",
icon: CalendarIcon,
},
enableColumnFilter: true,
},
{
}),
columnHelper.display({
id: "actions",
cell: function Cell({ row }) {
const [isUpdatePending, startUpdateTransition] = React.useTransition();
Expand Down Expand Up @@ -263,7 +258,7 @@ export function getTasksTableColumns({
</DropdownMenu>
);
},
size: 40,
},
];
size: 48,
}),
]);
}
2 changes: 1 addition & 1 deletion src/app/components/tasks-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function TasksTable({
columns,
initialState: {
sorting: [{ id: "createdAt", desc: true }],
columnPinning: { start: [], end: ["actions"] },
columnPinning: { start: ["select"], end: ["actions"] },
},
queryKeys,
getRowId: (originalRow) => originalRow.id,
Expand Down
50 changes: 46 additions & 4 deletions src/lib/data-table-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { Column, ColumnFilter, RowData } from "@tanstack/react-table";
import type {
Column,
ColumnFilter,
RowData,
Table,
} from "@tanstack/react-table";

import type { DataTableFeatures } from "@/lib/data-table-features";
import type {
Expand Down Expand Up @@ -119,6 +124,14 @@ const FILTER_OPERATORS_BY_VARIANT: Record<
multiSelect: MULTI_SELECT_OPERATORS,
};

function getColumnVar(columnId: string, property: "size" | "offset") {
const name = columnId.replace(
/[^a-zA-Z0-9-]/g,
(char) => `_${char.codePointAt(0)?.toString(16)}_`,
);
return `--column-${name}-${property}`;
}

export function getColumnPinningStyle<TData extends RowData>({
column,
withBorder = false,
Expand All @@ -140,16 +153,45 @@ export function getColumnPinningStyle<TData extends RowData>({
? "4px 0 4px -4px var(--border) inset"
: undefined
: undefined,
left: isPinned === "start" ? `${column.getStart("start")}px` : undefined,
right: isPinned === "end" ? `${column.getAfter("end")}px` : undefined,
insetInlineStart:
isPinned === "start"
? `var(${getColumnVar(column.id, "offset")})`
: undefined,
insetInlineEnd:
isPinned === "end"
? `var(${getColumnVar(column.id, "offset")})`
: undefined,
opacity: isPinned ? 0.97 : 1,
position: isPinned ? "sticky" : "relative",
background: isPinned ? "var(--background)" : "var(--background)",
width: column.getSize(),
width: `var(${getColumnVar(column.id, "size")})`,
zIndex: isPinned ? 1 : undefined,
};
}

export function getColumnSizingStyle<TData extends RowData>(
table: Table<DataTableFeatures, TData>,
): React.CSSProperties {
const style: Record<string, string> = {
minWidth: `${table.getTotalSize()}px`,
};

for (const header of table.getFlatHeaders()) {
style[getColumnVar(header.column.id, "size")] = `${header.getSize()}px`;
}

for (const { column } of table.getLeafHeaders()) {
const isPinned = column.getIsPinned();
if (!isPinned) continue;

const offset =
isPinned === "start" ? column.getStart("start") : column.getAfter("end");
style[getColumnVar(column.id, "offset")] = `${offset}px`;
}

return style;
}

export function getFilterOperators(filterVariant: FilterVariant) {
return FILTER_OPERATORS_BY_VARIANT[filterVariant] ?? [];
}
Expand Down
46 changes: 40 additions & 6 deletions src/registry/bases/base/components/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import * as React from "react";

import type { DataTableFeatures } from "@/lib/data-table-features";

import { getColumnPinningStyle } from "@/lib/data-table-utils";
import {
getColumnPinningStyle,
getColumnSizingStyle,
} from "@/lib/data-table-utils";
import { DataTablePagination } from "@/registry/bases/base/components/data-table/data-table-pagination";
import {
Table,
Expand Down Expand Up @@ -44,10 +47,10 @@ export function DataTable<TData extends RowData>({
>
{children}
<div className="overflow-hidden rounded-md border">
<Table>
<DataTableLayout table={table}>
<DataTableHeader table={table} />
<DataTableBody table={table} />
</Table>
</DataTableLayout>
</div>
<div className="flex flex-col gap-2.5">
<DataTablePagination table={table} />
Expand All @@ -59,6 +62,34 @@ export function DataTable<TData extends RowData>({
);
}

interface DataTableLayoutProps<TData extends RowData> {
table: TanstackTable<DataTableFeatures, TData>;
children: React.ReactNode;
}

function DataTableLayout<TData extends RowData>({
table,
children,
}: DataTableLayoutProps<TData>) {
return (
<Subscribe
source={table.store}
selector={(state) => ({
columnOrder: state.columnOrder,
columnPinning: state.columnPinning,
columnSizing: state.columnSizing,
columnVisibility: state.columnVisibility,
})}
>
{() => (
<Table className="table-fixed" style={getColumnSizingStyle(table)}>
{children}
</Table>
)}
</Subscribe>
);
}

function DataTableHeader<TData extends RowData>({
table,
}: {
Expand All @@ -70,7 +101,6 @@ function DataTableHeader<TData extends RowData>({
selector={(state) => ({
columnOrder: state.columnOrder,
columnPinning: state.columnPinning,
columnSizing: state.columnSizing,
columnVisibility: state.columnVisibility,
rowSelection: state.rowSelection,
sorting: state.sorting,
Expand All @@ -84,6 +114,7 @@ function DataTableHeader<TData extends RowData>({
<TableHead
key={header.id}
colSpan={header.colSpan}
className="overflow-hidden"
style={getColumnPinningStyle({ column: header.column })}
>
{header.isPlaceholder ? null : <FlexRender header={header} />}
Expand Down Expand Up @@ -147,7 +178,6 @@ function DataTableRow<TData extends RowData>({
selector={(state) => ({
columnOrder: state.columnOrder,
columnPinning: state.columnPinning,
columnSizing: state.columnSizing,
columnVisibility: state.columnVisibility,
})}
>
Expand All @@ -165,7 +195,11 @@ function DataTableRow<TData extends RowData>({
{(isSelected) => (
<TableRow data-state={isSelected ? "selected" : undefined}>
{cells.map(({ cell, style }) => (
<TableCell key={cell.id} style={style}>
<TableCell
key={cell.id}
className="overflow-hidden"
style={style}
>
<FlexRender cell={cell} />
</TableCell>
))}
Expand Down
Loading
Loading