mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: 移动组件优化:级联选择器、Select选择器、PopUp滑动穿透修复、移动端网络请求修复 (#3281)
* fix: 移动端网络请求修复 * fix: popup滑动穿透修复 * feat: 级联选择器移动端优化 * Update NestedSelect.tsx * fix: 修复select自定义内容导致复选框错位问题 * fix: PopUp增加底部安全区 * feat: select选择器移动端优化 * Update Cascader.tsx * Update NestedSelect.tsx * fix: Select选择器输入框样式优化 * fix: line-height 位置调整 * feat: ResultBox 移动端样式优化 * fix: prettier 格式化 * Update _popup.scss * Update PopUp.tsx * Update _popup.scss * Update TransferDropDown.tsx fix: 移动端样式调整 * Update PopUp.tsx * Update Cascader.tsx * Update NestedSelect.tsx 增加移动端通过接口获取数据 * Update NestedSelect.tsx Co-authored-by: zhangxulong <zhangxulong@baidu.com>
This commit is contained in:
parent
8bce8a0579
commit
a4803196ff
@ -47,7 +47,12 @@ class AMISComponent extends React.Component {
|
||||
headers // 请求头
|
||||
}) => {
|
||||
config = {
|
||||
url,
|
||||
dataType: 'json',
|
||||
method,
|
||||
data,
|
||||
headers,
|
||||
responseType,
|
||||
...config
|
||||
};
|
||||
|
||||
@ -61,12 +66,7 @@ class AMISComponent extends React.Component {
|
||||
config.validateStatus = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
const response = await axios[config.method](
|
||||
config.url,
|
||||
config.data,
|
||||
config
|
||||
);
|
||||
const response = await axios(config);
|
||||
|
||||
if (response.status >= 400) {
|
||||
if (response.data) {
|
||||
|
98
scss/components/_cascader.scss
Normal file
98
scss/components/_cascader.scss
Normal file
@ -0,0 +1,98 @@
|
||||
.#{$ns}Cascader-tabs {
|
||||
display: flex;
|
||||
&.scrollable {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.#{$ns}Cascader-tab {
|
||||
flex: 1;
|
||||
width: calc((100vw - 20px) / 3);
|
||||
height: px2rem(370px);
|
||||
overflow-y: auto;
|
||||
display: inline-block;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.#{$ns}Cascader {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
&-Nav {
|
||||
overflow-x: auto;
|
||||
&Item {
|
||||
display: inline-block;
|
||||
margin-right: px2rem(10px);
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding: 0 px2rem(6px);
|
||||
}
|
||||
}
|
||||
&-btnGroup {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: px2rem(60px);
|
||||
}
|
||||
&-options {
|
||||
box-sizing: border-box;
|
||||
height: var(--Cascader-option-height);
|
||||
padding-top: px2rem(6px);
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
&-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: px2rem(6px) 0;
|
||||
font-size: var(--fontSizeMd);
|
||||
line-height: var(--Cascader-option-lineHeight);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
&.selected {
|
||||
span {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
span {
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
&--text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
}
|
||||
&-selectedNum {
|
||||
min-width: px2rem(16px);
|
||||
height: px2rem(16px);
|
||||
line-height: px2rem(16px);
|
||||
border-radius: 100%;
|
||||
text-align: center;
|
||||
background: var(--Form-select-menu-onActive-color);
|
||||
color: var(--white) !important;
|
||||
font-size: var(--fontSizeSm);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
&-icon {
|
||||
color: var(--primary);
|
||||
}
|
||||
&-tab {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
}
|
||||
.#{$ns}PopUp {
|
||||
width: 100%;
|
||||
height: px2rem(400px);
|
||||
position: fixed;
|
||||
background: var(--PopOver-bg);
|
||||
left: 0;
|
||||
@ -55,38 +56,36 @@
|
||||
|
||||
&.in {
|
||||
animation-name: PopUpIn;
|
||||
.#{$ns}PopUp-overlay{
|
||||
.#{$ns}PopUp-overlay {
|
||||
animation-name: PopUpOpacityIn;
|
||||
}
|
||||
}
|
||||
|
||||
&.out {
|
||||
animation-name: PopUpOut;
|
||||
.#{$ns}PopUp-overlay{
|
||||
.#{$ns}PopUp-overlay {
|
||||
animation-name: PopUpOpacityOut;
|
||||
}
|
||||
}
|
||||
|
||||
&-inner{
|
||||
&-inner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: $white;
|
||||
padding-top: px2rem(36px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-closeWrap{
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
&-closeWrap {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
height: px2rem(48px);
|
||||
line-height: px2rem(48px);
|
||||
}
|
||||
|
||||
&-closeWrap &-close{
|
||||
&-closeWrap &-close {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
color: var(--icon-color);
|
||||
@ -95,9 +94,29 @@
|
||||
right: px2rem(15px);
|
||||
}
|
||||
|
||||
&-content{
|
||||
&-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: px2rem(60px);
|
||||
}
|
||||
&-title {
|
||||
font-size: var(--fontSizeMd);
|
||||
}
|
||||
|
||||
&-cancel {
|
||||
margin-left: var(--gap-sm);
|
||||
}
|
||||
|
||||
&-confirm {
|
||||
margin-right: var(--gap-sm);
|
||||
}
|
||||
|
||||
&-content {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
& > * {
|
||||
@ -112,7 +131,7 @@
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
opacity: 1;
|
||||
animation-duration: var(--animation-duration);
|
||||
animation-fill-mode: both;
|
||||
@ -124,4 +143,7 @@
|
||||
&--leftTopLeftBottom {
|
||||
margin-top: px2rem(-4px);
|
||||
}
|
||||
&-safearea {
|
||||
height: px2rem(16px);
|
||||
}
|
||||
}
|
||||
|
@ -126,4 +126,35 @@
|
||||
padding-left: 8px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
&.is-mobile {
|
||||
min-height: calc(var(--Form-input-lineHeight) * var(--fontSizeLg));
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: var(--fontSizeLg);
|
||||
border: none;
|
||||
justify-content: flex-end;
|
||||
|
||||
.#{$ns}ResultBox-arrow {
|
||||
margin-right: var(--gap-xs);
|
||||
// margin-left: var(--gap-xs);
|
||||
width: var(--gap-md);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
margin-left: 4px;
|
||||
|
||||
> svg {
|
||||
transition: transform var(--animation-duration);
|
||||
display: inline-block;
|
||||
color: var(--Form-select-caret-iconColor);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
top: 0;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,8 @@
|
||||
pointer-events: all;
|
||||
margin-left: var(--Checkbox-gap);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
> a {
|
||||
// float: right;
|
||||
|
@ -87,4 +87,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&-popup {
|
||||
height: px2rem(460px);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
position: relative;
|
||||
font-size: var(--Form-input-fontSize);
|
||||
@ -148,10 +149,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.is-opened &-arrow > svg {
|
||||
&.is-opened:not(.is-mobile) &-arrow > svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&.is-mobile {
|
||||
min-height: calc(var(--Form-input-lineHeight) * var(--fontSizeLg));
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: var(--fontSizeLg);
|
||||
|
||||
.#{$ns}Select-valueWrap {
|
||||
text-align: right;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.#{$ns}Select-arrow {
|
||||
> svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-menu {
|
||||
max-height: px2rem(300px);
|
||||
overflow: auto;
|
||||
@ -159,6 +177,13 @@
|
||||
.#{$ns}Checkbox--sm > i {
|
||||
margin-top: px2rem(-3px);
|
||||
}
|
||||
&.is-mobile {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
.#{$ns}Select-option {
|
||||
line-height: px2rem(36px);
|
||||
}
|
||||
}
|
||||
}
|
||||
&--longlist {
|
||||
overflow: hidden;
|
||||
@ -282,8 +307,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.is-focused,
|
||||
&.is-opened {
|
||||
&.is-focused:not(.is-mobile),
|
||||
&.is-opened:not(.is-mobile) {
|
||||
border-color: var(--Form-input-onFocused-borderColor);
|
||||
color: var(--Form-select-onFocused-color);
|
||||
}
|
||||
@ -314,6 +339,10 @@
|
||||
fill: var(--Form-input-onHover-iconColor);
|
||||
}
|
||||
}
|
||||
|
||||
&-popup {
|
||||
height: px2rem(320px);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Select-popover {
|
||||
|
@ -235,6 +235,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.is-mobile {
|
||||
width: 100%;
|
||||
}
|
||||
& > .#{$ns}Transfer-selection {
|
||||
flex-grow: 1;
|
||||
max-height: 100%;
|
||||
|
@ -112,6 +112,7 @@
|
||||
@import '../components/form/rating';
|
||||
@import '../components/form/transfer';
|
||||
@import '../components/form/nested-select';
|
||||
@import '../components/cascader';
|
||||
@import '../components/form/icon-picker';
|
||||
@import '../components/form/form';
|
||||
@import '../components/anchor-nav';
|
||||
|
565
src/components/Cascader.tsx
Normal file
565
src/components/Cascader.tsx
Normal file
@ -0,0 +1,565 @@
|
||||
/**
|
||||
* @file Cascader
|
||||
* @author fex
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {autobind, getTreeAncestors} from '../utils/helper';
|
||||
import {themeable} from '../theme';
|
||||
import {NestedSelectProps} from '../renderers/Form/NestedSelect';
|
||||
import {Option, Options} from './Select';
|
||||
import intersectionBy from 'lodash/intersectionBy';
|
||||
import compact from 'lodash/compact';
|
||||
import find from 'lodash/find';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import Button from './Button';
|
||||
import {flattenTree, findTree, getTreeDepth} from '../utils/helper';
|
||||
|
||||
export type CascaderOption = {
|
||||
text?: string;
|
||||
value?: string | number;
|
||||
color?: string;
|
||||
disabled?: boolean;
|
||||
children?: Options;
|
||||
className?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
export interface CascaderProps extends NestedSelectProps {
|
||||
value?: (number | string)[];
|
||||
activeColor?: string;
|
||||
optionRender?: ({
|
||||
option,
|
||||
selected
|
||||
}: {
|
||||
option: CascaderOption;
|
||||
selected: boolean;
|
||||
}) => React.ReactNode;
|
||||
onClose?: () => void;
|
||||
onConfirm?: (param: any) => void;
|
||||
multiple?: boolean;
|
||||
}
|
||||
export type CascaderTab = {
|
||||
options: Options;
|
||||
};
|
||||
|
||||
export interface CascaderState {
|
||||
selectedOptions: Options;
|
||||
activeTab: number;
|
||||
tabs: Array<{
|
||||
options: Options;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class Cascader extends React.Component<CascaderProps, CascaderState> {
|
||||
static defaultProps = {
|
||||
labelField: 'label',
|
||||
valueField: 'value'
|
||||
};
|
||||
tabsRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
tabRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
constructor(props: CascaderProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOptions: this.props.selectedOptions || [],
|
||||
activeTab: 0,
|
||||
tabs: [
|
||||
{
|
||||
options: this.props.options.slice() || []
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
const {multiple, options, valueField = 'value', cascade} = this.props;
|
||||
let selectedOptions = this.props.selectedOptions.slice();
|
||||
let parentsCount = 0;
|
||||
let parentTree: Options = [];
|
||||
selectedOptions.forEach((item: Option) => {
|
||||
const parents = getTreeAncestors(options, item as any);
|
||||
// 获取最长路径
|
||||
if (parents && parents?.length > parentsCount) {
|
||||
parentTree = parents;
|
||||
parentsCount = parentTree.length;
|
||||
}
|
||||
});
|
||||
const selectedValues = selectedOptions.map(
|
||||
(option: Option) => option[valueField]
|
||||
);
|
||||
const tabs = parentTree.map((option: Option) => {
|
||||
if (multiple && !cascade) {
|
||||
if (
|
||||
selectedValues.includes(option[valueField]) &&
|
||||
option?.children?.length
|
||||
) {
|
||||
option.children.forEach((option: Option) => (option.disabled = true));
|
||||
}
|
||||
}
|
||||
return multiple
|
||||
? {
|
||||
options: [
|
||||
{
|
||||
...option,
|
||||
isCheckAll: true
|
||||
},
|
||||
...(option.children ? option.children : [])
|
||||
]
|
||||
}
|
||||
: {
|
||||
options: option.children ? option.children : []
|
||||
};
|
||||
});
|
||||
this.setState({
|
||||
selectedOptions,
|
||||
tabs: [...this.state.tabs, ...tabs]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@autobind
|
||||
handleTabSelect(index: number) {
|
||||
const tabs = this.state.tabs.slice(0, index + 1);
|
||||
this.setState({
|
||||
activeTab: index,
|
||||
tabs
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
getOptionParent(option: Option) {
|
||||
const {options, valueField = 'value'} = this.props;
|
||||
let ancestors: any[] = [];
|
||||
findTree(options, (item, index, level, paths) => {
|
||||
if (item[valueField] === option[valueField]) {
|
||||
ancestors = paths;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return ancestors.length ? ancestors[ancestors.length - 1] : null;
|
||||
}
|
||||
|
||||
@autobind
|
||||
dealParentSelect(option: Option, selectedOptions: Options): Options {
|
||||
const {valueField = 'value'} = this.props;
|
||||
const parentOption = this.getOptionParent(option);
|
||||
if (parentOption) {
|
||||
const parentChildren = parentOption?.children;
|
||||
const equalOption = intersectionBy(
|
||||
selectedOptions,
|
||||
parentChildren,
|
||||
valueField
|
||||
);
|
||||
// 包含则选中父节点
|
||||
const isParentSelected = find(selectedOptions, {
|
||||
[valueField]: parentOption[valueField]
|
||||
});
|
||||
if (equalOption.length === parentChildren?.length && !isParentSelected) {
|
||||
selectedOptions.push(parentOption);
|
||||
}
|
||||
if (equalOption.length !== parentChildren?.length && isParentSelected) {
|
||||
const index = selectedOptions.findIndex(
|
||||
(item: Option) => item[valueField] === parentOption[valueField]
|
||||
);
|
||||
selectedOptions.splice(index, 1);
|
||||
}
|
||||
return this.dealParentSelect(parentOption, selectedOptions);
|
||||
} else {
|
||||
return selectedOptions;
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
flattenTreeWithLeafNodes(option: Option) {
|
||||
return compact(
|
||||
flattenTree(Array.isArray(option) ? option : [option], node => node)
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
adjustOptionSelect(option: Option): boolean {
|
||||
const {valueField = 'value'} = this.props;
|
||||
const {selectedOptions} = this.state;
|
||||
function loop(arr: any[]): boolean {
|
||||
if (!arr.length) {
|
||||
return false;
|
||||
}
|
||||
return arr.some((item: any) => item[valueField] === option[valueField]);
|
||||
}
|
||||
return loop(selectedOptions);
|
||||
}
|
||||
|
||||
@autobind
|
||||
getSelectedChildNum(option: Option): number {
|
||||
let count = 0;
|
||||
const loop = (arr: any[]) => {
|
||||
if (!arr || !arr.length) {
|
||||
return;
|
||||
}
|
||||
for (let item of arr) {
|
||||
if (item.children) {
|
||||
loop(item.children || []);
|
||||
} else {
|
||||
if (this.adjustOptionSelect(item)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
loop(option.children || []);
|
||||
return count;
|
||||
}
|
||||
|
||||
@autobind
|
||||
dealOptionDisable(selectedOptions: Options) {
|
||||
const {
|
||||
valueField = 'value',
|
||||
options,
|
||||
cascade,
|
||||
multiple,
|
||||
onlyChildren // 子节点可点击
|
||||
} = this.props;
|
||||
if (!multiple || cascade || onlyChildren) {
|
||||
return;
|
||||
}
|
||||
const selectedValues = selectedOptions.map(
|
||||
(option: Option) => option[valueField]
|
||||
);
|
||||
const loop = (option: Option) => {
|
||||
if (!option.children) {
|
||||
return;
|
||||
}
|
||||
option.children &&
|
||||
option.children.forEach((childOption: Option) => {
|
||||
if (
|
||||
!selectedValues.includes(option[valueField]) &&
|
||||
!option.disabled
|
||||
) {
|
||||
childOption.disabled = false;
|
||||
}
|
||||
|
||||
if (selectedValues.includes(option[valueField]) || option.disabled) {
|
||||
childOption.disabled = true;
|
||||
}
|
||||
loop(childOption);
|
||||
});
|
||||
};
|
||||
options.forEach((option: Option) => loop(option));
|
||||
}
|
||||
|
||||
@autobind
|
||||
dealChildrenSelect(option: Option, selectedOptions: Options) {
|
||||
const {valueField = 'value'} = this.props;
|
||||
let index = selectedOptions.findIndex(
|
||||
(item: Option) => item[valueField] === option[valueField]
|
||||
);
|
||||
if (index !== -1) {
|
||||
selectedOptions.splice(index, 1);
|
||||
} else {
|
||||
selectedOptions.push(option);
|
||||
}
|
||||
function loop(option: Option) {
|
||||
if (!option.children) {
|
||||
return;
|
||||
}
|
||||
option.children.forEach((item: Option) => {
|
||||
if (index !== -1) {
|
||||
// 删除选中节点及其子节点
|
||||
selectedOptions = selectedOptions.filter(
|
||||
(sItem: Option) => sItem[valueField] !== item[valueField]
|
||||
);
|
||||
} else {
|
||||
// 添加节点及其子节点
|
||||
selectedOptions.push(item);
|
||||
}
|
||||
loop(item);
|
||||
});
|
||||
}
|
||||
loop(option);
|
||||
return selectedOptions;
|
||||
}
|
||||
|
||||
getParentTree = (option: Option, arr: Options): Options => {
|
||||
const parentOption = this.getOptionParent(option);
|
||||
if (parentOption) {
|
||||
arr.push(parentOption);
|
||||
return this.getParentTree(parentOption, arr);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
@autobind
|
||||
onSelect(option: CascaderOption, tabIndex: number) {
|
||||
const {multiple, valueField = 'value', cascade} = this.props;
|
||||
|
||||
let tabs = this.state.tabs.slice();
|
||||
let {activeTab} = this.state;
|
||||
let selectedOptions = this.state.selectedOptions;
|
||||
const isDisable = option.disabled;
|
||||
if (!isDisable) {
|
||||
if (multiple) {
|
||||
// 父子级分离
|
||||
if (cascade) {
|
||||
if (
|
||||
option.isCheckAll ||
|
||||
!option.children ||
|
||||
!option.children.length
|
||||
) {
|
||||
let index = selectedOptions.findIndex(
|
||||
(item: Option) => item[valueField] === option[valueField]
|
||||
);
|
||||
if (index !== -1) {
|
||||
selectedOptions.splice(index, 1);
|
||||
} else {
|
||||
selectedOptions.push(option);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
option.isCheckAll ||
|
||||
!option.children ||
|
||||
!option.children.length
|
||||
) {
|
||||
selectedOptions = this.dealChildrenSelect(option, selectedOptions);
|
||||
selectedOptions = this.dealParentSelect(option, selectedOptions);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 单选
|
||||
selectedOptions = this.getParentTree(option, [option]);
|
||||
}
|
||||
}
|
||||
this.dealOptionDisable(selectedOptions);
|
||||
|
||||
if (tabs.length > tabIndex + 1) {
|
||||
tabs = tabs.slice(0, tabIndex + 1);
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const tabWidth = this.tabRef.current?.offsetWidth || 1;
|
||||
const parentTree = this.getParentTree(option, [option]);
|
||||
const scrollLeft = (parentTree.length - 2) * tabWidth;
|
||||
if (scrollLeft !== 0) {
|
||||
(this.tabsRef.current as HTMLElement).scrollTo(scrollLeft, 0);
|
||||
}
|
||||
});
|
||||
|
||||
if (option?.children && !option.isCheckAll) {
|
||||
const nextTab = multiple
|
||||
? {
|
||||
options: [
|
||||
{
|
||||
...option,
|
||||
isCheckAll: true
|
||||
},
|
||||
...option.children
|
||||
]
|
||||
}
|
||||
: {
|
||||
options: option.children
|
||||
};
|
||||
|
||||
if (tabs[tabIndex + 1]) {
|
||||
tabs[tabIndex + 1] = nextTab;
|
||||
} else {
|
||||
tabs.push(nextTab);
|
||||
}
|
||||
activeTab += 1;
|
||||
}
|
||||
this.setState({
|
||||
tabs,
|
||||
activeTab,
|
||||
selectedOptions
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
onNextClick(option: CascaderOption, tabIndex: number) {
|
||||
let {activeTab} = this.state;
|
||||
let tabs = this.state.tabs.slice();
|
||||
if (option.c)
|
||||
if (option?.children) {
|
||||
const nextTab = {
|
||||
options: option.children
|
||||
};
|
||||
if (tabs[tabIndex + 1]) {
|
||||
tabs[tabIndex + 1] = nextTab;
|
||||
} else {
|
||||
tabs.push(nextTab);
|
||||
}
|
||||
activeTab += 1;
|
||||
}
|
||||
this.setState({
|
||||
tabs,
|
||||
activeTab
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
getSubmitOptions(selectedOptions: Options): Options {
|
||||
const _selectedOptions: Options = [];
|
||||
const {
|
||||
multiple,
|
||||
options,
|
||||
valueField = 'value',
|
||||
cascade,
|
||||
onlyChildren,
|
||||
withChildren
|
||||
} = this.props;
|
||||
if (cascade || onlyChildren || withChildren || !multiple) {
|
||||
return selectedOptions;
|
||||
}
|
||||
const selectedValues = selectedOptions.map(
|
||||
(option: Option) => option[valueField]
|
||||
);
|
||||
function loop(options: Options) {
|
||||
if (!options || !options.length) {
|
||||
return;
|
||||
}
|
||||
options.forEach((option: Option) => {
|
||||
if (selectedValues.includes(option[valueField])) {
|
||||
_selectedOptions.push(option);
|
||||
} else {
|
||||
loop(option.children ? option.children : []);
|
||||
}
|
||||
});
|
||||
}
|
||||
loop(options);
|
||||
return _selectedOptions;
|
||||
}
|
||||
|
||||
@autobind
|
||||
confirm() {
|
||||
const {onChange, joinValues, delimiter, extractValue, valueField, onClose} =
|
||||
this.props;
|
||||
let {selectedOptions} = this.state;
|
||||
let _selectedOptions = this.getSubmitOptions(selectedOptions);
|
||||
_selectedOptions = uniqBy(_selectedOptions, valueField);
|
||||
onChange(
|
||||
joinValues
|
||||
? _selectedOptions
|
||||
.map(item => item[valueField as string])
|
||||
.join(delimiter)
|
||||
: extractValue
|
||||
? _selectedOptions.map(item => item[valueField as string])
|
||||
: _selectedOptions
|
||||
);
|
||||
onClose && onClose();
|
||||
}
|
||||
|
||||
@autobind
|
||||
renderOption(option: CascaderOption, tabIndex: number) {
|
||||
const {
|
||||
activeColor,
|
||||
optionRender,
|
||||
labelField,
|
||||
valueField = 'value',
|
||||
classnames: cx,
|
||||
cascade,
|
||||
multiple
|
||||
} = this.props;
|
||||
const {selectedOptions} = this.state;
|
||||
const selectedValueArr = selectedOptions.map(item => item[valueField]);
|
||||
|
||||
let selfChecked = selectedValueArr.includes(option[valueField]);
|
||||
const color = option.color || (selfChecked ? activeColor : undefined);
|
||||
const Text = optionRender ? (
|
||||
optionRender({option, selected: selfChecked})
|
||||
) : (
|
||||
<span>{option[labelField]}</span>
|
||||
);
|
||||
return (
|
||||
<li
|
||||
className={cx(
|
||||
'Cascader-option',
|
||||
{
|
||||
selected: selfChecked,
|
||||
disabled: option.disabled
|
||||
},
|
||||
option.className
|
||||
)}
|
||||
style={{color}}
|
||||
onClick={() => this.onSelect(option, tabIndex)}
|
||||
key={tabIndex + '-' + option[valueField]}
|
||||
>
|
||||
<span className={cx('Cascader-option--text')}>{Text}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
renderOptions(options: Options, tabIndex: number) {
|
||||
const {classnames: cx} = this.props;
|
||||
return (
|
||||
<ul key={tabIndex} className={cx('Cascader-options')}>
|
||||
{options.map(option => this.renderOption(option, tabIndex))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
renderTabs() {
|
||||
const {classnames: cx, options} = this.props;
|
||||
const {tabs} = this.state;
|
||||
const depth = getTreeDepth(options);
|
||||
return (
|
||||
<div
|
||||
className={cx(`Cascader-tabs`, depth > 3 ? 'scrollable' : '')}
|
||||
ref={this.tabsRef}
|
||||
>
|
||||
{tabs.map((tab: CascaderTab, tabIndex: number) => {
|
||||
const {options} = tab;
|
||||
return (
|
||||
<div
|
||||
className={cx(`Cascader-tab`)}
|
||||
ref={this.tabRef}
|
||||
key={tabIndex}
|
||||
>
|
||||
{this.renderOptions(options, tabIndex)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{depth <= 3 && options.length
|
||||
? Array(getTreeDepth(options) - tabs.length)
|
||||
.fill(1)
|
||||
.map((item: number, index: number) => (
|
||||
<div className={cx(`Cascader-tab`)} key={index}></div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
className,
|
||||
onClose,
|
||||
translate: __
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={cx(`Cascader`, className)}>
|
||||
<div className={cx(`Cascader-btnGroup`)}>
|
||||
<Button
|
||||
className={cx(`Cascader-btnCancel`)}
|
||||
level="default"
|
||||
onClick={onClose}
|
||||
>
|
||||
{__('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className={cx(`Cascader-btnConfirm`)}
|
||||
level="primary"
|
||||
onClick={this.confirm}
|
||||
>
|
||||
{__('confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
{this.renderTabs()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default themeable(Cascader);
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import {autobind} from '../utils/helper';
|
||||
import {autobind, isMobile} from '../utils/helper';
|
||||
import Overlay from './Overlay';
|
||||
import PopOver from './PopOver';
|
||||
import PopUp from './PopUp';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
|
||||
export interface PopOverContainerProps {
|
||||
@ -13,6 +14,7 @@ export interface PopOverContainerProps {
|
||||
popOverRender: (props: {onClose: () => void}) => JSX.Element;
|
||||
popOverContainer?: any;
|
||||
popOverClassName?: string;
|
||||
useMobileUI?: boolean;
|
||||
}
|
||||
|
||||
export interface PopOverContainerState {
|
||||
@ -60,11 +62,13 @@ export class PopOverContainer extends React.Component<
|
||||
|
||||
render() {
|
||||
const {
|
||||
useMobileUI,
|
||||
children,
|
||||
popOverContainer,
|
||||
popOverClassName,
|
||||
popOverRender: dropdownRender
|
||||
} = this.props;
|
||||
const mobileUI = useMobileUI && isMobile();
|
||||
return (
|
||||
<>
|
||||
{children({
|
||||
@ -72,26 +76,35 @@ export class PopOverContainer extends React.Component<
|
||||
onClick: this.handleClick,
|
||||
ref: this.targetRef
|
||||
})}
|
||||
|
||||
<Overlay
|
||||
container={popOverContainer || this.getParent}
|
||||
target={this.getTarget}
|
||||
placement={'auto'}
|
||||
show={this.state.isOpened}
|
||||
>
|
||||
<PopOver
|
||||
overlay
|
||||
{mobileUI ? (
|
||||
<PopUp
|
||||
isShow={this.state.isOpened}
|
||||
className={popOverClassName}
|
||||
style={{
|
||||
minWidth: this.target
|
||||
? Math.max(this.target.offsetWidth, 100)
|
||||
: 'auto'
|
||||
}}
|
||||
onHide={this.close}
|
||||
>
|
||||
{dropdownRender({onClose: this.close})}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
</PopUp>
|
||||
) : (
|
||||
<Overlay
|
||||
container={popOverContainer || this.getParent}
|
||||
target={this.getTarget}
|
||||
placement={'auto'}
|
||||
show={this.state.isOpened}
|
||||
>
|
||||
<PopOver
|
||||
overlay
|
||||
className={popOverClassName}
|
||||
style={{
|
||||
minWidth: this.target
|
||||
? Math.max(this.target.offsetWidth, 100)
|
||||
: 'auto'
|
||||
}}
|
||||
onHide={this.close}
|
||||
>
|
||||
{dropdownRender({onClose: this.close})}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -5,7 +5,8 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {ClassNamesFn, themeable} from '../theme';
|
||||
import {themeable, ThemeProps} from '../theme';
|
||||
import {localeable, LocaleProps} from '../locale';
|
||||
import Transition, {
|
||||
ENTERED,
|
||||
EXITING,
|
||||
@ -14,20 +15,21 @@ import Transition, {
|
||||
} from 'react-transition-group/Transition';
|
||||
import Portal from 'react-overlays/Portal';
|
||||
import {Icon} from './icons';
|
||||
import Button from './Button';
|
||||
|
||||
export interface PopUpPorps {
|
||||
export interface PopUpPorps extends ThemeProps, LocaleProps {
|
||||
title?: string;
|
||||
className?: string;
|
||||
style?: {
|
||||
[styleName: string]: string;
|
||||
};
|
||||
overlay?: boolean;
|
||||
onHide?: () => void;
|
||||
classPrefix: string;
|
||||
classnames: ClassNamesFn;
|
||||
[propName: string]: any;
|
||||
isShow?: boolean;
|
||||
container?: any;
|
||||
hideClose?: boolean;
|
||||
showConfirm?: boolean;
|
||||
onConfirm?: (value: any) => void;
|
||||
showClose?: boolean;
|
||||
placement?: 'left' | 'center' | 'right';
|
||||
header?: JSX.Element;
|
||||
}
|
||||
@ -41,15 +43,33 @@ const fadeStyles: {
|
||||
[ENTERING]: 'in'
|
||||
};
|
||||
export class PopUp extends React.PureComponent<PopUpPorps> {
|
||||
scrollTop: number = 0;
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
overlay: true,
|
||||
isShow: false,
|
||||
container: document.body,
|
||||
hideClose: false
|
||||
showClose: true,
|
||||
onConfirm: () => {}
|
||||
};
|
||||
|
||||
componentDidMount() {}
|
||||
componentDidUpdate() {
|
||||
if (this.props.isShow) {
|
||||
this.scrollTop =
|
||||
document.body.scrollTop || document.documentElement.scrollTop;
|
||||
document.body.style.overflow =
|
||||
'hidden';
|
||||
} else {
|
||||
document.body.style.overflow =
|
||||
'auto';
|
||||
document.body.scrollTop =
|
||||
this.scrollTop;
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
document.body.style.overflow = 'auto';
|
||||
document.body.scrollTop =
|
||||
this.scrollTop;
|
||||
}
|
||||
handleClick(e: React.MouseEvent) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
@ -57,15 +77,19 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
|
||||
render() {
|
||||
const {
|
||||
style,
|
||||
title,
|
||||
children,
|
||||
overlay,
|
||||
onHide,
|
||||
onConfirm,
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
className,
|
||||
isShow,
|
||||
container,
|
||||
hideClose,
|
||||
showConfirm,
|
||||
translate: __,
|
||||
showClose,
|
||||
header,
|
||||
placement = 'center',
|
||||
...rest
|
||||
@ -90,7 +114,7 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
|
||||
<div className={`${ns}PopUp-overlay`} onClick={onHide} />
|
||||
)}
|
||||
<div className={cx(`${ns}PopUp-inner`)}>
|
||||
{!hideClose && (
|
||||
{!showConfirm && showClose ? (
|
||||
<div className={cx(`${ns}PopUp-closeWrap`)}>
|
||||
{header}
|
||||
<Icon
|
||||
@ -99,12 +123,34 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
|
||||
onClick={onHide}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{showConfirm && (
|
||||
<div className={cx(`${ns}PopUp-toolbar`)}>
|
||||
<Button
|
||||
className={cx(`${ns}PopUp-cancel`)}
|
||||
level="default"
|
||||
onClick={onHide}
|
||||
>
|
||||
{__('cancel')}
|
||||
</Button>
|
||||
{title && (
|
||||
<span className={cx(`${ns}PopUp-title`)}>{title}</span>
|
||||
)}
|
||||
<Button
|
||||
className={cx(`${ns}PopUp-confirm`)}
|
||||
level="primary"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{__('confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cx(`${ns}PopUp-content`, `justify-${placement}`)}
|
||||
>
|
||||
{children}
|
||||
{isShow ? children : null}
|
||||
</div>
|
||||
<div className={cx(`PopUp-safearea`)}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -115,4 +161,4 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
|
||||
}
|
||||
}
|
||||
|
||||
export default themeable(PopUp);
|
||||
export default themeable(localeable(PopUp));
|
||||
|
@ -4,7 +4,7 @@ import {InputBoxProps} from './InputBox';
|
||||
import {uncontrollable} from 'uncontrollable';
|
||||
import {Icon} from './icons';
|
||||
import Input from './Input';
|
||||
import {autobind, ucFirst} from '../utils/helper';
|
||||
import {autobind, isMobile, ucFirst} from '../utils/helper';
|
||||
import {LocaleProps, localeable} from '../locale';
|
||||
import isPlainObject = require('lodash/isPlainObject');
|
||||
|
||||
@ -19,6 +19,7 @@ export interface ResultBoxProps
|
||||
onResultChange?: (value: Array<any>) => void;
|
||||
allowInput?: boolean;
|
||||
inputPlaceholder: string;
|
||||
useMobileUI?: boolean;
|
||||
}
|
||||
|
||||
export class ResultBox extends React.Component<ResultBoxProps> {
|
||||
@ -115,9 +116,11 @@ export class ResultBox extends React.Component<ResultBoxProps> {
|
||||
onFocus,
|
||||
onBlur,
|
||||
borderMode,
|
||||
useMobileUI,
|
||||
...rest
|
||||
} = this.props;
|
||||
const isFocused = this.state.isFocused;
|
||||
const mobileUI = useMobileUI && isMobile();
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -126,6 +129,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
|
||||
'is-disabled': disabled,
|
||||
'is-error': hasError,
|
||||
'is-clickable': onResultClick,
|
||||
'is-mobile': mobileUI,
|
||||
[`ResultBox--border${ucFirst(borderMode)}`]: borderMode
|
||||
})}
|
||||
onClick={onResultClick}
|
||||
@ -183,6 +187,11 @@ export class ResultBox extends React.Component<ResultBoxProps> {
|
||||
<Icon icon="close" className="icon" />
|
||||
</a>
|
||||
) : null}
|
||||
{!allowInput && mobileUI ? (
|
||||
<span className={cx('ResultBox-arrow')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import Spinner from './Spinner';
|
||||
import {Option, Options} from '../Schema';
|
||||
import {RemoteOptionsProps, withRemoteConfig} from './WithRemoteConfig';
|
||||
import Picker from './Picker';
|
||||
import PopUp from './PopUp';
|
||||
|
||||
export {Option, Options};
|
||||
|
||||
@ -923,23 +924,13 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
labelField: 'label',
|
||||
options: filtedOptions
|
||||
};
|
||||
const menu = mobileUI ? (
|
||||
<Picker
|
||||
className={cx('PickerColumns-column', mobileClassName)}
|
||||
labelField="label"
|
||||
value={value[0]}
|
||||
translate={this.props.translate}
|
||||
locale={this.props.locale}
|
||||
columns={[column]}
|
||||
onChange={checkAll ? noop : this.handlePickerChange}
|
||||
onClose={this.close}
|
||||
onConfirm={this.confirm}
|
||||
/>
|
||||
) : (
|
||||
const menu = (
|
||||
<div
|
||||
ref={this.menu}
|
||||
className={cx('Select-menu', {
|
||||
'Select--longlist': filtedOptions.length && filtedOptions.length > 100
|
||||
'Select--longlist':
|
||||
filtedOptions.length && filtedOptions.length > 100,
|
||||
'is-mobile': mobileUI
|
||||
})}
|
||||
>
|
||||
{searchable ? (
|
||||
@ -1021,8 +1012,15 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
return mobileUI ? (
|
||||
<PopUp
|
||||
className={cx(`Select-popup`)}
|
||||
isShow={this.state.isOpen}
|
||||
onHide={this.close}
|
||||
>
|
||||
{menu}
|
||||
</PopUp>
|
||||
) : (
|
||||
<Overlay
|
||||
container={popOverContainer || this.getTarget}
|
||||
target={this.getTarget}
|
||||
@ -1031,11 +1029,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
>
|
||||
<PopOver
|
||||
overlay
|
||||
className={cx(
|
||||
'Select-popover',
|
||||
popoverClassName,
|
||||
mobileUI ? 'PopOver-isMobile' : ''
|
||||
)}
|
||||
className={cx('Select-popover')}
|
||||
style={{
|
||||
minWidth: this.target ? this.target.offsetWidth : 'auto'
|
||||
}}
|
||||
@ -1061,13 +1055,14 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
labelField,
|
||||
disabled,
|
||||
checkAll,
|
||||
borderMode
|
||||
borderMode,
|
||||
useMobileUI
|
||||
} = this.props;
|
||||
|
||||
const selection = this.state.selection;
|
||||
const inputValue = this.state.inputValue;
|
||||
const resetValue = this.props.resetValue;
|
||||
|
||||
const mobileUI = useMobileUI && isMobile();
|
||||
return (
|
||||
<Downshift
|
||||
selectedItem={selection}
|
||||
@ -1101,6 +1096,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
'is-opened': isOpen,
|
||||
'is-focused': this.state.isFocused,
|
||||
'is-disabled': disabled,
|
||||
'is-mobile': mobileUI,
|
||||
[`Select--border${ucFirst(borderMode)}`]: borderMode
|
||||
},
|
||||
className
|
||||
|
@ -7,11 +7,13 @@ import ResultBox from './ResultBox';
|
||||
import {Icon} from './icons';
|
||||
import InputBox from './InputBox';
|
||||
import PopOverContainer from './PopOverContainer';
|
||||
import {isMobile} from '../utils/helper';
|
||||
|
||||
export interface TransferDropDownProps extends TransferProps {
|
||||
// 新的属性?
|
||||
multiple?: boolean;
|
||||
borderMode?: 'full' | 'half' | 'none';
|
||||
useMobileUI?: boolean;
|
||||
}
|
||||
|
||||
export class TransferDropDown extends Transfer<TransferDropDownProps> {
|
||||
@ -25,15 +27,22 @@ export class TransferDropDown extends Transfer<TransferDropDownProps> {
|
||||
onChange,
|
||||
onSearch,
|
||||
multiple,
|
||||
borderMode
|
||||
borderMode,
|
||||
useMobileUI
|
||||
} = this.props;
|
||||
const {inputValue, searchResult} = this.state;
|
||||
|
||||
const mobileUI = useMobileUI && isMobile();
|
||||
return (
|
||||
<PopOverContainer
|
||||
useMobileUI={useMobileUI}
|
||||
popOverClassName={cx('TransferDropDown-popover')}
|
||||
popOverRender={({onClose}) => (
|
||||
<div className={cx('TransferDropDown-content')}>
|
||||
<div
|
||||
className={cx('TransferDropDown-content', {
|
||||
'is-mobile': mobileUI
|
||||
})}
|
||||
>
|
||||
{onSearch ? (
|
||||
<div className={cx('Transfer-search')}>
|
||||
<InputBox
|
||||
@ -94,10 +103,15 @@ export class TransferDropDown extends Transfer<TransferDropDownProps> {
|
||||
placeholder={__('Select.placeholder')}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
useMobileUI={useMobileUI}
|
||||
>
|
||||
<span className={cx('TransferDropDown-icon')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
{!mobileUI ? (
|
||||
<span className={cx('TransferDropDown-icon')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ResultBox>
|
||||
)}
|
||||
</PopOverContainer>
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import Overlay from '../../components/Overlay';
|
||||
import Checkbox from '../../components/Checkbox';
|
||||
import PopOver from '../../components/PopOver';
|
||||
import PopUp from '../../components/PopUp';
|
||||
import {Icon} from '../../components/icons';
|
||||
import {
|
||||
autobind,
|
||||
@ -10,7 +11,8 @@ import {
|
||||
string2regExp,
|
||||
getTreeAncestors,
|
||||
getTreeParent,
|
||||
ucFirst
|
||||
ucFirst,
|
||||
isMobile
|
||||
} from '../../utils/helper';
|
||||
import {
|
||||
FormOptionsControl,
|
||||
@ -24,6 +26,7 @@ import xor from 'lodash/xor';
|
||||
import union from 'lodash/union';
|
||||
import compact from 'lodash/compact';
|
||||
import {RootClose} from '../../utils/RootClose';
|
||||
import Cascader from '../../components/Cascader';
|
||||
|
||||
/**
|
||||
* Nested Select
|
||||
@ -68,6 +71,7 @@ export interface NestedSelectProps extends OptionsControlProps {
|
||||
withChildren?: boolean;
|
||||
onlyChildren?: boolean;
|
||||
hideNodePathLabel?: boolean;
|
||||
useMobileUI?: boolean;
|
||||
}
|
||||
|
||||
export interface NestedSelectState {
|
||||
@ -625,12 +629,15 @@ export default class NestedSelectControl extends React.Component<
|
||||
selectedOptions,
|
||||
clearable,
|
||||
loading,
|
||||
borderMode
|
||||
borderMode,
|
||||
useMobileUI
|
||||
} = this.props;
|
||||
|
||||
const mobileUI = useMobileUI && isMobile();
|
||||
return (
|
||||
<div className={cx('NestedSelectControl', className)}>
|
||||
<ResultBox
|
||||
useMobileUI={useMobileUI}
|
||||
disabled={disabled}
|
||||
ref={this.domRef}
|
||||
placeholder={__(placeholder || '空')}
|
||||
@ -665,7 +672,24 @@ export default class NestedSelectControl extends React.Component<
|
||||
>
|
||||
{loading ? <Spinner size="sm" /> : undefined}
|
||||
</ResultBox>
|
||||
{this.state.isOpened ? this.renderOuter() : null}
|
||||
{mobileUI ? (
|
||||
<PopUp
|
||||
className={cx(`NestedSelect-popup`)}
|
||||
isShow={this.state.isOpened}
|
||||
onHide={this.close}
|
||||
showConfirm={false}
|
||||
showClose={false}
|
||||
>
|
||||
<Cascader
|
||||
onClose={this.close}
|
||||
{...this.props}
|
||||
options={this.props.options.slice()}
|
||||
value={selectedOptions}
|
||||
/>
|
||||
</PopUp>
|
||||
) : this.state.isOpened ? (
|
||||
this.renderOuter()
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ export interface SelectProps extends OptionsControlProps {
|
||||
autoComplete?: Api;
|
||||
searchable?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
useMobileUI?: boolean;
|
||||
}
|
||||
|
||||
export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
@ -297,7 +298,6 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
menuTpl,
|
||||
borderMode,
|
||||
selectMode,
|
||||
env,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
@ -347,6 +347,7 @@ export interface TransferDropDownProps
|
||||
| 'descriptionClassName'
|
||||
> {
|
||||
borderMode?: 'full' | 'half' | 'none';
|
||||
useMobileUI?: boolean;
|
||||
}
|
||||
|
||||
class TransferDropdownRenderer extends BaseTransferRenderer<TransferDropDownProps> {
|
||||
@ -367,7 +368,8 @@ class TransferDropdownRenderer extends BaseTransferRenderer<TransferDropDownProp
|
||||
multiple,
|
||||
columns,
|
||||
leftMode,
|
||||
borderMode
|
||||
borderMode,
|
||||
useMobileUI
|
||||
} = this.props;
|
||||
|
||||
// 目前 LeftOptions 没有接口可以动态加载
|
||||
@ -407,6 +409,7 @@ class TransferDropdownRenderer extends BaseTransferRenderer<TransferDropDownProp
|
||||
leftMode={leftMode}
|
||||
leftOptions={leftOptions}
|
||||
borderMode={borderMode}
|
||||
useMobileUI={useMobileUI}
|
||||
/>
|
||||
|
||||
<Spinner overlay key="info" show={loading} />
|
||||
|
Loading…
Reference in New Issue
Block a user