Skip to content

Add mention component #320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion client/packages/lowcoder-design/src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,5 @@ export { ReactComponent as CompressIcon } from "icons/icon-compress.svg";
export { ReactComponent as TableCellsIcon } from "icons/icon-table-cells.svg"; // Added By Aqib Mirza
export { ReactComponent as TimeLineIcon } from "icons/icon-timeline-comp.svg"
export { ReactComponent as LottieIcon } from "icons/icon-lottie.svg";
export { ReactComponent as AutoCompleteCompIcon } from "icons/icon-autocomplete-comp.svg";
export { ReactComponent as MentionIcon } from "icons/icon-mention-comp.svg";
export { ReactComponent as AutoCompleteCompIcon } from "icons/icon-autocomplete-comp.svg";
268 changes: 268 additions & 0 deletions client/packages/lowcoder/src/comps/comps/textInputComp/mentionComp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import { useState, useEffect } from "react";
import {
NameConfig,
NameConfigPlaceHolder,
NameConfigRequired,
withExposingConfigs,
} from "comps/generators/withExposing";
import { Section, sectionNames } from "lowcoder-design";
import { BoolControl } from "../../controls/boolControl";
import { AutoHeightControl } from "../../controls/autoHeightControl";
import { UICompBuilder } from "../../generators";
import { FormDataPropertyView } from "../formComp/formDataConstants";
import {
checkMentionListData,
textInputChildren,
} from "./textInputConstants";
import {
withMethodExposing,
refMethods,
} from "../../generators/withMethodExposing";
import { styleControl } from "comps/controls/styleControl";
import styled from "styled-components";
import {
InputLikeStyle,
InputLikeStyleType,
} from "comps/controls/styleControlConstants";
import {
disabledPropertyView,
hiddenPropertyView,
maxLengthPropertyView,
minLengthPropertyView,
readOnlyPropertyView,
requiredPropertyView,
} from "comps/utils/propertyUtils";
import { booleanExposingStateControl } from "comps/controls/codeStateControl";
import { trans } from "i18n";
import { RefControl } from "comps/controls/refControl";
import { TextAreaRef } from "antd/lib/input/TextArea";
import { Mentions, ConfigProvider } from "antd";
import { blurMethod, focusWithOptions } from "comps/utils/methodUtils";
import type { MentionsOptionProps } from "antd/es/mentions";
import {
textInputValidate,
} from "../textInputComp/textInputConstants";
import { jsonControl } from "@lowcoder-ee/comps/controls/codeControl";
// 事件控制
import {
submitEvent,
eventHandlerControl,
mentionEvent,
focusEvent,
blurEvent,
changeEvent
} from "comps/controls/eventHandlerControl";

const Wrapper = styled.div<{
$style: InputLikeStyleType;
}>`
height: 100%;

.ant-input-clear-icon {
opacity: 0.45;
color: ${(props) => props.$style.text};
top: 10px;

&:hover {
opacity: 0.65;
color: ${(props) => props.$style.text};
}
}
`;

const EventOptions = [
focusEvent,
blurEvent,
changeEvent,
mentionEvent,
submitEvent,
] as const;

