From 84673c5e2d36515ff1184dd4d50d05ed5dd531bb Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Sat, 20 Dec 2025 16:55:09 +0000 Subject: [PATCH] Added CopyableTableCell --- .../app/components/primitives/Table.tsx | 51 ++++++++++++++++++- .../app/routes/storybook.table/route.tsx | 28 ++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/components/primitives/Table.tsx b/apps/webapp/app/components/primitives/Table.tsx index a51c78cc8..f66ee4b96 100644 --- a/apps/webapp/app/components/primitives/Table.tsx +++ b/apps/webapp/app/components/primitives/Table.tsx @@ -1,9 +1,11 @@ import { ChevronRightIcon } from "@heroicons/react/24/solid"; import { Link } from "@remix-run/react"; +import { ClipboardCheckIcon, ClipboardIcon } from "lucide-react"; import React, { type ReactNode, forwardRef, useState, useContext, createContext } from "react"; +import { useCopy } from "~/hooks/useCopy"; import { cn } from "~/utils/cn"; import { Popover, PopoverContent, PopoverVerticalEllipseTrigger } from "./Popover"; -import { InfoIconTooltip } from "./Tooltip"; +import { InfoIconTooltip, SimpleTooltip } from "./Tooltip"; const variants = { bright: { @@ -269,6 +271,53 @@ export const TableCell = forwardRef( } ); +type CopyableTableCellProps = TableCellProps & { + value: string; +}; + +export const CopyableTableCell = forwardRef( + ({ value, children, className, ...props }, ref) => { + const { copy, copied } = useCopy(value); + + return ( + +
+ {children} + { + e.stopPropagation(); + e.preventDefault(); + copy(); + }} + className="absolute right-0 top-1/2 z-10 hidden -translate-y-1/2 cursor-pointer group-hover/copyable-cell:flex" + > + + {copied ? ( + + ) : ( + + )} + + } + content={copied ? "Copied!" : "Copy"} + disableHoverableContent + /> + +
+
+ ); + } +); + export const TableCellChevron = forwardRef< HTMLTableCellElement, { diff --git a/apps/webapp/app/routes/storybook.table/route.tsx b/apps/webapp/app/routes/storybook.table/route.tsx index 2a509e555..afe48a2a7 100644 --- a/apps/webapp/app/routes/storybook.table/route.tsx +++ b/apps/webapp/app/routes/storybook.table/route.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Header1, Header2 } from "~/components/primitives/Headers"; import { Paragraph } from "~/components/primitives/Paragraph"; import { + CopyableTableCell, Table, TableBody, TableCell, @@ -60,6 +61,33 @@ export default function Story() { +
+ Copyable cells + + Hover over the first column to see the copy button. Click to copy the cell value. + + + + + ID (copyable) + Name + Status + + + + {Array.from({ length: 5 }, (_, index) => { + const id = `run_${crypto.randomUUID().slice(0, 8)}`; + return ( + + {id} + Task {index + 1} + Completed + + ); + })} + +
+
); }