Skip to content

Commit

Permalink
Accordion Component SOV-567 (#47)
Browse files Browse the repository at this point in the history
* chore: remove unused file

* chore: add missing Icons

* feat: add Accordion component

* feat: add jest tests

* chore: update disabled styling

* chore: amends per review comments

* Create warm-icons-provide.md

Accordion Component SOV-567

* fix: arrow-up svg path
  • Loading branch information
soulBit authored and creed-victor committed Nov 8, 2022
1 parent 8523a06 commit efcc9e4
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/warm-icons-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sovryn/ui": patch
---

Accordion Component SOV-567
27 changes: 27 additions & 0 deletions packages/ui/src/1_atoms/Accordion/Accordion.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.accordion {
@apply flex flex-col my-3;
}

.label {
@apply flex flex-row cursor-pointer items-stretch;

&.disabled,
&:disabled {
@apply opacity-50 cursor-default;
}
}

.arrow {
@apply ml-2 flex flex-col items-center justify-center;
}

.icon {
@apply transition-transform;
&.isOpen {
@apply transform rotate-180;
}
}

.content {
@apply mt-2;
}
84 changes: 84 additions & 0 deletions packages/ui/src/1_atoms/Accordion/Accordion.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useArgs } from '@storybook/client-api';
import { Story, Meta } from '@storybook/react';

import React, { ComponentProps, useCallback } from 'react';

import {
Dropdown,
DropdownMode,
DropdownSize,
Menu,
MenuItem,
} from '../../2_molecules';
import { Paragraph } from '../Paragraph/Paragraph';
import { Accordion } from './Accordion';

export default {
title: 'Atoms/Accordion',
component: Accordion,
} as Meta;

const Template: Story<ComponentProps<typeof Accordion>> = args => {
const [, updateArgs] = useArgs();
const handleOnChange = useCallback(
(toOpen: boolean) => updateArgs({ open: toOpen }),
[updateArgs],
);

return <Accordion {...args} onClick={handleOnChange} />;
};

export const Default = Template.bind({});
Default.args = {
label: 'Test (click to toggle)',
children: <div>Simple Test content</div>,
disabled: false,
dataLayoutId: 'accordion-simple',
};

export const RichContent = Template.bind({});
RichContent.args = {
label: 'Test (click to toggle)',
children: (
<div className="w-75 bg-gray-30 p-3 rounded">
<Paragraph className="mb-2 text-gray-80">Content panel</Paragraph>
<Dropdown
text="Dropdown Button"
size={DropdownSize.large}
mode={DropdownMode.sameWidth}
>
<Menu>
<MenuItem text="Dropdown Item 1" />
<MenuItem text="Dropdown Item 2" />
<MenuItem text="Dropdown Item 3" />
</Menu>
</Dropdown>
</div>
),
disabled: false,
dataLayoutId: 'accordion-richcontent',
};

export const OpenAndDisabled = Template.bind({});
OpenAndDisabled.args = {
label: 'Test (disabled)',
children: (
<div className="w-75 bg-gray-30 p-3 rounded">
<Paragraph className="mb-2 text-gray-80">Content panel</Paragraph>
<Dropdown
text="Dropdown Button"
size={DropdownSize.large}
mode={DropdownMode.sameWidth}
>
<Menu>
<MenuItem text="Dropdown Item 1" />
<MenuItem text="Dropdown Item 2" />
<MenuItem text="Dropdown Item 3" />
</Menu>
</Dropdown>
</div>
),
disabled: true,
open: true,
dataLayoutId: 'accordion-opendisabled',
};
73 changes: 73 additions & 0 deletions packages/ui/src/1_atoms/Accordion/Accordion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import React from 'react';

import { Accordion } from './Accordion';

describe('Accordion', () => {
it('renders accordion', () => {
const { getByText } = render(
<Accordion label="Test" dataLayoutId="accordion-simple">
<div>Content</div>
</Accordion>,
);
expect(getByText('Test')).toBeInTheDocument();
});

it('calls eventHandler on click', () => {
const handleClick = jest.fn();
const { getByTestId } = render(
<Accordion
label="Test (click to toggle)"
dataLayoutId="accordion-simple"
onClick={handleClick}
>
<div>Content</div>
</Accordion>,
);
userEvent.click(getByTestId('accordion-simple'));
expect(handleClick).toHaveBeenCalledTimes(1);
});

it('shows content on click', async () => {
const handleClick = () => {
render(
<Accordion
label="Test (click to toggle)"
dataLayoutId="accordion-simple"
open
>
<div data-layout-id="content-to-show">Content</div>
</Accordion>,
);
};
const { getByTestId } = render(
<Accordion
label="Test (click to toggle)"
dataLayoutId="accordion-simple"
onClick={handleClick}
>
<div data-layout-id="original-content">Content</div>
</Accordion>,
);
userEvent.click(getByTestId('accordion-simple'));
expect(getByTestId('content-to-show')).toBeInTheDocument();
});

it('renders disabled accordion and prevents clicks on it', () => {
const handleClick = jest.fn();
const { getByTestId } = render(
<Accordion
label="Test (click to toggle)"
dataLayoutId="accordion-simple"
onClick={handleClick}
disabled
>
<div>Content</div>
</Accordion>,
);
userEvent.click(getByTestId('accordion-simple'));
expect(handleClick).toHaveBeenCalledTimes(0);
});
});
71 changes: 71 additions & 0 deletions packages/ui/src/1_atoms/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { ReactNode, FC, useCallback } from 'react';

