close #3445: textarea支持clearable; style: 输入框清除Icon更新, 部分样式问题修复 (#3453)

This commit is contained in:
RUNZE LU 2022-01-20 18:30:52 +08:00 committed by GitHub
parent 41dbf067f9
commit 89ad87d2bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 242 additions and 130 deletions

View File

@ -65,8 +65,8 @@ exports[`Renderer:date 1`] = `
class="cxd-DatePicker-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
classname="icon icon-input-clear"
icon="input-clear"
/>
</a>
<a

View File

@ -65,8 +65,8 @@ exports[`Renderer:dateRange 1`] = `
class="cxd-DateRangePicker-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
classname="icon icon-input-clear"
icon="input-clear"
/>
</a>
<a

View File

@ -65,8 +65,8 @@ exports[`Renderer:date 1`] = `
class="cxd-DatePicker-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
classname="icon icon-input-clear"
icon="input-clear"
/>
</a>
<a

View File

@ -982,8 +982,8 @@ exports[`Renderer:text with clearable 1`] = `
class="cxd-TextControl-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
classname="icon icon-input-clear"
icon="input-clear"
/>
</a>
</div>

View File

@ -318,7 +318,7 @@ order: 56
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | ----------------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| -------------- | ----------------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| options | `Array<object>`或`Array<string>` | | [选项组](./options#%E9%9D%99%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-options) |
| source | `string`或 [API](../../../docs/types/api) | | [动态选项组](./options#%E5%8A%A8%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-source) |
| autoComplete | `string`或 [API](../../../docs/types/api) | | [自动补全](./options#%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8-autocomplete) |
@ -331,6 +331,7 @@ order: 56
| addOn | `addOn` | | 输入框附加组件,比如附带一个提示文字,或者附带一个提交按钮。 |
| addOn.type | `string` | | 请选择 `text` 、`button` 或者 `submit`。 |
| addOn.label | `string` | | 文字说明 |
| addOn.position | `'left' \| 'right'` | `'right'` | addOn 位置 |
| addOn.xxx | `string` | | 其他参数请参考按钮文档 |
| trimContents | `boolean` | | 是否去除首尾空白文本。 |
| creatable | `boolean` | | 是否可以创建,默认为可以,除非设置为 false 即只能选择选项中的值 |

View File

@ -24,6 +24,43 @@ order: 57
}
```
## 清空输入框
带移除图标的输入框,点击图标删除所有内容。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"body": [
{
"name": "textarea",
"type": "textarea",
"label": "多行文本",
"clearable": true
}
]
}
```
设置`resetValue`,清空输入框后重置为指定值
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"body": [
{
"name": "textarea",
"type": "textarea",
"label": "多行文本",
"clearable": true,
"resetValue": "reset"
}
]
}
```
## 显示计数器
```schema: scope="body"
@ -35,7 +72,8 @@ order: 57
"type": "textarea",
"label": "A",
"showCounter": true,
"placeholder": "请输入"
"placeholder": "请输入",
"clearable": true
},
{
"name": "b",
@ -54,11 +92,12 @@ order: 57
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | --------- | ------ | -------------------- |
| minRows | `number` | | 最小行数 |
| maxRows | `number` | | 最大行数 |
| trimContents | `boolean` | | 是否去除首尾空白文本 |
| readOnly | `boolean` | | 是否只读 |
| showCounter | `boolean` | `` | 是否显示计数器 |
| minLength | `number` | `` | 限制最小字数 |
| maxLength | `number` | `` | 限制最大字数 |
| ------------ | --------- | ------- | ---------------------------- |
| minRows | `number` | `3` | 最小行数 |
| maxRows | `number` | `20` | 最大行数 |
| trimContents | `boolean` | `true` | 是否去除首尾空白文本 |
| readOnly | `boolean` | `false` | 是否只读 |
| showCounter | `boolean` | `false` | 是否显示计数器 |
| maxLength | `number` | - | 限制最大字数 |
| clearable | `boolean` | `false` | 是否可清除 |
| resetValue | `string` | `""` | 清除后设置此配置项给定的值。 |

View File

@ -303,22 +303,27 @@
}
@mixin input-clear {
padding: px2rem(3px);
padding: var(--Form-input-clearBtn-padding);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
text-decoration: none;
svg {
fill: var(--Form-input-iconColor);
fill: var(--Form-input-clearBtn-color);
top: 0;
width: px2rem(10px);
height: px2rem(10px);
width: var(--Form-input-clearBtn-size);
height: var(--Form-input-clearBtn-size);
}
&:hover svg {
fill: var(--Form-input-onHover-iconColor);
fill: var(--Form-input-clearBtn-color-onHover);
}
&:active svg {
fill: var(--Form-input-clearBtn-color-onActive);
}
}

