@charcuterie/ui 3.11.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CopyButton/CopyButton.d.ts +67 -0
- package/dist/CopyButton/CopyButton.d.ts.map +1 -0
- package/dist/CopyButton/CopyButton.js +76 -0
- package/dist/CopyButton/CopyButton.js.map +1 -0
- package/dist/CopyButton/copyText.d.ts +23 -0
- package/dist/CopyButton/copyText.d.ts.map +1 -0
- package/dist/CopyButton/copyText.js +65 -0
- package/dist/CopyButton/copyText.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/CopyButton/CopyButton.mdx +84 -0
- package/src/CopyButton/CopyButton.stories.tsx +111 -0
- package/src/CopyButton/CopyButton.test.tsx +227 -0
- package/src/CopyButton/CopyButton.tsx +155 -0
- package/src/CopyButton/copyText.ts +68 -0
- package/src/index.ts +6 -0
- package/src/sourceRules.test.ts +7 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ButtonProps } from "../Button/Button.tsx";
|
|
3
|
+
/** Where the button is in the one action it performs. */
|
|
4
|
+
export type CopyStatus = "copied" | "failed" | "idle";
|
|
5
|
+
export type CopyButtonProps = Omit<ButtonProps, "children" | "isLoading" | "loadingLabel" | "onCopy"> & {
|
|
6
|
+
/**
|
|
7
|
+
* How long the confirmation stays up, in milliseconds.
|
|
8
|
+
*
|
|
9
|
+
* Long enough to be read after the eye has moved back to the form
|
|
10
|
+
* being filled in, short enough that a second copy of a *different*
|
|
11
|
+
* value is not mistaken for the first one's tick.
|
|
12
|
+
*/
|
|
13
|
+
confirmDuration?: number;
|
|
14
|
+
/** Resting label. A word, not a glyph — this library ships no icons. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
/** Shown while the copy is confirmed. */
|
|
17
|
+
copiedLabel?: ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* The clipboard write, injected. Swapped in tests and in a host
|
|
20
|
+
* that owns its own clipboard bridge (Electron, a kiosk shell).
|
|
21
|
+
*/
|
|
22
|
+
copy?: (value: string) => Promise<boolean> | boolean;
|
|
23
|
+
/** Shown when the clipboard refused. */
|
|
24
|
+
failedLabel?: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* Told what actually happened, every press.
|
|
27
|
+
*
|
|
28
|
+
* `isCopied` is the point of the callback rather than a detail: a
|
|
29
|
+
* consumer that files the mail away on a copy must not file it away
|
|
30
|
+
* on a copy the browser refused.
|
|
31
|
+
*/
|
|
32
|
+
onCopy?: (isCopied: boolean, value: string) => void;
|
|
33
|
+
/** The string that goes on the clipboard. */
|
|
34
|
+
value: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A button that puts a string on the clipboard and says so.
|
|
38
|
+
*
|
|
39
|
+
* From a consumer, like `Alert` and `BadgeButton` before it: four
|
|
40
|
+
* repos spell this shape by hand — mux-magic's YAML dialog flips its
|
|
41
|
+
* own label on a `setTimeout`, spoolbuddy carries a 15-line
|
|
42
|
+
* `copyToClipboard` with the `execCommand` fallback inline, bambuddy
|
|
43
|
+
* copies a download link from two menus, and mail-sifter's
|
|
44
|
+
* verification-code banner is the fourth. None of the four agree on
|
|
45
|
+
* what happens when the clipboard says no, and three of them cannot
|
|
46
|
+
* tell.
|
|
47
|
+
*
|
|
48
|
+
* **The confirmation is the button, not a toast.** The press and the
|
|
49
|
+
* proof are the same object, so the eye does not travel; a toast is
|
|
50
|
+
* right for something that happened *elsewhere*. A consumer that
|
|
51
|
+
* wants both still gets `onCopy`.
|
|
52
|
+
*
|
|
53
|
+
* **The label is a word.** `Copy` → `Copied`, and the intent goes
|
|
54
|
+
* with it, because a colour change alone is not a message and a tick
|
|
55
|
+
* glyph is an icon this library does not ship.
|
|
56
|
+
*
|
|
57
|
+
* **It announces.** A screen reader is not reliably told that a
|
|
58
|
+
* focused button's own name changed, so the outcome is also written
|
|
59
|
+
* into a `role="status"` region — and the failure says what to do
|
|
60
|
+
* next rather than only that it went wrong.
|
|
61
|
+
*
|
|
62
|
+
* **The timer is cancelled on unmount and on a re-press**, which is
|
|
63
|
+
* the bug in two of the four hand-rolled copies: press, navigate
|
|
64
|
+
* away, and a `setState` lands on nothing.
|
|
65
|
+
*/
|
|
66
|
+
export declare const CopyButton: ({ appearance, children, confirmDuration, copiedLabel, copy, failedLabel, intent, onClick, onCopy, value, ...buttonProps }: CopyButtonProps) => ReactNode;
|
|
67
|
+
//# sourceMappingURL=CopyButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopyButton.d.ts","sourceRoot":"","sources":["../../src/CopyButton/CopyButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAKvD,yDAAyD;AACzD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,WAAW,EACX,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,QAAQ,CACrD,GAAG;IACF;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,yCAAyC;IACzC,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IACpD,wCAAwC;IACxC,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACnD,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,UAAU,GAAI,2HAYxB,eAAe,KAAG,SAmEpB,CAAA"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button } from "../Button/Button.js";
|
|
4
|
+
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
|
|
5
|
+
import { copyText as defaultCopyText } from "./copyText.js";
|
|
6
|
+
/**
|
|
7
|
+
* A button that puts a string on the clipboard and says so.
|
|
8
|
+
*
|
|
9
|
+
* From a consumer, like `Alert` and `BadgeButton` before it: four
|
|
10
|
+
* repos spell this shape by hand — mux-magic's YAML dialog flips its
|
|
11
|
+
* own label on a `setTimeout`, spoolbuddy carries a 15-line
|
|
12
|
+
* `copyToClipboard` with the `execCommand` fallback inline, bambuddy
|
|
13
|
+
* copies a download link from two menus, and mail-sifter's
|
|
14
|
+
* verification-code banner is the fourth. None of the four agree on
|
|
15
|
+
* what happens when the clipboard says no, and three of them cannot
|
|
16
|
+
* tell.
|
|
17
|
+
*
|
|
18
|
+
* **The confirmation is the button, not a toast.** The press and the
|
|
19
|
+
* proof are the same object, so the eye does not travel; a toast is
|
|
20
|
+
* right for something that happened *elsewhere*. A consumer that
|
|
21
|
+
* wants both still gets `onCopy`.
|
|
22
|
+
*
|
|
23
|
+
* **The label is a word.** `Copy` → `Copied`, and the intent goes
|
|
24
|
+
* with it, because a colour change alone is not a message and a tick
|
|
25
|
+
* glyph is an icon this library does not ship.
|
|
26
|
+
*
|
|
27
|
+
* **It announces.** A screen reader is not reliably told that a
|
|
28
|
+
* focused button's own name changed, so the outcome is also written
|
|
29
|
+
* into a `role="status"` region — and the failure says what to do
|
|
30
|
+
* next rather than only that it went wrong.
|
|
31
|
+
*
|
|
32
|
+
* **The timer is cancelled on unmount and on a re-press**, which is
|
|
33
|
+
* the bug in two of the four hand-rolled copies: press, navigate
|
|
34
|
+
* away, and a `setState` lands on nothing.
|
|
35
|
+
*/
|
|
36
|
+
export const CopyButton = ({ appearance = "solid", children = "Copy", confirmDuration = 2000, copiedLabel = "Copied", copy = defaultCopyText, failedLabel = "Copy failed", intent = "accent", onClick, onCopy, value, ...buttonProps }) => {
|
|
37
|
+
const [status, setStatus] = useState("idle");
|
|
38
|
+
const timerRef = useRef(undefined);
|
|
39
|
+
// One cleanup covers both the unmount and the re-press: every path
|
|
40
|
+
// that starts a timer clears the previous one first.
|
|
41
|
+
useEffect(() => () => {
|
|
42
|
+
clearTimeout(timerRef.current);
|
|
43
|
+
}, []);
|
|
44
|
+
const settle = (next) => {
|
|
45
|
+
setStatus(next);
|
|
46
|
+
clearTimeout(timerRef.current);
|
|
47
|
+
timerRef.current = setTimeout(() => {
|
|
48
|
+
setStatus("idle");
|
|
49
|
+
}, confirmDuration);
|
|
50
|
+
};
|
|
51
|
+
const handleClick = (event) => {
|
|
52
|
+
onClick?.(event);
|
|
53
|
+
if (event.defaultPrevented)
|
|
54
|
+
return;
|
|
55
|
+
void (async () => {
|
|
56
|
+
const isCopied = await copy(value);
|
|
57
|
+
settle(isCopied ? "copied" : "failed");
|
|
58
|
+
onCopy?.(isCopied, value);
|
|
59
|
+
})();
|
|
60
|
+
};
|
|
61
|
+
const label = status === "copied"
|
|
62
|
+
? copiedLabel
|
|
63
|
+
: status === "failed"
|
|
64
|
+
? failedLabel
|
|
65
|
+
: children;
|
|
66
|
+
return (_jsxs(_Fragment, { children: [_jsx(Button, { ...buttonProps, appearance: appearance, intent: status === "copied"
|
|
67
|
+
? "success"
|
|
68
|
+
: status === "failed"
|
|
69
|
+
? "danger"
|
|
70
|
+
: intent, onClick: handleClick, children: label }), _jsx(VisuallyHidden, { role: "status", children: status === "copied"
|
|
71
|
+
? "Copied to the clipboard."
|
|
72
|
+
: status === "failed"
|
|
73
|
+
? "The clipboard refused. Select the value and press Control C."
|
|
74
|
+
: "" })] }));
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=CopyButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopyButton.js","sourceRoot":"","sources":["../../src/CopyButton/CopyButton.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEnD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAsC,CAAA;AACrE,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,eAAe,CAAA;AAwC3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,UAAU,GAAG,OAAO,EACpB,QAAQ,GAAG,MAAM,EACjB,eAAe,GAAG,IAAI,EACtB,WAAW,GAAG,QAAQ,EACtB,IAAI,GAAG,eAAe,EACtB,WAAW,GAAG,aAAa,EAC3B,MAAM,GAAG,QAAQ,EACjB,OAAO,EACP,MAAM,EACN,KAAK,EACL,GAAG,WAAW,EACE,EAAa,EAAE;IAC/B,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAa,MAAM,CAAC,CAAA;IACxD,MAAM,QAAQ,GACZ,MAAM,CAAgC,SAAS,CAAC,CAAA;IAElD,mEAAmE;IACnE,qDAAqD;IACrD,SAAS,CACP,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IAChC,CAAC,EACD,EAAE,CACH,CAAA;IAED,MAAM,MAAM,GAAG,CAAC,IAAgB,EAAE,EAAE;QAClC,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAC9B,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,SAAS,CAAC,MAAM,CAAC,CAAA;QACnB,CAAC,EAAE,eAAe,CAAC,CAAA;IACrB,CAAC,CAAA;IAED,MAAM,WAAW,GAAwC,CACvD,KAAK,EACL,EAAE;QACF,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;QAChB,IAAI,KAAK,CAAC,gBAAgB;YAAE,OAAM;QAElC,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAA;YAClC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;YACtC,MAAM,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;QAC3B,CAAC,CAAC,EAAE,CAAA;IACN,CAAC,CAAA;IAED,MAAM,KAAK,GACT,MAAM,KAAK,QAAQ;QACjB,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,MAAM,KAAK,QAAQ;YACnB,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,QAAQ,CAAA;IAEhB,OAAO,CACL,8BACE,KAAC,MAAM,OACD,WAAW,EACf,UAAU,EAAE,UAAU,EACtB,MAAM,EACJ,MAAM,KAAK,QAAQ;oBACjB,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,MAAM,KAAK,QAAQ;wBACnB,CAAC,CAAC,QAAQ;wBACV,CAAC,CAAC,MAAM,EAEd,OAAO,EAAE,WAAW,YAEnB,KAAK,GACC,EACT,KAAC,cAAc,IAAC,IAAI,EAAC,QAAQ,YAC1B,MAAM,KAAK,QAAQ;oBAClB,CAAC,CAAC,0BAA0B;oBAC5B,CAAC,CAAC,MAAM,KAAK,QAAQ;wBACnB,CAAC,CAAC,8DAA8D;wBAChE,CAAC,CAAC,EAAE,GACO,IAChB,CACJ,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Put a string on the clipboard, and say whether it landed.
|
|
3
|
+
*
|
|
4
|
+
* Three routes, tried in order, because the fleet runs in all three
|
|
5
|
+
* conditions:
|
|
6
|
+
*
|
|
7
|
+
* 1. **`navigator.clipboard.writeText`.** The real API. It exists
|
|
8
|
+
* only in a *secure context*, which a `devshare` URL and
|
|
9
|
+
* `localhost` both are — and a plain `http://storeman.octen:8080`
|
|
10
|
+
* dev server is not.
|
|
11
|
+
* 2. **A hidden `<textarea>` plus `document.execCommand("copy")`.**
|
|
12
|
+
* Deprecated, and kept anyway: it is the only thing that works in
|
|
13
|
+
* an insecure context, spoolbuddy already hand-rolls exactly this
|
|
14
|
+
* block, and the alternative is a Copy button that silently does
|
|
15
|
+
* nothing on the household's own LAN.
|
|
16
|
+
* 3. **Give up and report it.** A caller that marks mail done on a
|
|
17
|
+
* copy must not mark it done on a copy that never happened.
|
|
18
|
+
*
|
|
19
|
+
* Never throws. The boolean is the whole result, so a call site is an
|
|
20
|
+
* `if` rather than a `try`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const copyText: (value: string) => Promise<boolean>;
|
|
23
|
+
//# sourceMappingURL=copyText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copyText.d.ts","sourceRoot":"","sources":["../../src/CopyButton/copyText.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,QAAQ,GACnB,OAAO,MAAM,KACZ,OAAO,CAAC,OAAO,CAWjB,CAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Put a string on the clipboard, and say whether it landed.
|
|
3
|
+
*
|
|
4
|
+
* Three routes, tried in order, because the fleet runs in all three
|
|
5
|
+
* conditions:
|
|
6
|
+
*
|
|
7
|
+
* 1. **`navigator.clipboard.writeText`.** The real API. It exists
|
|
8
|
+
* only in a *secure context*, which a `devshare` URL and
|
|
9
|
+
* `localhost` both are — and a plain `http://storeman.octen:8080`
|
|
10
|
+
* dev server is not.
|
|
11
|
+
* 2. **A hidden `<textarea>` plus `document.execCommand("copy")`.**
|
|
12
|
+
* Deprecated, and kept anyway: it is the only thing that works in
|
|
13
|
+
* an insecure context, spoolbuddy already hand-rolls exactly this
|
|
14
|
+
* block, and the alternative is a Copy button that silently does
|
|
15
|
+
* nothing on the household's own LAN.
|
|
16
|
+
* 3. **Give up and report it.** A caller that marks mail done on a
|
|
17
|
+
* copy must not mark it done on a copy that never happened.
|
|
18
|
+
*
|
|
19
|
+
* Never throws. The boolean is the whole result, so a call site is an
|
|
20
|
+
* `if` rather than a `try`.
|
|
21
|
+
*/
|
|
22
|
+
export const copyText = async (value) => {
|
|
23
|
+
try {
|
|
24
|
+
// `writeText` rejects on a denied permission and on a document
|
|
25
|
+
// that is not focused — both real, both recoverable below.
|
|
26
|
+
await globalThis.navigator?.clipboard?.writeText(value);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Fall through to the legacy route.
|
|
31
|
+
}
|
|
32
|
+
return copyBySelection(value);
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The pre-Clipboard-API route.
|
|
36
|
+
*
|
|
37
|
+
* The textarea is positioned off-screen rather than hidden: a
|
|
38
|
+
* `display: none` element has no selection to copy, and
|
|
39
|
+
* `visibility: hidden` is no better. `readOnly` stops the mobile
|
|
40
|
+
* keyboard appearing for the frame it is in the document.
|
|
41
|
+
*/
|
|
42
|
+
const copyBySelection = (value) => {
|
|
43
|
+
const owner = globalThis.document;
|
|
44
|
+
if (!owner?.body)
|
|
45
|
+
return false;
|
|
46
|
+
const field = owner.createElement("textarea");
|
|
47
|
+
field.value = value;
|
|
48
|
+
field.readOnly = true;
|
|
49
|
+
field.setAttribute("aria-hidden", "true");
|
|
50
|
+
field.style.position = "fixed";
|
|
51
|
+
field.style.insetInlineStart = "-9999px";
|
|
52
|
+
field.style.top = "0";
|
|
53
|
+
owner.body.append(field);
|
|
54
|
+
try {
|
|
55
|
+
field.select();
|
|
56
|
+
return owner.execCommand("copy");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
field.remove();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=copyText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copyText.js","sourceRoot":"","sources":["../../src/CopyButton/copyText.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAC3B,KAAa,EACK,EAAE;IACpB,IAAI,CAAC;QACH,+DAA+D;QAC/D,2DAA2D;QAC3D,MAAM,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,CAAA;QACvD,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,oCAAoC;IACtC,CAAC;IAED,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;AAC/B,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG,CAAC,KAAa,EAAW,EAAE;IACjD,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAA;IACjC,IAAI,CAAC,KAAK,EAAE,IAAI;QAAE,OAAO,KAAK,CAAA;IAE9B,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,CAAA;IAC7C,KAAK,CAAC,KAAK,GAAG,KAAK,CAAA;IACnB,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;IACrB,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;IACzC,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,OAAO,CAAA;IAC9B,KAAK,CAAC,KAAK,CAAC,gBAAgB,GAAG,SAAS,CAAA;IACxC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAA;IAErB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAExB,IAAI,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,CAAA;QAEd,OAAO,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,MAAM,EAAE,CAAA;IAChB,CAAC;AACH,CAAC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,9 @@ export type { ColorSchemeIcons, ColorSchemeToggleProps, } from "./ColorSchemeTog
|
|
|
66
66
|
export { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.tsx";
|
|
67
67
|
export type { ComboboxProps } from "./Combobox/Combobox.tsx";
|
|
68
68
|
export { Combobox } from "./Combobox/Combobox.tsx";
|
|
69
|
+
export type { CopyButtonProps, CopyStatus, } from "./CopyButton/CopyButton.tsx";
|
|
70
|
+
export { CopyButton } from "./CopyButton/CopyButton.tsx";
|
|
71
|
+
export { copyText } from "./CopyButton/copyText.ts";
|
|
69
72
|
export { CATEGORICAL_APPEARANCE_CLASS, CATEGORICAL_CONTENT_CLASS, CATEGORICAL_HOVER_CLASS, CATEGORICAL_SOLID_FILL_CLASS, } from "./categoricalStyles.ts";
|
|
70
73
|
export type { BadgeSize } from "./controlStyles.ts";
|
|
71
74
|
export { BADGE_SIZE_CLASS, CONTROL_BASE_CLASS, CONTROL_SIZE_CLASS, DOT_SIZE_CLASS, getControlClassName, ICON_CONTROL_SIZE_CLASS, MIN_TOUCH_TARGET_CLASS, SPINNER_SIZE_CLASS, } from "./controlStyles.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,8 @@ export { Checkbox } from "./Checkbox/Checkbox.js";
|
|
|
46
46
|
export { ColorSchemeSwitcher } from "./ColorSchemeSwitcher/ColorSchemeSwitcher.js";
|
|
47
47
|
export { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.js";
|
|
48
48
|
export { Combobox } from "./Combobox/Combobox.js";
|
|
49
|
+
export { CopyButton } from "./CopyButton/CopyButton.js";
|
|
50
|
+
export { copyText } from "./CopyButton/copyText.js";
|
|
49
51
|
export { CATEGORICAL_APPEARANCE_CLASS, CATEGORICAL_CONTENT_CLASS, CATEGORICAL_HOVER_CLASS, CATEGORICAL_SOLID_FILL_CLASS, } from "./categoricalStyles.js";
|
|
50
52
|
export { BADGE_SIZE_CLASS, CONTROL_BASE_CLASS, CONTROL_SIZE_CLASS, DOT_SIZE_CLASS, getControlClassName, ICON_CONTROL_SIZE_CLASS, MIN_TOUCH_TARGET_CLASS, SPINNER_SIZE_CLASS, } from "./controlStyles.js";
|
|
51
53
|
export { DataTable } from "./DataTable/DataTable.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,0BAA0B,EAC1B,iCAAiC,EACjC,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,iCAAiC,CAAA;AAKxC,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAK7C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAGzC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAK3D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AASzC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,cAAc,GACf,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAMxD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAGtC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA2C,CAAA;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
import * as CopyButtonStories from "./CopyButton.stories";
|
|
4
|
+
|
|
5
|
+
<Meta of={CopyButtonStories} />
|
|
6
|
+
|
|
7
|
+
# CopyButton
|
|
8
|
+
|
|
9
|
+
**A button that puts a string on the clipboard and says so.** Press it, the label turns to
|
|
10
|
+
`Copied`, and the caller is told whether that is true.
|
|
11
|
+
|
|
12
|
+
<Canvas of={CopyButtonStories.Default} />
|
|
13
|
+
|
|
14
|
+
## Props
|
|
15
|
+
|
|
16
|
+
<Controls />
|
|
17
|
+
|
|
18
|
+
## Where it came from
|
|
19
|
+
|
|
20
|
+
Four repos spell this shape by hand, and no two agree:
|
|
21
|
+
|
|
22
|
+
| Repo | How it copies | What it does when the clipboard says no |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| mux-magic (`YamlModal`) | flips its own label on a `setTimeout` | nothing — the label still says `Copied!` |
|
|
25
|
+
| spoolbuddy (`Settings`) | 15 lines inline, with the `execCommand` fallback | a toast |
|
|
26
|
+
| bambuddy (`ArchivesPage`) | a menu item, twice in one file | a toast |
|
|
27
|
+
| mail-sifter | a verification-code banner | **marks the mail done anyway** |
|
|
28
|
+
|
|
29
|
+
The last row is why the failure path is a first-class state here rather than a `catch` that
|
|
30
|
+
shrugs. That consumer files the mail away on a copy, so a copy that did not happen has to be
|
|
31
|
+
distinguishable from one that did.
|
|
32
|
+
|
|
33
|
+
## The confirmation is the button
|
|
34
|
+
|
|
35
|
+
The press and the proof are the same object, so the eye does not travel. A toast is the
|
|
36
|
+
right shape for something that happened **somewhere else** — a background job, another pane
|
|
37
|
+
— and the wrong shape for the thing directly under the pointer. A consumer that wants a
|
|
38
|
+
toast as well still gets one from `onCopy`.
|
|
39
|
+
|
|
40
|
+
<Canvas of={CopyButtonStories.Confirmed} />
|
|
41
|
+
|
|
42
|
+
## The label is a word, and so is the failure
|
|
43
|
+
|
|
44
|
+
`Copy` → `Copied`, with the intent moving to `success` underneath it. The colour is not the
|
|
45
|
+
message: a colour change alone is invisible to a screen reader, unreliable for a
|
|
46
|
+
colour-blind reader, and this library [ships no icons](?path=/docs/guides-building-an-app--docs),
|
|
47
|
+
so a tick glyph is not available to carry it either.
|
|
48
|
+
|
|
49
|
+
The refusal says what to do next rather than only that something went wrong.
|
|
50
|
+
|
|
51
|
+
<Canvas of={CopyButtonStories.Refused} />
|
|
52
|
+
|
|
53
|
+
## Three routes to the clipboard
|
|
54
|
+
|
|
55
|
+
`copyText.ts` tries them in order and never throws:
|
|
56
|
+
|
|
57
|
+
1. **`navigator.clipboard.writeText`** — the real API, and it exists only in a *secure
|
|
58
|
+
context*. A `devshare` URL and `localhost` are secure; a plain `http://` dev server on
|
|
59
|
+
the LAN is not.
|
|
60
|
+
2. **A hidden `<textarea>` and `document.execCommand("copy")`** — deprecated, kept because
|
|
61
|
+
it is the only thing that works in that insecure context, and because spoolbuddy already
|
|
62
|
+
carries this exact block inline.
|
|
63
|
+
3. **Report the failure**, so the caller can hold on to whatever it was about to discard.
|
|
64
|
+
|
|
65
|
+
`copy` is injectable, which is how the `Refused` story above exists and how a host with its
|
|
66
|
+
own clipboard bridge — Electron, a kiosk shell — supplies it.
|
|
67
|
+
|
|
68
|
+
## Own labels
|
|
69
|
+
|
|
70
|
+
The labels are content. A surface that copies one specific thing should say which thing.
|
|
71
|
+
|
|
72
|
+
<Canvas of={CopyButtonStories.OwnLabels} />
|
|
73
|
+
|
|
74
|
+
<Canvas of={CopyButtonStories.AllAppearances} />
|
|
75
|
+
|
|
76
|
+
## What it does not do
|
|
77
|
+
|
|
78
|
+
**It does not own the value.** No `children`-as-value, no reading text out of a sibling
|
|
79
|
+
node: the string is a prop, because the thing on screen is regularly not the thing worth
|
|
80
|
+
copying (a masked key, a truncated URL, a code split across two spans).
|
|
81
|
+
|
|
82
|
+
**It does not clear the timer for you and hope.** The confirmation timer is cancelled on
|
|
83
|
+
unmount and on a re-press — two of the four hand-rolled copies set state on an unmounted
|
|
84
|
+
component if you press and navigate away.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { userEvent, within } from "storybook/test"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
StoryCell,
|
|
6
|
+
StoryRow,
|
|
7
|
+
StorySection,
|
|
8
|
+
} from "../board.storyHelpers.tsx"
|
|
9
|
+
import { CopyButton } from "./CopyButton.tsx"
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: "Components/Actions/CopyButton",
|
|
13
|
+
component: CopyButton,
|
|
14
|
+
parameters: { layout: "padded" },
|
|
15
|
+
// The component's own defaults, restated — Storybook does not
|
|
16
|
+
// seed `args` from docgen, so an unstated default shows in the
|
|
17
|
+
// props table with nothing selected in its control.
|
|
18
|
+
args: {
|
|
19
|
+
appearance: "solid",
|
|
20
|
+
confirmDuration: 2000,
|
|
21
|
+
intent: "accent",
|
|
22
|
+
isDisabled: false,
|
|
23
|
+
size: "md",
|
|
24
|
+
value: "335590",
|
|
25
|
+
},
|
|
26
|
+
} satisfies Meta<typeof CopyButton>
|
|
27
|
+
|
|
28
|
+
export default meta
|
|
29
|
+
|
|
30
|
+
type Story = StoryObj<typeof meta>
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The confirmation, held open. `confirmDuration` is enormous here so
|
|
36
|
+
* the state can be read, hovered and screenshotted — a two-second
|
|
37
|
+
* default is right in an app and useless in a docs page.
|
|
38
|
+
*/
|
|
39
|
+
export const Confirmed: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
confirmDuration: 600_000,
|
|
42
|
+
// Injected, not real. A headless browser refuses
|
|
43
|
+
// `navigator.clipboard` outright — the document is not focused —
|
|
44
|
+
// so a story that used the real one would document the *failure*
|
|
45
|
+
// state under a name that promises the success one.
|
|
46
|
+
copy: () => true,
|
|
47
|
+
value: "335590",
|
|
48
|
+
},
|
|
49
|
+
// Driven, not faked: the story presses the button the way a reader
|
|
50
|
+
// would. No assertion — those live in `CopyButton.test.tsx`.
|
|
51
|
+
play: async ({ canvasElement }) => {
|
|
52
|
+
await userEvent.click(
|
|
53
|
+
within(canvasElement).getByRole("button", {
|
|
54
|
+
name: "Copy",
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The clipboard refused. Injected rather than provoked: a story
|
|
62
|
+
* cannot revoke the permission, and the failure path is the half of
|
|
63
|
+
* this component the hand-rolled copies in the fleet do not have.
|
|
64
|
+
*/
|
|
65
|
+
export const Refused: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
confirmDuration: 600_000,
|
|
68
|
+
copy: () => false,
|
|
69
|
+
},
|
|
70
|
+
play: async ({ canvasElement }) => {
|
|
71
|
+
await userEvent.click(
|
|
72
|
+
within(canvasElement).getByRole("button", {
|
|
73
|
+
name: "Copy",
|
|
74
|
+
}),
|
|
75
|
+
)
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Every appearance, so a toolbar's ghost copy is a real option. */
|
|
80
|
+
export const AllAppearances: Story = {
|
|
81
|
+
render: (args) => (
|
|
82
|
+
<StorySection title="Appearances">
|
|
83
|
+
<StoryRow>
|
|
84
|
+
<StoryCell label="solid">
|
|
85
|
+
<CopyButton {...args} appearance="solid" />
|
|
86
|
+
</StoryCell>
|
|
87
|
+
<StoryCell label="soft">
|
|
88
|
+
<CopyButton {...args} appearance="soft" />
|
|
89
|
+
</StoryCell>
|
|
90
|
+
<StoryCell label="outline">
|
|
91
|
+
<CopyButton {...args} appearance="outline" />
|
|
92
|
+
</StoryCell>
|
|
93
|
+
<StoryCell label="ghost">
|
|
94
|
+
<CopyButton {...args} appearance="ghost" />
|
|
95
|
+
</StoryCell>
|
|
96
|
+
</StoryRow>
|
|
97
|
+
</StorySection>
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A caller's own words. The label is content, so a one-off surface
|
|
103
|
+
* that copies something specific says which thing it copies.
|
|
104
|
+
*/
|
|
105
|
+
export const OwnLabels: Story = {
|
|
106
|
+
args: {
|
|
107
|
+
children: "Copy code",
|
|
108
|
+
copiedLabel: "Code copied",
|
|
109
|
+
value: "335590",
|
|
110
|
+
},
|
|
111
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
composeStories,
|
|
3
|
+
composeStory,
|
|
4
|
+
} from "@storybook/react"
|
|
5
|
+
import {
|
|
6
|
+
expect,
|
|
7
|
+
fn,
|
|
8
|
+
userEvent,
|
|
9
|
+
waitFor,
|
|
10
|
+
} from "storybook/test"
|
|
11
|
+
import { test, vi } from "vitest"
|
|
12
|
+
|
|
13
|
+
import { expectNoAxeViolations } from "../expectNoAxeViolations.testHelpers.ts"
|
|
14
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
15
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
16
|
+
import meta, * as stories from "./CopyButton.stories.tsx"
|
|
17
|
+
import { copyText } from "./copyText.ts"
|
|
18
|
+
|
|
19
|
+
const { Confirmed, Default, Refused } =
|
|
20
|
+
composeStories(stories)
|
|
21
|
+
|
|
22
|
+
test("is a button an agent can find by role and name", async () => {
|
|
23
|
+
const { canvas } = await mountStory(Default)
|
|
24
|
+
|
|
25
|
+
const button = expectAgentDrivable(canvas, {
|
|
26
|
+
name: "Copy",
|
|
27
|
+
role: "button",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Never `submit`. A copy control inside a form that reloads the
|
|
31
|
+
// page is a bug every repo in the fleet has shipped once.
|
|
32
|
+
await expect(button).toHaveAttribute("type", "button")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("copies the value and confirms in place", async () => {
|
|
36
|
+
const copy = fn(async () => true)
|
|
37
|
+
|
|
38
|
+
const { canvas } = await mountStory(
|
|
39
|
+
composeStory({ args: { copy, value: "335590" } }, meta),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
await userEvent.click(
|
|
43
|
+
canvas.getByRole("button", { name: "Copy" }),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
await expect(copy).toHaveBeenCalledWith("335590")
|
|
47
|
+
// The press and its proof are the same object — no toast, no
|
|
48
|
+
// second place to look.
|
|
49
|
+
await expect(
|
|
50
|
+
canvas.getByRole("button", { name: "Copied" }),
|
|
51
|
+
).toBeVisible()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The half the four hand-rolled copies in the fleet do not have.
|
|
56
|
+
*
|
|
57
|
+
* A refused clipboard has to *say so*, because the consumer's next
|
|
58
|
+
* move usually destroys the thing that held the value — mail-sifter
|
|
59
|
+
* marks the mail done on a copy.
|
|
60
|
+
*/
|
|
61
|
+
test("says so when the clipboard refuses", async () => {
|
|
62
|
+
const onCopy = fn()
|
|
63
|
+
|
|
64
|
+
const { canvas } = await mountStory(
|
|
65
|
+
composeStory(
|
|
66
|
+
{ args: { copy: () => false, onCopy } },
|
|
67
|
+
meta,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
await userEvent.click(
|
|
72
|
+
canvas.getByRole("button", { name: "Copy" }),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
await expect(
|
|
76
|
+
canvas.getByRole("button", { name: "Copy failed" }),
|
|
77
|
+
).toBeVisible()
|
|
78
|
+
await expect(onCopy).toHaveBeenCalledWith(false, "335590")
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("tells the caller what happened, both ways", async () => {
|
|
82
|
+
const onCopy = fn()
|
|
83
|
+
|
|
84
|
+
const { canvas } = await mountStory(
|
|
85
|
+
composeStory(
|
|
86
|
+
{ args: { copy: async () => true, onCopy } },
|
|
87
|
+
meta,
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
await userEvent.click(
|
|
92
|
+
canvas.getByRole("button", { name: "Copy" }),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
await expect(onCopy).toHaveBeenCalledWith(true, "335590")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A focused button whose own accessible name changes is not
|
|
100
|
+
* reliably announced, so the outcome is written into a live region
|
|
101
|
+
* as well.
|
|
102
|
+
*/
|
|
103
|
+
test("announces the outcome in a status region", async () => {
|
|
104
|
+
const { canvas } = await mountStory(Confirmed)
|
|
105
|
+
|
|
106
|
+
await expect(
|
|
107
|
+
canvas.getByRole("status"),
|
|
108
|
+
).toHaveTextContent("Copied to the clipboard.")
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("the failure says what to do instead", async () => {
|
|
112
|
+
const { canvas } = await mountStory(Refused)
|
|
113
|
+
|
|
114
|
+
await expect(
|
|
115
|
+
canvas.getByRole("status"),
|
|
116
|
+
).toHaveTextContent(
|
|
117
|
+
"The clipboard refused. Select the value and press Control C.",
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
/** The confirmation goes back on its own. */
|
|
122
|
+
test("returns to the resting label", async () => {
|
|
123
|
+
const { canvas } = await mountStory(
|
|
124
|
+
composeStory(
|
|
125
|
+
{ args: { confirmDuration: 20, copy: () => true } },
|
|
126
|
+
meta,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
await userEvent.click(
|
|
131
|
+
canvas.getByRole("button", { name: "Copy" }),
|
|
132
|
+
)
|
|
133
|
+
await expect(
|
|
134
|
+
canvas.getByRole("button", { name: "Copied" }),
|
|
135
|
+
).toBeVisible()
|
|
136
|
+
|
|
137
|
+
await waitFor(async () => {
|
|
138
|
+
await expect(
|
|
139
|
+
canvas.getByRole("button", { name: "Copy" }),
|
|
140
|
+
).toBeVisible()
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* A caller's `onClick` runs first and can call it off — the escape
|
|
146
|
+
* hatch for "confirm before this leaves the screen".
|
|
147
|
+
*/
|
|
148
|
+
test("a prevented click copies nothing", async () => {
|
|
149
|
+
const copy = fn(() => true)
|
|
150
|
+
|
|
151
|
+
const { canvas } = await mountStory(
|
|
152
|
+
composeStory(
|
|
153
|
+
{
|
|
154
|
+
args: {
|
|
155
|
+
copy,
|
|
156
|
+
onClick: (event: MouseEvent) => {
|
|
157
|
+
event.preventDefault()
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
meta,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
await userEvent.click(
|
|
166
|
+
canvas.getByRole("button", { name: "Copy" }),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
await expect(copy).not.toHaveBeenCalled()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test("has no axe violations", async () => {
|
|
173
|
+
const { canvasElement } = await mountStory(Default)
|
|
174
|
+
|
|
175
|
+
await expectNoAxeViolations(canvasElement)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* `copyText`'s own contract, exercised here rather than in a
|
|
180
|
+
* node-side `.ts` file, because it is a DOM routine and the `ui`
|
|
181
|
+
* project has no DOM.
|
|
182
|
+
*
|
|
183
|
+
* The real clipboard is unavailable in this chromium — the document
|
|
184
|
+
* is not focused, so `navigator.clipboard.writeText` rejects — which
|
|
185
|
+
* makes this an honest test of the fallback chain rather than of the
|
|
186
|
+
* happy path.
|
|
187
|
+
*/
|
|
188
|
+
test("copyText reports failure rather than throwing", async () => {
|
|
189
|
+
const execCommand = vi
|
|
190
|
+
.spyOn(document, "execCommand")
|
|
191
|
+
.mockReturnValue(false)
|
|
192
|
+
|
|
193
|
+
await expect(copyText("335590")).resolves.toBe(false)
|
|
194
|
+
|
|
195
|
+
execCommand.mockRestore()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test("copyText falls back to the selection route", async () => {
|
|
199
|
+
const execCommand = vi
|
|
200
|
+
.spyOn(document, "execCommand")
|
|
201
|
+
.mockReturnValue(true)
|
|
202
|
+
|
|
203
|
+
await expect(copyText("335590")).resolves.toBe(true)
|
|
204
|
+
await expect(execCommand).toHaveBeenCalledWith("copy")
|
|
205
|
+
|
|
206
|
+
execCommand.mockRestore()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The textarea is a means, not a side effect. A leaked one is a
|
|
211
|
+
* focusable, screen-reader-visible node holding the copied value,
|
|
212
|
+
* left in the document for good.
|
|
213
|
+
*/
|
|
214
|
+
test("copyText leaves no textarea behind", async () => {
|
|
215
|
+
const execCommand = vi
|
|
216
|
+
.spyOn(document, "execCommand")
|
|
217
|
+
.mockImplementation(() => {
|
|
218
|
+
throw new Error("refused mid-copy")
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
await expect(copyText("335590")).resolves.toBe(false)
|
|
222
|
+
await expect(
|
|
223
|
+
document.querySelectorAll("textarea"),
|
|
224
|
+
).toHaveLength(0)
|
|
225
|
+
|
|
226
|
+
execCommand.mockRestore()
|
|
227
|
+
})
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { useEffect, useRef, useState } from "react"
|
|
3
|
+
import type { ButtonProps } from "../Button/Button.tsx"
|
|
4
|
+
import { Button } from "../Button/Button.tsx"
|
|
5
|
+
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.tsx"
|
|
6
|
+
import { copyText as defaultCopyText } from "./copyText.ts"
|
|
7
|
+
|
|
8
|
+
/** Where the button is in the one action it performs. */
|
|
9
|
+
export type CopyStatus = "copied" | "failed" | "idle"
|
|
10
|
+
|
|
11
|
+
export type CopyButtonProps = Omit<
|
|
12
|
+
ButtonProps,
|
|
13
|
+
"children" | "isLoading" | "loadingLabel" | "onCopy"
|
|
14
|
+
> & {
|
|
15
|
+
/**
|
|
16
|
+
* How long the confirmation stays up, in milliseconds.
|
|
17
|
+
*
|
|
18
|
+
* Long enough to be read after the eye has moved back to the form
|
|
19
|
+
* being filled in, short enough that a second copy of a *different*
|
|
20
|
+
* value is not mistaken for the first one's tick.
|
|
21
|
+
*/
|
|
22
|
+
confirmDuration?: number
|
|
23
|
+
/** Resting label. A word, not a glyph — this library ships no icons. */
|
|
24
|
+
children?: ReactNode
|
|
25
|
+
/** Shown while the copy is confirmed. */
|
|
26
|
+
copiedLabel?: ReactNode
|
|
27
|
+
/**
|
|
28
|
+
* The clipboard write, injected. Swapped in tests and in a host
|
|
29
|
+
* that owns its own clipboard bridge (Electron, a kiosk shell).
|
|
30
|
+
*/
|
|
31
|
+
copy?: (value: string) => Promise<boolean> | boolean
|
|
32
|
+
/** Shown when the clipboard refused. */
|
|
33
|
+
failedLabel?: ReactNode
|
|
34
|
+
/**
|
|
35
|
+
* Told what actually happened, every press.
|
|
36
|
+
*
|
|
37
|
+
* `isCopied` is the point of the callback rather than a detail: a
|
|
38
|
+
* consumer that files the mail away on a copy must not file it away
|
|
39
|
+
* on a copy the browser refused.
|
|
40
|
+
*/
|
|
41
|
+
onCopy?: (isCopied: boolean, value: string) => void
|
|
42
|
+
/** The string that goes on the clipboard. */
|
|
43
|
+
value: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A button that puts a string on the clipboard and says so.
|
|
48
|
+
*
|
|
49
|
+
* From a consumer, like `Alert` and `BadgeButton` before it: four
|
|
50
|
+
* repos spell this shape by hand — mux-magic's YAML dialog flips its
|
|
51
|
+
* own label on a `setTimeout`, spoolbuddy carries a 15-line
|
|
52
|
+
* `copyToClipboard` with the `execCommand` fallback inline, bambuddy
|
|
53
|
+
* copies a download link from two menus, and mail-sifter's
|
|
54
|
+
* verification-code banner is the fourth. None of the four agree on
|
|
55
|
+
* what happens when the clipboard says no, and three of them cannot
|
|
56
|
+
* tell.
|
|
57
|
+
*
|
|
58
|
+
* **The confirmation is the button, not a toast.** The press and the
|
|
59
|
+
* proof are the same object, so the eye does not travel; a toast is
|
|
60
|
+
* right for something that happened *elsewhere*. A consumer that
|
|
61
|
+
* wants both still gets `onCopy`.
|
|
62
|
+
*
|
|
63
|
+
* **The label is a word.** `Copy` → `Copied`, and the intent goes
|
|
64
|
+
* with it, because a colour change alone is not a message and a tick
|
|
65
|
+
* glyph is an icon this library does not ship.
|
|
66
|
+
*
|
|
67
|
+
* **It announces.** A screen reader is not reliably told that a
|
|
68
|
+
* focused button's own name changed, so the outcome is also written
|
|
69
|
+
* into a `role="status"` region — and the failure says what to do
|
|
70
|
+
* next rather than only that it went wrong.
|
|
71
|
+
*
|
|
72
|
+
* **The timer is cancelled on unmount and on a re-press**, which is
|
|
73
|
+
* the bug in two of the four hand-rolled copies: press, navigate
|
|
74
|
+
* away, and a `setState` lands on nothing.
|
|
75
|
+
*/
|
|
76
|
+
export const CopyButton = ({
|
|
77
|
+
appearance = "solid",
|
|
78
|
+
children = "Copy",
|
|
79
|
+
confirmDuration = 2000,
|
|
80
|
+
copiedLabel = "Copied",
|
|
81
|
+
copy = defaultCopyText,
|
|
82
|
+
failedLabel = "Copy failed",
|
|
83
|
+
intent = "accent",
|
|
84
|
+
onClick,
|
|
85
|
+
onCopy,
|
|
86
|
+
value,
|
|
87
|
+
...buttonProps
|
|
88
|
+
}: CopyButtonProps): ReactNode => {
|
|
89
|
+
const [status, setStatus] = useState<CopyStatus>("idle")
|
|
90
|
+
const timerRef =
|
|
91
|
+
useRef<ReturnType<typeof setTimeout>>(undefined)
|
|
92
|
+
|
|
93
|
+
// One cleanup covers both the unmount and the re-press: every path
|
|
94
|
+
// that starts a timer clears the previous one first.
|
|
95
|
+
useEffect(
|
|
96
|
+
() => () => {
|
|
97
|
+
clearTimeout(timerRef.current)
|
|
98
|
+
},
|
|
99
|
+
[],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const settle = (next: CopyStatus) => {
|
|
103
|
+
setStatus(next)
|
|
104
|
+
clearTimeout(timerRef.current)
|
|
105
|
+
timerRef.current = setTimeout(() => {
|
|
106
|
+
setStatus("idle")
|
|
107
|
+
}, confirmDuration)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const handleClick: NonNullable<ButtonProps["onClick"]> = (
|
|
111
|
+
event,
|
|
112
|
+
) => {
|
|
113
|
+
onClick?.(event)
|
|
114
|
+
if (event.defaultPrevented) return
|
|
115
|
+
|
|
116
|
+
void (async () => {
|
|
117
|
+
const isCopied = await copy(value)
|
|
118
|
+
settle(isCopied ? "copied" : "failed")
|
|
119
|
+
onCopy?.(isCopied, value)
|
|
120
|
+
})()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const label =
|
|
124
|
+
status === "copied"
|
|
125
|
+
? copiedLabel
|
|
126
|
+
: status === "failed"
|
|
127
|
+
? failedLabel
|
|
128
|
+
: children
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<>
|
|
132
|
+
<Button
|
|
133
|
+
{...buttonProps}
|
|
134
|
+
appearance={appearance}
|
|
135
|
+
intent={
|
|
136
|
+
status === "copied"
|
|
137
|
+
? "success"
|
|
138
|
+
: status === "failed"
|
|
139
|
+
? "danger"
|
|
140
|
+
: intent
|
|
141
|
+
}
|
|
142
|
+
onClick={handleClick}
|
|
143
|
+
>
|
|
144
|
+
{label}
|
|
145
|
+
</Button>
|
|
146
|
+
<VisuallyHidden role="status">
|
|
147
|
+
{status === "copied"
|
|
148
|
+
? "Copied to the clipboard."
|
|
149
|
+
: status === "failed"
|
|
150
|
+
? "The clipboard refused. Select the value and press Control C."
|
|
151
|
+
: ""}
|
|
152
|
+
</VisuallyHidden>
|
|
153
|
+
</>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Put a string on the clipboard, and say whether it landed.
|
|
3
|
+
*
|
|
4
|
+
* Three routes, tried in order, because the fleet runs in all three
|
|
5
|
+
* conditions:
|
|
6
|
+
*
|
|
7
|
+
* 1. **`navigator.clipboard.writeText`.** The real API. It exists
|
|
8
|
+
* only in a *secure context*, which a `devshare` URL and
|
|
9
|
+
* `localhost` both are — and a plain `http://storeman.octen:8080`
|
|
10
|
+
* dev server is not.
|
|
11
|
+
* 2. **A hidden `<textarea>` plus `document.execCommand("copy")`.**
|
|
12
|
+
* Deprecated, and kept anyway: it is the only thing that works in
|
|
13
|
+
* an insecure context, spoolbuddy already hand-rolls exactly this
|
|
14
|
+
* block, and the alternative is a Copy button that silently does
|
|
15
|
+
* nothing on the household's own LAN.
|
|
16
|
+
* 3. **Give up and report it.** A caller that marks mail done on a
|
|
17
|
+
* copy must not mark it done on a copy that never happened.
|
|
18
|
+
*
|
|
19
|
+
* Never throws. The boolean is the whole result, so a call site is an
|
|
20
|
+
* `if` rather than a `try`.
|
|
21
|
+
*/
|
|
22
|
+
export const copyText = async (
|
|
23
|
+
value: string,
|
|
24
|
+
): Promise<boolean> => {
|
|
25
|
+
try {
|
|
26
|
+
// `writeText` rejects on a denied permission and on a document
|
|
27
|
+
// that is not focused — both real, both recoverable below.
|
|
28
|
+
await globalThis.navigator?.clipboard?.writeText(value)
|
|
29
|
+
return true
|
|
30
|
+
} catch {
|
|
31
|
+
// Fall through to the legacy route.
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return copyBySelection(value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The pre-Clipboard-API route.
|
|
39
|
+
*
|
|
40
|
+
* The textarea is positioned off-screen rather than hidden: a
|
|
41
|
+
* `display: none` element has no selection to copy, and
|
|
42
|
+
* `visibility: hidden` is no better. `readOnly` stops the mobile
|
|
43
|
+
* keyboard appearing for the frame it is in the document.
|
|
44
|
+
*/
|
|
45
|
+
const copyBySelection = (value: string): boolean => {
|
|
46
|
+
const owner = globalThis.document
|
|
47
|
+
if (!owner?.body) return false
|
|
48
|
+
|
|
49
|
+
const field = owner.createElement("textarea")
|
|
50
|
+
field.value = value
|
|
51
|
+
field.readOnly = true
|
|
52
|
+
field.setAttribute("aria-hidden", "true")
|
|
53
|
+
field.style.position = "fixed"
|
|
54
|
+
field.style.insetInlineStart = "-9999px"
|
|
55
|
+
field.style.top = "0"
|
|
56
|
+
|
|
57
|
+
owner.body.append(field)
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
field.select()
|
|
61
|
+
|
|
62
|
+
return owner.execCommand("copy")
|
|
63
|
+
} catch {
|
|
64
|
+
return false
|
|
65
|
+
} finally {
|
|
66
|
+
field.remove()
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -110,6 +110,12 @@ export type {
|
|
|
110
110
|
export { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.tsx"
|
|
111
111
|
export type { ComboboxProps } from "./Combobox/Combobox.tsx"
|
|
112
112
|
export { Combobox } from "./Combobox/Combobox.tsx"
|
|
113
|
+
export type {
|
|
114
|
+
CopyButtonProps,
|
|
115
|
+
CopyStatus,
|
|
116
|
+
} from "./CopyButton/CopyButton.tsx"
|
|
117
|
+
export { CopyButton } from "./CopyButton/CopyButton.tsx"
|
|
118
|
+
export { copyText } from "./CopyButton/copyText.ts"
|
|
113
119
|
export {
|
|
114
120
|
CATEGORICAL_APPEARANCE_CLASS,
|
|
115
121
|
CATEGORICAL_CONTENT_CLASS,
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -459,7 +459,13 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
459
459
|
// member of `Badge/` and so stays out of this count and out of the
|
|
460
460
|
// barrel as a component. Its PROPS type is exported, because a
|
|
461
461
|
// consumer writing a wrapper needs to name the visual half.
|
|
462
|
-
|
|
462
|
+
//
|
|
463
|
+
// `CopyButton` — the clipboard write, with a failure state — is
|
|
464
|
+
// +1 -> 54. `copyText.ts` is its member: it stays out of this
|
|
465
|
+
// count by the `<Name>/<Name>.tsx` rule, and it IS exported from
|
|
466
|
+
// the barrel as a function, because a host with its own clipboard
|
|
467
|
+
// bridge needs the fallback chain without the button.
|
|
468
|
+
expect(componentNames.length).toBe(54)
|
|
463
469
|
|
|
464
470
|
for (const name of componentNames) {
|
|
465
471
|
expect(barrel).toContain(`export { ${name} }`)
|