Skip to content

Commit

Permalink
ui:Added dropdown for mnemonic phrase input
Browse files Browse the repository at this point in the history
  • Loading branch information
amitx13 committed Sep 5, 2024
1 parent 2494a8c commit 20f77ce
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 6 deletions.
4 changes: 4 additions & 0 deletions src/components/MnemonicPhraseInput.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.dropdownMenu {
min-width: 100% !important;
max-height: 12.7rem;
}
90 changes: 84 additions & 6 deletions src/components/MnemonicPhraseInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useEffect, useRef, useState } from 'react'
import { Bip39MnemonicWordInput } from './MnemonicWordInput'
import { MNEMONIC_WORDS } from '../constants/bip39words'
import * as rb from 'react-bootstrap'
import { forwardRef } from 'react'
import style from './MnemonicPhraseInput.module.css'

interface MnemonicPhraseInputProps {
columns?: number
Expand All @@ -9,6 +13,26 @@ interface MnemonicPhraseInputProps {
onChange: (value: MnemonicPhrase) => void
}

interface MnemonicDropdownProps {
show: boolean
words: string[]
onSelect: (word: string) => void
}

const MnemonicDropdown = forwardRef<HTMLDivElement, MnemonicDropdownProps>(({ show, words, onSelect }, ref) => (
<rb.Dropdown show={show}>
<rb.Dropdown.Menu className={`table-responsive ${style.dropdownMenu}`} ref={ref}>
{words.map((word) => (
<div className="m-1">
<rb.Dropdown.Item key={word} onClick={() => onSelect(word)} className="p-2">
{word}
</rb.Dropdown.Item>
</div>
))}
</rb.Dropdown.Menu>
</rb.Dropdown>
))

export default function MnemonicPhraseInput({
columns = 3,
mnemonicPhrase,
Expand All @@ -18,6 +42,9 @@ export default function MnemonicPhraseInput({
}: MnemonicPhraseInputProps) {
const [activeIndex, setActiveIndex] = useState(0)
const inputRefs = useRef<HTMLInputElement[]>([])
const [showDropdown, setShowDropdown] = useState<number | null>(null)
const [filteredWords, setFilteredWords] = useState<string[] | undefined>(undefined)
const dropdownRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (activeIndex < mnemonicPhrase.length && isValid && isValid(activeIndex)) {
Expand All @@ -30,6 +57,43 @@ export default function MnemonicPhraseInput({
}
}, [mnemonicPhrase, activeIndex, isValid])

const handleInputChange = (value: string, index: number) => {
const newPhrase = mnemonicPhrase.map((old, i) => (i === index ? value : old))
onChange(newPhrase)
handleDropdownValue(value, index)
}

const handleDropdownValue = (value: string, index: number) => {
const matched = value ? MNEMONIC_WORDS.filter((word) => word.startsWith(value)) : []
if (matched.length > 0) {
setShowDropdown(index)
setFilteredWords(matched)
} else {
setShowDropdown(null)
setFilteredWords(undefined)
}
}

const handleSelectWord = (word: string, index: number) => {
const newPhrase = mnemonicPhrase.map((old, i) => (i === index ? word : old))
onChange(newPhrase)
setShowDropdown(null)
}

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
e.preventDefault()
if (filteredWords && filteredWords.length > 0 && dropdownRef.current) {
const firstItem = dropdownRef.current.querySelector('.dropdown-item')
if (firstItem) {
;(firstItem as HTMLElement).focus()
}
}
} else if (e.key === 'Enter') {
setShowDropdown(null)
}
}

return (
<div className="container slashed-zeroes p-0">
{mnemonicPhrase.map((_, outerIndex) => {
Expand All @@ -38,7 +102,13 @@ export default function MnemonicPhraseInput({
const wordGroup = mnemonicPhrase.slice(outerIndex, Math.min(outerIndex + columns, mnemonicPhrase.length))

return (
<div className="row mb-4" key={outerIndex}>
<div
className="row mb-4"
key={outerIndex}
onKeyDown={(e) => {
handleKeyDown(e)
}}
>
{wordGroup.map((givenWord, innerIndex) => {
const wordIndex = outerIndex + innerIndex
const isCurrentActive = wordIndex === activeIndex
Expand All @@ -48,15 +118,23 @@ export default function MnemonicPhraseInput({
forwardRef={(el: HTMLInputElement) => (inputRefs.current[wordIndex] = el)}
index={wordIndex}
value={givenWord}
setValue={(value, i) => {
const newPhrase = mnemonicPhrase.map((old, index) => (index === i ? value : old))
onChange(newPhrase)
}}
setValue={(value) => handleInputChange(value, wordIndex)}
isValid={isValid ? isValid(wordIndex) : undefined}
disabled={isDisabled ? isDisabled(wordIndex) : undefined}
onFocus={() => setActiveIndex(wordIndex)}
onFocus={() => {
setActiveIndex(wordIndex)
handleDropdownValue(givenWord, wordIndex)
}}
autoFocus={isCurrentActive}
/>
{filteredWords && (
<MnemonicDropdown
ref={dropdownRef}
show={showDropdown === wordIndex}
words={filteredWords}
onSelect={(word) => handleSelectWord(word, wordIndex)}
/>
)}
</div>
)
})}
Expand Down

0 comments on commit 20f77ce

Please sign in to comment.