Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -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;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export type TableCellLayoutSlots = {
* TableCellLayout Props
*/
export type TableCellLayoutProps = ComponentProps<Partial<TableCellLayoutSlots>> & {
/**
* Renders design variants of the table cell
* @default undefined
*/
appearance?: 'primary';
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export type TableHeaderCellSlots = {
* TableHeaderCell Props
*/
export type TableHeaderCellProps = ComponentProps<Partial<TableHeaderCellSlots>> & {
/**
* @default undefined
*/
sortDirection?: SortDirection;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export type TableRowSlots = {
*/
export type TableRowProps = ComponentProps<TableRowSlots> & {
/**
* 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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,25 @@ export type TableSelectionCellSlots = {
*/
export type TableSelectionCellProps = ComponentProps<Partial<Omit<TableSelectionCellSlots, 'media'>>> & {
/**
* 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;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,16 @@ export const CellActions = () => {
</Table>
);
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TableCellActions overlaps TableCellLayout. Is that expected?
image

Copy link
Contributor Author

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

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'),
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,14 @@ export const CellNavigation = () => {
</Table>
);
};

CellNavigation.parameters = {
Copy link
Member

Choose a reason for hiding this comment

The 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 columns and items are, where you import useArrowNavigationGroup from... :/

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Up @@ -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);
}
},
Expand All @@ -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}
/>
Expand All @@ -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}
Expand Down Expand Up @@ -184,3 +187,15 @@ export const MultipleSelect = () => {
</Table>
);
};

Copy link
Member

Choose a reason for hiding this comment

The 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.
For the uber checkbox for all lines I can only select it using Space (without scroll) cannot select it using Enter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 preventDefault to stop the scrolling

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'),
},
},
};
Loading