Skip to content

Commit cccebe6

Browse files
committed
fix(webapp): avoid mounting copy tooltips for every visible cell
Only mount the tooltip subtree for a copyable cell while it is hovered; the plain button (with its aria-label) stays mounted at all times so keyboard users can always reach it. Also give the icon-only CopyButton an accessible name when it renders without children.
1 parent 448ee41 commit cccebe6

3 files changed

Lines changed: 80 additions & 60 deletions

File tree

apps/webapp/app/components/code/TSQLResultsTable.tsx

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,35 @@ function CopyableCell({
843843
const [isHovered, setIsHovered] = useState(false);
844844
const { copy, copied } = useCopy(value);
845845

846+
// The button (with its aria-label) stays mounted at all times so keyboard users can always
847+
// reach it. The Radix tooltip subtree is comparatively expensive to keep alive for every
848+
// visible cell of a virtualized grid, so it's only mounted while the cell is hovered - the
849+
// tooltip is a hover affordance, not required for the button's accessible name.
850+
const copyButton = (
851+
<button
852+
type="button"
853+
aria-label={copied ? "Copied" : "Copy"}
854+
onClick={(e) => {
855+
e.stopPropagation();
856+
e.preventDefault();
857+
copy();
858+
}}
859+
className={cn(
860+
"absolute right-1 top-1/2 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded border border-border-bright bg-background-hover transition-opacity focus:opacity-100",
861+
isHovered ? "opacity-100" : "pointer-events-none opacity-0",
862+
copied
863+
? "text-green-500"
864+
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
865+
)}
866+
>
867+
{copied ? (
868+
<ClipboardCheckIcon className="size-3.5" />
869+
) : (
870+
<ClipboardIcon className="size-3.5" />
871+
)}
872+
</button>
873+
);
874+
846875
return (
847876
<div
848877
className={cn(
@@ -856,36 +885,17 @@ function CopyableCell({
856885
onMouseLeave={() => setIsHovered(false)}
857886
>
858887
<span className="flex items-center truncate">{children}</span>
859-
<SimpleTooltip
860-
asChild
861-
tabbable
862-
button={
863-
<button
864-
type="button"
865-
aria-label={copied ? "Copied" : "Copy"}
866-
onClick={(e) => {
867-
e.stopPropagation();
868-
e.preventDefault();
869-
copy();
870-
}}
871-
className={cn(
872-
"absolute right-1 top-1/2 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded border border-border-bright bg-background-hover transition-opacity focus:opacity-100",
873-
isHovered ? "opacity-100" : "pointer-events-none opacity-0",
874-
copied
875-
? "text-green-500"
876-
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
877-
)}
878-
>
879-
{copied ? (
880-
<ClipboardCheckIcon className="size-3.5" />
881-
) : (
882-
<ClipboardIcon className="size-3.5" />
883-
)}
884-
</button>
885-
}
886-
content={copied ? "Copied!" : "Copy"}
887-
disableHoverableContent
888-
/>
888+
{isHovered ? (
889+
<SimpleTooltip
890+
asChild
891+
tabbable
892+
button={copyButton}
893+
content={copied ? "Copied!" : "Copy"}
894+
disableHoverableContent
895+
/>
896+
) : (
897+
copyButton
898+
)}
889899
</div>
890900
);
891901
}

apps/webapp/app/components/primitives/CopyButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export function CopyButton({
5252
onClick={copy}
5353
className={cn("shrink-0", buttonClassName)}
5454
tooltip={showTooltip ? (copied ? "Copied!" : "Copy") : undefined}
55+
aria-label={children ? undefined : copied ? "Copied" : "Copy"}
5556
LeadingIcon={
5657
copied ? (
5758
<ClipboardCheckIcon

apps/webapp/app/components/primitives/Table.tsx

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,34 @@ export const CopyableTableCell = forwardRef<HTMLTableCellElement, CopyableTableC
478478
const [isHovered, setIsHovered] = useState(false);
479479
const { copy, copied } = useCopy(value);
480480

481+
// The button (with its aria-label) stays mounted at all times so keyboard users can always
482+
// reach it. The Radix tooltip subtree is only mounted while the cell is hovered - the
483+
// tooltip is a hover affordance, not required for the button's accessible name.
484+
const copyButton = (
485+
<button
486+
type="button"
487+
aria-label={copied ? "Copied" : "Copy"}
488+
onClick={(e) => {
489+
e.stopPropagation();
490+
e.preventDefault();
491+
copy();
492+
}}
493+
className={cn(
494+
"absolute -right-2 top-1/2 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded border border-border-bright bg-background-hover transition-opacity focus:opacity-100",
495+
isHovered ? "opacity-100" : "pointer-events-none opacity-0",
496+
copied
497+
? "text-green-500"
498+
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
499+
)}
500+
>
501+
{copied ? (
502+
<ClipboardCheckIcon className="size-3.5" />
503+
) : (
504+
<ClipboardIcon className="size-3.5" />
505+
)}
506+
</button>
507+
);
508+
481509
return (
482510
<TableCell ref={ref} className={className} {...props}>
483511
<div
@@ -486,36 +514,17 @@ export const CopyableTableCell = forwardRef<HTMLTableCellElement, CopyableTableC
486514
onMouseLeave={() => setIsHovered(false)}
487515
>
488516
{children}
489-
<SimpleTooltip
490-
asChild
491-
tabbable
492-
button={
493-
<button
494-
type="button"
495-
aria-label={copied ? "Copied" : "Copy"}
496-
onClick={(e) => {
497-
e.stopPropagation();
498-
e.preventDefault();
499-
copy();
500-
}}
501-
className={cn(
502-
"absolute -right-2 top-1/2 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded border border-border-bright bg-background-hover transition-opacity focus:opacity-100",
503-
isHovered ? "opacity-100" : "pointer-events-none opacity-0",
504-
copied
505-
? "text-green-500"
506-
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
507-
)}
508-
>
509-
{copied ? (
510-
<ClipboardCheckIcon className="size-3.5" />
511-
) : (
512-
<ClipboardIcon className="size-3.5" />
513-
)}
514-
</button>
515-
}
516-
content={copied ? "Copied!" : "Copy"}
517-
disableHoverableContent
518-
/>
517+
{isHovered ? (
518+
<SimpleTooltip
519+
asChild
520+
tabbable
521+
button={copyButton}
522+
content={copied ? "Copied!" : "Copy"}
523+
disableHoverableContent
524+
/>
525+
) : (
526+
copyButton
527+
)}
519528
</div>
520529
</TableCell>
521530
);

0 commit comments

Comments
 (0)