Add search box component

This commit is contained in:
MrKBear 2022-03-26 18:54:51 +08:00
parent ce5e6a34d8
commit 87023251a9
8 changed files with 369 additions and 30 deletions

View File

@ -4,3 +4,8 @@ div.behavior-popup {
width: 100%;
height: 100%;
}
div.behavior-popup-search-box {
padding: 10px 0 0 10px;
width: calc(100% - 10px);
}

View File

@ -1,13 +1,18 @@
import { Component, ReactNode } from "react";
import { Popup } from "@Context/Popups";
import { Theme } from "@Component/Theme/Theme";
import { Localization } from "@Component/Localization/Localization";
import { SearchBox } from "@Component/SearchBox/SearchBox";
import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup";
import "./BehaviorPopup.scss";
interface IBehaviorPopupProps {
}
interface IBehaviorPopupState {
searchValue: string;
}
class BehaviorPopup extends Popup<IBehaviorPopupProps> {
public minWidth: number = 400;
@ -24,10 +29,36 @@ class BehaviorPopup extends Popup<IBehaviorPopupProps> {
}
}
class BehaviorPopupComponent extends Component<IBehaviorPopupProps> {
class BehaviorPopupComponent extends Component<IBehaviorPopupProps, IBehaviorPopupState> {
state: Readonly<IBehaviorPopupState> = {
searchValue: ""
};
private renderHeader = () => {
return <div className="behavior-popup-search-box">
<SearchBox
valueChange={(value) => {
this.setState({
searchValue: value
});
}}
value={this.state.searchValue}
/>
</div>;
}
public render(): ReactNode {
return <Theme className="behavior-popup"></Theme>
return <ConfirmContent
className="behavior-popup"
actions={[{
i18nKey: "Popup.Add.Behavior.Action.Add"
}]}
header={this.renderHeader}
headerHeight={36}
>
</ConfirmContent>
}
}

View File

@ -4,10 +4,35 @@ div.confirm-root {
width: 100%;
height: 100%;
div.header-view {
width: 100%;
}
div.content-views {
width: 100%;
height: calc( 100% - 36px );
box-sizing: border-box;
overflow: scroll;
-ms-overflow-style: none;
flex-shrink: 1;
}
div.content-views::-webkit-scrollbar {
width : 8px; /*高宽分别对应横竖滚动条的尺寸*/
height: 0;
}
div.content-views::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 8px;
}
div.content-views::-webkit-scrollbar-track {
/*滚动条里面轨道*/
border-radius: 8px;
background-color: rgba($color: #000000, $alpha: 0);
}
div.content-views.has-padding {
padding: 10px;
}
@ -35,14 +60,27 @@ div.confirm-root {
div.action-button.red {
color: $lt-red;
}
div.action-button.blue {
color: $lt-blue;
}
div.action-button.disable {
opacity: .75;
cursor: not-allowed;
}
}
}
div.dark.confirm-root {
div.content-views::-webkit-scrollbar-thumb {
background-color: $lt-bg-color-lvl1-dark;
}
div.action-view {
div.action-button {
div.action-button, div.action-button.disable:hover {
background-color: $lt-bg-color-lvl3-dark;
}
@ -54,9 +92,13 @@ div.dark.confirm-root {
div.light.confirm-root {
div.content-views::-webkit-scrollbar-thumb {
background-color: $lt-bg-color-lvl1-light;
}
div.action-view {
div.action-button {
div.action-button, div.action-button.disable:hover {
background-color: $lt-bg-color-lvl3-light;
}

View File

@ -1,5 +1,5 @@
import { Popup } from "@Context/Popups";
import { ReactNode } from "react";
import { Component, ReactNode } from "react";
import { Message } from "@Component/Message/Message";
import { Theme } from "@Component/Theme/Theme";
import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
@ -7,12 +7,12 @@ import "./ConfirmPopup.scss";
interface IConfirmPopupProps {
titleI18N?: AllI18nKeys;
infoI18n: AllI18nKeys;
infoI18n?: AllI18nKeys;
yesI18n?: AllI18nKeys;
noI18n?: AllI18nKeys;
red?: "yes" | "no";
yes?: () => any;
no?: () => any;
red?: "yes" | "no";
}
class ConfirmPopup extends Popup<IConfirmPopupProps> {
@ -24,37 +24,139 @@ class ConfirmPopup extends Popup<IConfirmPopupProps> {
return <Localization i18nKey={this.props.titleI18N ?? "Popup.Title.Confirm"}/>
}
private genActionClickFunction(fn?: () => any): () => any {
return () => {
if (fn) fn();
this.close();
};
}
public render(): ReactNode {
const yesClassList: string[] = ["action-button", "yes-button"];
const noClassList: string[] = ["action-button", "no-button"];
if (this.props.red === "no") {
noClassList.push("red");
const actionList: IActionButtonProps[] = [];
if (this.props.yesI18n || this.props.yes) {
actionList.push({
i18nKey: this.props.yesI18n ?? "Popup.Action.Yes",
onClick: this.genActionClickFunction(this.props.yes),
color: this.props.red === "yes" ? "red" : undefined
});
}
if (this.props.red === "yes") {
yesClassList.push("red");
if (this.props.noI18n || this.props.no) {
actionList.push({
i18nKey: this.props.noI18n ?? "Popup.Action.Yes",
onClick: this.genActionClickFunction(this.props.no),
color: this.props.red === "no" ? "red" : undefined
});
}
return <ConfirmContent
actions={actionList}
>
{this.props.infoI18n ? <Message i18nKey={this.props.infoI18n}/> : null}
</ConfirmContent>
}
}
interface IConfirmContentProps {
hidePadding?: boolean;
className?: string;
actions: IActionButtonProps[];
header?: () => ReactNode;
headerHeight?: number;
}
interface IActionButtonProps {
className?: string;
disable?: boolean;
color?: "red" | "blue";
i18nKey: AllI18nKeys;
i18nOption?: Record<string, string>;
onClick?: () => void;
}
class ConfirmContent extends Component<IConfirmContentProps> {
public renderActionButton(props: IActionButtonProps, key: number): ReactNode {
const classList = ["action-button"];
if (props.className) {
classList.push(props.className);
}
if (props.color === "red") {
classList.push("red");
}
if (props.color === "blue") {
classList.push("blue");
}
if (props.disable) {
classList.push("disable");
}
return <div
className={classList.join(" ")}
onClick={props.disable ? undefined : props.onClick}
key={key}
>
<Localization i18nKey={props.i18nKey} options={props.i18nOption}/>
</div>
}
private getHeaderHeight(): number {
return this.props.headerHeight ?? 0;
}
private renderHeader() {
return <div
className="header-view"
style={{
maxHeight: this.getHeaderHeight(),
minHeight: this.getHeaderHeight(),
height: this.getHeaderHeight()
}}
>
{this.props.header ? this.props.header() : null}
</div>
}
public render(): ReactNode {
const contentClassNameList: string[] = ["content-views"];
if (this.props.className) {
contentClassNameList.push(this.props.className);
}
if (!this.props.hidePadding) {
contentClassNameList.push("has-padding");
}
return <Theme className="confirm-root">
<div className="content-views">
<Message i18nKey={this.props.infoI18n}/>
{this.props.header ? this.renderHeader() : null}
<div
className={contentClassNameList.join(" ")}
style={{
height: `calc( 100% - ${this.getHeaderHeight() + 36}px )`
}}
>
{this.props.children}
</div>
<div className="action-view">
<div className={yesClassList.join(" ")} onClick={() => {
this.props.yes ? this.props.yes() : null;
this.close();
}}>
<Localization i18nKey={this.props.yesI18n ?? "Popup.Action.Yes"}/>
</div>
<div className={noClassList.join(" ")} onClick={() => {
this.props.no ? this.props.no() : null;
this.close();
}}>
<Localization i18nKey={this.props.noI18n ?? "Popup.Action.No"}/>
</div>
{
this.props.actions.map((prop, index) => {
return this.renderActionButton(prop, index);
})
}
</div>
</Theme>;
}
}
export { ConfirmPopup }
export { ConfirmPopup, ConfirmContent }

View File

@ -0,0 +1,95 @@
@import "../Theme/Theme.scss";
$search-box-height: 26px;
div.search-box-root {
min-height: $search-box-height;
max-width: 280px;
width: 100%;
border-radius: 3px;
display: flex;
cursor: pointer;
overflow: hidden;
div.search-icon {
min-width: $search-box-height;
height: $search-box-height;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
}
div.input-box {
width: calc(100% - 26px);
height: $search-box-height;
input {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
border: 0;
outline: none;
background-color: transparent;
vertical-align: middle;
}
}
div.clean-box {
height: $search-box-height;
width: 0;
display: flex;
align-items: center;
div.clean-box-view {
flex-shrink: 0;
height: 24px;
width: 24px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
right: 24px;
border-radius: 3px;
user-select: none;
}
}
}
div.dark.search-box-root {
div.clean-box {
div.clean-box-view:hover {
background-color: $lt-bg-color-lvl2-dark;
}
div.clean-box-view {
background-color: $lt-bg-color-lvl3-dark;
}
}
div.input-box input {
color: $lt-font-color-normal-dark;
}
}
div.light.search-box-root {
div.clean-box {
div.clean-box-view:hover {
background-color: $lt-bg-color-lvl3-light;
}
div.clean-box-view {
background-color: $lt-bg-color-lvl2-light;
}
}
div.input-box input {
color: $lt-font-color-normal-light;
}
}

View File

@ -0,0 +1,60 @@
import { AllI18nKeys, I18N } from "@Component/Localization/Localization";
import { BackgroundLevel, FontLevel, Theme } from "@Component/Theme/Theme";
import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting";
import { Icon } from "@fluentui/react";
import { Component, ReactNode } from "react";
import "./SearchBox.scss";
interface ISearchBoxProps {
value?: string;
valueChange?: (value: string) => void;
placeholderI18N?: AllI18nKeys;
className?: string;
}
@useSettingWithEvent("language")
class SearchBox extends Component<ISearchBoxProps & IMixinSettingProps> {
private renderCleanBox() {
return <div className="clean-box">
<div
className="clean-box-view"
onClick={() => {
if (this.props.valueChange) {
this.props.valueChange("")
}
}}
>
<Icon iconName="CalculatorMultiply"/>
</div>
</div>;
}
public render(): ReactNode {
return <Theme
className={"search-box-root" + (this.props.className ? ` ${this.props.className}` : "")}
backgroundLevel={BackgroundLevel.Level3}
fontLevel={FontLevel.normal}
>
<div className="search-icon">
<Icon iconName="search"/>
</div>
<div className="input-box">
<input
value={this.props.value}
placeholder={
I18N(this.props, this.props.placeholderI18N ?? "Common.Search.Placeholder")
}
onInput={(e) => {
if (e.target instanceof HTMLInputElement && this.props.valueChange) {
this.props.valueChange(e.target.value)
}
}}
/>
</div>
{this.props.value ? this.renderCleanBox() : null}
</Theme>
}
}
export { SearchBox };

View File

@ -53,10 +53,12 @@ const EN_US = {
"Popup.Delete.Objects.Confirm": "Are you sure you want to delete this object(s)? The object is deleted and cannot be recalled.",
"Popup.Setting.Title": "Preferences setting",
"Popup.Add.Behavior.Title": "Add behavior",
"Popup.Add.Behavior.Action.Add": "Add all select behavior",
"Build.In.Label.Name.All.Group": "All group",
"Build.In.Label.Name.All.Range": "All range",
"Behavior.Template.Title": "Behavior",
"Behavior.Template.Intro": "This is a template behavior",
"Common.Search.Placeholder": "Search in here...",
"Common.No.Data": "No Data",
"Common.No.Unknown.Error": "Unknown error",
"Common.Attr.Title.Basic": "Basic properties",

View File

@ -53,10 +53,12 @@ const ZH_CN = {
"Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。",
"Popup.Setting.Title": "首选项设置",
"Popup.Add.Behavior.Title": "添加行为",
"Popup.Add.Behavior.Action.Add": "添加全部选中行为",
"Build.In.Label.Name.All.Group": "全部群",
"Build.In.Label.Name.All.Range": "全部范围",
"Behavior.Template.Title": "行为",
"Behavior.Template.Intro": "这是一个模板行为",
"Common.Search.Placeholder": "在此处搜索...",
"Common.No.Data": "暂无数据",
"Common.No.Unknown.Error": "未知错误",
"Common.Attr.Title.Basic": "基础属性",