Skip to content
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
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 67 additions & 40 deletions src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { type ReactNode, useState } from 'react';
import React, {type ReactNode, useEffect, useState} from 'react';
import { Box, Typography, Accordion as MuiAccordion, AccordionDetails, AccordionGroup, AccordionSummary } from '@mui/joy';
import type { SxProps } from '@mui/joy/styles/types';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
Expand All @@ -7,6 +7,8 @@ export interface AccordionItem {
id: string;
header: string;
children: ReactNode;
expand?: boolean;
onChange?: (isExpanded: boolean) => void;
}

export interface AccordionProps {
Expand Down Expand Up @@ -45,15 +47,33 @@ const GenericAccordion: React.FC<AccordionProps> = ({
return [];
});

const handleToggle = (itemId: string) => {
useEffect(() => {
const externallyExpanded = items
.filter((item) => item.expand)
.map((item) => item.id);

if (externallyExpanded.length > 0) {
if (multiple) {
setExpandedItems((prev) => Array.from(new Set([...prev, ...externallyExpanded])));
} else {
setExpandedItems([externallyExpanded[0]]);
}
}
}, [items, multiple]);

const handleToggle = (itemId: string, onChange?: (expanded: boolean) => void) => {
setExpandedItems(prev => {
let newExpanded: string[];

if (multiple) {
return prev.includes(itemId)
? prev.filter(id => id !== itemId)
: [...prev, itemId];
} else {
return prev.includes(itemId) ? [] : [itemId];
newExpanded = prev.includes(itemId) ? [] : [itemId];
}
if (onChange) onChange(newExpanded.includes(itemId));
return newExpanded;
});
};

Expand All @@ -65,45 +85,52 @@ const GenericAccordion: React.FC<AccordionProps> = ({
...accordionGroupSX,
}}
>
{items.map((item) => (
<MuiAccordion
key={item.id}
expanded={expandedItems.includes(item.id)}
onChange={() => handleToggle(item.id)}
sx={{
...accordionSX,
}}
>
<AccordionSummary
indicator={
<KeyboardArrowDownIcon
color='primary'
fontSize='large'
/>
}
{items.map((item) => {
const isExpanded =
typeof item.expand === 'boolean'
? item.expand
: expandedItems.includes(item.id);

return (
<MuiAccordion
key={item.id}
expanded={isExpanded}
onChange={() => handleToggle(item.id, item.onChange)}
sx={{
...accordionSX,
}}
>
<Typography
level="h3"
m={2}
sx={{
display: 'flex',
alignItems: 'center',
userSelect: 'none',
fontWeight: 'md',
color: 'primary.500',
...headerSX,
}}
<AccordionSummary
indicator={
<KeyboardArrowDownIcon
color='primary'
fontSize='large'
/>
}
>
{item.header}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ p: 1, marginLeft: 2 }}>
{item.children}
</Box>
</AccordionDetails>
</MuiAccordion>
))}
<Typography
level="h3"
m={2}
sx={{
display: 'flex',
alignItems: 'center',
userSelect: 'none',
fontWeight: 'md',
color: 'primary.500',
...headerSX,
}}
>
{item.header}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ p: 1, marginLeft: 2 }}>
{item.children}
</Box>
</AccordionDetails>
</MuiAccordion>
);
})},
</AccordionGroup>
);
};
Expand Down
17 changes: 14 additions & 3 deletions src/components/Accordion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Accordion } from "@agile-software/shared-components";
import { useState } from "react";

function MyComponent() {
const [expanded, setExpanded] = useState<string | null>("section1");

const accordionItems = [
{
id: "section1",
Expand All @@ -18,7 +20,10 @@ function MyComponent() {
<p>This is the content for the first section.</p>
<p>It can contain any React elements.</p>
</div>
)
),
expanded: expanded === 'section1',
onChange: (isExpanded: boolean) =>
setExpanded(isExpanded ? "section1" : null),
},
{
id: "section2",
Expand All @@ -31,7 +36,10 @@ function MyComponent() {
<li>Option 2: Description</li>
</ul>
</div>
)
),
expand: expanded === "section2",
onChange: (isExpanded: boolean) =>
setExpanded(isExpanded ? "section2" : null),
},
{
id: "section3",
Expand All @@ -52,7 +60,7 @@ function MyComponent() {

## Props

- `items`: AccordionItem[] - Array of accordion items with id, header, and children
- `items`: AccordionItem[] - Array of accordion items with id, header, children, expand (optional) and onChange (optional)
- `multiple`: boolean (optional, default: false) - Whether multiple sections can be open simultaneously
- `defaultExpanded`: string | string[] (optional) - Default expanded section(s) by id
- `accordionSX`: SxProps (optional) - Additional styles for individual accordion items
Expand All @@ -64,3 +72,6 @@ function MyComponent() {
- `id`: string - Unique identifier for the accordion item
- `header`: string - The title displayed in the accordion header
- `children`: ReactNode - The content to be rendered when the section is expanded
- `expand` : boolean - (Optional) Controls expansion externally. If set, overrides internal state. For example for sync with URL-parameters.
- `onChnage` : (isExpanded: boolean) => void - (Optional) Callback triggered whenever the expansion state changes

Loading