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 23, 2021
1 parent 49003cc commit b9e6acf
Show file tree
Hide file tree
Showing 13 changed files with 1,393 additions and 570 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.

65 changes: 31 additions & 34 deletions src/components/select/Empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,40 @@ 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}>
<svg>
<g id="图层_2" data-name="图层 2">
<g id="图层_1-2" data-name="图层 1">
<g className="cls-2">
<path id="路径-6" className="cls-3" d="M.59,35.88,9.38,16.74a4.5,4.5,0,0,1,4.09-2.62H46.53a4.5,4.5,0,0,1,4.09,2.62l8.79,19.14H35.88a5.88,5.88,0,1,1-11.76,0H.59Z" />
<path id="矩形" className="cls-4" d="M.59,35.88H24.12a5.88,5.88,0,1,0,11.76,0H59.41V56.41a3,3,0,0,1-3,3H3.59a3,3,0,0,1-3-3V35.88Z" />
<path className="cls-5" d="M56,60H4a4,4,0,0,1-4-4V36.76a3.87,3.87,0,0,1,.37-1.67L8.93,16.45a3.94,3.94,0,0,1,1.48-1.7,4,4,0,0,1,2.15-.63H47.44a4,4,0,0,1,2.15.63,3.94,3.94,0,0,1,1.48,1.7l8.56,18.64A3.87,3.87,0,0,1,60,36.76V56a4,4,0,0,1-4,4ZM12.56,15.12a3,3,0,0,0-1.62.47,3,3,0,0,0-1.1,1.28L1.27,35.5A3,3,0,0,0,1,36.76V56a3,3,0,0,0,3,3H56a3,3,0,0,0,3-3V36.76a3,3,0,0,0-.27-1.26L50.16,16.87a2.94,2.94,0,0,0-1.11-1.28,3,3,0,0,0-1.61-.47Z" />
<path className="cls-5" d="M30,42.26a6.38,6.38,0,0,1-6.36-5.88H.59a.5.5,0,0,1-.5-.5.5.5,0,0,1,.5-.5H24.12a.51.51,0,0,1,.5.5,5.38,5.38,0,1,0,10.76,0,.51.51,0,0,1,.5-.5H59.41a.5.5,0,0,1,.5.5.5.5,0,0,1-.5.5h-23A6.38,6.38,0,0,1,30,42.26Z" />
<path className="cls-5" d="M30,10.91a.75.75,0,0,1-.75-.75V.75a.75.75,0,0,1,1.5,0v9.41A.75.75,0,0,1,30,10.91Z" />
<path className="cls-5" d="M35.88,10.91a.71.71,0,0,1-.37-.1.75.75,0,0,1-.28-1l4.71-8.15a.74.74,0,0,1,1-.28.76.76,0,0,1,.28,1l-4.71,8.15A.75.75,0,0,1,35.88,10.91Z" />
<path className="cls-5" d="M24.12,10.91a.75.75,0,0,1-.65-.37L18.76,2.39a.76.76,0,0,1,.28-1,.75.75,0,0,1,1,.28l4.71,8.15a.75.75,0,0,1-.28,1A.71.71,0,0,1,24.12,10.91Z" />
<path
id="路径-6"
className="cls-3"
d="M.59,35.88,9.38,16.74a4.5,4.5,0,0,1,4.09-2.62H46.53a4.5,4.5,0,0,1,4.09,2.62l8.79,19.14H35.88a5.88,5.88,0,1,1-11.76,0H.59Z"
/>
<path
id="矩形"
className="cls-4"
d="M.59,35.88H24.12a5.88,5.88,0,1,0,11.76,0H59.41V56.41a3,3,0,0,1-3,3H3.59a3,3,0,0,1-3-3V35.88Z"
/>
<path
className="cls-5"
d="M56,60H4a4,4,0,0,1-4-4V36.76a3.87,3.87,0,0,1,.37-1.67L8.93,16.45a3.94,3.94,0,0,1,1.48-1.7,4,4,0,0,1,2.15-.63H47.44a4,4,0,0,1,2.15.63,3.94,3.94,0,0,1,1.48,1.7l8.56,18.64A3.87,3.87,0,0,1,60,36.76V56a4,4,0,0,1-4,4ZM12.56,15.12a3,3,0,0,0-1.62.47,3,3,0,0,0-1.1,1.28L1.27,35.5A3,3,0,0,0,1,36.76V56a3,3,0,0,0,3,3H56a3,3,0,0,0,3-3V36.76a3,3,0,0,0-.27-1.26L50.16,16.87a2.94,2.94,0,0,0-1.11-1.28,3,3,0,0,0-1.61-.47Z"
/>
<path
className="cls-5"
d="M30,42.26a6.38,6.38,0,0,1-6.36-5.88H.59a.5.5,0,0,1-.5-.5.5.5,0,0,1,.5-.5H24.12a.51.51,0,0,1,.5.5,5.38,5.38,0,1,0,10.76,0,.51.51,0,0,1,.5-.5H59.41a.5.5,0,0,1,.5.5.5.5,0,0,1-.5.5h-23A6.38,6.38,0,0,1,30,42.26Z"
/>
<path
className="cls-5"
d="M30,10.91a.75.75,0,0,1-.75-.75V.75a.75.75,0,0,1,1.5,0v9.41A.75.75,0,0,1,30,10.91Z"
/>
<path
className="cls-5"
d="M35.88,10.91a.71.71,0,0,1-.37-.1.75.75,0,0,1-.28-1l4.71-8.15a.74.74,0,0,1,1-.28.76.76,0,0,1,.28,1l-4.71,8.15A.75.75,0,0,1,35.88,10.91Z"
/>
<path
className="cls-5"
d="M24.12,10.91a.75.75,0,0,1-.65-.37L18.76,2.39a.76.76,0,0,1,.28-1,.75.75,0,0,1,1,.28l4.71,8.15a.75.75,0,0,1-.28,1A.71.71,0,0,1,24.12,10.91Z"
/>
</g>
</g>
</g>
Expand Down
138 changes: 138 additions & 0 deletions src/components/select/OptionsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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,
...restProps
} = props;

const flettensOptions = useMemo(() => {
const groupMap = new Map();
if (!hasGroup) return data;
data?.map((cur: Option) => {
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, label, title, ...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,
...restOption,
},
false
)
: title || label;
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;
152 changes: 147 additions & 5 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,156 @@ 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="option1" title="自定义111">
<Button size="small" onClick={(e) => console.log('自定义Option', e.stopPropagation())}>
自定义Option
</Button>
</Select.Option>

<Select.Option value="option2" title="">
<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,
style: { width: 160 },
options: normal,
};
Loading

0 comments on commit b9e6acf

Please sign in to comment.