From 47c703aa4d7b6fab2dc94d5feb659fecfc2d7085 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Mon, 10 Nov 2025 17:15:40 -0500 Subject: [PATCH] refactor: migrate CopyableValue from MUI to Radix UI (#20261) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Migrates the `CopyableValue` component from Material-UI Tooltip to Radix UI Tooltip. ## Changes ### CopyableValue Component - **Removed unused prop**: `PopperProps` (never used in any call site) - **Renamed prop**: `placement` → `side` (Radix naming convention) - **Updated pattern**: Now uses TooltipProvider/Tooltip/TooltipTrigger/TooltipContent composition **Minimal API surface change:** - `placement` prop renamed to `side` (only affects 1 call site - already updated) - `PopperProps` removed (was never used) --------- Co-authored-by: Claude --- .../CopyableValue/CopyableValue.tsx | 86 +++++++++++++++---- site/src/modules/resources/SensitiveValue.tsx | 13 +-- .../EditOAuth2AppPageView.tsx | 6 +- site/src/pages/IconsPage/IconsPage.tsx | 32 ++----- 4 files changed, 85 insertions(+), 52 deletions(-) diff --git a/site/src/components/CopyableValue/CopyableValue.tsx b/site/src/components/CopyableValue/CopyableValue.tsx index 92cb6ec3c4..89d98e43e0 100644 --- a/site/src/components/CopyableValue/CopyableValue.tsx +++ b/site/src/components/CopyableValue/CopyableValue.tsx @@ -1,35 +1,91 @@ -import Tooltip, { type TooltipProps } from "@mui/material/Tooltip"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import { useClickable } from "hooks/useClickable"; import { useClipboard } from "hooks/useClipboard"; -import type { FC, HTMLAttributes } from "react"; +import { type FC, type HTMLAttributes, useState } from "react"; +import { cn } from "utils/cn"; + +type TooltipSide = "top" | "right" | "bottom" | "left"; interface CopyableValueProps extends HTMLAttributes { value: string; - placement?: TooltipProps["placement"]; - PopperProps?: TooltipProps["PopperProps"]; + side?: TooltipSide; } export const CopyableValue: FC = ({ value, - placement = "bottom-start", - PopperProps, + side = "bottom", children, + className, + role, + tabIndex, + onClick, + onKeyDown, + onKeyUp, ...attrs }) => { const { showCopiedSuccess, copyToClipboard } = useClipboard(); + const [tooltipOpen, setTooltipOpen] = useState(false); + const [isFocused, setIsFocused] = useState(false); const clickableProps = useClickable(() => { copyToClipboard(value); + setTooltipOpen(true); }); return ( - - - {children} - - + + { + // Always keep the tooltip open when in focus to handle issues when onOpenChange is unexpectedly false + if (!shouldBeOpen && isFocused) return; + setTooltipOpen(shouldBeOpen); + }} + > + + { + clickableProps.onClick(event); + onClick?.(event); + }} + onKeyDown={(event) => { + clickableProps.onKeyDown(event); + onKeyDown?.(event); + }} + onKeyUp={(event) => { + clickableProps.onKeyUp(event); + onKeyUp?.(event); + }} + onMouseEnter={() => { + setIsFocused(true); + setTooltipOpen(true); + }} + onMouseLeave={() => { + setTooltipOpen(false); + }} + onFocus={() => { + setIsFocused(true); + }} + onBlur={() => { + setTooltipOpen(false); + }} + > + {children} + + + + {showCopiedSuccess ? "Copied!" : "Click to copy"} + + + ); }; diff --git a/site/src/modules/resources/SensitiveValue.tsx b/site/src/modules/resources/SensitiveValue.tsx index b1ec1b4410..3a0cf65bed 100644 --- a/site/src/modules/resources/SensitiveValue.tsx +++ b/site/src/modules/resources/SensitiveValue.tsx @@ -32,7 +32,10 @@ export const SensitiveValue: FC = ({ value }) => { gap: 4, }} > - + {displayValue} @@ -52,14 +55,6 @@ export const SensitiveValue: FC = ({ value }) => { }; const styles = { - value: { - // 22px is the button width - width: "calc(100% - 22px)", - overflow: "hidden", - whiteSpace: "nowrap", - textOverflow: "ellipsis", - }, - button: css` color: inherit; `, diff --git a/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx b/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx index 73149d2a30..7e2c4e31bc 100644 --- a/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx @@ -150,20 +150,20 @@ export const EditOAuth2AppPageView: FC = ({
Client ID
- + {app.id}
Authorization URL
- + {app.endpoints.authorization}{" "}
Token URL
- + {app.endpoints.token}
diff --git a/site/src/pages/IconsPage/IconsPage.tsx b/site/src/pages/IconsPage/IconsPage.tsx index a15ee72114..a7574fced5 100644 --- a/site/src/pages/IconsPage/IconsPage.tsx +++ b/site/src/pages/IconsPage/IconsPage.tsx @@ -12,7 +12,6 @@ import { PageHeaderSubtitle, PageHeaderTitle, } from "components/PageHeader/PageHeader"; -import { Stack } from "components/Stack/Stack"; import { SearchIcon, XIcon } from "lucide-react"; import { type FC, type ReactNode, useMemo, useState } from "react"; import { @@ -79,13 +78,7 @@ const IconsPage: FC = () => { +

You can suggest a new icon by submitting a Pull Request to our public GitHub repository. Just keep in mind that it should be relevant to many Coder users, and redistributable under a @@ -124,12 +117,7 @@ const IconsPage: FC = () => { }, startAdornment: ( - + ), endAdornment: searchInputText && ( @@ -147,19 +135,13 @@ const IconsPage: FC = () => { }} /> - +

{searchedIcons.length === 0 && ( )} {searchedIcons.map((icon) => ( - - + +
{icon.url} { > {icon.description} - +
))} -
+
);