Skip to content

Commit 72a85cc

Browse files
authored
docs: Disclosure hook docs (#7178)
* docs: disclosure hooks * docs types * move DisclosureGroup to useDisclosureGroupState docs page * add anatomy diagram from #7165 to useDisclosure docs * expand on hidden="until-found" * fix interface tables * Revert "move DisclosureGroup to useDisclosureGroupState docs page" This reverts commit 8216354.
1 parent b958085 commit 72a85cc

File tree

5 files changed

+442
-1
lines changed

5 files changed

+442
-1
lines changed
Lines changed: 25 additions & 0 deletions
Loading
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
{/* Copyright 2024 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 '@react-spectrum/docs';
11+
export default Layout;
12+
13+
import docs from 'docs:@react-aria/disclosure';
14+
import utilsDocs from 'docs:@react-aria/utils';
15+
import statelyDocs from 'docs:@react-stately/disclosure';
16+
import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink, PageDescription} from '@react-spectrum/docs';
17+
import {Keyboard} from '@react-spectrum/text';
18+
import packageData from '@react-aria/disclosure/package.json';
19+
import ChevronRight from '@spectrum-icons/workflow/ChevronRight';
20+
import Anatomy from './anatomy.svg';
21+
22+
---
23+
category: Navigation
24+
keywords: [disclosure, collapse, expand, aria]
25+
preRelease: alpha
26+
---
27+
28+
# useDisclosure
29+
30+
<PageDescription>{docs.exports.useDisclosure.description}</PageDescription>
31+
32+
<HeaderInfo
33+
packageData={packageData}
34+
componentNames={['useDisclosure']}
35+
sourceData={[
36+
{type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/'}
37+
]} />
38+
39+
## API
40+
41+
<FunctionAPI function={docs.exports.useDisclosure} links={docs.links} />
42+
43+
## Features
44+
45+
A disclosure is a collapsible section of content. It is composed of a trigger button and a panel that contains the content. `useDisclosure` can be used to implement these in an accessible way.
46+
47+
* Support for mouse, touch, and keyboard interactions to open and close the disclosure
48+
* Support for disabled disclosures
49+
* Follows the disclosure ARIA pattern, semantically linking the trigger button and panel
50+
* Uses [hidden="until-found"](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden#the_hidden_until_found_state) in supported browsers, enabling find-in-page search support and improved search engine visibility for collapsed content
51+
52+
## Anatomy
53+
54+
<Anatomy />
55+
56+
A disclosure consists of a trigger button and a panel. Clicking on or pressing <Keyboard>Enter</Keyboard> or <Keyboard>Space</Keyboard> while the trigger button is focused toggles the visibility of the panel.
57+
58+
`useDisclosure` returns props to spread onto the trigger button and panel.
59+
60+
<TypeContext.Provider value={docs.links}>
61+
<InterfaceType properties={docs.links[docs.exports.useDisclosure.return.id].properties} />
62+
</TypeContext.Provider>
63+
64+
65+
State is managed by the <TypeLink links={statelyDocs.links} type={statelyDocs.exports.useDisclosureState} />
66+
hook in `@react-stately/disclosure`. The state object should be passed as an option to `useDisclosure`.
67+
68+
## Example
69+
70+
This example displays a basic disclosure with a button that toggles the visibility of the panel.
71+
72+
```tsx example export=true
73+
import {useDisclosureState} from '@react-stately/disclosure';
74+
import {useDisclosure} from '@react-aria/disclosure';
75+
import {useButton} from '@react-aria/button';
76+
77+
function Disclosure(props) {
78+
let state = useDisclosureState(props);
79+
let panelRef = React.useRef<HTMLDivElement | null>(null);
80+
let triggerRef = React.useRef<HTMLButtonElement | null>(null);
81+
let {buttonProps: triggerProps, panelProps} = useDisclosure(props, state, panelRef);
82+
let {buttonProps} = useButton(triggerProps, triggerRef);
83+
84+
return (
85+
<div className="disclosure">
86+
<h3>
87+
<button className="trigger" ref={triggerRef} {...buttonProps}>
88+
<svg viewBox="0 0 24 24">
89+
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
90+
</svg>
91+
{props.title}
92+
</button>
93+
</h3>
94+
<div className="panel" ref={panelRef} {...panelProps}>
95+
<p>
96+
{props.children}
97+
</p>
98+
</div>
99+
</div>
100+
);
101+
};
102+
103+
<Disclosure title="System Requirements">
104+
Details about system requirements here.
105+
</Disclosure>
106+
```
107+
108+
<details>
109+
<summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
110+
111+
```css
112+
.disclosure {
113+
.trigger {
114+
background: none;
115+
border: none;
116+
box-shadow: none;
117+
font-weight: bold;
118+
font-size: 16px;
119+
display: flex;
120+
align-items: center;
121+
gap: 8px;
122+
123+
svg {
124+
rotate: 0deg;
125+
transition: rotate 200ms;
126+
width: 12px;
127+
height: 12px;
128+
fill: none;
129+
stroke: currentColor;
130+
stroke-width: 3px;
131+
}
132+
133+
&[aria-expanded="true"] svg {
134+
rotate: 90deg;
135+
}
136+
}
137+
}
138+
139+
.panel {
140+
margin-left: 32px;
141+
}
142+
```
143+
144+
</details>
145+
146+
## Usage
147+
148+
The following examples show how to use the `Disclosure` component created in the above example.
149+
150+
### Default expansion
151+
152+
Whether or not the disclosure is expanded or not by default can be set with the `defaultExpanded` prop.
153+
154+
```tsx example
155+
<Disclosure title="System Requirements" defaultExpanded>
156+
Details about system requirements here.
157+
</Disclosure>
158+
```
159+
160+
### Controlled expansion
161+
162+
Expansion can be controlled using the `isExpanded` prop, paired with the `onExpandedChange` event. The `onExpandedChange` event is fired when the user presses the trigger button.
163+
164+
```tsx example
165+
function ControlledDisclosure(props) {
166+
let [isExpanded, setExpanded] = React.useState(false);
167+
168+
return (
169+
<Disclosure title="System Requirements" isExpanded={isExpanded} onExpandedChange={setExpanded}>
170+
Details about system requirements here.
171+
</Disclosure>
172+
);
173+
}
174+
```
175+
176+
### Disabled
177+
178+
A disclosure can be disabled with the `isDisabled` prop. This will disable the trigger button and prevent the panel from being opened or closed.
179+
180+
```tsx example
181+
<Disclosure title="System Requirements" isDisabled>
182+
Details about system requirements here.
183+
</Disclosure>
184+
```
185+
186+
## Disclosure Group
187+
188+
A disclosure group (i.e. accordion) is a set of disclosures where only one disclosure can be expanded at a time. The following example shows how to create a `DisclosureGroup` component with the `useDisclosureGroupState` hook. We'll also create a `DisclosureItem` component that uses the `DisclosureGroupState` context for managing its state.
189+
190+
```tsx example export=true
191+
import {useDisclosureGroupState} from '@react-stately/disclosure';
192+
import {useId} from '@react-aria/utils';
193+
194+
const DisclosureGroupStateContext = React.createContext(null);
195+
196+
function DisclosureGroup(props) {
197+
let state = useDisclosureGroupState(props);
198+
199+
return (
200+
<div className="group">
201+
<DisclosureGroupStateContext.Provider value={state}>
202+
{props.children}
203+
</DisclosureGroupStateContext.Provider>
204+
</div>
205+
);
206+
}
207+
208+
function DisclosureItem(props) {
209+
let defaultId = useId();
210+
let id = props.id || defaultId;
211+
let groupState = React.useContext(DisclosureGroupStateContext);
212+
let isExpanded = groupState ? groupState.expandedKeys.has(id) : props.isExpanded;
213+
let state = useDisclosureState({
214+
...props,
215+
isExpanded,
216+
onExpandedChange(isExpanded) {
217+
if (groupState) {
218+
groupState.toggleKey(id);
219+
}
220+
221+
props.onExpandedChange?.(isExpanded);
222+
}
223+
});
224+
225+
let panelRef = React.useRef<HTMLDivElement | null>(null);
226+
let triggerRef = React.useRef<HTMLButtonElement | null>(null);
227+
let isDisabled = props.isDisabled || groupState?.isDisabled || false;
228+
let {buttonProps: triggerProps, panelProps} = useDisclosure({
229+
...props,
230+
isExpanded,
231+
isDisabled
232+
}, state, panelRef);
233+
let {buttonProps} = useButton(triggerProps, triggerRef);
234+
235+
return (
236+
<div className="disclosure">
237+
<h3>
238+
<button className="trigger" ref={triggerRef} {...buttonProps}>
239+
<svg viewBox="0 0 24 24">
240+
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
241+
</svg>
242+
{props.title}
243+
</button>
244+
</h3>
245+
<div className="panel" ref={panelRef} {...panelProps}>
246+
<p>
247+
{props.children}
248+
</p>
249+
</div>
250+
</div>
251+
);
252+
};
253+
```
254+
255+
### Usage
256+
257+
The following examples show how to use the `DisclosureGroup` component created in the above example.
258+
259+
```tsx example
260+
<DisclosureGroup>
261+
<DisclosureItem title="Personal Information">
262+
Personal information form here.
263+
</DisclosureItem>
264+
<DisclosureItem title="Billing Address">
265+
Billing address form here.
266+
</DisclosureItem>
267+
</DisclosureGroup>
268+
```
269+
270+
#### Default expansion
271+
272+
Which disclosure is expanded by default can be set with the `defaultExpandedKeys` prop.
273+
274+
```tsx example
275+
<DisclosureGroup defaultExpandedKeys={['billing']}>
276+
<DisclosureItem id="personal" title="Personal Information">
277+
Personal information form here.
278+
</DisclosureItem>
279+
<DisclosureItem id="billing" title="Billing Address">
280+
Billing address form here.
281+
</DisclosureItem>
282+
</DisclosureGroup>
283+
```
284+
285+
#### Controlled expansion
286+
287+
Expansion can be controlled using the `expandedKeys` prop, paired with the `onExpandedChange` event. The `onExpandedChange` event is fired when one of the disclosures is expanded or collapsed.
288+
289+
```tsx example
290+
function ControlledDisclosureGroup(props) {
291+
let [expandedKeys, setExpandedKeys] = React.useState(['personal']);
292+
293+
return (
294+
<DisclosureGroup expandedKeys={expandedKeys} onExpandedChange={setExpandedKeys}>
295+
<DisclosureItem id="personal" title="Personal Information">
296+
Personal information form here.
297+
</DisclosureItem>
298+
<DisclosureItem id="billing" title="Billing Address">
299+
Billing address form here.
300+
</DisclosureItem>
301+
</DisclosureGroup>
302+
);
303+
}
304+
```
305+
306+
#### Multiple expanded
307+
308+
Multiple disclosures can be expanded at the same time by setting the `allowsMultipleExpanded` prop to `true`.
309+
310+
```tsx example
311+
<DisclosureGroup allowsMultipleExpanded>
312+
<DisclosureItem title="Personal Information">
313+
Personal information form here.
314+
</DisclosureItem>
315+
<DisclosureItem title="Billing Address">
316+
Billing address form here.
317+
</DisclosureItem>
318+
</DisclosureGroup>
319+
```
320+
321+
#### Disabled
322+
323+
An entire disclosure group can be disabled with the `isDisabled` prop. This will disable all trigger buttons and prevent the panels from being opened or closed.
324+
325+
```tsx example
326+
<DisclosureGroup isDisabled>
327+
<DisclosureItem title="Personal Information">
328+
Personal information form here.
329+
</DisclosureItem>
330+
<DisclosureItem title="Billing Address">
331+
Billing address form here.
332+
</DisclosureItem>
333+
</DisclosureGroup>
334+
```

0 commit comments

Comments
 (0)