diff --git a/package-lock.json b/package-lock.json index 7362a50..d860ffa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@agile-software/shared-components", - "version": "2.1.0", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agile-software/shared-components", - "version": "2.1.0", + "version": "2.2.0", "dependencies": { "@mui/icons-material": "^7.3.2", "react-drag-drop-files": "^3.1.0" @@ -554,6 +554,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2268,6 +2269,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.2.tgz", "integrity": "sha512-TZWazBjWXBjR6iGcNkbKklnwodcwj0SrChCNHc9BhD9rBgET22J1eFhHsEmvSvru9+opDy3umqAimQjokhfJlQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.3" @@ -2336,6 +2338,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.2.tgz", "integrity": "sha512-qXvbnawQhqUVfH1LMgMaiytP+ZpGoYhnGl7yYq2x57GYzcFL/iPzSZ3L30tlbwEjSVKNYcbiKO8tANR1tadjUg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.3", @@ -2385,6 +2388,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.2.tgz", "integrity": "sha512-AOyfHjyDKVPGJJFtxOlept3EYEdLoar/RvssBTWVAvDJGIE676dLi2oT/Kx+FoVXFoA/JdV7DEMq/BVWV3KHRw==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -2395,6 +2399,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.2.tgz", "integrity": "sha512-ha7mFoOyZGJr75xeiO9lugS3joRROjc8tG1u4P50dH0KR7bwhHznVMcYg7MouochUy0OxooJm/OOSpJ7gKcMvg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.3", @@ -2422,6 +2427,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.2.tgz", "integrity": "sha512-PkJzW+mTaek4e0nPYZ6qLnW5RGa0KN+eRTf5FA2nc7cFZTeM+qebmGibaTLrgQBy3UpcpemaqfzToBNkzuxqew==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.3", @@ -2456,6 +2462,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.2.tgz", "integrity": "sha512-9d8JEvZW+H6cVkaZ+FK56R53vkJe3HsTpcjMUtH8v1xK6Y1TjzHdZ7Jck02mGXJsE6MQGWVs3ogRHTQmS9Q/rA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.3", @@ -2496,6 +2503,7 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.6.tgz", "integrity": "sha512-NVBbIw+4CDMMppNamVxyTccNv0WxtDb7motWDlMeSC8Oy95saj1TIZMGynPpFLePt3yOD8TskzumeqORCgRGWw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.3" @@ -2513,6 +2521,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.2.tgz", "integrity": "sha512-4DMWQGenOdLnM3y/SdFQFwKsCLM+mqxzvoWp9+x2XdEzXapkznauHLiXtSohHs/mc0+5/9UACt1GdugCX2te5g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.3", @@ -4580,7 +4589,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -8604,7 +8612,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -8766,7 +8773,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -9034,7 +9040,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9627,7 +9632,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -9639,7 +9643,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/punycode": { @@ -9695,7 +9698,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -9708,7 +9710,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -10030,7 +10031,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" diff --git a/src/components/Accordion/Accordion.tsx b/src/components/Accordion/Accordion.tsx index 4b6e2c1..8948fda 100644 --- a/src/components/Accordion/Accordion.tsx +++ b/src/components/Accordion/Accordion.tsx @@ -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'; @@ -7,6 +7,8 @@ export interface AccordionItem { id: string; header: string; children: ReactNode; + expand?: boolean; + onChange?: (isExpanded: boolean) => void; } export interface AccordionProps { @@ -45,15 +47,33 @@ const GenericAccordion: React.FC = ({ 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; }); }; @@ -65,45 +85,52 @@ const GenericAccordion: React.FC = ({ ...accordionGroupSX, }} > - {items.map((item) => ( - handleToggle(item.id)} - sx={{ - ...accordionSX, - }} - > - - } + {items.map((item) => { + const isExpanded = + typeof item.expand === 'boolean' + ? item.expand + : expandedItems.includes(item.id); + + return ( + handleToggle(item.id, item.onChange)} + sx={{ + ...accordionSX, + }} > - + } > - {item.header} - - - - - {item.children} - - - - ))} + + {item.header} + + + + + {item.children} + + + + ); + })}, ); }; diff --git a/src/components/Accordion/README.md b/src/components/Accordion/README.md index c3569d4..1231960 100644 --- a/src/components/Accordion/README.md +++ b/src/components/Accordion/README.md @@ -9,6 +9,8 @@ import { Accordion } from "@agile-software/shared-components"; import { useState } from "react"; function MyComponent() { + const [expanded, setExpanded] = useState("section1"); + const accordionItems = [ { id: "section1", @@ -18,7 +20,10 @@ function MyComponent() {

This is the content for the first section.

It can contain any React elements.

- ) + ), + expanded: expanded === 'section1', + onChange: (isExpanded: boolean) => + setExpanded(isExpanded ? "section1" : null), }, { id: "section2", @@ -31,7 +36,10 @@ function MyComponent() {
  • Option 2: Description
  • - ) + ), + expand: expanded === "section2", + onChange: (isExpanded: boolean) => + setExpanded(isExpanded ? "section2" : null), }, { id: "section3", @@ -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 @@ -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 +