mirror of
https://github.com/zhigang1992/react.git
synced 2026-02-12 22:29:35 +08:00
Merge pull request #30 from unix/auto-complete
feat(auto complete): add component
This commit is contained in:
20
components/auto-complete/auto-complete-context.ts
Normal file
20
components/auto-complete/auto-complete-context.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import React, { MutableRefObject } from 'react'
|
||||
import { NormalSizes } from '../utils/prop-types'
|
||||
|
||||
export interface AutoCompleteConfig {
|
||||
value?: string
|
||||
updateValue?: Function
|
||||
visible?: boolean
|
||||
updateVisible?: Function
|
||||
size?: NormalSizes
|
||||
ref?: MutableRefObject<HTMLElement | null>
|
||||
}
|
||||
|
||||
const defaultContext = {
|
||||
visible: false,
|
||||
size: 'medium' as NormalSizes,
|
||||
}
|
||||
|
||||
export const AutoCompleteContext = React.createContext<AutoCompleteConfig>(defaultContext)
|
||||
|
||||
export const useAutoCompleteContext = (): AutoCompleteConfig => React.useContext<AutoCompleteConfig>(AutoCompleteContext)
|
||||
37
components/auto-complete/auto-complete-dropdown.tsx
Normal file
37
components/auto-complete/auto-complete-dropdown.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import { useAutoCompleteContext } from './auto-complete-context'
|
||||
import Dropdown from '../shared/dropdown'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const AutoCompleteDropdown: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children, visible
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { ref } = useAutoCompleteContext()
|
||||
const clickHandler = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
event.nativeEvent.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown parent={ref} visible={visible}>
|
||||
<div className="auto-dropdown-dropdown" onClick={clickHandler}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.auto-dropdown-dropdown {
|
||||
border-radius: ${theme.layout.radius};
|
||||
box-shadow: ${theme.expressiveness.shadowLarge};
|
||||
background-color: ${theme.palette.background};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
export default AutoCompleteDropdown
|
||||
22
components/auto-complete/auto-complete-empty.tsx
Normal file
22
components/auto-complete/auto-complete-empty.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import AutoCompleteSearch from './auto-complete-searching'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
}
|
||||
|
||||
export type AutoCompleteEmptyProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const AutoCompleteEmpty: React.FC<React.PropsWithChildren<AutoCompleteEmptyProps>> = ({
|
||||
children, className,
|
||||
}) => {
|
||||
|
||||
return <AutoCompleteSearch className={className}>{children}</AutoCompleteSearch>
|
||||
}
|
||||
|
||||
export default withDefaults(AutoCompleteEmpty, defaultProps)
|
||||
84
components/auto-complete/auto-complete-item.tsx
Normal file
84
components/auto-complete/auto-complete-item.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import { useAutoCompleteContext } from './auto-complete-context'
|
||||
import { NormalSizes } from 'components/utils/prop-types'
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
}
|
||||
|
||||
export type AutoCompleteItemProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const getSizes = (size?: NormalSizes) => {
|
||||
const fontSizes: { [key in NormalSizes]: string } = {
|
||||
mini: '.7rem',
|
||||
small: '.75rem',
|
||||
medium: '.875rem',
|
||||
large: '1rem',
|
||||
}
|
||||
return size ? fontSizes[size] : fontSizes.medium
|
||||
}
|
||||
|
||||
const AutoCompleteItem: React.FC<React.PropsWithChildren<AutoCompleteItemProps>> = ({
|
||||
value: identValue, children, disabled,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { value, updateValue, size } = useAutoCompleteContext()
|
||||
const selectHandler = () => {
|
||||
updateValue && updateValue(identValue)
|
||||
}
|
||||
|
||||
const isActive = useMemo(() => value === identValue, [identValue, value])
|
||||
const fontSize = useMemo(() => getSizes(size), [size])
|
||||
|
||||
return (
|
||||
<div className={`item ${isActive ? 'active' : ''}`} onClick={selectHandler}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-weight: normal;
|
||||
white-space: pre;
|
||||
font-size: ${fontSize};
|
||||
padding: ${theme.layout.gapHalf};
|
||||
line-height: 1.2;
|
||||
background-color: ${theme.palette.background};
|
||||
color: ${theme.palette.foreground};
|
||||
user-select: none;
|
||||
border: 0;
|
||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
||||
transition: background 0.2s ease 0s, border-color 0.2s ease 0s;
|
||||
}
|
||||
|
||||
.item:first-of-type {
|
||||
border-top-left-radius: ${theme.layout.radius};
|
||||
border-top-right-radius: ${theme.layout.radius};
|
||||
}
|
||||
|
||||
.item:last-of-type {
|
||||
border-bottom-left-radius: ${theme.layout.radius};
|
||||
border-bottom-right-radius: ${theme.layout.radius};
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background-color: ${theme.palette.accents_1};
|
||||
}
|
||||
|
||||
.item.active {
|
||||
background-color: ${theme.palette.accents_1};
|
||||
color: ${theme.palette.success};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(AutoCompleteItem, defaultProps)
|
||||
45
components/auto-complete/auto-complete-searching.tsx
Normal file
45
components/auto-complete/auto-complete-searching.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import useTheme from '../styles/use-theme'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
}
|
||||
|
||||
export type AutoCompleteSearchProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const AutoCompleteSearch: React.FC<React.PropsWithChildren<AutoCompleteSearchProps>> = ({
|
||||
children, className,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
font-weight: normal;
|
||||
white-space: pre;
|
||||
font-size: .875rem;
|
||||
padding: ${theme.layout.gapHalf};
|
||||
line-height: 1;
|
||||
background-color: ${theme.palette.background};
|
||||
color: ${theme.palette.accents_5};
|
||||
user-select: none;
|
||||
border: 0;
|
||||
border-radius: ${theme.layout.radius};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(AutoCompleteSearch, defaultProps)
|
||||
166
components/auto-complete/auto-complete.tsx
Normal file
166
components/auto-complete/auto-complete.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import Input from '../input'
|
||||
import AutoCompleteItem from './auto-complete-item'
|
||||
import AutoCompleteDropdown from './auto-complete-dropdown'
|
||||
import AutoCompleteSearching from './auto-complete-searching'
|
||||
import AutoCompleteEmpty from './auto-complete-empty'
|
||||
import { AutoCompleteContext, AutoCompleteConfig } from './auto-complete-context'
|
||||
import { NormalSizes, NormalTypes } from '../utils/prop-types'
|
||||
import ButtonLoading from '../button/button.loading'
|
||||
import { pickChild } from 'components/utils/collections'
|
||||
|
||||
export type AutoCompleteOption = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type AutoCompleteOptions = Array<AutoCompleteOption | typeof AutoCompleteItem>
|
||||
|
||||
interface Props {
|
||||
options: AutoCompleteOptions
|
||||
size?: NormalSizes
|
||||
status?: NormalTypes
|
||||
initialValue?: string
|
||||
value?: string
|
||||
width?: string
|
||||
onChange?: (value: string) => void
|
||||
onSearch?: (value: string) => void
|
||||
onSelect?: (value: string) => void
|
||||
searching?: boolean | undefined
|
||||
clearable?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
options: [],
|
||||
initialValue: '',
|
||||
disabled: false,
|
||||
clearable: false,
|
||||
size: 'medium' as NormalSizes,
|
||||
className: '',
|
||||
}
|
||||
|
||||
export type AutoCompleteProps = Props & typeof defaultProps & React.InputHTMLAttributes<any>
|
||||
|
||||
const childrenToOptionsNode = (options: AutoCompleteOptions) => {
|
||||
if (options.length === 0) return null
|
||||
|
||||
return options.map((item, index) => {
|
||||
const key = `auto-complete-item-${index}`
|
||||
if (React.isValidElement(item)) return React.cloneElement(item, { key })
|
||||
const validItem = item as AutoCompleteOption
|
||||
return (
|
||||
<AutoCompleteItem key={key}
|
||||
value={validItem.value}>
|
||||
{validItem.label}
|
||||
</AutoCompleteItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// When the search is not set, the "clearable" icon can be displayed in the original location.
|
||||
// When the search is seted, at least one element should exist to avoid re-render.
|
||||
const getSearchIcon = (searching?: boolean) => {
|
||||
if (searching === undefined) return null
|
||||
return searching ? <ButtonLoading bgColor="transparent" /> : <span />
|
||||
}
|
||||
|
||||
const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
options, initialValue: customInitialValue, onSelect, onSearch, onChange,
|
||||
searching, children, size, status, value, width, clearable, ...props
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [state, setState] = useState<string>(customInitialValue)
|
||||
const [visible, setVisible] = useState<boolean>(false)
|
||||
const [, searchChild] = pickChild(children, AutoCompleteSearching)
|
||||
const [, emptyChild] = pickChild(children, AutoCompleteEmpty)
|
||||
const autoCompleteItems = useMemo(() => {
|
||||
const hasSearchChild = searchChild && React.Children.count(searchChild) > 0
|
||||
const hasEmptyChild = emptyChild && React.Children.count(emptyChild) > 0
|
||||
if (searching) {
|
||||
return hasSearchChild ? searchChild : <AutoCompleteSearching>Searching...</AutoCompleteSearching>
|
||||
}
|
||||
if (options.length === 0) {
|
||||
if (state === '') return null
|
||||
return hasEmptyChild ? emptyChild : <AutoCompleteEmpty>No Options</AutoCompleteEmpty>
|
||||
}
|
||||
return childrenToOptionsNode(options)
|
||||
}, [searching, options])
|
||||
const showClearIcon = useMemo(
|
||||
() => clearable && searching === undefined,
|
||||
[clearable, searching],
|
||||
)
|
||||
|
||||
const updateValue = (val: string) => {
|
||||
onSelect && onSelect(val)
|
||||
setState(val)
|
||||
}
|
||||
const updateVisible = (next: boolean) => setVisible(next)
|
||||
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onSearch && onSearch(event.target.value)
|
||||
setState(event.target.value)
|
||||
}
|
||||
|
||||
useEffect(() => onChange && onChange(state), [state])
|
||||
useEffect(() => {
|
||||
if (value === undefined) return
|
||||
setState(value)
|
||||
}, [value])
|
||||
|
||||
const initialValue = useMemo<AutoCompleteConfig>(() => ({
|
||||
ref, size,
|
||||
value: state,
|
||||
updateValue,
|
||||
visible,
|
||||
updateVisible,
|
||||
}), [state, visible, size])
|
||||
|
||||
const toggleFocusHandler = (next: boolean) => {
|
||||
setVisible(next)
|
||||
if (next) {
|
||||
onSearch && onSearch(state)
|
||||
}
|
||||
}
|
||||
|
||||
const inputProps = {
|
||||
...props,
|
||||
width,
|
||||
value: state,
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoCompleteContext.Provider value={initialValue}>
|
||||
<div ref={ref} className="auto-complete">
|
||||
<Input size={size} status={status}
|
||||
onChange={onInputChange}
|
||||
onFocus={() => toggleFocusHandler(true)}
|
||||
onBlur={() => toggleFocusHandler(false)}
|
||||
clearable={showClearIcon}
|
||||
iconRight={getSearchIcon(searching)}
|
||||
{...inputProps} />
|
||||
<AutoCompleteDropdown visible={visible}>
|
||||
{autoCompleteItems}
|
||||
</AutoCompleteDropdown>
|
||||
|
||||
<style jsx>{`
|
||||
.auto-complete {
|
||||
width: ${width || 'max-content'};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</AutoCompleteContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
type AutoCompleteComponent<P = {}> = React.FC<P> & {
|
||||
Item: typeof AutoCompleteItem
|
||||
Option: typeof AutoCompleteItem
|
||||
Searching: typeof AutoCompleteSearching
|
||||
Empty: typeof AutoCompleteEmpty
|
||||
}
|
||||
|
||||
type ComponentProps = Partial<typeof defaultProps> & Omit<Props, keyof typeof defaultProps>
|
||||
|
||||
(AutoComplete as AutoCompleteComponent<ComponentProps>).defaultProps = defaultProps
|
||||
|
||||
export default AutoComplete as AutoCompleteComponent<ComponentProps>
|
||||
11
components/auto-complete/index.ts
Normal file
11
components/auto-complete/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import AutoComplete from './auto-complete'
|
||||
import AutoCompleteItem from './auto-complete-item'
|
||||
import AutoCompleteSearching from './auto-complete-searching'
|
||||
import AutoCompleteEmpty from './auto-complete-empty'
|
||||
|
||||
AutoComplete.Item = AutoCompleteItem
|
||||
AutoComplete.Option = AutoCompleteItem
|
||||
AutoComplete.Searching = AutoCompleteSearching
|
||||
AutoComplete.Empty = AutoCompleteEmpty
|
||||
|
||||
export default AutoComplete
|
||||
@@ -1,7 +1,13 @@
|
||||
import React from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
|
||||
const ButtonLoading: React.FC<{}> = React.memo(() => {
|
||||
interface Props {
|
||||
bgColor?: string
|
||||
}
|
||||
|
||||
const ButtonLoading: React.FC<Props> = React.memo(({
|
||||
bgColor,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<span className="loading">
|
||||
@@ -21,7 +27,7 @@ const ButtonLoading: React.FC<{}> = React.memo(() => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${theme.palette.accents_1};
|
||||
background-color: ${bgColor || theme.palette.accents_1};
|
||||
}
|
||||
|
||||
i {
|
||||
|
||||
@@ -37,3 +37,4 @@ export { default as Tabs } from './tabs'
|
||||
export { default as Progress } from './progress'
|
||||
export { default as Tree } from './file-tree'
|
||||
export { default as Badge } from './badge'
|
||||
export { default as AutoComplete } from './auto-complete'
|
||||
|
||||
@@ -2,13 +2,14 @@ import React, { useMemo } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
|
||||
interface Props {
|
||||
visibale: boolean
|
||||
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void
|
||||
heightRatio?: string | undefined
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const InputIconClear: React.FC<Props> = ({
|
||||
onClick, heightRatio, disabled,
|
||||
onClick, heightRatio, disabled, visibale,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const width = useMemo(() => {
|
||||
@@ -21,7 +22,7 @@ const InputIconClear: React.FC<Props> = ({
|
||||
onClick && onClick(event)
|
||||
}
|
||||
return (
|
||||
<div onClick={clickHandler}>
|
||||
<div onClick={clickHandler} className={`${visibale ? 'visibale' : ''}`}>
|
||||
<svg viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"
|
||||
strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision">
|
||||
<path d="M18 6L6 18" />
|
||||
@@ -39,6 +40,13 @@ const InputIconClear: React.FC<Props> = ({
|
||||
box-sizing: border-box;
|
||||
transition: color 150ms ease 0s;
|
||||
color: ${theme.palette.accents_3};
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.visibale {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div:hover {
|
||||
|
||||
@@ -32,6 +32,7 @@ const InputIcon: React.FC<InputIconProps> = React.memo(({
|
||||
margin: 0;
|
||||
padding: 0 ${padding};
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
|
||||
@@ -24,6 +24,9 @@ interface Props {
|
||||
clearable?: boolean
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onClearClick?: (e: React.MouseEvent<HTMLDivElement>) => void
|
||||
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void
|
||||
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void
|
||||
autoComplete: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
@@ -33,6 +36,7 @@ const defaultProps = {
|
||||
width: 'initial',
|
||||
size: 'medium',
|
||||
status: 'default',
|
||||
autoComplete: 'off',
|
||||
className: '',
|
||||
placeholder: '',
|
||||
initialValue: '',
|
||||
@@ -43,12 +47,14 @@ export type InputProps = Props & typeof defaultProps & React.InputHTMLAttributes
|
||||
const Input: React.FC<InputProps> = ({
|
||||
placeholder, label, labelRight, size, status, disabled,
|
||||
icon, iconRight, initialValue, onChange, readOnly, value,
|
||||
onClearClick, clearable, width, className, ...props
|
||||
onClearClick, clearable, width, className, onBlur, onFocus,
|
||||
autoComplete, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const [selfValue, setSelfValue] = useState<string>(initialValue)
|
||||
const [hover, setHover] = useState<boolean>(false)
|
||||
const { heightRatio, fontSize } = useMemo(() => getSizes(size),[size])
|
||||
const showClearIcon = useMemo(() => clearable && selfValue !== '', [selfValue, clearable])
|
||||
const labelClasses = useMemo(
|
||||
() => labelRight ? 'right-label' : (label ? 'left-label' : ''),
|
||||
[label, labelRight],
|
||||
@@ -63,15 +69,23 @@ const Input: React.FC<InputProps> = ({
|
||||
)
|
||||
const changeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (disabled || readOnly) return
|
||||
console.log(123, event.target.value)
|
||||
setSelfValue(event.target.value)
|
||||
onChange && onChange(event)
|
||||
}
|
||||
|
||||
|
||||
const clearHandler = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
setSelfValue('')
|
||||
onClearClick && onClearClick(event)
|
||||
}
|
||||
|
||||
const focusHandler = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
setHover(true)
|
||||
onFocus && onFocus(e)
|
||||
}
|
||||
const blurHandler = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
setHover(false)
|
||||
onBlur && onBlur(e)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (value === undefined) return
|
||||
@@ -88,12 +102,15 @@ const Input: React.FC<InputProps> = ({
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
onFocus={() => setHover(true)}
|
||||
onBlur={() => setHover(false)}
|
||||
onFocus={focusHandler}
|
||||
onBlur={blurHandler}
|
||||
onChange={changeHandler}
|
||||
autoComplete={autoComplete}
|
||||
{...props}
|
||||
/>
|
||||
{clearable && <InputClearIcon heightRatio={heightRatio}
|
||||
{clearable && <InputClearIcon
|
||||
visibale={showClearIcon}
|
||||
heightRatio={heightRatio}
|
||||
disabled={disabled || readOnly}
|
||||
onClick={clearHandler} />}
|
||||
{iconRight && <InputIcon icon={iconRight} ratio={heightRatio} />}
|
||||
|
||||
257
pages/docs/components/auto-complete.mdx
Normal file
257
pages/docs/components/auto-complete.mdx
Normal file
@@ -0,0 +1,257 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { AutoComplete, Spacer, Badge, Row } from 'components'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
|
||||
export const meta = {
|
||||
title: 'AutoComplete',
|
||||
description: 'auto-complete',
|
||||
}
|
||||
|
||||
## Auto Complete
|
||||
|
||||
|
||||
<Playground
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const options = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
return <AutoComplete placeholder="Enter here" options={options} />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="disabled"
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const options = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
return <AutoComplete disabled options={options} initialValue="London" />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="search"
|
||||
scope={{ AutoComplete, useState }}
|
||||
code={`
|
||||
() => {
|
||||
const allOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
const [options, setOptions] = useState()
|
||||
const searchHandler = (currentValue) => {
|
||||
if (!currentValue) return setOptions([])
|
||||
const relatedOptions = allOptions.filter(item => item.value.includes(currentValue))
|
||||
setOptions(relatedOptions)
|
||||
}
|
||||
return <AutoComplete options={options} placeholder="Enter here" onSearch={searchHandler} />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Waiting in search"
|
||||
scope={{ AutoComplete, useState, useEffect, useRef }}
|
||||
code={`
|
||||
() => {
|
||||
const allOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
const [options, setOptions] = useState()
|
||||
const [searching, setSearching] = useState(false)
|
||||
const timer = useRef()
|
||||
// triggered every time input
|
||||
const searchHandler = (currentValue) => {
|
||||
if (!currentValue) return setOptions([])
|
||||
setSearching(true)
|
||||
const relatedOptions = allOptions.filter(item => item.value.includes(currentValue))
|
||||
// this is mock async request
|
||||
// you can get data in any way
|
||||
timer.current && clearTimeout(timer.current)
|
||||
timer.current = setTimeout(() => {
|
||||
setOptions(relatedOptions)
|
||||
setSearching(false)
|
||||
clearTimeout(timer.current)
|
||||
}, 1000)
|
||||
}
|
||||
return (
|
||||
<AutoComplete searching={searching}
|
||||
options={options}
|
||||
placeholder="Enter here"
|
||||
onSearch={searchHandler} />
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="custom searching text"
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
<AutoComplete searching placeholder="Enter here" width="100%">
|
||||
<AutoComplete.Searching>
|
||||
<span style={{ color: 'red' }}>waiting...</span>
|
||||
</AutoComplete.Searching>
|
||||
</AutoComplete>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="custom no options"
|
||||
scope={{ AutoComplete, useState }}
|
||||
code={`
|
||||
() => {
|
||||
const allOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
const [options, setOptions] = useState()
|
||||
const searchHandler = (currentValue) => {
|
||||
if (!currentValue) return setOptions([])
|
||||
const relatedOptions = allOptions.filter(item => item.value.includes(currentValue))
|
||||
setOptions(relatedOptions)
|
||||
}
|
||||
return (
|
||||
<AutoComplete placeholder="Enter here" width="100%" options={options} onSearch={searchHandler}>
|
||||
<AutoComplete.Empty>
|
||||
<span>no options...</span>
|
||||
</AutoComplete.Empty>
|
||||
</AutoComplete>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="custom option"
|
||||
scope={{ AutoComplete, useState, Spacer, Badge, Row }}
|
||||
code={`
|
||||
() => {
|
||||
const makeOption = (label, value) => (
|
||||
<AutoComplete.Option value={value}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex-inline', width: '100%', alignItems: 'space-between' }}>
|
||||
<h4>Recent search results </h4>
|
||||
<Badge type="success" style={{ float: 'right' }}>Recommended</Badge>
|
||||
</div>
|
||||
<Spacer y={.5} />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
</AutoComplete.Option>
|
||||
)
|
||||
const allOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
const [options, setOptions] = useState()
|
||||
const searchHandler = (currentValue) => {
|
||||
if (!currentValue) return setOptions([])
|
||||
const relatedOptions = allOptions.filter(item => item.value.includes(currentValue))
|
||||
const customOptions = relatedOptions.map(({ label, value }) => makeOption(label, value))
|
||||
setOptions(customOptions)
|
||||
}
|
||||
return (
|
||||
<AutoComplete placeholder="Enter here"
|
||||
width="100%"
|
||||
options={options}
|
||||
onSearch={searchHandler} />
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="size"
|
||||
scope={{ AutoComplete, Spacer }}
|
||||
code={`
|
||||
() => {
|
||||
const options = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
return (
|
||||
<>
|
||||
<AutoComplete placeholder="Mini" size="mini" options={options} />
|
||||
<Spacer y={.5} />
|
||||
<AutoComplete placeholder="Small" size="small" options={options} />
|
||||
<Spacer y={.5} />
|
||||
<AutoComplete placeholder="Medium" size="medium" options={options} />
|
||||
<Spacer y={.5} />
|
||||
<AutoComplete placeholder="Large" size="large" options={options} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="clearable"
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const options = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
return <AutoComplete placeholder="Enter here" clearable options={options} />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/docs/components/auto-complete.mdx">
|
||||
<Attributes.Title>AutoComplete.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **options** | options of input | `AutoCompleteOptions` | - | - |
|
||||
| **status** | input type | `NormalTypes` | `'default', 'secondary', 'success', 'warning', 'error'` | `default` |
|
||||
| **size** | input size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` |
|
||||
| **initialValue** | initial value | `string` | - | - |
|
||||
| **value** | current value | `string` | - | - |
|
||||
| **width** | container width | `string` | - | - |
|
||||
| **clearable** | show clear icon | `boolean` | - | `false` |
|
||||
| **searching** | show loading icon for search | `boolean` | - | `false` |
|
||||
| **onChange** | value of input is changed | `(value: string) => void` | - | - |
|
||||
| **onSearch** | called when searching items | `(value: string) => void` | - | - |
|
||||
| **onSelect** | called when a option is selected | `(value: string) => void` | - | - |
|
||||
| ... | native props | `InputHTMLAttributes` | `'autoComplete', 'type', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title alias="AutoComplete.Option">AutoComplete.Item</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **value** | a unique ident value | `string` | - | - |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>AutoComplete.Searching</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>AutoComplete.Empty</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>type AutoCompleteOptions</Attributes.Title>
|
||||
|
||||
```ts
|
||||
Array<{
|
||||
label: string
|
||||
value: string
|
||||
} | AutoComplete.Item>
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
Reference in New Issue
Block a user