feat(compactSelect): add section virtualization support#108394
feat(compactSelect): add section virtualization support#108394
Conversation
Replaces Form/JsonForm/accountEmailsFields with useScrapsForm + Zod validation. Uses useMutation + fetchMutation for the POST submission, invalidates the email list query on success, and resets the field after a successful add.
Remove the section gate from shouldVirtualize() so sectioned lists can now virtualize when total option count exceeds the threshold. Update height estimation to account for section headers and separators, and move SectionSeparator inside SectionWrap so measureElement captures full section height.
… refs Replace document.querySelector in HiddenSectionToggle with a React context-based ref map. SectionToggle registers its button element on mount and cleans up on unmount, so HiddenSectionToggle can find visible counterparts regardless of virtualization state.
Extract useVirtualizedItems to a shared module and wire it into GridList. GridListSection and GridListOption now accept virtualization props (ref, data-index). Move SectionSeparator inside SectionWrap in GridListSection for correct height measurement. Pass virtualized and showSectionHeaders through from List to GridList.
… changes Use conditional mergeRefs in GridListOption to preserve the original ref object when no external ref is provided, fixing focus management. Render Container wrappers only when virtualized to avoid DOM changes in the non-virtualized path. Fix separator elementType to match the actual div element.
Remove the hardcoded sizeLimit={25} fallback now that virtualization
handles rendering performance for large project lists.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| size={size} | ||
| isFirst={row.index === 0} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
GridListSection ignores showSectionHeaders prop causing height mismatch
Medium Severity
GridList accepts showSectionHeaders and passes it to useVirtualizedItems for height estimation, but never passes it to GridListSection. The section component always renders headers and separators regardless. This contrasts with ListBoxSection, which receives and respects showSectionHeaders. When showSectionHeaders is false, the virtualizer estimates zero height for headers but the DOM still renders them, causing a mismatch between estimated and actual section heights that can produce visual glitches (overlapping items or gaps) with virtualization enabled.
Additional Locations (1)
| }); | ||
| return longestChild ? getItemsWithKeys([longestChild]) : []; | ||
| } | ||
| return getItemsWithKeys([longestOption]); |
There was a problem hiding this comment.
Width measurement may pick wrong option across sections
Medium Severity
The maxBy call compares section header label lengths against option label/textValue lengths in a single pass. If a section header has the longest text, it "wins," and then only the longest child within that specific section is picked for width measurement. Options in other sections or at the top level that are actually wider get ignored, potentially resulting in a menu that's too narrow to display them.
| const SectionSeparatorInner = styled('div')` | ||
| border-top: solid 1px ${p => p.theme.tokens.border.secondary}; | ||
| margin: ${p => p.theme.space.xs} ${p => p.theme.space.lg}; | ||
| `; |
There was a problem hiding this comment.
Duplicate SectionSeparatorInner styled component across files
Low Severity
SectionSeparatorInner is identically defined in both gridList/section.tsx and listBox/section.tsx. There's already a shared SectionSeparator in styles.tsx (exported from the compactSelect package). This new component could be extracted to the shared styles module to avoid the duplication and risk of the two copies diverging.
Additional Locations (1)
Replace height="100%" with flex="1 1 0" and minHeight="0" on the scroll container. height: 100% didn't resolve to a constrained height because the parent Stack has no definite height, causing the scroll container to expand to fit all content and the virtualizer to render all items as "visible".
Add flex="1 1 0" to the Stack wrapper in control.tsx so it fills available space in StyledOverlay. This gives the scroll container's height: 100% a definite parent height to resolve against, enabling the virtualizer to correctly determine which items are visible.
|
Tried taking riptide for a spin, and the implemented solution doesn't actually work. |


Summary
Enable CompactSelect virtualization for lists with sections (grouped options). Previously,
shouldVirtualize()unconditionally returnedfalsewhen any section was present, forcing all sectioned lists to render every option in the DOM.Key changes:
shouldVirtualize()— it now counts total options across all sections. Add section-aware height estimation for@tanstack/react-virtual(sectionHeaderHeights,SECTION_SEPARATOR_HEIGHT). MoveSectionSeparatorinsideSectionWrapsomeasureElementcaptures the full section height including separatorsdocument.querySelectorDOM queries with a React context-based ref map (SectionToggleRefContext). Section toggle buttons register/deregister on mount/unmount, soHiddenSectionToggleworks regardless of whether sections are scrolled into viewuseVirtualizedItemsto a shared module, wire it intoGridList,GridListSection, andGridListOption. Non-virtualized path preserves identical DOM structure to avoid regressionssizeLimit={25}fallback now that virtualization handles rendering performance for large project lists