diff --git a/package.json b/package.json index ed8f895..64eceae 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "antd-dayjs-webpack-plugin": "^1.0.6", "babel-plugin-lodash": "^3.3.4", "chalk": "^4.1.0", + "copy-to-clipboard": "^3.3.1", "craco-alias": "^2.2.0", "diff": "^5.0.0", "eslint": "^7.22.0", diff --git a/src/components/SmallCopyable.less b/src/components/SmallCopyable.less new file mode 100644 index 0000000..7a51c0b --- /dev/null +++ b/src/components/SmallCopyable.less @@ -0,0 +1,10 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under AGPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +.small-copyable { + border: 0; + background: transparent; + padding: 0; + line-height: inherit; + display: inline-block; +} diff --git a/src/components/SmallCopyable.tsx b/src/components/SmallCopyable.tsx new file mode 100644 index 0000000..04c667b --- /dev/null +++ b/src/components/SmallCopyable.tsx @@ -0,0 +1,65 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under AGPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useState, useEffect, useRef } from "react"; +import classNames from "classnames"; +import { Tooltip } from "antd"; +import { CopyOutlined, CheckOutlined } from "@ant-design/icons"; + +import { useTranslation } from "react-i18next"; + +import { CopyConfig } from "./types"; +import copy from "copy-to-clipboard"; + +import "./SmallCopyable.less"; + +// This is based on the ant Typography copyable, but with some features removed, +// and without the overhead of the Typography Base component. The ResizeObserver +// in Typography seems to add a significant amount to render times when there +// are a lot of Text elements on the screen (for example, a table listing). + +// Force 'text' to be set (don't traverse the children at all) +type Props = CopyConfig & { + text: string; + className?: string; +}; + +export function SmallCopyable({ text, onCopy, className }: Props): JSX.Element { + const { t } = useTranslation(); + + const [copied, setCopied] = useState(false); + const copyId = useRef(); + + function onCopyClick(e: React.MouseEvent) { + e.preventDefault(); + + copy(text); + + // Display the 'Copied!' tooltip for 3 secs + setCopied(true); + onCopy?.(); + copyId.current = window.setTimeout(() => setCopied(false), 3000); + } + + // Clear the timeout on unmount + useEffect(() => () => window.clearTimeout(copyId.current), []); + + const title = copied ? t("copied") : t("copy"); + const classes = classNames(className, "ant-typography-copy", "small-copyable", + copied && "ant-typography-copy-success"); + + const btn = ( +
+ {copied ? : } +
+ ); + + return copied + ? {btn} + : btn; +} diff --git a/src/components/addresses/ContextualAddress.tsx b/src/components/addresses/ContextualAddress.tsx index 9c8870a..c1b52c5 100644 --- a/src/components/addresses/ContextualAddress.tsx +++ b/src/components/addresses/ContextualAddress.tsx @@ -2,7 +2,7 @@ // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt import classNames from "classnames"; -import { Tooltip, Typography } from "antd"; +import { Tooltip } from "antd"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -16,13 +16,12 @@ import { KristNameLink } from "../names/KristNameLink"; import { ConditionalLink } from "@comp/ConditionalLink"; +import { SmallCopyable } from "@comp/SmallCopyable"; import { getVerified, VerifiedAddressLink } from "./VerifiedAddress"; import "./ContextualAddress.less"; -const { Text } = Typography; - interface Props { address: KristAddress | string | null; wallet?: Wallet | false; @@ -80,8 +79,13 @@ const verified = getVerified(address); + // If the address definitely doesn't exist, show the 'not yet initialised' + // tooltip on hover instead. const showTooltip = !verified && ((hideNameAddress && !!hasMetaname) || !!wallet?.label || !!contact?.label); + const tooltipTitle = nonExistent + ? t("contextualAddressNonExistentTooltip") + : (showTooltip ? address : undefined); const copyable = !neverCopyable && addressCopyButtons ? { text: address } : undefined; @@ -91,56 +95,74 @@ "contextual-address-non-existent": nonExistent }); - /** The label of the wallet or contact, or the address itself (not a metaname) */ - function AddressContent(props: any): JSX.Element { - return wallet?.label - ? {wallet.label} - : (contact?.label - ? {contact.label} - : {address}); - } - - return - {/* If the address definitely doesn't exist, show the 'not yet initialised' - * tooltip on hover instead. */} - - {commonMeta && hasMetaname - ? ( - // Display the metaname and link to the name if possible - + ) + : (verified + // Display the verified address if possible + ? + : ( + // Display the regular address or label + + - ) - : (verified - // Display the verified address if possible - ? - : ( - // Display the regular address or label - - - - ) - ) - } + + ) + ); - {/* This empty child here forces the Tooltip to change its hover - * behaviour. Pretty funky, needs investigating. */} - <> - - ; + return + {/* Only render the tooltip component if it's actually used */} + {tooltipTitle + ? ( + + {mainContents} + + {/* This empty child here forces the Tooltip to change its hover + * behaviour. Pretty funky, needs investigating. */} + <> + + ) + : mainContents} + + {copyable && } + ; +} + +interface AddressContentProps { + wallet?: Wallet; + contact?: Contact; + address: string; +} + +/** The label of the wallet or contact, or the address itself (not a metaname) */ +function AddressContent({ + wallet, + contact, + address, + ...props +}: AddressContentProps): JSX.Element { + return wallet?.label + ? {wallet.label} + : (contact?.label + ? {contact.label} + : {address}); } interface AddressMetanameProps { diff --git a/yarn.lock b/yarn.lock index 2cb394b..890f617 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3972,7 +3972,7 @@ resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -copy-to-clipboard@^3.2.0: +copy-to-clipboard@^3.2.0, copy-to-clipboard@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==