Skip to content

Update defaults of allowMultipleExpanded and allowZeroExpanded to true #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,23 @@
> All notable changes to this project are documented in this file. This project
> adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [[v6.0.0]](https://github.com/springload/react-accessible-accordion/releases/tag/v6.0.0)

**A sort-of breaking change** which updates the default behavior regarding
`allowMultipleExpanded` and `allowZeroExpanded`. They both now default to
`true`.

This is based on
[advice from NNG](https://www.nngroup.com/articles/accordions-on-desktop/), as
well as Springload's views on accordion usability.

From this version onwards:

- to disable allowing more than one accordion panel to be open at once, you
must explicitly set `allowMultipleExpanded={false}`
- to disable collapsing all accordion panels, you must explicitly set
`allowZeroExpanded={false}`

## [[v5.0.0]](https://github.com/springload/react-accessible-accordion/releases/tag/v5.0.0)

React Accessible Accordion now supports React 18 with its out-of-order streaming
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -85,11 +85,11 @@ needs, particularly if you're using your own `className`s.

### Accordion

#### allowMultipleExpanded : `boolean` [*optional*, default: `false`]
#### allowMultipleExpanded : `boolean` [*optional*, default: `true`]

Don't autocollapse items when expanding other items.
Don't auto-collapse items when expanding other items.

#### allowZeroExpanded : `boolean` [*optional*, default: `false`]
#### allowZeroExpanded : `boolean` [*optional*, default: `true`]

Allow the only remaining expanded item to be collapsed.

17 changes: 1 addition & 16 deletions demo/src/code-examples.tsx
Original file line number Diff line number Diff line change
@@ -21,21 +21,6 @@ export const ExampleDefault = `import {
))}
</Accordion>`;

export const ExampleAllowMultipleExpanded = `<Accordion allowMultipleExpanded>
{items.map((item) => (
<AccordionItem key={item.uuid}>
<AccordionItemHeading>
<AccordionItemButton>
{item.heading}
</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>
{item.content}
</AccordionItemPanel>
</AccordionItem>
))}
</Accordion>`;

export const ExampleAllowMultipleExpandedFalse = `<Accordion allowMultipleExpanded={false}>
{items.map((item) => (
<AccordionItem key={item.uuid}>
@@ -51,7 +36,7 @@ export const ExampleAllowMultipleExpandedFalse = `<Accordion allowMultipleExpand
))}
</Accordion>`;

export const ExampleAllowZeroExpanded = `<Accordion allowZeroExpanded>
export const ExampleAllowZeroExpandedFalse = `<Accordion allowZeroExpanded={false} preExpanded={['a']}>
{items.map((item) => (
<AccordionItem key={item.uuid}>
<AccordionItemHeading>
82 changes: 38 additions & 44 deletions demo/src/index.tsx
Original file line number Diff line number Diff line change
@@ -18,9 +18,8 @@ import Code from './components/Code';
// tslint:disable-next-line no-import-side-effect ordered-imports
import {
ExampleDefault,
ExampleAllowMultipleExpanded,
ExampleAllowMultipleExpandedFalse,
ExampleAllowZeroExpanded,
ExampleAllowZeroExpandedFalse,
ExamplePreExpanded,
ExampleOnChange,
ExampleAccordionItemState,
@@ -54,8 +53,7 @@ const App = (): JSX.Element => (
<h2 className="u-margin-top">Default behaviour</h2>

<p>
By default, only one item may be expanded and it can only be
collapsed again by expanding another.
By default, any number of items may be expanded at any given time.
</p>

<Accordion>
@@ -73,32 +71,15 @@ const App = (): JSX.Element => (

<Code code={ExampleDefault} />

<h2 className="u-margin-top">Expanding multiple items at once</h2>

<p>
If you set <strong>allowMultipleExpanded</strong> to{' '}
<strong>true</strong> then the accordion will permit multiple items
to be expanded at once.
</p>

<Accordion allowMultipleExpanded={true}>
{placeholders.map((placeholder: Placeholder) => (
<AccordionItem key={placeholder.heading}>
<AccordionItemHeading>
<AccordionItemButton>
{placeholder.heading}
</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>{placeholder.panel}</AccordionItemPanel>
</AccordionItem>
))}
</Accordion>

<Code code={ExampleAllowMultipleExpanded} />

<h2 className="u-margin-top">
Same as above except with allowMultipleExpanded=false
Prevent multiple items being expanded at a time
</h2>
<p>
<strong>Note:</strong> we do not recommend this behavior. Users may
wish to view the content of more than one panel at once. Also,
collapsing a panel when opening another can cause unexpected scroll
position changes.
</p>

<Accordion allowMultipleExpanded={false}>
{placeholders.map((placeholder: Placeholder) => (
@@ -115,17 +96,25 @@ const App = (): JSX.Element => (

<Code code={ExampleAllowMultipleExpandedFalse} />

<h2 className="u-margin-top">Collapsing the last expanded item</h2>
<h2 className="u-margin-top">Pre-expanded items</h2>

<p>
If you set <strong>allowZeroExpanded</strong> to{' '}
<strong>true</strong> then a solitary expanded item may be collapsed
again.
If you set <strong>preExpanded</strong>, then you can choose which
items are expanded on mount.
</p>

<p>
The strings passed to <strong>preExpanded</strong> are directly
related to the <strong>uuid</strong> props of{' '}
<strong>AccordionItem</strong>.
</p>

<Accordion allowZeroExpanded={true}>
<Accordion preExpanded={[placeholders[0].uuid]}>
{placeholders.map((placeholder: Placeholder) => (
<AccordionItem key={placeholder.heading}>
<AccordionItem
key={placeholder.heading}
uuid={placeholder.uuid}
>
<AccordionItemHeading>
<AccordionItemButton>
{placeholder.heading}
@@ -136,22 +125,27 @@ const App = (): JSX.Element => (
))}
</Accordion>

<Code code={ExampleAllowZeroExpanded} />
<Code code={ExamplePreExpanded} />

<h2 className="u-margin-top">Pre-expanded items</h2>
<h2 className="u-margin-top">Preventing the collapsing of all items</h2>

<p>
If you set <strong>preExpanded</strong>, then you can choose which
items are expanded on mount.
If you set <strong>allowZeroExpanded</strong> to{' '}
<strong>false</strong> then the user must have at least one panel
open at a time.
</p>

<p>
The strings passed to <strong>preExpanded</strong> are directly
related to the <strong>uuid</strong> props of{' '}
<strong>AccordionItem</strong>.
<strong>Note:</strong> we do not recommend this behavior. Users
would be able to expand items but not necessarily collapse them,
which might not match their expectations. If you do choose to use
this setting, we recommend you pair it with having{' '}
<strong>preExpanded</strong> item(s).
</p>

<Accordion preExpanded={[placeholders[0].uuid]}>
<Accordion
allowZeroExpanded={false}
preExpanded={[placeholders[0].uuid]}
>
{placeholders.map((placeholder: Placeholder) => (
<AccordionItem
key={placeholder.heading}
@@ -167,7 +161,7 @@ const App = (): JSX.Element => (
))}
</Accordion>

<Code code={ExamplePreExpanded} />
<Code code={ExampleAllowZeroExpandedFalse} />

<h2 className="u-margin-top">Informative onChange</h2>

17 changes: 0 additions & 17 deletions integration/wai-aria.spec.js
Original file line number Diff line number Diff line change
@@ -365,23 +365,6 @@ describe('WAI ARIA Spec', () => {
}
});

it(`If the accordion panel associated with an accordion header is
visible, and if the accordion does not permit the panel to be
collapsed, the header button element has aria-disabled set to true.`, async () => {
const { browser, page, buttonsHandles } = await setup();
expect(buttonsHandles.length).toEqual(3);

const [firstButtonHandle] = buttonsHandles;
await firstButtonHandle.click();

const buttonAriaDisabled = await page.evaluate(
(button) => button.getAttribute('aria-disabled'),
firstButtonHandle,
);

expect(buttonAriaDisabled).toEqual('true');
});

it(`Optionally, each element that serves as a container for panel
content has role region and aria-labelledby with a value that refers
to the button that controls display of the panel.`, async () => {
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-accessible-accordion",
"version": "5.0.0",
"version": "6.0.0",
"description": "Accessible Accordion component for React",
"main": "dist/umd/index.js",
"module": "dist/es/index.js",
@@ -73,6 +73,10 @@
{
"name": "Emilia Zapata",
"url": "https://github.com/synecdokey"
},
{
"name": "Liam Johnston",
"url": "https://github.com/liamjohnston"
}
],
"license": "MIT",
22 changes: 12 additions & 10 deletions src/components/Accordion.spec.tsx
Original file line number Diff line number Diff line change
@@ -39,9 +39,9 @@ describe('Accordion', () => {

describe('expanding and collapsing: ', () => {
describe('allowMultipleExpanded prop', () => {
it('permits multiple items to be expanded when explicitly true', () => {
it('prevents multiple items to be expanded when set to false', () => {
const { getByTestId } = render(
<Accordion allowMultipleExpanded={true}>
<Accordion allowMultipleExpanded={false}>
<AccordionItem>
<AccordionItemHeading>
<AccordionItemButton data-testid={UUIDS.FOO} />
@@ -60,13 +60,13 @@ describe('Accordion', () => {

expect(
getByTestId(UUIDS.FOO).getAttribute('aria-expanded'),
).toEqual('true');
).toEqual('false');
expect(
getByTestId(UUIDS.BAR).getAttribute('aria-expanded'),
).toEqual('true');
});

it('prevents multiple items being expanded by default', () => {
it('allows multiple items being expanded by default', () => {
const { getByTestId } = render(
<Accordion>
<AccordionItem>
@@ -87,17 +87,17 @@ describe('Accordion', () => {

expect(
getByTestId(UUIDS.FOO).getAttribute('aria-expanded'),
).toEqual('false');
).toEqual('true');
expect(
getByTestId(UUIDS.BAR).getAttribute('aria-expanded'),
).toEqual('true');
});
});

describe('allowZeroExpanded prop', () => {
it('permits the last-expanded item to be collapsed when explicitly true', () => {
it('prevents the last-expanded item to be collapsed when explicitly false', () => {
const { getByTestId } = render(
<Accordion allowZeroExpanded={true}>
<Accordion allowZeroExpanded={false}>
<AccordionItem>
<AccordionItemHeading>
<AccordionItemButton data-testid={UUIDS.FOO} />
@@ -106,15 +106,17 @@ describe('Accordion', () => {
</Accordion>,
);

// open it
fireEvent.click(getByTestId(UUIDS.FOO));
// attempt to close it
fireEvent.click(getByTestId(UUIDS.FOO));

expect(
getByTestId(UUIDS.FOO).getAttribute('aria-expanded'),
).toEqual('false');
).toEqual('true');
});

it('prevents the last-expanded item being collapsed by default', () => {
it('allows the last-expanded to be collapsed by default', () => {
const { getByTestId } = render(
<Accordion>
<AccordionItem>
@@ -130,7 +132,7 @@ describe('Accordion', () => {

expect(
getByTestId(UUIDS.FOO).getAttribute('aria-expanded'),
).toEqual('true');
).toEqual('false');
});
});

4 changes: 2 additions & 2 deletions src/components/AccordionContext.tsx
Original file line number Diff line number Diff line change
@@ -42,8 +42,8 @@ export class Provider extends React.PureComponent<
ProviderState
> {
static defaultProps: ProviderProps = {
allowMultipleExpanded: false,
allowZeroExpanded: false,
allowMultipleExpanded: true,
allowZeroExpanded: true,
};

state: ProviderState = new AccordionStore({
34 changes: 9 additions & 25 deletions src/helpers/AccordionStore.spec.ts
Original file line number Diff line number Diff line change
@@ -53,62 +53,46 @@ describe('Accordion', () => {
expect(container.expanded).toEqual([UUIDS.FOO]);
});

it('collapses the currently expanded items', () => {
it('collapses the currently expanded items, if allowMultipleExpanded is set to false', () => {
const container = new AccordionStore({
expanded: [UUIDS.BAR],
allowMultipleExpanded: false,
}).toggleExpanded(UUIDS.FOO);

expect(container.expanded).toEqual([UUIDS.FOO]);
});
});

describe('collapsing', () => {
it('doesnt collapse the only expanded item', () => {
it('collapses the only expanded item', () => {
const container = new AccordionStore({
expanded: [UUIDS.FOO],
}).toggleExpanded(UUIDS.FOO);

expect(container.expanded).toEqual([UUIDS.FOO]);
expect(container.expanded).toEqual([]);
});

it('collapses the only expanded item when allowZeroExpanded', () => {
it('does not collapse the only expanded item when allowZeroExpanded is false', () => {
const container = new AccordionStore({
allowZeroExpanded: true,
allowZeroExpanded: false,
expanded: [UUIDS.FOO],
}).toggleExpanded(UUIDS.FOO);

expect(container.expanded).toEqual([]);
expect(container.expanded).toEqual([UUIDS.FOO]);
});
});
});

describe('isDisabled', () => {
describe('expanded item', () => {
it('is disabled if alone', () => {
it('is disabled if alone, if allowZeroExpanded is set to false', () => {
const container = new AccordionStore({
allowZeroExpanded: false,
expanded: [UUIDS.FOO],
});

expect(container.isItemDisabled(UUIDS.FOO)).toEqual(true);
});

it('is not disabled if multiple expanded', () => {
const container = new AccordionStore({
allowMultipleExpanded: true,
expanded: [UUIDS.FOO, UUIDS.BAR],
});

expect(container.isItemDisabled(UUIDS.FOO)).toEqual(false);
});

it('is not disabled if allowZeroExpanded', () => {
const container = new AccordionStore({
allowZeroExpanded: true,
expanded: [UUIDS.FOO],
});

expect(container.isItemDisabled(UUIDS.FOO)).toEqual(false);
});
});

describe('collapsed item', () => {
4 changes: 2 additions & 2 deletions src/helpers/AccordionStore.ts
Original file line number Diff line number Diff line change
@@ -28,8 +28,8 @@ export default class AccordionStore {

constructor({
expanded = [],
allowMultipleExpanded = false,
allowZeroExpanded = false,
allowMultipleExpanded = true,
allowZeroExpanded = true,
}: {
expanded?: ID[];
allowMultipleExpanded?: boolean;
45 changes: 18 additions & 27 deletions src/helpers/focus.spec.tsx
Original file line number Diff line number Diff line change
@@ -200,15 +200,12 @@ describe('focus', () => {
</div>
`);

const [
firstButton,
secondButton,
thirdButton,
]: HTMLElement[] = Array.from(
tree.querySelectorAll(
'[data-accordion-component="AccordionItemButton"]',
),
);
const [firstButton, secondButton, thirdButton]: HTMLElement[] =
Array.from(
tree.querySelectorAll(
'[data-accordion-component="AccordionItemButton"]',
),
);

// Predicate
if (
@@ -239,15 +236,12 @@ describe('focus', () => {
</div>
`);

const [
firstButton,
secondButton,
thirdButton,
]: HTMLElement[] = Array.from(
tree.querySelectorAll(
'[data-accordion-component="AccordionItemButton"]',
),
);
const [firstButton, secondButton, thirdButton]: HTMLElement[] =
Array.from(
tree.querySelectorAll(
'[data-accordion-component="AccordionItemButton"]',
),
);

// Predicate
if (
@@ -278,15 +272,12 @@ describe('focus', () => {
</div>
`);

const [
firstButton,
secondButton,
thirdButton,
]: HTMLElement[] = Array.from(
tree.querySelectorAll(
'[data-accordion-component="AccordionItemButton"]',
),
);
const [firstButton, secondButton, thirdButton]: HTMLElement[] =
Array.from(
tree.querySelectorAll(
'[data-accordion-component="AccordionItemButton"]',
),
);

// Predicate
if (