Skip to content

Commit

Permalink
fix(list-picker): selection consumer render children (#1519)
Browse files Browse the repository at this point in the history
Co-authored-by: shiliqian <shiliqian@growingio.com>
  • Loading branch information
berber1016 and shiliqian committed Nov 24, 2021
1 parent d06bd39 commit 0485f54
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 55 deletions.
81 changes: 73 additions & 8 deletions src/list-picker/demos/List-picker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Story, Meta } from '@storybook/react/types-6-0';
import '../style';
import { uniqueId } from 'lodash';
import CheckboxItem from '../../list/inner/ChckboxItem';
import { OptionProps } from '../../list/interfance';
// import { uniqueId } from 'lodash';
// import { uniqBy, uniqueId } from 'lodash';
Expand Down Expand Up @@ -38,6 +39,10 @@ const Template: Story<ListPickerProps> = () => {
const [multipleValue, setMultipleValue] = useState<undefined | string[]>(undefined);
const [activeTab, setActiveTab] = useState('tab1');
const [search, setSearch] = useState('');
const multipleOptions = [
{ label: '子一', value: 'ziyi' },
{ label: '子二', value: 'zier' },
];
const onChange = (val?: string, opt?: OptionProps | OptionProps[]) => {
setValue(val);
console.log('onChange执行', val, opt, search);
Expand All @@ -47,7 +52,7 @@ const Template: Story<ListPickerProps> = () => {
<h3>单选</h3>
<div className="demo-box">
<div style={{ padding: '10px' }}>
<Button size="small" onClick={() => setValue(undefined)}>
<Button size="small" onClick={() => setValue('')}>
清空
</Button>
</div>
Expand All @@ -56,7 +61,7 @@ const Template: Story<ListPickerProps> = () => {
onChange={onChange}
overlayStyle={{ width: '240px' }}
onClear={() => {
// setValue(undefined);
setValue('');
}}
triggerProps={{ allowClear: true }}
placeholder="请选择"
Expand Down Expand Up @@ -175,6 +180,7 @@ const Template: Story<ListPickerProps> = () => {
placeholder="请搜索名称"
onSearch={(val: string) => setSearch(val)}
/>

<List.Selection
options={[
{ label: '子一', value: 'ziyi' },
Expand All @@ -183,6 +189,7 @@ const Template: Story<ListPickerProps> = () => {
/>
</ListPicker>
</div>
<h3>不同全选的方式</h3>
<div className="demo-box">
<div style={{ padding: '10px' }}>
<Button size="small" onClick={() => setMultipleValue(['ziyi', 'zier'])}>
Expand Down Expand Up @@ -211,17 +218,75 @@ const Template: Story<ListPickerProps> = () => {
placeholder="请搜索名称"
onSearch={(val: string) => setSearch(val)}
/>
<List.Selection
options={[
{ label: '子一', value: 'ziyi' },
{ label: '子二', value: 'zier' },
]}

<List.Selection>
{(context) => (
<>
<CheckboxItem
selected={context.value?.length === 2}
onClick={() => {
if (context.value?.length === 2) {
context.onChange([]);
} else {
context.onChange(['ziyi', 'zier']);
}
}}
label="全部"
value="all"
/>
<List options={multipleOptions} id="id" title="有item" />
</>
)}
</List.Selection>
</ListPicker>
</div>
<h3>selection下list 无 item</h3>
<div className="demo-box">
<ListPicker overlayStyle={{ width: '240px' }} placeholder="请选择" onChange={(v) => console.log('v', v)}>
<SearchBar
size="small"
style={{ width: '100%' }}
placeholder="请搜索名称"
onSearch={(val: string) => setSearch(val)}
/>
<List.Selection>
<List options={multipleOptions} id="id" title="有item" />
<List options={[]} id="id2" title="无item" />
<List id="id3" title="有JSX item">
<List.Item value="id3-1">JSX-1</List.Item>
<List.Item value="id3-2">JSX-2</List.Item>
</List>
</List.Selection>
</ListPicker>
</div>

<h3>超长的ListPicker</h3>
<div className="demo-box">
<ListPicker
value={multipleValue}
onChange={(val) => {
console.log('multiple onChange 并不会触发', val);
}}
overlayStyle={{ width: '240px' }}
model="multiple"
onClear={() => {
setMultipleValue(undefined);
}}
onConfim={(val) => setMultipleValue(val as any)}
placeholder="请选择"
>
<SearchBar
size="small"
style={{ width: '100%' }}
placeholder="请搜索名称"
onSearch={(val: string) => setSearch(val)}
/>
<List.Selection options={largeOptions} />
</ListPicker>
</div>
<h3>disabled</h3>
<div className="demo-box">
<ListPicker
disabled
value={multipleValue}
onChange={(val) => {
console.log('multiple onChange 并不会触发', val);
Expand Down
9 changes: 4 additions & 5 deletions src/list-picker/interfance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { OptionProps } from '../list/interfance';
import { Placement, TriggerAction } from '../popover/interface';
import { ListProps } from '../list';

export interface ListPickerProps extends Pick<ListProps, 'model' | 'disabled' | 'className' | 'style'> {
export interface ListPickerProps extends Pick<ListProps, 'model' | 'className' | 'style'> {
size?: 'small' | 'normal';
/**
* 触发方式
*/
disabled?: boolean;
trigger?: TriggerAction | TriggerAction[];
value?: string | string[];
defaultValue?: string | string[];
Expand All @@ -23,10 +24,7 @@ export interface ListPickerProps extends Pick<ListProps, 'model' | 'disabled' |
/**
* 触发器样式
*/
triggerProps?: Pick<
InputButtonProps,
'disabled' | 'className' | 'style' | 'allowClear' | 'maxWidth' | 'value' | 'prefix' | 'suffix'
>;
triggerProps?: Pick<InputButtonProps, 'className' | 'style' | 'allowClear' | 'maxWidth' | 'prefix' | 'suffix'>;
/**
* custom trigger render
*/
Expand All @@ -48,6 +46,7 @@ export interface ListPickerProps extends Pick<ListProps, 'model' | 'disabled' |
// }

export interface TriggerProps extends Omit<InputButtonProps, 'value' | 'active'> {
disabled?: boolean;
value?: string | React.ReactNode;
separator?: string;
}
18 changes: 11 additions & 7 deletions src/list-picker/listPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { OptionProps } from '../list/interfance';
import Button from '../button';
import { ListContext } from '../list/context';
import useCacheOptions from '../list/hooks/useCacheOptions';
// import CheckboxItem from '../list/inner/ChckboxItem';

// const defaultEmpty = () => <Page type="noData" size="small" style={{ margin: '0 auto', padding: '40px 0px' }} />;

const ListPicker: React.FC<ListPickerProps> = (props) => {
const {
size,
placeholder,
disabled,
onClear,
value: controlledValue,
defaultValue,
Expand Down Expand Up @@ -53,7 +55,9 @@ const ListPicker: React.FC<ListPickerProps> = (props) => {
}, [controlledValue, getLabelByValue, separator, options]);

useEffect(() => {
setValue(controlledValue);
if (!needConfim) {
setValue(controlledValue);
}
}, [controlledValue, needConfim, setValue]);
useEffect(() => {
if (needConfim && !visible && !isEqual(controlledValue, value)) {
Expand All @@ -72,12 +76,11 @@ const ListPicker: React.FC<ListPickerProps> = (props) => {
};

const handleChange = (val?: string | string[], opts?: OptionProps | OptionProps[]) => {
setValue(val);
if (!needConfim) {
onChange?.(val, opts);
}
if (model !== 'multiple') {
onChange?.(val, opts);
handVisibleChange(false);
} else {
setValue(val);
}
};
const clearInput = () => {
Expand All @@ -95,18 +98,18 @@ const ListPicker: React.FC<ListPickerProps> = (props) => {
onClick={() => setVisible(!visible)}
value={title}
{...triggerProp}
// disabled={disabled}
disabled={disabled}
size={size}
placeholder={placeholder}
onClear={clearInput}
separator={separator}
/>
);
};

// render
const renderOverlay = () => (
<div className={classNames(defaultPrefix, className)} style={style}>
{/* {model === 'multiple' && selectAll && renderSelectAll()} */}
{children}
{model === 'multiple' && needConfim && (
<Button style={{ width: '100%' }} onClick={() => handleConfim()}>
Expand All @@ -120,6 +123,7 @@ const ListPicker: React.FC<ListPickerProps> = (props) => {
<ListContext.Provider value={{ value, model, onChange: handleChange, options, setOptions }}>
<Popover
distoryOnHide={false}
disabled={disabled}
content={renderOverlay()}
trigger={trigger}
visible={visible}
Expand Down
4 changes: 2 additions & 2 deletions src/list/List.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import classNames from 'classnames';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { difference, indexOf, isArray, isEmpty, isNil } from 'lodash';
import { difference, indexOf, isArray, isEmpty, isNil, toArray } from 'lodash';
import { OptionProps, ItemProps, ListProps } from './interfance';
import usePrefixCls from '../utils/hooks/use-prefix-cls';
import { PREFIX } from './constants';
Expand Down Expand Up @@ -74,7 +74,7 @@ const List: React.ForwardRefRenderFunction<HTMLDivElement, ListProps> & {
setOptions(mergedOptions);
}, [mergedOptions, setOptions]);

const renderOptions = initOptions?.length ? initOptions : React.Children.toArray(children);
const renderOptions = initOptions?.length ? initOptions : toArray(children);
const childrens = renderOptions.slice(0, collapse);
const isNeedCollapse = useMemo(() => renderOptions?.length > collapse, [renderOptions, collapse]);
const handleClick = (val: string) => {
Expand Down
87 changes: 58 additions & 29 deletions src/list/Selection.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,86 @@
import classNames from 'classnames';
import React, { useMemo } from 'react';
import { ListProps, SelectionProps } from './interfance';
import { isEmpty, isFunction } from 'lodash';
import toArray from 'rc-util/lib/Children/toArray';
import { ListProps, SelectionProps, OptionProps } from './interfance';
import usePrefixCls from '../utils/hooks/use-prefix-cls';
import { PREFIX } from './constants';
import { getFlattenOptions } from '../list-picker/util';
import List from './List';
import { OptionProps } from '.';
import { ListContext } from './context';

const Selection: React.FC<SelectionProps> & { isSelection: boolean } = (props) => {
const Selection: React.FC<SelectionProps> = (props) => {
const { className, style, options = [], children, ...rest } = props;
const prefixCls = `${usePrefixCls(PREFIX)}--selection`;
const childrens = React.Children.toArray(children);
const isSelection = options?.every((val) => 'groupId' in val) ?? false;
const selectionOptions: { groupId: string; groupName: string; options: OptionProps[] }[] | OptionProps[] = useMemo(
() => getFlattenOptions(options, isSelection),
[isSelection, options]
);
// 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] || [];
// r[a.make].push(a);
// return r;
// }, Object.create(null));

// console.log(result);
if (options.length) {
return (
<div className={classNames(prefixCls, className)} style={style}>
{isSelection &&
(selectionOptions as { groupId: string; groupName: string; options: OptionProps[] }[])?.map((option) => (
<div className={`${prefixCls}--item`}>
{option?.groupId && <div className={`${prefixCls}--title`}>{option?.groupName}</div>}
<List options={option.options} />
</div>
))}
{!isSelection && <List options={selectionOptions as OptionProps[]} />}
(selectionOptions as { groupId: string; groupName: string; options: OptionProps[] }[])?.map(
(option) =>
!isEmpty(option.options) && (
<div className={`${prefixCls}--item`}>
{option?.groupId && <div className={`${prefixCls}--title`}>{option?.groupName}</div>}
<List options={option.options} />
</div>
)
)}
{!isSelection && !isEmpty(selectionOptions) && <List options={selectionOptions as OptionProps[]} />}
</div>
);
}
if (isFunction(children)) {
return (
<div className={classNames(prefixCls, className)} style={style}>
<ListContext.Consumer>
{(context) =>
toArray(children?.(context))?.map((node: React.ReactElement<ListProps>) =>
!isEmpty(node?.props.options) ||
React.isValidElement(node.props.children) ||
!isEmpty(toArray(node.props.children)) ? (
<div className={`${prefixCls}--item`}>
{node?.props?.title && <div className={`${prefixCls}--title`}>{node?.props?.title}</div>}
{React.cloneElement(node, {
...rest,
...node.props,
})}
</div>
) : (
React.cloneElement(node, {
...rest,
...node.props,
})
)
)
}
</ListContext.Consumer>
</div>
);
}

return (
<div className={classNames(prefixCls, className)} style={style}>
{childrens?.map((node: React.ReactElement<ListProps>) => (
<div className={`${prefixCls}--item`}>
{node?.props?.title && <div className={`${prefixCls}--title`}>{node?.props?.title}</div>}
{React.cloneElement(node, {
...rest,
...node.props,
})}
</div>
))}
{toArray(children)?.map(
(node: React.ReactElement<ListProps>) =>
(!isEmpty(node?.props.options) ||
React.isValidElement(node.props.children) ||
!isEmpty(toArray(node.props.children))) && (
<div className={`${prefixCls}--item`}>
{node?.props?.title && <div className={`${prefixCls}--title`}>{node?.props?.title}</div>}
{React.cloneElement(node, {
...rest,
...node.props,
})}
</div>
)
)}
</div>
);
};
Selection.isSelection = true;

export default Selection;
2 changes: 1 addition & 1 deletion src/list/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import { List } from '.';
import { OptionProps, ListProps } from './interfance';

interface ListContextProps {
export interface ListContextProps {
value?: string | string[];
model?: 'single' | 'cascader' | 'multiple';
onChange?: (value?: string | string[], options?: OptionProps | OptionProps[]) => void;
Expand Down

1 comment on commit 0485f54

@vercel
Copy link

@vercel vercel bot commented on 0485f54 Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.