refactor: use DropdownItem component in Autocomplete

This commit is contained in:
Hossein Mehrabi 2023-02-23 15:19:55 +03:30
parent 59fba23e98
commit 61b141c653
4 changed files with 56 additions and 126 deletions

View File

@ -1,6 +1,10 @@
import { Meta, Story } from '@storybook/react'
import { Autocomplete, AutocompleteProps } from './Autocomplete'
const list = JSON.parse(
'["BitTorrent","VANIG","BenjiRolls","Status Network Token","GigTricks","Vechain","Insureum","Instant Sponsor Token","IslaCoin","Kcash","Litecoin","BoutsPro","Valorem","Artcoin","Insureum","Zen Protocol","CryptoPennies","MediBond","WishFinance","BlockNet","Big Data Block","Loom Network","Gamedex","Universal Recognition Token","Soarcoin","IZX","aelf","Bitcoin Private","TerraCoin","Coinnec","Revolution VR","SecureCoin","Swarm Fund","Fan360","LakeBanker","16BitCoin","CyberTrust","Actinium","ZestCoin","Monero 0","DACash","Bitswift","Bethereum","Hedge Token","Megastake","HeelCoin","RealChain","LiteBitcoin","SexCoin","Suretly","Internet of People","Rate3","Invictus","WorldPay","iOlite","Cashaa","NovaCoin","U Network","Gimli","Chynge.net","BMChain","Ethereum Premium","BlackShadowCoin","Javvy","Befund","Bancor Network Token","BOONSCoin","Pantos","IDEX Membership","BitStation","Lynx","Encrybit","HighVibe.Network","Credo","HiCoin","RiptideCoin","BitBoss","NXTTY","Presale Ventures","Urbit Data","Xeonbit","Newbium","Mint","Crypto Wine Exchange","HARA","Pioneer Coin","Ethereum Dark","FazzCoin","Fitrova","The EFFECT Network","CargoX","PolicyPal Network","Vechain","President Clinton","GenesysCoin","Trivver","Bitdeal","ShareMeAll","Primas","RoboAdvisorCoin","Liquid","Napoleon X","NOKU Master token","Liqnet","ZeroState","0chain","DarkCash","Sudan Gold Coin","PokerSports","Bankera","Think And Get Rich Coin","ELTCOIN","USOAMIC","XDNA","Autoria","Cosmo","Bigbom","EagsCoin","Stakinglab","SaffronCoin","BnrtxCoin","FoodCoin","PutinCoin","SID Token","Quartz","IOU1","Spend","NEO Gold","High Voltage Coin","Unobtanium","Sandcoin","FrazCoin","Sudan Gold Coin","VeriCoin","Aurora","NANJCOIN","Muse","DuckDuckCoin","Saifu","CryCash","Rustbits","BitFlip","Cpollo","Monkey Project","Coin Analyst","Scanetchain Token","Aditus","LendConnect","FOREXCOIN","Crypto Improvement Fund","Electra","Psilocybin","GameLeagueCoin","Switcheo","BitQuark","BinaryCoin","CyberVein","KATZcoin","BtcEX","ByteCoin","Shping Coin","Liquid","Stacktical","Tezos","ParkByte","Imbrex","Pinmo","Sigil","LePenCoin","OmiseGO Classic","Digix DAO","ShareRing","CryptoCarbon","CDX Network","SONM","PhoenixCoin","Incent","Tokyo Coin","Premium","Unattanium","Biotron","WETH","Rublebit","KRCoin","Aegis","Legends Cryptocurrency","VARcrypt","Witcoin","GlowShares","HOQU","VARcrypt","Linx","BlackholeCoin","NumbersCoin","ZayedCoin","CarVertical","Securosys","ElliotCoin","Zelcash","AcesCoin","EtherInc"]',
)
export default {
title: 'Autocomplete',
component: Autocomplete,
@ -25,46 +29,5 @@ Root.args = {
error: false,
placeholder: 'Placeholder',
onChange: undefined,
options: [
{
value: `1`,
name: `input`,
},
{
value: `2`,
name: `Input`,
},
{
value: `3`,
name: `InputValue`,
},
{
value: `4`,
name: `Input Value`,
},
{
value: `5`,
name: `inputValue`,
},
{
value: `6`,
name: `input Value`,
},
{
value: `7`,
name: `text`,
},
{
value: `8`,
name: `Text`,
},
{
value: `9`,
name: `placeholder`,
},
{
value: `10`,
name: `Placeholder`,
},
],
options: list,
}

View File

@ -66,31 +66,14 @@ export const AutocompleteStyles = css`
overflow: auto;
border: 1px solid rgb(var(--lsd-border-primary));
border-top: 0;
& .${autocompleteClasses.root} {
border: 0;
&:not(:last-child) {
border-bottom: 1px solid rgb(var(--lsd-border-primary));
}
}
}
.${autocompleteClasses.dropdownItem} {
width: 100%;
box-sizing: border-box;
border: 0;
display: flex;
flex-direction: row;
align-items: center;
padding: 10px 14px;
border-bottom: 1px solid rgb(var(--lsd-border-primary));
cursor: pointer;
}
.${autocompleteClasses.dropdownItem}:last-child {
border: none;
&:not(:last-child) {
border-bottom: 1px solid rgb(var(--lsd-border-primary));
}
}
.${autocompleteClasses.dropdownItemPlaceholder} {

View File

@ -1,11 +1,10 @@
import clsx from 'clsx'
import React, { useEffect, useRef, useState } from 'react'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useInput } from '../../utils/useInput'
import { DropdownOption } from '../Dropdown'
import { DropdownItem } from '../DropdownItem'
import { CloseIcon, SearchIcon } from '../Icons'
import { ListBox } from '../ListBox'
import { Portal } from '../PortalProvider/Portal'
import { Typography } from '../Typography'
import { autocompleteClasses } from './Autocomplete.classes'
export type AutocompleteProps = Omit<
@ -20,7 +19,7 @@ export type AutocompleteProps = Omit<
placeholder?: string
value?: string
defaultValue?: string
options?: DropdownOption[]
options?: string[]
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
}
@ -43,10 +42,10 @@ export const Autocomplete: React.FC<AutocompleteProps> & {
const ref = useRef<HTMLInputElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const input = useInput({ defaultValue, value, onChange, ref })
const inputValue = input.value as string
const [open, setOpen] = useState(false)
const [selected, setSelected] = useState('')
const [dropdownOption, setDropdownOption] = useState(options || [])
const [selected, setSelected] = useState<string>()
const onCancel = () => input.setValue('')
@ -56,56 +55,27 @@ export const Autocomplete: React.FC<AutocompleteProps> & {
input.setValue(value)
}
useEffect(() => {
const filteredOptions = options.filter(
(option) =>
typeof input.value === 'string' && option.name.startsWith(input.value),
)
if (input.value && filteredOptions.length && input.value !== selected) {
setDropdownOption(filteredOptions)
setOpen(true)
} else setOpen(false)
}, [input.value, setOpen, setDropdownOption])
const suggestions = useMemo(
() =>
input.filled
? options
.filter((option) =>
new RegExp(`^${input.value}.+`, 'i').test(option),
)
.map((option) => [
option,
option.slice(0, inputValue.length),
option.slice(inputValue.length),
])
: options,
[input.value, options],
)
useEffect(() => {
if (disabled) {
setOpen(false)
input.setValue('')
}
}, [disabled])
!selected && input.filled && !open && setOpen(true)
}, [input.value, selected, open])
const renderDropdownItem = (option: DropdownOption) => {
if (typeof input.value === 'string') {
const suggestion = option.name.substring(
option.name.indexOf(input.value) + input.value.length,
)
return (
<>
<Typography
variant={size === 'large' ? 'label1' : 'label2'}
component="span"
>
{input.value}
</Typography>
<Typography
variant={size === 'large' ? 'label1' : 'label2'}
component="span"
className={autocompleteClasses.dropdownItemPlaceholder}
>
{suggestion}
</Typography>
</>
)
} else
return (
<Typography
variant={size === 'large' ? 'label1' : 'label2'}
component="span"
>
{option.name}
</Typography>
)
}
const isOpen = !disabled && open && suggestions.length > 0 && input.filled
return (
<div
@ -127,6 +97,7 @@ export const Autocomplete: React.FC<AutocompleteProps> & {
placeholder={placeholder}
onChange={input.onChange}
disabled={disabled}
onFocus={() => setOpen(true)}
className={clsx(
inputProps.className,
autocompleteClasses.input,
@ -146,18 +117,29 @@ export const Autocomplete: React.FC<AutocompleteProps> & {
<Portal id="autocomplete">
<ListBox
handleRef={containerRef}
open={open}
open={isOpen}
onClose={() => setOpen(false)}
className={autocompleteClasses.listBox}
>
{dropdownOption.map((opt: DropdownOption, idx: number) => (
<div
{suggestions.map((opt, idx: number) => (
<DropdownItem
key={idx}
size={size}
tabIndex={0}
label={
<>
{opt[1]}
<span className={autocompleteClasses.dropdownItemPlaceholder}>
{opt[2]}
</span>
</>
}
className={autocompleteClasses.dropdownItem}
onClick={() => handleDropdownClick(opt.name)}
>
{renderDropdownItem(opt)}
</div>
onClick={() => handleDropdownClick(opt[0])}
onKeyDown={(e) =>
e.key === 'Enter' && handleDropdownClick(opt[0])
}
/>
))}
</ListBox>
</Portal>

View File

@ -4,8 +4,11 @@ import { CheckboxFilledIcon, CheckboxIcon, LsdIconProps } from '../Icons'
import { Typography } from '../Typography'
import { dropdownItemClasses } from './DropdownItem.classes'
export type DropdownItemProps = React.HTMLAttributes<HTMLDivElement> & {
label: string
export type DropdownItemProps = Omit<
React.HTMLAttributes<HTMLDivElement>,
'label'
> & {
label: React.ReactNode
selected?: boolean
withIcon?: boolean
disabled?: boolean
@ -32,7 +35,6 @@ export const DropdownItem: React.FC<DropdownItemProps> & {
<div
role="option"
aria-selected={selected ? 'true' : 'false'}
aria-label={label}
className={clsx(
className,
dropdownItemClasses.root,