import { Accessibility, tableBehavior, TableBehaviorProps } from '@fluentui/accessibility'; import { ComponentWithAs, getElementType, useTelemetry, mergeVariablesOverrides, useUnhandledProps, useAccessibility, useStyles, useFluentContext, } from '@fluentui/react-bindings'; import * as customPropTypes from '@fluentui/react-proptypes'; import * as PropTypes from 'prop-types'; import * as _ from 'lodash'; import * as React from 'react'; import { UIComponentProps, ChildrenComponentProps, commonPropTypes, childrenExist, createShorthandFactory, } from '../../utils'; import { TableRow, TableRowProps } from './TableRow'; import { TableCell } from './TableCell'; import { ShorthandCollection, ShorthandValue, FluentComponentStaticProps } from '../../types'; export interface TableSlotClassNames { header: string; } export interface TableProps extends UIComponentProps, ChildrenComponentProps { /** * Accessibility behavior if overridden by the user. * */ accessibility?: Accessibility; /** The columns of the Table with a space-separated list of values. */ header?: ShorthandValue; /** The rows of the Table with a space-separated list of values. */ rows?: ShorthandCollection; /** * Render table in compact mode */ compact?: boolean; } export const tableClassName = 'ui-table'; export const tableSlotClassNames: TableSlotClassNames = { header: `${tableClassName}__header`, }; export type TableStylesProps = never; /** * A Table is used to display data in tabular layout * * @accessibility * Implements ARIA [Data Grid](https://www.w3.org/TR/wai-aria-practices/#dataGrid) design pattern for presenting tabular information. * When gridcell contains actionable element, use [gridCellWithFocusableElementBehavior](/components/table/accessibility#grid-cell-with-focusable-element-behavior-ts). [More information available in aria documentation.](https://www.w3.org/TR/wai-aria-practices/#gridNav_focus) * Use [gridCellMultipleFocusableBehavior](/components/table/accessibility#gridCellMultipleFocusableBehavior), when gridcell contains: * \- editable content * \- multiple actionable elements * \- component that utilizes arrow keys in its navigation, like menu button, dropdown, radio group, slider, etc. * [More information available in aria documentation.](https://www.w3.org/TR/wai-aria-practices/#gridNav_inside) * @accessibilityIssues * [NVDA narrate table title(aria-label) twice](https://github.com/nvaccess/nvda/issues/10548) * [Accessibility DOM > Table > gridcell > when gridcell is focused, then selected state is send to reader](https://bugs.chromium.org/p/chromium/issues/detail?id=1030378) * [JAWS narrate grid name twice, once as table and second time as grid](https://github.com/FreedomScientific/VFO-standards-support/issues/346) * [JAWS doesn't narrate grid column name, when focus is on actionable element in the cell] (https://github.com/FreedomScientific/VFO-standards-support/issues/348) * [aria-sort is not output at child elements](https://github.com/FreedomScientific/VFO-standards-support/issues/319) * [VoiceOver not announcing rows correctly for a grid with presentation elements inside](https://bugs.chromium.org/p/chromium/issues/detail?id=1054424) * VoiceOver doesn't narrate aria-rowcount value in table or grid */ export const Table: ComponentWithAs<'div', TableProps> & FluentComponentStaticProps & { Cell: typeof TableCell; Row: typeof TableRow; } = props => { const context = useFluentContext(); const { setStart, setEnd } = useTelemetry(Table.displayName, context.telemetry); setStart(); const { children, rows, header, compact, accessibility, className, design, styles, variables } = props; const hasChildren = childrenExist(children); const ElementType = getElementType(props); const unhandledProps = useUnhandledProps(Table.handledProps, props); const getA11yProps = useAccessibility(accessibility, { debugName: Table.displayName, rtl: context.rtl, }); const { classes } = useStyles(Table.displayName, { className: tableClassName, mapPropsToInlineStyles: () => ({ className, design, styles, variables, }), rtl: context.rtl, }); const renderRows = () => { return _.map(rows, (row: TableRowProps) => { return TableRow.create(row, { defaultProps: () => getA11yProps('row', { compact, onClick: (e, props) => { _.invoke(row, 'onClick', e, props); }, }), overrideProps: predefinedProps => ({ variables: mergeVariablesOverrides(variables, predefinedProps.variables), }), }); }); }; const renderHeader = () => { return TableRow.create(header, { defaultProps: () => getA11yProps('row', { header: true, compact, className: tableSlotClassNames.header, }), overrideProps: predefinedProps => ({ variables: mergeVariablesOverrides(variables, predefinedProps.variables), }), }); }; const element = getA11yProps.unstable_wrapWithFocusZone( {hasChildren && children} {/* */} {!hasChildren && renderHeader()} {/* */} {/* */} {!hasChildren && renderRows()} {/* */} , ); setEnd(); return element; }; Table.displayName = 'Table'; Table.Cell = TableCell; Table.Row = TableRow; Table.create = createShorthandFactory({ Component: Table, }); Table.propTypes = { ...commonPropTypes.createCommon({ content: false, }), header: customPropTypes.itemShorthand, rows: customPropTypes.collectionShorthand, compact: PropTypes.bool, }; Table.handledProps = Object.keys(Table.propTypes) as any; Table.defaultProps = { accessibility: tableBehavior, };