Skip to content

Commit 47c703a

Browse files
jaaydenhclaude
andauthored
refactor: migrate CopyableValue from MUI to Radix UI (#20261)
## 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 <noreply@anthropic.com>
1 parent f2a1a7e commit 47c703a

File tree

4 files changed

+85
-52
lines changed

4 files changed

+85
-52
lines changed
Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,91 @@
1-
import Tooltip, { type TooltipProps } from "@mui/material/Tooltip";
1+
import {
2+
Tooltip,
3+
TooltipContent,
4+
TooltipProvider,
5+
TooltipTrigger,
6+
} from "components/Tooltip/Tooltip";
27
import { useClickable } from "hooks/useClickable";
38
import { useClipboard } from "hooks/useClipboard";
4-
import type { FC, HTMLAttributes } from "react";
9+
import { type FC, type HTMLAttributes, useState } from "react";
10+
import { cn } from "utils/cn";
11+
12+
type TooltipSide = "top" | "right" | "bottom" | "left";
513

614
interface CopyableValueProps extends HTMLAttributes<HTMLSpanElement> {
715
value: string;
8-
placement?: TooltipProps["placement"];
9-
PopperProps?: TooltipProps["PopperProps"];
16+
side?: TooltipSide;
1017
}
1118

1219
export const CopyableValue: FC<CopyableValueProps> = ({
1320
value,
14-
placement = "bottom-start",
15-
PopperProps,
21+
side = "bottom",
1622
children,
23+
className,
24+
role,
25+
tabIndex,
26+
onClick,
27+
onKeyDown,
28+
onKeyUp,
1729
...attrs
1830
}) => {
1931
const { showCopiedSuccess, copyToClipboard } = useClipboard();
32+
const [tooltipOpen, setTooltipOpen] = useState(false);
33+
const [isFocused, setIsFocused] = useState(false);
2034
const clickableProps = useClickable<HTMLSpanElement>(() => {
2135
copyToClipboard(value);
36+
setTooltipOpen(true);
2237
});
2338

2439
return (
25-
<Tooltip
26-
title={showCopiedSuccess ? "Copied!" : "Click to copy"}
27-
placement={placement}
28-
PopperProps={PopperProps}
29-
>
30-
<span {...attrs} {...clickableProps} css={{ cursor: "pointer" }}>
31-
{children}
32-
</span>
33-
</Tooltip>
40+
<TooltipProvider delayDuration={100}>
41+
<Tooltip
42+
open={tooltipOpen}
43+
onOpenChange={(shouldBeOpen) => {
44+
// Always keep the tooltip open when in focus to handle issues when onOpenChange is unexpectedly false
45+
if (!shouldBeOpen && isFocused) return;
46+
setTooltipOpen(shouldBeOpen);
47+
}}
48+
>
49+
<TooltipTrigger asChild>
50+
<span
51+
ref={clickableProps.ref}
52+
{...attrs}
53+
className={cn("cursor-pointer", className)}
54+
role={role ?? clickableProps.role}
55+
tabIndex={tabIndex ?? clickableProps.tabIndex}
56+
onClick={(event) => {
57+
clickableProps.onClick(event);
58+
onClick?.(event);
59+
}}
60+
onKeyDown={(event) => {
61+
clickableProps.onKeyDown(event);
62+
onKeyDown?.(event);
63+
}}
64+
onKeyUp={(event) => {
65+
clickableProps.onKeyUp(event);
66+
onKeyUp?.(event);
67+
}}
68+
onMouseEnter={() => {
69+
setIsFocused(true);
70+
setTooltipOpen(true);
71+
}}
72+
onMouseLeave={() => {
73+
setTooltipOpen(false);
74+
}}
75+
onFocus={() => {
76+
setIsFocused(true);
77+
}}
78+
onBlur={() => {
79+
setTooltipOpen(false);
80+
}}
81+
>
82+
{children}
83+
</span>
84+
</TooltipTrigger>
85+
<TooltipContent side={side}>
86+
{showCopiedSuccess ? "Copied!" : "Click to copy"}
87+
</TooltipContent>
88+
</Tooltip>
89+
</TooltipProvider>
3490
);
3591
};

site/src/modules/resources/SensitiveValue.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ export const SensitiveValue: FC<SensitiveValueProps> = ({ value }) => {
3232
gap: 4,
3333
}}
3434
>
35-
<CopyableValue value={value} css={styles.value}>
35+
<CopyableValue
36+
value={value}
37+
className="w-[calc(100%-22px)] overflow-hidden whitespace-nowrap text-ellipsis"
38+
>
3639
{displayValue}
3740
</CopyableValue>
3841
<Tooltip title={buttonLabel}>
@@ -52,14 +55,6 @@ export const SensitiveValue: FC<SensitiveValueProps> = ({ value }) => {
5255
};
5356

