Skip to content

Commit

Permalink
fix accordion (#275)
Browse files Browse the repository at this point in the history
  • Loading branch information
prabhuignoto authored Jul 24, 2023
1 parent 0f04626 commit 19fbdfd
Show file tree
Hide file tree
Showing 14 changed files with 1,064 additions and 747 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ bundle-analysis**
storybook-static
build-storybook.log

debug.log
debug.log

coverage
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@relative-ci/agent": "^4.1.5",
"@sentry/react": "^7.59.2",
"@sentry/tracing": "^7.59.2",
"@sentry/react": "^7.60.0",
"@sentry/tracing": "^7.60.0",
"@types/bluebird": "^3.5.38",
"@types/node": "^20.4.2",
"@types/node": "^20.4.4",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"autoprefixer": "^10.4.14",
"cssnano": "^6.0.1",
"esbuild": "^0.18.14",
"esbuild": "^0.18.15",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jest": "^27.2.3",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"eslint-plugin-typescript-sort-keys": "^2.3.0",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
"postcss": "^8.4.26",
"postcss": "^8.4.27",
"postcss-bem-linter": "^4.0.0",
"postcss-import": "^15.1.0",
"postcss-preset-env": "^9.0.0",
Expand All @@ -50,16 +50,16 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"remove-files-webpack-plugin": "^1.5.0",
"sass": "^1.63.6",
"snyk": "^1.1192.0",
"sass": "^1.64.1",
"snyk": "^1.1193.0",
"stylelint": "15.10.2",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-sass-guidelines": "^10.0.0",
"stylelint-order": "^6.0.3",
"stylelint-scss": "^5.0.1",
"thread-loader": "^4.0.2",
"ts-toolbelt": "^9.6.0",
"turbo": "^1.10.8",
"turbo": "^1.10.9",
"typescript": "^5.1.6",
"webpack": "^5.88.2",
"webpack-stats-plugin": "^1.1.3"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { AccordionGroup } from '../accordion-group';
import { describe, expect, it } from 'vitest';
import userEvent from '@testing-library/user-event';

describe('AccordionGroup', () => {
it('renders children', async () => {
const { getByText } = render(
<AccordionGroup expanded titles={['title 1', 'title 2']}>
<div>Content 1</div>
<div>Content 2</div>
</AccordionGroup>
);

await expect(getByText('Content 1')).toBeInTheDocument();
await expect(getByText('Content 2')).toBeInTheDocument();
});

it('expands accordion when clicked', async () => {
render(
<AccordionGroup titles={['Title 1', 'Title 2']}>
<div>Content 1</div>
<div>Content 2</div>
</AccordionGroup>
);

const title1 = screen.getByText('Title 1');
userEvent.click(title1);

expect(await screen.findByText('Content 1')).toBeVisible();
});

it('only one accordion expanded at a time if autoClose', async () => {
const { getByText } = render(
<AccordionGroup autoClose titles={['Title 1', 'Title 2']}>
<div>Content 1</div>
<div>Content 2</div>
</AccordionGroup>
);

const title1 = getByText('Title 1');
const title2 = getByText('Title 2');

fireEvent.click(title1);
await waitFor(
() => {
expect(getByText('Content 1')).toBeInTheDocument();
},
{ timeout: 2000 }
);

fireEvent.click(title2);
await waitFor(
() => {
expect(getByText('Content 2')).toBeInTheDocument();
},
{
timeout: 2000,
}
);
});

it('multiple accordions stay open if no autoClose', async () => {
const { getByText } = render(
<AccordionGroup titles={['Title 1', 'Title 2']}>
<div>Content 1</div>
<div>Content 2</div>
</AccordionGroup>
);

const title1 = getByText('Title 1');
const title2 = getByText('Title 2');

fireEvent.click(title1);

await waitFor(
() => {
expect(getByText('Content 1')).toBeInTheDocument();
},
{
timeout: 1000,
}
);

fireEvent.click(title2);

await waitFor(
() => {
expect(getByText('Content 2')).toBeInTheDocument();
expect(getByText('Content 1')).toBeInTheDocument();
},
{
timeout: 2000,
}
);
});
});
94 changes: 70 additions & 24 deletions packages/lib/components/accordion-group/accordion-group.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
import classNames from 'classnames';
import { nanoid } from 'nanoid';
import React, { useCallback, useLayoutEffect, useMemo } from 'react';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { Accordion } from '../accordion/accordion';
import {
AccordionGroupProps,
AccordionItemProps,
} from '../accordion/accordion-model';
import { AccordionGroupProps } from '../accordion/accordion-model';
import styles from './accordion-group.module.scss';

/**
* AccordionGroup component that renders a group of Accordion components.
* @param {boolean} alignIconRight - Whether to align the icon to the right or not. Default is false.
* @param {boolean} autoClose - Whether to automatically close other accordions when one is expanded. Default is false.
* @param {boolean} border - Whether to show a border around the group. Default is false.
* @param {React.ReactNode} children - The content of the AccordionGroup component.
* @param {boolean} expanded - Whether to expand the accordion by default. Default is false.
* @param {string} iconColor - The color of the icon. Default is undefined.
* @param {string} iconType - The type of the icon. Default is 'chevron'.
* @param {string} titleColor - The color of the title. Default is '#000'.
* @param {string[]} titles - The titles of the accordions.
* @param {boolean} isTitleBold - Whether to make the title bold or not. Default is false.
* @param {boolean} disableCollapse - Whether to disable collapsing of the accordion. Default is false.
* @param {boolean} focusable - Whether the accordion is focusable or not. Default is true.
* @param {React.ReactNode[]} icons - The custom icons for the accordions.
* @param {boolean} disableIcon - Whether to disable the icon or not. Default is false.
* @param {boolean} disableARIA - Whether to disable ARIA attributes or not. Default is false.
* @param {string} size - The size of the accordion. Default is 'sm'.
* @param {boolean} fullWidth - Whether to make the accordion full width or not. Default is false.
* @param {boolean} colorizeHeader - Whether to colorize the header or not. Default is false.
* @param {number} headerHeight - The height of the header. Default is 40.
* @returns {JSX.Element} - The AccordionGroup component.
*/
const AccordionGroup = ({
alignIconRight = false,
autoClose = false,
Expand All @@ -28,40 +48,66 @@ const AccordionGroup = ({
fullWidth = false,
colorizeHeader = false,
headerHeight = 40,
}: AccordionGroupProps) => {
const [items, setItems] = React.useState<Array<AccordionItemProps>>(
Array.isArray(children)
? children.map(() => ({
expanded: expanded,
id: nanoid(),
}))
: []
);
}: AccordionGroupProps): JSX.Element => {
const [items, setItems] = useState(() => {
if (Array.isArray(children)) {
return children.map(() => ({
expanded,
id: nanoid(),
}));
}

return [];
});

/**
* Handles the expansion of an accordion.
* @param {string} id - The id of the accordion.
*/
const handleExpand = useCallback((id: string) => {
setItems(prev =>
prev.map(item => ({
...item,
expanded: autoClose ? item.id === id : item.id === id || item.expanded,
}))
setItems(prevItems =>
prevItems.map(item => {
if (item.id === id) {
return { ...item, expanded: true };
}

if (!autoClose) {
return item;
}

return { ...item, expanded: false };
})
);
}, []);

/**
* Handles the collapsing of an accordion.
* @param {string} id - The id of the accordion.
*/
const handleCollapse = useCallback((id: string) => {
setItems(prev =>
prev.map(item => ({
...item,
expanded: autoClose ? false : item.id === id ? false : item.expanded,
}))
setItems(prevItems =>
prevItems.map(item => {
if (item.id === id) {
return { ...item, expanded: false };
}

return item;
})
);
}, []);

/**
* The class name of the AccordionGroup component.
*/
const groupClass = useMemo(() => {
return classNames(styles.group, {
[styles.grp_no_border]: !border,
});
}, []);

/**
* Sets the items of the AccordionGroup component.
*/
useLayoutEffect(() => {
if (titles.length) {
setItems(() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ exports[`Accordion > should render snapshot 1`] = `
class="_accordion_f93c7f _no-border_f93c7f"
>
<div
aria-controls="accordion-body-XfRrlOZr-kAK81oCjc2g4"
aria-controls="accordion-body-ylmb_HCLwEG6TjWllk6pl"
aria-expanded="false"
class="_header_c2ceb5 _focusable_c2ceb5 _size_c2ceb5"
id="accordion-arH2_AXQ4MB5xQEXQYl78"
role="button"
id="accordion-m2khAmLLYptJsjjdjJeGx"
role="heading"
style="--rc-accordion-header-height: 40px; outline: none; position: relative;"
tabindex="0"
>
Expand Down Expand Up @@ -40,9 +40,10 @@ exports[`Accordion > should render snapshot 1`] = `
/>
</div>
<div
aria-labelledby="accordion-arH2_AXQ4MB5xQEXQYl78"
aria-labelledby="accordion-m2khAmLLYptJsjjdjJeGx"
class="_body_f93c7f _animate_f93c7f _close_f93c7f"
id="accordion-body-XfRrlOZr-kAK81oCjc2g4"
id="accordion-body-ylmb_HCLwEG6TjWllk6pl"
role="region"
style="--title-color: #000; --transition: cubic-bezier(0.19, 1, 0.22, 1); --max-height: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// test.jsamp

import { expect, describe, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { AccordionHeader } from '../accordion-header';

describe('AccordionHeader', () => {
it('renders title', () => {
render(<AccordionHeader title="Test" />);

expect(screen.getByText('Test')).toBeInTheDocument();
});

it('renders custom icon when provided', () => {
const Icon = () => <div data-testid="custom-icon" />;

render(<AccordionHeader customIcon={<Icon />} />);

expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
});

it('calls onToggle when clicked', async () => {
const onToggle = vi.fn();

render(<AccordionHeader onToggle={onToggle} />);

await userEvent.click(screen.getByRole('heading'));

expect(onToggle).toHaveBeenCalledTimes(1);
});

it('has aria-expanded=true when open', () => {
render(<AccordionHeader open />);

expect(screen.getByRole('heading')).toHaveAttribute(
'aria-expanded',
'true'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('Accordion', () => {
</Accordion>
);

fireEvent.click(getByRole('button'));
fireEvent.click(getByRole('heading'));

await waitFor(async () => {
expect(container.firstChild).toHaveClass(styles.open);
Expand All @@ -52,7 +52,7 @@ describe('Accordion', () => {
}
);

fireEvent.click(getByRole('button'));
fireEvent.click(getByRole('heading'));

expect(onExpanded).toHaveBeenCalled();
});
Expand Down
Loading

1 comment on commit 19fbdfd

@vercel
Copy link

@vercel vercel bot commented on 19fbdfd Jul 24, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.