diff --git a/packages/react-cascader/README.md b/packages/react-cascader/README.md index 42fdff3c91..8429d8cd3c 100644 --- a/packages/react-cascader/README.md +++ b/packages/react-cascader/README.md @@ -57,7 +57,71 @@ const Demo = () => { return (
- console.log(value, seleteds)} /> + < Cascader + allowClear={true} + placeholder="请选择" + value={[1, 4, 7]} + option={options} + onChange={(value, seleteds) => console.log(value, seleteds)} + /> +
+ ) +}; +ReactDOM.render(, _mount_); +``` + +## 搜索选项 + + +```jsx +import ReactDOM from 'react-dom'; +import { Cascader } from 'uiw'; + +const Demo = () => { + + const options = [ + { + label: '上海市', + value: 1, + children: [ + { + label: '徐汇区', + value: 4, + children: [ + { label: '半淞园路街道', value: 6 }, + { label: '南京东路街道', value: 7 }, + { label: '外滩街道', value: 8 }, + ] + }, + ] + }, + { + label: '北京市', + value: 9, + children: [ + { + label: '崇文区', + value: 12, + children: [ + { label: '东花市街道', value: 13 }, + { label: '体育馆路街道', value: 14 }, + { label: '前门街道', value: 15 }, + ] + }, + ] + }, + ]; + + return ( +
+ console.log(value, seleteds)} + onSearch={(text)=> console.log('text', text)} + />
) }; @@ -138,6 +202,7 @@ const options = [ children: ( console.log(value, seleteds)} @@ -183,3 +248,4 @@ ReactDOM.render(, _mount_); | option | 选项菜单 | { value: String \| Number, label: React.ReactNode, children: Array} | - | - | | value | 指定当前选中的条目,多选时为一个数组 | String[] \| Number[] | - | - | | onChange | 选中选项调用此函数 | function( isSeleted, value, selectedOptions) | - | - | +| onSearch | 开启搜索选项 | functionon(searchText) \| Boolean | - | v4.16.1 | diff --git a/packages/react-cascader/src/index.tsx b/packages/react-cascader/src/index.tsx index eaa79b2dc3..0e4b2d92e5 100644 --- a/packages/react-cascader/src/index.tsx +++ b/packages/react-cascader/src/index.tsx @@ -8,20 +8,22 @@ import './style/index.less'; type ValueType = Array; type OptionType = { value: string | number; label: React.ReactNode; children?: Array }; +type SearchOptionType = { label: string; options: Array }; export interface CascaderProps extends IProps, DropdownProps { option?: Array; value?: ValueType; onChange?: (isSeleted: boolean, value: ValueType, selectedOptions: Array) => void; + onSearch?: boolean | ((searchText: string) => void); allowClear?: boolean; placeholder?: string; - isOpen?: boolean; } function Cascader(props: CascaderProps) { const { value, onChange, + onSearch, allowClear, placeholder, @@ -36,16 +38,48 @@ function Cascader(props: CascaderProps) { const [innerIsOpen, setInnerIsOpen] = useState(false); const [selectedValue, setSelectedValue] = useState>([]); const [selectIconType, setSelectIconType] = useState(''); + const [searchText, setSearchText] = useState(''); + const [searchOn, setSearchOn] = useState(false); + const [inputValue, setInputValue] = useState(''); + const [searchOption, setSearchOption] = useState>(); useEffect(() => { - const valueTemp: Array = []; - let optChildren = option; - value?.map((item) => { - const findOpt = optChildren.find((opt) => opt.value === item); - optChildren = findOpt?.children || []; - valueTemp.push({ label: item, value: item, ...findOpt }); + if (onSearch) { + const tempOptions: Array = []; + iteratorOption(option, (opt) => { + const label = opt.map((m) => m.label).join(' / '); + tempOptions.push({ label, options: opt }); + }); + setSearchOption(tempOptions); + } + }, [onSearch]); + + const iteratorOption = ( + options: Array, + cb: (opt: Array) => void, + opts: Array = [], + ) => { + options.map((opt) => { + const optsTemp = [...opts, opt]; + if (opt.children) { + iteratorOption(opt.children, cb, optsTemp); + } else { + cb?.(optsTemp); + } }); - setSelectedValue(valueTemp); + }; + + useEffect(() => { + if (value) { + const valueTemp: Array = []; + let optChildren = option; + value?.map((item) => { + const findOpt = optChildren.find((opt) => opt.value === item); + optChildren = findOpt?.children || []; + valueTemp.push({ label: item, value: item, ...findOpt }); + }); + setSelectedValue(valueTemp); + } }, [value]); function onVisibleChange(isOpen: boolean) { @@ -62,6 +96,12 @@ function Cascader(props: CascaderProps) { setSelectIconType(selectIconType); } + const searchItemClick = (options: Array) => { + setSearchText(''); + setInnerIsOpen(false); + handelChange(false, options); + }; + const handleItemClick = (optionsItem: OptionType, level: number) => { selectedValue.splice(level, selectedValue.length - level, optionsItem); @@ -76,10 +116,21 @@ function Cascader(props: CascaderProps) { const onClear = (e: React.MouseEvent) => { e.stopPropagation(); - console.log(123); handelChange(false, []); }; + const handelSearch = (searchText: string) => { + setSearchText(searchText); + }; + + const inputChange = (e: React.ChangeEvent) => { + if (!innerIsOpen) { + setInnerIsOpen(!innerIsOpen); + } + const value = e.target.value; + onSearch && handelSearch(value); + }; + const widths = (style?.width as number) * 0.5 || undefined; const OptionIter = (option: Array, level: number = 0) => { @@ -116,9 +167,10 @@ function Cascader(props: CascaderProps) { ) as JSX.Element; }; - const inputValue = useMemo(() => { - return selectedValue.map((opt) => opt.label).join(' / '); - }, [selectedValue.length]); + useEffect(() => { + const inputValue = selectedValue.map((opt) => opt.label).join(' / '); + setInputValue(inputValue); + }, [selectedValue]); return ( - {new Array(selectedValue.length + 1) - .fill(0) - .map((_, index) => { - const options = index ? selectedValue[index - 1]?.children! : option; - return OptionIter(options, index); - }) - .filter((m) => !!m)} - + !searchText ? ( +
+ {new Array(selectedValue.length + 1) + .fill(0) + .map((_, index) => { + const options = index ? selectedValue[index - 1]?.children! : option; + return OptionIter(options, index); + }) + .filter((m) => !!m)} +
+ ) : ( + + {!searchOption || searchOption.length === 0 ? ( +
{'没有数据'}
+ ) : ( + searchOption + .filter((opt) => opt.label.includes(searchText.trim())) + .map((opt, index) => { + return ( + searchItemClick(opt.options)} // + /> + ); + }) + )} +
+ ) } > renderSelectIcon('leave')} onMouseOver={() => renderSelectIcon('enter')}> {}} - placeholder={placeholder} + value={searchOn ? searchText : inputValue} + onChange={inputChange} + placeholder={searchOn ? inputValue : placeholder} style={{ width: style?.width }} - readOnly + onFocus={() => onSearch && setSearchOn(true)} + onBlur={() => onSearch && setSearchOn(false)} + readOnly={!onSearch} addonAfter={ - selectIconType === 'close' && ( - - ) + + {selectIconType === 'close' && ( + + )} + } />