Skip to content

Commit 1098d68

Browse files
authored
Update keyboard navigation in CardView (#2737)
* remove action menu and footer from cards in cardview * removing footer + action menu from standalone cards and fixing css for detail * fixing Card tests * adding prop to stop focus from making it to cell children * adding keyboard nav tests for CardView * fixing tests * reserving more room for detail text in large scale * adding warning if focusable elements are present in card also removed footer and action menu slots from card * fixing lint * removing preventChildrenFocus prop in favor of deleting gridcell keydown handler from Card stopgap solution so that we dont have to add a new prop just to prevent focus on Card checkbox during keyboard nav * Reduce vertical item spacing in CardView and remove item padding option from Waterfall layout (#2758) * reducing gallery and waterfall quiet card vertical in between spacing * removing item padding from waterfall layout * fixing errorneous scaled height calculation
1 parent 556773f commit 1098d68

File tree

13 files changed

+226
-590
lines changed

13 files changed

+226
-590
lines changed

packages/@adobe/spectrum-css-temp/components/card/index.css

Lines changed: 37 additions & 145 deletions
Large diffs are not rendered by default.

packages/@react-spectrum/card/src/CardBase.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
import {AriaCardProps, SpectrumCardProps} from '@react-types/card';
1414
import {Checkbox} from '@react-spectrum/checkbox';
1515
import {classNames, SlotProvider, useDOMRef, useHasChild, useStyleProps} from '@react-spectrum/utils';
16-
import {Divider} from '@react-spectrum/divider';
1716
import {DOMRef, Node} from '@react-types/shared';
1817
import {filterDOMProps, mergeProps, useLayoutEffect, useResizeObserver, useSlotId} from '@react-aria/utils';
1918
import {FocusRing} from '@react-aria/focus';
19+
import {getFocusableTreeWalker} from '@react-aria/focus';
2020
import React, {HTMLAttributes, useCallback, useMemo, useRef, useState} from 'react';
2121
import styles from '@adobe/spectrum-css-temp/components/card/vars.css';
2222
import {useCardViewContext} from './CardViewContext';
@@ -38,7 +38,8 @@ function CardBase<T extends object>(props: CardBaseProps<T>, ref: DOMRef<HTMLDiv
3838
orientation = 'vertical',
3939
articleProps = {},
4040
item,
41-
layout
41+
layout,
42+
children
4243
} = props;
4344

4445
let key = item?.key;
@@ -50,6 +51,7 @@ function CardBase<T extends object>(props: CardBaseProps<T>, ref: DOMRef<HTMLDiv
5051
let {cardProps, titleProps, contentProps} = useCard(props);
5152
let domRef = useDOMRef(ref);
5253
let gridRef = useRef<HTMLDivElement>();
54+
let checkboxRef = useRef(null);
5355

5456
// cards are only interactive if there is a selection manager and it allows selection
5557
let {hoverProps, isHovered} = useHover({isDisabled: manager === undefined || manager?.selectionMode === 'none' || isDisabled});
@@ -59,8 +61,6 @@ function CardBase<T extends object>(props: CardBaseProps<T>, ref: DOMRef<HTMLDiv
5961
isDisabled
6062
});
6163

62-
let hasFooter = useHasChild(`.${styles['spectrum-Card-footer']}`, gridRef);
63-
6464
// ToDo: see css for comment about avatar under selector .spectrum-Card--noLayout.spectrum-Card--default
6565
let hasPreviewImage = useHasChild(`.${styles['spectrum-Card-image']}`, gridRef);
6666
let hasPreviewIllustration = useHasChild(`.${styles['spectrum-Card-illustration']}`, gridRef);
@@ -92,10 +92,7 @@ function CardBase<T extends object>(props: CardBaseProps<T>, ref: DOMRef<HTMLDiv
9292
avatar: {UNSAFE_className: classNames(styles, 'spectrum-Card-avatar'), size: 'avatar-size-400'},
9393
heading: {UNSAFE_className: classNames(styles, 'spectrum-Card-heading'), ...titleProps},
9494
content: {UNSAFE_className: classNames(styles, 'spectrum-Card-content'), ...contentProps},
95-
detail: {UNSAFE_className: classNames(styles, 'spectrum-Card-detail')},
96-
actionmenu: {UNSAFE_className: classNames(styles, 'spectrum-Card-actions'), align: 'end', isQuiet: true},
97-
footer: {UNSAFE_className: classNames(styles, 'spectrum-Card-footer'), isHidden: isQuiet},
98-
divider: {UNSAFE_className: classNames(styles, 'spectrum-Card-divider'), size: 'S'}
95+
detail: {UNSAFE_className: classNames(styles, 'spectrum-Card-detail')}
9996
}), [titleProps, contentProps, height, isQuiet, orientation]);
10097

10198
// This is only for quiet grid cards
@@ -147,8 +144,21 @@ function CardBase<T extends object>(props: CardBaseProps<T>, ref: DOMRef<HTMLDiv
147144
};
148145
}
149146
// ToDo: how to re-run if image src changes?
150-
}, [props.children, setIsCloseToSquare, isQuiet, layout]);
147+
}, [children, setIsCloseToSquare, isQuiet, layout]);
151148

