Improve DataTable

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-04-04 22:15:45 +01:00
parent dbd6b5915c
commit 4c9a6c94ee
13 changed files with 328 additions and 221 deletions
+28 -7
View File
@@ -94,6 +94,34 @@
height: 1.5rem;
}
.canon-DataTableRoot {
gap: var(--canon-space-3);
flex-direction: column;
display: flex;
}
.canon-TablePagination {
padding-top: var(--canon-space-3);
border-top: 1px solid var(--canon-border);
margin-top: var(--canon-space-3);
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-TablePagination--left {
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-TablePagination--right {
justify-content: space-between;
align-items: center;
gap: var(--canon-space-2);
display: flex;
}
.canon-Flex {
display: flex;
}
@@ -163,14 +191,7 @@
}
.table {
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
width: 100%;
padding-bottom: var(--canon-space-0_5);
padding-top: var(--canon-space-0_5);
font-size: var(--canon-font-size-3);
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
position: relative;
overflow: auto;
+21
View File
@@ -0,0 +1,21 @@
.canon-TablePagination {
padding-top: var(--canon-space-3);
border-top: 1px solid var(--canon-border);
margin-top: var(--canon-space-3);
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-TablePagination--left {
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-TablePagination--right {
justify-content: space-between;
align-items: center;
gap: var(--canon-space-2);
display: flex;
}
+28 -7
View File
@@ -9300,6 +9300,34 @@
height: 1.5rem;
}
.canon-DataTableRoot {
gap: var(--canon-space-3);
flex-direction: column;
display: flex;
}
.canon-TablePagination {
padding-top: var(--canon-space-3);
border-top: 1px solid var(--canon-border);
margin-top: var(--canon-space-3);
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-TablePagination--left {
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-TablePagination--right {
justify-content: space-between;
align-items: center;
gap: var(--canon-space-2);
display: flex;
}
.canon-Flex {
display: flex;
}
@@ -9369,14 +9397,7 @@
}
.table {
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
width: 100%;
padding-bottom: var(--canon-space-0_5);
padding-top: var(--canon-space-0_5);
font-size: var(--canon-font-size-3);
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
position: relative;
overflow: auto;
-7
View File
@@ -1,12 +1,5 @@
.table {
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
width: 100%;
padding-bottom: var(--canon-space-0_5);
padding-top: var(--canon-space-0_5);
font-size: var(--canon-font-size-3);
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
position: relative;
overflow: auto;
+58 -41
View File
@@ -213,6 +213,33 @@ export interface ContainerProps {
style?: React.CSSProperties;
}
// @public
export const DataTable: {
Root: ForwardRefExoticComponent<
DataTableRootProps & RefAttributes<HTMLDivElement>
>;
Pagination: ForwardRefExoticComponent<
DataTablePaginationProps & RefAttributes<HTMLDivElement>
>;
};
// @public (undocumented)
export interface DataTablePaginationProps
extends React.HTMLAttributes<HTMLDivElement> {
canNext?: boolean;
canPrevious?: boolean;
onClickNext?: () => void;
onClickPrevious?: () => void;
pageIndex?: number;
pageSize?: number;
setPageSize?: (pageSize: number) => void;
totalRows?: number;
}
// @public (undocumented)
export interface DataTableRootProps
extends React.HTMLAttributes<HTMLDivElement> {}
// @public (undocumented)
export type Display = 'none' | 'flex' | 'block' | 'inline';
@@ -1005,47 +1032,37 @@ export type StylingPropDef = {
parseValue?: (value: string) => string | undefined;
};
// @public (undocumented)
export const Table: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableElement> &
React_3.RefAttributes<HTMLTableElement>
>;
// @public (undocumented)
export const TableBody: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableSectionElement> &
React_3.RefAttributes<HTMLTableSectionElement>
>;
// @public (undocumented)
export const TableCell: React_3.ForwardRefExoticComponent<
React_3.TdHTMLAttributes<HTMLTableCellElement> &
React_3.RefAttributes<HTMLTableCellElement>
>;
// @public (undocumented)
export const TableFooter: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableSectionElement> &
React_3.RefAttributes<HTMLTableSectionElement>
>;
// @public (undocumented)
export const TableHead: React_3.ForwardRefExoticComponent<
React_3.ThHTMLAttributes<HTMLTableCellElement> &
React_3.RefAttributes<HTMLTableCellElement>
>;
// @public (undocumented)
export const TableHeader: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableSectionElement> &
React_3.RefAttributes<HTMLTableSectionElement>
>;
// @public (undocumented)
export const TableRow: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableRowElement> &
React_3.RefAttributes<HTMLTableRowElement>
>;
// @public
export const Table: {
Root: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableElement> &
React_3.RefAttributes<HTMLTableElement>
>;
Header: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableSectionElement> &
React_3.RefAttributes<HTMLTableSectionElement>
>;
Body: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableSectionElement> &
React_3.RefAttributes<HTMLTableSectionElement>
>;
Head: React_3.ForwardRefExoticComponent<
React_3.ThHTMLAttributes<HTMLTableCellElement> &
React_3.RefAttributes<HTMLTableCellElement>
>;
Row: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableRowElement> &
React_3.RefAttributes<HTMLTableRowElement>
>;
Cell: React_3.ForwardRefExoticComponent<
React_3.TdHTMLAttributes<HTMLTableCellElement> &
React_3.RefAttributes<HTMLTableCellElement>
>;
Caption: React_3.ForwardRefExoticComponent<
React_3.HTMLAttributes<HTMLTableCaptionElement> &
React_3.RefAttributes<HTMLTableCaptionElement>
>;
};
// @public (undocumented)
const Text_2: React_2.ForwardRefExoticComponent<
@@ -0,0 +1,113 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Table } from '../Table';
import { DataTable } from '.';
import { components } from './mocked-components';
import { columns } from './mocked-columns';
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
const meta = {
title: 'Components/DataTable',
} satisfies Meta;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const table = useReactTable({
data: components,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});
return (
<DataTable.Root>
<Table.Root>
<Table.Header>
{table.getHeaderGroups().map(headerGroup => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<Table.Head key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</Table.Head>
);
})}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<Table.Row
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map(cell => (
<Table.Cell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Table.Cell>
))}
</Table.Row>
))
) : (
<Table.Row>
<Table.Cell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table.Root>
<DataTable.Pagination
pageIndex={table.getState().pagination.pageIndex}
pageSize={table.getState().pagination.pageSize}
totalRows={table.getRowCount()}
onClickPrevious={() => table.previousPage()}
onClickNext={() => table.nextPage()}
canPrevious={table.getCanPreviousPage()}
canNext={table.getCanNextPage()}
setPageSize={pageSize => table.setPageSize(pageSize)}
/>
</DataTable.Root>
);
},
};
@@ -17,6 +17,10 @@
import { DataTableRoot } from './Root/DataTableRoot';
import { DataTablePagination } from './Pagination/DataTablePagination';
/**
* DataTable component for displaying tabular data with pagination
* @public
*/
export const DataTable = {
Root: DataTableRoot,
Pagination: DataTablePagination,
@@ -15,9 +15,9 @@
*/
import React from 'react';
import { ColumnDef } from '@tanstack/react-table';
import { Component } from './components';
import { Checkbox } from '../../Checkbox';
import { Text } from '../../Text';
import { Component } from './mocked-components';
import { Checkbox } from '../Checkbox';
import { Text } from '../Text';
export const columns: ColumnDef<Component>[] = [
{
@@ -17,20 +17,51 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Table } from '../Table';
import { components } from './mocked-data/components';
import {
// ColumnFiltersState,
// SortingState,
// VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { columns } from './mocked-data/columns';
import { TablePagination } from '../TablePagination';
const invoices = [
{
invoice: 'INV001',
paymentStatus: 'Paid',
totalAmount: '$250.00',
paymentMethod: 'Credit Card',
},
{
invoice: 'INV002',
paymentStatus: 'Pending',
totalAmount: '$150.00',
paymentMethod: 'PayPal',
},
{
invoice: 'INV003',
paymentStatus: 'Unpaid',
totalAmount: '$350.00',
paymentMethod: 'Bank Transfer',
},
{
invoice: 'INV004',
paymentStatus: 'Paid',
totalAmount: '$450.00',
paymentMethod: 'Credit Card',
},
{
invoice: 'INV005',
paymentStatus: 'Paid',
totalAmount: '$550.00',
paymentMethod: 'PayPal',
},
{
invoice: 'INV006',
paymentStatus: 'Pending',
totalAmount: '$200.00',
paymentMethod: 'Bank Transfer',
},
{
invoice: 'INV007',
paymentStatus: 'Unpaid',
totalAmount: '$300.00',
paymentMethod: 'Credit Card',
},
];
const meta = {
title: 'Components/Table',
@@ -38,7 +69,6 @@ const meta = {
subcomponents: {
Body: Table.Body as React.ComponentType<unknown>,
Cell: Table.Cell as React.ComponentType<unknown>,
Pagination: TablePagination as React.ComponentType<unknown>,
Head: Table.Head as React.ComponentType<unknown>,
Header: Table.Header as React.ComponentType<unknown>,
Row: Table.Row as React.ComponentType<unknown>,
@@ -49,87 +79,28 @@ export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const table = useReactTable({
data: components,
columns,
// onSortingChange: setSorting,
// onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
// onColumnVisibilityChange: setColumnVisibility,
// onRowSelectionChange: setRowSelection,
// state: {
// sorting,
// columnFilters,
// columnVisibility,
// rowSelection,
// },
});
return (
<>
<Table.Root>
<Table.Header>
{table.getHeaderGroups().map(headerGroup => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<Table.Head key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</Table.Head>
);
})}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<Table.Row
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map(cell => (
<Table.Cell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Table.Cell>
))}
</Table.Row>
))
) : (
<Table.Row>
<Table.Cell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table.Root>
<TablePagination
pageIndex={table.getState().pagination.pageIndex}
pageSize={table.getState().pagination.pageSize}
totalRows={table.getRowCount()}
onClickPrevious={() => table.previousPage()}
onClickNext={() => table.nextPage()}
canPrevious={table.getCanPreviousPage()}
canNext={table.getCanNextPage()}
setPageSize={pageSize => table.setPageSize(pageSize)}
/>
</>
);
},
render: () => (
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head className="w-[100px]">Invoice</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head>Method</Table.Head>
<Table.Head className="text-right">Amount</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{invoices.map(invoice => (
<Table.Row key={invoice.invoice}>
<Table.Cell className="font-medium">{invoice.invoice}</Table.Cell>
<Table.Cell>{invoice.paymentStatus}</Table.Cell>
<Table.Cell>{invoice.paymentMethod}</Table.Cell>
<Table.Cell className="text-right">
{invoice.totalAmount}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
),
};
@@ -15,7 +15,6 @@
*/
import * as React from 'react';
/** @public */
const TableRoot = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
@@ -26,7 +25,6 @@ const TableRoot = React.forwardRef<
));
TableRoot.displayName = 'TableRoot';
/** @public */
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
@@ -39,7 +37,6 @@ const TableHeader = React.forwardRef<
));
TableHeader.displayName = 'TableHeader';
/** @public */
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
@@ -48,7 +45,6 @@ const TableBody = React.forwardRef<
));
TableBody.displayName = 'TableBody';
/** @public */
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
@@ -59,7 +55,6 @@ const TableRow = React.forwardRef<
));
TableRow.displayName = 'TableRow';
/** @public */
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
@@ -68,7 +63,6 @@ const TableHead = React.forwardRef<
));
TableHead.displayName = 'TableHead';
/** @public */
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
@@ -77,7 +71,6 @@ const TableCell = React.forwardRef<
));
TableCell.displayName = 'TableCell';
/** @public */
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
@@ -90,6 +83,10 @@ const TableCaption = React.forwardRef<
));
TableCaption.displayName = 'TableCaption';
/**
* Table component for displaying tabular data
* @public
*/
export const Table = {
Root: TableRoot,
Header: TableHeader,
@@ -15,4 +15,3 @@
*/
export * from './Table';
export * from './types';
@@ -1,50 +0,0 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface TablePaginationProps
extends React.HTMLAttributes<HTMLDivElement> {
/**
* The current page index.
*/
pageIndex: number;
/**
* The current page size.
*/
pageSize: number;
/**
* The total number of rows.
*/
totalRows: number;
/**
* The function to call when the previous button is clicked.
*/
onClickPrevious: () => void;
/**
* The function to call when the next button is clicked.
*/
onClickNext: () => void;
/**
* Whether the previous button is disabled.
*/
canPrevious: boolean;
/**
* Whether the next button is disabled.
*/
canNext: boolean;
/**
* The function to call when the page size is changed.
*/
setPageSize: (pageSize: number) => void;
}