mirror of https://github.com/acid-info/lsd.git
refactor: use DropdownItem component in Autocomplete
This commit is contained in:
parent
59fba23e98
commit
61b141c653
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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} {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue