35 lines
1.6 KiB
TypeScript
35 lines
1.6 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { copyText } from "../lib/clipboard";
|
|
import { notify } from "../lib/notifications";
|
|
|
|
// How long a Copy button reads "Copied" after it worked.
|
|
const COPIED_MS = 2000;
|
|
|
|
/** The feedback of a Copy button that sits beside the value it copies (a token, a share link, a
|
|
* set-password link), the same at every such button: it reads "Copied" for a moment, and a notice
|
|
* says it too — the notices are a live region, so a screen reader hears what a swapped label would
|
|
* not tell it. A copy that failed is a warning, not an error: the value is on screen and
|
|
* selectable, so it is a convenience that failed, not the action.
|
|
*
|
|
* A feature file, not `ui/`: it speaks the app's notifications (as `copyItems` does). */
|
|
export function useCopyFeedback(): { copied: boolean; copy: (value: string) => Promise<void> } {
|
|
const { t } = useTranslation();
|
|
const [copied, setCopied] = useState(false);
|
|
// One reset at a time: a copy again within the moment starts it over, not cut short by the last.
|
|
const timer = useRef<number | undefined>(undefined);
|
|
useEffect(() => () => window.clearTimeout(timer.current), []);
|
|
const copy = async (value: string) => {
|
|
if (await copyText(value)) {
|
|
setCopied(true);
|
|
notify({ level: "success", message: t("common.copy.done") });
|
|
window.clearTimeout(timer.current);
|
|
timer.current = window.setTimeout(() => setCopied(false), COPIED_MS);
|
|
return;
|
|
}
|
|
notify({ level: "warning", message: t("common.copy.failed") });
|
|
};
|
|
return { copied, copy };
|
|
}
|