feat(ui): add Table row selection support with visual states and documentation
- Add row state styling for hover, selected, pressed, and disabled states - Fix checkbox rendering to only appear for multi-select toggle mode - Add fixed 40px width for selection column header and cells - Fix Column component to properly merge className prop - Switch from React Aria headless Checkbox to styled Backstage UI Checkbox - Add 12 selection Storybook stories covering all mode/behavior combinations - Add interactive playground stories for selection mode and behavior - Document row selection in docs-ui with examples Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode.
|
||||
|
||||
Affected components: Table, TableHeader, Row, Column
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
tableHybridSnippet,
|
||||
tableCellInteractionsSnippet,
|
||||
tablePaginationSnippet,
|
||||
tableSelectionSnippet,
|
||||
tableSelectionActionsSnippet,
|
||||
tableSelectionModeSnippet,
|
||||
tableSelectionBehaviorSnippet,
|
||||
tableSortingSnippet,
|
||||
columnPropDefs,
|
||||
rowPropDefs,
|
||||
@@ -91,7 +93,36 @@ Coming soon.
|
||||
|
||||
### Row selection
|
||||
|
||||
Coming soon.
|
||||
Tables support row selection with two configuration options: selection mode and selection behavior.
|
||||
|
||||
#### Selection mode
|
||||
|
||||
Use `selectionMode` to control how many rows can be selected. With `single`, only one row can be selected at a time. With `multiple`, any number of rows can be selected, and a header checkbox provides select-all functionality.
|
||||
|
||||
<Snippet
|
||||
preview={<TableSnippet story="SelectionModePlayground" />}
|
||||
code={tableSelectionModeSnippet}
|
||||
/>
|
||||
|
||||
#### Selection behavior
|
||||
|
||||
Use `selectionBehavior` to control how selection is indicated and interacted with. With `toggle`, checkboxes appear for selection. With `replace`, selection is indicated by row background color—click to select, Cmd/Ctrl+click for multiple.
|
||||
|
||||
<Snippet
|
||||
preview={<TableSnippet story="SelectionBehaviorPlayground" />}
|
||||
code={tableSelectionBehaviorSnippet}
|
||||
/>
|
||||
|
||||
#### With row actions
|
||||
|
||||
With toggle behavior, clicking a row triggers its action when nothing is selected. Once any row is selected, clicking toggles selection instead.
|
||||
|
||||
With replace behavior, clicking selects the row and double-clicking triggers the action.
|
||||
|
||||
<Snippet
|
||||
preview={<TableSnippet story="SelectionToggleWithActions" />}
|
||||
code={tableSelectionActionsSnippet}
|
||||
/>
|
||||
|
||||
### Row Clicks
|
||||
|
||||
|
||||
@@ -326,3 +326,52 @@ const { data: paginatedData, paginationProps } = useTable({
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />`;
|
||||
|
||||
export const tableSelectionActionsSnippet = `import { Table, TableHeader, TableBody, Column, Row, Cell } from '@backstage/ui';
|
||||
|
||||
function MyTable() {
|
||||
const [selectedKeys, setSelectedKeys] = React.useState(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
onRowAction={(key) => console.log('Opening', key)}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Status</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component A" />
|
||||
<Cell title="Active" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="Component B" />
|
||||
<Cell title="Inactive" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}`;
|
||||
|
||||
export const tableSelectionModeSnippet = `<Table
|
||||
selectionMode="multiple" // or "single"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
{/* ... */}
|
||||
</Table>`;
|
||||
|
||||
export const tableSelectionBehaviorSnippet = `<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle" // or "replace"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
{/* ... */}
|
||||
</Table>`;
|
||||
|
||||
@@ -1265,6 +1265,8 @@ export const TableDefinition: {
|
||||
readonly cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback';
|
||||
readonly cellProfileName: 'bui-TableCellProfileName';
|
||||
readonly cellProfileLink: 'bui-TableCellProfileLink';
|
||||
readonly headSelection: 'bui-TableHeadSelection';
|
||||
readonly cellSelection: 'bui-TableCellSelection';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TableHeadSelection {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.bui-TableHeadContent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -81,23 +85,34 @@
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bui-bg-tint-hover);
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
background-color: var(--bui-bg-tint-pressed);
|
||||
}
|
||||
|
||||
&[data-pressed] {
|
||||
background-color: var(--bui-bg-tint-pressed);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--bui-bg-tint-disabled);
|
||||
}
|
||||
|
||||
&[data-react-aria-pressable='true'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.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-TableCellSelection {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.bui-TableCellContentWrapper {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import type { Meta, StoryFn, StoryObj } from '@storybook/react-vite';
|
||||
import { type Selection } from 'react-aria-components';
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
@@ -26,6 +27,8 @@ import {
|
||||
CellProfile as CellProfileBUI,
|
||||
useTable,
|
||||
} from '.';
|
||||
import { RadioGroup, Radio } from '../RadioGroup';
|
||||
import { Flex } from '../Flex';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { data as data1Raw } from './mocked-data1';
|
||||
import { data as data2 } from './mocked-data2';
|
||||
@@ -33,6 +36,7 @@ import { data as data3 } from './mocked-data3';
|
||||
import { data as data4 } from './mocked-data4';
|
||||
import { RiCactusLine } from '@remixicon/react';
|
||||
import { TablePagination } from '../TablePagination';
|
||||
import { Text } from '../Text';
|
||||
|
||||
const meta = {
|
||||
title: 'Backstage UI/Table',
|
||||
@@ -367,3 +371,508 @@ export const CellProfile: Story = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionSingleToggle: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="single"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionMultiToggle: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionSingleReplace: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="single"
|
||||
selectionBehavior="replace"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionMultiReplace: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="replace"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionToggleWithActions: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
onRowAction={key => alert(`Opening ${key}`)}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionReplaceWithActions: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="replace"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
onRowAction={key => alert(`Opening ${key}`)}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionToggleWithLinks: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1" href="https://example.com/library">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2" href="https://example.com/gateway">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3" href="https://example.com/docs">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionReplaceWithLinks: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="replace"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1" href="https://example.com/library">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2" href="https://example.com/gateway">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3" href="https://example.com/docs">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionWithDisabledRows: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
disabledKeys={['2']}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway (Disabled)" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionWithPagination: Story = {
|
||||
render: () => {
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
const { data, paginationProps } = useTable({
|
||||
data: data1,
|
||||
pagination: {
|
||||
defaultPageSize: 5,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map(item => (
|
||||
<Row key={item.name} id={item.name}>
|
||||
<Cell title={item.name} />
|
||||
<Cell title={item.owner.name} />
|
||||
<Cell title={item.type} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionModePlayground: Story = {
|
||||
render: () => {
|
||||
const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>(
|
||||
'multiple',
|
||||
);
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="8">
|
||||
<Table
|
||||
selectionMode={selectionMode}
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div>
|
||||
<Text as="h4" style={{ marginBottom: 'var(--bui-space-2)' }}>
|
||||
Selection mode:
|
||||
</Text>
|
||||
<RadioGroup
|
||||
aria-label="Selection mode"
|
||||
orientation="horizontal"
|
||||
value={selectionMode}
|
||||
onChange={value => {
|
||||
setSelectionMode(value as 'single' | 'multiple');
|
||||
setSelectedKeys(new Set([]));
|
||||
}}
|
||||
>
|
||||
<Radio value="single">single</Radio>
|
||||
<Radio value="multiple">multiple</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectionBehaviorPlayground: Story = {
|
||||
render: () => {
|
||||
const [selectionBehavior, setSelectionBehavior] = useState<
|
||||
'toggle' | 'replace'
|
||||
>('toggle');
|
||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="8">
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior={selectionBehavior}
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<Cell title="Component Library" />
|
||||
<Cell title="Design System" />
|
||||
<Cell title="library" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<Cell title="API Gateway" />
|
||||
<Cell title="Platform" />
|
||||
<Cell title="service" />
|
||||
</Row>
|
||||
<Row id="3">
|
||||
<Cell title="Documentation Site" />
|
||||
<Cell title="DevEx" />
|
||||
<Cell title="website" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div>
|
||||
<Text as="h4" style={{ marginBottom: 'var(--bui-space-2)' }}>
|
||||
Selection behavior:
|
||||
</Text>
|
||||
<RadioGroup
|
||||
aria-label="Selection behavior"
|
||||
orientation="horizontal"
|
||||
value={selectionBehavior}
|
||||
onChange={value => {
|
||||
setSelectionBehavior(value as 'toggle' | 'replace');
|
||||
setSelectedKeys(new Set([]));
|
||||
}}
|
||||
>
|
||||
<Radio value="toggle">toggle</Radio>
|
||||
<Radio value="replace">replace</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,11 +25,11 @@ import { RiArrowUpLine, RiArrowDownLine } from '@remixicon/react';
|
||||
/** @public */
|
||||
export const Column = (props: ColumnProps) => {
|
||||
const { classNames, cleanedProps } = useStyles(TableDefinition, props);
|
||||
const { children, ...rest } = cleanedProps;
|
||||
const { className, children, ...rest } = cleanedProps;
|
||||
|
||||
return (
|
||||
<ReactAriaColumn
|
||||
className={clsx(classNames.head, styles[classNames.head])}
|
||||
className={clsx(classNames.head, styles[classNames.head], className)}
|
||||
{...rest}
|
||||
>
|
||||
{({ allowsSorting, sortDirection }) => (
|
||||
|
||||
@@ -18,11 +18,11 @@ import {
|
||||
Row as ReactAriaRow,
|
||||
RowProps,
|
||||
useTableOptions,
|
||||
Cell,
|
||||
Cell as ReactAriaCell,
|
||||
Collection,
|
||||
Checkbox,
|
||||
RouterProvider,
|
||||
} from 'react-aria-components';
|
||||
import { Checkbox } from '../../Checkbox';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import { TableDefinition } from '../definition';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -30,6 +30,7 @@ import { useHref } from 'react-router-dom';
|
||||
import { isExternalLink } from '../../../utils/isExternalLink';
|
||||
import styles from '../Table.module.css';
|
||||
import clsx from 'clsx';
|
||||
import { Flex } from '../../Flex';
|
||||
|
||||
/** @public */
|
||||
export function Row<T extends object>(props: RowProps<T>) {
|
||||
@@ -38,14 +39,23 @@ export function Row<T extends object>(props: RowProps<T>) {
|
||||
const navigate = useNavigate();
|
||||
const isExternal = isExternalLink(href);
|
||||
|
||||
let { selectionBehavior } = useTableOptions();
|
||||
let { selectionBehavior, selectionMode } = useTableOptions();
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{selectionBehavior === 'toggle' && (
|
||||
<Cell>
|
||||
<Checkbox slot="selection" />
|
||||
</Cell>
|
||||
{selectionBehavior === 'toggle' && selectionMode === 'multiple' && (
|
||||
<ReactAriaCell
|
||||
className={clsx(
|
||||
classNames.cellSelection,
|
||||
styles[classNames.cellSelection],
|
||||
)}
|
||||
>
|
||||
<Flex justify="center" align="center">
|
||||
<Checkbox slot="selection">
|
||||
<></>
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
</ReactAriaCell>
|
||||
)}
|
||||
<Collection items={columns}>{children}</Collection>
|
||||
</>
|
||||
|
||||
@@ -17,14 +17,16 @@
|
||||
import {
|
||||
TableHeader as ReactAriaTableHeader,
|
||||
type TableHeaderProps,
|
||||
Checkbox,
|
||||
Collection,
|
||||
useTableOptions,
|
||||
} from 'react-aria-components';
|
||||
import { Collection, useTableOptions } from 'react-aria-components';
|
||||
import { Checkbox } from '../../Checkbox';
|
||||
import { Column } from './Column';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import { TableDefinition } from '../definition';
|
||||
import styles from '../Table.module.css';
|
||||
import clsx from 'clsx';
|
||||
import { Flex } from '../../Flex';
|
||||
|
||||
/** @public */
|
||||
export const TableHeader = <T extends object>(props: TableHeaderProps<T>) => {
|
||||
@@ -38,9 +40,21 @@ export const TableHeader = <T extends object>(props: TableHeaderProps<T>) => {
|
||||
className={clsx(classNames.header, styles[classNames.header])}
|
||||
{...rest}
|
||||
>
|
||||
{selectionBehavior === 'toggle' && (
|
||||
<Column>
|
||||
{selectionMode === 'multiple' && <Checkbox slot="selection" />}
|
||||
{selectionBehavior === 'toggle' && selectionMode === 'multiple' && (
|
||||
<Column
|
||||
width={40}
|
||||
minWidth={40}
|
||||
maxWidth={40}
|
||||
className={clsx(
|
||||
classNames.headSelection,
|
||||
styles[classNames.headSelection],
|
||||
)}
|
||||
>
|
||||
<Flex justify="center" align="center">
|
||||
<Checkbox slot="selection">
|
||||
<></>
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
</Column>
|
||||
)}
|
||||
<Collection items={columns}>{children}</Collection>
|
||||
|
||||
@@ -39,5 +39,7 @@ export const TableDefinition = {
|
||||
cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback',
|
||||
cellProfileName: 'bui-TableCellProfileName',
|
||||
cellProfileLink: 'bui-TableCellProfileLink',
|
||||
headSelection: 'bui-TableHeadSelection',
|
||||
cellSelection: 'bui-TableCellSelection',
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
Reference in New Issue
Block a user