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