From f4666bc3557419af9d5272a4c3ee9f39408149b8 Mon Sep 17 00:00:00 2001 From: GEARLESS JOE <31471551+berber1016@users.noreply.github.com> Date: Fri, 19 Nov 2021 15:23:28 +0800 Subject: [PATCH] feat(list-picker): list-picker multiple mode (#1487) Co-authored-by: shiliqian --- src/cascader/Cascader.tsx | 8 ++--- src/input/Input.tsx | 1 - src/list-picker/Trigger.tsx | 1 + src/list-picker/demos/List-picker.stories.tsx | 32 +++++++++++++++++ src/list-picker/listPicker.tsx | 20 ++++------- src/list-picker/style/index.less | 4 +-- src/list-picker/util.ts | 1 - src/list/List.tsx | 35 +++++++++++-------- src/list/Selection.tsx | 2 -- src/list/context.tsx | 4 ++- src/list/hooks/useCacheOptions.ts | 1 - src/list/inner/CascaderItem.tsx | 5 +-- src/list/inner/ChckboxItem.tsx | 2 +- src/list/inner/baseItem.tsx | 2 +- src/list/interfance.ts | 2 +- src/list/style/index.less | 4 +-- src/select/Select.tsx | 12 +++---- 17 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/cascader/Cascader.tsx b/src/cascader/Cascader.tsx index 21abf1831f..41adaf8b12 100644 --- a/src/cascader/Cascader.tsx +++ b/src/cascader/Cascader.tsx @@ -14,7 +14,7 @@ const Cascader: React.FC = (props) => { const { options, value: controlledValue, - defaultValue = '', + defaultValue = undefined, visible: controlledVisible, onChange, prefixCls = 'cascader--new', @@ -37,7 +37,7 @@ const Cascader: React.FC = (props) => { ...rest } = props; const defaultPrefixCls = usePrefixCls(prefixCls); - const [value, setSelectValue] = useControlledState(controlledValue, defaultValue); + const [value, setValue] = useControlledState(controlledValue, defaultValue); const [visible, setVisible] = useControlledState(controlledVisible, false); const [title, setTitle] = useState(''); const cache = useChacheOptions(); @@ -55,7 +55,7 @@ const Cascader: React.FC = (props) => { const handleChange = (val?: string | string[]) => { onChange?.(val); - setSelectValue((val as string) ?? ''); + setValue(val as string); setVisible(false); }; @@ -77,7 +77,7 @@ const Cascader: React.FC = (props) => { disabled={disabled} onClear={handleOnClear} onInputChange={(val) => { - isEmpty(val) && handleChange(''); + isEmpty(val) && handleChange(); }} {...triggerProps} /> diff --git a/src/input/Input.tsx b/src/input/Input.tsx index 66ca7d9940..7f8bd6b901 100644 --- a/src/input/Input.tsx +++ b/src/input/Input.tsx @@ -69,7 +69,6 @@ const Input = React.forwardRef((props, ref) => { () => (customizeSuffix ?
{customizeSuffix}
: null), [suffixCls, customizeSuffix] ); - return ( {prefix} diff --git a/src/list-picker/Trigger.tsx b/src/list-picker/Trigger.tsx index bdede30b30..bca9c85167 100644 --- a/src/list-picker/Trigger.tsx +++ b/src/list-picker/Trigger.tsx @@ -8,6 +8,7 @@ const Trigger: React.FC = (props) => { onClear?.(e); e.stopPropagation(); }; + return ; }; diff --git a/src/list-picker/demos/List-picker.stories.tsx b/src/list-picker/demos/List-picker.stories.tsx index db92c16c9f..416765d362 100644 --- a/src/list-picker/demos/List-picker.stories.tsx +++ b/src/list-picker/demos/List-picker.stories.tsx @@ -30,6 +30,7 @@ export default { const Template: Story = () => { const [value, setValue] = useState('banana2'); + const [multipleValue, setMultipleValue] = useState(['ziyi']); const [activeTab, setActiveTab] = useState('tab1'); const [search, setSearch] = useState(''); const onChange = (val?: string, opt?: OptionProps | OptionProps[]) => { @@ -149,6 +150,37 @@ const Template: Story = () => { /> +
+ { + console.log('multiple onChange 并不会触发', val); + }} + onConfim={(val) => { + console.log(val); + setMultipleValue(val as string[]); + }} + overlayStyle={{ width: '240px' }} + model="multiple" + onClear={() => { + setValue(''); + }} + placeholder="请选择" + > + setSearch(val)} + /> + + +
); diff --git a/src/list-picker/listPicker.tsx b/src/list-picker/listPicker.tsx index 92f53f1770..5c1eae37ec 100644 --- a/src/list-picker/listPicker.tsx +++ b/src/list-picker/listPicker.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; -import { isArray } from 'lodash'; import { ListPickerProps } from './interfance'; import Popover from '../popover'; import Trigger from './Trigger'; @@ -32,12 +31,12 @@ const ListPicker: React.FC = (props) => { overlayStyle, children, onConfim, - needConfim = false, confimText = '确定', separator = '', className, style, - model, + model = 'simple', + needConfim = model === 'multiple', } = props; const defaultPrefix = usePrefixCls(prefixCls); const [visible, setVisible] = useControlledState(controlledVisible, false); @@ -46,20 +45,16 @@ const ListPicker: React.FC = (props) => { const { options, setOptions, getLabelByValue, getOptionsByValue } = useCacheOptions(); + // title仅跟随controlledValue变动 useEffect(() => { - setValue(controlledValue); - }, [controlledValue]); - - useEffect(() => { - console.log('getLabelByValue(value, separator)', getLabelByValue(value, separator)); - setTitle(getLabelByValue(value, separator)); - }, [value, getLabelByValue, separator]); + setTitle(getLabelByValue(controlledValue, separator)); + }, [controlledValue, getLabelByValue, separator]); useEffect(() => { if (!needConfim) { setValue(controlledValue); } - }, [controlledValue, needConfim]); + }, [controlledValue, needConfim, setValue]); // methods const handVisibleChange = (vis: boolean) => { setVisible(vis); @@ -72,7 +67,6 @@ const ListPicker: React.FC = (props) => { const handleChange = (val?: string | string[], opts?: OptionProps | OptionProps[]) => { setValue(val); - setTitle(!isArray(opts) ? opts?.label : ''); if (!needConfim) { onChange?.(val, opts); } @@ -116,7 +110,7 @@ const ListPicker: React.FC = (props) => { ); return ( - + { flattenOption.push({ groupName: value.label, diff --git a/src/list/List.tsx b/src/list/List.tsx index 0aa163a2ea..dc56d4302a 100644 --- a/src/list/List.tsx +++ b/src/list/List.tsx @@ -47,13 +47,19 @@ const List: React.ForwardRefRenderFunction & { const [collapse, setCollapse] = useState(initCollapse); // value and onChange - const { value: contextValue, onChange: contextOnChange, setOptions: contextSetOptions } = useContext(ListContext); + const { + value: contextValue, + model: contextModel, + onChange: contextOnChange, + setOptions: contextSetOptions, + } = useContext(ListContext); + const mergedModel = contextModel ?? model; const { value, onChange } = useValue( controlledValue, controlledOnChange, contextValue, contextOnChange, - isMultipe(model) + isMultipe(mergedModel) ); const cache = useCacheOptions(); @@ -73,22 +79,21 @@ const List: React.ForwardRefRenderFunction & { }, [mergedOptions, setOptions]); const renderOptions = initOptions?.length ? initOptions : React.Children.toArray(children); - console.log('renderOptions', renderOptions); const childrens = renderOptions.slice(0, collapse); const isNeedCollapse = useMemo(() => renderOptions?.length > collapse, [renderOptions, collapse]); - const handleClick = (val: string, opt?: OptionProps) => { + const handleClick = (val: string) => { // multiple if (isArray(value)) { const resultValue = indexOf(value, val) !== -1 ? difference(value, [val]) : [...value, val]; - onChange?.(resultValue, opt ?? cache.getOptionsByValue(resultValue)); + onChange?.(resultValue, cache.getOptionsByValue(resultValue)); } // cascader - else if (isCascader(model)) { - onChange?.(val, opt); + else if (isCascader(mergedModel)) { + onChange?.(val, cache.getOptionsByValue(val)); } // normal else if (value !== val) { - onChange?.(val, opt ?? cache.getOptionsByValue(val)); + onChange?.(val, cache.getOptionsByValue(val)); } }; @@ -119,8 +124,8 @@ const List: React.ForwardRefRenderFunction & { prefix={prefix?.(option)} suffix={suffix?.(option)} disabled={option?.disabled ?? disabled} - isMultiple={isMultipe(model)} - isCascader={isCascader(model)} + isMultiple={isMultipe(mergedModel)} + isCascader={isCascader(mergedModel)} selectValue={value} selected={selectStatus(option?.value, value)} onClick={handleClick} @@ -143,19 +148,19 @@ const List: React.ForwardRefRenderFunction & { } = node; const item = { label: node?.props?.label, value: node?.props?.value } as OptionProps; - console.log('item', item); + return React.cloneElement(node, { ...rest, disabled: itemDisabled ?? disabled, prefix: itemPrefix ?? prefix?.(item), suffix: itemSuffix ?? suffix?.(item), - isMultiple: isMultipe(model), - isCascader: isCascader(model), + isMultiple: isMultipe(mergedModel), + isCascader: isCascader(mergedModel), selectValue: value, selected: selectStatus(item.value, value), - onClick: (val: string, opt: OptionProps) => { + onClick: (val: string) => { handleClick(val); - onClick?.(val, opt); + onClick?.(val); }, }); } diff --git a/src/list/Selection.tsx b/src/list/Selection.tsx index 8c8afbf740..45309467c1 100644 --- a/src/list/Selection.tsx +++ b/src/list/Selection.tsx @@ -12,12 +12,10 @@ const Selection: React.FC & { isSelection: boolean } = (props) = const prefixCls = `${usePrefixCls(PREFIX)}--selection`; const childrens = React.Children.toArray(children); const isSelection = options?.every((val) => 'groupId' in val) ?? false; - console.log('isSelection', isSelection); const selectionOptions: { groupId: string; groupName: string; options: OptionProps[] }[] | OptionProps[] = useMemo( () => getFlattenOptions(options, isSelection), [isSelection, options] ); - console.log('selectionOptions', selectionOptions); // var cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }], // result = cars.reduce(function (r, a) { // r[a.make] = r[a.make] || []; diff --git a/src/list/context.tsx b/src/list/context.tsx index d3e4b995de..e72ce82a18 100644 --- a/src/list/context.tsx +++ b/src/list/context.tsx @@ -5,12 +5,14 @@ import { OptionProps, ListProps } from './interfance'; interface ListContextProps { value?: string | string[]; + model?: 'simple' | 'cascader' | 'multiple'; onChange?: (value?: string | string[], options?: OptionProps | OptionProps[]) => void; options?: Map; setOptions?: (options?: OptionProps[]) => void; } -const defaultList = { +const defaultList: ListContextProps = { value: '', + model: undefined, onChange: noop, setOptions: noop, }; diff --git a/src/list/hooks/useCacheOptions.ts b/src/list/hooks/useCacheOptions.ts index c5d6b10022..f4f6e24976 100644 --- a/src/list/hooks/useCacheOptions.ts +++ b/src/list/hooks/useCacheOptions.ts @@ -34,7 +34,6 @@ const useCacheOptions = () => { : getOptionByValue(optValue); const getLabelByValue = (val?: string | string[], separator = '') => { - console.log('val', val, separator); if (val === '' || typeof val === 'undefined') { return ''; } diff --git a/src/list/inner/CascaderItem.tsx b/src/list/inner/CascaderItem.tsx index db440a5299..d3ed6ab3ca 100644 --- a/src/list/inner/CascaderItem.tsx +++ b/src/list/inner/CascaderItem.tsx @@ -41,10 +41,7 @@ const CascaderItem: React.ForwardRefRenderFunction< // list const prefixClsItem = `${prefixCls}--item`; - const onClick = () => - !disabled - ? propsOnClick?.(generateString(value, selectParent), selectParent?.[0] ?? { label: '', value: '' }) - : noop; + const onClick = () => (!disabled ? propsOnClick?.(generateString(value, selectParent)) : noop); const content = () => { if (!isEmpty(childrens)) { diff --git a/src/list/inner/ChckboxItem.tsx b/src/list/inner/ChckboxItem.tsx index f62234c656..62eb93ece6 100644 --- a/src/list/inner/ChckboxItem.tsx +++ b/src/list/inner/ChckboxItem.tsx @@ -22,7 +22,7 @@ const CheckboxItem: React.ForwardRefRenderFunction< disabled={disabled} onClick={(e) => { if (!disabled) { - onClick?.(value, { label: '', value: '' }); + onClick?.(value); e?.stopPropagation(); } }} diff --git a/src/list/inner/baseItem.tsx b/src/list/inner/baseItem.tsx index 2c2e2c3ebf..63ddcb94c6 100644 --- a/src/list/inner/baseItem.tsx +++ b/src/list/inner/baseItem.tsx @@ -68,7 +68,7 @@ const BaseItem: React.ForwardRefRenderFunction< aria-hidden="true" ref={ref} {...rest} - onClick={() => (!disabled ? onClick?.(value, { label: content as string, value }) : noop)} + onClick={() => (!disabled ? onClick?.(value) : noop)} > {contentRender?.(contentElement)} diff --git a/src/list/interfance.ts b/src/list/interfance.ts index e6c6f32d98..700ad37def 100644 --- a/src/list/interfance.ts +++ b/src/list/interfance.ts @@ -128,5 +128,5 @@ export interface BaseItemProps extends Pick { selected?: boolean; prefix?: string | React.ReactNode; suffix?: string | React.ReactNode; - onClick?: (value: string, option: OptionProps) => void; + onClick?: (value: string) => void; } diff --git a/src/list/style/index.less b/src/list/style/index.less index 09ea7f1f17..bb3d89833d 100644 --- a/src/list/style/index.less +++ b/src/list/style/index.less @@ -1,8 +1,8 @@ @import '../../stylesheet/variables/index.less'; @import '~@gio-design/utils/lib/less/mixins.less'; @list-prefix-cls: ~'@{component-prefix}-list-new'; -@cascader-prefix-cls: ~'gio-cascader--new'; -@cascader-content-prefix-cls: ~'gio-cascader--new--cascader--content'; +@cascader-prefix-cls: ~'@{component-prefix}-cascader--new'; +@cascader-content-prefix-cls: ~'@{cascader-prefix-cls}--cascader--content'; .@{cascader-content-prefix-cls} { &[data-popper-placement^='top'] { diff --git a/src/select/Select.tsx b/src/select/Select.tsx index b067ac65df..a202ebccbb 100644 --- a/src/select/Select.tsx +++ b/src/select/Select.tsx @@ -16,7 +16,7 @@ const Select: React.FC = (props) => { const { prefixCls = 'select--new', value: controlledValue, - defaultValue = '', + defaultValue = undefined, options = [], size, triggerProps = {}, @@ -42,18 +42,18 @@ const Select: React.FC = (props) => { const defaultPrefixCls = usePrefixCls(prefixCls); const [value, setValue] = useControlledState(controlledValue, defaultValue); const [visible, setVisible] = useControlledState(controlledVisible, false); - const [title, setTitle] = useState(undefined); + const [title, setTitle] = useState(undefined); const cache = useChacheOptions(); // options const nodesToOptions = useMemo(() => convertChildrenToData(children), [children]); const mergedOptions = useMemo(() => [...nodesToOptions, ...options], [nodesToOptions, options]); cache.setOptions(mergedOptions); - const activeOption = useMemo(() => cache.options.get(value), [cache.options, value]); + const activeOption = useMemo(() => cache.getOptionByValue(value), [cache, value]); // update trigger value useEffect(() => { - setTitle(activeOption?.label ?? ''); + setTitle(activeOption?.label); }, [activeOption?.label]); const handVisibleChange = (vis: boolean) => { @@ -63,7 +63,7 @@ const Select: React.FC = (props) => { const handleChange = (val?: string, opts?: OptionProps) => { onChange?.(val, opts); - setValue(val ?? ''); + setValue(val); setVisible(false); }; @@ -85,7 +85,7 @@ const Select: React.FC = (props) => { disabled={disabled} onClear={handleOnClear} onInputChange={(val) => { - isEmpty(val) && handleChange(''); + isEmpty(val) && handleChange(); }} {...triggerProps} />