Problem
Five toolbar components share nearly identical dropdown filter logic (menu toggle, open/close state, keyboard handling, click-outside detection, selection callbacks). This results in ~2500 lines of duplicated code across:
| File |
Lines |
Filters |
console/src/app/Actions/AuditLogs/AuditLogsTableToolbar.tsx |
609 |
Account, Provider, Action, Result, Requester |
console/src/app/Actions/Scheduler/components/ActionsToolBar.tsx |
731 |
Account, Provider, Action, Status |
console/src/app/Clusters/components/ClustersTableToolbar.tsx |
468 |
Cluster Name, Account Name, Status, Provider |
console/src/app/Nodes/components/NodesTableToolbar.tsx |
432 |
Instance Name, Provider, Status |
console/src/app/Accounts/components/AccountsToolbar.tsx |
313 |
Account Name, Provider |
Each toolbar repeats the same pattern per dropdown filter:
- State:
const [isMenuOpen, setIsMenuOpen] = useState(false)
- Refs:
toggleRef, menuRef, containerRef (via usePopperContainer)
- Effect for keyboard/click-outside handlers (using refs to avoid stale closures)
- Effect to register/cleanup
window event listeners
- Toggle click handler with
stopPropagation + setTimeout focus
- Select handler (single-select or multi-select with badge count)
- JSX:
MenuToggle + Menu + MenuContent + MenuList + MenuItem + Popper
This pattern is repeated 2-4 times per toolbar, totaling ~15 nearly identical instances across the codebase.
Proposed Solution
Option A: Reusable FilterMenuSelect component
Create console/src/app/components/common/FilterMenuSelect.tsx:
interface FilterMenuSelectProps<T extends string> {
label: string; // "Filter by provider"
options: { id: T; label: string }[];
selected: T | T[] | null; // single or multi-select
onSelect: (value: T) => void;
onClear?: () => void;
isMulti?: boolean; // multi-select with badge count
width?: string; // default "200px"
}
This component encapsulates:
- All open/close state management
- Keyboard (Escape/Tab) and click-outside handlers
Popper positioning via usePopperContainer
- Single-select (close on pick) vs multi-select (stay open, show badge) behavior
- The full
MenuToggle → Popper → Menu → MenuList → MenuItem JSX tree
Option B: useFilterMenu() hook + slim wrapper
If the JSX customization varies too much between toolbars, extract a hook instead:
const { isOpen, toggleRef, menuRef, containerRef, containerElement, onToggleClick, getSortParams } = useFilterMenu();
This handles state, refs, and event listeners — each toolbar still renders its own JSX but without the boilerplate.
Recommendation
Option A covers all current use cases. The only variation across toolbars is the list of options and single vs multi-select — both handled by props. Start with one toolbar (e.g., AccountsToolbar — smallest at 313 lines), validate, then migrate the others.
Expected Impact
- ~1500-1800 lines removed across 5 files
- Each toolbar reduced to ~50-150 lines (just filter composition + search inputs)
- Single place to fix menu accessibility or behavior bugs
Files to Modify
- New:
console/src/app/components/common/FilterMenuSelect.tsx
- Modify: All 5 toolbar files listed above
- Possibly modify:
console/src/app/hooks/usePopperContainer.ts (may be absorbed into the new component)
Problem
Five toolbar components share nearly identical dropdown filter logic (menu toggle, open/close state, keyboard handling, click-outside detection, selection callbacks). This results in ~2500 lines of duplicated code across:
console/src/app/Actions/AuditLogs/AuditLogsTableToolbar.tsxconsole/src/app/Actions/Scheduler/components/ActionsToolBar.tsxconsole/src/app/Clusters/components/ClustersTableToolbar.tsxconsole/src/app/Nodes/components/NodesTableToolbar.tsxconsole/src/app/Accounts/components/AccountsToolbar.tsxEach toolbar repeats the same pattern per dropdown filter:
const [isMenuOpen, setIsMenuOpen] = useState(false)toggleRef,menuRef,containerRef(viausePopperContainer)windowevent listenersstopPropagation+setTimeoutfocusMenuToggle+Menu+MenuContent+MenuList+MenuItem+PopperThis pattern is repeated 2-4 times per toolbar, totaling ~15 nearly identical instances across the codebase.
Proposed Solution
Option A: Reusable
FilterMenuSelectcomponentCreate
console/src/app/components/common/FilterMenuSelect.tsx:This component encapsulates:
Popperpositioning viausePopperContainerMenuToggle→Popper→Menu→MenuList→MenuItemJSX treeOption B:
useFilterMenu()hook + slim wrapperIf the JSX customization varies too much between toolbars, extract a hook instead:
This handles state, refs, and event listeners — each toolbar still renders its own JSX but without the boilerplate.
Recommendation
Option A covers all current use cases. The only variation across toolbars is the list of options and single vs multi-select — both handled by props. Start with one toolbar (e.g.,
AccountsToolbar— smallest at 313 lines), validate, then migrate the others.Expected Impact
Files to Modify
console/src/app/components/common/FilterMenuSelect.tsxconsole/src/app/hooks/usePopperContainer.ts(may be absorbed into the new component)