diff --git a/explorer/src/components/common/Address.tsx b/explorer/src/components/common/Address.tsx index d5cd799762..a51dd5fcd9 100644 --- a/explorer/src/components/common/Address.tsx +++ b/explorer/src/components/common/Address.tsx @@ -1,11 +1,11 @@ -import React, { useState } from "react"; +import React from "react"; import { Link } from "react-router-dom"; import { PublicKey } from "@solana/web3.js"; import { clusterPath } from "utils/url"; import { displayAddress } from "utils/tx"; import { useCluster } from "providers/cluster"; +import { Copyable } from "./Copyable"; -type CopyState = "copy" | "copied"; type Props = { pubkey: PublicKey; alignRight?: boolean; @@ -23,31 +23,15 @@ export function Address({ truncate, truncateUnknown, }: Props) { - const [state, setState] = useState("copy"); const address = pubkey.toBase58(); const { cluster } = useCluster(); - const copyToClipboard = () => navigator.clipboard.writeText(address); - const handleClick = () => - copyToClipboard().then(() => { - setState("copied"); - setTimeout(() => setState("copy"), 1000); - }); - - const copyIcon = - state === "copy" ? ( - - ) : ( - - ); - if (truncateUnknown && address === displayAddress(address, cluster)) { truncate = true; } const content = ( - <> - {copyIcon} + {link ? ( )} - + ); return ( diff --git a/explorer/src/components/common/Copyable.tsx b/explorer/src/components/common/Copyable.tsx index 5b7f45ca0d..baad55e2e0 100644 --- a/explorer/src/components/common/Copyable.tsx +++ b/explorer/src/components/common/Copyable.tsx @@ -1,56 +1,98 @@ -import React, { useState, ReactNode } from "react"; +import React, { ReactNode, useState } from "react"; -type CopyableProps = { +type CopyState = "copy" | "copied" | "errored"; + +export function Copyable({ + text, + children, + replaceText, +}: { text: string; children: ReactNode; - bottom?: boolean; - right?: boolean; -}; - -type State = "hide" | "copy" | "copied"; - -function Popover({ - state, - bottom, - right, -}: { - state: State; - bottom?: boolean; - right?: boolean; + replaceText?: boolean; }) { - if (state === "hide") return null; - const text = state === "copy" ? "Copy" : "Copied!"; - return ( -
-
-
{text}
-
- ); -} + const [state, setState] = useState("copy"); -export function Copyable({ bottom, right, text, children }: CopyableProps) { - const [state, setState] = useState("hide"); - - const copyToClipboard = () => navigator.clipboard.writeText(text); - const handleClick = () => - copyToClipboard().then(() => { + const handleClick = async () => { + try { + await navigator.clipboard.writeText(text); setState("copied"); - setTimeout(() => setState("hide"), 1000); - }); + } catch (err) { + setState("errored"); + } + setTimeout(() => setState("copy"), 1000); + }; + + function CopyIcon() { + if (state === "copy") { + return ( + + ); + } else if (state === "copied") { + return ; + } else if (state === "errored") { + return ( + + ); + } + return null; + } + + let message = ""; + let textColor = ""; + if (state === "copied") { + message = "Copied"; + textColor = "text-info"; + } else if (state === "errored") { + message = "Copy Failed"; + textColor = "text-danger"; + } + + function PrependCopyIcon() { + return ( + <> + + + {message} + + + + {children} + + ); + } + + function ReplaceWithMessage() { + return ( + + + + + {message} + + + {children} + + ); + } + + if (state === "copy") { + return ; + } else if (replaceText) { + return ; + } return ( -
setState("copy")} - onMouseOut={() => state === "copy" && setState("hide")} - > - {children} - -
+ <> + + + + + + + ); } diff --git a/explorer/src/components/common/Signature.tsx b/explorer/src/components/common/Signature.tsx index 1e8dbe7bbb..3f35a67632 100644 --- a/explorer/src/components/common/Signature.tsx +++ b/explorer/src/components/common/Signature.tsx @@ -1,9 +1,9 @@ -import React, { useState } from "react"; +import React from "react"; import { Link } from "react-router-dom"; import { TransactionSignature } from "@solana/web3.js"; import { clusterPath } from "utils/url"; +import { Copyable } from "./Copyable"; -type CopyState = "copy" | "copied"; type Props = { signature: TransactionSignature; alignRight?: boolean; @@ -12,45 +12,26 @@ type Props = { }; export function Signature({ signature, alignRight, link, truncate }: Props) { - const [state, setState] = useState("copy"); - - const copyToClipboard = () => navigator.clipboard.writeText(signature); - const handleClick = () => - copyToClipboard().then(() => { - setState("copied"); - setTimeout(() => setState("copy"), 1000); - }); - - const copyIcon = - state === "copy" ? ( - - ) : ( - - ); - - const copyButton = ( - {copyIcon} - ); - return (
- {copyButton} - - {link ? ( - - {signature} - - ) : ( - signature - )} - + + + {link ? ( + + {signature} + + ) : ( + signature + )} + +
); } diff --git a/explorer/src/components/common/Slot.tsx b/explorer/src/components/common/Slot.tsx index 88da2025b3..f9c6880e22 100644 --- a/explorer/src/components/common/Slot.tsx +++ b/explorer/src/components/common/Slot.tsx @@ -1,40 +1,21 @@ -import React, { useState } from "react"; +import React from "react"; import { Link } from "react-router-dom"; import { clusterPath } from "utils/url"; +import { Copyable } from "./Copyable"; -type CopyState = "copy" | "copied"; type Props = { slot: number; link?: boolean; }; export function Slot({ slot, link }: Props) { - const [state, setState] = useState("copy"); - - const copyToClipboard = () => navigator.clipboard.writeText(slot.toString()); - const handleClick = () => - copyToClipboard().then(() => { - setState("copied"); - setTimeout(() => setState("copy"), 1000); - }); - - const copyIcon = - state === "copy" ? ( - - ) : ( - - ); - - const copyButton = ( - {copyIcon} - ); - return link ? ( - - {copyButton} - - {slot.toLocaleString("en-US")} - - + + + + {slot.toLocaleString("en-US")} + + + ) : ( {slot.toLocaleString("en-US")} ); diff --git a/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx b/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx index cd7a114c44..6c3d94d34e 100644 --- a/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx +++ b/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx @@ -52,7 +52,7 @@ export function AllocateWithSeedDetailsCard(props: { Seed - + {info.seed} diff --git a/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx b/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx index bf04d30475..b4ea0c3530 100644 --- a/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx +++ b/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx @@ -52,7 +52,7 @@ export function AssignWithSeedDetailsCard(props: { Seed - + {info.seed} diff --git a/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx b/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx index 84302f7773..28c3a42b36 100644 --- a/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx +++ b/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx @@ -60,7 +60,7 @@ export function CreateWithSeedDetailsCard(props: { Seed - + {info.seed} diff --git a/explorer/src/components/instruction/system/TransferWithSeedDetailsCard.tsx b/explorer/src/components/instruction/system/TransferWithSeedDetailsCard.tsx index 355536e4ab..8af35baafb 100644 --- a/explorer/src/components/instruction/system/TransferWithSeedDetailsCard.tsx +++ b/explorer/src/components/instruction/system/TransferWithSeedDetailsCard.tsx @@ -65,7 +65,7 @@ export function TransferWithSeedDetailsCard(props: { Seed - + {info.sourceSeed} diff --git a/explorer/src/scss/_solana.scss b/explorer/src/scss/_solana.scss index 09c0f194ed..dabfe2f7f7 100644 --- a/explorer/src/scss/_solana.scss +++ b/explorer/src/scss/_solana.scss @@ -71,6 +71,15 @@ ul.log-messages { cursor: pointer; } +.v-hidden { + visibility: hidden; + max-height: 0; +} + +.flex-nowrap { + flex-wrap: nowrap; +} + .dropdown-exit { position: absolute; z-index: 100;