Skip to content
Source
Sign inSign up

CopyTextButton.tsx

75 lines2.5 KB

1import * as Dialog from "../../components/ui/Dialog";
2import { SymbolView } from "../../components/ui/AppSymbol";
3import { memo, useState } from "react";
4import { Pressable, type ColorValue } from "react-native";
5​
6import { useDeadlinePassed } from "../../lib/clock";
7import { tryCopyTextWithHaptic } from "../../lib/copyTextWithHaptic";
8​
9const COPY_FEEDBACK_DURATION_MS = 1200;
10​
11export const CopyTextButton = memo(function CopyTextButton(props: {
12 readonly accessibilityLabel: string;
13 readonly text: string;
14 readonly onCopy?: () => Promise<void>;
15 /** Lets a long-press menu wrap the button (`Menu` injects it on Android). */
16 readonly onLongPress?: () => void;
17 readonly tintColor?: ColorValue;
18 readonly copiedTintColor?: ColorValue;
19 readonly backgroundColor?: ColorValue;
20 readonly borderColor?: ColorValue;
21 readonly iconSize?: number;
22 readonly buttonSize?: number;
23}) {
24 const [copiedAtMs, setCopiedAtMs] = useState<number | null>(null);
25 const feedbackEnded = useDeadlinePassed(
26 copiedAtMs === null ? null : copiedAtMs + COPY_FEEDBACK_DURATION_MS,
27 );
28 const copied = copiedAtMs !== null && !feedbackEnded;
29​
30 return (
31 <Pressable
32 accessibilityRole="button"
33 accessibilityLabel={copied ? "Copied" : props.accessibilityLabel}
34 disabled={props.text.length === 0}
35 hitSlop={8}
36 onLongPress={props.onLongPress}
37 onPress={async () => {
38 try {
39 if (props.onCopy) await props.onCopy();
40 else if (!(await tryCopyTextWithHaptic(props.text))) {
41 Dialog.alert("Could not copy", "Try again.");
42 return;
43 }
44 } catch {
45 Dialog.alert("Could not copy", "Try again.");
46 return;
47 }
48 setCopiedAtMs(Date.now());
49 }}
50 style={({ pressed }) => ({
51 width: props.buttonSize ?? 30,
52 height: props.buttonSize ?? 30,
53 alignItems: "center",
54 justifyContent: "center",
55 borderRadius: 9,
56 borderWidth: props.borderColor ? 1 : 0,
57 borderColor: props.borderColor,
58 backgroundColor: props.backgroundColor,
59 opacity: pressed ? 0.52 : 1,
60 })}
61 >
62 <SymbolView
63 name={
64 copied
65 ? { ios: "checkmark", android: "check" }
66 : { ios: "doc.on.doc", android: "content_copy" }
67 }
68 size={props.iconSize ?? 13}
69 tintColor={copied ? (props.copiedTintColor ?? props.tintColor) : props.tintColor}
70 tintColorClassName={props.tintColor ? undefined : "accent-foreground"}
71 type="monochrome"
72 />
73 </Pressable>
74 );
75});