Skip to content

Commit 65e644e

Browse files
docs: add s2 non-component hook docs (#9106)
* docs: add non-component hook docs * add useClipboard * add more hook docs * clean up interaction hooks * clean up dnd hooks * cleanup focus hooks * cleanup internationalized hooks * add page description * fix types * fix lint * actually fix lint * remove console log * update css import * fix styles --------- Co-authored-by: Robert Snow <rsnow@adobe.com>
1 parent 4d19430 commit 65e644e

38 files changed

+3505
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"use client";
2+
import React from 'react';
3+
import {useDrag} from '@react-aria/dnd';
4+
5+
export function Draggable() {
6+
let {dragProps, isDragging} = useDrag({
7+
getItems() {
8+
return [{
9+
'text/plain': 'hello world',
10+
'my-app-custom-type': JSON.stringify({message: 'hello world'})
11+
}];
12+
}
13+
});
14+
15+
return (
16+
<div {...dragProps} role="button" tabIndex={0} className={`draggable ${isDragging ? 'dragging' : ''}`}>
17+
Drag me
18+
</div>
19+
);
20+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import React, {JSX} from 'react';
4+
import type {TextDropItem} from '@react-aria/dnd';
5+
import {useDrop} from '@react-aria/dnd';
6+
7+
interface DroppedItem {
8+
message: string;
9+
style?: 'bold' | 'italic';
10+
}
11+
12+
export function DropTarget() {
13+
let [dropped, setDropped] = React.useState<DroppedItem[] | null>(null);
14+
let ref = React.useRef(null);
15+
let {dropProps, isDropTarget} = useDrop({
16+
ref,
17+
async onDrop(e) {
18+
let items = await Promise.all(
19+
(e.items as TextDropItem[])
20+
.filter(item => item.kind === 'text' && (item.types.has('text/plain') || item.types.has('my-app-custom-type')))
21+
.map(async item => {
22+
if (item.types.has('my-app-custom-type')) {
23+
return JSON.parse(await item.getText('my-app-custom-type'));
24+
} else {
25+
return {message: await item.getText('text/plain')};
26+
}
27+
})
28+
);
29+
setDropped(items);
30+
}
31+
});
32+
33+
let message: JSX.Element[] = [<div>{`Drop here`}</div>];
34+
if (dropped) {
35+
message = dropped.map((d, index) => {
36+
let m = d.message;
37+
if (d.style === 'bold') {
38+
message = [<strong>{m}</strong>];
39+
} else if (d.style === 'italic') {
40+
message = [<em>{m}</em>];
41+
}
42+
return <div key={index}>{message}</div>;
43+
});
44+
}
45+
46+
return (
47+
<div {...dropProps} role="button" tabIndex={0} ref={ref} className={`droppable ${isDropTarget ? 'target' : ''}`}>
48+
{message}
49+
</div>
50+
);
51+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{/* Copyright 2025 Adobe. All rights reserved.
2+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License. You may obtain a copy
4+
of the License at http://www.apache.org/licenses/LICENSE-2.0
5+
Unless required by applicable law or agreed to in writing, software distributed under
6+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
7+
OF ANY KIND, either express or implied. See the License for the specific language
8+
governing permissions and limitations under the License. */}
9+
10+
import {Layout} from '../../src/Layout';
11+
export default Layout;
12+
import {GroupedPropTable} from '../../src/PropTable';
13+
import {FunctionAPI} from '../../src/FunctionAPI';
14+
import docs from 'docs:@react-aria/focus';
15+
16+
export const section = 'Focus';
17+
18+
# FocusRing
19+
20+
<PageDescription>{docs.exports.FocusRing.description}</PageDescription>
21+
22+
## Introduction
23+
24+
`FocusRing` is a utility component that can be used to apply a CSS class when an element has keyboard focus.
25+
This helps keyboard users determine which element on a page or in an application has keyboard focus as they
26+
navigate around. Focus rings are only visible when interacting with a keyboard so as not to distract mouse
27+
and touch screen users. When we are unable to detect if the user is using a mouse or touch screen, such as
28+
switching in from a different tab, we show the focus ring.
29+
30+
If CSS classes are not being used for styling, see [useFocusRing](useFocusRing.html) for a hooks version.
31+
32+
## Props
33+
34+
<PropTable links={docs.links} component={docs.exports.FocusRing} />
35+
36+
## Example
37+
38+
This example shows how to use `<FocusRing>` to apply a CSS class when keyboard focus is on a button.
39+
40+
```tsx render files={["packages/dev/s2-docs/pages/react-aria/FocusRingExample.css"]}
41+
'use client';
42+
import {FocusRing} from '@react-aria/focus';
43+
import './FocusRingExample.css';
44+
45+
<FocusRing focusRingClass="focus-ring">
46+
<button className="button">Test</button>
47+
</FocusRing>
48+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.button {
2+
-webkit-appearance: none;
3+
appearance: none;
4+
background: green;
5+
border: none;
6+
color: white;
7+
font-size: 14px;
8+
padding: 4px 8px;
9+
}
10+
11+
.button.focus-ring {
12+
outline: 2px solid dodgerblue;
13+
outline-offset: 2px;
14+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
{/* Copyright 2025 Adobe. All rights reserved.
2+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License. You may obtain a copy
4+
of the License at http://www.apache.org/licenses/LICENSE-2.0
5+
Unless required by applicable law or agreed to in writing, software distributed under
6+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
7+
OF ANY KIND, either express or implied. See the License for the specific language
8+
governing permissions and limitations under the License. */}
9+
10+
import {Layout} from '../../src/Layout';
11+
export default Layout;
12+
import {GroupedPropTable} from '../../src/PropTable';
13+
import {FunctionAPI} from '../../src/FunctionAPI';
14+
import docs from 'docs:@react-aria/focus';
15+
16+
export const section = 'Focus';
17+
18+
# FocusScope
19+
20+
<PageDescription>{docs.exports.FocusScope.description}</PageDescription>
21+
22+
## Introduction
23+
24+
`FocusScope` is a utility component that can be used to manage focus for its descendants.
25+
When the `contain` prop is set, focus is contained within the scope. This is useful when
26+
implementing overlays like modal dialogs, which should not allow focus to escape them while open.
27+
In addition, the `restoreFocus` prop can be used to restore focus back to the previously focused
28+
element when the focus scope unmounts, for example, back to a button which opened a dialog.
29+
A FocusScope can also optionally auto focus the first focusable element within it on mount
30+
when the `autoFocus` prop is set.
31+
32+
The <TypeLink links={docs.links} type={docs.exports.useFocusManager} /> hook can also be used
33+
in combination with a FocusScope to programmatically move focus within the scope. For example,
34+
arrow key navigation could be implemented by handling keyboard events and using a focus manager
35+
to move focus to the next and previous elements.
36+
37+
## Props
38+
39+
<PropTable links={docs.links} component={docs.exports.FocusScope} />
40+
41+
## FocusManager Interface
42+
43+
To get a focus manager, call the <TypeLink links={docs.links} type={docs.exports.useFocusManager} /> hook
44+
from a component within the FocusScope. A focus manager supports the following methods:
45+
46+
<ClassAPI links={docs.links} class={docs.exports.FocusManager} />
47+
48+
## Example
49+
50+
A basic example of a focus scope that contains focus within it is below. Clicking the "Open"
51+
button mounts a FocusScope, which auto focuses the first input inside it. Once open, you can
52+
press the <Keyboard>Tab</Keyboard> key to move within the scope, but focus is contained inside. Clicking the "Close"
53+
button unmounts the focus scope, which restores focus back to the button.
54+
55+
{/* Not implemented yet */}
56+
{/* For a full example of building a modal dialog, see [useDialog](useDialog.html). */}
57+
58+
```tsx render
59+
'use client';
60+
import React from 'react';
61+
import {FocusScope} from '@react-aria/focus';
62+
63+
function Example() {
64+
let [isOpen, setOpen] = React.useState(false);
65+
return (
66+
<>
67+
<button onClick={() => setOpen(true)}>Open</button>
68+
{isOpen &&
69+
<FocusScope contain restoreFocus autoFocus>
70+
<label htmlFor="first-input">First Input</label>
71+
<input id="first-input" />
72+
<label htmlFor="second-input">Second Input</label>
73+
<input id="second-input" />
74+
<button onClick={() => setOpen(false)}>Close</button>
75+
</FocusScope>
76+
}
77+
</>
78+
);
79+
}
80+
```
81+
82+
## useFocusManager Example
83+
84+
This example shows how to use `useFocusManager` to programmatically move focus within a
85+
`FocusScope`. It implements a basic toolbar component, which allows using the left and
86+
right arrow keys to move focus to the previous and next buttons. The `wrap` option is
87+
used to make focus wrap around when it reaches the first or last button.
88+
89+
```tsx render
90+
'use client';
91+
import {FocusScope} from '@react-aria/focus';
92+
import {useFocusManager} from '@react-aria/focus';
93+
94+
function Toolbar(props) {
95+
return (
96+
<div role="toolbar">
97+
<FocusScope>
98+
{props.children}
99+
</FocusScope>
100+
</div>
101+
);
102+
}
103+
104+
function ToolbarButton(props) {
105+
let focusManager = useFocusManager();
106+
let onKeyDown = (e) => {
107+
switch (e.key) {
108+
case 'ArrowRight':
109+
focusManager.focusNext({wrap: true});
110+
break;
111+
case 'ArrowLeft':
112+
focusManager.focusPrevious({wrap: true});
113+
break;
114+
}
115+
};
116+
117+
return (
118+
<button
119+
onKeyDown={onKeyDown}>
120+
{props.children}
121+
</button>
122+
);
123+
}
124+
125+
<Toolbar>
126+
<ToolbarButton>Cut</ToolbarButton>
127+
<ToolbarButton>Copy</ToolbarButton>
128+
<ToolbarButton>Paste</ToolbarButton>
129+
</Toolbar>
130+
```
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{/* Copyright 2025 Adobe. All rights reserved.
2+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License. You may obtain a copy
4+
of the License at http://www.apache.org/licenses/LICENSE-2.0
5+
Unless required by applicable law or agreed to in writing, software distributed under
6+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
7+
OF ANY KIND, either express or implied. See the License for the specific language
8+
governing permissions and limitations under the License. */}
9+
10+
import {Layout} from '../../src/Layout';
11+
export default Layout;
12+
import {FunctionAPI} from '../../src/FunctionAPI';
13+
import docs from 'docs:@react-aria/i18n';
14+
15+
export const section = 'Internationalization';
16+
export const description = 'Implementing collections in React Aria';
17+
18+
19+
# I18nProvider
20+
21+
## Introduction
22+
23+
`I18nProvider` allows you to override the default locale as determined by the browser/system setting
24+
with a locale defined by your application (e.g. application setting). This should be done by wrapping
25+
your entire application in the provider, which will be cause all child elements to receive the new locale
26+
information via [useLocale](useLocale.html).
27+
28+
## Props
29+
30+
<PropTable component={docs.exports.I18nProvider} links={docs.links} />
31+
32+
## Example
33+
34+
```tsx
35+
import {I18nProvider} from '@react-aria/i18n';
36+
37+
<I18nProvider locale="fr-FR">
38+
<YourApp />
39+
</I18nProvider>
40+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use client';
2+
import React from 'react';
3+
import {UNSTABLE_ToastRegion as ToastRegion, UNSTABLE_Toast as Toast, UNSTABLE_ToastQueue as ToastQueue, UNSTABLE_ToastContent as ToastContent, Button, Text} from 'react-aria-components';
4+
5+
// Define the type for your toast content.
6+
interface MyToastContent {
7+
title: string,
8+
description?: string
9+
}
10+
11+
export function MyToastRegion({queue}: {queue: ToastQueue<MyToastContent>}) {
12+
return (
13+
<ToastRegion queue={queue}>
14+
{({toast}) => (
15+
<Toast toast={toast}>
16+
<ToastContent>
17+
<Text slot="title">{toast.content.title}</Text>
18+
<Text slot="description">{toast.content.description}</Text>
19+
</ToastContent>
20+
<Button slot="close">x</Button>
21+
</Toast>
22+
)}
23+
</ToastRegion>
24+
);
25+
}

0 commit comments

Comments
 (0)