Skip to content

Conversation

@jaaydenh
Copy link
Contributor

@jaaydenh jaaydenh commented Oct 12, 2025

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: placementside (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)

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 <noreply@anthropic.com>
Comment on lines 40 to 41
if (showCopiedSuccess) {
setShowCopiedText(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have both showCopiedSuccess and showCopiedText? this just feels like duplicated state with messy synchronization code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm struggling to wrap my head around what the code is trying to solve that isn't handled by using showCopiedSuccess directly.

The hook already has timeouts built in (with cleanup). Is the goal to expose a different timeout value to the rest of the component? Because if so, it feels like the better change is to update useClipboard so that the timeout is parameterized

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original goal was to make it so that the tooltip is removed after showCopiedSuccess returns to false as it felt like a better experience when I originally looked at this. I think after thinking over this again. The existing functionality makes sense and I will remove all of this extra logic.

Copy link
Contributor

@buenos-nachos buenos-nachos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code breaks a bunch of React rules, and I don't see any benefits to it. I see a lot of these changes causing bugs down the line

Comment on lines 40 to 41
if (showCopiedSuccess) {
setShowCopiedText(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm struggling to wrap my head around what the code is trying to solve that isn't handled by using showCopiedSuccess directly.

The hook already has timeouts built in (with cleanup). Is the goal to expose a different timeout value to the rest of the component? Because if so, it feels like the better change is to update useClipboard so that the timeout is parameterized

@jaaydenh
Copy link
Contributor Author

@Parkreiner @aslilac Removed the code where there was concerns and fixed issues with keyboard handling as well. The component should have now have parity with the MUI implementation.

Copy link
Contributor

@buenos-nachos buenos-nachos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're making progress, but I don't think we're there just yet

Comment on lines 46 to 47
{...attrs}
{...clickableProps}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is gong to cause prop shadowing for all the props that clickableProps currently has (as in, if a consumer passes in any of tabIndex, role, onClick, onKeyDown or onKeyUp, they'll get dropped in favor of the clickableProps versions)

I feel like we want to be more finer-grained with how we wire everything together (this just illustrates the main idea, and doesn't account for all the other code comments I made):

export const CopyableValue: FC<CopyableValueProps> = ({
	value,
	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<HTMLSpanElement>(() => {
		copyToClipboard(value);
		setTooltipOpen(true);
	});

	return (
		<TooltipProvider delayDuration={100}>
			<Tooltip
				open={tooltipOpen}
				onOpenChange={(next) => {
					if (!next && isFocused) return;
					setTooltipOpen(next);
				}}
			>
				<TooltipTrigger asChild>
					<span
						ref={clickableProps.ref}
						{...attrs}
						className={cn("cursor-pointer", className)}
						role={role ?? clickableProps.role}
						tabIndex={tabIndex ?? clickableProps.tabIndex}
						onClick={(event) => {
							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}
					</span>
				</TooltipTrigger>
				<TooltipContent side={side}>
					{showCopiedSuccess ? "Copied!" : "Click to copy"}
				</TooltipContent>
			</Tooltip>
		</TooltipProvider>
	);
};

}) => {
const { showCopiedSuccess, copyToClipboard } = useClipboard();
const [tooltipOpen, setTooltipOpen] = useState(false);
const [isFocused, setIsFocused] = useState(false);
Copy link
Contributor

@buenos-nachos buenos-nachos Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is our goal with the focus state? Because right now, it looks like we're flipping the state within React, but we're not actually changing the focus behavior on the actual HTML element

The main area where I'm worried about drift between React and the underlying HTML is the onMouseEnter prop setting isFocused to true

Comment on lines +57 to +59
onFocus={() => {
setIsFocused(true);
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want to open the tooltip here?

Comment on lines 39 to 40
// Always keep the tooltip open when in focus to handle issues when onOpenChange is unexpectedly false
if (!next && isFocused) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What situations were you running into that was causing this problem? I know that the libraries can get a little unruly and sometimes need these hacks, but I'm wondering if there's a chance we could remove the need for this

<CopyableValue key={icon.url} value={icon.url} placement="bottom">
<Stack alignItems="center" css={{ margin: 12 }}>
<CopyableValue key={icon.url} value={icon.url}>
<div className="flex flex-col gap-4 items-center max-w-full m-3">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div className="flex flex-col gap-4 items-center max-w-full m-3">
<div className="flex flex-col gap-4 items-center max-w-full p-3">

can we do padding instead if it looks the same?

<TooltipProvider delayDuration={100}>
<Tooltip
open={tooltipOpen}
onOpenChange={(next) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onOpenChange={(next) => {
onOpenChange={(shouldBeOpen) => {

the name next just makes this really hard to reason about for me. if I substitute it for something like this in my head it makes it clearer.

@github-actions github-actions bot added the stale This issue is like stale bread. label Nov 6, 2025
@github-actions github-actions bot closed this Nov 9, 2025
@jaaydenh jaaydenh reopened this Nov 10, 2025
@jaaydenh jaaydenh merged commit 47c703a into main Nov 10, 2025
26 checks passed
@jaaydenh jaaydenh deleted the jaaydenh/refactor-copyable-value branch November 10, 2025 22:15
@github-actions github-actions bot locked and limited conversation to collaborators Nov 10, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

stale This issue is like stale bread.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants