16964 frontend [ ui] Standardize dropdown options - #321
16964 frontend [ ui] Standardize dropdown options#321aicarma-artyom-maslov wants to merge 10 commits into
Conversation
8f44396
| )); | ||
| const menuContent = renderMenuContent?.(renderedContent) || renderedContent; | ||
| const isWide = Array.isArray(options) && options.length > 0 && options.every((option) => option.size === 'lg'); | ||
| const menu = menuContent ? ( |
There was a problem hiding this comment.
🟠 High Dropdown/Dropdown.tsx:91
Submenus are clipped and inaccessible: DropdownOptions renders nested Dropdown components inside the parent menu wrapper, and Popper positions them at placement="right-start" so they extend beyond the parent menu's bounds. Because the parent menu wrapper (styles['dropdown-menu']) applies overflow: hidden, the submenu fly-out is cut off and subOptions/customSubOption content can't be seen or interacted with.
The previous implementation used UncontrolledDropdown/DropdownMenu from reactstrap, which portaled each submenu independently. Consider either removing the overflow: hidden constraint on .dropdown-menu or portaling submenu Dropdown elements out of the parent menu so Popper can position them without clipping.
Also found in 1 other location(s)
frontend/src/public/components/UI/Dropdown/Dropdown.css:34
.dropdown-menunow setsoverflow: hidden, but nested submenuDropdowns are rendered inside the parent menu and Popper places them atright-start. The submenu therefore extends outside the parent menu's bounds and is clipped, makingsubOptions/customSubOptionmenus inaccessible.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @frontend/src/public/components/UI/Dropdown/Dropdown.tsx around line 91:
Submenus are clipped and inaccessible: `DropdownOptions` renders nested `Dropdown` components inside the parent menu wrapper, and Popper positions them at `placement="right-start"` so they extend beyond the parent menu's bounds. Because the parent menu wrapper (`styles['dropdown-menu']`) applies `overflow: hidden`, the submenu fly-out is cut off and `subOptions`/`customSubOption` content can't be seen or interacted with.
The previous implementation used `UncontrolledDropdown`/`DropdownMenu` from `reactstrap`, which portaled each submenu independently. Consider either removing the `overflow: hidden` constraint on `.dropdown-menu` or portaling submenu `Dropdown` elements out of the parent menu so Popper can position them without clipping.
Also found in 1 other location(s):
- frontend/src/public/components/UI/Dropdown/Dropdown.css:34 -- `.dropdown-menu` now sets `overflow: hidden`, but nested submenu `Dropdown`s are rendered inside the parent menu and Popper places them at `right-start`. The submenu therefore extends outside the parent menu's bounds and is clipped, making `subOptions`/`customSubOption` menus inaccessible.
| dropdownRef={ref} | ||
| placement={placement} | ||
| className={containerClassName} | ||
| menuClassName={styles['content']} |
There was a problem hiding this comment.
🟠 High DropdownArea/DropdownArea.tsx:22
The Add Guest form in GuestsController is clipped: passing styles['content'] as menuClassName merges it with the .dropdown-menu class, which sets a fixed 19.2rem width and overflow: hidden on the popover. That container is only sized for narrow option menus, not the wider 272px guest form (plus 1.6rem padding), so content overflows and is cut off instead of being sized to its contents. Consider using a different prop (or overriding the width/overflow) so DropdownArea content retains its natural size.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @frontend/src/public/components/UI/DropdownArea/DropdownArea.tsx around line 22:
The Add Guest form in `GuestsController` is clipped: passing `styles['content']` as `menuClassName` merges it with the `.dropdown-menu` class, which sets a fixed `19.2rem` width and `overflow: hidden` on the popover. That container is only sized for narrow option menus, not the wider `272px` guest form (plus `1.6rem` padding), so content overflows and is cut off instead of being sized to its contents. Consider using a different prop (or overriding the width/overflow) so `DropdownArea` content retains its natural size.
07f20fe to
9d4598d
Compare
1. Release notes
Standardized dropdown usage by separating value selection from action menus.
DropdownListandUsersDropdownuse the existingreact-selectselection foundation, whileDropdownandModifyDropdownremain Popper-based action menus. Fixed the display regressions this split introduced: filter/sorting menus are aligned with their pill again, submenus drop under their row as on production, and the selection menu no longer slides under page content or collapses onto a narrow toggle.2. Context
DropdownSurface,DropdownControl, andDropdownOptionfor shared visuals; restoresreact-selectas the selection engine forDropdownList; keepsDropdownas the Popper-based action-menu engine.3. Solution & Implementation Details
Selection engine (
DropdownList/UsersDropdown)DropdownListback ontoreact-selectfor value selection, preserving the branch’s shared control, menu surface, option row, static menu, search, grouped options, controlled/uncontrolled values, Formik integration, and action metadata behavior.UsersDropdownas a domain preset overDropdownList, preserving avatars, checkboxes, Invite, All Users, select/deselect callbacks, and invited-user updates.componentsobject once per instance and fed it the current render’s data through a ref. A fresh object per render changed the component identities, which madereact-selectremount the whole menu mid-interaction and detach the option node betweenpointerdownandclick— that broke role selection inCreateUserModal.OutsideClickHandler:react-selectonly closes its menu when its ownInputblurs, and the customControlrenders a toggle button instead of that input.backspaceRemovesValue={false}. The in-menu search field is a plain input, so Backspace on an empty query used to pop the last selected value and emit an undeclaredpop-valueaction.role="listbox"witharia-multiselectable, the toggle asaria-haspopup="listbox", and gave the keyboard-focused option a visible highlight viaisFocused(the previousrole="menu"+role="option"pairing was invalid).stylesfactory, which is the only layer that can reach thereact-selectwrapper:z-index: 1000to match the baseDropdown(the default1slid the menu under page content),position: relativefor static menus, and a transparent background for the compactsmcontrol, which paints its own chip.width(30.4remforsm,100%forlg) withoverflow: hidden. The previous--dropdown-menu-widthcustom property is only read byDropdown.css, so it went dead onceDropdownListstopped composingDropdown, and the compact menu collapsed onto the toggle width.Filters and sorting (
SelectMenu/FilterSelect)direction="right", which resolved to abottom-endPopper placement and pushed the menu leftwards out of alignment. On production the equivalent bootstrap class sat on the container rather than the menu, so those menus have always been left-aligned..containerand aligned the menu with compensating margins (margin-top: 1.2rem,margin-left: -1.6rem) instead of moving the padding onto the toggle..dropdown-togglecarriesall: unset, which wipes padding declared on the toggle depending on stylesheet order — that flattened the workflow toolbar pills from 32px to 20px.searchPlaceholderto the task template and step filters, which rendered an empty search strip.Action menus (
Dropdown/ModifyDropdown)Dropdown/ModifyDropdownas action menus for edit/delete/clone/profile-style commands, confirmation items, submenus, outside-click dismissal, Escape dismissal, and Popper placement.placement="bottom-start") and line up with the parent menu, matching production; the previousright-startfly-out landed outside it.box-sizing: border-boxto.dropdown-toggle.all: unsetresets it tocontent-box, so a submenu row styled as a full-width dropdown item overflowed its menu by the item padding — visible as the highlight sticking out past the Snooze row.Storybook
UI/Dropdowns/Dropdown(action menus, presets, building blocks) andUI/Dropdowns/Select(value selection plus theFilterSelect,SelectMenuandUsersDropdownpresets).roomparameter, so an opened menu is never clipped or scrolled to, and single controls stay narrow viastoryWidth.playfunction, and variant galleries use a fixed grid so cases line up.DropdownListMenu.tsx.4. What to Test
4.1 Preconditions
4.2 Positive Scenarios / Testing Report
DropdownListform field and select an option.DropdownList, select and deselect options.DropdownList/UsersDropdown.Dropdown/ModifyDropdownand click Edit/Delete/Clone.DropdownandSelectstory groups.4.3 Negative Scenarios & Edge Cases
onClick.4.4 Verification Points
DropdownListimports and usesreact-selectfor value selection.UsersDropdownremains aDropdownListpreset, not an action menu.Dropdownremains Popper-based and is not used as the value-select engine.listboxwithoptionchildren; the action menu is amenuwithmenuitem-family children.Dropdowntoggle class, becauseall: unsetcan wipe it depending on stylesheet order.DropdownandSelect, and each covers its variants.DropdownSurface,DropdownControl,DropdownOption) are still used where appropriate.4.5 API Verification
4.6 What Was NOT Tested
Dropdownstacking order, but could not be reproduced in Storybook and was not re-checked in the running app.smcontrol background transparent; it was not repeated afterwards. Type-check, lint and the full Jest suite were re-run after that tweak.npm run build-storybookwas not re-run after the story consolidation.5. Affected Areas
DropdownandSelect, with reserved height, auto-opened menus and grid galleries.DropdownListregression tests.6. Unit Tests
npx jest --ci src/public— 1517 tests in 213 suites, 15 snapshots.npx tsc --noEmit --skipLibCheck -p tsconfig.json— no project-source errors.npx eslint src/public/components/UI src/public/layout --ext .ts,.tsx. Story files are covered by the repo’s ESLint ignore pattern.npx stylelint "src/public/components/UI/**/*.css".npx tsc --noEmitwithout--skipLibCheckfails on pre-existing dependency type conflicts innode_modules(@types/enzyme/ React duplicate declarations), before surfacing project-file errors.7. Commits
f798a908—16964 fix(ui): standardize dropdown options608efa60—16964 refactor(ui): compose dropdown option variants8f443964—16964 refactor(ui): share compact dropdown control7b4262c5—16964 fix(ui): address dropdown accessibility review7891f636—16964 refactor(ui): unify dropdown foundations4517c278—16964 refactor(ui): drop react-select from dropdowns9d4598d5—16964 fix(ui): address dropdown review findings7b350a2d—16964 fix(ui): restore multi-select reset and single-select change action9c016618—16964 fix(ui): close nested submenus explicitly on option selecta16f7e66—16964 fix(ui): fix dropdown menu display and consolidate dropdown storiesNote
[!NOTE]
Replace react-select with custom dropdown components across the UI
DropdownListto use new internalDropdown,DropdownControl,DropdownSurface, andDropdownListMenucomponents instead of react-select, adding support for grouped options, built-in search, controlled/uncontrolled selection, and multi-select.Dropdownto use react-popper for positioning and native<button>elements with ARIA attributes, closing on outside click and Escape key; submenus now nestDropdowncomponents.DropdownControl,DropdownSurface, andDropdownListMenuas reusable shared primitives consumed byDropdownList,DropdownArea,DropdownButton,FilterSelect, andSelectMenu.UsersDropdown,FilterSelect,SelectMenu,DropdownButton, andDropdownAreato consume the new APIs, replacing reactstrap and react-select dependencies.--dropdown-list-control-min-height,--dropdown-menu-width, etc.) across multiple feature stylesheets.DropdownList.onChangenow emits{action, option}instead of react-select'sActionMeta, and multi-select no longer renders chips in the control.Changes since #321 opened
DropdownList.handleSelecthandler to prevent deselection in single-select mode [7b350a2]FilterSelectMenucomponent to render reset entry in multi-select mode [7b350a2]Dropdowncomponent [7b350a2]DropdownOptionscomponent to use render-prop pattern for submenu rendering that invokes bothcloseSubmenu()andcloseDropdown()callbacks when a nested option is selected [9c01661]Dropdowntest suite verifying that selecting a deeply nested option closes all menu levels [9c01661]DropdownListcomponent to usereact-selectlibrary instead of custom dropdown implementation [a16f7e6]Dropdown.stories.tsxandSelect.stories.tsx[a16f7e6]FilterSelectandSelectMenucomponents and adjusted dropdown menu margins inSelect.css[a16f7e6]FilterSelectcomponents inTasksLayout.TasksLayoutComponent[a16f7e6]Macroscope summarized 9d4598d.