diff --git a/change/@fluentui-react-table-393b87fd-7546-484a-8f7f-c8bc7d2d6b08.json b/change/@fluentui-react-table-393b87fd-7546-484a-8f7f-c8bc7d2d6b08.json new file mode 100644 index 00000000000000..70eff36aefc3b5 --- /dev/null +++ b/change/@fluentui-react-table-393b87fd-7546-484a-8f7f-c8bc7d2d6b08.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "docs: Update docstrings for props", + "packageName": "@fluentui/react-table", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-table/src/components/Table/Table.types.ts b/packages/react-components/react-table/src/components/Table/Table.types.ts index 33ea8da5eb242d..c1e7d840e6126b 100644 --- a/packages/react-components/react-table/src/components/Table/Table.types.ts +++ b/packages/react-components/react-table/src/components/Table/Table.types.ts @@ -7,18 +7,20 @@ export type TableSlots = { export type TableContextValue = { /** * Affects the sizes of all table subcomponents - * @default + * @default medium */ size: 'extra-small' | 'small' | 'medium'; /** * Render all table elements as divs intead of semantic table elements * Using divs no longer uses `display: table` layout but `display: flex` + * @default false */ noNativeElements: boolean; /** * Whether the table is sortable + * @default false */ sortable: boolean; }; diff --git a/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts b/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts index 3fb76fd596372a..c0a7e107ab2910 100644 --- a/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts +++ b/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts @@ -36,6 +36,10 @@ export type TableCellLayoutSlots = { * TableCellLayout Props */ export type TableCellLayoutProps = ComponentProps> & { + /** + * Renders design variants of the table cell + * @default undefined + */ appearance?: 'primary'; }; diff --git a/packages/react-components/react-table/src/components/TableHeaderCell/TableHeaderCell.types.ts b/packages/react-components/react-table/src/components/TableHeaderCell/TableHeaderCell.types.ts index b809530a81228b..cb82b8587e9bea 100644 --- a/packages/react-components/react-table/src/components/TableHeaderCell/TableHeaderCell.types.ts +++ b/packages/react-components/react-table/src/components/TableHeaderCell/TableHeaderCell.types.ts @@ -17,6 +17,9 @@ export type TableHeaderCellSlots = { * TableHeaderCell Props */ export type TableHeaderCellProps = ComponentProps> & { + /** + * @default undefined + */ sortDirection?: SortDirection; }; diff --git a/packages/react-components/react-table/src/components/TableRow/TableRow.types.ts b/packages/react-components/react-table/src/components/TableRow/TableRow.types.ts index e8a5e9e2560457..9965025e3096f9 100644 --- a/packages/react-components/react-table/src/components/TableRow/TableRow.types.ts +++ b/packages/react-components/react-table/src/components/TableRow/TableRow.types.ts @@ -10,7 +10,8 @@ export type TableRowSlots = { */ export type TableRowProps = ComponentProps & { /** - * A table row can have different variants + * A table row can have different variants. These appearances are + * intended to be used with selection. * @default none */ appearance?: 'brand' | 'neutral' | 'none'; diff --git a/packages/react-components/react-table/src/components/TableSelectionCell/TableSelectionCell.types.ts b/packages/react-components/react-table/src/components/TableSelectionCell/TableSelectionCell.types.ts index 41ac7112637022..15b93af1f00ba4 100644 --- a/packages/react-components/react-table/src/components/TableSelectionCell/TableSelectionCell.types.ts +++ b/packages/react-components/react-table/src/components/TableSelectionCell/TableSelectionCell.types.ts @@ -20,17 +20,25 @@ export type TableSelectionCellSlots = { */ export type TableSelectionCellProps = ComponentProps>> & { /** - * A table can have two kinds of selection modes + * A table can have two kinds of selection modes. + * @default checkbox */ type?: 'checkbox' | 'radio'; + + /** + * @default false + */ checked?: CheckboxProps['checked']; + /** - * Only visible when checked or the parent row is hovered + * Only visible when checked or the parent row is hovered/focused + * @default false */ subtle?: boolean; /** * Completely hides the selection cell visually but takes up the same space + * @default false */ hidden?: boolean; }; diff --git a/packages/react-components/react-table/stories/Table/CellActions.stories.tsx b/packages/react-components/react-table/stories/Table/CellActions.stories.tsx index 2adb695347af7a..8415b40a24e1db 100644 --- a/packages/react-components/react-table/stories/Table/CellActions.stories.tsx +++ b/packages/react-components/react-table/stories/Table/CellActions.stories.tsx @@ -112,3 +112,16 @@ export const CellActions = () => { ); }; + +CellActions.parameters = { + docs: { + description: { + story: [ + '`TableCellActions` is a container that is visible when the `TableRow`is hovered', + 'or when the current focused element is within the row. It is commonly used to contain interactive actions', + 'like buttons, but can be used for any generic content. Please ensure that the contents of cell actions', + 'are accessible.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/CellNavigation.stories.tsx b/packages/react-components/react-table/stories/Table/CellNavigation.stories.tsx index be4d9b99b308cc..3590435b423ba2 100644 --- a/packages/react-components/react-table/stories/Table/CellNavigation.stories.tsx +++ b/packages/react-components/react-table/stories/Table/CellNavigation.stories.tsx @@ -108,3 +108,14 @@ export const CellNavigation = () => { ); }; + +CellNavigation.parameters = { + 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'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/DataGrid.stories.tsx b/packages/react-components/react-table/stories/Table/DataGrid.stories.tsx new file mode 100644 index 00000000000000..a59145151ae634 --- /dev/null +++ b/packages/react-components/react-table/stories/Table/DataGrid.stories.tsx @@ -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: }, + author: { label: 'Max Mustermann', status: 'available' }, + lastUpdated: { label: '7h ago', timestamp: 3 }, + lastUpdate: { + label: 'You edited this', + icon: , + }, + }, + { + file: { label: 'Thursday presentation', icon: }, + author: { label: 'Erika Mustermann', status: 'busy' }, + lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, + lastUpdate: { + label: 'You recently opened this', + icon: , + }, + }, + { + file: { label: 'Training recording', icon: }, + author: { label: 'John Doe', status: 'away' }, + lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, + lastUpdate: { + label: 'You recently opened this', + icon: , + }, + }, + { + file: { label: 'Purchase order', icon: }, + 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: , + }, + }, +]; + +export const DataGrid = () => { + const columns: ColumnDefinition[] = React.useMemo( + () => [ + createColumn({ + columnId: 'file', + compare: (a, b) => { + return a.file.label.localeCompare(b.file.label); + }, + }), + createColumn({ + columnId: 'author', + compare: (a, b) => { + return a.author.label.localeCompare(b.author.label); + }, + }), + createColumn({ + columnId: 'lastUpdated', + compare: (a, b) => { + return a.lastUpdated.timestamp - b.lastUpdated.timestamp; + }, + }), + createColumn({ + 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 ( + + + + + File + Author + Last updated + Last update + + + + {rows.map(({ item, selected, onClick, onKeyDown, appearance }) => ( + + + + {item.file.label} + + + }> + {item.author.label} + + + {item.lastUpdated.label} + + {item.lastUpdate.label} + + + ))} + +
+ ); +}; + +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'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/MultipleSelect.stories.tsx b/packages/react-components/react-table/stories/Table/MultipleSelect.stories.tsx index cf489d0552426e..dccf74ca5839b4 100644 --- a/packages/react-components/react-table/stories/Table/MultipleSelect.stories.tsx +++ b/packages/react-components/react-table/stories/Table/MultipleSelect.stories.tsx @@ -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 ( - +
@@ -156,7 +159,7 @@ export const MultipleSelect = () => { Last update - + {rows.map(({ item, selected, onClick, onKeyDown, appearance }) => ( {
); }; + +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'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/MultipleSelectControlled.stories.tsx b/packages/react-components/react-table/stories/Table/MultipleSelectControlled.stories.tsx index 392da74235676c..1a27b55abf1d58 100644 --- a/packages/react-components/react-table/stories/Table/MultipleSelectControlled.stories.tsx +++ b/packages/react-components/react-table/stories/Table/MultipleSelectControlled.stories.tsx @@ -137,7 +137,8 @@ export const MultipleSelectControlled = () => { ...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); } }, @@ -149,10 +150,12 @@ export const MultipleSelectControlled = () => { const keyboardNavAttr = useArrowNavigationGroup({ axis: 'grid' }); return ( - +
@@ -162,7 +165,7 @@ export const MultipleSelectControlled = () => { Last update - + {rows.map(({ item, selected, onClick, onKeyDown, appearance }) => ( {
); }; + +MultipleSelectControlled.parameters = { + docs: { + description: { + story: [ + 'By default our hook is uncontrolled. However, it is possible to control selection features with external', + 'user state.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/NonNativeElements.stories.tsx b/packages/react-components/react-table/stories/Table/NonNativeElements.stories.tsx index 4a7c1bfd3cd8be..9871c60d049f7a 100644 --- a/packages/react-components/react-table/stories/Table/NonNativeElements.stories.tsx +++ b/packages/react-components/react-table/stories/Table/NonNativeElements.stories.tsx @@ -100,3 +100,17 @@ export const NonNativeElements = () => { ); }; + +NonNativeElements.parameters = { + docs: { + description: { + story: [ + 'By default `Table` components will render native semantic `` HTML elements.', + 'Native semantic elements provide the best accessibility support and it is recommended to user them.', + 'However, for certain use cases (i.e. Virtualization) the native semantic elements are not advisable', + 'due to strict browser layouting. Using the `noNativeElements` prop will ensure that all components are', + 'rendered as divs while ensuring a layout experience similar to semantic table elements.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/PrimaryCell.stories.tsx b/packages/react-components/react-table/stories/Table/PrimaryCell.stories.tsx index b4c9117c4bd9f8..ad77cf8997af95 100644 --- a/packages/react-components/react-table/stories/Table/PrimaryCell.stories.tsx +++ b/packages/react-components/react-table/stories/Table/PrimaryCell.stories.tsx @@ -115,3 +115,15 @@ export const PrimaryCell = () => {
); }; + +PrimaryCell.parameters = { + docs: { + description: { + story: [ + 'A primary cell creates emphasis by increasing icon size and font weight for the main text.', + 'Generally the primary cell should be used in the first column of a table, but there is no obligation', + 'to do so and can be used in any column by the user.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/SingleSelect.stories.tsx b/packages/react-components/react-table/stories/Table/SingleSelect.stories.tsx index 1f8a5a9cce57a6..c010b8ac60d110 100644 --- a/packages/react-components/react-table/stories/Table/SingleSelect.stories.tsx +++ b/packages/react-components/react-table/stories/Table/SingleSelect.stories.tsx @@ -131,7 +131,8 @@ export const SingleSelect = () => { ...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); } }, @@ -180,3 +181,17 @@ export const SingleSelect = () => { ); }; + +SingleSelect.parameters = { + docs: { + description: { + story: [ + 'The single selection scenario is similar to the multiple selection scenario. The `TableSelectionCell`', + 'Can render both checkbox and radio style components which are Fluent', + '[Checkbox](?path=/docs/components-checkbox--default) and [Radio](?path=/docs/components-radio--default)', + 'components. While the design recommendation is to use checkbox for multiselect and radio for single select.', + 'There is no obligation to do so.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/SingleSelectControlled.stories.tsx b/packages/react-components/react-table/stories/Table/SingleSelectControlled.stories.tsx index 0f9cd4fc9fd9b4..759fdcad48d193 100644 --- a/packages/react-components/react-table/stories/Table/SingleSelectControlled.stories.tsx +++ b/packages/react-components/react-table/stories/Table/SingleSelectControlled.stories.tsx @@ -136,7 +136,8 @@ export const SingleSelectControlled = () => { ...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); } }, @@ -186,3 +187,14 @@ export const SingleSelectControlled = () => { ); }; + +SingleSelectControlled.parameters = { + docs: { + description: { + story: [ + 'By default our hook is uncontrolled. However, it is possible to control selection features with external', + 'user state.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/Sort.stories.tsx b/packages/react-components/react-table/stories/Table/Sort.stories.tsx index 0f53ad5886f92d..86116e587ac3ee 100644 --- a/packages/react-components/react-table/stories/Table/Sort.stories.tsx +++ b/packages/react-components/react-table/stories/Table/Sort.stories.tsx @@ -178,3 +178,15 @@ export const Sort = () => { ); }; + +Sort.parameters = { + docs: { + description: { + story: [ + 'Using the `sortable` prop will configure all header cells to be buttons and add extra styles.', + 'The `TableHeaderCell` component accepts a `sortDirection` prop that will indicate whether the', + 'header is sorted. Handling the sort of data and column state is handled by our hook.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/SortControlled.stories.tsx b/packages/react-components/react-table/stories/Table/SortControlled.stories.tsx index a4bb1077a0fd32..55be1a3560f43d 100644 --- a/packages/react-components/react-table/stories/Table/SortControlled.stories.tsx +++ b/packages/react-components/react-table/stories/Table/SortControlled.stories.tsx @@ -183,3 +183,14 @@ export const SortControlled = () => { ); }; + +SortControlled.parameters = { + docs: { + description: { + story: [ + 'By default our hook is uncontrolled. However, it is possible to control sort features', + 'with external user state.', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/SubtleSelection.stories.tsx b/packages/react-components/react-table/stories/Table/SubtleSelection.stories.tsx index 97e1c9577ea986..92b3a51d4fe7ca 100644 --- a/packages/react-components/react-table/stories/Table/SubtleSelection.stories.tsx +++ b/packages/react-components/react-table/stories/Table/SubtleSelection.stories.tsx @@ -131,7 +131,7 @@ export const SubtleSelection = () => { ...row, onClick: (e: React.MouseEvent) => toggleRow(e, row.rowId), onKeyDown: (e: React.KeyboardEvent) => { - if (e.key === ' ' || e.key === 'Enter') { + if (e.key === ' ') { toggleRow(e, row.rowId); } }, @@ -143,10 +143,12 @@ export const SubtleSelection = () => { const keyboardNavAttr = useArrowNavigationGroup({ axis: 'grid' }); return ( - +
@@ -156,7 +158,7 @@ export const SubtleSelection = () => { Last update - + {rows.map(({ item, selected, onClick, onKeyDown, appearance }) => ( { aria-selected={selected} appearance={appearance} > - + {item.file.label} @@ -184,3 +186,17 @@ export const SubtleSelection = () => {
); }; + +SubtleSelection.parameters = { + docs: { + description: { + story: [ + 'By setting the `subtle` prop on the `TableSelectionCell` component, the selection indicator will only', + 'appear when:', + '- the `TableRow` component is hovered.', + '- The current focused element is within the `TableRow`', + '- The `TableSelectionCell` is checked', + ].join('\n'), + }, + }, +}; diff --git a/packages/react-components/react-table/stories/Table/TableDescription.md b/packages/react-components/react-table/stories/Table/TableDescription.md index ceb1ef61112628..4c0a261989ba12 100644 --- a/packages/react-components/react-table/stories/Table/TableDescription.md +++ b/packages/react-components/react-table/stories/Table/TableDescription.md @@ -10,3 +10,12 @@ > > - Features and APIs may change before final release > - Please contact us if you intend to use this in your product + +> 💡 This component is considered **low-level** and should be used when there is a need for more **customization** and +> support for **non-standard features**. Please check out the **DataGrid component** +> if you don't need lots of customization and rely on common features. There is less work involved and you will benefit +> from first class Microsoft design and accessibility support. + +A Table displays sets of two-dimensional data. The primitive components can be fully customized to support different +feature sets. The library provides a default hook that handles the business logic and state management of common +features. There is no obligation to use our hook with these components, we've created it for convenience. diff --git a/packages/react-components/react-table/stories/Table/index.stories.tsx b/packages/react-components/react-table/stories/Table/index.stories.tsx index a4f0a782ad7b22..4645bce07fd304 100644 --- a/packages/react-components/react-table/stories/Table/index.stories.tsx +++ b/packages/react-components/react-table/stories/Table/index.stories.tsx @@ -1,24 +1,43 @@ -import { Table } from '@fluentui/react-components/unstable'; +import { + Table, + TableHeader, + TableHeaderCell, + TableBody, + TableRow, + TableCell, + TableSelectionCell, + TableCellLayout, +} from '@fluentui/react-components/unstable'; import descriptionMd from './TableDescription.md'; export { Default } from './Default.stories'; -export { Sort } from './Sort.stories'; -export { SortControlled } from './SortControlled.stories'; -export { CellActions } from './CellActions.stories'; -export { PrimaryCell } from './PrimaryCell.stories'; export { SizeSmall } from './SizeSmall.stories'; export { SizeExtraSmall } from './SizeExtraSmall.stories'; export { NonNativeElements } from './NonNativeElements.stories'; +export { CellActions } from './CellActions.stories'; +export { PrimaryCell } from './PrimaryCell.stories'; +export { CellNavigation } from './CellNavigation.stories'; +export { Sort } from './Sort.stories'; +export { SortControlled } from './SortControlled.stories'; export { MultipleSelect } from './MultipleSelect.stories'; export { SingleSelect } from './SingleSelect.stories'; export { MultipleSelectControlled } from './MultipleSelectControlled.stories'; export { SingleSelectControlled } from './SingleSelectControlled.stories'; -export { CellNavigation } from './CellNavigation.stories'; export { SubtleSelection } from './SubtleSelection.stories'; +export { DataGrid } from './DataGrid.stories'; export default { title: 'Preview Components/Table', component: Table, + subcomponents: { + TableHeader, + TableHeaderCell, + TableBody, + TableRow, + TableCell, + TableSelectionCell, + TableCellLayout, + }, parameters: { docs: { description: {