import classNames from 'classnames';

import { Heading } from '../Heading/Heading';
import { HeadingType } from '../Heading/Heading.types';
import { Icon } from '../Icon/Icon';
import { IconNames } from '../Icon/Icon.types';
import styles from './Accordion.module.css';

export interface IAccordionProps {
label: ReactNode;
children: ReactNode;
className?: string;
disabled?: boolean;
open?: boolean;
onClick?: (toOpen: boolean) => void;
dataLayoutId?: string;
}

export const Accordion: FC<IAccordionProps> = ({
label,
children,
className,
disabled = false,
open = false,
onClick,
dataLayoutId,
}) => {
const onClickCallback = useCallback(
() => !disabled && onClick?.(!open),
[disabled, open, onClick],
);

return (
<div className={classNames(styles.accordion, className)}>
<button
className={classNames(styles.label, {
[styles.disabled]: disabled,
})}
onClick={onClickCallback}
data-layout-id={dataLayoutId}
>
<>
{typeof label === 'string' ? (
<Heading type={HeadingType.h3}>{label}</Heading>
) : (
label
)}
</>
<div className={styles.arrow}>
<Icon
icon={IconNames.ARROW_UP}
size={8}
className={classNames(styles.icon, {
[styles.isOpen]: open,
})}
/>
</div>
</button>
{open && (
<div
className={styles.content}
data-layout-id={`${dataLayoutId}-content`}
>
{children}
</div>
)}
</div>
);
};
1 change: 1 addition & 0 deletions packages/ui/src/1_atoms/Accordion/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Accordion';
3 changes: 3 additions & 0 deletions packages/ui/src/1_atoms/Icon/Icon.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import type { IconProp } from '@fortawesome/fontawesome-svg-core';
import { ReactNode } from 'react';

export enum IconNames {
ARROW_UP = 'arrow-up',
ARROW_BACK = 'arrow-back',
ARROW_DOWN_WIDE = 'arrow-down-wide',
ARROW_DOWN = 'arrow-down',
ARROW_RIGHT = 'arrow-right',
ARROW_FORWARD = 'arrow-forward',
ARROW_BACK = 'arrow-back',
DEPOSIT = 'deposit',
DISCONNECT = 'disconnect',
EDIT = 'edit',
FAILED_TX = 'failed-tx',
INFO = 'info',
Expand Down
26 changes: 0 additions & 26 deletions packages/ui/src/1_atoms/Icon/iconNames.ts

This file was deleted.

6 changes: 6 additions & 0 deletions packages/ui/src/1_atoms/Icon/iconSvgPaths.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { IconName } from './Icon.types';

export const IconSvgPaths: Record<IconName, string[]> = {
'arrow-up': [
'M3.70223 18.6016L12.5 9.82311L21.2978 18.6016L24 15.8994L12.5 4.39939L0.999999 15.8994L3.70223 18.6016Z',
],
'arrow-down-wide': [
'm24,11.94l-12,12.06l-12,-12.06l8.14,0l0,-11.94l7.72,0l0,11.94l8.14,0z',
],
Expand All @@ -19,6 +22,9 @@ export const IconSvgPaths: Record<IconName, string[]> = {
deposit: [
'M12.2352 17.7504L8.2832 13.7824H11.2472V6.8112H13.2232V13.7824H16.1872L12.2352 17.7504Z',
],
disconnect: [
'M18 5.88889L16.308 7.61222L19.404 10.7778H7.2V13.2222H19.404L16.308 16.3756L18 18.1111L24 12L18 5.88889ZM2.4 3.44444H12V1H2.4C1.08 1 0 2.1 0 3.44444V20.5556C0 21.9 1.08 23 2.4 23H12V20.5556H2.4V3.44444Z',
],
edit: [
'M3.6 16.8996V20.4H7.1004L17.4204 10.08L13.92 6.5772L3.6 16.8996ZM20.13 7.3704C20.2171 7.28396 20.2862 7.18113 20.3333 7.06786C20.3805 6.95459 20.4048 6.8331 20.4048 6.7104C20.4048 6.5877 20.3805 6.46621 20.3333 6.35294C20.2862 6.23967 20.2171 6.13685 20.13 6.0504L17.946 3.8664C17.8596 3.77932 17.7567 3.71021 17.6435 3.66305C17.5302 3.61589 17.4087 3.59161 17.286 3.59161C17.1633 3.59161 17.0418 3.61589 16.9285 3.66305C16.8153 3.71021 16.7124 3.77932 16.626 3.8664L14.922 5.5776L18.4224 9.078L20.13 7.3704Z',
],
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/1_atoms/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './Portal';
export * from './Paragraph';
export * from './Badge';
export * from './Link';
export * from './Accordion';

0 comments on commit efcc9e4

Please sign in to comment.