Skip to content

Commit bf297bc

Browse files
authored
Tableview col resizing (adobe#3210)
* Table Col resizing passes all tests * remove deprecated code * match function signatures and pull resizing out
1 parent f1de813 commit bf297bc

File tree

13 files changed

+964
-2091
lines changed

13 files changed

+964
-2091
lines changed

packages/@react-aria/table/src/useTableColumnResize.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13+
import {ColumnResizeState} from '@react-stately/table';
1314
import {focusSafely, useFocusable} from '@react-aria/focus';
15+
import {GridNode} from '@react-types/grid';
16+
import {HTMLAttributes, RefObject, useRef} from 'react';
1417
import {mergeProps} from '@react-aria/utils';
1518
import {useKeyboard, useMove} from '@react-aria/interactions';
1619
import {useLocale} from '@react-aria/i18n';
17-
import {useRef} from 'react';
1820

19-
export function useTableColumnResize(state, item, ref): any {
21+
interface ResizerAria {
22+
resizerProps: HTMLAttributes<HTMLElement>
23+
}
24+
25+
interface ResizerProps<T> {
26+
column: GridNode<T>
27+
}
28+
29+
export function useTableColumnResize<T>(props: ResizerProps<T>, state: ColumnResizeState<T>, ref: RefObject<HTMLDivElement>): ResizerAria {
30+
let {column: item} = props;
2031
const stateRef = useRef(null);
2132
// keep track of what the cursor on the body is so it can be restored back to that when done resizing
2233
const cursor = useRef(null);
@@ -32,7 +43,7 @@ export function useTableColumnResize(state, item, ref): any {
3243
}
3344
if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') {
3445
// switch focus back to the column header on escape
35-
const columnHeader = ref.current.previousSibling;
46+
const columnHeader = ref.current.previousSibling as HTMLElement;
3647
if (columnHeader) {
3748
focusSafely(columnHeader);
3849
}

packages/@react-spectrum/table/src/Resizer.tsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import {useTableContext} from './TableView';
88

99

1010
function Resizer(props, ref) {
11-
const {item} = props;
12-
let {state} = useTableContext();
13-
let {resizerProps} = useTableColumnResize(state, item, ref);
11+
const {column} = props;
12+
let {columnState} = useTableContext();
13+
let {resizerProps} = useTableColumnResize({column}, columnState, ref);
1414
return (
1515
<FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1616
<div
@@ -20,10 +20,10 @@ function Resizer(props, ref) {
2020
role="separator"
2121
aria-orientation="vertical"
2222
aria-label="Resize column"
23-
aria-labelledby={item.key}
24-
aria-valuenow={state.getColumnWidth(item.key)}
25-
aria-valuemin={state.getColumnMinWidth(item.key)}
26-
aria-valuemax={state.getColumnMaxWidth(item.key)} />
23+
aria-labelledby={column.key}
24+
aria-valuenow={columnState.getColumnWidth(column.key)}
25+
aria-valuemin={columnState.getColumnMinWidth(column.key)}
26+
aria-valuemax={columnState.getColumnMaxWidth(column.key)} />
2727
</FocusRing>
2828
);
2929
}

packages/@react-spectrum/table/src/TableView.tsx

+54-61
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ArrowDownSmall from '@spectrum-icons/ui/ArrowDownSmall';
1414
import {chain, mergeProps, useLayoutEffect} from '@react-aria/utils';
1515
import {Checkbox} from '@react-spectrum/checkbox';
1616
import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils';
17+
import {ColumnResizeState, TableState, useTableColumnResizeState, useTableState} from '@react-stately/table';
1718
import {DOMRef} from '@react-types/shared';
1819
import {FocusRing, focusSafely, useFocusRing} from '@react-aria/focus';
1920
import {GridNode} from '@react-types/grid';
@@ -29,8 +30,6 @@ import {SpectrumColumnProps, SpectrumTableProps} from '@react-types/table';
2930
import styles from '@adobe/spectrum-css-temp/components/table/vars.css';
3031
import stylesOverrides from './table.css';
3132
import {TableLayout} from '@react-stately/layout';
32-
import {TableState, useTableState} from '@react-stately/table';
33-
import {TableView_DEPRECATED} from './TableView_DEPRECATED';
3433
import {Tooltip, TooltipTrigger} from '@react-spectrum/tooltip';
3534
import {useButton} from '@react-aria/button';
3635
import {useHover} from '@react-aria/interactions';
@@ -81,7 +80,8 @@ const SELECTION_CELL_DEFAULT_WIDTH = {
8180

8281
interface TableContextValue<T> {
8382
state: TableState<T>,
84-
layout: TableLayout<T>
83+
layout: TableLayout<T>,
84+
columnState: ColumnResizeState<T>
8585
}
8686

8787
const TableContext = React.createContext<TableContextValue<unknown>>(null);
@@ -95,6 +95,7 @@ function TableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<H
9595
let {styleProps} = useStyleProps(props);
9696

9797
let [showSelectionCheckboxes, setShowSelectionCheckboxes] = useState(props.selectionStyle !== 'highlight');
98+
let {direction} = useLocale();
9899
let {scale} = useProvider();
99100

100101
const getDefaultWidth = useCallback(({hideHeader, isSelectionCell, showDivider}) => {
@@ -109,10 +110,11 @@ function TableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<H
109110
let state = useTableState({
110111
...props,
111112
showSelectionCheckboxes,
112-
selectionBehavior: props.selectionStyle === 'highlight' ? 'replace' : 'toggle',
113-
getDefaultWidth
113+
selectionBehavior: props.selectionStyle === 'highlight' ? 'replace' : 'toggle'
114114
});
115115

116+
const columnState = useTableColumnResizeState({...props, columns: state.collection.columns, getDefaultWidth, onColumnResize: props.onColumnResize, onColumnResizeEnd: props.onColumnResizeEnd});
117+
116118
// If the selection behavior changes in state, we need to update showSelectionCheckboxes here due to the circular dependency...
117119
let shouldShowCheckboxes = state.selectionManager.selectionBehavior !== 'replace';
118120
if (shouldShowCheckboxes !== showSelectionCheckboxes) {
@@ -141,9 +143,8 @@ function TableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<H
141143
}),
142144
[props.overflowMode, scale, density]
143145
);
144-
let {direction} = useLocale();
145146
layout.collection = state.collection;
146-
layout.getColumnWidth = state.getColumnWidth;
147+
layout.getColumnWidth = columnState.getColumnWidth;
147148

148149
let {gridProps} = useTable({
149150
...props,
@@ -259,7 +260,7 @@ function TableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<H
259260
}
260261

261262
if (item.props.allowsResizing) {
262-
return <ResizableTableColumnHeader item={item} state={state} />;
263+
return <ResizableTableColumnHeader column={item} />;
263264
}
264265

265266
return (
@@ -303,7 +304,7 @@ function TableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<H
303304
}, []);
304305

305306
return (
306-
<TableContext.Provider value={{state, layout}}>
307+
<TableContext.Provider value={{state, layout, columnState}}>
307308
<TableVirtualizer
308309
{...gridProps}
309310
{...styleProps}
@@ -316,7 +317,7 @@ function TableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<H
316317
'spectrum-Table--quiet': isQuiet,
317318
'spectrum-Table--wrap': props.overflowMode === 'wrap',
318319
'spectrum-Table--loadingMore': state.collection.body.props.loadingState === 'loadingMore',
319-
'spectrum-Table--resizingColumn': state.isResizingColumn,
320+
'spectrum-Table--resizingColumn': columnState.isResizingColumn,
320321
'spectrum-Table--isVerticalScrollbarVisible': isVerticalScrollbarVisible,
321322
'spectrum-Table--isHorizontalScrollbarVisible': isHorizontalScrollbarVisible
322323
},
@@ -331,11 +332,11 @@ function TableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<H
331332
focusedKey={state.selectionManager.focusedKey}
332333
renderView={renderView}
333334
renderWrapper={renderWrapper}
334-
setTableWidth={state.setTableWidth}
335+
setTableWidth={columnState.setTableWidth}
335336
onVisibleRectChange={onVisibleRectChange}
336337
domRef={domRef}
337338
bodyRef={bodyRef}
338-
getColumnWidth={state.getColumnWidth} />
339+
getColumnWidth={columnState.getColumnWidth} />
339340
</TableContext.Provider>
340341
);
341342
}
@@ -391,10 +392,7 @@ function TableVirtualizer({layout, collection, focusedKey, renderView, renderWra
391392
}, [bodyRef]);
392393

393394
let onVisibleRectChange = useCallback((rect: Rect) => {
394-
// setting the table width will recalculate column widths which we only want to do once the virtualizer is done initializing
395-
if (state.virtualizer.contentSize.height > 0) {
396-
setTableWidth(rect.width);
397-
}
395+
setTableWidth(rect.width);
398396

399397
state.setVisibleRect(rect);
400398

@@ -477,12 +475,14 @@ function TableColumnHeader(props) {
477475
}
478476

479477
let {hoverProps, isHovered} = useHover({});
480-
481-
const allProps = [columnHeaderProps, hoverProps];
482478
if (columnProps.allowsResizing) {
483-
allProps.push(buttonProps);
479+
// if we allow resizing, override the usePress that useTableColumnHeader generates so that clicking brings up the menu
480+
// instead of sorting the column immediately
481+
columnHeaderProps = {...columnHeaderProps, ...buttonProps};
484482
}
485483

484+
const allProps = [columnHeaderProps, hoverProps];
485+
486486
return (
487487
<FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
488488
<div
@@ -522,17 +522,18 @@ function TableColumnHeader(props) {
522522
);
523523
}
524524

525-
function ResizableTableColumnHeader({item, state}) {
525+
function ResizableTableColumnHeader({column}) {
526526
let ref = useRef();
527+
let {state} = useTableContext();
527528
let formatMessage = useMessageFormatter(intlMessages);
528529

529530
const onMenuSelect = (key) => {
530531
switch (key) {
531532
case 'sort-asc':
532-
state.sort(item.key, 'ascending');
533+
state.sort(column.key, 'ascending');
533534
break;
534535
case 'sort-desc':
535-
state.sort(item.key, 'descending');
536+
state.sort(column.key, 'descending');
536537
break;
537538
case 'resize':
538539
// focusResizer, needs timeout so that it happens after the animation timeout for menu close
@@ -542,28 +543,43 @@ function ResizableTableColumnHeader({item, state}) {
542543
break;
543544
}
544545
};
546+
let allowsSorting = column.props?.allowsSorting;
547+
let items = useMemo(() => {
548+
let options = {
549+
sortAscending: allowsSorting && {
550+
label: formatMessage('sortAscending'),
551+
id: 'sort-asc'
552+
},
553+
sortDescending: allowsSorting && {
554+
label: formatMessage('sortDescending'),
555+
id: 'sort-desc'
556+
},
557+
resize: {
558+
label: formatMessage('resizeColumn'),
559+
id: 'resize'
560+
}
561+
};
562+
return Object.keys(options).reduce((acc, key) => {
563+
if (options[key]) {
564+
acc.push(options[key]);
565+
}
566+
return acc;
567+
}, []);
568+
}, [allowsSorting]);
545569

546570
return (
547571
<>
548572
<MenuTrigger>
549-
<TableColumnHeader column={item} />
550-
<Menu onAction={onMenuSelect} minWidth="size-2000">
551-
{item.props?.allowsSorting &&
552-
<Item key="sort-asc">
553-
{formatMessage('sortAscending')}
573+
<TableColumnHeader column={column} />
574+
<Menu onAction={onMenuSelect} minWidth="size-2000" items={items}>
575+
{(item) => (
576+
<Item>
577+
{item.label}
554578
</Item>
555-
}
556-
{item.props?.allowsSorting &&
557-
<Item key="sort-desc">
558-
{formatMessage('sortDescending')}
559-
</Item>
560-
}
561-
<Item key="resize">
562-
{formatMessage('resizeColumn')}
563-
</Item>
579+
)}
564580
</Menu>
565581
</MenuTrigger>
566-
<Resizer ref={ref} item={item} />
582+
<Resizer ref={ref} column={column} />
567583
</>
568584
);
569585
}
@@ -815,27 +831,4 @@ function CenteredWrapper({children}) {
815831
*/
816832
const _TableView = React.forwardRef(TableView) as <T>(props: SpectrumTableProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
817833

818-
/*
819-
When ready to remove this feature flag, you can remove this whole section of code, delete the _DEPRECATED files, and just replace the export with the _TableView above.
820-
*/
821-
function FeatureFlaggedTableView<T extends object>(props: SpectrumTableProps<T>, ref: DOMRef<HTMLDivElement>) {
822-
let state = useTableState({
823-
...props
824-
});
825-
826-
const someColumnsAllowResizing = state.collection.columns.some(c => c.props?.allowsResizing);
827-
828-
if (someColumnsAllowResizing) {
829-
return <_TableView {...props} ref={ref} />;
830-
} else {
831-
return <TableView_DEPRECATED {...props} ref={ref} />;
832-
}
833-
}
834-
835-
/**
836-
* Tables are containers for displaying information. They allow users to quickly scan, sort, compare, and take action on large amounts of data.
837-
*/
838-
const _FeatureFlaggedTableView = React.forwardRef(FeatureFlaggedTableView) as <T>(props: SpectrumTableProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
839-
840-
841-
export {_FeatureFlaggedTableView as TableView};
834+
export {_TableView as TableView};

0 commit comments

Comments
 (0)