Add DataTable
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -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 };
|
||||
+11
-9
@@ -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);
|
||||
}
|
||||
+19
-2
@@ -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 };
|
||||
+3
-4
@@ -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 };
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user