Skip to content

Commit

Permalink
Merge branch 'feature/accordion-component' into q/1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
bert-e committed Oct 31, 2024
2 parents 62b8ff1 + cf89cce commit 1be1400
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
93 changes: 93 additions & 0 deletions src/lib/components/accordion/Accordion.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useState } from 'react';

import { spacing, Stack } from '../../spacing';
import { Box } from '../box/Box';
import { Icon } from '../icon/Icon.component';

import styled from 'styled-components';

import { Text } from '../text/Text.component';

export type AccordionProps = {
title: string;
id: string;
children: React.ReactNode;
style?: React.CSSProperties;
};

const AccordionHeader = styled.button`
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
gap: ${spacing.r8};
width: 100%;
cursor: pointer;
background-color: transparent;
color: ${(props) => props.theme.textPrimary};
padding: ${spacing.r4};
width: 100%;
`;
const AccordionContainer = styled.div<{
isOpen: boolean;
}>`
overflow: hidden;
opacity: ${(props) => (props.isOpen ? 1 : 0)};
transition: height 0.3s ease-in, opacity 0.3s ease-in, visibility 0.3s;
visibility: ${(props) => (props.isOpen ? 'visible' : 'hidden')};
`;
const Wrapper = styled.div`
padding-block: ${spacing.r8};
`;

export const Accordion = ({ title, id, style, children }: AccordionProps) => {
const [isOpen, setIsOpen] = useState(false);

const handleToggleContent = () => {
setIsOpen((prev) => !prev);
};

return (
<Box style={{ width: '100%', height: 'auto' }}>
<h3 style={{ margin: 0 }}>
<AccordionHeader
id={`Accordion-header-${id}`}
onClick={handleToggleContent}
aria-controls={id}
aria-expanded={isOpen}
onKeyDown={(e) =>
(e.key === 'Enter' || e.key === ' ') && handleToggleContent
}
>
<Stack direction="horizontal" gap="r8">
<Icon
name="Chevron-up"
size="lg"
style={{
transform: isOpen ? 'rotate(0deg)' : 'rotate(180deg)',
transition: 'transform 0.3s ease-in',
}}
/>
<Text isEmphazed>{title}</Text>
</Stack>
</AccordionHeader>
</h3>

<AccordionContainer
ref={(element) => {
if (isOpen) {
element?.style.setProperty('height', element.scrollHeight + 'px');
} else {
element?.style.setProperty('height', '0px');
}
}}
isOpen={isOpen}
id={id}
aria-labelledby={`Accordion-header-${id}`}
role="region"
>
<Wrapper style={style}>{children}</Wrapper>
</AccordionContainer>
</Box>
);
};
52 changes: 52 additions & 0 deletions src/lib/components/accordion/Accordion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { Accordion } from './Accordion.component';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from 'react-query';

describe('Accordion', () => {
const selectors = {
accordionToggle: () => screen.getByRole('button'),
accordionContainer: () => screen.getByRole('region'),
accordionContent: () => screen.queryByText(/Test content/i),
};
const renderAccordion = () => {
const queryClient = new QueryClient();
render(
<QueryClientProvider client={queryClient}>
<Accordion title="Advanced Testings" id="test-accordion">
<div>Test content</div>
</Accordion>
</QueryClientProvider>,
);
};
it('should render the Accordion component with title and content', () => {
renderAccordion();

const accordionToggle = selectors.accordionToggle();
expect(accordionToggle).toBeInTheDocument();
const accordionContent = selectors.accordionContent();
expect(accordionContent).toBeInTheDocument();
});

it('should toggle the content when clicking on the accordion header', () => {
renderAccordion();
const accordionToggle = selectors.accordionToggle();
const accordionContent = selectors.accordionContent();
expect(accordionContent).not.toBeVisible();
userEvent.click(accordionToggle);
expect(accordionContent).toBeVisible();
});

it('should toggle the content when pressing the enter key or space key on the accordion header', () => {
renderAccordion();
const accordionToggle = selectors.accordionToggle();
const accordionContent = selectors.accordionContent();
expect(accordionContent).not.toBeVisible();
accordionToggle.focus();
userEvent.keyboard('{enter}');
expect(accordionContent).toBeVisible();
userEvent.keyboard('{space}');
expect(accordionContent).not.toBeVisible();
});
});
1 change: 1 addition & 0 deletions src/lib/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export { HealthSelector } from './components/healthselectorv2/HealthSelector.com
export { CoreUiThemeProvider } from './components/coreuithemeprovider/CoreUiThemeProvider';
export { Box } from './components/box/Box';
export { Input } from './components/inputv2/inputv2';
export { Accordion } from './components/accordion/Accordion.component';
26 changes: 26 additions & 0 deletions stories/Accordion/accordion.guideline.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
Meta,
Story,
Canvas,
Primary,
Controls,
Unstyled,
Source,
Title,
} from '@storybook/blocks';
import { Accordion } from '../../src/lib/components/accordion/Accordion.component';

import * as Stories from './accordion.stories';

<Meta of={Stories} name="Guideline" />

# Accordion

Accordions are used to toggle the visibility of content.
It is used to hide non essential information or to reduce the amount of information displayed on the screen.

## Playground

<Canvas of={Stories.Playground} layout="fullscreen" />

<Controls of={Stories.Playground} />
65 changes: 65 additions & 0 deletions stories/Accordion/accordion.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Meta, StoryObj } from '@storybook/react';
import React from 'react';

import {
Accordion,
AccordionProps,
} from '../../src/lib/components/accordion/Accordion.component';
import { Stack } from '../../src/lib/spacing';
import { Button } from '../../src/lib/components/buttonv2/Buttonv2.component';

type AccordionStory = StoryObj<AccordionProps>;

const meta: Meta<AccordionProps> = {
title: 'Components/Accordion',
component: Accordion,
args: {
title: 'Accordion title',
children: (
<Stack direction="vertical" gap="r8">
<div>This is the content of the accordion.</div>
<Button label={'Check'} onClick={() => console.log('click')}></Button>
</Stack>
),
},
argTypes: {
children: {
control: { disable: true },
description: 'Content of the accordion',
table: {
type: { summary: 'React.ReactNode' },
},
},
title: {
control: { type: 'text' },
description: 'Title of the accordion',
table: {
type: { summary: 'string' },
},
},
style: {
control: { disable: true },
description: 'Use this to style the accordion content container',
table: { type: { summary: 'CSSProperties' } },
},
id: {
control: { disable: true },
table: { type: { summary: 'string' } },
description: 'Unique id for the accordion content container',
},
},
};

export default meta;

export const Playground: AccordionStory = {};

export const Stacked: AccordionStory = {
render: (args) => (
<Stack direction="vertical" gap="r8">
<Accordion {...args} />
<Accordion {...args} />
<Accordion {...args} style={{ backgroundColor: 'grey' }} />
</Stack>
),
};

0 comments on commit 1be1400

Please sign in to comment.