Adding new table with react-aria
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { useArgs } from '@storybook/preview-api';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { TablePagination } from './TablePagination';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/TablePagination',
|
||||
component: TablePagination,
|
||||
argTypes: {
|
||||
pageIndex: { control: 'number' },
|
||||
pageSize: { control: 'radio', options: [5, 10, 20, 30, 40, 50] },
|
||||
rowCount: { control: 'number' },
|
||||
showPageSizeOptions: { control: 'boolean', defaultValue: true },
|
||||
setPageIndex: { action: 'setPageIndex' },
|
||||
setPageSize: { action: 'setPageSize' },
|
||||
},
|
||||
} satisfies Meta<typeof TablePagination>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
rowCount: 100,
|
||||
},
|
||||
render: args => {
|
||||
const [{}, updateArgs] = useArgs();
|
||||
|
||||
return (
|
||||
<TablePagination
|
||||
{...args}
|
||||
setPageIndex={value => {
|
||||
updateArgs({ pageIndex: value });
|
||||
}}
|
||||
setPageSize={value => {
|
||||
updateArgs({ pageSize: value });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.bui-DataTablePagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: var(--bui-space-5);
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--select {
|
||||
min-width: 10.5rem;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { Text, ButtonIcon, Select, Icon } from '../..';
|
||||
import type { TablePaginationProps } from './types';
|
||||
|
||||
/**
|
||||
* Pagination controls for Table components with page navigation and size selection.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function TablePagination(props: TablePaginationProps) {
|
||||
const {
|
||||
className,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
rowCount,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onPageSizeChange,
|
||||
setPageIndex,
|
||||
setPageSize,
|
||||
showPageSizeOptions = true,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const fromCount = (pageIndex ?? 0) * (pageSize ?? 10) + 1;
|
||||
const toCount = Math.min(
|
||||
((pageIndex ?? 0) + 1) * (pageSize ?? 10),
|
||||
rowCount ?? 0,
|
||||
);
|
||||
|
||||
const nextPage = () => {
|
||||
const currentPageIndex = pageIndex ?? 0;
|
||||
const currentPageSize = pageSize ?? 10;
|
||||
const totalRows = rowCount ?? 0;
|
||||
|
||||
// Check if there are more pages to navigate to
|
||||
const maxPageIndex = Math.ceil(totalRows / currentPageSize) - 1;
|
||||
|
||||
if (currentPageIndex < maxPageIndex) {
|
||||
onNextPage?.(); // Analytics tracking
|
||||
setPageIndex?.(currentPageIndex + 1); // Navigate to next page
|
||||
}
|
||||
};
|
||||
|
||||
const previousPage = () => {
|
||||
const currentPageIndex = pageIndex ?? 0;
|
||||
|
||||
// Check if we can go to previous page
|
||||
if (currentPageIndex > 0) {
|
||||
onPreviousPage?.(); // Analytics tracking
|
||||
setPageIndex?.(currentPageIndex - 1); // Navigate to previous page
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx('bui-DataTablePagination', className)} {...rest}>
|
||||
<div className="bui-DataTablePagination--left">
|
||||
{showPageSizeOptions && (
|
||||
<Select
|
||||
name="pageSize"
|
||||
size="small"
|
||||
placeholder="Show 10 results"
|
||||
options={[
|
||||
{ label: 'Show 5 results', value: '5' },
|
||||
{ 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' },
|
||||
]}
|
||||
selectedKey={pageSize?.toString()}
|
||||
onSelectionChange={value => {
|
||||
const newPageSize = Number(value);
|
||||
setPageSize?.(newPageSize);
|
||||
onPageSizeChange?.(newPageSize);
|
||||
}}
|
||||
className="bui-DataTablePagination--select"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="bui-DataTablePagination--right">
|
||||
<Text
|
||||
as="p"
|
||||
variant="body-medium"
|
||||
>{`${fromCount} - ${toCount} of ${rowCount}`}</Text>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={previousPage}
|
||||
isDisabled={pageIndex === 0}
|
||||
icon={<Icon name="chevron-left" />}
|
||||
aria-label="Previous"
|
||||
/>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={nextPage}
|
||||
isDisabled={
|
||||
pageIndex !== undefined &&
|
||||
pageSize !== undefined &&
|
||||
rowCount !== undefined &&
|
||||
pageIndex >= Math.ceil(rowCount / pageSize) - 1
|
||||
}
|
||||
icon={<Icon name="chevron-right" />}
|
||||
aria-label="Next"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 { TablePagination } from './TablePagination';
|
||||
export type { TablePaginationProps } from './types';
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export interface TablePaginationProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
pageIndex?: number;
|
||||
pageSize?: number;
|
||||
setPageSize?: (pageSize: number) => void;
|
||||
setPageIndex?: (pageIndex: number) => void;
|
||||
rowCount?: number;
|
||||
onNextPage?: () => void;
|
||||
onPreviousPage?: () => void;
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
showPageSizeOptions?: boolean;
|
||||
}
|
||||
@@ -14,19 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
|
||||
import { Table, TableHeader, Column, TableBody } from '.';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
Table as ReactAriaTable,
|
||||
TableHeader as ReactAriaTableHeader,
|
||||
Column as ReactAriaColumn,
|
||||
Cell,
|
||||
Row as ReactAriaRow,
|
||||
Cell as ReactAriaCell,
|
||||
Table,
|
||||
TableHeader,
|
||||
Column,
|
||||
TableBody,
|
||||
Row,
|
||||
Checkbox,
|
||||
} from 'react-aria-components';
|
||||
Cell,
|
||||
CellProfile as CellProfileBUI,
|
||||
} from '.';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { data as data1 } from './mocked-data1';
|
||||
import { data as data2 } from './mocked-data2';
|
||||
import { data as data3 } from './mocked-data3';
|
||||
import { RiCactusLine } from '@remixicon/react';
|
||||
import { TablePagination } from '../TablePagination';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/TableRA',
|
||||
@@ -48,43 +52,120 @@ export const Uncontrolled: Story = {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
<Column>Size</Column>
|
||||
<Column>Date Modified</Column>
|
||||
<Column>Lifecycle</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<ReactAriaRow>
|
||||
<ReactAriaCell>
|
||||
<Checkbox slot="selection" />
|
||||
</ReactAriaCell>
|
||||
<ReactAriaCell>Games</ReactAriaCell>
|
||||
<ReactAriaCell>File folder</ReactAriaCell>
|
||||
<ReactAriaCell>6/7/2020</ReactAriaCell>
|
||||
</ReactAriaRow>
|
||||
<ReactAriaRow>
|
||||
<ReactAriaCell>
|
||||
<Checkbox slot="selection" />
|
||||
</ReactAriaCell>
|
||||
<ReactAriaCell>Program Files</ReactAriaCell>
|
||||
<ReactAriaCell>File folder</ReactAriaCell>
|
||||
<ReactAriaCell>4/7/2021</ReactAriaCell>
|
||||
</ReactAriaRow>
|
||||
<ReactAriaRow>
|
||||
<ReactAriaCell>
|
||||
<Checkbox slot="selection" />
|
||||
</ReactAriaCell>
|
||||
<ReactAriaCell>bootmgr</ReactAriaCell>
|
||||
<ReactAriaCell>System file</ReactAriaCell>
|
||||
<ReactAriaCell>11/20/2010</ReactAriaCell>
|
||||
</ReactAriaRow>
|
||||
<ReactAriaRow>
|
||||
<ReactAriaCell>
|
||||
<Checkbox slot="selection" />
|
||||
</ReactAriaCell>
|
||||
<ReactAriaCell>log.txt</ReactAriaCell>
|
||||
<ReactAriaCell>Text Document</ReactAriaCell>
|
||||
<ReactAriaCell>1/18/2016</ReactAriaCell>
|
||||
</ReactAriaRow>
|
||||
{data1.map(item => (
|
||||
<Row key={item.name}>
|
||||
<Cell
|
||||
title={item.name}
|
||||
leadingIcon={<RiCactusLine />}
|
||||
description={item.description}
|
||||
/>
|
||||
<CellProfileBUI
|
||||
name={item.owner.name}
|
||||
src={item.owner.profilePicture}
|
||||
href={item.owner.link}
|
||||
/>
|
||||
<Cell title={item.type} />
|
||||
<Cell title={item.lifecycle} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithPagination: Story = {
|
||||
render: () => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const newData = data1.slice(
|
||||
pageIndex * pageSize,
|
||||
(pageIndex + 1) * pageSize,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
<Column>Lifecycle</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{newData.map(item => (
|
||||
<Row key={item.name}>
|
||||
<Cell
|
||||
title={item.name}
|
||||
leadingIcon={<RiCactusLine />}
|
||||
description={item.description}
|
||||
/>
|
||||
<Cell title={item.owner.name} />
|
||||
<Cell title={item.type} />
|
||||
<Cell title={item.lifecycle} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
rowCount={data1.length}
|
||||
setPageIndex={setPageIndex}
|
||||
setPageSize={setPageSize}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CellText: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data2.map(item => (
|
||||
<Row key={item.name}>
|
||||
<Cell
|
||||
title={item.name}
|
||||
leadingIcon={item.icon}
|
||||
description={item.description}
|
||||
/>
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CellProfile: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data3.map(item => (
|
||||
<Row key={item.name}>
|
||||
<CellProfileBUI
|
||||
name={item.name}
|
||||
src={item.profilePicture}
|
||||
href={item.link}
|
||||
description={item.description}
|
||||
/>
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.bui-Table {
|
||||
width: 100%;
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.bui-TableHeader {
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.bui-TableHead {
|
||||
text-align: left;
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableHeadSortButton {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-1);
|
||||
|
||||
&:hover svg {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
& svg {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
&[data-sort-order='asc'] svg {
|
||||
opacity: 1;
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
&[data-sort-order='desc'] svg {
|
||||
opacity: 1;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TableBody {
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableRow {
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.bui-TableBody .bui-TableRow:hover {
|
||||
background-color: var(--bui-gray-2);
|
||||
}
|
||||
|
||||
.bui-TableCell {
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
}
|
||||
|
||||
.bui-TableCell {
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
}
|
||||
|
||||
.bui-TableCellContentWrapper {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.bui-TableCellIcon,
|
||||
.bui-TableCellIcon svg {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableCellContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-0_5);
|
||||
}
|
||||
|
||||
.bui-TableCellProfile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--bui-space-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatar {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
border-radius: 100%;
|
||||
user-select: none;
|
||||
font-weight: 500;
|
||||
color: var(--bui-fg-primary);
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatarImage {
|
||||
object-fit: cover;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatarFallback {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
box-shadow: inset 0 0 0 1px var(--bui-border);
|
||||
border-radius: var(--bui-radius-full);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 clsx from 'clsx';
|
||||
import { Text } from '../../Text';
|
||||
import { Link } from '../../Link';
|
||||
import { Cell as ReactAriaCell } from 'react-aria-components';
|
||||
import type { CellProps } from '../types';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
const Cell = (props: CellProps) => {
|
||||
const {
|
||||
className,
|
||||
title,
|
||||
description,
|
||||
color = 'primary',
|
||||
leadingIcon,
|
||||
href,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { classNames } = useStyles('TableRA');
|
||||
|
||||
return (
|
||||
<ReactAriaCell className={clsx(classNames.cell, className)} {...rest}>
|
||||
<div className={classNames.cellContentWrapper}>
|
||||
{leadingIcon && (
|
||||
<div className={classNames.cellIcon}>{leadingIcon}</div>
|
||||
)}
|
||||
<div className={classNames.cellContent}>
|
||||
{href ? (
|
||||
<Link href={href} variant="body-medium" color={color}>
|
||||
{title}
|
||||
</Link>
|
||||
) : (
|
||||
<Text as="p" variant="body-medium" color={color}>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text variant="body-medium" color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ReactAriaCell>
|
||||
);
|
||||
};
|
||||
|
||||
Cell.displayName = 'Cell';
|
||||
|
||||
export { Cell };
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 clsx from 'clsx';
|
||||
import { CellProfileProps } from '../types';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { Link } from '../../Link/Link';
|
||||
import { Avatar } from '@base-ui-components/react/avatar';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import { Cell as ReactAriaCell } from 'react-aria-components';
|
||||
|
||||
/** @public */
|
||||
export const CellProfile = (props: CellProfileProps) => {
|
||||
const {
|
||||
className,
|
||||
src,
|
||||
name,
|
||||
href,
|
||||
description,
|
||||
color = 'primary',
|
||||
...rest
|
||||
} = props;
|
||||
const { classNames } = useStyles('TableRA');
|
||||
|
||||
return (
|
||||
<ReactAriaCell className={clsx(classNames.cell, className)} {...rest}>
|
||||
<div className={classNames.cellContentWrapper}>
|
||||
<div className={classNames.cellIcon}>
|
||||
{src && (
|
||||
<Avatar.Root className={classNames.cellProfileAvatar}>
|
||||
<Avatar.Image
|
||||
src={src}
|
||||
width="20"
|
||||
height="20"
|
||||
className={classNames.cellProfileAvatarImage}
|
||||
/>
|
||||
<Avatar.Fallback className={classNames.cellProfileAvatarFallback}>
|
||||
{(name || '')
|
||||
.split(' ')
|
||||
.map(word => word[0])
|
||||
.join('')
|
||||
.toLocaleUpperCase('en-US')
|
||||
.slice(0, 1)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
)}
|
||||
</div>
|
||||
<div className={classNames.cellContent}>
|
||||
{name && href ? (
|
||||
<Link href={href}>{name}</Link>
|
||||
) : (
|
||||
<Text as="p" variant="body-medium" color={color}>
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text variant="body-medium" color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ReactAriaCell>
|
||||
);
|
||||
};
|
||||
+8
-5
@@ -18,18 +18,21 @@ import {
|
||||
Column as ReactAriaColumn,
|
||||
type ColumnProps,
|
||||
} from 'react-aria-components';
|
||||
import { Icon } from '../Icon';
|
||||
import { Icon } from '../../Icon';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
export const Column = (
|
||||
props: Omit<ColumnProps, 'children'> & { children?: React.ReactNode },
|
||||
) => {
|
||||
const { classNames } = useStyles('TableRA');
|
||||
|
||||
return (
|
||||
<ReactAriaColumn {...props}>
|
||||
<ReactAriaColumn className={classNames.head} {...props}>
|
||||
{({ allowsSorting, sortDirection }) => (
|
||||
<div className="column-header">
|
||||
<>
|
||||
{props.children}
|
||||
{allowsSorting && (
|
||||
<span aria-hidden="true" className="sort-indicator">
|
||||
<span aria-hidden="true" className={classNames.headSortButton}>
|
||||
{sortDirection === 'ascending' ? (
|
||||
<Icon name="arrow-up" size={16} />
|
||||
) : (
|
||||
@@ -37,7 +40,7 @@ export const Column = (
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ReactAriaColumn>
|
||||
);
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
Row as ReactAriaRow,
|
||||
RowProps,
|
||||
useTableOptions,
|
||||
Cell,
|
||||
Collection,
|
||||
Checkbox,
|
||||
} from 'react-aria-components';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
export function Row<T extends object>({
|
||||
id,
|
||||
columns,
|
||||
children,
|
||||
...otherProps
|
||||
}: RowProps<T>) {
|
||||
const { classNames } = useStyles('TableRA');
|
||||
|
||||
let { selectionBehavior } = useTableOptions();
|
||||
|
||||
return (
|
||||
<ReactAriaRow id={id} className={classNames.row} {...otherProps}>
|
||||
{selectionBehavior === 'toggle' && (
|
||||
<Cell>
|
||||
<Checkbox slot="selection" />
|
||||
</Cell>
|
||||
)}
|
||||
<Collection items={columns}>{children}</Collection>
|
||||
</ReactAriaRow>
|
||||
);
|
||||
}
|
||||
+10
-1
@@ -14,11 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import {
|
||||
Table as ReactAriaTable,
|
||||
type TableProps,
|
||||
} from 'react-aria-components';
|
||||
|
||||
export const Table = (props: TableProps) => {
|
||||
return <ReactAriaTable {...props} />;
|
||||
const { classNames } = useStyles('TableRA');
|
||||
|
||||
return (
|
||||
<ReactAriaTable
|
||||
className={classNames.table}
|
||||
aria-label="Data table"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+4
-1
@@ -18,7 +18,10 @@ import {
|
||||
TableBody as ReactAriaTableBody,
|
||||
type TableBodyProps,
|
||||
} from 'react-aria-components';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
export const TableBody = <T extends object>(props: TableBodyProps<T>) => {
|
||||
return <ReactAriaTableBody {...props} />;
|
||||
const { classNames } = useStyles('TableRA');
|
||||
|
||||
return <ReactAriaTableBody className={classNames.body} {...props} />;
|
||||
};
|
||||
+4
-1
@@ -21,6 +21,7 @@ import {
|
||||
} from 'react-aria-components';
|
||||
import { Collection, useTableOptions } from 'react-aria-components';
|
||||
import { Column } from './Column';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
export const TableHeader = <T extends object>({
|
||||
columns,
|
||||
@@ -28,8 +29,10 @@ export const TableHeader = <T extends object>({
|
||||
}: TableHeaderProps<T>) => {
|
||||
let { selectionBehavior, selectionMode, allowsDragging } = useTableOptions();
|
||||
|
||||
const { classNames } = useStyles('TableRA');
|
||||
|
||||
return (
|
||||
<ReactAriaTableHeader>
|
||||
<ReactAriaTableHeader className={classNames.header}>
|
||||
{/* Add extra columns for drag and drop and selection. */}
|
||||
{allowsDragging && <Column />}
|
||||
{selectionBehavior === 'toggle' && (
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Table } from './Table';
|
||||
export { TableHeader } from './TableHeader';
|
||||
export { TableBody } from './TableBody';
|
||||
export { Column } from './Column';
|
||||
export { Table } from './components/Table';
|
||||
export { TableHeader } from './components/TableHeader';
|
||||
export { TableBody } from './components/TableBody';
|
||||
export { Column } from './components/Column';
|
||||
export { Row } from './components/Row';
|
||||
export { Cell } from './components/Cell';
|
||||
export { CellProfile } from './components/CellProfile';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { createElement } from 'react';
|
||||
import { RiCactusLine } from '@remixicon/react';
|
||||
|
||||
export interface DataProps {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const data: DataProps[] = [
|
||||
{
|
||||
name: 'Cell with title only',
|
||||
},
|
||||
{
|
||||
name: 'Cell with title and description',
|
||||
description:
|
||||
'A comprehensive service handling user authentication and role-based access control across all applications.',
|
||||
},
|
||||
{
|
||||
name: 'Cell with title and icon',
|
||||
icon: createElement(RiCactusLine),
|
||||
},
|
||||
{
|
||||
name: 'Cell with title, description and icon',
|
||||
|
||||
description:
|
||||
'A comprehensive service handling user authentication and role-based access control across all applications.',
|
||||
icon: createElement(RiCactusLine),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 DataProps {
|
||||
name: string;
|
||||
profilePicture?: string;
|
||||
link?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export const data: DataProps[] = [
|
||||
{
|
||||
name: 'John Lennon',
|
||||
profilePicture:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/85/John_Lennon_1969_%28cropped%29.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Paul McCartney',
|
||||
profilePicture:
|
||||
'https://d2kdkfqxnvpuu9.cloudfront.net/images/giant/51895.jpg?1341834484',
|
||||
},
|
||||
{
|
||||
name: 'Paul McCartney (Broken image link - fallback instead)',
|
||||
profilePicture:
|
||||
'https://d2kdkfqxnvpuu9.clont.net/images/giant/51895.jpg?1341834484',
|
||||
},
|
||||
{
|
||||
name: 'George Harrison (with link)',
|
||||
profilePicture:
|
||||
'https://www.who2.com/wp-content/uploads/2015/10/georgeharrison-6-scaled.jpg',
|
||||
link: 'https://en.wikipedia.org/wiki/George_Harrison',
|
||||
},
|
||||
{
|
||||
name: 'Ringo Starr (with description)',
|
||||
profilePicture:
|
||||
'https://ntvb.tmsimg.com/assets/assets/1686_v9_bb.jpg?w=360&h=480',
|
||||
description: 'Ringo Starr is a drummer and singer.',
|
||||
},
|
||||
{
|
||||
name: 'Ringo Starr (with everything)',
|
||||
profilePicture:
|
||||
'https://ntvb.tmsimg.com/assets/assets/1686_v9_bb.jpg?w=360&h=480',
|
||||
description: 'Ringo Starr is a drummer and singer.',
|
||||
link: 'https://en.wikipedia.org/wiki/George_Harrison',
|
||||
},
|
||||
];
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* 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.
|
||||
@@ -13,3 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CellProps as ReactAriaCellProps } from 'react-aria-components';
|
||||
|
||||
/** @public */
|
||||
export interface CellProps extends ReactAriaCellProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
color?: 'primary' | 'secondary';
|
||||
leadingIcon?: React.ReactNode | null;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface CellProfileProps extends ReactAriaCellProps {
|
||||
src?: string;
|
||||
name?: string;
|
||||
href?: string;
|
||||
description?: string;
|
||||
color?: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
@import '../components/Table/TableCellProfile/TableCellProfile.styles.css';
|
||||
@import '../components/Table/TablePagination/TablePagination.styles.css';
|
||||
|
||||
@import '../components/TableRA/Table.styles.css';
|
||||
|
||||
@import '../components/Tabs/Tabs.styles.css';
|
||||
@import '../components/Text/styles.css';
|
||||
@import '../components/TextField/TextField.styles.css';
|
||||
|
||||
@@ -43,7 +43,8 @@ export * from './components/ButtonIcon';
|
||||
export * from './components/ButtonLink';
|
||||
export * from './components/Checkbox';
|
||||
export * from './components/RadioGroup';
|
||||
export * from './components/Table';
|
||||
export * from './components/TableRA';
|
||||
export * from './components/TablePagination';
|
||||
export * from './components/Tabs';
|
||||
export * from './components/Text';
|
||||
export * from './components/TextField';
|
||||
|
||||
@@ -250,6 +250,26 @@ export const componentDefinitions = {
|
||||
cellProfileLink: 'bui-TableCellProfileLink',
|
||||
},
|
||||
},
|
||||
TableRA: {
|
||||
classNames: {
|
||||
table: 'bui-Table',
|
||||
header: 'bui-TableHeader',
|
||||
body: 'bui-TableBody',
|
||||
row: 'bui-TableRow',
|
||||
head: 'bui-TableHead',
|
||||
headSortButton: 'bui-TableHeadSortButton',
|
||||
caption: 'bui-TableCaption',
|
||||
cell: 'bui-TableCell',
|
||||
cellContentWrapper: 'bui-TableCellContentWrapper',
|
||||
cellContent: 'bui-TableCellTextContent',
|
||||
cellIcon: 'bui-TableCellTextIcon',
|
||||
cellProfileAvatar: 'bui-TableCellProfileAvatar',
|
||||
cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage',
|
||||
cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback',
|
||||
cellProfileName: 'bui-TableCellProfileName',
|
||||
cellProfileLink: 'bui-TableCellProfileLink',
|
||||
},
|
||||
},
|
||||
Tabs: {
|
||||
classNames: {
|
||||
tabs: 'bui-Tabs',
|
||||
|
||||
Reference in New Issue
Block a user