From 4064b602de2e3da8e61b149f3d7f243bab15eac0 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 16 Apr 2026 19:48:08 +0700 Subject: [PATCH] chore: update design of add users dialog (#24287) update design to match Figma design Figma: https://www.figma.com/design/klGTlHSPQwI4KBvAMdebrx/Customer-Usage-Controls-for-AI-Governance-Add-On?node-id=448-9898&m=dev Before: Screenshot 2026-04-13 at 16 48 32 After: Screenshot 2026-04-13 at 16 45 55 --- .../MultiUserSelect/MultiUserSelect.tsx | 171 ++++++++++-------- .../src/pages/GroupsPage/GroupMembersPage.tsx | 84 ++++++--- 2 files changed, 153 insertions(+), 102 deletions(-) diff --git a/site/src/components/MultiUserSelect/MultiUserSelect.tsx b/site/src/components/MultiUserSelect/MultiUserSelect.tsx index fa73ecec31..c9f2407db2 100644 --- a/site/src/components/MultiUserSelect/MultiUserSelect.tsx +++ b/site/src/components/MultiUserSelect/MultiUserSelect.tsx @@ -13,18 +13,7 @@ import { AvatarDataSkeleton } from "#/components/Avatar/AvatarDataSkeleton"; import { Checkbox } from "#/components/Checkbox/Checkbox"; import { EmptyState } from "#/components/EmptyState/EmptyState"; import { SearchField } from "#/components/SearchField/SearchField"; -import { - Table, - TableBody, - TableCell, - TableRow, -} from "#/components/Table/Table"; -import { - TableLoaderSkeleton, - TableRowSkeleton, -} from "#/components/TableLoader/TableLoader"; import { useDebouncedFunction } from "#/hooks/debounce"; -import { useClickableTableRow } from "#/hooks/useClickableTableRow"; import { cn } from "#/utils/cn"; import { prepareQuery } from "#/utils/filters"; @@ -120,9 +109,9 @@ const InnerMultiSelect = ({ ); return ( -
+
{ @@ -136,17 +125,22 @@ const InnerMultiSelect = ({ }} placeholder="Search users..." /> -
- - +
+
+
{ + event.stopPropagation(); + }} + > - -
+
+
); @@ -167,11 +161,9 @@ const UsersTable = ({ }: UsersTable) => { if (error) { return ( - - - - - +
+ +
); } @@ -181,66 +173,72 @@ const UsersTable = ({ if (users.length === 0) { return ( - - - - - +
+ +
); } - return users.map((user) => { - const checked = selected.some((u) => userMatches(u, user)); - return ( - - -
- { - e.stopPropagation(); - }} - onCheckedChange={(checked) => { - onChange(user, Boolean(checked)); - }} - aria-label={`Select user ${user.username}`} - /> - -
-
-
- ); - }); + return ( +
+ {users.map((user, index) => { + const checked = selected.some((u) => userMatches(u, user)); + return ( + +
+ { + e.stopPropagation(); + }} + onCheckedChange={(checked) => { + onChange(user, Boolean(checked)); + }} + aria-label={`Select user ${user.username}`} + /> + +
+
+ ); + })} +
+ ); }; const TableLoader: FC = () => { + const skeletonRows = Array.from({ length: 6 }, (_, index) => index); + return ( - - - -
+
+ {skeletonRows.map((row) => ( +
+
- - - +
+ ))} +
); }; interface UserRowProps { checked: boolean; children?: ReactNode; + isFirst: boolean; + isLast: boolean; onChange: (user: T, checked: boolean) => void; user: T; } @@ -248,23 +246,40 @@ interface UserRowProps { const UserRow = ({ checked, children, + isFirst, + isLast, onChange, user, }: UserRowProps) => { - const clickableProps = useClickableTableRow({ - onClick: () => onChange(user, !checked), - }); return ( - div]:ring-1 hover:[&>div]:ring-inset hover:[&>div]:ring-border-hover", + checked + ? "[&>div]:bg-surface-secondary hover:[&>div]:bg-surface-secondary" + : undefined, + )} + onClick={() => onChange(user, !checked)} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + onChange(user, !checked); + } + }} > - {children} - +
+ {children} +
+
); }; diff --git a/site/src/pages/GroupsPage/GroupMembersPage.tsx b/site/src/pages/GroupsPage/GroupMembersPage.tsx index e42734c26b..0c4cad6183 100644 --- a/site/src/pages/GroupsPage/GroupMembersPage.tsx +++ b/site/src/pages/GroupsPage/GroupMembersPage.tsx @@ -14,7 +14,12 @@ import type { import { Avatar } from "#/components/Avatar/Avatar"; import { AvatarData } from "#/components/Avatar/AvatarData"; import { Button } from "#/components/Button/Button"; -import { ConfirmDialog } from "#/components/Dialogs/ConfirmDialog/ConfirmDialog"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogTitle, +} from "#/components/Dialog/Dialog"; import { DropdownMenu, DropdownMenuContent, @@ -26,6 +31,7 @@ import { UsersFilter } from "#/components/Filter/UsersFilter"; import { LastSeen } from "#/components/LastSeen/LastSeen"; import { MultiMemberSelect } from "#/components/MultiUserSelect/MultiUserSelect"; import { PaginationContainer } from "#/components/PaginationWidget/PaginationContainer"; +import { Spinner } from "#/components/Spinner/Spinner"; import { Table, TableBody, @@ -134,17 +140,33 @@ const AddUsersDialog: FC = ({ const [selected, setSelected] = useState( [], ); + const closeDialog = () => { + setAddUserDialogOpen(false); + setFilter(""); + setSelected([]); + }; + return ( <> - { + if (!open) { + closeDialog(); + } + }} + > + + + Add user(s) + = ({ }} selected={selected} /> - } - hideCancel={false} - cancelText="Cancel" - confirmText="Add users" - onClose={() => setAddUserDialogOpen(false)} - onConfirm={async () => { - try { - setSubmitting(true); - await onSubmit(selected); - setAddUserDialogOpen(false); - } catch (error) { - toast.error(getErrorMessage(error, "Failed to add members."), { - description: getErrorDetail(error), - }); - } finally { - setSubmitting(false); - } - }} - /> + + + + + + ); };