Skip to content

feat: Update Accordion expansion behavior#3262

Merged
brandonlenz merged 13 commits intotrussworks:mainfrom
mdmower-csnw:accordion-expansions
Nov 13, 2025
Merged

feat: Update Accordion expansion behavior#3262
brandonlenz merged 13 commits intotrussworks:mainfrom
mdmower-csnw:accordion-expansions

Conversation

@mdmower-csnw
Copy link
Contributor

@mdmower-csnw mdmower-csnw commented Sep 17, 2025

  • When multiselectable is not enabled, only expand the last item with expanded: true on mount.
  • When new items are added to the accordion:
    • If multiselectable is enabled, maintain existing expansions and respect expanded: true value of new items.
    • If multiselectable is not enabled, either maintain the existing expansion if no new items have expanded: true or collapse the existing expansion and expand the last new item with expanded: true.
  • Add and update tests for the updated behavior.

Dev notes
Opted for Map here since the expansion dictionary keys come from users (consumers of this package, not typically end-users, though that is possible). This satisfies lint checks for security/detect-object-injection.

- When `multiselectable` is not enabled, only expand the last item with
  `expanded: true` on mount.
- When new items are added to the accordion:
  - If `multiselectable` is enabled, maintain existing expansions and
    respect `expanded: true` value of new items.
  - If `multiselectable` is not enabled, either maintain the existing
    expansion if no new items have `expanded: true` or collapse the
    existing expansion and expand the last new item with `expanded:
    true`.
- Add and update tests for the updated behavior.
@mdmower-csnw mdmower-csnw requested a review from a team as a code owner September 17, 2025 03:59
@mdmower-csnw
Copy link
Contributor Author

@brandonlenz - Is this pull request something you all would be willing to consider? I'm mostly interested in respecting the expanded: true status of newly added accordion items (i.e. items added after mount), so if the behavior change on mount is not acceptable, I could back out that piece (though, I do think it makes the most sense for an accordion without multiselectable).

- Switch dictionary to Map to address eslint error
  `security/detect-object-injection`
- Replace `.toArray()` with `Array.from()`
@brandonlenz
Copy link
Contributor

@brandonlenz - Is this pull request something you all would be willing to consider? I'm mostly interested in respecting the expanded: true status of newly added accordion items (i.e. items added after mount), so if the behavior change on mount is not acceptable, I could back out that piece (though, I do think it makes the most sense for an accordion without multiselectable).

At a first glance, this seems reasonable to me, but we ultimately just have to make sure our component behaves the same way as the base USDWS version of the component.

I'm going to take a look today and verify against the USWDS implementation.

My gut says we should also log an error when multiselectable is false and multiple items items have expanded set to true

const newExpansions = buildExpansions(newItems, multiselectable)
const newEntries = Array.from(newExpansions.entries())
if (!multiselectable && newEntries.some(([, value]) => value)) {
updatedExpansions.forEach((val, key, map) => map.set(key, false))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
updatedExpansions.forEach((val, key, map) => map.set(key, false))
updatedExpansions.forEach((_val, key, map) => map.set(key, false))

newOpenItems.splice(0, newOpenItems.length)
newOpenItems.push(itemId)
if (!multiselectable) {
updatedExpansions.forEach((val, key, map) => map.set(key, false))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
updatedExpansions.forEach((val, key, map) => map.set(key, false))
updatedExpansions.forEach((_val, key, map) => map.set(key, false))

@mdmower-csnw
Copy link
Contributor Author

Thanks for taking a look at this PR @brandonlenz . In the time since I posted this, I had to move to our own implementation of the accordion because refs associated with elements inside accordion items become mis-associated when array items are removed (I believe this is due to the component using array index for the key of each AccordionItem instead of item.id). And then with the release of eslint-plugin-react-hooks v7, I also reworked expansion tracking to not rely on a useEffect(setState) pattern. So, while there isn't anything particularly wrong this pull request, it could be done better... if more invasive changes to the component were allowed. Do you have an opinion about whether you'd prefer this PR as-is or would like to see a more invasive change that does not rely on useEffect(setState)? (The key bug would be a separate issue and pull request, regardless)

@brandonlenz
Copy link
Contributor

Thanks for taking a look at this PR @brandonlenz . In the time since I posted this, I had to move to our own implementation of the accordion because refs associated with elements inside accordion items become mis-associated when array items are removed (I believe this is due to the component using array index for the key of each AccordionItem instead of item.id). And then with the release of eslint-plugin-react-hooks v7, I also reworked expansion tracking to not rely on a useEffect(setState) pattern. So, while there isn't anything particularly wrong this pull request, it could be done better... if more invasive changes to the component were allowed. Do you have an opinion about whether you'd prefer this PR as-is or would like to see a more invasive change that does not rely on useEffect(setState)? (The key bug would be a separate issue and pull request, regardless)

Understood, I think we're on the same page actually! I'm toying with a simplification of the accordion because my gut reaction was that a useEffect might be overkill for this.

Also, to follow up on my earlier note about USWDS implementation, I believe this matches. The current USWDS looks as though it loops through each accordion item and targets the open state based on the aria-expanded property:
https://github.com/uswds/uswds/blob/9999d08fe6c8e7f0aaa3f24b86ba37b281c25b54/packages/usa-accordion/src/index.js#L87-L88

If the accordion is not does not support multiselect, the last one would "win" and be open:
https://github.com/uswds/uswds/blob/9999d08fe6c8e7f0aaa3f24b86ba37b281c25b54/packages/usa-accordion/src/index.js#L49-L53

I'll follow up later today, hopefully on the simplification. If you already have something for that, feel free to push it up to your PR though. Having the tests in place on this branch already is very helpful as I poke around refactoring but making sure the behavior you need still works.

@mdmower-csnw
Copy link
Contributor Author

I'll follow up later today, hopefully on the simplification. If you already have something for that, feel free to push it up to your PR though.

Sounds good, thanks. Our component is sufficiently different that I don't have changes prepared for immediate post; I'd need to work on a changeset specific to this project. Happy to do so or to let you take the reigns.

@mdmower-csnw
Copy link
Contributor Author

mdmower-csnw commented Nov 10, 2025

@brandonlenz - following up on the useEffect usage and the key issue:

Copy link
Contributor

@brandonlenz brandonlenz left a comment

Choose a reason for hiding this comment

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

A couple of minor suggestions, not blocking.

I'll let you weigh in on what you'd like to do before merging.

@brandonlenz brandonlenz enabled auto-merge (squash) November 13, 2025 14:38
@brandonlenz brandonlenz merged commit dc8bf86 into trussworks:main Nov 13, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants