useDisclosureGroupState
Manages state for a group of disclosures, e.g. an accordion. It supports both single and multiple expanded items.
install | yarn add @react-stately/disclosure |
---|---|
version | 3.0.0-alpha.0 |
usage | import {useDisclosureGroupState} from '@react-stately/disclosure' |
API#
useDisclosureGroupState(
(props: DisclosureGroupProps
)): DisclosureGroupState
Interface#
Properties
Name | Type | Description |
allowsMultipleExpanded | boolean | Whether multiple items can be expanded at the same time. |
isDisabled | boolean | Whether all items are disabled. |
expandedKeys | Set<Key> | A set of keys for items that are expanded. |
Methods
Method | Description |
toggleKey(
(key: Key
)): void | Toggles the expanded state for an item by its key. |
setExpandedKeys(
(keys: Set<Key>
)): void | Replaces the set of expanded keys. |
Example#
The following example shows how to create a DisclosureGroup
component with the useDisclosureGroupState
hook. We'll also create a Disclosure
component that uses the DisclosureGroupState
context for managing its state.
import {useButton} from '@react-aria/button';
import {useDisclosure} from '@react-aria/disclosure';
import {useDisclosureGroupState, useDisclosureState} from '@react-stately/disclosure';
import {useId} from '@react-aria/utils';
const DisclosureGroupStateContext = React.createContext(null);
function DisclosureGroup(props) {
let state = useDisclosureGroupState(props);
return (
<div className="group">
<DisclosureGroupStateContext.Provider value={state}>
{props.children}
</DisclosureGroupStateContext.Provider>
</div>
);
}
function Disclosure(props) {
let defaultId = useId();
let id = props.id || defaultId;
let groupState = React.useContext(DisclosureGroupStateContext);
let isExpanded = groupState
? groupState.expandedKeys.has(id)
: props.isExpanded;
let state = useDisclosureState({
...props,
isExpanded,
onExpandedChange(isExpanded) {
if (groupState) {
groupState.toggleKey(id);
}
props.onExpandedChange?.(isExpanded);
}
});
let panelRef = React.useRef<HTMLDivElement | null>(null);
let triggerRef = React.useRef<HTMLButtonElement | null>(null);
let isDisabled = props.isDisabled || groupState?.isDisabled || false;
let { buttonProps: triggerProps, panelProps } = useDisclosure(
{
...props,
isExpanded,
isDisabled
},
state,
panelRef
);
let { buttonProps } = useButton(triggerProps, triggerRef);
return (
<div className="disclosure">
<h3>
<button className="trigger" ref={triggerRef} {...buttonProps}>
<svg viewBox="0 0 24 24">
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
{props.title}
</button>
</h3>
<div className="panel" ref={panelRef} {...panelProps}>
<p>
{props.children}
</p>
</div>
</div>
);
}
import {useButton} from '@react-aria/button';
import {useDisclosure} from '@react-aria/disclosure';
import {
useDisclosureGroupState,
useDisclosureState
} from '@react-stately/disclosure';
import {useId} from '@react-aria/utils';
const DisclosureGroupStateContext = React.createContext(
null
);
function DisclosureGroup(props) {
let state = useDisclosureGroupState(props);
return (
<div className="group">
<DisclosureGroupStateContext.Provider value={state}>
{props.children}
</DisclosureGroupStateContext.Provider>
</div>
);
}
function Disclosure(props) {
let defaultId = useId();
let id = props.id || defaultId;
let groupState = React.useContext(
DisclosureGroupStateContext
);
let isExpanded = groupState
? groupState.expandedKeys.has(id)
: props.isExpanded;
let state = useDisclosureState({
...props,
isExpanded,
onExpandedChange(isExpanded) {
if (groupState) {
groupState.toggleKey(id);
}
props.onExpandedChange?.(isExpanded);
}
});
let panelRef = React.useRef<HTMLDivElement | null>(null);
let triggerRef = React.useRef<HTMLButtonElement | null>(
null
);
let isDisabled = props.isDisabled ||
groupState?.isDisabled || false;
let { buttonProps: triggerProps, panelProps } =
useDisclosure(
{
...props,
isExpanded,
isDisabled
},
state,
panelRef
);
let { buttonProps } = useButton(triggerProps, triggerRef);
return (
<div className="disclosure">
<h3>
<button
className="trigger"
ref={triggerRef}
{...buttonProps}
>
<svg viewBox="0 0 24 24">
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
{props.title}
</button>
</h3>
<div className="panel" ref={panelRef} {...panelProps}>
<p>
{props.children}
</p>
</div>
</div>
);
}
import {useButton} from '@react-aria/button';
import {useDisclosure} from '@react-aria/disclosure';
import {
useDisclosureGroupState,
useDisclosureState
} from '@react-stately/disclosure';
import {useId} from '@react-aria/utils';
const DisclosureGroupStateContext =
React.createContext(
null
);
function DisclosureGroup(
props
) {
let state =
useDisclosureGroupState(
props
);
return (
<div className="group">
<DisclosureGroupStateContext.Provider
value={state}
>
{props.children}
</DisclosureGroupStateContext.Provider>
</div>
);
}
function Disclosure(
props
) {
let defaultId =
useId();
let id = props.id ||
defaultId;
let groupState = React
.useContext(
DisclosureGroupStateContext
);
let isExpanded =
groupState
? groupState
.expandedKeys
.has(id)
: props.isExpanded;
let state =
useDisclosureState({
...props,
isExpanded,
onExpandedChange(
isExpanded
) {
if (groupState) {
groupState
.toggleKey(
id
);
}
props
.onExpandedChange?.(
isExpanded
);
}
});
let panelRef = React
.useRef<
| HTMLDivElement
| null
>(null);
let triggerRef = React
.useRef<
| HTMLButtonElement
| null
>(null);
let isDisabled =
props.isDisabled ||
groupState
?.isDisabled ||
false;
let {
buttonProps:
triggerProps,
panelProps
} = useDisclosure(
{
...props,
isExpanded,
isDisabled
},
state,
panelRef
);
let { buttonProps } =
useButton(
triggerProps,
triggerRef
);
return (
<div className="disclosure">
<h3>
<button
className="trigger"
ref={triggerRef}
{...buttonProps}
>
<svg viewBox="0 0 24 24">
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
{props.title}
</button>
</h3>
<div
className="panel"
ref={panelRef}
{...panelProps}
>
<p>
{props
.children}
</p>
</div>
</div>
);
}
Show CSS
.disclosure {
.trigger {
background: none;
border: none;
box-shadow: none;
font-weight: bold;
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
svg {
rotate: 0deg;
transition: rotate 200ms;
width: 12px;
height: 12px;
fill: none;
stroke: currentColor;
stroke-width: 3px;
}
&[aria-expanded="true"] svg {
rotate: 90deg;
}
}
}
.panel {
margin-left: 32px;
}
.disclosure {
.trigger {
background: none;
border: none;
box-shadow: none;
font-weight: bold;
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
svg {
rotate: 0deg;
transition: rotate 200ms;
width: 12px;
height: 12px;
fill: none;
stroke: currentColor;
stroke-width: 3px;
}
&[aria-expanded="true"] svg {
rotate: 90deg;
}
}
}
.panel {
margin-left: 32px;
}
.disclosure {
.trigger {
background: none;
border: none;
box-shadow: none;
font-weight: bold;
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
svg {
rotate: 0deg;
transition: rotate 200ms;
width: 12px;
height: 12px;
fill: none;
stroke: currentColor;
stroke-width: 3px;
}
&[aria-expanded="true"] svg {
rotate: 90deg;
}
}
}
.panel {
margin-left: 32px;
}
Usage#
The following examples show how to use the DisclosureGroup
component created in the above example.
<DisclosureGroup>
<Disclosure title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup>
<Disclosure title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup>
<Disclosure title="Personal Information">
Personal
information form
here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address
form here.
</Disclosure>
</DisclosureGroup>
Default expansion#
Which disclosure is expanded by default can be set with the defaultExpandedKeys
prop.
<DisclosureGroup defaultExpandedKeys={['billing']}>
<Disclosure id="personal" title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure id="billing" title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup defaultExpandedKeys={['billing']}>
<Disclosure id="personal" title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure id="billing" title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup
defaultExpandedKeys={[
'billing'
]}
>
<Disclosure
id="personal"
title="Personal Information"
>
Personal
information form
here.
</Disclosure>
<Disclosure
id="billing"
title="Billing Address"
>
Billing address
form here.
</Disclosure>
</DisclosureGroup>
Controlled expansion#
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.
function ControlledDisclosureGroup(props) {
let [expandedKeys, setExpandedKeys] = React.useState(['personal']);
return (
<DisclosureGroup
expandedKeys={expandedKeys}
onExpandedChange={setExpandedKeys}
>
<Disclosure id="personal" title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure id="billing" title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
);
}
function ControlledDisclosureGroup(props) {
let [expandedKeys, setExpandedKeys] = React.useState([
'personal'
]);
return (
<DisclosureGroup
expandedKeys={expandedKeys}
onExpandedChange={setExpandedKeys}
>
<Disclosure
id="personal"
title="Personal Information"
>
Personal information form here.
</Disclosure>
<Disclosure id="billing" title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
);
}
function ControlledDisclosureGroup(
props
) {
let [
expandedKeys,
setExpandedKeys
] = React.useState([
'personal'
]);
return (
<DisclosureGroup
expandedKeys={expandedKeys}
onExpandedChange={setExpandedKeys}
>
<Disclosure
id="personal"
title="Personal Information"
>
Personal
information form
here.
</Disclosure>
<Disclosure
id="billing"
title="Billing Address"
>
Billing address
form here.
</Disclosure>
</DisclosureGroup>
);
}
Multiple expanded#
Multiple disclosures can be expanded at the same time by setting the allowsMultipleExpanded
prop to true
.
<DisclosureGroup allowsMultipleExpanded>
<Disclosure title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup allowsMultipleExpanded>
<Disclosure title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup
allowsMultipleExpanded
>
<Disclosure title="Personal Information">
Personal
information form
here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address
form here.
</Disclosure>
</DisclosureGroup>
Disabled#
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.
<DisclosureGroup isDisabled>
<Disclosure title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup isDisabled>
<Disclosure title="Personal Information">
Personal information form here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address form here.
</Disclosure>
</DisclosureGroup>
<DisclosureGroup
isDisabled
>
<Disclosure title="Personal Information">
Personal
information form
here.
</Disclosure>
<Disclosure title="Billing Address">
Billing address
form here.
</Disclosure>
</DisclosureGroup>