5457
const styles = {
55-
value: {
56-
// 22px is the button width
57-
width: "calc(100% - 22px)",
58-
overflow: "hidden",
59-
whiteSpace: "nowrap",
60-
textOverflow: "ellipsis",
61-
},
62-
6358
button: css`
6459
color: inherit;
6560
`,

site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,20 +150,20 @@ export const EditOAuth2AppPageView: FC<EditOAuth2AppProps> = ({
150150
<dl css={styles.dataList}>
151151
<dt>Client ID</dt>
152152
<dd>
153-
<CopyableValue value={app.id}>
153+
<CopyableValue value={app.id} side="right">
154154
{app.id} <CopyIcon className="size-icon-xs" />
155155
</CopyableValue>
156156
</dd>
157157
<dt>Authorization URL</dt>
158158
<dd>
159-
<CopyableValue value={app.endpoints.authorization}>
159+
<CopyableValue value={app.endpoints.authorization} side="right">
160160
{app.endpoints.authorization}{" "}
161161
<CopyIcon className="size-icon-xs" />
162162
</CopyableValue>
163163
</dd>
164164
<dt>Token URL</dt>
165165
<dd>
166-
<CopyableValue value={app.endpoints.token}>
166+
<CopyableValue value={app.endpoints.token} side="right">
167167
{app.endpoints.token} <CopyIcon className="size-icon-xs" />
168168
</CopyableValue>
169169
</dd>

site/src/pages/IconsPage/IconsPage.tsx

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
PageHeaderSubtitle,
1313
PageHeaderTitle,
1414
} from "components/PageHeader/PageHeader";
15-
import { Stack } from "components/Stack/Stack";
1615
import { SearchIcon, XIcon } from "lucide-react";
1716
import { type FC, type ReactNode, useMemo, useState } from "react";
1817
import {
@@ -79,13 +78,7 @@ const IconsPage: FC = () => {
7978
<Tooltip
8079
placement="bottom-end"
8180
title={
82-
<p
83-
css={{
84-
padding: 8,
85-
fontSize: 13,
86-
lineHeight: 1.5,
87-
}}
88-
>
81+
<p className="p-2 leading-6 text-sm">
8982
You can suggest a new icon by submitting a Pull Request to our
9083
public GitHub repository. Just keep in mind that it should be
9184
relevant to many Coder users, and redistributable under a
@@ -124,12 +117,7 @@ const IconsPage: FC = () => {
124117
},
125118
startAdornment: (
126119
<InputAdornment position="start">
127-
<SearchIcon
128-
className="size-icon-xs"
129-
css={{
130-
color: theme.palette.text.secondary,
131-
}}
132-
/>
120+
<SearchIcon className="size-icon-xs text-content-secondary" />
133121
</InputAdornment>
134122
),
135123
endAdornment: searchInputText && (
@@ -147,19 +135,13 @@ const IconsPage: FC = () => {
147135
}}
148136
/>
149137

150-
<Stack
151-
direction="row"
152-
wrap="wrap"
153-
spacing={1}
154-
justifyContent="center"
155-
css={{ marginTop: 32 }}
156-
>
138+
<div className="flex flex-row gap-2 justify-center flex-wrap max-w-full mt-8">
157139
{searchedIcons.length === 0 && (
158140
<EmptyState message="No results matched your search" />
159141
)}
160142
{searchedIcons.map((icon) => (
161-
<CopyableValue key={icon.url} value={icon.url} placement="bottom">
162-
<Stack alignItems="center" css={{ margin: 12 }}>
143+
<CopyableValue key={icon.url} value={icon.url}>
144+
<div className="flex flex-col gap-4 items-center max-w-full p-3">
163145
<img
164146
alt={icon.url}
165147
src={icon.url}
@@ -189,10 +171,10 @@ const IconsPage: FC = () => {
189171
>
190172
{icon.description}
191173
</figcaption>
192-
</Stack>
174+
</div>
193175
</CopyableValue>
194176
))}
195-
</Stack>
177+
</div>
196178
</Margins>
197179
</>
198180
);

0 commit comments

Comments
 (0)