View File

@ -703,6 +703,12 @@
--Form-input-placeholderColor: var(--text--muted-color);
--Form-input-onDisabled-color: var(--text--muted-color);
--Form-input-clearBtn-size: var(--fontSizeMd);
--Form-input-clearBtn-padding: #{px2rem(3px)};
--Form-input-clearBtn-color: #cecfd1;
--Form-input-clearBtn-color-onHover: #5e626a;
--Form-input-clearBtn-color-onActive: #2d323c;
--Form-item-gap: var(--gap-base);
--Form-label-paddingTop: calc(

View File

@ -51,19 +51,6 @@
&-clear {
@include input-clear();
width: px2rem(26px);
height: px2rem(26px);
margin: 0 px2rem(-2px);
margin-left: auto;
&:hover {
background: var(--ResultBox-value-bg);
}
> svg {
width: px2rem(12px);
height: px2rem(12px);
}
}
> svg {

View File

@ -74,8 +74,6 @@
&-clear {
@include input-clear();
display: inline-block;
line-height: 1;
}
&-arrow {

View File

@ -55,19 +55,26 @@
}
&-toggler {
cursor: pointer;
color: var(--DatePicker-iconColor);
display: inline-flex;
align-items: center;
@include input-clear();
line-height: 1;
&:hover {
color: var(--DatePicker-onHover-iconColor);
svg {
polyline,
circle {
stroke: var(--DatePicker-iconColor);
}
}
&:hover svg {
polyline,
circle {
stroke: var(--DatePicker-onHover-iconColor);
}
}
}
&-clear {
@include input-clear();
display: inline-block;
line-height: 1;
margin-right: var(--gap-xs);
}

View File

@ -56,18 +56,25 @@
}
&-toggler {
cursor: pointer;
color: var(--DatePicker-iconColor);
display: inline-flex;
align-items: center;
@include input-clear();
line-height: 1;
&:hover {
color: var(--DatePicker-onHover-iconColor);
svg {
polyline,
circle {
stroke: var(--DatePicker-iconColor);
}
}
&:hover svg {
polyline,
circle {
stroke: var(--DatePicker-onHover-iconColor);
}
}
}
&-clear {
display: inline-block;
@include input-clear();
line-height: 1;
margin-right: var(--gap-xs);

View File

@ -12,6 +12,8 @@
}
&-valueWrap {
display: flex;
justify-content: space-between;
flex-grow: 1;
line-height: 1;
white-space: nowrap;

View File

@ -63,7 +63,6 @@
}
&-clear {
display: inline-block;
@include input-clear();
line-height: 1;
margin-right: var(--gap-xs);

View File

@ -87,7 +87,6 @@
}
&-clear {
display: inline-block;
@include input-clear();
line-height: 1;
margin-right: var(--gap-xs);

View File

@ -46,7 +46,6 @@
flex-basis: 1;
flex-grow: 1;
min-width: 0;
border-radius: 0;
&:first-child {
@ -103,16 +102,16 @@
}
&:first-child .#{$ns}Button {
@if var(--InputGroup-button-borderRadius) {
border-top-left-radius: var(--InputGroup-button-borderRadius);
border-bottom-left-radius: var(--InputGroup-button-borderRadius);
@if var(--InputGroup-addOn-borderRadius) {
border-top-left-radius: var(--InputGroup-addOn-borderRadius);
border-bottom-left-radius: var(--InputGroup-addOn-borderRadius);
}
}
&:last-child .#{$ns}Button {
@if var(--InputGroup-button-borderRadius) {
border-top-right-radius: var(--InputGroup-button-borderRadius);
border-bottom-right-radius: var(--InputGroup-button-borderRadius);
@if var(--InputGroup-addOn-borderRadius) {
border-top-right-radius: var(--InputGroup-addOn-borderRadius);
border-bottom-right-radius: var(--InputGroup-addOn-borderRadius);
}
}
}

View File

@ -1,4 +1,6 @@
.#{$ns}TextareaControl {
--Form-input-clearBtn-padding: #{px2rem(2px)};
position: relative;
> textarea {
@ -62,9 +64,23 @@
color: #666;
background: rgba(0, 0, 0, 0.4);
}
&.is-clearable {
right: calc(
var(--Form-input-paddingX) + var(--Form-input-clearBtn-size) +
var(--Form-input-clearBtn-padding) * 2 + #{px2rem(5px)}
);
}
}
.has-error--maxLength &-counter {
background: var(--danger);
}
&-clear {
@include input-clear();
position: absolute;
right: var(--Form-input-paddingX);
bottom: var(--Form-input-paddingY);
}
}

View File

@ -148,6 +148,9 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06),
--Form-input-addOnColor: #666;
--Form-input-onFocus-addOnColor: var(--primary);
--Form-input-addOnDividerBorderWidth: 0;
--Form-input-clearBtn-color: #{$G7};
--Form-input-clearBtn-color-onHover: #{$G4};
--Form-input-clearBtn-color-onActive: #{$G3};
--Form-select-borderWidth: #{px2rem(1px)};
--Form-select-borderRadius: #{$R3};
@ -179,7 +182,7 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06),
--InputGroup-select-arrowColor: #999;
--InputGroup-select-onFocused-arrowColor: var(--primary);
--InputGroup-button-borderWidth: #{px2rem(1px)};
--InputGroup-button-borderRadius: 0;
--InputGroup-button-borderRadius: #{$R3};
// $Number-bg: #eaf6fe;
--Number-borderWidth: #{px2rem(1px)};

View File

@ -106,19 +106,6 @@
}
}
.#{$ns}InputGroup {
// .#{$ns}TextControl-input {
// }
.#{$ns}InputGroup-btn .#{$ns}Button {
border-radius: px2rem(3px);
}
.#{$ns}InputGroup-btn:last-child .#{$ns}Button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
.#{$ns}Button--iconOnly {
min-width: var(--Button-height);
}

View File

@ -267,7 +267,7 @@ export class ColorControl extends React.PureComponent<
{clearable && !disabled && value ? (
<a onClick={this.clearValue} className={cx('ColorPicker-clear')}>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}

View File

@ -700,7 +700,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
{clearable && !disabled && normalizeValue(value, format) ? (
<a className={cx(`DatePicker-clear`)} onClick={this.clearValue}>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}

View File

@ -885,7 +885,7 @@ export class DateRangePicker extends React.Component<
{clearable && !disabled && value ? (
<a className={`${ns}DateRangePicker-clear`} onClick={this.clearValue}>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}

View File

@ -171,7 +171,7 @@ export class LocationPicker extends React.Component<
{clearable && !disabled && value ? (
<a className={cx('LocationPicker-clear')} onClick={this.clearValue}>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}

View File

@ -184,7 +184,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
!disabled &&
(Array.isArray(result) ? result.length : result) ? (
<a onClick={this.clearValue} className={cx('ResultBox-clear')}>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}
{!allowInput && mobileUI ? (

View File

@ -77,6 +77,7 @@ import AlertInfo from '../icons/alert-info.svg';
import AlertWarning from '../icons/alert-warning.svg';
import AlertDanger from '../icons/alert-danger.svg';
import FunctionIcon from '../icons/function.svg';
import InputClearIcon from '../icons/input-clear.svg';
// 兼容原来的用法,后续不直接试用。
@ -180,6 +181,7 @@ registerIcon('alert-warning', AlertWarning);
registerIcon('alert-danger', AlertDanger);
registerIcon('tree-down', TreeDownIcon);
registerIcon('function', FunctionIcon);
registerIcon('input-clear', InputClearIcon);
export function Icon({
icon,
@ -202,6 +204,7 @@ export function Icon({
}
export {
InputClearIcon,
CloseIcon,
UnDoIcon,
ReDoIcon,

View File

@ -0,0 +1 @@
<svg t="1642652418667" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3606" width="200" height="200"><path d="M512 39.384615C250.092308 39.384615 39.384615 250.092308 39.384615 512s210.707692 472.615385 472.615385 472.615385 472.615385-210.707692 472.615385-472.615385S773.907692 39.384615 512 39.384615z m96.492308 488.369231l153.6 153.6c7.876923 7.876923 7.876923 19.692308 0 27.569231l-55.138462 55.138461c-7.876923 7.876923-19.692308 7.876923-27.569231 0L525.784615 610.461538c-7.876923-7.876923-19.692308-7.876923-27.56923 0l-153.6 153.6c-7.876923 7.876923-19.692308 7.876923-27.569231 0L261.907692 708.923077c-7.876923-7.876923-7.876923-19.692308 0-27.569231l153.6-153.6c7.876923-7.876923 7.876923-19.692308 0-27.569231l-155.56923-155.56923c-7.876923-7.876923-7.876923-19.692308 0-27.569231l55.138461-55.138462c7.876923-7.876923 19.692308-7.876923 27.569231 0l155.569231 155.569231c7.876923 7.876923 19.692308 7.876923 27.56923 0l153.6-153.6c7.876923-7.876923 19.692308-7.876923 27.569231 0l55.138462 55.138462c7.876923 7.876923 7.876923 19.692308 0 27.56923l-153.6 153.6c-5.907692 7.876923-5.907692 19.692308 0 27.569231z" p-id="3607"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -7,6 +7,7 @@ import {autobind} from '../../utils/helper';
import {ICONS} from './IconPickerIcons';
import {FormItem, FormControlProps, FormBaseControl} from './Item';
import {Option} from '../../components/Select';
import {Icon} from '../../components/icons';
/**
*
@ -22,6 +23,7 @@ export interface IconPickerProps extends FormControlProps {
placeholder?: string;
resetValue?: any;
noDataTip?: string;
clearable?: boolean;
}
export interface IconPickerState {
@ -210,6 +212,23 @@ export default class IconPickerControl extends React.PureComponent<
}
}
@autobind
handleClear() {
const {onChange, resetValue} = this.props;
onChange?.(resetValue);
this.setState(
{
inputValue: resetValue,
isFocused: true
},
() => {
this.focus();
}
);
}
renderFontIcons() {
const {
className,
@ -220,6 +239,7 @@ export default class IconPickerControl extends React.PureComponent<
value,
noDataTip,
disabled,
clearable,
translate: __
} = this.props;
const options = this.formatOptions();
@ -278,6 +298,15 @@ export default class IconPickerControl extends React.PureComponent<
disabled={disabled}
size={10}
/>
{clearable && !disabled && value ? (
<a
onClick={this.handleClear}
className={cx('IconPickerControl-clear')}
>
<Icon icon="input-clear" className="icon" />
</a>
) : null}
</div>
{isOpen ? (
<div className={cx('IconPickerControl-sugsPanel')}>

View File

@ -634,7 +634,7 @@ export default class TextControl extends React.PureComponent<
onClick={this.clearValue}
className={cx('TextControl-clear')}
>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}
@ -740,7 +740,7 @@ export default class TextControl extends React.PureComponent<
/>
{clearable && !disabled && value ? (
<a onClick={this.clearValue} className={`${ns}TextControl-clear`}>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}
{showCounter ? (

View File

@ -469,7 +469,7 @@ export default class PickerControl extends React.PureComponent<
{clearable && !disabled && selectedOptions.length ? (
<a onClick={this.clearValue} className={cx('Picker-clear')}>
<Icon icon="close" className="icon" />
<Icon icon="input-clear" className="icon" />
</a>
) : null}

View File

@ -2,6 +2,7 @@ import React from 'react';
import {FormItem, FormControlProps, FormBaseControl} from './Item';
import cx from 'classnames';
import Textarea from '../../components/Textarea';
import {Icon} from '../../components/icons';
import {findDOMNode} from 'react-dom';
import {autobind, ucFirst} from '../../utils/helper';
/**
@ -43,12 +44,24 @@ export interface TextareaControlSchema extends FormBaseControl {
*
*/
showCounter?: boolean;
/**
*
*/
clearable?: boolean;
/**
*
*/
resetValue?: string;
}
export interface TextAreaProps extends FormControlProps {
placeholder?: string;
minRows?: number;
maxRows?: number;
clearable?: boolean;
resetValue?: string;
}
export default class TextAreaControl extends React.Component<
@ -58,7 +71,9 @@ export default class TextAreaControl extends React.Component<
static defaultProps: Partial<TextAreaProps> = {
minRows: 3,
maxRows: 20,
trimContents: true
trimContents: true,
resetValue: '',
clearable: false
};
state = {
@ -68,6 +83,14 @@ export default class TextAreaControl extends React.Component<
input?: HTMLInputElement;
inputRef = (ref: any) => (this.input = findDOMNode(ref) as HTMLInputElement);
valueToString(value: any) {
return typeof value === 'undefined' || value === null
? ''
: typeof value === 'string'
? value
: JSON.stringify(value);
}
focus() {
if (!this.input) {
return;
@ -132,6 +155,14 @@ export default class TextAreaControl extends React.Component<
);
}
@autobind
handleClear() {
const {onChange, resetValue} = this.props;
onChange?.(resetValue);
this.focus();
}
render() {
const {
className,
@ -147,17 +178,10 @@ export default class TextAreaControl extends React.Component<
borderMode,
classnames: cx,
maxLength,
showCounter
showCounter,
clearable
} = this.props;
let counter = showCounter
? (typeof value === 'undefined' || value === null
? ''
: typeof value === 'string'
? value
: JSON.stringify(value)
).length
: 0;
const counter = showCounter ? this.valueToString(value).length : 0;
return (
<div
@ -175,13 +199,7 @@ export default class TextAreaControl extends React.Component<
ref={this.inputRef}
name={name}
disabled={disabled}
value={
typeof value === 'undefined' || value === null
? ''
: typeof value === 'string'
? value
: JSON.stringify(value)
}
value={this.valueToString(value)}
placeholder={placeholder}
autoCorrect="off"
spellCheck="false"
@ -193,12 +211,18 @@ export default class TextAreaControl extends React.Component<
onBlur={this.handleBlur}
/>
{clearable && !disabled && value ? (
<a onClick={this.handleClear} className={cx('TextareaControl-clear')}>
<Icon icon="input-clear" className="icon" />
</a>
) : null}
{showCounter ? (
<span
className={cx(
'TextareaControl-counter',
counter === 0 ? 'is-empty' : ''
)}
className={cx('TextareaControl-counter', {
'is-empty': counter === 0,
'is-clearable': clearable && !disabled && value
})}
>
{`${counter}${
typeof maxLength === 'number' && maxLength ? `/${maxLength}` : ''