Getting Table together
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -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" />
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>`;
|
||||
|
||||
Reference in New Issue
Block a user