Skip to content

[CDX-285] Allow default collapsing of individual filter groups#212

Merged
esezen merged 14 commits into
cdx-336-plp-ui-feature-add-visual-filter-support-to-filtersfrom
cdx-285-plp-ui-feature-allow-default-collapsing-of-individual-filter
Apr 7, 2026
Merged

[CDX-285] Allow default collapsing of individual filter groups#212
esezen merged 14 commits into
cdx-336-plp-ui-feature-add-visual-filter-support-to-filtersfrom
cdx-285-plp-ui-feature-allow-default-collapsing-of-individual-filter

Conversation

@Alexey-Pavlov
Copy link
Copy Markdown
Contributor

Pull Request Description

This pull request adds support and testing for controlling the collapsed/expanded state of filter groups in the PLP (Product Listing Page) Filters component. It introduces new configuration options, ensures consistent behavior across different usage scenarios (including per-facet, global, and metadata-driven collapse), and adds tests.

Pull Request Checklist

Before you submit a pull request, please make sure you have to following:

  • I have added or updated TypeScript types for my changes, ensuring they are compatible with the existing codebase.
  • I have added JSDoc comments to my TypeScript definitions for improved documentation.
  • I have added tests that prove that my feature works.
  • I have added any necessary documentation (if appropriate).
  • I have made sure my PR is up-to-date with the main branch.

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no API changes)
  • Documentation content changes
  • TypeScript type definitions update
  • Other... Please describe:

@Alexey-Pavlov Alexey-Pavlov requested a review from a team February 26, 2026 14:50
Copilot AI review requested due to automatic review settings February 26, 2026 14:50
@constructor-claude-bedrock

This comment has been minimized.

@constructor-claude-bedrock

This comment has been minimized.

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@Mudaafi Mudaafi left a comment

Choose a reason for hiding this comment

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

I like the implementation! I left some comments, mostly around tests and docs, but lmk wyt

Comment thread src/utils/itemFieldGetters.ts Outdated
}

