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

Add accordion element of the design system #79

Merged
merged 6 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
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
79 changes: 79 additions & 0 deletions cypress/component/ui/accordion/Accordion.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Accordion } from '../../../../src/sections/ui/accordion/Accordion'
import { ThemeProvider } from '../../../../src/sections/ui/theme/ThemeProvider'

//TODO: remove ThemeProvider and replace with customMount()

describe('Accordion', () => {
const section1Header = 'Section 1'
const section1Body = 'Content 1'
const section2Header = 'Section 2'
const section2Body = 'Content 2'
it('renders an accordion with defaultActiveKey prop and children', () => {
cy.mount(
<ThemeProvider>
<Accordion defaultActiveKey="1">
<Accordion.Item eventKey="1">
<Accordion.Header>{section1Header}</Accordion.Header>
<Accordion.Body>{section1Body}</Accordion.Body>
</Accordion.Item>
<Accordion.Item eventKey="2">
<Accordion.Header>{section2Header}</Accordion.Header>
<Accordion.Body>{section2Body}</Accordion.Body>
</Accordion.Item>
</Accordion>
</ThemeProvider>
)

cy.findByText(section1Header).should('be.visible')
cy.findByText(section1Body).should('be.visible')
cy.findByText(section2Header).should('be.visible')
cy.findByText(section2Body).should('not.be.visible')

cy.findByText(section1Header).click()
cy.findByText(section1Body).should('not.be.visible')
})
it('renders fully collapsed without a defaultActiveKey', () => {
cy.mount(
<ThemeProvider>
<Accordion alwaysOpen={true}>
<Accordion.Item eventKey="1">
<Accordion.Header>{section1Header}</Accordion.Header>
<Accordion.Body>{section1Body}</Accordion.Body>
</Accordion.Item>
<Accordion.Item eventKey="2">
<Accordion.Header>{section2Header}</Accordion.Header>
<Accordion.Body>{section2Body}</Accordion.Body>
</Accordion.Item>
</Accordion>
</ThemeProvider>
)
cy.findByText(section1Header).should('be.visible')
cy.findByText(section1Body).should('not.be.visible')
cy.findByText(section2Header).should('be.visible')
cy.findByText(section2Body).should('not.be.visible')
})
it('renders the always open tab correctly', () => {
cy.mount(
<ThemeProvider>
<Accordion defaultActiveKey={['1']} alwaysOpen={true}>
<Accordion.Item eventKey="1">
<Accordion.Header>{section1Header}</Accordion.Header>
<Accordion.Body>{section1Body}</Accordion.Body>
</Accordion.Item>
<Accordion.Item eventKey="2">
<Accordion.Header>{section2Header}</Accordion.Header>
<Accordion.Body>{section2Body}</Accordion.Body>
</Accordion.Item>
</Accordion>
</ThemeProvider>
)
cy.findByText(section1Header).should('be.visible')
cy.findByText(section1Body).should('be.visible')
cy.findByText(section2Header).should('be.visible')
cy.findByText(section2Body).should('not.be.visible')

cy.findByText(section2Header).click()
cy.findByText(section1Body).should('be.visible')
cy.findByText(section2Body).should('be.visible')
})
})
25 changes: 25 additions & 0 deletions src/sections/ui/accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ReactNode } from 'react'
import { Accordion as AccordionBS } from 'react-bootstrap'
import { AccordionItem } from './AccordionItem'
import { AccordionBody } from './AccordionBody'
import { AccordionHeader } from './AccordionHeader'

interface AccordionProps {
defaultActiveKey?: string[] | string
alwaysOpen?: boolean
children: ReactNode
}

function Accordion({ defaultActiveKey, children, alwaysOpen = false }: AccordionProps) {
return (
<AccordionBS defaultActiveKey={defaultActiveKey} alwaysOpen={alwaysOpen}>
{children}
</AccordionBS>
)
}

Accordion.Item = AccordionItem
Accordion.Body = AccordionBody
Accordion.Header = AccordionHeader

export { Accordion }
10 changes: 10 additions & 0 deletions src/sections/ui/accordion/AccordionBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ReactNode } from 'react'
import { Accordion as AccordionBS } from 'react-bootstrap'

interface AccordionBodyProps {
children: ReactNode
}

export function AccordionBody({ children }: AccordionBodyProps) {
return <AccordionBS.Body>{children}</AccordionBS.Body>
}
10 changes: 10 additions & 0 deletions src/sections/ui/accordion/AccordionHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ReactNode } from 'react'
import { Accordion as AccordionBS } from 'react-bootstrap'

interface AccordionHeaderProps {
children: ReactNode
}

export function AccordionHeader({ children }: AccordionHeaderProps) {
return <AccordionBS.Header>{children}</AccordionBS.Header>
}
11 changes: 11 additions & 0 deletions src/sections/ui/accordion/AccordionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ReactNode } from 'react'
import { Accordion as AccordionBS } from 'react-bootstrap'

interface AccordionItemProps {
eventKey: string
children: ReactNode
}

export function AccordionItem({ eventKey, children }: AccordionItemProps) {
return <AccordionBS.Item eventKey={eventKey}>{children}</AccordionBS.Item>
}
5 changes: 3 additions & 2 deletions src/sections/ui/assets/styles/bootstrap-customized.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ $link-hover-color: $dv-link-hover-color;
// Badge
@import "bootstrap/scss/badge";

// Accordion
@import "bootstrap/scss/accordion";

// Modal
@import "bootstrap/scss/modal";
@import "bootstrap/scss/close";
Expand All @@ -69,8 +72,6 @@ $breadcrumb-divider: ">";
// Alert
@import "bootstrap/scss/alert";

// Close button for Alert
@import "bootstrap/scss/close";

// Navbar

Expand Down
106 changes: 106 additions & 0 deletions src/stories/ui/accordion/Accordion.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Accordion } from '../../../sections/ui/accordion/Accordion'
/**
* ## Description
* The Accordion is a list of collapsable items.
*
* ## Usage guidelines
*
* - To display the Accordion fully collapsed, don't set the defaultActiveKey
* - When using alwaysOpen=true, the defaultActiveKey must be set in an array object ( for example: {['0']} )
*
*/

const meta: Meta<typeof Accordion> = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a little jsdoc like a073464

That way we get the nice docs from Storybook for free. 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekraffmiller can you take a look at this? I think is the only thing left in the review

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I added that in my latest commit

title: 'UI/Accordion',
component: Accordion,
tags: ['autodocs']
}

export default meta
type Story = StoryObj<typeof Accordion>

export const Default: Story = {
render: () => (
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="0">
<Accordion.Header>Accordion Item #1</Accordion.Header>
<Accordion.Body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</Accordion.Body>
</Accordion.Item>
<Accordion.Item eventKey="1">
<Accordion.Header>Accordion Item #2</Accordion.Header>
<Accordion.Body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
export const FullyCollapsed: Story = {
render: () => (
<Accordion alwaysOpen={true}>
<Accordion.Item eventKey="0">
<Accordion.Header>Accordion Item #1</Accordion.Header>
<Accordion.Body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</Accordion.Body>
</Accordion.Item>
<Accordion.Item eventKey="1">
<Accordion.Header>Accordion Item #2</Accordion.Header>
<Accordion.Body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
export const AlwaysOpen: Story = {
render: () => (
<Accordion defaultActiveKey={['0']} alwaysOpen={true}>
<Accordion.Item eventKey="0">
<Accordion.Header>Accordion Item #1</Accordion.Header>
<Accordion.Body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</Accordion.Body>
</Accordion.Item>
<Accordion.Item eventKey="1">
<Accordion.Header>Accordion Item #2</Accordion.Header>
<Accordion.Body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
30 changes: 30 additions & 0 deletions tests/sections/ui/accordion/Accordion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { render, screen } from '@testing-library/react'
import { Accordion } from '../../../../src/sections/ui/accordion/Accordion'

describe('Accordion', () => {
test('renders an accordion with defaultActiveKey prop and children', () => {
render(
<Accordion defaultActiveKey="1">
<Accordion.Item eventKey="1">
<Accordion.Header>Section 1</Accordion.Header>
<Accordion.Body>Content 1</Accordion.Body>
</Accordion.Item>
<Accordion.Item eventKey="2">
<Accordion.Header>Section 2</Accordion.Header>
<Accordion.Body>Content 2</Accordion.Body>
</Accordion.Item>
</Accordion>
)

const section1Header = screen.getByText('Section 1')
const section1Body = screen.getByText('Content 1')

const section2Header = screen.getByText('Section 2')
const section2Body = screen.getByText('Content 2')

expect(section1Header).toBeInTheDocument()
expect(section1Body).toBeInTheDocument()
expect(section2Header).toBeInTheDocument()
expect(section2Body).toBeInTheDocument()
})
})
19 changes: 19 additions & 0 deletions tests/sections/ui/accordion/AccordionItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { render, screen } from '@testing-library/react'
import { Accordion } from '../../../../src/sections/ui/accordion/Accordion'

describe('AccordionItem', () => {
test('renders an accordion item with eventKey and children', () => {
render(
<Accordion.Item eventKey="1">
<Accordion.Header>Section 1</Accordion.Header>
<Accordion.Body>Content 1</Accordion.Body>
</Accordion.Item>
)

const sectionHeader = screen.getByText('Section 1')
const sectionBody = screen.getByText('Content 1')

expect(sectionHeader).toBeInTheDocument()
expect(sectionBody).toBeInTheDocument()
})
})