feat(select): add clearable option to select multiple with test and english doc (#396)

* docs: add clearable option to select multiple with test and english doc

* fix: fix types for onClear

* fix: fix import path for use-theme

add more test for coverage

* docs(select): add chinese document

Co-authored-by: unix <unix.bio@gmail.com>
This commit is contained in:
William
2020-10-29 03:55:05 +00:00
committed by GitHub
parent 732f44dd37
commit a9867cc50d
6 changed files with 176 additions and 22 deletions

View File

@@ -15,6 +15,26 @@ describe('Select Multiple', () => {
expect(() => wrapper.unmount()).not.toThrow()
})
it('should render correctly with clearable option', () => {
const wrapper = mount(
<Select multiple initialValue={['1']}>
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">Option 2</Select.Option>
</Select>,
)
expect(wrapper.find('.clear-icon').length).toBe(1)
})
it('should render correctly without clearable option', () => {
const wrapper = mount(
<Select multiple clearable={false} initialValue={['1']}>
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">Option 2</Select.Option>
</Select>,
)
expect(wrapper.find('.clear-icon').length).toBe(0)
})
it('should render value with initial-value', () => {
const wrapper = mount(
<Select initialValue={['1', '2']} multiple>
@@ -56,3 +76,18 @@ describe('Select Multiple', () => {
changeHandler.mockRestore()
})
})
it('should trigger event correctly when clicked', async () => {
const changeHandler = jest.fn()
const wrapper = mount(
<Select onChange={changeHandler} multiple initialValue={['1']}>
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">Option 2</Select.Option>
</Select>,
)
expect(wrapper.find('.clear-icon').length).toBe(1)
wrapper.find('.clear-icon').simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expect(changeHandler).toHaveBeenCalled()
expect(wrapper.find('.clear-icon').length).toBe(0)
})

View File

@@ -0,0 +1,66 @@
import React, { useMemo } from 'react'
import useTheme from '../use-theme'
interface Props {
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void
heightRatio?: string | undefined
}
const SelectIconClear: React.FC<Props> = ({ onClick, heightRatio }) => {
const theme = useTheme()
const width = useMemo(() => {
return heightRatio ? `calc(10.66px * ${heightRatio})` : '18px'
}, [heightRatio])
const clickHandler = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault()
event.stopPropagation()
event.nativeEvent.stopImmediatePropagation()
onClick && onClick(event)
}
return (
<div onClick={clickHandler} className="clear-icon">
<svg
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shapeRendering="geometricPrecision">
<path d="M18 6L6 18" />
<path d="M6 6l12 12" />
</svg>
<style jsx>{`
.clear-icon {
padding: 0;
padding-left: ${theme.layout.gapQuarter};
margin: 0;
display: inline-flex;
align-items: center;
height: 100%;
cursor: pointer;
box-sizing: border-box;
transition: color 150ms ease 0s;
color: ${theme.palette.accents_3};
visibility: visible;
opacity: 1;
}
.clear-icon:hover {
color: ${theme.palette.foreground};
}
svg {
color: currentColor;
width: ${width};
height: ${width};
}
`}</style>
</div>
)
}
const MemoSelectIconClear = React.memo(SelectIconClear)
export default MemoSelectIconClear

View File

@@ -1,26 +1,33 @@
import React from 'react'
import useTheme from '../use-theme'
import Grid from '../grid'
import SelectClearIcon from './select-icon-clear'
interface Props {
disabled: boolean
onClear: (() => void) | null
size: string
}
const SelectMultipleValue: React.FC<React.PropsWithChildren<Props>> = ({
disabled,
size,
onClear,
children,
}) => {
const theme = useTheme()
return (
<Grid>
<div className="item">{children}</div>
<div className="item">
{children}
{!!onClear && <SelectClearIcon heightRatio="1.5" onClick={onClear} />}
</div>
<style jsx>{`
.item {
display: inline-flex;
height: calc(${size} * 2);
justify-items: center;
align-items: center;
line-height: 1;
padding: 0 0.5rem;
@@ -30,8 +37,8 @@ const SelectMultipleValue: React.FC<React.PropsWithChildren<Props>> = ({
color: ${disabled ? theme.palette.accents_4 : theme.palette.accents_6};
}
.item > :global(div),
.item > :global(div:hover) {
.item > :global(div:not(.clear-icon)),
.item > :global(div:not(.clear-icon):hover) {
border-radius: 0;
background-color: transparent;
padding: 0;

View File

@@ -23,6 +23,7 @@ interface Props {
onChange?: (value: string | string[]) => void
pure?: boolean
multiple?: boolean
clearable?: boolean
className?: string
width?: string
dropdownClassName?: string
@@ -37,6 +38,7 @@ const defaultProps = {
icon: SelectIcon as React.ComponentType,
pure: false,
multiple: false,
clearable: true,
width: 'initial',
className: '',
disableMatchWidth: false,
@@ -55,6 +57,7 @@ const Select: React.FC<React.PropsWithChildren<SelectProps>> = ({
onChange,
pure,
multiple,
clearable,
placeholder,
width,
className,
@@ -125,7 +128,10 @@ const Select: React.FC<React.PropsWithChildren<SelectProps>> = ({
const el = React.cloneElement(child, { preventAllEvents: true })
if (!multiple) return el
return (
<SelectMultipleValue size={sizes.fontSize} disabled={disabled}>
<SelectMultipleValue
size={sizes.fontSize}
disabled={disabled}
onClear={clearable ? () => updateValue(child.props.value) : null}>
{el}
</SelectMultipleValue>
)

View File

@@ -111,6 +111,25 @@ Display a dropdown list of items.
`}
/>
<Playground
title="Multiple without clear"
desc="`Select` supports multiple values to be selected without the clear icon."
scope={{ Select }}
code={`
<Select placeholder="Frameworks" multiple width="200px" clearable={false} initialValue={['1', '3', '4', '6']}>
<Select.Option value="1">React</Select.Option>
<Select.Option value="2">Angular</Select.Option>
<Select.Option value="3">Vue</Select.Option>
<Select.Option divider />
<Select.Option value="4">Rails</Select.Option>
<Select.Option value="5">Sinatra</Select.Option>
<Select.Option divider />
<Select.Option value="6">Express</Select.Option>
<Select.Option value="7">Koa</Select.Option>
</Select>
`}
/>
<Playground
title="Compose"
desc="Use with other components."
@@ -175,23 +194,24 @@ Display a dropdown list of items.
<Attributes edit="/pages/en-us/components/select.mdx">
<Attributes.Title>Select.Props</Attributes.Title>
| Attribute | Description | Type | Accepted values | Default |
| --------------------- | ----------------------------------------------------- | --------------------------------------------------- | --------------------------------- | --------------- |
| **value** | selected value | `string`, `string[]` | - | - |
| **initialValue** | initial value | `string`, `string[]` | - | - |
| **placeholder** | placeholder string | `string` | - | - |
| **width** | css width value of select | `string` | - | `initial` |
| **size** | select component size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
| **icon** | icon component | `ComponentType` | - | `SVG Component` |
| **pure** | remove icon component | `boolean` | - | `false` |
| **multiple** | support multiple selection | `boolean` | - | `false` |
| **disabled** | disable current radio | `boolean` | - | `false` |
| **onChange** | selected value | <Code>(val: string &#124; string[]) => void </Code> | - | - |
| **dropdownClassName** | className of dropdown menu | `string` | - | - |
| **dropdownStyle** | style of dropdown menu | `object` | - | - |
| **disableMatchWidth** | disable Option from follow Select width | `boolean` | - | `false` |
| **getPopupContainer** | dropdown render parent element, the default is `body` | `() => HTMLElement` | - | - |
| ... | native props | `HTMLAttributes` | `'name', 'alt', 'className', ...` | - |
| Attribute | Description | Type | Accepted values | Default |
| --------------------- | -------------------------------------------------------- | --------------------------------------------------- | --------------------------------- | --------------- |
| **value** | selected value | `string`, `string[]` | - | - |
| **initialValue** | initial value | `string`, `string[]` | - | - |
| **placeholder** | placeholder string | `string` | - | - |
| **width** | css width value of select | `string` | - | `initial` |
| **size** | select component size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
| **icon** | icon component | `ComponentType` | - | `SVG Component` |
| **pure** | remove icon component | `boolean` | - | `false` |
| **multiple** | support multiple selection | `boolean` | - | `false` |
| **clearable** | add clear icon on multiple selection (ignored otherwise) | `boolean` | - | `true` |
| **disabled** | disable current radio | `boolean` | - | `false` |
| **onChange** | selected value | <Code>(val: string &#124; string[]) => void </Code> | - | - |
| **dropdownClassName** | className of dropdown menu | `string` | - | - |
| **dropdownStyle** | style of dropdown menu | `object` | - | - |
| **disableMatchWidth** | disable Option from follow Select width | `boolean` | - | `false` |
| **getPopupContainer** | dropdown render parent element, the default is `body` | `() => HTMLElement` | - | - |
| ... | native props | `HTMLAttributes` | `'name', 'alt', 'className', ...` | - |
<Attributes.Title>Select.Option.Props</Attributes.Title>

View File

@@ -94,7 +94,7 @@ export const meta = {
<Playground
title="多选"
desc="Select 组件支持同时选中多个值。"
desc="`Select` 组件支持同时选中多个值。"
scope={{ Select }}
code={`
<Select placeholder="选择框架" multiple width="200px" initialValue={['1', '3', '4', '6']}>
@@ -111,6 +111,25 @@ export const meta = {
`}
/>
<Playground
title="无移除图标的多选"
desc="`Select` 支持不携带移除图标的多选控件。"
scope={{ Select }}
code={`
<Select placeholder="选择框架" multiple width="200px" clearable={false} initialValue={['1', '3', '4', '6']}>
<Select.Option value="1">React</Select.Option>
<Select.Option value="2">Angular</Select.Option>
<Select.Option value="3">Vue</Select.Option>
<Select.Option divider />
<Select.Option value="4">Rails</Select.Option>
<Select.Option value="5">Sinatra</Select.Option>
<Select.Option divider />
<Select.Option value="6">Express</Select.Option>
<Select.Option value="7">Koa</Select.Option>
</Select>
`}
/>
<Playground
title="组合"
desc="与其他组件组合使用。"
@@ -185,6 +204,7 @@ export const meta = {
| **icon** | 右侧图标组件 | `ComponentType` | - | `SVG Component` |
| **pure** | 隐藏右侧图标组件 | `boolean` | - | `false` |
| **multiple** | 是否支持多选 | `boolean` | - | `false` |
| **clearable** | 多选时是否展示移除图标 | `boolean` | - | `true` |
| **disabled** | 禁用所有的交互 | `boolean` | - | `false` |
| **onChange** | 选项被选中所触发的事件 | <Code>(val: string &#124; string[]) => void </Code> | - | - |
| **dropdownClassName** | 下拉框的自定义类名 | `string` | - | - |