Skip to content

Commit 8e40782

Browse files
authored
feat(component): search input support to specify validateInfo (#167)
* feat(component): search input support to specify validateInfo * fix(component): remove duplicated codes
1 parent b41d645 commit 8e40782

File tree

10 files changed

+261
-9
lines changed

10 files changed

+261
-9
lines changed

src/components/search/base.ts

+20
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,23 @@ export const replaceBtnClassName = getBEMElement(
3131
replaceContainerClassName,
3232
'button'
3333
);
34+
35+
export const validationBaseInputClassName = getBEMElement(
36+
defaultSearchClassName,
37+
'base'
38+
);
39+
40+
export const validationInfoInputClassName = getBEMElement(
41+
defaultSearchClassName,
42+
'info'
43+
);
44+
45+
export const validationWarningInputClassName = getBEMElement(
46+
defaultSearchClassName,
47+
'warning'
48+
);
49+
50+
export const validationErrorInputClassName = getBEMElement(
51+
defaultSearchClassName,
52+
'error'
53+
);

src/components/search/index.tsx

+19-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import { useState } from 'react';
33
import { IActionBarItemProps } from '../actionBar';
4-
import Input from './input';
4+
import Input, { InfoTypeEnum } from './input';
55
import { classNames } from 'mo/common/className';
66
import {
77
baseInputClassName,
@@ -16,6 +16,7 @@ export interface ISearchProps extends React.ComponentProps<any> {
1616
values?: (string | undefined)[];
1717
placeholders?: string[];
1818
addons?: (IActionBarItemProps[] | undefined)[];
19+
validationInfo?: string | { type: keyof typeof InfoTypeEnum; text: string };
1920
onAddonClick?: (addon) => void;
2021
onButtonClick?: (status: boolean) => void;
2122
/**
@@ -29,14 +30,15 @@ export interface ISearchProps extends React.ComponentProps<any> {
2930
/**
3031
* onSearch always be triggered behind onChange or onClick
3132
*/
32-
onSearch?: (queryVal: string | undefined, replaceVal?: string) => void;
33+
onSearch?: (value?: (string | undefined)[]) => void;
3334
}
3435

3536
export function Search(props: ISearchProps) {
3637
const {
3738
className = '',
3839
style,
3940
placeholders = [],
41+
validationInfo: rawInfo,
4042
addons = [],
4143
values = [],
4244
onAddonClick,
@@ -62,7 +64,7 @@ export function Search(props: ISearchProps) {
6264
const onToggleReplaceBtn = () => {
6365
setShowReplace(!isShowReplace);
6466
onButtonClick?.(!isShowReplace);
65-
onSearch?.(searchVal, replaceVal);
67+
onSearch?.([searchVal, replaceVal]);
6668
};
6769

6870
const handleSearchChange = (
@@ -73,15 +75,26 @@ export function Search(props: ISearchProps) {
7375
const values =
7476
source === 'search' ? [value, replaceVal] : [searchVal, value];
7577
onChange(values);
76-
onSearch?.(searchVal, replaceVal);
78+
onSearch?.(values);
7779
}
7880
};
7981

8082
const handleToolbarClick = (addon) => {
8183
onAddonClick?.(addon);
82-
onSearch?.(searchVal, replaceVal);
84+
onSearch?.([searchVal, replaceVal]);
8385
};
8486

87+
const getInfoFromRaw = () => {
88+
if (rawInfo) {
89+
if (typeof rawInfo === 'string') {
90+
return { type: InfoTypeEnum.info, text: rawInfo };
91+
}
92+
return rawInfo;
93+
}
94+
return undefined;
95+
};
96+
97+
const validationInfo = getInfoFromRaw();
8598
return (
8699
<div
87100
style={style}
@@ -98,6 +111,7 @@ export function Search(props: ISearchProps) {
98111
baseInputClassName,
99112
searchTargetContainerClassName
100113
)}
114+
info={validationInfo}
101115
placeholder={searchPlaceholder}
102116
onChange={(v) => handleSearchChange(v, 'search')}
103117
toolbarData={searchAddons}

src/components/search/input.tsx

+64-2
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,95 @@
11
import * as React from 'react';
22
import { Toolbar } from 'mo/components/toolbar';
33
import { IActionBarItemProps } from 'mo/components/actionBar';
4-
import { inputGroupClassName, searchToolBarClassName } from './base';
4+
import {
5+
inputGroupClassName,
6+
searchToolBarClassName,
7+
validationBaseInputClassName,
8+
validationErrorInputClassName,
9+
validationInfoInputClassName,
10+
validationWarningInputClassName,
11+
} from './base';
12+
import { classNames } from 'mo/common/className';
13+
14+
export enum InfoTypeEnum {
15+
info = 'info',
16+
warning = 'warning',
17+
error = 'error',
18+
}
519

620
export interface IBaseInputProps {
721
value?: string;
822
className?: string;
923
placeholder?: string;
1024
toolbarData?: IActionBarItemProps[];
25+
info?: { type: keyof typeof InfoTypeEnum; text: string };
1126
onChange?: (value: string) => void;
1227
onToolbarClick?: (addon) => void;
1328
}
1429

1530
function Input(props: IBaseInputProps) {
16-
const { className, placeholder, toolbarData = [], onChange, value } = props;
31+
const {
32+
className,
33+
placeholder,
34+
toolbarData = [],
35+
onChange,
36+
value,
37+
info,
38+
} = props;
39+
40+
const [focusStatus, setFocus] = React.useState(false);
41+
const inputRef = React.useRef<HTMLInputElement>(null);
1742

1843
const onToolbarClick = (e, item) => {
44+
// toolbar click can trigger input focus
45+
inputRef.current?.focus();
1946
props.onToolbarClick?.(item);
2047
};
2148

49+
const getInfoClassName = (classname: string) => {
50+
switch (classname) {
51+
case InfoTypeEnum.info:
52+
return validationInfoInputClassName;
53+
case InfoTypeEnum.warning:
54+
return validationWarningInputClassName;
55+
case InfoTypeEnum.error:
56+
return validationErrorInputClassName;
57+
default:
58+
return '';
59+
}
60+
};
61+
62+
const handleInputFocus = () => {
63+
setFocus(true);
64+
};
65+
66+
const handleInputBlur = () => {
67+
setFocus(false);
68+
};
69+
2270
return (
2371
<div className={className}>
2472
<input
73+
ref={inputRef}
74+
className={classNames(getInfoClassName(info?.type || ''))}
2575
value={value || ''}
2676
placeholder={placeholder}
77+
onFocus={handleInputFocus}
78+
onBlur={handleInputBlur}
2779
onChange={(e) => {
2880
onChange?.(e.target.value || '');
2981
}}
3082
/>
83+
{info && focusStatus && (
84+
<div
85+
className={classNames(
86+
validationBaseInputClassName,
87+
getInfoClassName(info.type)
88+
)}
89+
>
90+
{info.text}
91+
</div>
92+
)}
3193
<Toolbar
3294
className={searchToolBarClassName}
3395
data={toolbarData}

src/components/search/style.scss

+67
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@
2828
}
2929

3030
input {
31+
border: 1px solid transparent;
3132
box-sizing: border-box;
3233
font-size: inherit;
3334
height: 23px;
3435
padding: 3px;
3536
padding-right: 4px;
3637
text-indent: 4px;
3738
width: 100%;
39+
40+
&:focus,
41+
&:active {
42+
outline: none;
43+
}
3844
}
3945

4046
&__toolbar {
@@ -45,13 +51,23 @@
4551

4652
#{$actionBar} {
4753
&__label {
54+
height: 19px;
55+
margin: 2px 1px;
56+
min-width: auto;
4857
opacity: 0.7;
58+
padding: 0;
4959
transition: opacity 0.3s;
60+
width: 22px;
5061

5162
&:hover {
5263
opacity: 1;
5364
}
5465
}
66+
67+
&__item--checked {
68+
background: var(--inputOption-activeBackground);
69+
opacity: 1;
70+
}
5571
}
5672
}
5773

@@ -62,4 +78,55 @@
6278
&__group &__input + &__input {
6379
margin-top: 5px;
6480
}
81+
82+
// validation styles
83+
&__base {
84+
padding: 5px;
85+
position: absolute;
86+
z-index: 1;
87+
88+
&:not(input) {
89+
margin-top: -1px;
90+
width: calc(100% - 12px);
91+
}
92+
}
93+
94+
& &__info {
95+
border: 1px solid var(--inputValidation-infoBorder);
96+
97+
&:active,
98+
&:focus {
99+
border-color: var(--inputValidation-infoBorder);
100+
}
101+
102+
&:not(input) {
103+
background: var(--inputValidation-infoBackground);
104+
}
105+
}
106+
107+
& &__warning {
108+
border: 1px solid var(--inputValidation-warningBorder);
109+
110+
&:active,
111+
&:focus {
112+
border-color: var(--inputValidation-warningBorder);
113+
}
114+
115+
&:not(input) {
116+
background: var(--inputValidation-warningBackground);
117+
}
118+
}
119+
120+
& &__error {
121+
border: 1px solid var(--inputValidation-errorBorder);
122+
123+
&:active,
124+
&:focus {
125+
border-color: var(--inputValidation-errorBorder);
126+
}
127+
128+
&:not(input) {
129+
background: var(--inputValidation-errorBackground);
130+
}
131+
}
65132
}

src/controller/search/search.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import {
2626
} from 'mo/services';
2727
import { ITreeNodeItemProps } from 'mo/components';
2828
export interface ISearchController {
29+
/**
30+
* Validate value if is valid
31+
*/
32+
validateValue: (value: string) => { valid: boolean; errMessage?: string };
2933
setSearchValue?: (value?: string) => void;
3034
setReplaceValue?: (value?: string) => void;
3135
convertFoldToSearchTree?: (
@@ -68,6 +72,8 @@ export class SearchController extends Controller implements ISearchController {
6872
);
6973

7074
const searchEvent = {
75+
validateValue: this.validateValue,
76+
setValidateInfo: this.setValidateInfo,
7177
setSearchValue: this.setSearchValue,
7278
setReplaceValue: this.setReplaceValue,
7379
onToggleMode: this.onToggleMode,
@@ -102,6 +108,14 @@ export class SearchController extends Controller implements ISearchController {
102108
});
103109
}
104110

111+
public readonly validateValue = (value: string) => {
112+
return this.searchService.validateValue(value);
113+
};
114+
115+
public readonly setValidateInfo = (info) => {
116+
this.searchService.setValidateInfo?.(info);
117+
};
118+
105119
public readonly setSearchValue = (value?: string) => {
106120
this.searchService.setSearchValue?.(value);
107121
};

src/extensions/theme-defaults/themes/dark_defaults.json

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
"activityBarBadge.background": "#007ACC",
1515
"sidebarTitle.foreground": "#BBBBBB",
1616
"input.placeholderForeground": "#A6A6A6",
17+
"inputOption.activeBackground": "#007fd466",
18+
"inputValidation.infoBackground": "#063B49",
19+
"inputValidation.infoBorder": "#007acc",
20+
"inputValidation.warningBackground": "#352A05",
21+
"inputValidation.warningBorder": "#B89500",
22+
"inputValidation.errorBackground": "#5A1D1D",
23+
"inputValidation.errorBorder": "#BE1100",
1724
"settings.textInputBackground": "#292929",
1825
"settings.numberInputBackground": "#292929",
1926
"menu.background": "#252526",

src/extensions/theme-defaults/themes/light_defaults.json

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
"sidebarTitle.foreground": "#6F6F6F",
1515
"list.hoverBackground": "#E8E8E8",
1616
"input.placeholderForeground": "#767676",
17+
"inputOption.activeBackground": "#007fd466",
18+
"inputValidation.infoBackground": "#D6ECF2",
19+
"inputValidation.infoBorder": "#007acc",
20+
"inputValidation.warningBackground": "#F6F5D2",
21+
"inputValidation.warningBorder": "#B89500",
22+
"inputValidation.errorBackground": "#F2DEDE",
23+
"inputValidation.errorBorder": "#BE1100",
1724
"searchEditor.textInputBorder": "#CECECE",
1825
"diffEditor.insertedTextBackground": "#9bb95533",
1926
"diffEditor.removedTextBackground": "#ff000033",

0 commit comments

Comments
 (0)