-
Notifications
You must be signed in to change notification settings - Fork 2.9k
docs: Improves Table documentation
#25787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "type": "prerelease", | ||
| "comment": "docs: Update docstrings for props", | ||
| "packageName": "@fluentui/react-table", | ||
| "email": "[email protected]", | ||
| "dependentChangeType": "patch" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,3 +108,14 @@ export const CellNavigation = () => { | |
| </Table> | ||
| ); | ||
| }; | ||
|
|
||
| CellNavigation.parameters = { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not related to the PR, but the fact that storybook shows only the exported component source code makes the examples really hard to read - I do not know what the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah we've had an open issue on it for a while #20046 |
||
| docs: { | ||
| description: { | ||
| story: [ | ||
| 'The `Table` primitive components do not support keyboard navigation. This should be added by users.', | ||
| 'Cell navigation can be achieved simply using the `useArrowNavigationGroup` utility provided by the Library.', | ||
| ].join('\n'), | ||
| }, | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| import * as React from 'react'; | ||
| import { | ||
| FolderRegular, | ||
| EditRegular, | ||
| OpenRegular, | ||
| DocumentRegular, | ||
| PeopleRegular, | ||
| DocumentPdfRegular, | ||
| VideoRegular, | ||
| } from '@fluentui/react-icons'; | ||
| import { PresenceBadgeStatus, Avatar, useArrowNavigationGroup } from '@fluentui/react-components'; | ||
| import { | ||
| TableBody, | ||
| TableCell, | ||
| TableRow, | ||
| Table, | ||
| TableHeader, | ||
| TableHeaderCell, | ||
| TableSelectionCell, | ||
| TableCellLayout, | ||
| useTable, | ||
| ColumnDefinition, | ||
| useSelection, | ||
| useSort, | ||
| createColumn, | ||
| ColumnId, | ||
| } from '@fluentui/react-components/unstable'; | ||
|
|
||
| type FileCell = { | ||
| label: string; | ||
| icon: JSX.Element; | ||
| }; | ||
|
|
||
| type LastUpdatedCell = { | ||
| label: string; | ||
| timestamp: number; | ||
| }; | ||
|
|
||
| type LastUpdateCell = { | ||
| label: string; | ||
| icon: JSX.Element; | ||
| }; | ||
|
|
||
| type AuthorCell = { | ||
| label: string; | ||
| status: PresenceBadgeStatus; | ||
| }; | ||
|
|
||
| type Item = { | ||
| file: FileCell; | ||
| author: AuthorCell; | ||
| lastUpdated: LastUpdatedCell; | ||
| lastUpdate: LastUpdateCell; | ||
| }; | ||
|
|
||
| const items: Item[] = [ | ||
| { | ||
| file: { label: 'Meeting notes', icon: <DocumentRegular /> }, | ||
| author: { label: 'Max Mustermann', status: 'available' }, | ||
| lastUpdated: { label: '7h ago', timestamp: 3 }, | ||
| lastUpdate: { | ||
| label: 'You edited this', | ||
| icon: <EditRegular />, | ||
| }, | ||
| }, | ||
| { | ||
| file: { label: 'Thursday presentation', icon: <FolderRegular /> }, | ||
| author: { label: 'Erika Mustermann', status: 'busy' }, | ||
| lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, | ||
| lastUpdate: { | ||
| label: 'You recently opened this', | ||
| icon: <OpenRegular />, | ||
| }, | ||
| }, | ||
| { | ||
| file: { label: 'Training recording', icon: <VideoRegular /> }, | ||
| author: { label: 'John Doe', status: 'away' }, | ||
| lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, | ||
| lastUpdate: { | ||
| label: 'You recently opened this', | ||
| icon: <OpenRegular />, | ||
| }, | ||
| }, | ||
| { | ||
| file: { label: 'Purchase order', icon: <DocumentPdfRegular /> }, | ||
| author: { label: 'Jane Doe', status: 'offline' }, | ||
| lastUpdated: { label: 'Tue at 9:30 AM', timestamp: 1 }, | ||
| lastUpdate: { | ||
| label: 'You shared this in a Teams chat', | ||
| icon: <PeopleRegular />, | ||
| }, | ||
| }, | ||
| ]; | ||
|
|
||
| export const DataGrid = () => { | ||
| const columns: ColumnDefinition<Item>[] = React.useMemo( | ||
| () => [ | ||
| createColumn<Item>({ | ||
| columnId: 'file', | ||
| compare: (a, b) => { | ||
| return a.file.label.localeCompare(b.file.label); | ||
| }, | ||
| }), | ||
| createColumn<Item>({ | ||
| columnId: 'author', | ||
| compare: (a, b) => { | ||
| return a.author.label.localeCompare(b.author.label); | ||
| }, | ||
| }), | ||
| createColumn<Item>({ | ||
| columnId: 'lastUpdated', | ||
| compare: (a, b) => { | ||
| return a.lastUpdated.timestamp - b.lastUpdated.timestamp; | ||
| }, | ||
| }), | ||
| createColumn<Item>({ | ||
| columnId: 'lastUpdate', | ||
| compare: (a, b) => { | ||
| return a.lastUpdate.label.localeCompare(b.lastUpdate.label); | ||
| }, | ||
| }), | ||
| ], | ||
| [], | ||
| ); | ||
|
|
||
| const { | ||
| getRows, | ||
| selection: { allRowsSelected, someRowsSelected, toggleAllRows, toggleRow, isRowSelected }, | ||
| sort: { getSortDirection, toggleColumnSort, sort }, | ||
| } = useTable( | ||
| { | ||
| columns, | ||
| items, | ||
| }, | ||
| [ | ||
| useSelection({ | ||
| selectionMode: 'multiselect', | ||
| defaultSelectedItems: new Set([0, 1]), | ||
| }), | ||
| useSort({ defaultSortState: { sortColumn: 'file', sortDirection: 'ascending' } }), | ||
| ], | ||
| ); | ||
|
|
||
| const rows = sort( | ||
| getRows(row => { | ||
| const selected = isRowSelected(row.rowId); | ||
| return { | ||
| ...row, | ||
| onClick: (e: React.MouseEvent) => toggleRow(e, row.rowId), | ||
| onKeyDown: (e: React.KeyboardEvent) => { | ||
| if (e.key === ' ') { | ||
| e.preventDefault(); | ||
| toggleRow(e, row.rowId); | ||
| } | ||
| }, | ||
| selected, | ||
| appearance: selected ? ('brand' as const) : ('none' as const), | ||
| }; | ||
| }), | ||
| ); | ||
|
|
||
| const headerSortProps = (columnId: ColumnId) => ({ | ||
| onClick: (e: React.MouseEvent) => { | ||
| toggleColumnSort(e, columnId); | ||
| }, | ||
| sortDirection: getSortDirection(columnId), | ||
| }); | ||
|
|
||
| const keyboardNavAttr = useArrowNavigationGroup({ axis: 'grid' }); | ||
|
|
||
| return ( | ||
| <Table {...keyboardNavAttr} sortable> | ||
| <TableHeader> | ||
| <TableRow> | ||
| <TableSelectionCell | ||
| tabIndex={0} | ||
| checkboxIndicator={{ tabIndex: -1 }} | ||
| checked={allRowsSelected ? true : someRowsSelected ? 'mixed' : false} | ||
| onClick={toggleAllRows} | ||
| /> | ||
| <TableHeaderCell {...headerSortProps('file')}>File</TableHeaderCell> | ||
| <TableHeaderCell {...headerSortProps('author')}>Author</TableHeaderCell> | ||
| <TableHeaderCell {...headerSortProps('lastUpdated')}>Last updated</TableHeaderCell> | ||
| <TableHeaderCell {...headerSortProps('lastUpdate')}>Last update</TableHeaderCell> | ||
| </TableRow> | ||
| </TableHeader> | ||
| <TableBody> | ||
| {rows.map(({ item, selected, onClick, onKeyDown, appearance }) => ( | ||
| <TableRow | ||
| key={item.file.label} | ||
| onClick={onClick} | ||
| onKeyDown={onKeyDown} | ||
| aria-selected={selected} | ||
| appearance={appearance} | ||
| > | ||
| <TableSelectionCell tabIndex={0} checkboxIndicator={{ tabIndex: -1 }} checked={selected} /> | ||
| <TableCell tabIndex={0}> | ||
| <TableCellLayout media={item.file.icon}>{item.file.label}</TableCellLayout> | ||
| </TableCell> | ||
| <TableCell tabIndex={0}> | ||
| <TableCellLayout media={<Avatar badge={{ status: item.author.status }} />}> | ||
| {item.author.label} | ||
| </TableCellLayout> | ||
| </TableCell> | ||
| <TableCell tabIndex={0}>{item.lastUpdated.label}</TableCell> | ||
| <TableCell tabIndex={0}> | ||
| <TableCellLayout media={item.lastUpdate.icon}>{item.lastUpdate.label}</TableCellLayout> | ||
| </TableCell> | ||
| </TableRow> | ||
| ))} | ||
| </TableBody> | ||
| </Table> | ||
| ); | ||
| }; | ||
|
|
||
| DataGrid.parameters = { | ||
| docs: { | ||
| description: { | ||
| story: [ | ||
| 'The `DataGrid` component is simply a composition of hook and primitive `Table` components', | ||
| 'along with some convenience features such as accessible markup and event handlers.', | ||
| 'Any feature of the `DataGrid` is achievable with the primitive components and hook', | ||
| ].join('\n'), | ||
| }, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,7 +131,8 @@ export const MultipleSelect = () => { | |
| ...row, | ||
| onClick: (e: React.MouseEvent) => toggleRow(e, row.rowId), | ||
| onKeyDown: (e: React.KeyboardEvent) => { | ||
| if (e.key === ' ' || e.key === 'Enter') { | ||
| if (e.key === ' ') { | ||
| e.preventDefault(); | ||
| toggleRow(e, row.rowId); | ||
| } | ||
| }, | ||
|
|
@@ -143,10 +144,12 @@ export const MultipleSelect = () => { | |
| const keyboardNavAttr = useArrowNavigationGroup({ axis: 'grid' }); | ||
|
|
||
| return ( | ||
| <Table> | ||
| <Table {...keyboardNavAttr}> | ||
| <TableHeader> | ||
| <TableRow> | ||
| <TableSelectionCell | ||
| tabIndex={0} | ||
| checkboxIndicator={{ tabIndex: -1 }} | ||
| checked={allRowsSelected ? true : someRowsSelected ? 'mixed' : false} | ||
| onClick={toggleAllRows} | ||
| /> | ||
|
|
@@ -156,7 +159,7 @@ export const MultipleSelect = () => { | |
| <TableHeaderCell>Last update</TableHeaderCell> | ||
| </TableRow> | ||
| </TableHeader> | ||
| <TableBody {...keyboardNavAttr}> | ||
| <TableBody> | ||
| {rows.map(({ item, selected, onClick, onKeyDown, appearance }) => ( | ||
| <TableRow | ||
| key={item.file.label} | ||
|
|
@@ -184,3 +187,15 @@ export const MultipleSelect = () => { | |
| </Table> | ||
| ); | ||
| }; | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please double check keyboard navigation - I can select the row checkbox by both Enter and Space but Space scrolls.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I've made it behave consistently to uber checkbox and added |
||
| MultipleSelect.parameters = { | ||
| docs: { | ||
| description: { | ||
| story: [ | ||
| 'Selection can be achieved easily by combining the `TableSelectionCell` component along with', | ||
| 'other primitive components. The hook can handle state management for selection, although its use is not', | ||
| 'necessary if users already have their own state management.', | ||
| ].join('\n'), | ||
| }, | ||
| }, | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TableCellActionsoverlapsTableCellLayout. Is that expected?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Submitted a bug for this #25789