Searchable Dropdown on chains, setSelectedChain, add fuse.js

pull/5285/head
Kaan Uzdoğan 5 months ago committed by Aniket
parent 09e3670f70
commit 0110e8993b
  1. 2
      apps/contract-verification/src/app/AppContext.tsx
  2. 3
      apps/contract-verification/src/app/app.tsx
  3. 4
      apps/contract-verification/src/app/components/Dropdown.tsx
  4. 94
      apps/contract-verification/src/app/components/SearchableDropdown.tsx
  5. 1
      apps/contract-verification/src/app/components/index.ts
  6. 3
      apps/contract-verification/src/app/components/index.tsx
  7. 21
      apps/contract-verification/src/app/views/HomeView.tsx
  8. 1
      package.json
  9. 5
      yarn.lock

@ -7,4 +7,6 @@ export const AppContext = React.createContext({
console.log('Calling Set Theme Type')
},
chains: [],
selectedChain: null,
setSelectedChain: (chain: string) => {},
})

@ -14,6 +14,7 @@ const plugin = new ContractVerificationPluginClient()
const App = () => {
const [themeType, setThemeType] = useState<ThemeType>('dark')
const [chains, setChains] = useState([]) // State to hold the chains data
const [selectedChain, setSelectedChain] = useState(null)
useEffect(() => {
// Fetch chains.json and update state
@ -24,7 +25,7 @@ const App = () => {
}, [])
return (
<AppContext.Provider value={{themeType, setThemeType, chains}}>
<AppContext.Provider value={{themeType, setThemeType, chains, selectedChain, setSelectedChain}}>
<DisplayRoutes />
</AppContext.Provider>
)

@ -2,7 +2,7 @@ import React from 'react'
interface DropdownItem {
value: string
text: string
name: string
}
interface DropdownProps {
@ -18,7 +18,7 @@ export const Dropdown: React.FC<DropdownProps> = ({label, items, id}) => {
<select className="form-control custom-select pr-4" id={id}>
{items.map((item, index) => (
<option value={item.value} key={index}>
{item.text}
{item.name}
</option>
))}
</select>

@ -0,0 +1,94 @@
import React, {useState, useEffect, useRef} from 'react'
import Fuse from 'fuse.js'
interface DropdownItem {
value: string
name: string
}
interface DropdownProps {
label: string
options: DropdownItem[]
id: string
value: string
onChange: (value: string) => void
}
export const SearchableDropdown: React.FC<DropdownProps> = ({options, label, id, value, onChange}) => {
const [searchTerm, setSearchTerm] = useState('')
const [selectedOption, setSelectedOption] = useState<DropdownItem | null>(null)
const [isOpen, setIsOpen] = useState(false)
const [filteredOptions, setFilteredOptions] = useState<DropdownItem[]>(options)
const dropdownRef = useRef<HTMLDivElement>(null)
const fuse = new Fuse(options, {
keys: ['name'],
threshold: 0.3,
})
useEffect(() => {
if (searchTerm === '') {
setFilteredOptions(options)
} else {
const result = fuse.search(searchTerm)
setFilteredOptions(result.map(({item}) => item))
}
}, [searchTerm, options])
// Close dropdown when user clicks outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [])
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value)
onChange(e.target.value)
setIsOpen(true)
}
const handleOptionClick = (option: DropdownItem) => {
setSelectedOption(option)
setSearchTerm(option.name)
setIsOpen(false)
}
const openDropdown = () => {
setIsOpen(true)
setSearchTerm('')
}
if (!options || options.length === 0) {
return (
<div className="dropdown">
<label htmlFor={id}>{label}</label>
<div>Loading chains...</div>
</div>
)
}
return (
<div className="dropdown" ref={dropdownRef}>
{' '}
{/* Add ref here */}
<label htmlFor={id}>{label}</label>
<input type="text" value={searchTerm} onChange={handleInputChange} onClick={openDropdown} placeholder="Select a chain" className="form-control" />
{isOpen && (
<ul className="dropdown-menu show w-100" style={{maxHeight: '400px', overflowY: 'auto'}}>
{filteredOptions.map((option) => (
<li key={option.value} onClick={() => handleOptionClick(option)} className={`dropdown-item ${selectedOption === option ? 'active' : ''}`} style={{cursor: 'pointer', whiteSpace: 'normal'}}>
{option.name}
</li>
))}
</ul>
)}
</div>
)
}

@ -1 +0,0 @@
export {NavMenu} from './NavMenu'

@ -0,0 +1,3 @@
export {NavMenu} from './NavMenu'
export {Dropdown} from './Dropdown'
export {SearchableDropdown} from './SearchableDropdown'

@ -1,16 +1,17 @@
import React from 'react'
import {AppContext} from '../AppContext'
import {Dropdown} from '../components/Input/Dropdown'
import {Dropdown} from '../components'
import {SearchableDropdown} from '../components'
export const HomeView = () => {
const {chains} = React.useContext(AppContext)
const {chains, selectedChain, setSelectedChain} = React.useContext(AppContext)
const ethereumChainIds = [1, 11155111, 17000]
const ethereumChainIds = [1, 3, 4, 5, 11155111, 17000]
// Add Ethereum chains to the head of the chains list. Sort the rest alphabetically
const dropdownChains = chains
.map((chain) => ({value: chain.chainId, text: `${chain.name} (${chain.chainId})`}))
.map((chain) => ({value: chain.chainId, name: `${chain.title || chain.name} (${chain.chainId})`}))
.sort((a, b) => {
const isAInEthereum = ethereumChainIds.includes(a.value)
const isBInEthereum = ethereumChainIds.includes(b.value)
@ -19,7 +20,7 @@ export const HomeView = () => {
if (!isAInEthereum && isBInEthereum) return 1
if (isAInEthereum && isBInEthereum) return ethereumChainIds.indexOf(a.value) - ethereumChainIds.indexOf(b.value)
return a.text.localeCompare(b.text)
return a.name.localeCompare(b.name)
})
return (
@ -31,17 +32,19 @@ export const HomeView = () => {
</p>
</div>
<div>
<Dropdown label="Network" items={dropdownChains} id="network-dropdown" />
<SearchableDropdown label="Contract Chain" options={dropdownChains} id="network-dropdown" value={selectedChain} onChange={setSelectedChain} />
<div className="form-group">
<label htmlFor="contract-address">Contract Address</label>
<input type="text" className="form-control" id="contract-address" placeholder="0x2738d13E81e..." />
</div>
<Dropdown
label="Contract Name"
items={[
{value: 'ERC20', text: 'ERC20'},
{value: 'ERC721', text: 'ERC721'},
{value: 'ERC1155', text: 'ERC1155'},
{value: 'ERC20', name: 'ERC20'},
{value: 'ERC721', name: 'ERC721'},
{value: 'ERC1155', name: 'ERC1155'},
]}
id="contract-name-dropdown"
/>

@ -153,6 +153,7 @@
"formik": "^2.4.5",
"from-exponential": "1.1.1",
"fs-extra": "^3.0.1",
"fuse.js": "^7.0.0",
"ganache": "^7.9.1",
"graphql": "^16.8.1",
"html-react-parser": "^3.0.4",

@ -16531,6 +16531,11 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
fuse.js@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.0.0.tgz#6573c9fcd4c8268e403b4fc7d7131ffcf99a9eb2"
integrity sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==
galactus@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/galactus/-/galactus-0.2.1.tgz#cbed2d20a40c1f5679a35908e2b9415733e78db9"

Loading…
Cancel
Save