Skip to content

Commit

Permalink
feat(select): independent select
Browse files Browse the repository at this point in the history
Remove list component dependency

fix growingio#677
  • Loading branch information
shiliqian committed Jan 22, 2021
1 parent 66fa6a1 commit 3ba66b5
Show file tree
Hide file tree
Showing 13 changed files with 1,154 additions and 324 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"rc-trigger": "^4.3.0",
"rc-upload": "^3.2.0",
"rc-util": "^5.0.5",
"rc-virtual-list": "^3.2.6",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-use": "^15.3.3",
Expand Down
220 changes: 176 additions & 44 deletions src/components/pagination/__test__/__snapshots__/Pagination.test.js.snap

Large diffs are not rendered by default.

26 changes: 0 additions & 26 deletions src/components/select/Empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,6 @@ import './style/empty.less';

function SvgComponent(): React.ReactElement {
return (
// <svg width={60} height={61} viewBox="0 0 60 61">
// <g fill="none" fillRule="evenodd">
// <path d="M.588 36.882l10-21.764h38.824l10 21.764h-23.53a5.882 5.882 0 11-11.764 0H.588z" fill="#F7F8FC" />
// <path
// d="M.588 36.882h23.53a5.882 5.882 0 1011.764 0h23.53v20.53a3 3 0 01-3 3H3.588a3 3 0 01-3-3v-20.53z"
// fill="#FFF"
// />
// <path
// d="M47.436 15.618a3.497 3.497 0 013.18 2.038h0l8.564 18.64c.211.458.32.957.32 1.461h0V57c0 .966-.392 1.841-1.025 2.475A3.489 3.489 0 0156 60.5h0H4a3.489 3.489 0 01-2.475-1.025A3.489 3.489 0 01.5 57h0V37.757a3.5 3.5 0 01.32-1.46h0l8.564-18.64a3.497 3.497 0 013.18-2.04h0z"
// stroke="#DCDFED"
// />
// <path
// d="M.588 36.882h23.53a5.882 5.882 0 1011.764 0h23.53"
// stroke="#DCDFED"
// strokeLinecap="round"
// strokeLinejoin="round"
// />
// <path
// stroke="#DCDFED"
// strokeWidth={1.5}
// strokeLinecap="round"
// strokeLinejoin="round"
// d="M30 1v9.412M40.588 2.261l-4.706 8.151M19.412 2.261l4.706 8.151"
// />
// </g>
// </svg>
<svg viewBox="0 0 60 60" width={48} height={48}>
<g id="图层_2" data-name="图层 2">
<g id="图层_1-2" data-name="图层 1">
Expand Down
139 changes: 139 additions & 0 deletions src/components/select/OptionsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useMemo } from 'react';
import { noop } from 'lodash';
import classnames from 'classnames';
import VirtualList from './VirtualList';
import { OptionsListProps, Option } from './interface';
import Tooltip from '../tooltip';
import Checkbox from '../checkbox';

const OptionsList: React.FC<OptionsListProps> = (props) => {
const {
labelRenderer,
prefixCls,
data,
groupStyle,
optionStyle,
selected,
multiple,
hasGroup,
height,
itemHeight,
onOptionClick,
optionLabelRenderer,
...restProps
} = props;

const flettensOptions = useMemo(() => {
const groupMap = new Map();
if (!hasGroup) return data;
data.map((cur) => {
const gvalue = groupMap.get(cur.groupValue);
if (gvalue) {
const { options, ...rest } = gvalue;
return groupMap.set(cur.groupValue, {
options: [...options, cur],
...rest,
});
}
return groupMap.set(cur.groupValue, {
label: cur.groupLabel,
value: cur.groupValue,
isSelectOptGroup: true,
options: [cur],
});
});
const flettenOption: Option[] = [];
groupMap.forEach((value) => {
flettenOption.push(value);
flettenOption.push(...value.options);
});
return flettenOption;
}, [data, hasGroup]);

const renderGroupItem = (option: Option) => {
const { value, label } = option;
return (
<div className={`${prefixCls}-list-group`} style={groupStyle} aria-hidden="true">
{label !== undefined ? label : value}
</div>
);
};

const renderTootip = (option: Option, render: JSX.Element & React.ReactNode) => {
const { tooltip } = option;
if (tooltip) {
return (
<Tooltip title={tooltip} placement="top">
{render}
</Tooltip>
);
}
return render;
};

const renderOption = (option: Option) => {
const { value, disabled, tooltip, groupValue, groupLabel, ...restOption } = option;
const isSelected =
typeof selected === 'string' || typeof selected === 'number' || typeof selected === 'undefined'
? selected === value
: selected.includes(value);
const onClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event.stopPropagation();
event.preventDefault();
if (onOptionClick) {
onOptionClick(value);
}
};
const labelNode = labelRenderer
? labelRenderer(
{
value,
disabled,
tooltip,
groupValue,
groupLabel,
label: optionLabelRenderer(value, option),
...restOption,
},
false
)
: optionLabelRenderer(value, option);
return (
<div className={`${prefixCls}-list-option-container`} style={optionStyle}>
<div
className={classnames(`${prefixCls}-list-option`, {
[`${prefixCls}-list-option-isSelected`]: isSelected,
[`${prefixCls}-list-option-disabled`]: disabled,
})}
onClick={disabled ? undefined : onClick}
onKeyDown={noop}
aria-hidden="true"
>
{multiple && (
<>
<Checkbox checked={isSelected} disabled={disabled} onChange={noop} />
<span style={{ width: 10 }} />
</>
)}
{labelNode}
</div>
</div>
);
};
return (
<VirtualList
itemKey="value"
prefixCls={prefixCls}
data={flettensOptions}
height={height}
itemHeight={itemHeight}
{...restProps}
>
{(option: Option & { isSelectOptGroup: boolean }) => {
return option.isSelectOptGroup ? renderGroupItem(option) : renderTootip(option, renderOption(option));
}}
</VirtualList>
);
};

