Add DataTable

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-04-04 21:32:59 +01:00
parent 68b49e3433
commit 2b74f7be4b
14 changed files with 281 additions and 155 deletions
@@ -0,0 +1,39 @@
/*
* 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 { DataTablePagination } from './DataTablePagination';
const meta = {
title: 'Components/DataTable/Pagination',
component: DataTablePagination,
} satisfies Meta<typeof DataTablePagination>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
pageIndex: 0,
pageSize: 10,
totalRows: 100,
onClickPrevious: () => {},
onClickNext: () => {},
canPrevious: true,
canNext: true,
setPageSize: () => {},
},
};
@@ -0,0 +1,21 @@
.canon-TablePagination {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: var(--canon-space-3);
border-top: 1px solid var(--canon-border);
margin-top: var(--canon-space-3);
}
.canon-TablePagination--left {
display: flex;
align-items: center;
justify-content: space-between;
}
.canon-TablePagination--right {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--canon-space-2);
}
@@ -0,0 +1,86 @@
/*
* 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 * as React from 'react';
import { Text } from '../../Text';
import { DataTablePaginationProps } from './types';
import { IconButton } from '../../IconButton';
import clsx from 'clsx';
import { Select } from '../../Select';
/** @public */
const DataTablePagination = React.forwardRef<
HTMLDivElement,
DataTablePaginationProps
>(({ className, ...props }, ref) => {
const {
pageIndex,
pageSize,
onClickPrevious,
onClickNext,
canPrevious,
canNext,
totalRows,
setPageSize,
} = props;
return (
<div
ref={ref}
className={clsx('canon-TablePagination', className)}
{...props}
>
<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 => {
setPageSize?.(Number(value));
}}
/>
</div>
<div className="canon-TablePagination--right">
<Text variant="body">{`${(pageIndex ?? 0) * (pageSize ?? 10) + 1} - ${
((pageIndex ?? 0) + 1) * (pageSize ?? 10)
} of ${totalRows}`}</Text>
<IconButton
variant="secondary"
size="small"
onClick={onClickPrevious}
disabled={!canPrevious}
icon="chevron-left"
/>
<IconButton
variant="secondary"
size="small"
onClick={onClickNext}
disabled={!canNext}
icon="chevron-right"
/>
</div>
</div>
);
});
DataTablePagination.displayName = 'DataTablePagination';
export { DataTablePagination };
@@ -13,38 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface TablePaginationProps
/** @public */
export interface DataTablePaginationProps
extends React.HTMLAttributes<HTMLDivElement> {
/**
* The current page index.
*/
pageIndex: number;
pageIndex?: number;
/**
* The current page size.
*/
pageSize: number;
pageSize?: number;
/**
* The total number of rows.
*/
totalRows: number;
totalRows?: number;
/**
* The function to call when the previous button is clicked.
*/
onClickPrevious: () => void;
onClickPrevious?: () => void;
/**
* The function to call when the next button is clicked.
*/
onClickNext: () => void;
onClickNext?: () => void;
/**
* Whether the previous button is disabled.
*/
canPrevious: boolean;
canPrevious?: boolean;
/**
* Whether the next button is disabled.
*/
canNext: boolean;
canNext?: boolean;
/**
* The function to call when the page size is changed.
*/
setPageSize: (pageSize: number) => void;
setPageSize?: (pageSize: number) => void;
}
@@ -0,0 +1,35 @@
/*
* 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 { 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} />,
},
};
@@ -0,0 +1,5 @@
.canon-DataTableRoot {
display: flex;
flex-direction: column;
gap: var(--canon-space-3);
}
@@ -14,5 +14,22 @@
* limitations under the License.
*/
export * from './TablePagination';
export * from './types';
import * as React from 'react';
import clsx from 'clsx';
import { DataTableRootProps } from './types';
/** @public */
const DataTableRoot = React.forwardRef<HTMLDivElement, DataTableRootProps>(
({ className, ...props }, ref) => {
return (
<div
ref={ref}
className={clsx('canon-DataTable', className)}
{...props}
/>
);
},
);
DataTableRoot.displayName = 'DataTableRoot';
export { DataTableRoot };
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
export const DataTable = () => {
return <div>DataTable</div>;
};
/** @public */
export interface DataTableRootProps
extends React.HTMLAttributes<HTMLDivElement> {}
@@ -14,4 +14,13 @@
* limitations under the License.
*/
export * from './DataTable';
import { DataTableRoot } from './Root/DataTableRoot';
import { DataTablePagination } from './Pagination/DataTablePagination';
export const DataTable = {
Root: DataTableRoot,
Pagination: DataTablePagination,
};
export * from './Root/types';
export * from './Pagination/types';
@@ -70,47 +70,55 @@ export const Default: Story = {
});
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.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.Row>
<Table.Cell colSpan={columns.length} className="h-24 text-center">
No results.
</Table.Cell>
</Table.Row>
)}
</Table.Body>
))}
</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}
@@ -121,7 +129,7 @@ export const Default: Story = {
canNext={table.getCanNextPage()}
setPageSize={pageSize => table.setPageSize(pageSize)}
/>
</Table.Root>
</>
);
},
};
@@ -2,13 +2,6 @@
position: relative;
overflow: auto;
width: 100%;
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
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);
table {
width: 100%;
@@ -59,13 +52,3 @@
box-shadow: inset 0px 2px 0 0 var(--canon-bg-surface-1),
inset 0px -2px 0 0 var(--canon-bg-surface-1);
}
.canon-TablePagination {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--canon-space-3);
background-color: var(--canon-bg-surface-1);
border-top: 1px solid var(--canon-border);
}
@@ -1,81 +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 * as React from 'react';
import { Text } from '../Text';
import { TablePaginationProps } from './types';
import { Button } from '../Button';
/** @public */
const TablePagination = React.forwardRef<HTMLDivElement, TablePaginationProps>(
({ className, ...props }, ref) => {
const {
pageIndex,
pageSize,
onClickPrevious,
onClickNext,
canPrevious,
canNext,
totalRows,
setPageSize,
} = props;
return (
<div
ref={ref}
className={['canon-TablePagination', className].join(' ')}
{...props}
>
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
<div className="canon-TablePagination-info">
<Text variant="body">{`${pageIndex * pageSize + 1} - ${
(pageIndex + 1) * pageSize
} of ${totalRows}`}</Text>
</div>
<div className="canon-TablePagination-buttons">
<Button
variant="secondary"
size="small"
onClick={onClickPrevious}
disabled={!canPrevious}
>
Previous
</Button>
<Button
variant="secondary"
size="small"
onClick={onClickNext}
disabled={!canNext}
>
Next
</Button>
</div>
</div>
);
},
);
TablePagination.displayName = 'TablePagination';
export { TablePagination };
+2
View File
@@ -16,6 +16,8 @@
@import '../components/Box/styles.css';
@import '../components/Button/styles.css';
@import '../components/DataTable/Root/DataTableRoot.styles.css';
@import '../components/DataTable/Pagination/DataTablePagination.styles.css';
@import '../components/Flex/styles.css';
@import '../components/Grid/styles.css';
@import '../components/Container/styles.css';
+1
View File
@@ -33,6 +33,7 @@ export * from './components/Heading';
// UI components
export * from './components/Button';
export * from './components/DataTable';
export * from './components/Icon';
export * from './components/IconButton';
export * from './components/Checkbox';