From 9806fd8adf4047db465a89819532350e65d5face Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 11 Apr 2025 09:12:35 +0200 Subject: [PATCH] Update DataTable Signed-off-by: Charles de Dreuille --- packages/canon/report.api.md | 72 +++++++++-- .../DataTable/DataTable.stories.tsx | 49 ++++---- .../src/components/DataTable/DataTable.tsx | 76 ++++++++++++ .../DataTablePagination.stories.tsx | 11 +- .../Pagination/DataTablePagination.tsx | 116 +++++++++--------- .../components/DataTable/Pagination/types.ts | 11 +- .../DataTable/Root/DataTableRoot.stories.tsx | 34 ----- .../DataTable/Root/DataTableRoot.tsx | 43 +++++-- .../src/components/DataTable/Root/types.ts | 11 +- .../canon/src/components/DataTable/index.ts | 13 +- .../components/DataTable/mocked-components.ts | 2 +- .../src/components/Select/Select.stories.tsx | 26 ++++ 12 files changed, 297 insertions(+), 167 deletions(-) create mode 100644 packages/canon/src/components/DataTable/DataTable.tsx delete mode 100644 packages/canon/src/components/DataTable/Root/DataTableRoot.stories.tsx diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index e03b2dfe1e..ec859f34e6 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -222,25 +222,71 @@ export interface ContainerProps { // @public export const DataTable: { - Root: ForwardRefExoticComponent< - DataTableRootProps & RefAttributes + Root: ( + props: { + table: Table_2; + } & React.HTMLAttributes, + ) => JSX.Element; + Pagination: ForwardRefExoticComponent< + DataTablePaginationProps & RefAttributes + >; + Table: ForwardRefExoticComponent< + Omit< + HTMLAttributes & RefAttributes, + 'ref' + > & + RefAttributes + >; + Header: ForwardRefExoticComponent< + Omit< + HTMLAttributes & + RefAttributes, + 'ref' + > & + RefAttributes + >; + Body: ForwardRefExoticComponent< + Omit< + HTMLAttributes & + RefAttributes, + 'ref' + > & + RefAttributes + >; + Row: ForwardRefExoticComponent< + Omit< + HTMLAttributes & RefAttributes, + 'ref' + > & + RefAttributes + >; + Cell: ForwardRefExoticComponent< + Omit< + TdHTMLAttributes & + RefAttributes, + 'ref' + > & + RefAttributes + >; + Head: ForwardRefExoticComponent< + Omit< + ThHTMLAttributes & + RefAttributes, + 'ref' + > & + RefAttributes >; - Pagination: ( - props: DataTablePaginationProps & { - ref?: React.ForwardedRef; - }, - ) => React.ReactElement; }; // @public (undocumented) -export interface DataTablePaginationProps - extends React.HTMLAttributes { - table?: Table_2; -} +export interface DataTablePaginationProps + extends React.HTMLAttributes {} // @public (undocumented) -export interface DataTableRootProps - extends React.HTMLAttributes {} +export interface DataTableRootProps + extends React.HTMLAttributes { + table: Table_2; +} // @public (undocumented) export type Display = 'none' | 'flex' | 'block' | 'inline'; diff --git a/packages/canon/src/components/DataTable/DataTable.stories.tsx b/packages/canon/src/components/DataTable/DataTable.stories.tsx index 5a7c5dbfbc..9bcdc23ad3 100644 --- a/packages/canon/src/components/DataTable/DataTable.stories.tsx +++ b/packages/canon/src/components/DataTable/DataTable.stories.tsx @@ -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; export const Default: Story = { render: () => { const table = useReactTable({ - data: components, + data, columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), }); return ( - - - + + + {table.getHeaderGroups().map(headerGroup => ( - + {headerGroup.headers.map(header => { return ( - + {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} - + ); })} - + ))} - - + + {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map(row => ( - {row.getVisibleCells().map(cell => ( - + {flexRender( cell.column.columnDef.cell, cell.getContext(), )} - + ))} - + )) ) : ( - - + No results. - - + + )} - - - + + + ); }, diff --git a/packages/canon/src/components/DataTable/DataTable.tsx b/packages/canon/src/components/DataTable/DataTable.tsx new file mode 100644 index 0000000000..83251b473b --- /dev/null +++ b/packages/canon/src/components/DataTable/DataTable.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +TableRoot.displayName = Table.Root.displayName; + +const TableHeader = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +TableHeader.displayName = Table.Header.displayName; + +const TableBody = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +TableBody.displayName = Table.Body.displayName; + +const TableRow = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +TableRow.displayName = Table.Row.displayName; + +const TableCell = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +TableCell.displayName = Table.Cell.displayName; + +const TableHead = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +TableHead.displayName = Table.Head.displayName; + +/** + * DataTable component for displaying tabular data with pagination + * @public + */ +export const DataTable = { + Root: DataTableRoot as ( + props: { + table: TanstackTable; + } & React.HTMLAttributes, + ) => JSX.Element, + Pagination: DataTablePagination, + Table: TableRoot, + Header: TableHeader, + Body: TableBody, + Row: TableRow, + Cell: TableCell, + Head: TableHead, +}; diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx index ce3c8288a7..7a45b111b9 100644 --- a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx +++ b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx @@ -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; export const Default: Story = { render: () => { const table = useReactTable({ - data: components, + data, columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), @@ -45,6 +46,10 @@ export const Default: Story = { getFilteredRowModel: getFilteredRowModel(), }); - return ; + return ( + + + + ); }, }; diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx index 51144aa690..c85b90f743 100644 --- a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx +++ b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx @@ -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( - props: DataTablePaginationProps, - ref: React.ForwardedRef, -) { - 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, + ) => { + const { className, ...rest } = props; + const { table } = useDataTable(); + const pageIndex = table?.getState().pagination.pageIndex; + const pageSize = table?.getState().pagination.pageSize; - return ( -
-
- { + table?.setPageSize(Number(value)); + }} + /> +
+
+ {`${(pageIndex ?? 0) * (pageSize ?? 10) + 1} - ${ + ((pageIndex ?? 0) + 1) * (pageSize ?? 10) + } of ${table?.getRowCount()}`} + table?.previousPage()} + disabled={!table?.getCanPreviousPage()} + icon="chevron-left" + /> + table?.nextPage()} + disabled={!table?.getCanNextPage()} + icon="chevron-right" + /> +
-
- {`${(pageIndex ?? 0) * (pageSize ?? 10) + 1} - ${ - ((pageIndex ?? 0) + 1) * (pageSize ?? 10) - } of ${table?.getRowCount()}`} - table?.previousPage()} - disabled={!table?.getCanPreviousPage()} - icon="chevron-left" - /> - table?.nextPage()} - disabled={!table?.getCanNextPage()} - icon="chevron-right" - /> -
- - ); -} - -const ForwardedDataTablePagination = forwardRef(DataTablePagination) as ( - props: DataTablePaginationProps & { - ref?: React.ForwardedRef; + ); }, -) => React.ReactElement; +); -export { ForwardedDataTablePagination as DataTablePagination }; +DataTablePagination.displayName = 'DataTablePagination'; + +export { DataTablePagination }; diff --git a/packages/canon/src/components/DataTable/Pagination/types.ts b/packages/canon/src/components/DataTable/Pagination/types.ts index d8dbe76582..b2a895c465 100644 --- a/packages/canon/src/components/DataTable/Pagination/types.ts +++ b/packages/canon/src/components/DataTable/Pagination/types.ts @@ -14,13 +14,6 @@ * limitations under the License. */ -import { Table } from '@tanstack/react-table'; - /** @public */ -export interface DataTablePaginationProps - extends React.HTMLAttributes { - /** - * The table instance. - */ - table?: Table; -} +export interface DataTablePaginationProps + extends React.HTMLAttributes {} diff --git a/packages/canon/src/components/DataTable/Root/DataTableRoot.stories.tsx b/packages/canon/src/components/DataTable/Root/DataTableRoot.stories.tsx deleted file mode 100644 index 371183aa7c..0000000000 --- a/packages/canon/src/components/DataTable/Root/DataTableRoot.stories.tsx +++ /dev/null @@ -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; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - children: , - }, -}; diff --git a/packages/canon/src/components/DataTable/Root/DataTableRoot.tsx b/packages/canon/src/components/DataTable/Root/DataTableRoot.tsx index 8ca6db736c..8207fe4412 100644 --- a/packages/canon/src/components/DataTable/Root/DataTableRoot.tsx +++ b/packages/canon/src/components/DataTable/Root/DataTableRoot.tsx @@ -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 = { + table: Table; +}; /** @public */ -const DataTableRoot = forwardRef( - ({ className, ...props }, ref) => { +export const DataTableContext = createContext | null>( + null, +); + +/** @public */ +const DataTableRoot = forwardRef( + ( + props: DataTableRootProps, + ref: React.ForwardedRef, + ) => { + const { className, table, ...rest } = props; + return ( -
+ +
+ ); }, ); + DataTableRoot.displayName = 'DataTableRoot'; export { DataTableRoot }; + +/** @public */ +export function useDataTable() { + const context = useContext(DataTableContext); + if (!context) { + throw new Error('useDataTable must be used within a DataTableRoot'); + } + return context as DataTableContextType; +} diff --git a/packages/canon/src/components/DataTable/Root/types.ts b/packages/canon/src/components/DataTable/Root/types.ts index 420181feaf..0a14f81fb3 100644 --- a/packages/canon/src/components/DataTable/Root/types.ts +++ b/packages/canon/src/components/DataTable/Root/types.ts @@ -14,6 +14,13 @@ * limitations under the License. */ +import { Table } from '@tanstack/react-table'; + /** @public */ -export interface DataTableRootProps - extends React.HTMLAttributes {} +export interface DataTableRootProps + extends React.HTMLAttributes { + /** + * The table instance. + */ + table: Table; +} diff --git a/packages/canon/src/components/DataTable/index.ts b/packages/canon/src/components/DataTable/index.ts index 688df19b90..d684c2b48c 100644 --- a/packages/canon/src/components/DataTable/index.ts +++ b/packages/canon/src/components/DataTable/index.ts @@ -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'; diff --git a/packages/canon/src/components/DataTable/mocked-components.ts b/packages/canon/src/components/DataTable/mocked-components.ts index f7861023d3..35b43d887f 100644 --- a/packages/canon/src/components/DataTable/mocked-components.ts +++ b/packages/canon/src/components/DataTable/mocked-components.ts @@ -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', diff --git a/packages/canon/src/components/Select/Select.stories.tsx b/packages/canon/src/components/Select/Select.stories.tsx index dc951a390f..0a621162b5 100644 --- a/packages/canon/src/components/Select/Select.stories.tsx +++ b/packages/canon/src/components/Select/Select.stories.tsx @@ -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 }, + }, +};