Adding new table with react-aria

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-07-30 18:24:31 +01:00
parent b441d6c9ec
commit f86b0bba93
22 changed files with 2188 additions and 58 deletions
@@ -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>
);
};
@@ -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>
);
}
@@ -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}
/>
);
};
@@ -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} />;
};
@@ -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' && (
+7 -4
View File
@@ -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',
},
];
+21 -1
View File
@@ -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';
}
+2
View File
@@ -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';
+2 -1
View File
@@ -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',