MnemonicInputs.tsx raw
1 import { wordlist } from "@scure/bip39/wordlists/english.js";
2 import { useState } from "react";
3 import RevealPasswordToggle from "src/components/password/RevealPasswordToggle";
4 import {
5 Card,
6 CardContent,
7 CardHeader,
8 CardTitle,
9 } from "src/components/ui/card";
10 import { InputWithAdornment } from "src/components/ui/custom/input-with-adornment";
11
12 type MnemonicInputsProps = {
13 mnemonic?: string;
14 setMnemonic?(mnemonic: string): void;
15 readOnly?: boolean;
16 asCard?: boolean;
17 };
18
19 export default function MnemonicInputs({
20 mnemonic,
21 setMnemonic,
22 readOnly,
23 children,
24 asCard = true,
25 }: React.PropsWithChildren<MnemonicInputsProps>) {
26 const words = mnemonic?.split(" ") || [];
27 while (words.length < 12) {
28 words.push("");
29 }
30
31 while (words.length > 12) {
32 words.pop();
33 }
34
35 const [revealedIndex, setRevealedIndex] = useState<number | undefined>(
36 undefined
37 );
38
39 const content = (
40 <>
41 <div className="grid grid-cols-2 gap-y-5 gap-8 justify-center backup sensitive">
42 {words.map((word, i) => {
43 const isRevealed = revealedIndex === i;
44 const inputId = `mnemonic-word-${i}`;
45 return (
46 <div key={i} className="flex justify-center items-center gap-2">
47 <span className="text-foreground text-right">{i + 1}.</span>
48 <div className="relative">
49 <InputWithAdornment
50 id={inputId}
51 autoFocus={!readOnly && i === 0}
52 readOnly={readOnly}
53 onFocus={() => setRevealedIndex(i)}
54 onBlur={() => setRevealedIndex(undefined)}
55 className="w-32 sm:w-44 border-[#E4E4E7] text-muted-foreground"
56 list={readOnly ? undefined : "wordlist"}
57 value={isRevealed ? word : word.length ? "•••••" : ""}
58 onChange={(e) => {
59 if (revealedIndex !== i) {
60 return;
61 }
62 words[i] = e.target.value;
63 setMnemonic?.(
64 words
65 .map((word) => word.trim())
66 .join(" ")
67 .trim()
68 );
69 }}
70 endAdornment={
71 <RevealPasswordToggle
72 isRevealed={isRevealed}
73 onChange={(passwordView) => {
74 if (passwordView) {
75 document.getElementById(inputId)?.focus();
76 }
77 }}
78 iconClass="text-muted-foreground"
79 />
80 }
81 />
82 </div>
83 </div>
84 );
85 })}
86 </div>
87 {!readOnly && (
88 <datalist id="wordlist">
89 {wordlist.map((word) => (
90 <option key={word} value={word} />
91 ))}
92 </datalist>
93 )}
94 {children}
95 </>
96 );
97 if (!asCard) {
98 return content;
99 }
100
101 return (
102 <>
103 <Card>
104 <CardHeader>
105 <CardTitle className="text-xl text-center">Recovery Phrase</CardTitle>
106 </CardHeader>
107
108 <CardContent>{content}</CardContent>
109 </Card>
110 </>
111 );
112 }
113