diff --git a/packages/@react-aria/table/docs/useTable.mdx b/packages/@react-aria/table/docs/useTable.mdx
index 3abd5836698..27b4cb6fa2f 100644
--- a/packages/@react-aria/table/docs/useTable.mdx
+++ b/packages/@react-aria/table/docs/useTable.mdx
@@ -816,7 +816,7 @@ update the column widths during a column resize operation. Note that this state
The second column resizing hook is . This hook handles the interactions for a table column's resizer
element, allowing the user to drag the resizer or use the keyboard arrows to expand the column's width. Be sure to pass the state returned by
to this hook so the tracked widths can be updated appropriately. We'll walk through all the required changes to the previous table implementation step by step below. For simplicity's sake, we'll be
-omitting support for selection and sorting.
+omitting support for selection, sorting, and nested columns.
### Table
@@ -866,32 +866,15 @@ function ResizableColumnsTable(props) {
return (
+
-
+ type="thead">
{collection.headerRows.map(headerRow => (
{[...headerRow.childNodes].map(column => (
@@ -912,9 +895,7 @@ function ResizableColumnsTable(props) {
{[...collection.body.childNodes].map(row => (
@@ -937,23 +918,53 @@ function ResizableColumnsTable(props) {
}
```
+
+ Show CSS
+
+```css
+/*
+ * Override the table's default display with "block" so that our defined column widths
+ * are respected. Without this and other table element display overrides, the columns
+ * can grow/shrink beyond their applied "width"
+*/
+.aria-table {
+ border-collapse: collapse;
+ width: 300px;
+ height: 200px;
+ display: block;
+ position: relative;
+ overflow: auto;
+}
+
+
+.aria-table-rowGroupHeader {
+ border-bottom: 2px solid var(--spectrum-global-color-gray-800);
+ position: sticky;
+ top: 0;
+ background: var(--spectrum-gray-100);
+ width: fit-content;
+}
+
+.aria-table-rowGroupBody {
+ max-height: 200px;
+}
+```
+
+
### Resizable table header
The `TableRowGroup` and `TableHeaderRow` only require minor style changes to accommodate the new `display` changes made in
`ResizableColumnsTable`.
```tsx example export=true render=false
-function ResizableTableRowGroup({type: Element, style, children}) {
+function ResizableTableRowGroup({type: Element, className, children}) {
let {rowGroupProps} = useTableRowGroup();
return (
+ /*- begin highlight -*/
+ className={`aria-table-rowGroup ${className}`}
+ /*- end highlight -*/
+ {...rowGroupProps}>
{children}
);
@@ -968,78 +979,57 @@ function ResizableTableHeaderRow({item, state, children}) {
return (
+ ref={ref}>
{children}
);
}
```
-The `TableColumnHeader` is where we see the bulk of the changes required to support resizable columns. First of all, we need to accommodate a `Resizer` element in every resizable column that
-the user can drag or focus to perform a resize operation. To avoid interfering with the existing grid keyboard navigation, we only display this resizer when
-the column is hovered or if the column itself is being resized. This however introduces another issue: how will keyboard, screen reader, and touch users begin a resize operation if the resizer
-is only visible on hover? We could have the user press on the column header to trigger a resize operation, but that action is often reserved for sorting and may feel unexpected to a user.
+
+ Show CSS
+
+```css
+.aria-table-rowGroup {
+ display: block;
+}
-To resolve this, we need to render a menu button in the column header if the column is resizable, allowing non-mouse users to select from a list of available actions on the column. For resizing, selecting the corresponding option
-should cause focus to shift to the resizer itself, displaying the resizer for touch screen users to drag and letting keyboard and screen readers resize the column via keyboard arrows and screen reader operations
-accordingly.
+.aria-table-row {
+ display: flex;
+}
+```
+
+
+The `TableColumnHeader` is where we see the bulk of the changes required to support resizable columns. First of all, we need to accommodate a `Resizer` element in every resizable column that
+the user can drag or focus to perform a resize operation. Since the resizer will be a focusable element within the table header, we need to make the header title a focusable element as well so keyboard
+focus won't be immediately sent to the resizer as you navigate between the column headers. Finally, we apply the computed width of our column from
+to the header element.
```tsx example export=true render=false
-import {useDescription} from '@react-aria/utils';
-import {useHover, usePress} from '@react-aria/interactions';
+// Reuse the Button from your component library. See below for details.
+import {Button} from 'your-component-library';
function ResizableTableColumnHeader({column, state, layoutState, onResizeStart, onResize, onResizeEnd}) {
- let {widths} = layoutState;
+ let {widths} = layoutState;
+ let allowsResizing = column.props.allowsResizing;
let ref = useRef(null);
- let resizerRef = useRef(null);
let {columnHeaderProps} = useTableColumnHeader({node: column}, state, ref);
- let {isFocusVisible, focusProps} = useFocusRing();
- let {hoverProps, isHovered} = useHover({});
- let showResizer = isHovered || layoutState.resizingColumn === column.key;
- let allowsResizing = column.props.allowsResizing;
-
- let {pressProps} = usePress({
- isDisabled: !allowsResizing,
- onPress() {
- layoutState.onColumnResizeStart(column.key);
- // Brief delay before moving focus to resizer input for screenreaders/Safari
- setTimeout(() => resizerRef.current?.focus(), 50);
- }
- });
- let descriptionProps = useDescription('Press to start resizing the column.');
return (
@@ -1047,6 +1037,60 @@ function ResizableTableColumnHeader({column, state, layoutState, onResizeStart,
}
```
+
+ Show CSS
+
+```css
+.aria-table-headerCell {
+ padding: 5px 10px;
+ outline: none;
+ cursor: default;
+ display: block;
+ flex: 0 0 auto;
+ box-sizing: border-box;
+ box-shadow: none;
+ text-align: left;
+}
+
+.aria-table-headerTitle {
+ width: 100%;
+ text-align: left;
+ border: none;
+ background: transparent;
+ flex: 1 1 auto;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ margin-inline-start: -6px;
+ outline: none;
+}
+
+.aria-table-headerTitle.focus {
+ outline: 2px solid orange;
+}
+```
+
+
+### Button
+
+The `Button` component is used in the above example to represent the table column header title. It is built using the [useButton](useButton.html) hook, and can be shared with many other components.
+
+
+ Show code
+
+```tsx example export=true render=false
+import {useButton} from '@react-aria/button';
+
+function Button(props) {
+ let ref = useRef(null);
+ let {focusProps, isFocusVisible} = useFocusRing();
+ let {buttonProps} = useButton(props, ref);
+ return ;
+}
+```
+
+
+
### Resizer
As described above, we need to implement an element that the user can drag/interact with to resize a column. Here we'll use the
@@ -1055,49 +1099,60 @@ Users can press and drag on the visible resizer to trigger the `onResize` callba
resize events and press Enter, Esc, or Space to exit resizing. Similarly, touch screen readers can swipe up and down to
resize the column and double tap to exit resizing.
-Note that we apply `visibility: hidden` to hide the resizer so that we reserve space for it in the column header at all times.
-
```tsx example export=true render=false
import {useTableColumnResize} from '@react-aria/table';
-const Resizer = React.forwardRef((props, ref) => {
- let {column, layoutState, onResizeStart, onResize, onResizeEnd, triggerRef, showResizer} = props;
+function Resizer(props) {
+ let {column, layoutState, onResizeStart, onResize, onResizeEnd} = props;
+ let ref = useRef(null);
let {resizerProps, inputProps} = useTableColumnResize({
column,
label: 'Resizer',
onResizeStart,
onResize,
- onResizeEnd,
- triggerRef
+ onResizeEnd
}, layoutState, ref);
+ let {focusProps, isFocusVisible} = useFocusRing();
return (
+ {...mergeProps(inputProps, focusProps)} />
);
-});
+};
+```
+
+
+ Show CSS
+
+```css
+.aria-table-resizer {
+ cursor: col-resize;
+ width: 6px;
+ height: auto;
+ border: 2px;
+ border-style: none solid;
+ border-color: grey;
+ touch-action: none;
+ flex: 0 0 auto;
+ box-sizing: border-box;
+ margin-left: 4px;
+}
+
+.aria-table-resizer.focus {
+ border-color: orange;
+}
```
+
+
### Resizable table body
Similar to `TableRowGroup` and `TableHeaderRow`, `TableRow` and `TableCell` only require minor style changes to
@@ -1107,7 +1162,7 @@ and handles text overflow.
```tsx example export=true render=false
function ResizableTableRow({item, children, state}) {
- // Same as previous TableRow implementation
+ // Same as previous TableRow implementation...
///- begin collapse -///
let ref = useRef();
let isSelected = state.selectionManager.isSelected(item.key);
@@ -1118,24 +1173,15 @@ function ResizableTableRow({item, children, state}) {
///- end collapse -///
return (
);
@@ -1144,34 +1190,24 @@ function ResizableTableRow({item, children, state}) {
```tsx example export=true render=false
function ResizableTableCell({cell, state, widths}) {
- // Same as previous TableCell implementation
+ /*- begin highlight -*/
+ let column = cell.column;
+ /*- end highlight -*/
+ // Same as previous TableCell implementation...
///- begin collapse -///
let ref = useRef();
let {gridCellProps} = useTableCell({node: cell}, state, ref);
let {isFocusVisible, focusProps} = useFocusRing();
///- end collapse -///
- /*- begin highlight -*/
- let column = cell.column;
- /*- end highlight -*/
-
return (