export default OptionsList;
148 changes: 142 additions & 6 deletions src/components/select/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import React, { createRef, useState } from 'react';
import { Story, Meta } from '@storybook/react/types-6-0';

import Button from '../button';
import Select from './index';
import Radio from '../radio';
import { SelectProps } from './interface';
import './style';
import { SizeType } from '../config-provider/SizeContext';

export default {
title: 'Components/Functional/Select',
Expand All @@ -12,16 +14,150 @@ export default {

const labels = ['全部', '已上线', '待上线', '已下线', '草稿'];
const values = ['all', 'online', 'pending', 'off', 'draft'];
const optionsWithoutGroup = values.map((value, index) => ({

const defaultValues = [];
let i = 0;

while (i < 100) {
defaultValues.push(`${values[i % 5]}${i}`);
i += 1;
}

const options = values.map((value, index) => ({
value,
label: labels[index],
disabled: !!(index % 2),
}));

export const Default: Story<SelectProps> = (args) => <Select {...args} />;

const optionsWithoutGroup = defaultValues.map((value, index) => ({
groupLabel: labels[index % 5],
groupValue: values[index % 5],
value,
label: `${labels[index % 5]}-${index}`,
}));
const defaultGroup = (
<span>
<a href="https://github.com/growingio/gio-design">自定义Group</a>
</span>
);
const normal = options;
export const Default: Story<SelectProps> = () => {
const [size, setSize] = useState<SizeType>('middle');
const [disabled, setDisabled] = useState<boolean>(false);
const [bordered, setBordered] = useState<boolean>(true);
const [allowClear, setAllowClear] = useState<boolean>(false);
const [searchable, setSearchable] = useState<boolean>(false);
const selectRef = createRef<HTMLElement>();
return (
<>
<div style={{ display: 'flex' }}>
<div style={{ margin: 16 }}>
<h2>size</h2>
<Radio.Group value={size} onChange={(e) => setSize(e.target.value)}>
<Radio value="large"></Radio>
<Radio value="middle"></Radio>
<Radio value="small"></Radio>
</Radio.Group>
</div>
<div style={{ margin: 16 }}>
<h2>disabled</h2>
<Radio.Group value={disabled} onChange={(e) => setDisabled(e.target.value)}>
<Radio value={false}>不禁用</Radio>
<Radio value>禁用</Radio>
</Radio.Group>
</div>
<div style={{ margin: 16 }}>
<h2>bordered</h2>
<Radio.Group value={bordered} onChange={(e) => setBordered(e.target.value)}>
<Radio value={false}>无边框</Radio>
<Radio value>有边框</Radio>
</Radio.Group>
</div>
<div style={{ margin: 16 }}>
<h2>allowClear</h2>
<Radio.Group value={allowClear} onChange={(e) => setAllowClear(e.target.value)}>
<Radio value={false}>不开启</Radio>
<Radio value>开启</Radio>
</Radio.Group>
</div>
<div style={{ margin: 16 }}>
<h2>searchable</h2>
<Radio.Group value={searchable} onChange={(e) => setSearchable(e.target.value)}>
<Radio value={false}>不开启</Radio>
<Radio value>开启</Radio>
</Radio.Group>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'space-around', marginTop: 50 }}>
<div>
<h1>单选</h1>
<Select
ref={selectRef}
disabled={disabled}
bordered={bordered}
options={options}
allowClear={allowClear}
searchable={searchable}
size={size}
style={{ width: 190 }}
placeholder="请选择"
/>
</div>
<div>
<h1>多选</h1>
<Select
ref={selectRef}
disabled={disabled}
bordered={bordered}
multiple
searchable={searchable}
options={optionsWithoutGroup}
allowClear={allowClear}
size={size}
style={{ width: 190 }}
placeholder="请选择"
/>
</div>
<div>
<h1>自定义Group Option</h1>
<Select
ref={selectRef}
disabled={disabled}
bordered={bordered}
searchable={searchable}
allowClear={allowClear}
groupStyle={{ height: 50, padding: 10, background: 'antiquewhite' }}
optionStyle={{ height: 70, padding: 10, background: 'orange' }}
size={size}
style={{ width: 190 }}
placeholder="请选择"
>
<Select.Group label={defaultGroup} value="demo">
<Select.Option value="option">
<Button size="small" onClick={(e) => console.log('自定义Option', e.stopPropagation())}>
自定义Option
</Button>
</Select.Option>
</Select.Group>
</Select>
</div>
</div>
</>
);
};
export const Search: Story<SelectProps> = (args) => <Select {...args} />;
Default.args = {
size: 'small',
style: { width: 140 },
placeholder: '请选择',
options: optionsWithoutGroup,
};

Search.args = {
// size: 'small',
searchable: true,
multiple: true,
placeholder: '请选择',
allowClear: true,
// allowCustomOption:true,
options: normal,
};
Loading

0 comments on commit 3ba66b5

Please sign in to comment.