Skip to content
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

[Accordion] New Accordion components and hooks #577

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
190 changes: 190 additions & 0 deletions docs/app/experiments/accordion-animations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
'use client';
import * as React from 'react';
import * as Accordion from '@base_ui/react/Accordion';
import { ExpandMoreIcon } from './accordion';

const DURATION = '300ms';

export default function App() {
return (
<div className="AnimatedAccordions">
<h3>CSS @keyframe animations + `hidden=&quot;until-found&quot;`</h3>
<Accordion.Root
className="MyAccordion-root"
aria-label="Uncontrolled Material UI Accordion"
hiddenUntilFound
>
{[0, 1, 2].map((index) => (
<Accordion.Item className="MyAccordion-item" key={index}>
<Accordion.Header className="MyAccordion-header">
<Accordion.Trigger className="MyAccordion-trigger">
<span className="trigger-text">Trigger {index + 1}</span>
<ExpandMoreIcon />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel className="MyAccordion-panel cssanimation">
<p>
This is the contents of Accordion.Panel {index + 1}
<br />
It uses `hidden=&quot;until-found&quot;` and can be opened by the browser&apos;s
in-page search
</p>
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion.Root>

<h3>CSS transitions</h3>
<Accordion.Root className="MyAccordion-root" aria-label="Uncontrolled Material UI Accordion">
{[0, 1, 2].map((index) => (
<Accordion.Item className="MyAccordion-item" key={index}>
<Accordion.Header className="MyAccordion-header">
<Accordion.Trigger className="MyAccordion-trigger">
<span className="trigger-text">Trigger {index + 1}</span>
<ExpandMoreIcon />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel className="MyAccordion-panel csstransition">
<p>This is the contents of Accordion.Panel {index + 1}</p>
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion.Root>
<MaterialStyles />
</div>
);
}

function MaterialStyles() {
return (
<style suppressHydrationWarning>
{`
.AnimatedAccordions {
width: 40rem;
margin: 1rem;
display: flex;
flex-direction: column;
gap: 2rem;
}

.MyAccordion-root {
--Paper-shadow:
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);

font-family: system-ui, sans-serif;
box-shadow: var(--Paper-shadow);
background-color: rgba(0,0,0,0.12);
border-radius: .3rem;
}

.MyAccordion-item {
position: relative;
background-color: #fff;
color: rgba(0, 0, 0, .87);
}

.MyAccordion-item:not(:first-of-type) {
margin-top: 1px;
}

.MyAccordion-item:first-of-type {
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
}

.MyAccordion-item:last-of-type {
border-bottom-left-radius: .25rem;
border-bottom-right-radius: .25rem;
}

.MyAccordion-header {
margin: 0;
}

.MyAccordion-trigger {
appearance: none;
background-color: transparent;
border: 0;
color: inherit;
cursor: pointer;
padding: 0 1rem;
position: relative;
width: 100%;
display: flex;
flex-flow: row nowrap;
align-items: center;
}

.MyAccordion-trigger:focus-visible {
outline: 0;
background-color: rgba(0,0,0,0.12);
}

.MyAccordion-trigger .trigger-text {
font-size: 1rem;
line-height: 1.5;
margin: 12px auto 12px 0;
}

.MyAccordion-trigger svg {
transition: transform 300ms;
}

.MyAccordion-trigger[data-collapsible="open"] svg {
transform: rotate(180deg);
}

.MyAccordion-panel {
overflow: hidden;
}

.MyAccordion-panel p {
margin: 0;
padding: 1rem;
}

.MyAccordion-panel.cssanimation[data-collapsible="open"] {
animation: slideDown ${DURATION} ease-out;
}

.MyAccordion-panel.cssanimation[data-collapsible="closed"] {
animation: slideUp ${DURATION} ease-out;
}

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

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

.MyAccordion-panel.csstransition[data-collapsible="open"] {
height: var(--accordion-content-height);
transition: height ${DURATION} ease-out;
}

.MyAccordion-panel.csstransition[data-collapsible="closed"] {
height: 0;
transition: height ${DURATION} ease-in;
}

.MyAccordion-panel.csstransition[data-entering] {
height: 0;
}
`}
</style>
);
}
180 changes: 180 additions & 0 deletions docs/app/experiments/accordion-horizontal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
'use client';
import * as React from 'react';
import * as Accordion from '@base_ui/react/Accordion';

function classNames(...classes: Array<string | undefined | null | false>) {
return classes.filter(Boolean).join(' ');
}

export default function App() {
const [val, setVal] = React.useState(['one']);
return (
<div className="HorizontalAccordionDemo">
<h2>Horizontal LTR</h2>
<Accordion.Root
className="MyHorizontalAccordion-root"
aria-label="Uncontrolled Horizontal Accordion"
openMultiple={false}
orientation="horizontal"
>
{['one', 'two', 'three'].map((value, index) => (
<Accordion.Item className="MyHorizontalAccordion-section" key={value}>
<Accordion.Header className="MyHorizontalAccordion-heading">
<Accordion.Trigger className={classNames('MyHorizontalAccordion-trigger', value)}>
<span className="trigger-text">{index + 1}</span>
<span className="trigger-label">{value}</span>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel className="MyHorizontalAccordion-panel">
This is the contents of Accordion.Panel {index + 1}
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion.Root>

<span>
<h2>Horizontal RTL</h2>
<p>one section must remain open</p>
</span>
<Accordion.Root
className="MyHorizontalAccordion-root"
aria-label="Controlled Horizontal RTL Accordion"
openMultiple={false}
orientation="horizontal"
direction="rtl"
value={val}
onOpenChange={(newValue: Accordion.Root.Props['Value']) => {
if (newValue.length > 0) {
setVal(newValue);
}
}}
>
{['one', 'two', 'three'].map((value, index) => (
<Accordion.Item className="MyHorizontalAccordion-section" key={value} value={value}>
<Accordion.Header className="MyHorizontalAccordion-heading">
<Accordion.Trigger className={classNames('MyHorizontalAccordion-trigger', value)}>
<span className="trigger-text">{index + 1}</span>
<span className="trigger-label">{value}</span>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel className="MyHorizontalAccordion-panel">
This is the contents of Accordion.Panel {index + 1}
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion.Root>
<HorizontalStyles />
</div>
);
}

function HorizontalStyles() {
return (
<style suppressHydrationWarning>
{`
.HorizontalAccordionDemo {
margin: 1rem;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2rem;
}

.MyHorizontalAccordion-root {
--Paper-shadow:
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);

font-family: system-ui, sans-serif;
box-shadow: var(--Paper-shadow);
background-color: rgba(0,0,0,0.12);
border-radius: .3rem;
height: 40rem;
display: inline-flex;
}

.MyHorizontalAccordion-section {
position: relative;
background-color: #fff;
color: rgba(0, 0, 0, .87);
display: flex;
}

.MyHorizontalAccordion-section:not(:first-of-type) {
margin-left: 1px;
}

.MyHorizontalAccordion-section:first-of-type {
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
}

.MyHorizontalAccordion-section:last-of-type {
border-bottom-left-radius: .25rem;
border-bottom-right-radius: .25rem;
}

.MyHorizontalAccordion-heading {
margin: 0;
width: 4rem;
}

.MyHorizontalAccordion-trigger {
appearance: none;
background-color: transparent;
border: 0;
color: inherit;
cursor: pointer;
padding: 1rem;
position: relative;
height: 100%;
width: 100%;
display: flex;
flex-flow: column nowrap;
align-items: center;
}

.MyHorizontalAccordion-trigger.one {
background-color: #ddd;
}

.MyHorizontalAccordion-trigger.two {
background-color: #bbb;
}

.MyHorizontalAccordion-trigger.three {
background-color: #999;
}

.MyHorizontalAccordion-trigger:focus-visible {
outline: 0;
background-color: rgba(0,0,0,0.88);
color: #eee;
}

.MyHorizontalAccordion-trigger .trigger-text {
font-size: 1rem;
line-height: 1.5;
margin-bottom: auto;
}

.MyHorizontalAccordion-trigger .trigger-label {
font-size: 1rem;
line-height: 1.5;
margin-bottom: 1rem;
transform: rotate(-90deg);
}

.MyHorizontalAccordion-trigger[data-accordion="open"] svg {
transform: rotate(180deg);
}

.MyHorizontalAccordion-panel {
padding: 1rem;
width: 32rem;
}
`}
</style>
);
}
Loading