149+
useLayoutEffect(() => {
150+
if (gridRef?.current) {
151+
let walker = getFocusableTreeWalker(gridRef.current);
152+
let nextNode = walker.nextNode();
153+
while (nextNode != null) {
154+
if (checkboxRef.current && !checkboxRef.current.UNSAFE_getDOMNode().contains(nextNode)) {
155+
console.warn('Card does not support focusable elements, please contact the team regarding your use case.');
156+
break;
157+
}
158+
nextNode = walker.nextNode();
159+
}
160+
}
161+
}, [children]);
152162

153163
return (
154164
<FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
@@ -174,6 +184,7 @@ function CardBase<T extends object>(props: CardBaseProps<T>, ref: DOMRef<HTMLDiv
174184
{manager && manager.selectionMode !== 'none' && (
175185
<div className={classNames(styles, 'spectrum-Card-checkboxWrapper')}>
176186
<Checkbox
187+
ref={checkboxRef}
177188
isDisabled={isDisabled}
178189
excludeFromTabOrder
179190
isSelected={isSelected}
@@ -184,8 +195,7 @@ function CardBase<T extends object>(props: CardBaseProps<T>, ref: DOMRef<HTMLDiv
184195
</div>
185196
)}
186197
<SlotProvider slots={slots}>
187-
{props.children}
188-
{hasFooter && <Divider />}
198+
{children}
189199
</SlotProvider>
190200
<div className={classNames(styles, 'spectrum-Card-decoration')} />
191201
</div>

packages/@react-spectrum/card/src/CardView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ function InternalCard(props) {
204204
cardOrientation = 'vertical';
205205
}
206206

207+
// We don't want to focus the checkbox (or any other focusable elements) within the Card
208+
// when pressing the arrow keys so we delete the key down handler here. Arrow key navigation between
209+
// the cards in the CardView is handled by useGrid => useSelectableCollection instead.
210+
delete gridCellProps.onKeyDownCapture;
207211
return (
208212
<div {...rowProps} ref={rowRef} className={classNames(styles, 'spectrum-CardView-row')}>
209213
<CardBase

packages/@react-spectrum/card/src/GalleryLayout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface GalleryLayoutOptions extends BaseLayoutOptions {
3030
itemSpacing?: Size,
3131
/**
3232
* The vertical padding for an item.
33-
* @default 114
33+
* @default 78
3434
*/
3535
itemPadding?: number,
3636
/**
@@ -60,8 +60,8 @@ const DEFAULT_OPTIONS = {
6060
minItemSize: new Size(136, 136),
6161
itemSpacing: new Size(18, 18),
6262
itemPadding: {
63-
'medium': 114,
64-
'large': 143
63+
'medium': 78,
64+
'large': 99
6565
},
6666
dropSpacing: 100,
6767
margin: 24

packages/@react-spectrum/card/src/WaterfallLayout.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,7 @@ export interface WaterfallLayoutOptions extends BaseLayoutOptions {
3535
* The maximum number of columns.
3636
* @default Infinity
3737
*/
38-
maxColumns?: number,
39-
/**
40-
* The vertical padding for an item.
41-
* @default 56
42-
*/
43-
itemPadding?: number
38+
maxColumns?: number
4439
}
4540

4641
// TODO: this didn't have any options that varied with card size, should it have?
@@ -49,7 +44,6 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
4944
protected maxItemSize: Size;
5045
protected minSpace: Size;
5146
protected maxColumns: number;
52-
itemPadding: number;
5347
protected numColumns: number;
5448
protected itemWidth: number;
5549
protected horizontalSpacing: number;
@@ -63,8 +57,6 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
6357
this.margin = options.margin != null ? options.margin : 24;
6458
this.minSpace = options.minSpace || new Size(18, 18);
6559
this.maxColumns = options.maxColumns || Infinity;
66-
// TODO: not entirely sure what this is for since the layout will automatically shift itself to the correct vertical space for the card
67-
this.itemPadding = options.itemPadding != null ? options.itemPadding : 56;
6860

6961
this.itemWidth = 0;
7062
this.numColumns = 0;
@@ -113,8 +105,8 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
113105
} else if (node.props.width && node.props.height) {
114106
let nodeWidth = node.props.width;
115107
let nodeHeight = node.props.height;
116-
let scaledHeight = Math.round(nodeWidth * ((itemWidth) / nodeHeight));
117-
height = Math.max(this.minItemSize.height, Math.min(this.maxItemSize.height, scaledHeight)) + this.itemPadding;
108+
let scaledHeight = Math.round(nodeHeight * ((itemWidth) / nodeWidth));
109+
height = Math.max(this.minItemSize.height, Math.min(this.maxItemSize.height, scaledHeight));
118110
} else {
119111
height = itemWidth;
120112
}

0 commit comments

Comments
 (0)