InfoPanel: show confirmation icons on successful SVG or snippet copy

This patch makes immediate use of useTransientState() to fire a
temporary state transition to show success/failure of copy action.

Note: we should handle error states using other icon.
This commit is contained in:
Tobias Fried
2020-07-31 19:59:25 -04:00
parent 6202c4d2bb
commit f79eb5a5d3

View File

@@ -1,10 +1,22 @@
import React, { useRef } from "react"; import React, { useRef } from "react";
import { useRecoilValue } from "recoil"; import { useRecoilValue, useSetRecoilState } from "recoil";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import { styleQueryAtom, iconSizeAtom, iconColorAtom } from "../../state/atoms"; import {
import { Icon, ArrowUpRightCircle, Copy } from "phosphor-react"; styleQueryAtom,
iconSizeAtom,
iconColorAtom,
iconPreviewOpenAtom,
} from "../../state/atoms";
import useTransientState from "../../hooks/useTransientState";
import {
Icon,
ArrowUpRightCircle,
Copy,
Prohibit,
CheckCircle,
} from "phosphor-react";
const infoVariants = { const infoVariants = {
open: { open: {
@@ -34,26 +46,43 @@ const InfoPanel: React.FC<InfoPanelProps> = (props) => {
const weight = useRecoilValue(styleQueryAtom); const weight = useRecoilValue(styleQueryAtom);
const size = useRecoilValue(iconSizeAtom); const size = useRecoilValue(iconSizeAtom);
const color = useRecoilValue(iconColorAtom); const color = useRecoilValue(iconColorAtom);
const setOpen = useSetRecoilState(iconPreviewOpenAtom);
const [copied, setCopied] = useTransientState<string | false>(false, 2000);
const ref = useRef<SVGSVGElement>(null); const ref = useRef<SVGSVGElement>(null);
const htmlString = `<i class="ph-${name}${ const snippets = {
weight === "regular" ? "" : `-${weight}` html: `<i class="ph-${name}${
}"></i>`; weight === "regular" ? "" : `-${weight}`
const reactString = `<${Icon.displayName} size={${size}} color="${color}" ${ }"></i>`,
weight === "regular" ? "" : `weight="${weight}" ` react: `<${Icon.displayName} size={${size}} color="${color}" ${
}/>`; weight === "regular" ? "" : `weight="${weight}" `
}/>`,
};
const handleCopySnippet = (data: string) => { const handleCopySnippet = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
type: "html" | "react"
) => {
event.currentTarget.blur();
setCopied(type);
const data = snippets[type];
data && navigator.clipboard.writeText(data); data && navigator.clipboard.writeText(data);
}; };
const handleDownloadSVG = () => { const handleDownloadSVG = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.currentTarget.blur();
if (!ref.current?.outerHTML) return; if (!ref.current?.outerHTML) return;
const blob = new Blob([ref.current.outerHTML]); const blob = new Blob([ref.current.outerHTML]);
saveAs(blob, `${name}.svg`); saveAs(blob, `${name}.svg`);
}; };
const handleCopySVG = () => { const handleCopySVG = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.currentTarget.blur();
setCopied("svg");
ref.current && navigator.clipboard.writeText(ref.current.outerHTML); ref.current && navigator.clipboard.writeText(ref.current.outerHTML);
}; };
@@ -79,24 +108,32 @@ const InfoPanel: React.FC<InfoPanelProps> = (props) => {
<div className="snippet"> <div className="snippet">
HTML/CSS HTML/CSS
<pre style={{ color: "black" }}> <pre style={{ color: "black" }}>
{htmlString} {snippets.html}
<button <button
title="Copy snippet" title="Copy snippet"
onClick={() => handleCopySnippet(htmlString)} onClick={(e) => handleCopySnippet(e, "html")}
> >
<Copy size={24} color="currentColor" weight="regular" /> {copied === "html" ? (
<CheckCircle size={24} color="#1FA647" weight="fill" />
) : (
<Copy size={24} color="currentColor" weight="regular" />
)}
</button> </button>
</pre> </pre>
</div> </div>
<div className="snippet"> <div className="snippet">
React React
<pre style={{ color: "black" }}> <pre style={{ color: "black" }}>
{reactString} {snippets.react}
<button <button
title="Copy snippet" title="Copy snippet"
onClick={() => handleCopySnippet(reactString)} onClick={(e) => handleCopySnippet(e, "react")}
> >
<Copy size={24} color="currentColor" weight="regular" /> {copied === "react" ? (
<CheckCircle size={24} color="#1FA647" weight="fill" />
) : (
<Copy size={24} color="currentColor" weight="regular" />
)}
</button> </button>
</pre> </pre>
</div> </div>
@@ -117,16 +154,34 @@ const InfoPanel: React.FC<InfoPanelProps> = (props) => {
style={{ color: isDark ? "white" : "black" }} style={{ color: isDark ? "white" : "black" }}
onClick={handleCopySVG} onClick={handleCopySVG}
> >
<Copy {copied === "svg" ? (
size={32} <CheckCircle
style={{ marginRight: 8 }} size={32}
color="currentColor" style={{ marginRight: 8 }}
weight="regular" color="#1FA647"
/>{" "} weight="fill"
Copy SVG />
) : (
<Copy
size={32}
style={{ marginRight: 8 }}
color="currentColor"
weight="regular"
/>
)}
{copied === "svg" ? "Copied!" : "Copy SVG"}
</button> </button>
</div> </div>
</div> </div>
<div className="close">
<Prohibit
className="close-icon"
color="currentColor"
size={32}
weight="regular"
onClick={() => setOpen(false)}
/>
</div>
</motion.section> </motion.section>
); );
}; };