Update DataTable

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-04-11 09:12:35 +02:00
parent 4dc47f7519
commit 9806fd8adf
12 changed files with 297 additions and 167 deletions
+59 -13
View File
@@ -222,25 +222,71 @@ export interface ContainerProps {
// @public
export const DataTable: {
Root: ForwardRefExoticComponent<
DataTableRootProps & RefAttributes<HTMLDivElement>
Root: <TData>(
props: {
table: Table_2<TData>;
} & React.HTMLAttributes<HTMLDivElement>,
) => JSX.Element;
Pagination: ForwardRefExoticComponent<
DataTablePaginationProps & RefAttributes<HTMLDivElement>
>;
Table: ForwardRefExoticComponent<
Omit<
HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement>,
'ref'
> &
RefAttributes<HTMLTableElement>
>;
Header: ForwardRefExoticComponent<
Omit<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>,
'ref'
> &
RefAttributes<HTMLTableSectionElement>
>;
Body: ForwardRefExoticComponent<
Omit<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>,
'ref'
> &
RefAttributes<HTMLTableSectionElement>
>;
Row: ForwardRefExoticComponent<
Omit<
HTMLAttributes<HTMLTableRowElement> & RefAttributes<HTMLTableRowElement>,
'ref'
> &
RefAttributes<HTMLTableRowElement>
>;
Cell: ForwardRefExoticComponent<
Omit<
TdHTMLAttributes<HTMLTableCellElement> &
RefAttributes<HTMLTableCellElement>,
'ref'
> &
RefAttributes<HTMLTableCellElement>
>;
Head: ForwardRefExoticComponent<
Omit<
ThHTMLAttributes<HTMLTableCellElement> &
RefAttributes<HTMLTableCellElement>,
'ref'
> &
RefAttributes<HTMLTableCellElement>
>;
Pagination: <TData>(
props: DataTablePaginationProps<TData> & {
ref?: React.ForwardedRef<HTMLDivElement>;
},
) => React.ReactElement;
};
// @public (undocumented)
export interface DataTablePaginationProps<TData>
extends React.HTMLAttributes<HTMLDivElement> {
table?: Table_2<TData>;
}
export interface DataTablePaginationProps
extends React.HTMLAttributes<HTMLDivElement> {}
// @public (undocumented)
export interface DataTableRootProps
extends React.HTMLAttributes<HTMLDivElement> {}
export interface DataTableRootProps<TData>
extends React.HTMLAttributes<HTMLDivElement> {
table: Table_2<TData>;
}
// @public (undocumented)
export type Display = 'none' | 'flex' | 'block' | 'inline';
@@ -15,16 +15,13 @@
*/
import type { Meta, StoryObj } from '@storybook/react';
import { Table } from '../Table';
import { DataTable } from '.';
import { components, Component } from './mocked-components';
import { data, Component } from './mocked-components';
import { columns } from './mocked-columns';
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
@@ -38,65 +35,63 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const table = useReactTable<Component>({
data: components,
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});
return (
<DataTable.Root>
<Table.Root>
<Table.Header>
<DataTable.Root table={table}>
<DataTable.Table>
<DataTable.Header>
{table.getHeaderGroups().map(headerGroup => (
<Table.Row key={headerGroup.id}>
<DataTable.Row key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<Table.Head key={header.id}>
<DataTable.Head key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</Table.Head>
</DataTable.Head>
);
})}
</Table.Row>
</DataTable.Row>
))}
</Table.Header>
<Table.Body>
</DataTable.Header>
<DataTable.Body>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<Table.Row
<DataTable.Row
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map(cell => (
<Table.Cell key={cell.id}>
<DataTable.Cell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Table.Cell>
</DataTable.Cell>
))}
</Table.Row>
</DataTable.Row>
))
) : (
<Table.Row>
<Table.Cell
<DataTable.Row>
<DataTable.Cell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</Table.Cell>
</Table.Row>
</DataTable.Cell>
</DataTable.Row>
)}
</Table.Body>
</Table.Root>
<DataTable.Pagination table={table} />
</DataTable.Body>
</DataTable.Table>
<DataTable.Pagination />
</DataTable.Root>
);
},
@@ -0,0 +1,76 @@
/*
* 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 { forwardRef } from 'react';
import { Table } from '../Table';
import { DataTableRoot } from './Root/DataTableRoot';
import { DataTablePagination } from './Pagination/DataTablePagination';
import { Table as TanstackTable } from '@tanstack/react-table';
const TableRoot = forwardRef<
React.ElementRef<typeof Table.Root>,
React.ComponentPropsWithoutRef<typeof Table.Root>
>(({ className, ...props }, ref) => <Table.Root ref={ref} {...props} />);
TableRoot.displayName = Table.Root.displayName;
const TableHeader = forwardRef<
React.ElementRef<typeof Table.Header>,
React.ComponentPropsWithoutRef<typeof Table.Header>
>(({ className, ...props }, ref) => <Table.Header ref={ref} {...props} />);
TableHeader.displayName = Table.Header.displayName;
const TableBody = forwardRef<
React.ElementRef<typeof Table.Body>,
React.ComponentPropsWithoutRef<typeof Table.Body>
>(({ className, ...props }, ref) => <Table.Body ref={ref} {...props} />);
TableBody.displayName = Table.Body.displayName;
const TableRow = forwardRef<
React.ElementRef<typeof Table.Row>,
React.ComponentPropsWithoutRef<typeof Table.Row>
>(({ className, ...props }, ref) => <Table.Row ref={ref} {...props} />);
TableRow.displayName = Table.Row.displayName;
const TableCell = forwardRef<
React.ElementRef<typeof Table.Cell>,
React.ComponentPropsWithoutRef<typeof Table.Cell>
>(({ className, ...props }, ref) => <Table.Cell ref={ref} {...props} />);
TableCell.displayName = Table.Cell.displayName;
const TableHead = forwardRef<
React.ElementRef<typeof Table.Head>,
React.ComponentPropsWithoutRef<typeof Table.Head>
>(({ className, ...props }, ref) => <Table.Head ref={ref} {...props} />);
TableHead.displayName = Table.Head.displayName;
/**
* DataTable component for displaying tabular data with pagination
* @public
*/
export const DataTable = {
Root: DataTableRoot as <TData>(
props: {
table: TanstackTable<TData>;
} & React.HTMLAttributes<HTMLDivElement>,
) => JSX.Element,
Pagination: DataTablePagination,
Table: TableRoot,
Header: TableHeader,
Body: TableBody,
Row: TableRow,
Cell: TableCell,
Head: TableHead,
};
@@ -23,8 +23,9 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { components, Component } from '../mocked-components';
import { data, Component } from '../mocked-components';
import { columns } from '../mocked-columns';
import { DataTable } from '../DataTable';
const meta = {
title: 'Components/DataTable/Pagination',
@@ -37,7 +38,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const table = useReactTable<Component>({
data: components,
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
@@ -45,6 +46,10 @@ export const Default: Story = {
getFilteredRowModel: getFilteredRowModel(),
});
return <DataTablePagination table={table} />;
return (
<DataTable.Root table={table}>
<DataTable.Pagination />
</DataTable.Root>
);
},
};
@@ -20,67 +20,67 @@ import { DataTablePaginationProps } from './types';
import { IconButton } from '../../IconButton';
import clsx from 'clsx';
import { Select } from '../../Select';
import { useDataTable } from '../Root/DataTableRoot';
/** @public */
function DataTablePagination<TData>(
props: DataTablePaginationProps<TData>,
ref: React.ForwardedRef<HTMLDivElement>,
) {
const { className, table, ...rest } = props;
const pageIndex = table?.getState().pagination.pageIndex;
const pageSize = table?.getState().pagination.pageSize;
const DataTablePagination = forwardRef(
(
props: DataTablePaginationProps,
ref: React.ForwardedRef<HTMLDivElement>,
) => {
const { className, ...rest } = props;
const { table } = useDataTable();
const pageIndex = table?.getState().pagination.pageIndex;
const pageSize = table?.getState().pagination.pageSize;
return (
<div
ref={ref}
className={clsx('canon-TablePagination', className)}
{...rest}
>
<div className="canon-TablePagination--left">
<Select
name="pageSize"
size="small"
placeholder="Show 10 results"
options={[
{ label: 'Show 10 results', value: '10' },
{ label: 'Show 20 results', value: '20' },
{ label: 'Show 30 results', value: '30' },
{ label: 'Show 40 results', value: '40' },
{ label: 'Show 50 results', value: '50' },
]}
value={pageSize?.toString()}
onValueChange={value => {
table?.setPageSize(Number(value));
}}
/>
return (
<div
ref={ref}
className={clsx('canon-TablePagination', className)}
{...rest}
>
<div className="canon-TablePagination--left">
<Select
name="pageSize"
size="small"
placeholder="Show 10 results"
options={[
{ label: 'Show 10 results', value: '10' },
{ label: 'Show 20 results', value: '20' },
{ label: 'Show 30 results', value: '30' },
{ label: 'Show 40 results', value: '40' },
{ label: 'Show 50 results', value: '50' },
]}
value={pageSize?.toString()}
onValueChange={value => {
table?.setPageSize(Number(value));
}}
/>
</div>
<div className="canon-TablePagination--right">
<Text variant="body">{`${(pageIndex ?? 0) * (pageSize ?? 10) + 1} - ${
((pageIndex ?? 0) + 1) * (pageSize ?? 10)
} of ${table?.getRowCount()}`}</Text>
<IconButton
variant="secondary"
size="small"
onClick={() => table?.previousPage()}
disabled={!table?.getCanPreviousPage()}
icon="chevron-left"
/>
<IconButton
variant="secondary"
size="small"
onClick={() => table?.nextPage()}
disabled={!table?.getCanNextPage()}
icon="chevron-right"
/>
</div>
</div>
<div className="canon-TablePagination--right">
<Text variant="body">{`${(pageIndex ?? 0) * (pageSize ?? 10) + 1} - ${
((pageIndex ?? 0) + 1) * (pageSize ?? 10)
} of ${table?.getRowCount()}`}</Text>
<IconButton
variant="secondary"
size="small"
onClick={() => table?.previousPage()}
disabled={!table?.getCanPreviousPage()}
icon="chevron-left"
/>
<IconButton
variant="secondary"
size="small"
onClick={() => table?.nextPage()}
disabled={!table?.getCanNextPage()}
icon="chevron-right"
/>
</div>
</div>
);
}
const ForwardedDataTablePagination = forwardRef(DataTablePagination) as <TData>(
props: DataTablePaginationProps<TData> & {
ref?: React.ForwardedRef<HTMLDivElement>;
);
},
) => React.ReactElement;
);
export { ForwardedDataTablePagination as DataTablePagination };
DataTablePagination.displayName = 'DataTablePagination';
export { DataTablePagination };
@@ -14,13 +14,6 @@
* limitations under the License.
*/
import { Table } from '@tanstack/react-table';
/** @public */
export interface DataTablePaginationProps<TData>
extends React.HTMLAttributes<HTMLDivElement> {
/**
* The table instance.
*/
table?: Table<TData>;
}
export interface DataTablePaginationProps
extends React.HTMLAttributes<HTMLDivElement> {}
@@ -1,34 +0,0 @@
/*
* 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 type { Meta, StoryObj } from '@storybook/react';
import { DataTableRoot } from './DataTableRoot';
import { DataTablePagination } from '../Pagination/DataTablePagination';
import { Default as DataTablePaginationDefault } from '../Pagination/DataTablePagination.stories';
const meta = {
title: 'Components/DataTable/Root',
component: DataTableRoot,
} satisfies Meta<typeof DataTableRoot>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: <DataTablePagination {...DataTablePaginationDefault.args} />,
},
};
@@ -14,22 +14,49 @@
* limitations under the License.
*/
import { forwardRef } from 'react';
import { forwardRef, createContext, useContext } from 'react';
import clsx from 'clsx';
import { DataTableRootProps } from './types';
import { Table } from '@tanstack/react-table';
type DataTableContextType<TData> = {
table: Table<TData>;
};
/** @public */
const DataTableRoot = forwardRef<HTMLDivElement, DataTableRootProps>(
({ className, ...props }, ref) => {
export const DataTableContext = createContext<DataTableContextType<any> | null>(
null,
);
/** @public */
const DataTableRoot = forwardRef(
<TData,>(
props: DataTableRootProps<TData>,
ref: React.ForwardedRef<HTMLDivElement>,
) => {
const { className, table, ...rest } = props;
return (
<div
ref={ref}
className={clsx('canon-DataTable', className)}
{...props}
/>
<DataTableContext.Provider value={{ table }}>
<div
ref={ref}
className={clsx('canon-DataTable', className)}
{...rest}
/>
</DataTableContext.Provider>
);
},
);
DataTableRoot.displayName = 'DataTableRoot';
export { DataTableRoot };
/** @public */
export function useDataTable<TData>() {
const context = useContext(DataTableContext);
if (!context) {
throw new Error('useDataTable must be used within a DataTableRoot');
}
return context as DataTableContextType<TData>;
}
@@ -14,6 +14,13 @@
* limitations under the License.
*/
import { Table } from '@tanstack/react-table';
/** @public */
export interface DataTableRootProps
extends React.HTMLAttributes<HTMLDivElement> {}
export interface DataTableRootProps<TData>
extends React.HTMLAttributes<HTMLDivElement> {
/**
* The table instance.
*/
table: Table<TData>;
}
@@ -14,17 +14,6 @@
* limitations under the License.
*/
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,
};
export * from './DataTable';
export * from './Root/types';
export * from './Pagination/types';
@@ -21,7 +21,7 @@ export interface Component {
tags?: string[];
}
export const components: Component[] = [
export const data: Component[] = [
{
name: 'authentication-and-authorization-service',
owner: 'security-team',
@@ -252,3 +252,29 @@ export const WithErrorAndDescription: Story = {
error: 'Invalid font family',
},
};
export const WithLongLabels: Story = {
args: {
label: 'Document Template',
options: [
{
value: 'annual-report-2024',
label:
'Annual Financial Report and Strategic Planning Document for Fiscal Year 2024 with Comprehensive Analysis of Market Trends, Competitive Landscape, Financial Performance Metrics, Revenue Projections, Cost Optimization Strategies, Risk Assessment, and Long-term Growth Initiatives Across All Business Units and Geographical Regions',
},
{
value: 'product-roadmap',
label:
'Comprehensive Product Development Roadmap and Feature Implementation Timeline Including Detailed Technical Specifications, Resource Allocation Plans, Cross-functional Team Dependencies, Milestone Tracking, Quality Assurance Procedures, User Acceptance Testing Protocols, and Post-launch Support Strategy for All Product Lines and Service Offerings',
},
{
value: 'user-guide',
label:
'Detailed User Guide and Technical Documentation for Advanced System Features Covering Installation Procedures, Configuration Settings, Security Protocols, Troubleshooting Guidelines, Best Practices, Common Use Cases, Performance Optimization Tips, Integration Methods, API Documentation, and Frequently Asked Questions with Step-by-Step Solutions',
},
],
placeholder: 'Select a document template',
name: 'template',
style: { maxWidth: 400 },
},
};