Skip to content

Commit

Permalink
Support links in collection components (#4993)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett authored Sep 14, 2023
1 parent f6239e3 commit b01500b
Show file tree
Hide file tree
Showing 82 changed files with 1,962 additions and 294 deletions.
9 changes: 9 additions & 0 deletions packages/@adobe/spectrum-css-temp/components/menu/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ governing permissions and limitations under the License.
.spectrum-Menu-item {
cursor: default;
position: relative;
display: block;

box-sizing: border-box;

Expand All @@ -89,10 +90,18 @@ governing permissions and limitations under the License.
font-style: normal;
text-decoration: none;

&[href] {
cursor: pointer;
}

&:focus {
outline: none;
}

&.is-disabled {
cursor: default;
}

&.is-selected {
.spectrum-Menu-checkmark {
display: block;
Expand Down
9 changes: 4 additions & 5 deletions packages/@adobe/spectrum-css-temp/components/tabs/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ governing permissions and limitations under the License.
cursor: default;
outline: none;

&[href] {
cursor: pointer;
}

&.is-disabled {
cursor: default;

.spectrum-Tabs-itemLabel {
cursor: default;
}
}

.spectrum-Icon {
Expand Down Expand Up @@ -107,7 +107,6 @@ governing permissions and limitations under the License.
}

.spectrum-Tabs-itemLabel {
cursor: default;
vertical-align: top;
display: inline-block;

Expand Down
7 changes: 6 additions & 1 deletion packages/@adobe/spectrum-css-temp/components/tags/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ governing permissions and limitations under the License.
align-items: center;
box-sizing: border-box;
position: relative;
cursor: default;

margin: var(--spectrum-tag-margin);
padding-inline-start: calc(var(--spectrum-tag-padding-x) - var(--spectrum-tag-border-size));
Expand All @@ -65,7 +66,12 @@ governing permissions and limitations under the License.
box-shadow var(--spectrum-global-animation-duration-100) ease-in-out,
background-color var(--spectrum-global-animation-duration-100) ease-in-out;

&[data-href] {
cursor: pointer;
}

&.is-disabled {
cursor: default;
pointer-events: none;
}

Expand Down Expand Up @@ -100,7 +106,6 @@ governing permissions and limitations under the License.
margin-inline-end: var(--spectrum-tag-padding-x);
flex: 1 1 auto;
font-size: var(--spectrum-tag-text-size);
cursor: default;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-aria/accordion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
},
"publishConfig": {
"access": "public"
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-aria/actiongroup/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
},
"publishConfig": {
"access": "public"
Expand Down
21 changes: 18 additions & 3 deletions packages/@react-aria/combobox/src/useComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {AriaComboBoxProps} from '@react-types/combobox';
import {ariaHideOutside} from '@react-aria/overlays';
import {AriaListBoxOptions, getItemId, listData} from '@react-aria/listbox';
import {BaseEvent, DOMAttributes, KeyboardDelegate, PressEvent} from '@react-types/shared';
import {chain, isAppleDevice, mergeProps, useLabels} from '@react-aria/utils';
import {chain, isAppleDevice, mergeProps, useLabels, useRouter} from '@react-aria/utils';
import {ComboBoxState} from '@react-stately/combobox';
import {FocusEvent, InputHTMLAttributes, KeyboardEvent, RefObject, TouchEvent, useEffect, useMemo, useRef} from 'react';
import {getChildNodes, getItemCount} from '@react-stately/collections';
Expand Down Expand Up @@ -106,6 +106,8 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
isVirtualized: true
});

let router = useRouter();

// For textfield specific keydown operations
let onKeyDown = (e: BaseEvent<KeyboardEvent<any>>) => {
switch (e.key) {
Expand All @@ -116,7 +118,19 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
e.preventDefault();
}

state.commit();
// If the focused item is a link, trigger opening it. Items that are links are not selectable.
if (state.isOpen && state.selectionManager.focusedKey != null && state.selectionManager.isLink(state.selectionManager.focusedKey)) {
if (e.key === 'Enter') {
let item = listBoxRef.current.querySelector(`[data-key="${state.selectionManager.focusedKey}"]`);
if (item instanceof HTMLAnchorElement) {
router.open(item, e);
}
}

state.close();
} else {
state.commit();
}
break;
case 'Escape':
if (
Expand Down Expand Up @@ -330,7 +344,8 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
autoFocus: state.focusStrategy,
shouldUseVirtualFocus: true,
shouldSelectOnPressUp: true,
shouldFocusOnHover: true
shouldFocusOnHover: true,
linkBehavior: 'selection' as const
}),
descriptionProps,
errorMessageProps
Expand Down
18 changes: 14 additions & 4 deletions packages/@react-aria/gridlist/src/useGridList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ export interface AriaGridListOptions<T> extends Omit<AriaGridListProps<T>, 'chil
* Whether focus should wrap around when the end/start is reached.
* @default false
*/
shouldFocusWrap?: boolean
shouldFocusWrap?: boolean,
/**
* The behavior of links in the collection.
* - 'action': link behaves like onAction.
* - 'selection': link follows selection interactions (e.g. if URL drives selection).
* - 'override': links override all other interactions (link items are not selectable).
* @default 'action'
*/
linkBehavior?: 'action' | 'selection' | 'override'
}

export interface GridListAria {
Expand All @@ -70,7 +78,8 @@ export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T
let {
isVirtualized,
keyboardDelegate,
onAction
onAction,
linkBehavior = 'action'
} = props;

if (!props['aria-label'] && !props['aria-labelledby']) {
Expand All @@ -85,11 +94,12 @@ export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T
keyboardDelegate: keyboardDelegate,
isVirtualized,
selectOnFocus: state.selectionManager.selectionBehavior === 'replace',
shouldFocusWrap: props.shouldFocusWrap
shouldFocusWrap: props.shouldFocusWrap,
linkBehavior
});

let id = useId(props.id);
listMap.set(state, {id, onAction});
listMap.set(state, {id, onAction, linkBehavior});

let descriptionProps = useHighlightSelectionDescription({
selectionManager: state.selectionManager,
Expand Down
10 changes: 6 additions & 4 deletions packages/@react-aria/gridlist/src/useGridListItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import {DOMAttributes, FocusableElement, Node as RSNode} from '@react-types/shared';
import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
import {getRowId, listMap} from './utils';
import {getScrollParent, mergeProps, scrollIntoViewport, useSlotId} from '@react-aria/utils';
import {getScrollParent, getSyntheticLinkProps, mergeProps, scrollIntoViewport, useSlotId} from '@react-aria/utils';
import {isFocusVisible} from '@react-aria/interactions';
import type {ListState} from '@react-stately/list';
import {KeyboardEvent as ReactKeyboardEvent, RefObject, useRef} from 'react';
Expand Down Expand Up @@ -53,7 +53,7 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
} = props;

let {direction} = useLocale();
let {onAction} = listMap.get(state);
let {onAction, linkBehavior} = listMap.get(state);
let descriptionId = useSlotId();

// We need to track the key of the item at the time it was last focused so that we force
Expand All @@ -77,7 +77,8 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
isVirtualized,
shouldSelectOnPressUp,
onAction: onAction ? () => onAction(node.key) : undefined,
focus
focus,
linkBehavior
});

let onKeyDown = (e: ReactKeyboardEvent) => {
Expand Down Expand Up @@ -177,7 +178,8 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
}
};

let rowProps: DOMAttributes = mergeProps(itemProps, {
let linkProps = getSyntheticLinkProps(node.props);
let rowProps: DOMAttributes = mergeProps(itemProps, linkProps, {
role: 'row',
onKeyDownCapture: onKeyDown,
onFocus,
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-aria/gridlist/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import type {ListState} from '@react-stately/list';

interface ListMapShared {
id: string,
onAction: (key: Key) => void
onAction: (key: Key) => void,
linkBehavior?: 'action' | 'selection' | 'override'
}

// Used to share:
Expand Down
Loading

1 comment on commit b01500b

@rspbot
Copy link

@rspbot rspbot commented on b01500b Sep 14, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.