From 75bf770bdf623877d0f58e046274d09b85d15422 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Sun, 12 Oct 2025 15:16:31 +0000 Subject: [PATCH 1/6] refactor: migrate CopyableValue from MUI to Radix UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace MUI Tooltip with Radix Tooltip components and migrate Emotion CSS to Tailwind utility classes. This is part of the ongoing effort to standardize on Radix UI/shadcn components and Tailwind CSS. Changes: - Replace @mui/material/Tooltip with components/Tooltip/Tooltip (Radix) - Remove unused PopperProps (not used anywhere in codebase) - Rename placement prop to side to match Radix API - Replace Emotion css={{ cursor: "pointer" }} with Tailwind cursor-pointer class - Update single call site in IconsPage.tsx (placement → side) - Wrap in TooltipProvider as required by Radix Technical details: - Radix uses Provider/Tooltip/Trigger/Content pattern (more verbose but flexible) - Maintained backward compatibility for all other call sites (6 files) - No functional changes to component behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../CopyableValue/CopyableValue.tsx | 41 ++++++++++++------- site/src/pages/IconsPage/IconsPage.tsx | 19 ++++----- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/site/src/components/CopyableValue/CopyableValue.tsx b/site/src/components/CopyableValue/CopyableValue.tsx index 92cb6ec3c4e42..b455bd98eae21 100644 --- a/site/src/components/CopyableValue/CopyableValue.tsx +++ b/site/src/components/CopyableValue/CopyableValue.tsx @@ -1,19 +1,25 @@ -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"; +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, ...attrs }) => { const { showCopiedSuccess, copyToClipboard } = useClipboard(); @@ -22,14 +28,21 @@ export const CopyableValue: FC = ({ }); return ( - - - {children} - - + + + + + {children} + + + + {showCopiedSuccess ? "Copied!" : "Click to copy"} + + + ); }; diff --git a/site/src/pages/IconsPage/IconsPage.tsx b/site/src/pages/IconsPage/IconsPage.tsx index a15ee72114dc6..9f4eb25862df6 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 { @@ -147,19 +146,19 @@ const IconsPage: FC = () => { }} /> - {searchedIcons.length === 0 && ( )} {searchedIcons.map((icon) => ( - - + +
{icon.url} { > {icon.description} - +
))} -
+ ); From e199c5448d22026bd21c98cd13b9e60bd8509cd9 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Sun, 12 Oct 2025 20:56:11 +0000 Subject: [PATCH 2/6] refactor: update SensitiveValue to pass className instead of css --- site/src/components/CopyableValue/CopyableValue.tsx | 3 ++- site/src/modules/resources/SensitiveValue.tsx | 13 ++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/site/src/components/CopyableValue/CopyableValue.tsx b/site/src/components/CopyableValue/CopyableValue.tsx index b455bd98eae21..b8b072b3d22fc 100644 --- a/site/src/components/CopyableValue/CopyableValue.tsx +++ b/site/src/components/CopyableValue/CopyableValue.tsx @@ -7,6 +7,7 @@ import { import { useClickable } from "hooks/useClickable"; import { useClipboard } from "hooks/useClipboard"; import type { FC, HTMLAttributes } from "react"; +import { cn } from "utils/cn"; type TooltipSide = "top" | "right" | "bottom" | "left"; @@ -34,7 +35,7 @@ export const CopyableValue: FC = ({ {children} diff --git a/site/src/modules/resources/SensitiveValue.tsx b/site/src/modules/resources/SensitiveValue.tsx index b1ec1b4410e3e..3a0cf65bedafe 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; `, From 3c90fd2fb532748be772d92494ca73585f6f5b1e Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Mon, 13 Oct 2025 18:43:04 +0000 Subject: [PATCH 3/6] fix: improvements --- .../CopyableValue/CopyableValue.tsx | 30 ++++++++++++++++--- .../EditOAuth2AppPageView.tsx | 6 ++-- site/src/pages/IconsPage/IconsPage.tsx | 2 +- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/site/src/components/CopyableValue/CopyableValue.tsx b/site/src/components/CopyableValue/CopyableValue.tsx index b8b072b3d22fc..96d2c81cc1750 100644 --- a/site/src/components/CopyableValue/CopyableValue.tsx +++ b/site/src/components/CopyableValue/CopyableValue.tsx @@ -6,7 +6,7 @@ import { } 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, useRef, useState } from "react"; import { cn } from "utils/cn"; type TooltipSide = "top" | "right" | "bottom" | "left"; @@ -24,13 +24,35 @@ export const CopyableValue: FC = ({ ...attrs }) => { const { showCopiedSuccess, copyToClipboard } = useClipboard(); + const [tooltipOpen, setTooltipOpen] = useState(false); + const [showCopiedText, setShowCopiedText] = useState(false); + const prevCopiedRef = useRef(false); + const timeoutRef = useRef(undefined); + const clickableProps = useClickable(() => { copyToClipboard(value); + setTooltipOpen(true); }); + if (showCopiedSuccess !== prevCopiedRef.current) { + prevCopiedRef.current = showCopiedSuccess; + + if (showCopiedSuccess) { + setShowCopiedText(true); + } else { + setTooltipOpen(false); + clearTimeout(timeoutRef.current); + // showCopiedText should reset after tooltip is closed to avoid a flash of the text + timeoutRef.current = window.setTimeout( + () => setShowCopiedText(false), + 100, + ); + } + } + return ( - - + + = ({ - {showCopiedSuccess ? "Copied!" : "Click to copy"} + {showCopiedText ? "Copied!" : "Click to copy"} diff --git a/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx b/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx index 73149d2a30645..7e2c4e31bcd06 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 9f4eb25862df6..17b1e468cc9da 100644 --- a/site/src/pages/IconsPage/IconsPage.tsx +++ b/site/src/pages/IconsPage/IconsPage.tsx @@ -154,7 +154,7 @@ const IconsPage: FC = () => { )} {searchedIcons.map((icon) => ( - +
Date: Tue, 21 Oct 2025 23:01:45 +0000 Subject: [PATCH 4/6] fix: simplify and handle keyboard usage --- .../CopyableValue/CopyableValue.tsx | 49 ++++++++++--------- site/src/pages/IconsPage/IconsPage.tsx | 25 ++-------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/site/src/components/CopyableValue/CopyableValue.tsx b/site/src/components/CopyableValue/CopyableValue.tsx index 96d2c81cc1750..732f21689e033 100644 --- a/site/src/components/CopyableValue/CopyableValue.tsx +++ b/site/src/components/CopyableValue/CopyableValue.tsx @@ -6,7 +6,7 @@ import { } from "components/Tooltip/Tooltip"; import { useClickable } from "hooks/useClickable"; import { useClipboard } from "hooks/useClipboard"; -import { type FC, type HTMLAttributes, useRef, useState } from "react"; +import { type FC, type HTMLAttributes, useState } from "react"; import { cn } from "utils/cn"; type TooltipSide = "top" | "right" | "bottom" | "left"; @@ -25,45 +25,48 @@ export const CopyableValue: FC = ({ }) => { const { showCopiedSuccess, copyToClipboard } = useClipboard(); const [tooltipOpen, setTooltipOpen] = useState(false); - const [showCopiedText, setShowCopiedText] = useState(false); - const prevCopiedRef = useRef(false); - const timeoutRef = useRef(undefined); - + const [isFocused, setIsFocused] = useState(false); const clickableProps = useClickable(() => { copyToClipboard(value); setTooltipOpen(true); }); - if (showCopiedSuccess !== prevCopiedRef.current) { - prevCopiedRef.current = showCopiedSuccess; - - if (showCopiedSuccess) { - setShowCopiedText(true); - } else { - setTooltipOpen(false); - clearTimeout(timeoutRef.current); - // showCopiedText should reset after tooltip is closed to avoid a flash of the text - timeoutRef.current = window.setTimeout( - () => setShowCopiedText(false), - 100, - ); - } - } - return ( - + { + // Always keep the tooltip open when in focus to handle issues when onOpenChange is unexpectedly false + if (!next && isFocused) return; + setTooltipOpen(next); + }} + > { + setIsFocused(true); + setTooltipOpen(true); + }} + onMouseLeave={() => { + setTooltipOpen(false); + }} + onFocus={() => { + setIsFocused(true); + }} + onBlur={() => { + setTooltipOpen(false); + }} className={cn("cursor-pointer", className)} > {children} - {showCopiedText ? "Copied!" : "Click to copy"} + {showCopiedSuccess ? "Copied!" : "Click to copy"} diff --git a/site/src/pages/IconsPage/IconsPage.tsx b/site/src/pages/IconsPage/IconsPage.tsx index 17b1e468cc9da..8e43c89243aa4 100644 --- a/site/src/pages/IconsPage/IconsPage.tsx +++ b/site/src/pages/IconsPage/IconsPage.tsx @@ -78,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 @@ -123,12 +117,7 @@ const IconsPage: FC = () => { }, startAdornment: ( - + ), endAdornment: searchInputText && ( @@ -146,19 +135,13 @@ const IconsPage: FC = () => { }} /> -

+
{searchedIcons.length === 0 && ( )} {searchedIcons.map((icon) => ( -
+
{icon.url} Date: Sun, 26 Oct 2025 13:24:27 +0000 Subject: [PATCH 5/6] chore: cleanup --- .../CopyableValue/CopyableValue.tsx | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/site/src/components/CopyableValue/CopyableValue.tsx b/site/src/components/CopyableValue/CopyableValue.tsx index 732f21689e033..716846994ef1e 100644 --- a/site/src/components/CopyableValue/CopyableValue.tsx +++ b/site/src/components/CopyableValue/CopyableValue.tsx @@ -21,6 +21,11 @@ export const CopyableValue: FC = ({ side = "bottom", children, className, + role, + tabIndex, + onClick, + onKeyDown, + onKeyUp, ...attrs }) => { const { showCopiedSuccess, copyToClipboard } = useClipboard(); @@ -43,10 +48,23 @@ export const CopyableValue: FC = ({ > { + clickableProps.onClick(event); + onClick?.(event); + }} + onKeyDown={(event) => { + clickableProps.onKeyDown(event); + onKeyDown?.(event); + }} + onKeyUp={(event) => { + clickableProps.onKeyUp(event); + onKeyUp?.(event); + }} onMouseEnter={() => { setIsFocused(true); setTooltipOpen(true); @@ -60,7 +78,6 @@ export const CopyableValue: FC = ({ onBlur={() => { setTooltipOpen(false); }} - className={cn("cursor-pointer", className)} > {children} From 589a27a8e7d519938bfcf01717b5c078014bd51b Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Mon, 10 Nov 2025 22:07:37 +0000 Subject: [PATCH 6/6] chore: PR review updates --- site/src/components/CopyableValue/CopyableValue.tsx | 6 +++--- site/src/pages/IconsPage/IconsPage.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/src/components/CopyableValue/CopyableValue.tsx b/site/src/components/CopyableValue/CopyableValue.tsx index 716846994ef1e..89d98e43e0474 100644 --- a/site/src/components/CopyableValue/CopyableValue.tsx +++ b/site/src/components/CopyableValue/CopyableValue.tsx @@ -40,10 +40,10 @@ export const CopyableValue: FC = ({ { + onOpenChange={(shouldBeOpen) => { // Always keep the tooltip open when in focus to handle issues when onOpenChange is unexpectedly false - if (!next && isFocused) return; - setTooltipOpen(next); + if (!shouldBeOpen && isFocused) return; + setTooltipOpen(shouldBeOpen); }} > diff --git a/site/src/pages/IconsPage/IconsPage.tsx b/site/src/pages/IconsPage/IconsPage.tsx index 8e43c89243aa4..a7574fced567c 100644 --- a/site/src/pages/IconsPage/IconsPage.tsx +++ b/site/src/pages/IconsPage/IconsPage.tsx @@ -141,7 +141,7 @@ const IconsPage: FC = () => { )} {searchedIcons.map((icon) => ( -
+