Skip to content

Commit

Permalink
feat: list component
Browse files Browse the repository at this point in the history
  • Loading branch information
chen_gh committed Sep 3, 2020
1 parent 3e0f03e commit c1008d9
Show file tree
Hide file tree
Showing 25 changed files with 1,656 additions and 1 deletion.
649 changes: 649 additions & 0 deletions packages/components/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@types/lodash": "^4.14.136",
"@types/react": "^16.8.23",
"@types/react-dom": "^16.8.2",
"@types/react-virtualized": "^9.21.10",
"@types/shallowequal": "^1.1.1",
"classnames": "^2.2.6",
"lodash": "^4.17.14",
Expand All @@ -40,6 +41,7 @@
"rc-upload": "^3.2.0",
"rc-util": "^5.0.5",
"react-use": "^15.1.1",
"react-virtualized": "^9.22.2",
"shallowequal": "^1.1.0"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react';
import SelectList from '../SelectList';
import { SelectCoreProps } from '../../interface';

interface State {
options: any[];
value?: string | string[];
keyword: string;
}

class SelectCore extends React.Component<SelectCoreProps, State> {
public static defaultProps: Partial<SelectCoreProps> = {
showSearch: true,
searchableFields: ['name'],
valueKey: 'id',
isMultiple: false,
isLoading: false,
required: false,
height: 400,
rowHeight: 40,
emptyPlaceholder: '没有找到相关结果',
};

public state: State = {
options: [],
value: undefined,
keyword: '',
};

public componentDidMount() {
this.setState({
value: this.props.value,
options: this.props.options,
});
}

public componentWillReceiveProps(nextProps: SelectCoreProps) {
this.setState({
value: nextProps.value,
options: this.props.options,
});
}

public render() {
const {
disabledOptions,
valueKey,
renderKey,
isMultiple,
allowDuplicate,
required,
max,
width,
height,
getGroupIcon,
onSelect,
onDeselect,
emptyPlaceholder,
labelRenderer,
rowHeight,
} = this.props;

if (this.state.options && this.state.options.length) {
return (
<div className="gio-select-core">
<SelectList
options={this.state.options}
disabledOptions={disabledOptions}
value={this.state.value}
valueKey={valueKey}
renderKey={renderKey}
isMultiple={isMultiple}
allowDuplicate={allowDuplicate}
required={required}
max={max}
width={width}
height={height}
onSelect={onSelect}
onDeselect={onDeselect}
onChange={this.handleSelect}
getGroupIcon={getGroupIcon}
labelRenderer={labelRenderer}
rowHeight={rowHeight}
/>
</div>
);
}

return (
<div className="gio-select-core">
<div style={{ padding: '50% 10px 0', textAlign: 'center', height }}>{emptyPlaceholder}</div>
</div>
);
}

private handleSelect = (value: any) => {
this.setState({ value });
this.props.onChange(value);
};
}

