Skip to content

Commit 15721e5

Browse files
authored
Add docs for links in collections and client side routing guide (#5078)
1 parent a399db1 commit 15721e5

File tree

74 files changed

+1710
-279
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1710
-279
lines changed

packages/@adobe/react-spectrum/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"dependencies": {
2727
"@react-aria/i18n": "^3.8.2",
2828
"@react-aria/ssr": "^3.8.0",
29+
"@react-aria/utils": "^3.20.0",
2930
"@react-aria/visually-hidden": "^3.8.4",
3031
"@react-spectrum/actionbar": "^3.2.0",
3132
"@react-spectrum/actiongroup": "^3.9.2",

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ governing permissions and limitations under the License.
102102
margin-block-end: 0;
103103
font-size: inherit;
104104

105-
&[href],
106-
&[tabindex="0"] {
105+
&:not([aria-disabled]) {
107106
cursor: pointer;
108107

109108
&:hover,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ svg.spectrum-Table-sortedIcon {
370370
cursor: default;
371371
transition: background-color var(--spectrum-global-animation-duration-100) ease-in-out;
372372

373+
&[data-href] {
374+
cursor: pointer;
375+
}
376+
373377
&:focus {
374378
outline: 0;
375379
}

packages/@react-aria/autocomplete/docs/useSearchAutocomplete.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,10 @@ function AsyncLoadingExample() {
572572
}
573573
```
574574

575+
### Links
576+
577+
By default, interacting with an item in a SearchAutocomplete updates the input value. Alternatively, items may be links to another page or website. This can be achieved by passing the `href` prop to the `<Item>` component. Interacting with link items navigates to the provided URL and does not update the input value. See the [links](useListBox.html#links) section in the `useListBox` docs for details on how to support this.
578+
575579
## Internationalization
576580

577581
`useSearchAutocomplete` handles some aspects of internationalization automatically.

packages/@react-aria/combobox/docs/useComboBox.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,10 @@ function AsyncLoadingExample() {
671671
}
672672
```
673673

674+
### Links
675+
676+
By default, interacting with an item in a ComboBox selects it and updates the input value. Alternatively, items may be links to another page or website. This can be achieved by passing the `href` prop to the `<Item>` component. Interacting with link items navigates to the provided URL and does not update the selection or input value. See the [links](useListBox.html#links) section in the `useListBox` docs for details on how to support this.
677+
674678
## Internationalization
675679

676680
`useComboBox` handles some aspects of internationalization automatically.

packages/@react-aria/gridlist/docs/useGridList.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import selectionDocs from 'docs:@react-stately/selection';
1717
import statelyDocs from 'docs:@react-stately/list';
1818
import focusDocs from 'docs:@react-aria/focus';
1919
import checkboxDocs from 'docs:@react-aria/checkbox';
20+
import utilsDocs from 'docs:@react-aria/utils';
2021
import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink, PageDescription} from '@react-spectrum/docs';
2122
import {Keyboard} from '@react-spectrum/text';
2223
import packageData from '@react-aria/gridlist/package.json';
@@ -484,6 +485,29 @@ This behavior is slightly different when `selectionBehavior="replace"`, where si
484485
</div>
485486
```
486487

488+
### Links
489+
490+
Items in a GridList may also be links to another page or website. This can be achieved by passing the `href` prop to the `<Item>` component. Links behave the same way as described above for row actions depending on the `selectionMode` and `selectionBehavior`.
491+
492+
```tsx example
493+
<List aria-label="Links" selectionMode="multiple">
494+
<Item href="https://adobe.com/" target="_blank">Adobe</Item>
495+
<Item href="https://apple.com/" target="_blank">Apple</Item>
496+
<Item href="https://google.com/" target="_blank">Google</Item>
497+
<Item href="https://microsoft.com/" target="_blank">Microsoft</Item>
498+
</List>
499+
```
500+
501+
```css hidden
502+
.list li[data-href] {
503+
cursor: pointer;
504+
}
505+
```
506+
507+
#### Client side routing
508+
509+
The `<Item>` component works with frameworks and client side routers like [Next.js](https://nextjs.org/) and [React Router](https://reactrouter.com/en/main). As with other React Aria components that support links, this works via the <TypeLink links={utilsDocs.links} type={utilsDocs.exports.RouterProvider} /> component at the root of your app. See the [client side routing guide](routing.html) to learn how to set this up.
510+
487511
### Asynchronous loading
488512

489513
This example uses the [useAsyncList](../react-stately/useAsyncList.html) hook to handle asynchronous loading of data from a server. You may additionally want to display a spinner to indicate the loading state to the user, or support features like infinite scroll to load more data.

packages/@react-aria/gridlist/src/useGridListItem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
178178
}
179179
};
180180

181-
let linkProps = getSyntheticLinkProps(node.props);
181+
let linkProps = itemStates.hasAction ? getSyntheticLinkProps(node.props) : {};
182182
let rowProps: DOMAttributes = mergeProps(itemProps, linkProps, {
183183
role: 'row',
184184
onKeyDownCapture: onKeyDown,

packages/@react-aria/link/docs/useLink.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ function Link(props) {
7676
<a
7777
{...linkProps}
7878
ref={ref}
79-
href={props.href}
80-
target={props.target}
8179
style={{color: 'var(--blue)'}}>
8280
{props.children}
8381
</a>
@@ -136,8 +134,6 @@ function Link(props) {
136134
<a
137135
{...linkProps}
138136
ref={ref}
139-
href={props.href}
140-
target={props.target}
141137
style={{
142138
color: props.isDisabled ? 'var(--gray)' : 'var(--blue)',
143139
cursor: props.isDisabled ? 'default' : 'pointer'

packages/@react-aria/link/src/useLink.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
import {AriaLinkProps} from '@react-types/link';
1414
import {DOMAttributes, FocusableElement} from '@react-types/shared';
15-
import {filterDOMProps, mergeProps} from '@react-aria/utils';
16-
import {RefObject} from 'react';
15+
import {filterDOMProps, mergeProps, shouldClientNavigate, useRouter} from '@react-aria/utils';
16+
import React, {RefObject} from 'react';
1717
import {useFocusable} from '@react-aria/focus';
1818
import {usePress} from '@react-aria/interactions';
1919

@@ -62,6 +62,7 @@ export function useLink(props: AriaLinkOptions, ref: RefObject<FocusableElement>
6262
let {pressProps, isPressed} = usePress({onPress, onPressStart, onPressEnd, isDisabled, ref});
6363
let domProps = filterDOMProps(otherProps, {labelable: true, isLink: elementType === 'a'});
6464
let interactionHandlers = mergeProps(focusableProps, pressProps);
65+
let router = useRouter();
6566

6667
return {
6768
isPressed, // Used to indicate press state for visual
@@ -70,12 +71,25 @@ export function useLink(props: AriaLinkOptions, ref: RefObject<FocusableElement>
7071
...linkProps,
7172
'aria-disabled': isDisabled || undefined,
7273
'aria-current': props['aria-current'],
73-
onClick: (e) => {
74+
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
7475
pressProps.onClick?.(e);
7576
if (deprecatedOnClick) {
7677
deprecatedOnClick(e);
7778
console.warn('onClick is deprecated, please use onPress');
7879
}
80+
81+
// If a custom router is provided, prevent default and forward if this link should client navigate.
82+
if (
83+
!router.isNative &&
84+
e.currentTarget instanceof HTMLAnchorElement &&
85+
e.currentTarget.href &&
86+
// If props are applied to a router Link component, it may have already prevented default.
87+
!e.isDefaultPrevented() &&
88+
shouldClientNavigate(e.currentTarget, e)
89+
) {
90+
e.preventDefault();
91+
router.open(e.currentTarget, e);
92+
}
7993
}
8094
})
8195
};

0 commit comments

Comments
 (0)