Skip to content

Commit

Permalink
Add Accordion and AccordionGroup components (#499)
Browse files Browse the repository at this point in the history
* staging Accordion and AccordionGroup file structure

* bump RAC to 1.4.0

* basic shape of Accordion component

* tweak props import

* add story wireframe for Accordion

* styling accordion

* styling

* additional styling and alternative icon implementation

* refining button styling

* scaffolding AccordionGroup

* add title prop and styling to AccordionGroup

* continuing work on Accordion and AccordionGroup styling

* fix focus state for accordions inside accordiongroup

* fleshing out argTypes

* scaffolding accordion docs

* add examples to vite kitchen sink

* fleshing out examples on vite

* expanding stories and docs

* stories and docs

* typo

* renaming CSS classes to bcds-react-aria-* to match convention

* typo

* add comments to accordion group props

* remove unused CSS class

* bump RAC to 1.5.0

* update RAC imports
  • Loading branch information
mkernohanbc authored Dec 11, 2024
1 parent 20a47d4 commit cab8e45
Show file tree
Hide file tree
Showing 14 changed files with 684 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/react-components/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "@bcgov/bc-sans/css/BC_Sans.css";
import { Button, Footer, FooterLinks, Header } from "@/components";
import useWindowDimensions from "@/hooks/useWindowDimensions";
import {
AccordionGroupPage,
AlertBannerPage,
ButtonPage,
ButtonGroupPage,
Expand Down Expand Up @@ -156,6 +157,7 @@ function App() {
<h1>Components</h1>
<ButtonPage />
<ButtonGroupPage />
<AccordionGroupPage />
<CalloutPage />
<CheckboxGroupPage />
<SwitchPage />
Expand Down
63 changes: 63 additions & 0 deletions packages/react-components/src/components/Accordion/Accordion.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.bcds-react-aria-Disclosure {
display: flex;
flex-direction: column;
border: var(--layout-border-width-small) solid
var(--surface-color-border-default);
border-radius: var(--layout-border-radius-medium);
overflow: hidden;
}

.bcds-react-aria-Disclosure--Button {
display: inline-flex;
flex-direction: row;
padding: var(--layout-padding-small) var(--layout-padding-medium);
gap: var(--layout-margin-small);
justify-content: space-between;
align-items: center;
background-color: var(--surface-color-background-light-gray);
border: none;
cursor: pointer;
font: var(--typography-bold-body);
color: var(--typography-color-primary);
}

/* Hover */
.bcds-react-aria-Disclosure--Button[data-hovered] {
background-color: var(--surface-color-menus-hover);
}

/* Accordion content */
.bcds-react-aria-Disclosure[data-expanded]
> .bcds-react-aria-Disclosure--Panel {
display: flex;
align-items: flex-start;
padding: var(--layout-padding-medium);
gap: var(--layout-margin-small);
background-color: var(--surface-color-forms-default);
font: var(--typography-regular-body);
}

/* Focus state */
.bcds-react-aria-Disclosure[data-focus-visible-within] {
outline: solid var(--layout-border-width-medium)
var(--surface-color-border-active);
outline-offset: var(--layout-margin-hair);
}

/* Override default focus behaviour for button */
.bcds-react-aria-Disclosure[data-focus-visible-within]
> .bcds-react-aria-Disclosure--Button {
outline: none;
}

.bcds-react-aria-Disclosure--Button[data-focused] {
outline: none;
}

/* Disabled state */
.bcds-react-aria-Disclosure[data-disabled]
> .bcds-react-aria-Disclosure--Button {
background-color: var(--surface-color-forms-disabled);
color: var(--typography-color-disabled);
cursor: not-allowed;
}
42 changes: 42 additions & 0 deletions packages/react-components/src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
Disclosure,
DisclosurePanel,
DisclosureProps as DisclosureProps,
} from "react-aria-components";

import "./Accordion.css";
import Button from "../Button";
import SvgChevronUpIcon from "../Icons/SvgChevronUpIcon";
import SvgChevronDownIcon from "../Icons/SvgChevronDownIcon";

export interface AccordionProps extends DisclosureProps {
/* Button label text */
label?: string;
}

export default function Accordion({
label,
children,
...props
}: AccordionProps) {
return (
<Disclosure className={`bcds-react-aria-Disclosure`} {...props}>
{({ isExpanded }) => (
<>
<Button
className="bcds-react-aria-Disclosure--Button"
size="small"
variant="link"
slot="trigger"
>
{label}
{isExpanded ? <SvgChevronUpIcon /> : <SvgChevronDownIcon />}
</Button>
<DisclosurePanel className="bcds-react-aria-Disclosure--Panel">
<>{children}</>
</DisclosurePanel>
</>
)}
</Disclosure>
);
}
2 changes: 2 additions & 0 deletions packages/react-components/src/components/Accordion/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "./Accordion";
export type { AccordionProps } from "./Accordion";
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.bcds-react-aria-DisclosureGroup {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: var(--layout-border-width-small);
background-color: var(--surface-color-border-default);
border: var(--layout-border-width-small) solid
var(--surface-color-border-default);
border-radius: var(--layout-border-radius-medium);
overflow: hidden;
}

/* Override individual accordion item borders */
.bcds-react-aria-DisclosureGroup > .bcds-react-aria-Disclosure {
border: none;
border-radius: var(--layout-border-radius-none);
}

/* Focus state */
.bcds-react-aria-DisclosureGroup
> .bcds-react-aria-Disclosure[data-focus-visible-within] {
border-radius: var(--layout-border-radius-hair);
outline-offset: calc(var(--layout-margin-hair) * -1);
}

/* Title styles */
.bcds-react-aria-DisclosureGroup--Title {
color: var(--typography-color-primary);
margin: var(--layout-margin-medium) var(--layout-margin-none);
}

.bcds-react-aria-DisclosureGroup--Title > h1 {
font: var(--typography-bold-h1);
margin-block-start: 0;
margin-block-end: 0;
}

.bcds-react-aria-DisclosureGroup--Title > h2 {
font: var(--typography-bold-h2);
margin-block-start: 0;
margin-block-end: 0;
}

.bcds-react-aria-DisclosureGroup--Title > h3 {
font: var(--typography-bold-h3);
margin-block-start: 0;
margin-block-end: 0;
}

.bcds-react-aria-DisclosureGroup--Title > h4 {
font: var(--typography-bold-h4);
margin-block-start: 0;
margin-block-end: 0;
}

.bcds-react-aria-DisclosureGroup--Title > h5 {
font: var(--typography-bold-h5);
margin-block-start: 0;
margin-block-end: 0;
}

.bcds-react-aria-DisclosureGroup--Title > h6 {
font: var(--typography-bold-h6);
margin-block-start: 0;
margin-block-end: 0;
}

.bcds-react-aria-DisclosureGroup--Title > span {
font: var(--typography-bold-large-body);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { DisclosureGroup, DisclosureGroupProps } from "react-aria-components";

import "./AccordionGroup.css";

export interface AccordionGroupProps extends DisclosureGroupProps {
/* Sets label displayed above accordion group */
title?: string;
/* Sets element type for accordion group title. If not set, defaults to <span> */
titleElement?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
}

export default function AccordionGroup({
title,
titleElement,
children,
...props
}: AccordionGroupProps) {
function getTitle() {
switch (titleElement) {
case "h1":
return <h1>{title}</h1>;
case "h2":
return <h2>{title}</h2>;
case "h3":
return <h3>{title}</h3>;
case "h4":
return <h4>{title}</h4>;
case "h5":
return <h5>{title}</h5>;
case "h6":
return <h6>{title}</h6>;
default:
return <span>{title}</span>;
}
}
return (
<>
{title && (
<div className="bcds-react-aria-DisclosureGroup--Title">
{getTitle()}
</div>
)}
<DisclosureGroup className="bcds-react-aria-DisclosureGroup" {...props}>
{children}
</DisclosureGroup>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "./AccordionGroup";
export type { AccordionGroupProps } from "./AccordionGroup";
2 changes: 2 additions & 0 deletions packages/react-components/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import "@bcgov/design-tokens/css/variables.css";

export { default as Accordion } from "./Accordion";
export { default as AccordionGroup } from "./AccordionGroup";
export { default as AlertBanner } from "./AlertBanner";
export { default as AlertDialog } from "./AlertDialog";
export { default as Button } from "./Button";
Expand Down
126 changes: 126 additions & 0 deletions packages/react-components/src/pages/AccordionGroup/AccordionGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
Accordion,
AccordionGroup,
Button,
Form,
Select,
TextField,
} from "@/components";

export default function AccordionGroupPage() {
return (
<>
<h2>AccordionGroup</h2>
<AccordionGroup title="Accordion group (default)" titleElement="h4">
<Accordion id="accordion1" label="Accordion 1">
<div
style={{
width: "100%",
height: "0",
paddingBottom: "51%",
position: "relative",
}}
>
<iframe
src="https://giphy.com/embed/8gjHXLJumLNv1BtJxX"
width="100%"
height="100%"
style={{ position: "absolute" }}
className="giphy-embed"
allowFullScreen
></iframe>
</div>
</Accordion>
<Accordion id="accordion2" label="Accordion 2">
Accordion 2
</Accordion>
<Accordion id="accordion3" label="Accordion 3 is disabled" isDisabled>
Accordion 3 is disabled
</Accordion>
<Accordion
id="accordion4"
label="Accordion 4 is a thing you can do (but please don't do it like this)"
>
<Form
style={{
display: "flex",
flexDirection: "column",
gap: "var(--layout-margin-small)",
width: "100%",
maxWidth: "100%",
}}
>
<div className="field">
<TextField isRequired label="Name" />
</div>
<div className="field">
<TextField label="Email address" type="email" />
</div>
<div className="field">
<Select
items={[
{
id: "1",
label: "Agnetha",
},
{
id: "2",
label: "Björn",
},
{
id: "3",
label: "Benny",
},
{
id: "4",
label: "Anni-Frid",
},
]}
label="Favourite member of Abba"
isRequired
/>
</div>
<div
style={{
display: "inline-flex",
flexDirection: "row",
alignItems: "flex-start",
gap: "var(--layout-margin-none) var(--layout-margin-small)",
margin: "var(--layout-margin-small) var(--layout-margin-none);",
}}
>
<Button variant="primary" size="small" type="submit">
Submit
</Button>
<Button variant="secondary" size="small" type="reset">
Reset
</Button>
</div>
</Form>
</Accordion>
</AccordionGroup>
<hr />
<AccordionGroup
title="Accordion group (allows multiple expanded)"
titleElement="h4"
allowsMultipleExpanded
>
<Accordion id="1" label="Accordion 1">
Two roads diverged in a yellow wood
</Accordion>
<Accordion id="2" label="Accordion 2">
And sorry I could not travel both
</Accordion>
<Accordion id="3" label="Accordion 3">
And be one traveler, long I stood
</Accordion>
<Accordion id="4" label="Accordion 4">
And looked down one as far as I could
</Accordion>
<Accordion id="5" label="Accordion 5">
To where it bent in the undergrowth;
</Accordion>
</AccordionGroup>
</>
);
}
3 changes: 3 additions & 0 deletions packages/react-components/src/pages/AccordionGroup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AccordionGroupPage from "./AccordionGroup";

export default AccordionGroupPage;
Loading

0 comments on commit cab8e45

Please sign in to comment.