let MentionTmpComp = (function () {
const childrenMap = {
...textInputChildren,
viewRef: RefControl<TextAreaRef>,
allowClear: BoolControl,
autoHeight: AutoHeightControl,
style: styleControl(InputLikeStyle),
mentionList: jsonControl(checkMentionListData, {
"@": ["Li Lei", "Han Meimei"],
"#": ["123", "456", "789"],
}),
onEvent: eventHandlerControl(EventOptions),
invalid: booleanExposingStateControl("invalid"),
};

return new UICompBuilder(childrenMap, (props) => {
const { mentionList } = props;
const [validateState, setvalidateState] = useState({});
const [activationFlag, setActivationFlag] = useState(false);
const [prefix, setPrefix] = useState<PrefixType>("@");
type PrefixType = "@" | keyof typeof mentionList;

// 获取提及搜索关键字
const onSearch = (_: string, newPrefix: PrefixType) => {
setPrefix(newPrefix);
};
const onChange = (value: string) => {
props.value.onChange(value);
props.onEvent("change");
};

const onPressEnter = (e: any) => {
if (e.shiftKey) {
e.preventDefault();
props.onEvent("submit");
}
};

const onSelect = (option: MentionsOptionProps) => {
props.onEvent("mention");
};
const getValidate = (value: any): "" | "warning" | "error" | undefined => {
if (
value.hasOwnProperty("validateStatus") &&
value["validateStatus"] === "error"
)
return "error";
return "";
};

const getTextInputValidate = () => {
return {
value: { value: props.value.value },
required: props.required,
minLength: props?.minLength ?? 0,
maxLength: props?.maxLength ?? 0,
validationType: props.validationType,
regex: props.regex,
customRule: props.customRule,
};
};

useEffect(() => {
if (activationFlag) {
const temp = textInputValidate(getTextInputValidate());
setvalidateState(temp);
props.invalid.onChange(temp.validateStatus !== "");
}
}, [
props.value.value,
props.required,
props?.minLength,
props?.maxLength,
props.validationType,
props.regex,
props.customRule,
]);
return props.label({
required: props.required,
children: (
<Wrapper $style={props.style}>
<ConfigProvider
theme={{
token: {
colorBgContainer: props.style.background,
colorBorder: props.style.border,
borderRadius: parseInt(props.style.radius),
colorText: props.style.text,
colorPrimary: props.style.accent,
},
}}
>
<Mentions
prefix={Object.keys(mentionList)}
onFocus={() => {
setActivationFlag(true);
props.onEvent("focus");
}}
onBlur={() => props.onEvent("blur")}
onPressEnter={onPressEnter}
onSearch={onSearch}
onChange={onChange}
onSelect={onSelect}
placeholder={props.placeholder}
value={props.value.value}
disabled={props.disabled}
status={getValidate(validateState)}
options={(mentionList[prefix] || []).map((value: string) => ({
key: value,
value,
label: value,
}))}
autoSize={props.autoHeight}
style={{ height: "100%", maxHeight: "100%", resize: "none", padding: props.style.padding }}
readOnly={props.readOnly}
/>
</ConfigProvider>
</Wrapper>
),
style: props.style,
...validateState,
});
})
.setPropertyViewFn((children) => (
<>
<Section name={sectionNames.basic}>
{children.mentionList.propertyView({
label: trans("mention.mentionList"),
})}
{children.value.propertyView({ label: trans("prop.defaultValue") })}
{children.placeholder.propertyView({
label: trans("prop.placeholder"),
})}
</Section>
<FormDataPropertyView {...children} />
{children.label.getPropertyView()}

<Section name={sectionNames.interaction}>
{children.onEvent.getPropertyView()}
{disabledPropertyView(children)}
</Section>

<Section name={sectionNames.advanced}>
{readOnlyPropertyView(children)}
</Section>

<Section name={sectionNames.validation}>
{requiredPropertyView(children)}
{children.validationType.propertyView({
label: trans("prop.textType"),
})}
{minLengthPropertyView(children)}
{maxLengthPropertyView(children)}
{children.customRule.propertyView({})}
</Section>

<Section name={sectionNames.layout}>
{children.autoHeight.getPropertyView()}
{hiddenPropertyView(children)}
</Section>

<Section name={sectionNames.style}>
{children.style.getPropertyView()}
</Section>
</>
))
.build();
})();

MentionTmpComp = class extends MentionTmpComp {
override autoHeight(): boolean {
return this.children.autoHeight.getView();
}
};

const TextareaTmp2Comp = withMethodExposing(
MentionTmpComp,
refMethods([focusWithOptions, blurMethod])
);

