Getting Table together

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-08-01 08:25:00 +01:00
parent 3479157e92
commit a01ef53197
52 changed files with 589 additions and 3457 deletions
File diff suppressed because one or more lines are too long
+7 -142
View File
@@ -25,7 +25,7 @@ import { Theming } from '@/components/Theming';
<Snippet
py={4}
preview={<TableSnippet story="Uncontrolled" />}
preview={<TableSnippet story="TableRockBand" />}
code={tableBasicSnippet}
/>
@@ -39,166 +39,31 @@ import { Theming } from '@/components/Theming';
The main table component that renders data in a structured grid format.
<PropsTable data={tablePropDefs} />
Coming soon.
### TablePagination
A pagination component designed to work with the Table component.
<PropsTable data={tablePaginationPropDefs} />
Coming soon.
## Examples
### Basic Table
A simple table with data display.
<Snippet
py={4}
open
preview={<TableSnippet story="Uncontrolled" />}
code={tableBasicSnippet}
/>
Coming soon.
### Row Clicks
The Table supports a hybrid interaction model where you can add row-level click handlers while still allowing cell-level interactions to take precedence.
<Snippet
py={4}
open
preview={<TableSnippet story="WithRowClick" />}
code={tableRowClickSnippet}
/>
### Hybrid Interactions
This demonstrates the most common pattern: specific cells (like names) have links for navigation, while clicking empty row areas triggers a different action like selection or quick preview.
<Snippet
py={4}
open
preview={<TableSnippet story="WithNavigationLinks" />}
code={tableHybridSnippet}
/>
### Cell-Level Interactions
Different cells can have completely different interaction behaviors. Links navigate, buttons trigger actions, and read-only cells allow row clicks.
<Snippet
py={4}
open
preview={<TableSnippet story="WithMixedInteractions" />}
code={tableCellInteractionsSnippet}
/>
Coming soon.
### Pagination
Enable pagination by adding the pagination row model and using the TablePagination component.
<Snippet
py={4}
open
preview={<TableSnippet story="Controlled" />}
code={tablePaginationSnippet}
/>
## Advanced Features
### Row Selection
Enable row selection using TanStack Table's built-in selection features.
<CodeBlock code={tableSelectionSnippet} />
Coming soon.
### Sorting
Enable column sorting by adding the sorting row model.
<CodeBlock code={tableSortingSnippet} />
## Interaction Patterns
The Table component supports a **two-level interaction model**:
1. **Primary**: Cell-level interactions (Links, buttons, etc.) - handled in column definitions
2. **Secondary**: Row-level background handler - handles clicks on empty row areas
### When to Use Each Pattern
**Use `onRowClick` only:**
- Simple tables where entire rows navigate to the same place
- Selection-based interactions
- Quick actions that apply to the whole row
**Use cell-level interactions only:**
- Complex tables with many different interactive elements
- When you need precise control over each interaction
- When different cells need different behaviors
**Use hybrid approach:**
- **Most common case** - Some cells have specific actions, others are background-clickable
- Data tables with primary actions (links) and secondary actions (selection/preview)
- When you want the convenience of row clicking but flexibility of cell control
### Event Handling
Cell-level interactions should call `e.stopPropagation()` to prevent row clicks:
```tsx
// ✅ Correct - prevents row click
<Link
to="/somewhere"
onClick={(e) => e.stopPropagation()}
>
Navigate
</Link>
// ✅ Also correct for custom actions
<button onClick={(e) => {
e.stopPropagation();
handleAction();
}}>
Action
</button>
```
## Best Practices
1. **Use Link components for navigation** - Better UX and accessibility
2. **Handle keyboard modifiers** - Support Cmd/Ctrl+click for new tabs
3. **Use `stopPropagation()`** in cell interactions to prevent row clicks
4. **Provide visual feedback** - Use appropriate cursor styles
5. **Consider accessibility** - Screen readers understand Links and buttons in context
## TypeScript Support
The Table component is fully typed with generic support:
```tsx
interface MyDataType {
id: string;
name: string;
status: string;
}
const table = useReactTable<MyDataType>({
data: myData, // ✅ Typed as MyDataType[]
columns: myColumns, // ✅ Typed as ColumnDef<MyDataType>[]
getCoreRowModel: getCoreRowModel(),
});
const handleRowClick = (row: MyDataType) => {
// ✅ row is fully typed as MyDataType
console.log(row.name);
};
return <Table table={table} onRowClick={handleRowClick} />;
```
Coming soon.
<Theming component="Table" />
+66 -207
View File
@@ -61,218 +61,77 @@ export const tablePaginationPropDefs: Record<string, PropDef> = {
},
};
export const tableUsageSnippet = `import { useReactTable, getCoreRowModel, ColumnDef } from '@tanstack/react-table';
import { Table, TablePagination } from '@backstage/ui/components/Table';
export const tableUsageSnippet = `import {
Cell,
CellProfile,
Column,
Row,
Table,
TableBody,
TableHeader,
TablePagination,
} from '@backstage/ui';
interface Person {
firstName: string;
lastName: string;
age: number;
visits: number;
status: string;
}
<Table>
<TableHeader>
<Column />
</TableHeader>
<TableBody>
<Row>
<Cell />
<CellProfile />
</Row>
</TableBody>
</Table>
<TablePagination />`;
const data: Person[] = [
export const tableBasicSnippet = `import { Table, TablePagination } from '@backstage/ui';
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(5);
const data = [
{
firstName: 'tanner',
lastName: 'linsley',
age: 24,
visits: 100,
status: 'In Relationship',
name: 'The Beatles',
image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg',
genre: 'Rock, Pop, Psychedelic Rock',
yearFormed: 1960,
albums: 13
},
// ... more data
];
const columns: ColumnDef<Person>[] = [
{
accessorKey: 'firstName',
header: 'First Name',
},
{
accessorKey: 'lastName',
header: 'Last Name',
},
{
accessorKey: 'age',
header: 'Age',
},
{
accessorKey: 'visits',
header: 'Visits',
},
{
accessorKey: 'status',
header: 'Status',
},
];
const newData = data4.slice(
pageIndex * pageSize,
(pageIndex + 1) * pageSize,
);
function MyTable() {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return (
<>
<Table table={table} />
<TablePagination table={table} />
</>
);
}`;
export const tableBasicSnippet = `const table = useReactTable({
data: myData,
columns: myColumns,
getCoreRowModel: getCoreRowModel(),
});
return <Table table={table} />;`;
export const tableRowClickSnippet = `const table = useReactTable({
data: myData,
columns: myColumns,
getCoreRowModel: getCoreRowModel(),
});
const handleRowClick = (rowData: MyDataType) => {
console.log('Row clicked:', rowData);
navigate(\`/details/\${rowData.id}\`);
};
return <Table table={table} onRowClick={handleRowClick} />;`;
export const tableHybridSnippet = `const columns: ColumnDef<MyDataType>[] = [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => (
<Link
to={\`/items/\${row.original.id}\`}
onClick={(e) => e.stopPropagation()} // Prevent row click
>
<TableCellText title={row.getValue('name')} />
</Link>
),
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => <TableCellText title={row.getValue('status')} />,
},
];
const table = useReactTable({
data: myData,
columns,
getCoreRowModel: getCoreRowModel(),
});
const handleRowClick = (rowData: MyDataType) => {
// Called when clicking empty row areas (not the name link)
showQuickPreview(rowData);
};
return <Table table={table} onRowClick={handleRowClick} />;`;
export const tableCellInteractionsSnippet = `const columns: ColumnDef<MyDataType>[] = [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => (
<Link to={\`/items/\${row.original.id}\`}>
<TableCellText title={row.getValue('name')} />
</Link>
),
},
{
accessorKey: 'owner',
header: 'Owner',
cell: ({ row }) => (
<div
onClick={(e) => {
e.stopPropagation();
openContactModal(row.original.owner);
}}
style={{ cursor: 'pointer' }}
>
<TableCellText title={row.original.owner.name} />
</div>
),
},
{
id: 'actions',
header: 'Actions',
cell: ({ row }) => (
<button
onClick={(e) => {
e.stopPropagation();
handleEdit(row.original);
}}
>
Edit
</button>
),
},
];`;
export const tablePaginationSnippet = `import {
useReactTable,
getCoreRowModel,
getPaginationRowModel
} from '@tanstack/react-table';
import { Table, TablePagination } from '@backstage/ui/components/Table';
const table = useReactTable({
data: myData,
columns: myColumns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), // Enable pagination
});
return (
<>
<Table table={table} />
<TablePagination table={table} />
</>
);`;
export const tableSelectionSnippet = `import { useState } from 'react';
import {
useReactTable,
getCoreRowModel,
RowSelectionState
} from '@tanstack/react-table';
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const table = useReactTable({
data: myData,
columns: myColumns,
getCoreRowModel: getCoreRowModel(),
enableRowSelection: true,
state: {
rowSelection,
},
onRowSelectionChange: setRowSelection,
});
// Access selected rows
const selectedRows = table.getFilteredSelectedRowModel().rows;
return <Table table={table} />;`;
export const tableSortingSnippet = `import {
useReactTable,
getCoreRowModel,
getSortedRowModel
} from '@tanstack/react-table';
const table = useReactTable({
data: myData,
columns: myColumns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), // Enable sorting
});
return <Table table={table} />;`;
<Table>
<TableHeader>
<Column isRowHeader>Band name</Column>
<Column>Genre</Column>
<Column>Year formed</Column>
<Column>Albums</Column>
</TableHeader>
<TableBody>
{newData.map(item => (
<Row key={item.name}>
<CellProfileBUI
name={item.name}
src={item.image}
href={item.website}
/>
<Cell title={item.genre} />
<Cell title={item.yearFormed.toString()} />
<Cell title={item.albums.toString()} />
</Row>
))}
</TableBody>
</Table>
<TablePagination
pageIndex={pageIndex}
pageSize={pageSize}
rowCount={data4.length}
setPageIndex={setPageIndex}
setPageSize={setPageSize}
/>`;