export default SelectCore;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.gio-select-core {
position: relative;

.ant-tabs-ink-bar {
background-color: #fc5f3a;
}

.ant-tabs-nav {
.ant-tabs-tab {
margin-right: 0;
padding: 5px 15px;
font-size: 12px;
}
.ant-tabs-tab-active {
color: #5c4e61;
}
}

.ant-tabs-bar {
margin-bottom: 0;
}

&-header {
padding: 10px;
}

.gio-user-picker-fetch-btn {
opacity: 0.7;
transition: 0.3s opacity ease;

&:hover {
opacity: 1;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React from 'react';
import SelectOption from '../SelectOption';
import Group from '../SelectOption/Group';
import { get } from 'lodash';
import withGroupedOptions from '../../utils/withGroupedOptions';
import { List } from 'react-virtualized';
import { SelectListProps } from '../../interface';

import './style.less';

interface State {
value: any | any[];
}

class SelectList extends React.Component<SelectListProps, {}> {
constructor(props: SelectListProps) {
super(props);
this.ref = React.createRef();
}

public ref: React.RefObject<HTMLDivElement>;

public static defaultProps: Partial<SelectListProps> = {
disabledOptions: [],
isMultiple: false,
width: 280,
height: 450,
};

public state: State = {
value: null,
};

public render() {
return (
<div className="gio-select-list-wrapper" ref={this.ref}>
{this.renderList()}
</div>
);
}

private getPopupContainer = () => {
return this.ref.current as HTMLElement;
};

private renderList = () => {
const { width, height, disabledOptions, rowHeight } = this.props;
const getRowHeight = ({ index }: { index: number }) => {
if (typeof rowHeight === 'function') {
return rowHeight(this.props.options[index]);
} else {
return rowHeight;
}
};
return (
<List
value={this.props.value}
width={width}
height={height}
rowCount={this.props.options.length}
rowHeight={typeof rowHeight === 'function' ? getRowHeight : rowHeight}
rowRenderer={this.renderListItem(this.props.options)}
disabledOptions={disabledOptions}
className={'gio-select-list'}
/>
);
};

private checkIsMax = (option: any) => {
const { isMultiple, value, max } = this.props;
if (Array.isArray(value) && !this.getSelected(option) && isMultiple && max) {
return value.length >= max;
} else {
return false;
}
};

private renderListItem = (options: any) => ({ index, style }: { index: number; style: React.CSSProperties }) => {
const { isMultiple, required, value, valueKey, renderKey, disabledOptions, labelRenderer } = this.props;
const option = options[index];
const isGroup = get(option, 'type') === 'groupName';
const label = labelRenderer ? labelRenderer(option) : get(option, 'name') || option;

const optionKey = renderKey || valueKey;
const key = optionKey ? option[optionKey] : option;

const isSelectedAndRequired = this.getSelected(option) && required && (isMultiple ? value?.length === 1 : true);

const isMax = this.checkIsMax(option);

const disabled =
isSelectedAndRequired ||
isMax ||
disabledOptions.indexOf(valueKey ? option[valueKey] : option) > -1 ||
option.disabled;

const groupIcon = this.props.getGroupIcon ? this.props.getGroupIcon(option.group) : null;

return isGroup ? (
<Group
key={option.name}
name={option.name}
option={option}
style={style}
icon={groupIcon}
isSelected={this.getSelected(option)}
isMultiple={!!this.props.isMultiple}
labelRenderer={labelRenderer}
/>
) : (
<SelectOption
key={key}
style={style}
option={option}
title={!labelRenderer ? label : undefined}
isSelected={this.getSelected(option)}
isMultiple={!!this.props.isMultiple}
allowDuplicate={this.props.allowDuplicate}
onSelect={this.handleSelect}
disabled={disabled}
hasGroupIcon={!!groupIcon}
getPopupContainer={this.getPopupContainer}
>
{label}
</SelectOption>
);
};

private handleSelect = (option: any) => {
const { isMultiple, allowDuplicate, onSelect, onDeselect, onChange } = this.props;

const isMax = this.checkIsMax(option);

if (isMax) {
return;
}

const selectedValue = this.getValue(option);
const isSelected = this.getSelected(option);
let value;
if (isSelected && !allowDuplicate) {
if (onDeselect) {
onDeselect(selectedValue, this.props.value, option);
}
value = isMultiple ? this.props.value.filter((v: any) => v !== selectedValue) : null;
} else {
if (onSelect) {
onSelect(selectedValue, this.props.value, option);
}
value = isMultiple ? [...(this.props.value || []), selectedValue] : selectedValue;
}
if (onChange) {
onChange(value);
}
};

private getValue = (option: any) => {
const { valueKey } = this.props;
return valueKey ? option[valueKey] : option;
};

private getSelected = (option: any) => {
const { value, getSelected, isMultiple, valueKey } = this.props;
if (getSelected) {
return getSelected(option, value);
}
const target = valueKey ? option[valueKey] : option;

return isMultiple ? value && value.indexOf(target) > -1 : value === target;
};
}

const WithGroupList = withGroupedOptions(SelectList);

export default WithGroupList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.gio-select-list-wrapper {
background-color: #fff;

.gio-select-list {
&:focus {
outline: none;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import Checkbox from 'antd/lib/checkbox';
import noop from 'lodash/noop';
import { GroupProps } from '../../interface';

export default class Group extends React.PureComponent<GroupProps> {
public static defaultProps = {
onSelect: noop,
style: {},
};

public render() {
const {
name,
icon,
style,
isMultiple,
showGroupCheckBox = false,
isSelected,
indeterminate,
option,
labelRenderer,
} = this.props;

return (
<div className="gio-select-option group" style={{ ...style, color: '#1248E9' }} onClick={this.handleSelect}>
{icon}
{isMultiple && showGroupCheckBox && <Checkbox checked={isSelected} indeterminate={indeterminate} />}
{labelRenderer ? labelRenderer(option, true) : name}
</div>
);
}

private handleSelect = () => {
this.props.onSelect && this.props.onSelect(this.props.option);
};
}
Loading

0 comments on commit c1008d9

Please sign in to comment.