Skip to content

Commit

Permalink
feat: [WIP] Accordion refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
timoheddes committed Aug 17, 2023
1 parent b0e7c54 commit 57b487d
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 202 deletions.
Original file line number Diff line number Diff line change
@@ -1,59 +1,79 @@
import { Accordion, IAccordionProps } from './Accordion';
import type { IAccordionRootProps, IAccordionSectionProps } from './';
import { Accordion } from './';

import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';

const meta: Meta<{} & IAccordionProps> = {
const generateSection = (index: number): IAccordionSectionProps => ({
title: <span>Section {index + 1}</span>,
children: <p>This is the content for section {index + 1}</p>,
onOpen: () => console.log(`open section ${index + 1}`),
onClose: () => console.log(`close section ${index + 1}`),
});
const generateSections = (n: number): IAccordionSectionProps[] =>
Array.from({ length: n }, (d, i) => generateSection(i));

const sampleSections: IAccordionSectionProps[] = generateSections(5);

type StoryProps = { sectionCount: number } & IAccordionRootProps;

const meta: Meta<StoryProps> = {
title: 'Components/Accordion',
parameters: {
controls: {
hideNoControlsWarning: true,
sort: 'requiredFirst',
},
docs: {
description: {
component: '',
},
},
},
argTypes: {
linked: {
type: 'boolean',
defaultValue: true,
control: { type: 'boolean' },
description:
'Each section will close the other sections if they are linked',
control: {
type: 'boolean',
},
},
sections: {
defaultValue: [],
description: 'Accordion children',
control: {
type: 'array',
},
sectionCount: {
control: { type: 'range', min: 1, max: sampleSections.length, step: 1 },
description: 'Adjust sample section items count',
},
},
};

export default meta;
type Story = StoryObj<{} & IAccordionProps>;
type IStory = StoryObj<StoryProps>;

export const Dynamic: Story = {
export const Dynamic: IStory = {
name: 'Accordion',
args: {
linked: true,
sections: [
{
title: <span>First Section</span>,
children: <p>This is the content for the first section</p>,
onOpen: () => console.log('open first item'),
onClose: () => console.log('close first item'),
},
{
title: <span>Second Section</span>,
children: <p>This is the content for the second section</p>,
onOpen: () => console.log('open second item'),
onClose: () => console.log('close second item'),
},
{
title: <span>Third Section</span>,
children: <p>This is the content for the third section</p>,
onOpen: () => console.log('open third item'),
onClose: () => console.log('close third item'),
},
],
linked: false,
sectionCount: 3,
},
render: ({ linked, sections }) => {
return <Accordion linked={Boolean(linked)} sections={sections} />;
render: ({ linked, sectionCount }) => {
return (
<Accordion.Root linked={linked}>
{sampleSections
.slice(0, sectionCount)
.map(
(
{ title, children, onOpen, onClose }: IAccordionSectionProps,
index,
) => (
<Accordion.Section
onOpen={onOpen}
onClose={onClose}
title={title}
key={index}
>
{children}
</Accordion.Section>
),
)}
</Accordion.Root>
);
},
};

export default meta;
98 changes: 0 additions & 98 deletions packages/libs/react-ui/src/components/Accordion/Accordion.test.tsx

This file was deleted.

51 changes: 16 additions & 35 deletions packages/libs/react-ui/src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,22 @@
import { AccordionSection, IAccordionSectionProps } from './AccordionSection';
import useLinked from './useLinked';
import { IAccordionSectionsProps } from '.';

import React, { FC, useState } from 'react';
import React, { FC, FunctionComponentElement } from 'react';

export interface IAccordionProps {
sections: Omit<IAccordionSectionProps, 'isOpen' | 'onToggle'>[];
export interface IAccordionRootProps {
children?: React.ReactNode;
linked?: boolean;
openSection?: number;
}

export const Accordion: FC<IAccordionProps> = ({ sections, linked = true }) => {
const [expandedSections, setExpandedSections] = useState<number[]>([]);
const handleOpen = (index: number): void => {
if (linked) setExpandedSections([index]);
else setExpandedSections([...expandedSections, index]);
};
const handleClose = (index: number): void => {
setExpandedSections(expandedSections.filter((item) => item !== index));
};

const isOpen = (index: number): boolean => expandedSections.includes(index);

const handleToggle = (index: number): void => {
if (isOpen(index)) handleClose(index);
else handleOpen(index);
};

return (
<div data-testid="kda-accordion-wrapper">
{sections.map((section, index) => (
<AccordionSection
{...section}
isOpen={isOpen(index)}
onToggle={() => handleToggle(index)}
key={String(section.title)}
>
{section.children}
</AccordionSection>
))}
</div>
);
export const AccordionRoot: FC<IAccordionRootProps> = ({
children,
linked,
openSection,
}) => {
if (linked) {
const { setUsingLinked } = useLinked(openSection);
setUsingLinked(true);
}
return <div data-testid="kda-accordion-wrapper">{children}</div>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,42 @@ import {
accordionTitleVariants,
toggleButtonClass,
} from './Accordion.css';
import useLinked from './useLinked';

import { SystemIcon } from '@components/Icon';
import classNames from 'classnames';
import React, { FC, useEffect, useRef } from 'react';
import React, { FC, useState } from 'react';

export interface IAccordionSectionProps {
title: React.ReactNode;
children: React.ReactNode;
isOpen: boolean;
onToggle: () => void;
onToggle?: () => void;
onOpen?: () => void;
onClose?: () => void;
}

export const AccordionSection: FC<IAccordionSectionProps> = ({
isOpen = false,
title,
children,
onToggle,
onOpen,
onClose,
}) => {
const didMountRef = useRef(false);
const { usingLinked, activeSection, setActiveSection } = useLinked();
const [isOpen, setIsOpen] = useState(false);

useEffect(() => {
if (!didMountRef.current) {
didMountRef.current = true;
return;
}

if (isOpen) onOpen?.();
else onClose?.();
}, [isOpen]);
const onToggle = (): void => (isOpen ? onClose?.() : onOpen?.());
const handleClick = (): void => {
setIsOpen(!isOpen);
};

return (
<div className={accordionSectionClass}>
<div className={accordionSectionClass} data-testid="kda-accordion-section">
<div
data-testid="kda-accordion-title"
onClick={onToggle}
data-testid="kda-accordion-section-title"
onClick={() => {
handleClick();
onToggle();
}}
className={classNames(
accordionTitleClass,
accordionTitleVariants[isOpen ? 'opened' : 'closed'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import useLinked from './useLinked';
import { IAccordionSectionProps } from '.';

import React, { FC, FunctionComponentElement } from 'react';

export interface IAccordionSectionsProps {
// children?: FunctionComponentElement<IAccordionSectionProps>[];
children?: React.ReactNode;
linked?: boolean;
openSection?: number;
}

export const AccordionSections: FC<IAccordionSectionsProps> = ({
children,
linked,
openSection = 0,
}) => {
return <div data-testid="kda-accordion-sections">{children}</div>;
};
25 changes: 22 additions & 3 deletions packages/libs/react-ui/src/components/Accordion/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
export { Accordion } from './Accordion';
export type { IAccordionProps } from './Accordion';
export type { IAccordionSectionProps } from './AccordionSection';
import type { IAccordionRootProps } from './Accordion';
import { AccordionRoot } from './Accordion';
import type { IAccordionSectionProps } from './AccordionSection';
import { AccordionSection } from './AccordionSection';
import type { IAccordionSectionsProps } from './AccordionSections';
import { AccordionSections } from './AccordionSections';

import { FC } from 'react';

export { IAccordionRootProps, IAccordionSectionProps, IAccordionSectionsProps };

export interface IAccordionProps {
Root: FC<IAccordionRootProps>;
Sections: FC<IAccordionSectionsProps>;
Section: FC<IAccordionSectionProps>;
}

export const Accordion: IAccordionProps = {
Root: AccordionRoot,
Sections: AccordionSections,
Section: AccordionSection,
};
16 changes: 16 additions & 0 deletions packages/libs/react-ui/src/components/Accordion/useLinked.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useState } from 'react';

interface IUseLinkedReturn {
activeSection: number;
setActiveSection: React.Dispatch<React.SetStateAction<number>>;
usingLinked: boolean;
setUsingLinked: React.Dispatch<React.SetStateAction<boolean>>;
}

const useLinked = (openSection = 0): IUseLinkedReturn => {
const [usingLinked, setUsingLinked] = useState(false);
const [activeSection, setActiveSection] = useState(openSection);
return { activeSection, setActiveSection, usingLinked, setUsingLinked };
};

export default useLinked;
Loading

0 comments on commit 57b487d

Please sign in to comment.