From bf5669880974d92a5398211cc47e1c014d35b31c Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Thu, 2 Jul 2026 12:51:30 -0700 Subject: [PATCH 1/3] fix: rework user group management with an accessible selector Replace the toggle-checkbox group list and the separate primary-group dropdown with a single accessible control: a combobox that adds groups to the membership list, and a radio group where each member row selects the primary group and offers a button to revoke membership. The old list rendered each row as role="option" wrapping a Radix checkbox, producing conflicting roles, double tab stops, and a dead decorative checkbox. It also scaled poorly as the number of groups grew. Generalize the orphaned base ComboBox with a placeholder prop and match its height to the standard Input. Remove UserGroup and PrimaryGroup. --- apps/web/src/base/ComboBox.tsx | 9 +- .../web/src/users/components/PrimaryGroup.tsx | 55 ------ apps/web/src/users/components/UserDetail.tsx | 8 +- apps/web/src/users/components/UserGroup.tsx | 31 ---- apps/web/src/users/components/UserGroups.tsx | 164 +++++++++++++----- .../__tests__/PrimaryGroup.test.tsx | 80 --------- .../components/__tests__/UserDetail.test.tsx | 25 ++- .../components/__tests__/UserGroup.test.tsx | 44 ----- .../components/__tests__/UserGroups.test.tsx | 148 ++++++++++++++++ 9 files changed, 299 insertions(+), 265 deletions(-) delete mode 100644 apps/web/src/users/components/PrimaryGroup.tsx delete mode 100644 apps/web/src/users/components/UserGroup.tsx delete mode 100644 apps/web/src/users/components/__tests__/PrimaryGroup.test.tsx delete mode 100644 apps/web/src/users/components/__tests__/UserGroup.test.tsx create mode 100644 apps/web/src/users/components/__tests__/UserGroups.test.tsx diff --git a/apps/web/src/base/ComboBox.tsx b/apps/web/src/base/ComboBox.tsx index d6e6dd029..42ff86ca4 100644 --- a/apps/web/src/base/ComboBox.tsx +++ b/apps/web/src/base/ComboBox.tsx @@ -10,16 +10,17 @@ function ComboboxTriggerButton({ TriggerButtonProps, selectedItem, renderRow, + placeholder, id, }) { return ( ); @@ -41,6 +42,8 @@ type ComboBoxProps = { onFilter: (term: string) => void; onChange: (item: unknown) => void; itemToString?: (item: unknown) => string; + /** Text shown on the trigger when nothing is selected */ + placeholder?: string; id?: string; }; @@ -56,6 +59,7 @@ export default function ComboBox({ onFilter, onChange, itemToString, + placeholder = "Select…", id, }: ComboBoxProps) { itemToString = itemToString || defaultToString; @@ -87,6 +91,7 @@ export default function ComboBox({ TriggerButtonProps={getToggleButtonProps()} selectedItem={selectedItem} renderRow={renderRow} + placeholder={placeholder} id={id} />