Skip to content

Commit

Permalink
feat(Cascader): 添加表单支持 & 增加清除按钮 (#713)
Browse files Browse the repository at this point in the history
  • Loading branch information
nullptr-z committed Mar 25, 2022
1 parent 910904a commit d6757ee
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 129 deletions.
180 changes: 144 additions & 36 deletions packages/react-cascader/README.md
Expand Up @@ -23,49 +23,157 @@ import { Cascader } from 'uiw';
const Demo = () => {

const options = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
{
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(
return (
<div style={{ width: 200 }}>
<Cascader placeholder="请选择" option={options} onChange={(value,seleteds)=>console.log(value,seleteds)}/>
<Cascader allowClear={true} placeholder="请选择" value={[1, 4, 7]} option={options} onChange={(value, seleteds) => console.log(value, seleteds)} />
</div>
)
};
ReactDOM.render(<Demo />, _mount_);
```

### 在表单中使用

[`<Form />`](#/components/form) 表单中应用 `<Cascader />` 组件。

<!--rehype:bgWhite=true&codeSandbox=true&codePen=true&noScroll=true-->
```jsx
import ReactDOM from 'react-dom';
import { Form, Row, Col, Cascader, Button } 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 (
<div>
<Form
onSubmitError={(error) => {
if (error.filed) {
return { ...error.filed };
}
return null;
}}
onSubmit={({initial, current}) => {
const errorObj = {};
if (!current.selectField) {
errorObj.selectField = '默认需要选择内容,选择入内容';
}
if(Object.keys(errorObj).length > 0) {
const err = new Error();
err.filed = errorObj;
Notify.error({ title: '提交失败!', description: '请确认提交表单是否正确!' });
throw err;
}
Notify.success({
title: '提交成功!',
description: `表单提交成功,选择值为:${current.selectField},将自动填充初始化值!`,
});
}}
fields={{
cascader: {
initialValue:[1, 4, 7],
children: (
<Cascader
allowClear={true}
placeholder="请选择"
option={options}
onChange={(value, seleteds) => console.log(value, seleteds)}
/>
)
},
}}
>
{({ fields, state, canSubmit }) => {
return (
<div>
<Row >
<Col fixed style={{width:200}}>{fields.cascader}</Col>
</Row>
<Row>
<Col fixed>
<Button disabled={!canSubmit()} type="primary" htmlType="submit">提交</Button>
</Col>
</Row>
<Row>
<Col>
<pre style={{ padding: 10, marginTop: 10 }}>
{JSON.stringify(state.current, null, 2)}
</pre>
</Col>
</Row>
</div>
)
}}
</Form>
</div>
);
}
ReactDOM.render(<Demo />, _mount_);
```

## Props

| 参数 | 说明 | 类型 | 默认值 | 版本 |
Expand All @@ -74,4 +182,4 @@ ReactDOM.render(<Demo />, _mount_);
| placeholder | 选择框默认文字 | String | - | - |
| option | 选项菜单 | { value: String \| Number, label: React.ReactNode, children: Array<String \| Number>} | - | - |
| value | 指定当前选中的条目,多选时为一个数组 | String[] \| Number[] | - | - |
| onChange | 选中选项调用此函数 | function(value, selectedOptions) | - | - |
| onChange | 选中选项调用此函数 | function( isSeleted, value, selectedOptions) | - | - |
91 changes: 72 additions & 19 deletions packages/react-cascader/src/index.tsx
Expand Up @@ -4,45 +4,85 @@ import { IProps } from '@uiw/utils';
import Dropdown, { DropdownProps } from '@uiw/react-dropdown';
import Menu from '@uiw/react-menu';
import Icon from '@uiw/react-icon';
import './style/index.less';

type ValueType = Array<string | number>;
type optionType = { value: string | number; label: React.ReactNode; children?: Array<optionType> };
type OptionType = { value: string | number; label: React.ReactNode; children?: Array<OptionType> };

export interface CascaderProps extends IProps, DropdownProps {
option?: Array<optionType>;
option?: Array<OptionType>;
value?: ValueType;
onChange?: (value: ValueType, selectedOptions: Array<optionType>) => void;
onChange?: (isSeleted: boolean, value: ValueType, selectedOptions: Array<OptionType>) => void;
allowClear?: boolean;
placeholder?: string;
isOpen?: boolean;
}

function Cascader(props: CascaderProps) {
const { placeholder, prefixCls = 'w-search-select', className, style = { width: 200 }, option = [], others } = props;
const {
value,
onChange,

allowClear,
placeholder,
prefixCls = 'w-cascader',
className,
style = { width: 200 },
option = [],
others,
} = props;

const cls = [prefixCls, className].filter(Boolean).join(' ').trim();
const [innerIsOpen, setInnerIsOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState<Array<optionType>>([]);
const [selectedValue, setSelectedValue] = useState<Array<OptionType>>([]);
const [selectIconType, setSelectIconType] = useState('');

useEffect(() => {
const valueTemp: Array<OptionType> = [];
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) {
setInnerIsOpen(isOpen);
}

const handleItemClick = (optionsItem: optionType, level: number) => {
function renderSelectIcon(type: string) {
let selectIconType;
if (type === 'enter' && allowClear && selectedValue.length > 0) {
selectIconType = 'close';
} else {
selectIconType = '';
}
setSelectIconType(selectIconType);
}

const handleItemClick = (optionsItem: OptionType, level: number) => {
selectedValue.splice(level, selectedValue.length - level, optionsItem);
setSelectedValue([...selectedValue]);

handelChange();
handelChange(true, selectedValue);
};

const handelChange = () => {
const handelChange = (isSeleted: boolean, selectedValue: Array<OptionType>) => {
setSelectedValue([...selectedValue]);
const value = selectedValue.map((item) => item.value);
props.onChange?.(value, selectedValue);
onChange?.(isSeleted, value, selectedValue);
};

const onClear = (e: React.MouseEvent<any, MouseEvent>) => {
e.stopPropagation();
console.log(123);
handelChange(false, []);
};

const widths = (style?.width as number) * 0.6 || undefined;
const widths = (style?.width as number) * 0.5 || undefined;

const OptionIter = (option: Array<optionType>, level: number = 0) => {
const OptionIter = (option: Array<OptionType>, level: number = 0) => {
if (!option) return;

return (
Expand All @@ -59,15 +99,15 @@ function Cascader(props: CascaderProps) {
{!option || option.length === 0 ? (
<div style={{ color: '#c7c7c7', fontSize: 12 }}>{'没有数据'}</div>
) : (
option.map((item, index) => {
const active = selectedValue[level]?.value === item.value;
option.map((opt, index) => {
const active = selectedValue[level]?.value === opt.value;
return (
<Menu.Item
active={active}
key={index}
text={item.label}
addonAfter={item.children ? <Icon type="right" /> : undefined}
onClick={() => handleItemClick(item, level)}
text={opt.label}
addonAfter={opt.children ? <Icon type="right" /> : undefined}
onClick={() => handleItemClick(opt, level)}
/>
);
})
Expand All @@ -77,7 +117,7 @@ function Cascader(props: CascaderProps) {
};

const inputValue = useMemo(() => {
return selectedValue.map((item) => item.label).join(' / ');
return selectedValue.map((opt) => opt.label).join(' / ');
}, [selectedValue.length]);

return (
Expand All @@ -101,7 +141,20 @@ function Cascader(props: CascaderProps) {
</div>
}
>
<Input value={inputValue} onChange={() => {}} placeholder={placeholder} style={{ width: style?.width }} />
<span onMouseLeave={() => renderSelectIcon('leave')} onMouseOver={() => renderSelectIcon('enter')}>
<Input
value={inputValue}
onChange={() => {}}
placeholder={placeholder}
style={{ width: style?.width }}
readOnly
addonAfter={
selectIconType === 'close' && (
<Icon type={`${selectIconType}`} onClick={onClear} className={`${prefixCls}-close`} />
)
}
/>
</span>
</Dropdown>
);
}
Expand Down

0 comments on commit d6757ee

Please sign in to comment.