diff --git a/package-lock.json b/package-lock.json index 7d3493d..99a2730 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,6 +147,8 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -2179,19 +2181,32 @@ "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.13.tgz", + "integrity": "sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, "node_modules/@esbuild/netbsd-x64": { @@ -9913,6 +9928,12 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" diff --git a/src/components/AddressDisplay.test.tsx b/src/components/AddressDisplay.test.tsx index 2c952a0..30b72f0 100644 --- a/src/components/AddressDisplay.test.tsx +++ b/src/components/AddressDisplay.test.tsx @@ -65,4 +65,40 @@ describe("AddressDisplay", () => { render(); expect(screen.getByText("Destination")).toBeInTheDocument(); }); + + it("fires onCopy callback after successful clipboard write", async () => { + const onCopy = vi.fn(); + render(); + + const copyBtn = screen.getByRole("button", { name: "Copy address" }); + await act(async () => { + fireEvent.click(copyBtn); + }); + + expect(onCopy).toHaveBeenCalled(); + }); + + it("adds select-all class to the address span when showFull is true", () => { + render(); + const el = screen.getByText(address); + expect(el.className).toContain("select-all"); + }); + + it("does not add select-all class when showFull is false", () => { + render(); + const el = screen.getByText("GBAMQXTQ...ZJQQQQ"); + expect(el.className).not.toContain("select-all"); + }); + + it("applies correct font size for sm size when showFull is true", () => { + render(); + const el = screen.getByText(address); + expect(el.className).toContain("text-[10px]"); + }); + + it("applies correct font size for lg size when showFull is true", () => { + render(); + const el = screen.getByText(address); + expect(el.className).toContain("text-[13px]"); + }); }); diff --git a/src/components/AddressDisplay.tsx b/src/components/AddressDisplay.tsx index a7478a7..0d4a750 100644 --- a/src/components/AddressDisplay.tsx +++ b/src/components/AddressDisplay.tsx @@ -5,15 +5,23 @@ import { useState } from "react"; import { cn } from "@/lib/utils"; import { truncateAddress } from "@/lib/utils"; -interface AddressDisplayProps { +export interface AddressDisplayProps { address: string; start?: number; end?: number; showFull?: boolean; className?: string; label?: string; + onCopy?: () => void; + size?: "sm" | "md" | "lg"; } +const sizeConfig = { + sm: { text: "text-[10px]", icon: 10 }, + md: { text: "text-[11px]", icon: 12 }, + lg: { text: "text-[13px]", icon: 14 }, +} as const; + export function AddressDisplay({ address, start = 8, @@ -21,6 +29,8 @@ export function AddressDisplay({ showFull = false, className, label, + onCopy, + size = "md", }: AddressDisplayProps) { const [copied, setCopied] = useState(false); @@ -28,6 +38,7 @@ export function AddressDisplay({ try { await navigator.clipboard.writeText(address); setCopied(true); + onCopy?.(); setTimeout(() => setCopied(false), 2000); } catch { /* fallback */ @@ -35,6 +46,7 @@ export function AddressDisplay({ } const display = showFull ? address : truncateAddress(address, start, end); + const { text, icon: iconSize } = sizeConfig[size]; return (
@@ -48,7 +60,8 @@ export function AddressDisplay({ data-address className={cn( "break-all leading-relaxed", - showFull ? "text-[11px]" : "", + showFull && text, + showFull && "select-all", )} title={address} > @@ -67,7 +80,7 @@ export function AddressDisplay({ >