Skip to content

Commit

Permalink
feat(component): search input support to specify validateInfo (#167)
Browse files Browse the repository at this point in the history
* feat(component): search input support to specify validateInfo

* fix(component): remove duplicated codes
  • Loading branch information
mortalYoung authored Jun 7, 2021
1 parent b41d645 commit 8e40782
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 9 deletions.
20 changes: 20 additions & 0 deletions src/components/search/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,23 @@ export const replaceBtnClassName = getBEMElement(
replaceContainerClassName,
'button'
);

export const validationBaseInputClassName = getBEMElement(
defaultSearchClassName,
'base'
);

export const validationInfoInputClassName = getBEMElement(
defaultSearchClassName,
'info'
);

export const validationWarningInputClassName = getBEMElement(
defaultSearchClassName,
'warning'
);

export const validationErrorInputClassName = getBEMElement(
defaultSearchClassName,
'error'
);
24 changes: 19 additions & 5 deletions src/components/search/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { useState } from 'react';
import { IActionBarItemProps } from '../actionBar';
import Input from './input';
import Input, { InfoTypeEnum } from './input';
import { classNames } from 'mo/common/className';
import {
baseInputClassName,
Expand All @@ -16,6 +16,7 @@ export interface ISearchProps extends React.ComponentProps<any> {
values?: (string | undefined)[];
placeholders?: string[];
addons?: (IActionBarItemProps[] | undefined)[];
validationInfo?: string | { type: keyof typeof InfoTypeEnum; text: string };
onAddonClick?: (addon) => void;
onButtonClick?: (status: boolean) => void;
/**
Expand All @@ -29,14 +30,15 @@ export interface ISearchProps extends React.ComponentProps<any> {
/**
* onSearch always be triggered behind onChange or onClick
*/
onSearch?: (queryVal: string | undefined, replaceVal?: string) => void;
onSearch?: (value?: (string | undefined)[]) => void;
}

export function Search(props: ISearchProps) {
const {
className = '',
style,
placeholders = [],
validationInfo: rawInfo,
addons = [],
values = [],
onAddonClick,
Expand All @@ -62,7 +64,7 @@ export function Search(props: ISearchProps) {
const onToggleReplaceBtn = () => {
setShowReplace(!isShowReplace);
onButtonClick?.(!isShowReplace);
onSearch?.(searchVal, replaceVal);
onSearch?.([searchVal, replaceVal]);
};

const handleSearchChange = (
Expand All @@ -73,15 +75,26 @@ export function Search(props: ISearchProps) {
const values =
source === 'search' ? [value, replaceVal] : [searchVal, value];
onChange(values);
onSearch?.(searchVal, replaceVal);
onSearch?.(values);
}
};

const handleToolbarClick = (addon) => {
onAddonClick?.(addon);
onSearch?.(searchVal, replaceVal);
onSearch?.([searchVal, replaceVal]);
};

const getInfoFromRaw = () => {
if (rawInfo) {
if (typeof rawInfo === 'string') {
return { type: InfoTypeEnum.info, text: rawInfo };
}
return rawInfo;
}
return undefined;
};

const validationInfo = getInfoFromRaw();
return (
<div
style={style}
Expand All @@ -98,6 +111,7 @@ export function Search(props: ISearchProps) {
baseInputClassName,
searchTargetContainerClassName
)}
info={validationInfo}
placeholder={searchPlaceholder}
onChange={(v) => handleSearchChange(v, 'search')}
toolbarData={searchAddons}
Expand Down
66 changes: 64 additions & 2 deletions src/components/search/input.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,95 @@
import * as React from 'react';
import { Toolbar } from 'mo/components/toolbar';
import { IActionBarItemProps } from 'mo/components/actionBar';
import { inputGroupClassName, searchToolBarClassName } from './base';
import {
inputGroupClassName,
searchToolBarClassName,
validationBaseInputClassName,
validationErrorInputClassName,
validationInfoInputClassName,
validationWarningInputClassName,
} from './base';
import { classNames } from 'mo/common/className';

export enum InfoTypeEnum {
info = 'info',
warning = 'warning',
error = 'error',
}

export interface IBaseInputProps {
value?: string;
className?: string;
placeholder?: string;
toolbarData?: IActionBarItemProps[];
info?: { type: keyof typeof InfoTypeEnum; text: string };
onChange?: (value: string) => void;
onToolbarClick?: (addon) => void;
}

function Input(props: IBaseInputProps) {
const { className, placeholder, toolbarData = [], onChange, value } = props;
const {
className,
placeholder,
toolbarData = [],
onChange,
value,
info,
} = props;

const [focusStatus, setFocus] = React.useState(false);
const inputRef = React.useRef<HTMLInputElement>(null);

const onToolbarClick = (e, item) => {
// toolbar click can trigger input focus
inputRef.current?.focus();
props.onToolbarClick?.(item);
};

const getInfoClassName = (classname: string) => {
switch (classname) {
case InfoTypeEnum.info:
return validationInfoInputClassName;
case InfoTypeEnum.warning:
return validationWarningInputClassName;
case InfoTypeEnum.error:
return validationErrorInputClassName;
default:
return '';
}
};

const handleInputFocus = () => {
setFocus(true);
};

const handleInputBlur = () => {
setFocus(false);
};

return (
<div className={className}>
<input
ref={inputRef}
className={classNames(getInfoClassName(info?.type || ''))}
value={value || ''}
placeholder={placeholder}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
onChange={(e) => {
onChange?.(e.target.value || '');
}}
/>
{info && focusStatus && (
<div
className={classNames(
validationBaseInputClassName,
getInfoClassName(info.type)
)}
>
{info.text}
</div>
)}
<Toolbar
className={searchToolBarClassName}
data={toolbarData}
Expand Down
67 changes: 67 additions & 0 deletions src/components/search/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@
}

input {
border: 1px solid transparent;
box-sizing: border-box;
font-size: inherit;
height: 23px;
padding: 3px;
padding-right: 4px;
text-indent: 4px;
width: 100%;

&:focus,
&:active {
outline: none;
}
}

&__toolbar {
Expand All @@ -45,13 +51,23 @@

#{$actionBar} {
&__label {
height: 19px;
margin: 2px 1px;
min-width: auto;
opacity: 0.7;
padding: 0;
transition: opacity 0.3s;
width: 22px;

&:hover {
opacity: 1;
}
}

&__item--checked {
background: var(--inputOption-activeBackground);
opacity: 1;
}
}
}

Expand All @@ -62,4 +78,55 @@
&__group &__input + &__input {
margin-top: 5px;
}

// validation styles
&__base {
padding: 5px;
position: absolute;
z-index: 1;

&:not(input) {
margin-top: -1px;
width: calc(100% - 12px);
}
}

& &__info {
border: 1px solid var(--inputValidation-infoBorder);

&:active,
&:focus {
border-color: var(--inputValidation-infoBorder);
}

&:not(input) {
background: var(--inputValidation-infoBackground);
}
}

& &__warning {
border: 1px solid var(--inputValidation-warningBorder);

&:active,
&:focus {
border-color: var(--inputValidation-warningBorder);
}

&:not(input) {
background: var(--inputValidation-warningBackground);
}
}

& &__error {
border: 1px solid var(--inputValidation-errorBorder);

&:active,
&:focus {
border-color: var(--inputValidation-errorBorder);
}

&:not(input) {
background: var(--inputValidation-errorBackground);
}
}
}
14 changes: 14 additions & 0 deletions src/controller/search/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import {
} from 'mo/services';
import { ITreeNodeItemProps } from 'mo/components';
export interface ISearchController {
/**
* Validate value if is valid
*/
validateValue: (value: string) => { valid: boolean; errMessage?: string };
setSearchValue?: (value?: string) => void;
setReplaceValue?: (value?: string) => void;
convertFoldToSearchTree?: (
Expand Down Expand Up @@ -68,6 +72,8 @@ export class SearchController extends Controller implements ISearchController {
);

const searchEvent = {
validateValue: this.validateValue,
setValidateInfo: this.setValidateInfo,
setSearchValue: this.setSearchValue,
setReplaceValue: this.setReplaceValue,
onToggleMode: this.onToggleMode,
Expand Down Expand Up @@ -102,6 +108,14 @@ export class SearchController extends Controller implements ISearchController {
});
}

public readonly validateValue = (value: string) => {
return this.searchService.validateValue(value);
};

public readonly setValidateInfo = (info) => {
this.searchService.setValidateInfo?.(info);
};

public readonly setSearchValue = (value?: string) => {
this.searchService.setSearchValue?.(value);
};
Expand Down
7 changes: 7 additions & 0 deletions src/extensions/theme-defaults/themes/dark_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
"activityBarBadge.background": "#007ACC",
"sidebarTitle.foreground": "#BBBBBB",
"input.placeholderForeground": "#A6A6A6",
"inputOption.activeBackground": "#007fd466",
"inputValidation.infoBackground": "#063B49",
"inputValidation.infoBorder": "#007acc",
"inputValidation.warningBackground": "#352A05",
"inputValidation.warningBorder": "#B89500",
"inputValidation.errorBackground": "#5A1D1D",
"inputValidation.errorBorder": "#BE1100",
"settings.textInputBackground": "#292929",
"settings.numberInputBackground": "#292929",
"menu.background": "#252526",
Expand Down
7 changes: 7 additions & 0 deletions src/extensions/theme-defaults/themes/light_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
"sidebarTitle.foreground": "#6F6F6F",
"list.hoverBackground": "#E8E8E8",
"input.placeholderForeground": "#767676",
"inputOption.activeBackground": "#007fd466",
"inputValidation.infoBackground": "#D6ECF2",
"inputValidation.infoBorder": "#007acc",
"inputValidation.warningBackground": "#F6F5D2",
"inputValidation.warningBorder": "#B89500",
"inputValidation.errorBackground": "#F2DEDE",
"inputValidation.errorBorder": "#BE1100",
"searchEditor.textInputBorder": "#CECECE",
"diffEditor.insertedTextBackground": "#9bb95533",
"diffEditor.removedTextBackground": "#ff000033",
Expand Down
Loading

0 comments on commit 8e40782

Please sign in to comment.