From a00cfedc19f05a7e801bfc94924de296bbd2b814 Mon Sep 17 00:00:00 2001 From: Brian Rinaldi Date: Fri, 20 Jun 2025 16:11:40 -0400 Subject: [PATCH 1/2] Fix table sizing Make the table width to 100% and limit the resizing of columns --- .gitignore | 2 + .../PersistenceCoverage.tsx | 127 ++++++++++------- .../ReplicatorCoverage.tsx | 134 ++++++++++++++---- src/components/ui/table.tsx | 55 +++---- src/hooks/useTableColumnSizing.ts | 28 ++++ 5 files changed, 246 insertions(+), 100 deletions(-) create mode 100644 src/hooks/useTableColumnSizing.ts diff --git a/.gitignore b/.gitignore index 6240da8b..f3c36888 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ pnpm-debug.log* # macOS-specific files .DS_Store +.kiro +.vscode \ No newline at end of file diff --git a/src/components/persistence-coverage/PersistenceCoverage.tsx b/src/components/persistence-coverage/PersistenceCoverage.tsx index 961ff2bf..471d6919 100644 --- a/src/components/persistence-coverage/PersistenceCoverage.tsx +++ b/src/components/persistence-coverage/PersistenceCoverage.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import data from "@/data/persistence/coverage.json"; +import React from 'react'; +import data from '@/data/persistence/coverage.json'; import { Table, TableHeader, @@ -7,7 +7,7 @@ import { TableRow, TableHead, TableCell, -} from "@/components/ui/table"; +} from '@/components/ui/table'; import { useReactTable, getCoreRowModel, @@ -15,15 +15,19 @@ import { flexRender, getFilteredRowModel, getPaginationRowModel, -} from "@tanstack/react-table"; -import type { SortingState, ColumnDef, ColumnFiltersState } from "@tanstack/react-table"; +} from '@tanstack/react-table'; +import type { + SortingState, + ColumnDef, + ColumnFiltersState, +} from '@tanstack/react-table'; const coverage = Object.values(data); const columns: ColumnDef[] = [ { - accessorKey: "full_name", - header: () => "Service", + accessorKey: 'full_name', + header: () => 'Service', cell: ({ row }) => ( {row.original.full_name} ), @@ -31,53 +35,55 @@ const columns: ColumnDef[] = [ filterFn: (row, columnId, filterValue) => { return row.original.full_name .toLowerCase() - .includes((filterValue ?? "").toLowerCase()); + .includes((filterValue ?? '').toLowerCase()); }, - meta: { className: "w-1/3" }, + meta: { className: 'w-[30%]' }, }, { - accessorKey: "support", - header: () => "Supported", + accessorKey: 'support', + header: () => 'Supported', cell: ({ row }) => - row.original.support === "supported" || - row.original.support === "supported with limitations" - ? "✔️" - : "", - meta: { className: "w-1/6" }, + row.original.support === 'supported' || + row.original.support === 'supported with limitations' + ? '✔️' + : '', + meta: { className: 'w-[15%] text-center' }, enableSorting: true, sortingFn: (rowA, rowB) => { // Sort supported to the top const a = rowA.original.support; const b = rowB.original.support; if (a === b) return 0; - if (a === "supported") return -1; - if (b === "supported") return 1; - if (a === "supported with limitations") return -1; - if (b === "supported with limitations") return 1; + if (a === 'supported') return -1; + if (b === 'supported') return 1; + if (a === 'supported with limitations') return -1; + if (b === 'supported with limitations') return 1; return a.localeCompare(b); }, }, { - accessorKey: "test_suite", - header: () => "Persistence Test Suite", - cell: ({ row }) => (row.original.test_suite ? "✔️" : ""), - meta: { className: "w-1/6" }, + accessorKey: 'test_suite', + header: () => 'Persistence Test Suite', + cell: ({ row }) => (row.original.test_suite ? '✔️' : ''), + meta: { className: 'w-[20%] text-center' }, enableSorting: true, }, { - accessorKey: "limitations", - header: () => "Limitations", + accessorKey: 'limitations', + header: () => 'Limitations', cell: ({ row }) => row.original.limitations, enableSorting: false, - meta: { className: "whitespace-normal" }, + meta: { className: 'w-[35%] whitespace-normal' }, }, ]; export default function PersistenceCoverage() { const [sorting, setSorting] = React.useState([ - { id: "full_name", desc: false }, + { id: 'full_name', desc: false }, ]); - const [columnFilters, setColumnFilters] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); const table = useReactTable({ data: coverage, @@ -100,39 +106,53 @@ export default function PersistenceCoverage() { type="text" placeholder="Filter by service name..." value={ - table.getColumn("full_name")?.getFilterValue() as string || "" + (table.getColumn('full_name')?.getFilterValue() as string) || '' } - onChange={e => - table.getColumn("full_name")?.setFilterValue(e.target.value) + onChange={(e) => + table.getColumn('full_name')?.setFilterValue(e.target.value) } className="border rounded px-2 py-1 w-full max-w-xs" /> -
- +
+
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const canSort = header.column.getCanSort(); - const meta = header.column.columnDef.meta as { className?: string } | undefined; + const meta = header.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(header.column.columnDef.header, header.getContext())} + {flexRender( + header.column.columnDef.header, + header.getContext() + )} {canSort && ( - {header.column.getIsSorted() === "asc" - ? " ▲" - : header.column.getIsSorted() === "desc" - ? " ▼" - : ""} + {header.column.getIsSorted() === 'asc' + ? ' ▲' + : header.column.getIsSorted() === 'desc' + ? ' ▼' + : ''} )} @@ -145,13 +165,23 @@ export default function PersistenceCoverage() { {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { - const meta = cell.column.columnDef.meta as { className?: string } | undefined; + const meta = cell.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} ); })} @@ -169,7 +199,8 @@ export default function PersistenceCoverage() { Previous - Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + Page {table.getState().pagination.pageIndex + 1} of{' '} + {table.getPageCount()}
+ +
+
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { - const meta = header.column.columnDef.meta as { className?: string } | undefined; + const meta = header.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(header.column.columnDef.header, header.getContext())} + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {header.column.getCanResize() && ( +
+ )}
); })} @@ -84,13 +155,19 @@ export default function PersistenceCoverage() { {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { - const meta = cell.column.columnDef.meta as { className?: string } | undefined; + const meta = cell.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} ); })} @@ -101,4 +178,11 @@ export default function PersistenceCoverage() { ); -} \ No newline at end of file +} + +// Testing instructions: +// 1. Verify that the table expands to 100% width of its container +// 2. Check that columns maintain their widths during pagination +// 3. Test with different viewport sizes to ensure responsive behavior +// 4. Try resizing columns to ensure the resize functionality works +// 5. Verify that content in cells is properly displayed with ellipsis for overflow diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 5513a5cd..f41393e1 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -1,8 +1,8 @@ -import * as React from "react" +import * as React from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; -function Table({ className, ...props }: React.ComponentProps<"table">) { +function Table({ className, ...props }: React.ComponentProps<'table'>) { return (
) { >
- ) + ); } -function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { +function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) { return ( - ) + ); } -function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { +function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) { return ( - ) + ); } -function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { +function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) { return ( tr]:last:border-b-0", + 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className )} {...props} /> - ) + ); } -function TableRow({ className, ...props }: React.ComponentProps<"tr">) { +function TableRow({ className, ...props }: React.ComponentProps<'tr'>) { return ( - ) + ); } -function TableHead({ className, ...props }: React.ComponentProps<"th">) { +function TableHead({ className, ...props }: React.ComponentProps<'th'>) { return (
[role=checkbox]]:translate-y-[2px]", + 'text-foreground h-10 px-2 text-left align-middle font-medium overflow-hidden text-ellipsis [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className )} {...props} /> - ) + ); } -function TableCell({ className, ...props }: React.ComponentProps<"td">) { +function TableCell({ className, ...props }: React.ComponentProps<'td'>) { return ( [role=checkbox]]:translate-y-[2px]", + 'p-2 align-middle overflow-hidden text-ellipsis [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className )} {...props} /> - ) + ); } function TableCaption({ className, ...props -}: React.ComponentProps<"caption">) { +}: React.ComponentProps<'caption'>) { return (
- ) + ); } export { @@ -111,4 +112,4 @@ export { TableRow, TableCell, TableCaption, -} +}; diff --git a/src/hooks/useTableColumnSizing.ts b/src/hooks/useTableColumnSizing.ts new file mode 100644 index 00000000..49afa578 --- /dev/null +++ b/src/hooks/useTableColumnSizing.ts @@ -0,0 +1,28 @@ +import { useState, useEffect } from 'react'; +import type { ColumnDef, ColumnSizingState } from '@tanstack/react-table'; + +/** + * A hook to manage column sizing for tables + * @param columns The column definitions with size information + * @returns An object with column sizing state and setter + */ +export function useTableColumnSizing(columns: ColumnDef[]) { + const [columnSizing, setColumnSizing] = useState({}); + + // Initialize column sizing with default values from column definitions + useEffect(() => { + const initialSizing: ColumnSizingState = {}; + columns.forEach(col => { + const columnId = typeof col.accessorKey === 'string' ? col.accessorKey : col.id as string; + if (columnId) { + initialSizing[columnId] = col.size || 150; + } + }); + setColumnSizing(initialSizing); + }, [columns]); + + return { + columnSizing, + setColumnSizing, + }; +} \ No newline at end of file From c22eb179197990614596d6eaf75cfdc7bb0d9b25 Mon Sep 17 00:00:00 2001 From: Brian Rinaldi Date: Mon, 23 Jun 2025 12:41:02 -0400 Subject: [PATCH 2/2] Update FeatureCoverage.tsx --- .../feature-coverage/FeatureCoverage.tsx | 152 ++++++++++++------ 1 file changed, 105 insertions(+), 47 deletions(-) diff --git a/src/components/feature-coverage/FeatureCoverage.tsx b/src/components/feature-coverage/FeatureCoverage.tsx index 76fa034e..c70b9b1d 100644 --- a/src/components/feature-coverage/FeatureCoverage.tsx +++ b/src/components/feature-coverage/FeatureCoverage.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React from 'react'; const jsonData = import.meta.glob('/src/data/coverage/*.json'); import { Table, @@ -7,7 +7,7 @@ import { TableRow, TableHead, TableCell, -} from "@/components/ui/table"; +} from '@/components/ui/table'; import { useReactTable, getCoreRowModel, @@ -15,52 +15,59 @@ import { flexRender, getFilteredRowModel, getPaginationRowModel, -} from "@tanstack/react-table"; -import type { SortingState, ColumnDef, ColumnFiltersState } from "@tanstack/react-table"; +} from '@tanstack/react-table'; +import type { + SortingState, + ColumnDef, + ColumnFiltersState, +} from '@tanstack/react-table'; + const columns: ColumnDef[] = [ { - id: "operation", - accessorFn: (row) => ( - Object.keys(row)[0] - ), - header: () => "Operation", + id: 'operation', + accessorFn: (row) => Object.keys(row)[0], + header: () => 'Operation', enableColumnFilter: true, - filterFn: (row, columnId, filterValue) => { + filterFn: (row, _, filterValue) => { let operation = Object.keys(row.original)[0]; return operation .toLowerCase() - .includes((filterValue ?? "").toLowerCase()); + .includes((filterValue ?? '').toLowerCase()); }, - meta: { className: "w-1/3" }, + enableResizing: false, }, { - id: "implemented", - accessorFn: row => row[Object.keys(row)[0]].implemented, - header: () => "Implemented", - cell: ({ getValue }) => (getValue() ? "✔️" : ""), - meta: { className: "w-1/6" }, + id: 'implemented', + accessorFn: (row) => row[Object.keys(row)[0]].implemented, + header: () => 'Implemented', + cell: ({ getValue }) => (getValue() ? '✔️' : ''), enableSorting: true, + enableResizing: false, }, { - id: "image", - accessorFn: row => row[Object.keys(row)[0]].availability, - header: () => "Image", - meta: { className: "w-1/6" }, + id: 'image', + accessorFn: (row) => row[Object.keys(row)[0]].availability, + header: () => 'Image', enableSorting: false, + enableResizing: false, }, ]; -export default function PersistenceCoverage({service}: {service: string}) { +export default function PersistenceCoverage({ service }: { service: string }) { const [coverage, setCoverage] = React.useState([]); const [sorting, setSorting] = React.useState([ - { id: "operation", desc: false }, + { id: 'operation', desc: false }, ]); - const [columnFilters, setColumnFilters] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); React.useEffect(() => { const loadData = async () => { - const moduleData = await jsonData[`/src/data/coverage/${service}.json`]() as { default: Record }; + const moduleData = (await jsonData[ + `/src/data/coverage/${service}.json` + ]()) as { default: Record }; setCoverage(moduleData.default.operations); }; loadData(); @@ -69,7 +76,10 @@ export default function PersistenceCoverage({service}: {service: string}) { const table = useReactTable({ data: coverage, columns, - state: { sorting, columnFilters }, + state: { + sorting, + columnFilters, + }, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), @@ -87,39 +97,65 @@ export default function PersistenceCoverage({service}: {service: string}) { type="text" placeholder="Filter by operation name..." value={ - table.getColumn("operation")?.getFilterValue() as string || "" + (table.getColumn('operation')?.getFilterValue() as string) || '' } - onChange={e => - table.getColumn("operation")?.setFilterValue(e.target.value) + onChange={(e) => + table.getColumn('operation')?.setFilterValue(e.target.value) } className="border rounded px-2 py-1 w-full max-w-xs" /> -
- +
+
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const canSort = header.column.getCanSort(); - const meta = header.column.columnDef.meta as { className?: string } | undefined; + // Calculate percentage-based widths: Operation 60%, others 20% each + const getColumnWidth = (columnId: string) => { + switch (columnId) { + case 'operation': + return '75%'; + case 'implemented': + case 'image': + return '15%'; + default: + return 'auto'; + } + }; + return ( - {flexRender(header.column.columnDef.header, header.getContext())} + {flexRender( + header.column.columnDef.header, + header.getContext() + )} {canSort && ( - {header.column.getIsSorted() === "asc" - ? " ▲" - : header.column.getIsSorted() === "desc" - ? " ▼" - : ""} + {header.column.getIsSorted() === 'asc' + ? ' ▲' + : header.column.getIsSorted() === 'desc' + ? ' ▼' + : ''} )} @@ -132,13 +168,34 @@ export default function PersistenceCoverage({service}: {service: string}) { {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { - const meta = cell.column.columnDef.meta as { className?: string } | undefined; + // Same width calculation for cells + const getColumnWidth = (columnId: string) => { + switch (columnId) { + case 'operation': + return '60%'; + case 'implemented': + case 'image': + return '20%'; + default: + return 'auto'; + } + }; + return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} ); })} @@ -156,7 +213,8 @@ export default function PersistenceCoverage({service}: {service: string}) { Previous - Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + Page {table.getState().pagination.pageIndex + 1} of{' '} + {table.getPageCount()}