export function getIsCollapsedFacetField(facet: PlpFacet): boolean | undefined {
return facet?.data?.cio_render_collapsed ? true : undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Alexey-Pavlov Curious if you have any thoughts on what Copilot is suggesting here


The Filters component supports various configuration options that can be passed via component props, or via the `filterConfigs` prop at the `CioPlp`-level.

### Collapse All Filter Groups
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we tag this with a version number like when we implemented filtering in Groups?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I put a v1.10.0, as the next version 🤔

ae5f553

}
```

### Collapse Specific Filter Groups
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +61 to +74
/**
* When filterConfigs.renderCollapsed is set and groupsConfigs.isCollapsed is not explicitly set,
* apply the global collapse setting to the Groups filter as well.
*/
function resolveGroupsConfigs(
filterConfigs?: Omit<UseFilterProps, 'facets'>,
groupsConfigs?: Omit<GroupsProps, 'groups'>,
): Omit<GroupsProps, 'groups'> | undefined {
if (filterConfigs?.renderCollapsed !== undefined && groupsConfigs?.isCollapsed === undefined) {
return { ...groupsConfigs, isCollapsed: filterConfigs.renderCollapsed };
}
return groupsConfigs;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting, I don't know if we want these to be merged actually now that I think about it. What if a customer wanted to always expand the group hierarchy but always collapse the filters? 🤔 wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, that absolutely makes sense
I made a change, so Filters and Groups are now independent
ae5f553

Comment on lines +857 to +860
const isFilterGroupExpanded = (filterGroup) => {
const arrow = filterGroup.querySelector('.cio-arrow');
return arrow?.classList.contains('cio-arrow-down');
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting way to check. I'd have gone with the height, but this works too

Comment on lines +67 to +76
it('Should render all filter groups expanded when renderCollapsed is false', () => {
const html = renderToString(
<CioPlp apiKey={DEMO_API_KEY}>
<Filters {...filterProps} renderCollapsed={false} />
</CioPlp>,
);

expect(html).toContain('cio-arrow-down');
expect(html).not.toContain('cio-arrow-up');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's add a test for the default behavior to be expanded to ensure we have backwards compatibility

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread spec/components/CioPlp/CioPlp.test.jsx Outdated
);

await waitFor(() => {
// All filter group arrows should indicate collapsed state (cio-arrow-up)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's abstract it into a function instead of relying on comments to tell us what we're checking here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

…e-allow-default-collapsing-of-individual-filter

# Conflicts:
#	spec/components/Filters/Filters.test.jsx
#	src/components/Filters/FilterGroup.tsx
#	src/components/Filters/Filters.tsx
#	src/hooks/useFilter.ts
#	src/stories/components/Filters/Filters.stories.tsx
#	src/types.ts
#	src/utils/itemFieldGetters.ts
@constructor-claude-bedrock

This comment has been minimized.

@constructor-claude-bedrock

This comment has been minimized.

@Alexey-Pavlov Alexey-Pavlov requested a review from Mudaafi March 11, 2026 13:26
@constructor-claude-bedrock

This comment has been minimized.

…l-filter-support-to-filters' into cdx-285-plp-ui-feature-allow-default-collapsing-of-individual-filter

# Conflicts:
#	src/components/CioPlpGrid/CioPlpGrid.tsx
#	src/components/Filters/FilterGroup.tsx
#	src/components/Filters/Filters.tsx
#	src/hooks/useCioPlp.ts
#	src/hooks/useFilter.ts
#	src/stories/components/CioPlp/Code Examples.mdx
#	src/stories/components/CioPlp/Props.mdx
#	src/stories/components/Filters/Filters.stories.tsx
#	src/stories/hooks/UseFilter/UseFilterReturn.md
@constructor-claude-bedrock

This comment was marked as outdated.

@Alexey-Pavlov Alexey-Pavlov changed the base branch from main to cdx-336-plp-ui-feature-add-visual-filter-support-to-filters March 19, 2026 18:17
@Alexey-Pavlov
Copy link
Copy Markdown
Contributor Author

Hi @Mudaafi! Thanks so much for your review! I recently made some updates—would you mind taking a look? I borrowed perFacetConfig from this @esezen PR, integrated it, and updated the target branch.

…ters' into cdx-285-plp-ui-feature-allow-default-collapsing-of-individual-filter
constructor-claude-bedrock[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@esezen esezen left a comment

Choose a reason for hiding this comment

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

This is looking great. I left some comments

Comment thread src/hooks/useFilter.ts Outdated
Comment thread src/hooks/useFilter.ts Outdated
Comment thread src/types.ts Outdated
@esezen esezen requested a review from Copilot March 24, 2026 12:12

This comment was marked as outdated.

constructor-claude-bedrock[bot]

This comment was marked as outdated.

@Alexey-Pavlov Alexey-Pavlov requested a review from esezen March 30, 2026 11:33
@esezen esezen requested a review from Copilot March 30, 2026 13:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/hooks/useFilter.ts Outdated
Comment on lines +120 to +124
if (
typeof getIsCollapsedFacetField === 'function' &&
getIsCollapsedFacetField(facet) !== undefined
) {
return !!getIsCollapsedFacetField(facet);
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

getIsCollapsedFacetField(facet) is invoked twice. Store the result in a local variable and use that for both the undefined check and the boolean coercion to avoid duplicate work and to prevent inconsistent behavior if a custom getter has side effects.

Suggested change
if (
typeof getIsCollapsedFacetField === 'function' &&
getIsCollapsedFacetField(facet) !== undefined
) {
return !!getIsCollapsedFacetField(facet);
if (typeof getIsCollapsedFacetField === 'function') {
const collapsedFromFacetField = getIsCollapsedFacetField(facet);
if (collapsedFromFacetField !== undefined) {
return !!collapsedFromFacetField;
}

Copilot uses AI. Check for mistakes.
Comment thread src/stories/components/CioPlp/Props.mdx Outdated
| isHiddenFilterOptionFn | `(option: PlpFacetOption) => boolean` | Function that returns true if an option should be hidden |
| sliderStep | `number` | Global slider step for all range facets |
| facetSliderSteps | `Record<string, number>` | Per-facet slider step configuration |
| defaultCollapsed | `boolean` | When `true`, all filter groups render collapsed by default. When `false`, all render expanded (overrides metadata). Per-facet overrides via `perFacetConfigs` take precedence. |
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

This description conflicts with the implemented priority order in getIsCollapsed: facet metadata (cio_render_collapsed) takes precedence over defaultCollapsed (both true and false). Update the docs to reflect that defaultCollapsed is the lowest priority default and does not override facet metadata, unless the code is intended to change.

Suggested change
| defaultCollapsed | `boolean` | When `true`, all filter groups render collapsed by default. When `false`, all render expanded (overrides metadata). Per-facet overrides via `perFacetConfigs` take precedence. |
| defaultCollapsed | `boolean` | Global default collapse behavior for filter groups when no facet metadata (`cio_render_collapsed`) or `perFacetConfigs` override is provided. When `true`, unmatched filter groups render collapsed by default; when `false`, they render expanded by default. Facet metadata and per-facet overrides via `perFacetConfigs` take precedence. |

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Alexey-Pavlov I think Copilot is right here, right 🤔 ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yess, I think I need to check his comments more often, especially on big PRs like this
58c1e0c

constructor-claude-bedrock[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@esezen esezen left a comment

Choose a reason for hiding this comment

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

LGTM!

…ters' into cdx-285-plp-ui-feature-allow-default-collapsing-of-individual-filter
Copy link
Copy Markdown

@constructor-claude-bedrock constructor-claude-bedrock Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This PR adds support for controlling the default collapsed/expanded state of filter groups via three interfaces: per-facet config (perFacetConfigs[name].isCollapsed), facet metadata (cio_render_collapsed), and a global defaultCollapsed prop — with good test coverage and documentation. The implementation is generally clean, but there are a few notable issues to address.

Inline comments: 6 discussions added

Overall Assessment: ⚠️ Needs Work

Comment thread src/components/Filters/FilterGroup.tsx
Comment thread src/hooks/useFilter.ts
Comment thread src/types.ts
Comment thread spec/components/Filters/FilterGroup.test.jsx
});
});

const expectAllFilterGroupsCollapsed = (container) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The helper functions expectAllFilterGroupsCollapsed and expectAllFilterGroupsExpanded are defined inside the describe block but outside any it block. They rely on the class convention that collapsed state uses cio-arrow-up and expanded uses cio-arrow-down. This is a somewhat fragile coupling to CSS class names that encode both visual direction and semantic state in the same token. If the styling convention ever changes, these helpers will silently produce wrong results.

Consider adding a comment above each helper explaining this class-name convention, or look up the aria-expanded attribute if it's set on filter headers (which would be a more robust semantic selector for this type of assertion).

@esezen esezen merged commit 19ff36c into cdx-336-plp-ui-feature-add-visual-filter-support-to-filters Apr 7, 2026
13 of 14 checks passed
@esezen esezen deleted the cdx-285-plp-ui-feature-allow-default-collapsing-of-individual-filter branch April 7, 2026 14:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const checkboxPosition = perFacetConfigs?.[facet.name]?.checkboxPosition;
const [isCollapsed, setIsCollapsed] = useState(false);

const toggleIsCollapsed = () => setIsCollapsed(!isCollapsed);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

toggleIsCollapsed uses setIsCollapsed(!isCollapsed), which can apply a stale value under React concurrent rendering/batched updates. Prefer the functional updater form so toggling always uses the latest state.

Suggested change
const toggleIsCollapsed = () => setIsCollapsed(!isCollapsed);
const toggleIsCollapsed = () => setIsCollapsed((prevIsCollapsed) => !prevIsCollapsed);

Copilot uses AI. Check for mistakes.
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.

4 participants