From 014693ba34266c5241d46cf82b2bc2cf1c3a6573 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Wed, 4 Feb 2026 13:08:10 +1100 Subject: [PATCH] feat: refactor `` (#21809) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull-request takes our `` component and converts it to a ``. This is done so that we can more easily standardise the content among multiple Dropdown's, and as an added bonus helps us to remove MUI dependencies (win win). > [!NOTE] > I removed the avatar here whilst we debate internally on how we show the user account. This differs from the screenshot below 🙂 | Old | New | | --- | --- | | | | --- site/src/components/Popover/Popover.tsx | 2 - .../Navbar/UserDropdown/UserDropdown.tsx | 24 +- .../UserDropdown/UserDropdownContent.test.tsx | 41 ++-- .../UserDropdown/UserDropdownContent.tsx | 227 +++++++----------- 4 files changed, 114 insertions(+), 180 deletions(-) diff --git a/site/src/components/Popover/Popover.tsx b/site/src/components/Popover/Popover.tsx index 91ee43115d..3f69bdfa32 100644 --- a/site/src/components/Popover/Popover.tsx +++ b/site/src/components/Popover/Popover.tsx @@ -18,8 +18,6 @@ export const Popover = PopoverPrimitive.Root; export const PopoverTrigger = PopoverPrimitive.Trigger; -export const PopoverClose = PopoverPrimitive.PopoverClose; - export const PopoverContent = forwardRef< ElementRef, ComponentPropsWithoutRef diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx index 662a661e40..6b1bea9062 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx @@ -1,10 +1,10 @@ import type * as TypesGen from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/Popover/Popover"; + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from "components/DropdownMenu/DropdownMenu"; import type { FC } from "react"; import { UserDropdownContent } from "./UserDropdownContent"; @@ -22,28 +22,24 @@ export const UserDropdown: FC = ({ onSignOut, }) => { return ( - - + + - + - e.preventDefault()} - > + - - + + ); }; diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx index a134d706d1..64a44481a4 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx @@ -1,20 +1,31 @@ import { MockUserOwner } from "testHelpers/entities"; import { render, waitForLoaderToBeRemoved } from "testHelpers/renderHelpers"; import { screen } from "@testing-library/react"; -import { Popover } from "components/Popover/Popover"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from "components/DropdownMenu/DropdownMenu"; import { Language, UserDropdownContent } from "./UserDropdownContent"; +const renderUserDropdownContent = (props: { onSignOut: () => void }) => { + return render( + + Open + + + + , + ); +}; + describe("UserDropdownContent", () => { it("has the correct link for the account item", async () => { - render( - - - , - ); + renderUserDropdownContent({ onSignOut: vi.fn() }); await waitForLoaderToBeRemoved(); const link = screen.getByText(Language.accountLabel).closest("a"); @@ -27,15 +38,7 @@ describe("UserDropdownContent", () => { it("calls the onSignOut function", async () => { const onSignOut = vi.fn(); - render( - - - , - ); + renderUserDropdownContent({ onSignOut }); await waitForLoaderToBeRemoved(); screen.getByText(Language.signOutLabel).click(); expect(onSignOut).toBeCalledTimes(1); diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.tsx index 44529260c8..79c08b9d31 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.tsx @@ -1,22 +1,18 @@ -import { - type CSSObject, - css, - type Interpolation, - type Theme, -} from "@emotion/react"; -import Divider from "@mui/material/Divider"; -import MenuItem from "@mui/material/MenuItem"; import type * as TypesGen from "api/typesGenerated"; -import { CopyButton } from "components/CopyButton/CopyButton"; -import { PopoverClose } from "components/Popover/Popover"; -import { Stack } from "components/Stack/Stack"; +import { + DropdownMenuItem, + DropdownMenuSeparator, +} from "components/DropdownMenu/DropdownMenu"; import { Tooltip, TooltipContent, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import { useClipboard } from "hooks/useClipboard"; import { + CheckIcon, CircleUserIcon, + CopyIcon, LogOutIcon, MonitorDownIcon, SquareArrowOutUpRightIcon, @@ -44,153 +40,94 @@ export const UserDropdownContent: FC = ({ supportLinks, onSignOut, }) => { + const { showCopiedSuccess, copyToClipboard } = useClipboard(); + return ( -
- - {user.username} - {user.email} - - - - - - - - - Install CLI - - - - - - - - - {Language.accountLabel} - - - - - - - {Language.signOutLabel} - - - {supportLinks && ( + <> + + +
+ {user.username} + {user.email} +
+ +
+ + + + + Install CLI + + + + + + Account + + + + + Sign Out + + {supportLinks && supportLinks.length > 0 && ( <> - + {supportLinks.map((link) => ( - - - - {link.icon && ( - - )} - {link.name} - - - + + + {link.icon && } + {link.name} + + ))} )} - - - - - - + + + + - {buildInfo?.version} + {buildInfo?.version} + + + + Browse the source code + + {buildInfo?.deployment_id && ( + + + { + e.preventDefault(); + copyToClipboard(buildInfo.deployment_id); + }} + > + {buildInfo.deployment_id} + {showCopiedSuccess ? ( + + ) : ( + + )} + - Browse the source code + + {showCopiedSuccess ? "Copied!" : "Copy deployment ID"} + - - {buildInfo?.deployment_id && ( -
- - - - {buildInfo.deployment_id} - - - - Deployment Identifier - - - -
- )} - -
{Language.copyrightText}
-
-
+ )} + + {Language.copyrightText} + + ); }; - -const styles = { - info: (theme) => [ - theme.typography.body2 as CSSObject, - { - padding: 20, - }, - ], - userName: { - fontWeight: 600, - }, - userEmail: (theme) => ({ - color: theme.palette.text.secondary, - width: "100%", - textOverflow: "ellipsis", - overflow: "hidden", - }), - link: { - textDecoration: "none", - color: "inherit", - }, - menuItem: (theme) => css` - gap: 20px; - padding: 8px 20px; - - &:hover { - background-color: ${theme.palette.action.hover}; - transition: background-color 0.3s ease; - } - `, - menuItemText: { - fontSize: 14, - }, - footerText: (theme) => css` - font-size: 12px; - text-decoration: none; - color: ${theme.palette.text.secondary}; - display: flex; - align-items: center; - gap: 4px; - - & svg { - width: 12px; - height: 12px; - } - `, - buildInfo: (theme) => ({ - color: theme.palette.text.primary, - }), -} satisfies Record>;