export const MentionComp = withExposingConfigs(TextareaTmp2Comp, [
new NameConfig("value", trans("export.inputValueDesc")),
NameConfigPlaceHolder,
NameConfigRequired,
new NameConfig("invalid", trans("export.invalidDesc")),
new NameConfig("hidden", trans("export.hiddenDesc")),
new NameConfig("disabled", trans("export.disabledDesc")),
]);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BoolControl } from "comps/controls/boolControl";
import { check } from "util/convertUtils";
import {
BoolCodeControl,
CustomRuleControl,
Expand Down Expand Up @@ -268,3 +269,14 @@ export const inputRefMethods = [
(comp.children.viewRef.viewRef?.input?.setRangeText as any)?.(...params),
},
];

export function checkMentionListData(data: any) {
if(data === "") return {}
for(const key in data) {
check(data[key], ["array"], key,(node)=>{
check(node, ["string"], );
return node
})
}
return data
}
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ export const successEvent: EventConfigType = {
value: "success",
description: trans("event.successDesc"),
};
export const mentionEvent: EventConfigType = {
label: trans("event.mention"),
value: "mention",
description: trans("event.mentionDesc"),
};

export const InputEventHandlerControl = eventHandlerControl([
changeEvent,
Expand Down
13 changes: 12 additions & 1 deletion client/packages/lowcoder/src/comps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import {
VideoCompIcon,
TimeLineIcon,
LottieIcon,
MentionIcon,
AutoCompleteCompIcon,
} from "lowcoder-design";

Expand Down Expand Up @@ -121,7 +122,8 @@ import { RemoteCompInfo } from "types/remoteComp";
import { ScannerComp } from "./comps/buttonComp/scannerComp";
import { SignatureComp } from "./comps/signatureComp";
import { TimeLineComp } from "./comps/timelineComp/timelineComp";
import { AutoCompleteComp } from "./comps/autoCompleteComp/autoCompleteComp";
import { MentionComp } from "./comps/textInputComp/mentionComp";
import { AutoCompleteComp } from "./comps/autoCompleteComp/autoCompleteComp"
//Added by Aqib Mirza
import { JsonLottieComp } from "./comps/jsonComp/jsonLottieComp";

Expand Down Expand Up @@ -857,6 +859,15 @@ const uiCompMap: Registry = {
h: 55,
},
},
mention: {
name: trans("uiComp.mentionCompName"),
enName: "mention",
description: trans("uiComp.mentionCompDesc"),
categories: ["dataInputText"],
icon: MentionIcon,
keywords: trans("uiComp.mentionCompKeywords"),
comp: MentionComp,
},
autocomplete: {
name: trans("uiComp.autoCompleteCompName"),
enName: "autoComplete",
Expand Down
1 change: 1 addition & 0 deletions client/packages/lowcoder/src/comps/uiCompRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export type UICompType =
| "signature"
| "jsonLottie" //Added By Aqib Mirza
| "timeline"
| "mention"
| "autocomplete"

export const uiCompRegistry = {} as Record<UICompType | string, UICompManifest>;
Expand Down
8 changes: 8 additions & 0 deletions client/packages/lowcoder/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ export const en = {
parseDesc: "Triggers on parse",
success: "Success",
successDesc: "Triggers on success",
mention: "mention",
mentionDesc: "Triggers on mention",
},
themeDetail: {
primary: "Brand color",
Expand Down Expand Up @@ -845,6 +847,9 @@ export const en = {
timelineCompName: "Time Line",
timelineCompDesc: "Time Line",
timelineCompKeywords: "",
mentionCompName: "mention",
mentionCompDesc: "mention",
mentionCompKeywords: "",
autoCompleteCompName: "autoComplete",
autoCompleteCompDesc: "autoComplete",
autoCompleteCompKeywords: "",
Expand Down Expand Up @@ -2471,6 +2476,9 @@ export const en = {
clickedObjectDesc: "clicked item data",
clickedIndexDesc: "clicked item index",
},
mention:{
mentionList: "mention list",
},
autoComplete: {
value: "auto complete value",
checkedValueFrom: "checked value from",
Expand Down
Loading