mirror of
https://github.com/zhigang1992/react.git
synced 2026-04-26 23:04:55 +08:00
style(prettier): format code style
This commit is contained in:
@@ -9,7 +9,7 @@ describe('AutoComplete', () => {
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
expect(<AutoComplete />).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
||||
it('should support sizes and status', () => {
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
@@ -17,37 +17,37 @@ describe('AutoComplete', () => {
|
||||
<AutoComplete status="success" />
|
||||
<AutoComplete size="mini" />
|
||||
<AutoComplete size="large" />
|
||||
</div>
|
||||
</div>,
|
||||
)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
|
||||
it('should set input value from initial value', () => {
|
||||
let wrapper = mount(<AutoComplete initialValue="value" />)
|
||||
let input = wrapper.find('input').at(0).getDOMNode()
|
||||
expect((input as HTMLInputElement).value).toEqual('value')
|
||||
|
||||
|
||||
wrapper = mount(<AutoComplete value="value2" />)
|
||||
input = wrapper.find('input').at(0).getDOMNode()
|
||||
expect((input as HTMLInputElement).value).toEqual('value2')
|
||||
})
|
||||
|
||||
|
||||
it('should render clear icon', () => {
|
||||
const wrapper = mount(<AutoComplete initialValue="value" />)
|
||||
expect(wrapper.find('svg').length).toBe(0)
|
||||
|
||||
|
||||
wrapper.setProps({ clearable: true })
|
||||
expect(wrapper.find('svg').length).toBe(1)
|
||||
|
||||
|
||||
wrapper.find('svg').simulate('click', nativeEvent)
|
||||
const input = wrapper.find('input').at(0).getDOMNode()
|
||||
expect((input as HTMLInputElement).value).toEqual('')
|
||||
})
|
||||
|
||||
|
||||
it('should reponse width change', () => {
|
||||
const wrapper = mount(<AutoComplete initialValue="value" width="100px" />)
|
||||
expect(wrapper.prop('width')).toEqual('100px')
|
||||
|
||||
|
||||
wrapper.setProps({ width: '200px' })
|
||||
expect(wrapper.prop('width')).toEqual('200px')
|
||||
})
|
||||
|
||||
@@ -2,9 +2,7 @@ import React from 'react'
|
||||
import { mount, render } from 'enzyme'
|
||||
import { AutoComplete } from 'components'
|
||||
import { nativeEvent } from 'tests/utils'
|
||||
const mockOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
]
|
||||
const mockOptions = [{ label: 'London', value: 'london' }]
|
||||
|
||||
describe('AutoComplete Search', () => {
|
||||
it('should render options element', () => {
|
||||
@@ -12,88 +10,85 @@ describe('AutoComplete Search', () => {
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
let dropdown = wrapper.find('.auto-complete-dropdown').children()
|
||||
expect(dropdown.length).not.toBe(0)
|
||||
|
||||
|
||||
wrapper.find('input').at(0).simulate('blur')
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
|
||||
it('should update value when dropdown clicked', () => {
|
||||
let value = ''
|
||||
const wrapper = mount(<AutoComplete options={mockOptions} onChange={val => value = val} />)
|
||||
const wrapper = mount(<AutoComplete options={mockOptions} onChange={(val) => (value = val)} />)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(value).toEqual('london')
|
||||
})
|
||||
|
||||
|
||||
it('should ignore events when disabled', () => {
|
||||
let value = ''
|
||||
const wrapper = mount(
|
||||
<AutoComplete disabled options={mockOptions}
|
||||
onChange={val => value = val} />
|
||||
<AutoComplete disabled options={mockOptions} onChange={(val) => (value = val)} />,
|
||||
)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(value).not.toEqual('london')
|
||||
})
|
||||
|
||||
|
||||
it('should render searching component', () => {
|
||||
let wrapper = mount(
|
||||
<AutoComplete searching={false} options={mockOptions}/>
|
||||
)
|
||||
let wrapper = mount(<AutoComplete searching={false} options={mockOptions} />)
|
||||
wrapper.setProps({ searching: true })
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
let dropdown = wrapper.find('.auto-complete-dropdown')
|
||||
expect(dropdown.text()).not.toContain('london')
|
||||
|
||||
|
||||
const loading = wrapper.find('.loading')
|
||||
expect(loading.length).not.toBe(0)
|
||||
|
||||
|
||||
wrapper = mount(
|
||||
<AutoComplete searching options={mockOptions}>
|
||||
<AutoComplete.Searching>
|
||||
<span>waiting...</span>
|
||||
</AutoComplete.Searching>
|
||||
</AutoComplete>
|
||||
</AutoComplete>,
|
||||
)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
dropdown = wrapper.find('.auto-complete-dropdown')
|
||||
expect(dropdown.text()).toContain('waiting')
|
||||
})
|
||||
|
||||
|
||||
it('should hide empty component', () => {
|
||||
let wrapper = render(
|
||||
<AutoComplete placeholder="Enter here">
|
||||
<AutoComplete.Empty hidden />
|
||||
</AutoComplete>
|
||||
</AutoComplete>,
|
||||
)
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
|
||||
|
||||
wrapper = render(
|
||||
<AutoComplete placeholder="Enter here">
|
||||
<AutoComplete.Empty>empty</AutoComplete.Empty>
|
||||
</AutoComplete>
|
||||
</AutoComplete>,
|
||||
)
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
|
||||
|
||||
const mountWrapper = mount(
|
||||
<AutoComplete placeholder="Enter here" initialValue="value">
|
||||
<AutoComplete.Empty>empty</AutoComplete.Empty>
|
||||
</AutoComplete>
|
||||
</AutoComplete>,
|
||||
)
|
||||
mountWrapper.find('input').at(0).simulate('focus')
|
||||
const text = mountWrapper.find('.auto-complete-dropdown').text()
|
||||
expect(text).toContain('empty')
|
||||
|
||||
|
||||
const mountWrapper2 = mount(
|
||||
<AutoComplete placeholder="Enter here" initialValue="value">
|
||||
<AutoComplete.Empty hidden>empty</AutoComplete.Empty>
|
||||
</AutoComplete>
|
||||
</AutoComplete>,
|
||||
)
|
||||
mountWrapper2.find('input').at(0).simulate('focus')
|
||||
const text2 = mountWrapper2.find('.auto-complete-dropdown').text()
|
||||
expect(text2).not.toContain('empty')
|
||||
})
|
||||
|
||||
|
||||
it('should trigger search handler', () => {
|
||||
const handler = jest.fn()
|
||||
const wrapper = mount(<AutoComplete initialValue="value" onSearch={handler} />)
|
||||
@@ -103,20 +98,24 @@ describe('AutoComplete Search', () => {
|
||||
;(input.getDOMNode() as HTMLInputElement).value = 'value'
|
||||
expect(handler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
it('should trigger select and change handler', () => {
|
||||
const selectHandler = jest.fn()
|
||||
const changeHandler = jest.fn()
|
||||
const wrapper = mount(
|
||||
<AutoComplete options={mockOptions} initialValue="value"
|
||||
onSelect={selectHandler} onChange={changeHandler} />
|
||||
<AutoComplete
|
||||
options={mockOptions}
|
||||
initialValue="value"
|
||||
onSelect={selectHandler}
|
||||
onChange={changeHandler}
|
||||
/>,
|
||||
)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(selectHandler).toHaveBeenCalled()
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
it('should work with custom options', () => {
|
||||
const changeHandler = jest.fn()
|
||||
const makeOption = (label: string, value: string): any => (
|
||||
@@ -124,19 +123,17 @@ describe('AutoComplete Search', () => {
|
||||
<span>{label}</span>
|
||||
</AutoComplete.Option>
|
||||
)
|
||||
const options = mockOptions
|
||||
.map(({ label, value }) => makeOption(label, value) as typeof AutoComplete.Option)
|
||||
const wrapper = mount(
|
||||
<AutoComplete options={options} onChange={changeHandler} />
|
||||
const options = mockOptions.map(
|
||||
({ label, value }) => makeOption(label, value) as typeof AutoComplete.Option,
|
||||
)
|
||||
const wrapper = mount(<AutoComplete options={options} onChange={changeHandler} />)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
it('should work correctly without options', () => {
|
||||
const wrapper = mount(<AutoComplete options={[]} />)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -17,4 +17,5 @@ const defaultContext = {
|
||||
|
||||
export const AutoCompleteContext = React.createContext<AutoCompleteConfig>(defaultContext)
|
||||
|
||||
export const useAutoCompleteContext = (): AutoCompleteConfig => React.useContext<AutoCompleteConfig>(AutoCompleteContext)
|
||||
export const useAutoCompleteContext = (): AutoCompleteConfig =>
|
||||
React.useContext<AutoCompleteConfig>(AutoCompleteContext)
|
||||
|
||||
@@ -7,9 +7,7 @@ interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const AutoCompleteDropdown: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children, visible
|
||||
}) => {
|
||||
const AutoCompleteDropdown: React.FC<React.PropsWithChildren<Props>> = ({ children, visible }) => {
|
||||
const theme = useTheme()
|
||||
const { ref } = useAutoCompleteContext()
|
||||
const clickHandler = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
@@ -17,18 +15,18 @@ const AutoCompleteDropdown: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
event.stopPropagation()
|
||||
event.nativeEvent.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Dropdown parent={ref} visible={visible}>
|
||||
<div className="auto-complete-dropdown" onClick={clickHandler}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.auto-complete-dropdown {
|
||||
border-radius: ${theme.layout.radius};
|
||||
box-shadow: ${theme.expressiveness.shadowLarge};
|
||||
background-color: ${theme.palette.background};
|
||||
}
|
||||
`}</style>
|
||||
.auto-complete-dropdown {
|
||||
border-radius: ${theme.layout.radius};
|
||||
box-shadow: ${theme.expressiveness.shadowLarge};
|
||||
background-color: ${theme.palette.background};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</Dropdown>
|
||||
)
|
||||
|
||||
@@ -15,7 +15,9 @@ const defaultProps = {
|
||||
export type AutoCompleteEmptyProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const AutoCompleteEmpty: React.FC<React.PropsWithChildren<AutoCompleteEmptyProps>> = ({
|
||||
children, hidden, className,
|
||||
children,
|
||||
hidden,
|
||||
className,
|
||||
}) => {
|
||||
if (hidden) return null
|
||||
return <AutoCompleteSearch className={className}>{children}</AutoCompleteSearch>
|
||||
|
||||
@@ -8,8 +8,7 @@ interface Props {
|
||||
value: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
}
|
||||
const defaultProps = {}
|
||||
|
||||
export type AutoCompleteItemProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
@@ -24,17 +23,18 @@ const getSizes = (size: NormalSizes) => {
|
||||
}
|
||||
|
||||
const AutoCompleteItem: React.FC<React.PropsWithChildren<AutoCompleteItemProps>> = ({
|
||||
value: identValue, children,
|
||||
value: identValue,
|
||||
children,
|
||||
}) => {
|
||||
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}
|
||||
@@ -55,21 +55,21 @@ const AutoCompleteItem: React.FC<React.PropsWithChildren<AutoCompleteItemProps>>
|
||||
cursor: 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};
|
||||
|
||||
@@ -13,7 +13,8 @@ const defaultProps = {
|
||||
export type AutoCompleteSearchProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const AutoCompleteSearch: React.FC<React.PropsWithChildren<AutoCompleteSearchProps>> = ({
|
||||
children, className,
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
|
||||
@@ -28,7 +29,7 @@ const AutoCompleteSearch: React.FC<React.PropsWithChildren<AutoCompleteSearchPro
|
||||
align-items: center;
|
||||
font-weight: normal;
|
||||
white-space: pre;
|
||||
font-size: .875rem;
|
||||
font-size: 0.875rem;
|
||||
padding: ${theme.layout.gapHalf};
|
||||
line-height: 1;
|
||||
background-color: ${theme.palette.background};
|
||||
|
||||
@@ -49,8 +49,7 @@ const childrenToOptionsNode = (options: Array<AutoCompleteOption>) =>
|
||||
if (React.isValidElement(item)) return React.cloneElement(item, { key })
|
||||
const validItem = item as AutoCompleteOption
|
||||
return (
|
||||
<AutoCompleteItem key={key}
|
||||
value={validItem.value}>
|
||||
<AutoCompleteItem key={key} value={validItem.value}>
|
||||
{validItem.label}
|
||||
</AutoCompleteItem>
|
||||
)
|
||||
@@ -64,9 +63,20 @@ const getSearchIcon = (searching?: boolean) => {
|
||||
}
|
||||
|
||||
const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
options, initialValue: customInitialValue, onSelect, onSearch, onChange,
|
||||
searching, children, size, status, value, width, clearable,
|
||||
disabled, ...props
|
||||
options,
|
||||
initialValue: customInitialValue,
|
||||
onSelect,
|
||||
onSearch,
|
||||
onChange,
|
||||
searching,
|
||||
children,
|
||||
size,
|
||||
status,
|
||||
value,
|
||||
width,
|
||||
clearable,
|
||||
disabled,
|
||||
...props
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [state, setState] = useState<string>(customInitialValue)
|
||||
@@ -77,7 +87,11 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
const hasSearchChild = searchChild && React.Children.count(searchChild) > 0
|
||||
const hasEmptyChild = emptyChild && React.Children.count(emptyChild) > 0
|
||||
if (searching) {
|
||||
return hasSearchChild ? searchChild : <AutoCompleteSearching>Searching...</AutoCompleteSearching>
|
||||
return hasSearchChild ? (
|
||||
searchChild
|
||||
) : (
|
||||
<AutoCompleteSearching>Searching...</AutoCompleteSearching>
|
||||
)
|
||||
}
|
||||
if (options.length === 0) {
|
||||
if (state === '') return null
|
||||
@@ -85,11 +99,8 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
}
|
||||
return childrenToOptionsNode(options as Array<AutoCompleteOption>)
|
||||
}, [searching, options])
|
||||
const showClearIcon = useMemo(
|
||||
() => clearable && searching === undefined,
|
||||
[clearable, searching],
|
||||
)
|
||||
|
||||
const showClearIcon = useMemo(() => clearable && searching === undefined, [clearable, searching])
|
||||
|
||||
const updateValue = (val: string) => {
|
||||
if (disabled) return
|
||||
onSelect && onSelect(val)
|
||||
@@ -100,7 +111,7 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
onSearch && onSearch(event.target.value)
|
||||
setState(event.target.value)
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
onChange && onChange(state)
|
||||
}, [state])
|
||||
@@ -108,22 +119,26 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
if (value === undefined) return
|
||||
setState(value)
|
||||
}, [value])
|
||||
|
||||
const initialValue = useMemo<AutoCompleteConfig>(() => ({
|
||||
ref, size,
|
||||
value: state,
|
||||
updateValue,
|
||||
visible,
|
||||
updateVisible,
|
||||
}), [state, visible, size])
|
||||
|
||||
|
||||
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,
|
||||
@@ -134,22 +149,23 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
return (
|
||||
<AutoCompleteContext.Provider value={initialValue}>
|
||||
<div ref={ref} className="auto-complete">
|
||||
<Input size={size} status={status}
|
||||
<Input
|
||||
size={size}
|
||||
status={status}
|
||||
onChange={onInputChange}
|
||||
onFocus={() => toggleFocusHandler(true)}
|
||||
onBlur={() => toggleFocusHandler(false)}
|
||||
clearable={showClearIcon}
|
||||
iconRight={getSearchIcon(searching)}
|
||||
{...inputProps} />
|
||||
<AutoCompleteDropdown visible={visible}>
|
||||
{autoCompleteItems}
|
||||
</AutoCompleteDropdown>
|
||||
{...inputProps}
|
||||
/>
|
||||
<AutoCompleteDropdown visible={visible}>{autoCompleteItems}</AutoCompleteDropdown>
|
||||
|
||||
<style jsx>{`
|
||||
.auto-complete {
|
||||
width: ${width || 'max-content'};
|
||||
}
|
||||
|
||||
|
||||
.auto-complete :global(.loading) {
|
||||
left: -3px;
|
||||
right: -3px;
|
||||
@@ -168,8 +184,9 @@ type AutoCompleteComponent<P = {}> = React.FC<P> & {
|
||||
Empty: typeof AutoCompleteEmpty
|
||||
}
|
||||
|
||||
type ComponentProps = Partial<typeof defaultProps> & Omit<Props, keyof typeof defaultProps> & NativeAttrs
|
||||
|
||||
(AutoComplete as AutoCompleteComponent<ComponentProps>).defaultProps = defaultProps
|
||||
type ComponentProps = Partial<typeof defaultProps> &
|
||||
Omit<Props, keyof typeof defaultProps> &
|
||||
NativeAttrs
|
||||
;(AutoComplete as AutoCompleteComponent<ComponentProps>).defaultProps = defaultProps
|
||||
|
||||
export default AutoComplete as AutoCompleteComponent<ComponentProps>
|
||||
|
||||
Reference in New Issue
Block a user