Skip to content

Commit

Permalink
Misc fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Sep 5, 2024
1 parent 2cd57ad commit d0a3838
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 213 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client';
import * as React from 'react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import * as Accordion from '@base_ui/react/Accordion';
Expand Down Expand Up @@ -122,7 +123,7 @@ function Styles() {
margin: 12px auto 12px 0;
}
.Accordion-trigger[data-state="open"] svg {
.Accordion-trigger[data-collapsible="open"] svg {
transform: rotate(180deg);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client';
import * as React from 'react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import * as Accordion from '@base_ui/react/Accordion';
Expand Down Expand Up @@ -122,7 +123,7 @@ function Styles() {
margin: 12px auto 12px 0;
}
.Accordion-trigger[data-state="open"] svg {
.Accordion-trigger[data-collapsible="open"] svg {
transform: rotate(180deg);
}
Expand Down
255 changes: 177 additions & 78 deletions docs/data/base/components/accordion/accordion.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,15 @@ Accordions are implemented using a collection of related components:
<Accordion.Root>
<Accordion.Section>
<Accordion.Heading>
<Accordion.Trigger>
Toggle one
</Accordion.Trigger>
<Accordion.Trigger>Toggle one</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section one content
</Accordion.Panel>
<Accordion.Panel>Section one content</Accordion.Panel>
</Accordion.Section>
<Accordion.Section>
<Accordion.Heading>
<Accordion.Trigger>
Toggle two
</Accordion.Trigger>
<Accordion.Trigger>Toggle two</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section two content
</Accordion.Panel>
<Accordion.Panel>Section two content</Accordion.Panel>
</Accordion.Section>
</Accordion.Root>
```
Expand All @@ -94,23 +86,15 @@ You can optionally specify a custom `value` prop on `Section`:
<Accordion.Root>
<Accordion.Section value="one">
<Accordion.Heading>
<Accordion.Trigger>
Toggle one
</Accordion.Trigger>
<Accordion.Trigger>Toggle one</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section one content
</Accordion.Panel>
<Accordion.Panel>Section one content</Accordion.Panel>
</Accordion.Section>
<Accordion.Section value="two">
<Accordion.Heading>
<Accordion.Trigger>
Toggle two
</Accordion.Trigger>
<Accordion.Trigger>Toggle two</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section two content
</Accordion.Panel>
<Accordion.Panel>Section two content</Accordion.Panel>
</Accordion.Section>
</Accordion.Root>
```
Expand All @@ -121,51 +105,35 @@ When uncontrolled, use the `defaultValue` prop to set the initial state of the a

```tsx
<Accordion.Root defaultValue={[0]}>
<Accordion.Section> {/* `value={0}` by default */}
<Accordion.Section {/* `value={0}` by default */}>
<Accordion.Heading>
<Accordion.Trigger>
Toggle one
</Accordion.Trigger>
<Accordion.Trigger>Toggle one</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section one content
</Accordion.Panel>
<Accordion.Panel>Section one content</Accordion.Panel>
</Accordion.Section>
<Accordion.Section> {/* `value={1}` by default */}
<Accordion.Section {/* `value={1}` by default */}>
<Accordion.Heading>
<Accordion.Trigger>
Toggle two
</Accordion.Trigger>
<Accordion.Trigger>Toggle two</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section two content
</Accordion.Panel>
<Accordion.Panel>Section two content</Accordion.Panel>
</Accordion.Section>
</Accordion.Root>
</Accordion.Root>;

{/* with custom `value`s */}
// with custom `value`s
<Accordion.Root defaultValue={['a']}>
<Accordion.Section value="a">
<Accordion.Heading>
<Accordion.Trigger>
Toggle one
</Accordion.Trigger>
<Accordion.Trigger>Toggle one</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section one content
</Accordion.Panel>
<Accordion.Panel>Section one content</Accordion.Panel>
</Accordion.Section>
<Accordion.Section value="b">
<Accordion.Heading>
<Accordion.Trigger>
Toggle two
</Accordion.Trigger>
<Accordion.Trigger>Toggle two</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section two content
</Accordion.Panel>
<Accordion.Panel>Section two content</Accordion.Panel>
</Accordion.Section>
</Accordion.Root>
</Accordion.Root>;
```

### Controlled
Expand All @@ -179,26 +147,18 @@ return (
<Accordion.Root value={value} onOpenChange={setValue}>
<Accordion.Section value="a">
<Accordion.Heading>
<Accordion.Trigger>
Toggle one
</Accordion.Trigger>
<Accordion.Trigger>Toggle one</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section one content
</Accordion.Panel>
<Accordion.Panel>Section one content</Accordion.Panel>
</Accordion.Section>
<Accordion.Section value="b">
<Accordion.Heading>
<Accordion.Trigger>
Toggle two
</Accordion.Trigger>
<Accordion.Trigger>Toggle two</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
Section two content
</Accordion.Panel>
<Accordion.Panel>Section two content</Accordion.Panel>
</Accordion.Section>
</Accordion.Root>
)
);
```

## Customization
Expand All @@ -208,9 +168,7 @@ return (
By default, all accordion sections can be opened at the same time. Use the `openMultiple` prop to only allow one open section at a time:

```tsx
<Accordion.Root openMultiple={false}>
{/* subcomponents */}
</Accordion.Root>
<Accordion.Root openMultiple={false}>{/* subcomponents */}</Accordion.Root>
```

### At least one section remains open
Expand All @@ -222,35 +180,176 @@ const [value, setValue] = React.useState([0]);

const handleOpenChange = (newValue) => {
if (newValue.length > 0) {
setValue(newValue)
setValue(newValue);
}
}
};

return (
<Accordion.Root value={value} onOpenChange={handleOpenChange}>
{/* subcomponents */}
</Accordion.Root>
)
);
```

## Horizontal

Use the `orientation` prop to configure a horizontal accordion. In a horizontal accordion, focus will move between `Accordion.Trigger`s with the <kbd class="key">Right Arrow</kbd> and <kbd class="key">Left Arrow</kbd> keys, instead of Down/Up.

```tsx
<Accordion.Root orientation="horizontal">
{/* subcomponents */}
</Accordion.Root>
<Accordion.Root orientation="horizontal">{/* subcomponents */}</Accordion.Root>
```

## RTL

Use the `direction` prop to configure a RTL accordion:

```tsx
<Accordion.Root direction="rtl">
{/* subcomponents */}
</Accordion.Root>
<Accordion.Root direction="rtl">{/* subcomponents */}</Accordion.Root>
```

When a horizontal accordion is set to `direction="rtl"`, keyboard actions are reversed accordingly - <kbd class="key">Left Arrow</kbd> moves focus to the next trigger and <kbd class="key">Right Arrow</kbd> moves focus to the previous trigger.

## Improving searchability of hidden content

:::warning
This is [not yet supported](https://caniuse.com/mdn-html_global_attributes_hidden_until-found_value) in Safari and Firefox as of August 2024 and will fall back to the default `hidden` behavior.

:::

Content hidden by `Accordion.Panel` components can be made accessible only to a browser's find-in-page functionality with the `htmlHidden` prop to improve searchability:

```js
<Accordion.Root htmlHidden="until-found">{/* subcomponents */}</Accordion.Root>
```

Alternatively `htmlHidden` can be passed to `Accordion.Panel` directly to enable this for only one section instead of the whole accordion.

We recommend using [CSS animations](#css-animations) for animated accordions that use this feature. Currently there is browser bug that does not highlight the found text inside elements that have a [CSS transition](#css-transitions) applied.

This relies on the HTML `hidden="until-found"` attribute which only has [partial browser support](https://caniuse.com/mdn-html_global_attributes_hidden_until-found_value) as of August 2024, but automatically falls back to the default `hidden` state in unsupported browsers.

## Animations

Accordion uses [`Collapsible`](/base-ui/react-collapsible/) internally, and can be animated in a [similar way](/base-ui/react-collapsible/#animations).

Four states are available as data attributes to animate the `Accordion.Panel`:

- `[data-collapsible="open"]` - `open` state is `true`.
- `[data-collapsible="closed"]` - `open` state is `false`. Can still be mounted to the DOM if closing.
- `[data-entering]` - the `hidden` attribute was just removed from the DOM and the content element participates in page layout. The `data-entering` attribute will be removed 1 animation frame later.
- `[data-exiting]` - the content element is in the process of being hidden from the DOM, but is still mounted.

The component can be animate when opening or closing using either:

- CSS animations
- CSS transitions
- JavaScript animations

The dimensions of the `Accordion.Panel` subcomponent are provided as the `--accordion-content-height` and `--accordion-content-width` CSS variables.

### CSS Animations

CSS animations can be used with two declarations:

```css
.Accordion-panel {
overflow: hidden;
}

.Accordion-panel[data-collapsible='open'] {
animation: slideDown 300ms ease-out;
}

.Accordion-panel[data-collapsible='closed'] {
animation: slideUp 300ms ease-in;
}

@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--accordion-content-height);
}
}

@keyframes slideUp {
from {
height: var(--accordion-content-height);
}
to {
height: 0;
}
}
```

### CSS Transitions

When using CSS transitions, styles for the `Panel` must be applied to three states:

- The closed styles with `[data-collapsible="closed"]`
- The open styles with `[data-collapsible="open"]`
- The entering styles with `[data-entering]`

```css
.Accordion-panel {
overflow: hidden;
}

.Accordion-panel[data-collapsible='open'] {
height: var(--accordion-content-height);
transition: height 300ms ease-out;
}

.Accordion-panel[data-entering] {
height: 0;
}

.Accordion-panel[data-collapsible='closed'] {
height: 0;
transition: height 300ms ease-in;
}
```

### JavaScript Animations

When using external libraries for animation, for example `framer-motion`, be aware that `Accordion.Section`s hides content using the html `hidden` attribute in the closed state, and does not unmount from the DOM.

```js
function App() {
const [value, setValue] = useState([0]);
return (
<Accordion.Root value={value} onOpenChange={setValue}>
<Accordion.Section>
<Accordion.Heading>
<Accordion.Trigger>Toggle</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel
render={
<motion.div
key="AccordionPanel"
initial={false}
animate={open ? 'open' : 'closed'}
exit={!open ? 'open' : 'closed'}
variants={{
open: {
height: 'auto',
transition: { duration: 0.3, ease: 'ease-out' },
},
closed: {
height: 0,
transition: { duration: 0.3, ease: 'ease-in' },
transitionEnd: { display: 'revert-layer' },
},
}}
/>
}
>
This is the content
</Accordion.Panel>
</Accordion.Section>
{/* more accordion sections */}
</Accordion.Root>
);
}
```
Loading

0 comments on commit d0a3838

Please sign in to comment.