Skip to content

Commit 3d672ff

Browse files
committed
Add mention component
1 parent ab68b67 commit 3d672ff

File tree

10 files changed

+320
-3
lines changed

10 files changed

+320
-3
lines changed
Lines changed: 1 addition & 0 deletions
Loading

client/packages/lowcoder-design/src/icons/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,5 @@ export { ReactComponent as ExpandIcon } from "icons/icon-expand.svg";
288288
export { ReactComponent as CompressIcon } from "icons/icon-compress.svg";
289289
export { ReactComponent as TableCellsIcon } from "icons/icon-table-cells.svg"; // Added By Aqib Mirza
290290
export { ReactComponent as TimeLineIcon } from "icons/icon-timeline-comp.svg"
291-
export { ReactComponent as LottieIcon } from "icons/icon-lottie.svg";
291+
export { ReactComponent as LottieIcon } from "icons/icon-lottie.svg";
292+
export { ReactComponent as MentionIcon } from "icons/icon-mention-comp.svg";
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import { useState, useEffect } from "react";
2+
import {
3+
NameConfig,
4+
NameConfigPlaceHolder,
5+
NameConfigRequired,
6+
withExposingConfigs,
7+
} from "comps/generators/withExposing";
8+
import { Section, sectionNames } from "lowcoder-design";
9+
import { BoolControl } from "../../controls/boolControl";
10+
import { AutoHeightControl } from "../../controls/autoHeightControl";
11+
import { UICompBuilder } from "../../generators";
12+
import { FormDataPropertyView } from "../formComp/formDataConstants";
13+
import {
14+
checkMentionListData,
15+
textInputChildren,
16+
} from "./textInputConstants";
17+
import {
18+
withMethodExposing,
19+
refMethods,
20+
} from "../../generators/withMethodExposing";
21+
import { styleControl } from "comps/controls/styleControl";
22+
import styled from "styled-components";
23+
import {
24+
InputLikeStyle,
25+
InputLikeStyleType,
26+
} from "comps/controls/styleControlConstants";
27+
import {
28+
disabledPropertyView,
29+
hiddenPropertyView,
30+
maxLengthPropertyView,
31+
minLengthPropertyView,
32+
readOnlyPropertyView,
33+
requiredPropertyView,
34+
} from "comps/utils/propertyUtils";
35+
import { booleanExposingStateControl } from "comps/controls/codeStateControl";
36+
import { trans } from "i18n";
37+
import { RefControl } from "comps/controls/refControl";
38+
import { TextAreaRef } from "antd/lib/input/TextArea";
39+
import { Mentions, ConfigProvider } from "antd";
40+
import { blurMethod, focusWithOptions } from "comps/utils/methodUtils";
41+
import type { MentionsOptionProps } from "antd/es/mentions";
42+
import {
43+
textInputValidate,
44+
} from "../textInputComp/textInputConstants";
45+
import { jsonControl } from "@lowcoder-ee/comps/controls/codeControl";
46+
// 事件控制
47+
import {
48+
submitEvent,
49+
eventHandlerControl,
50+
mentionEvent,
51+
focusEvent,
52+
blurEvent,
53+
changeEvent
54+
} from "comps/controls/eventHandlerControl";
55+
56+
const Wrapper = styled.div<{
57+
$style: InputLikeStyleType;
58+
}>`
59+
height: 100%;
60+
61+
.ant-input-clear-icon {
62+
opacity: 0.45;
63+
color: ${(props) => props.$style.text};
64+
top: 10px;
65+
66+
&:hover {
67+
opacity: 0.65;
68+
color: ${(props) => props.$style.text};
69+
}
70+
}
71+
`;
72+
73+
const EventOptions = [
74+
focusEvent,
75+
blurEvent,
76+
changeEvent,
77+
mentionEvent,
78+
submitEvent,
79+
] as const;
80+
81+
let MentionTmpComp = (function () {
82+
const childrenMap = {
83+
...textInputChildren,
84+
viewRef: RefControl<TextAreaRef>,
85+
allowClear: BoolControl,
86+
autoHeight: AutoHeightControl,
87+
style: styleControl(InputLikeStyle),
88+
mentionList: jsonControl(checkMentionListData, {
89+
"@": ["Li Lei", "Han Meimei"],
90+
"#": ["123", "456", "789"],
91+
}),
92+
onEvent: eventHandlerControl(EventOptions),
93+
invalid: booleanExposingStateControl("invalid"),
94+
};
95+
96+
return new UICompBuilder(childrenMap, (props) => {
97+
const { mentionList } = props;
98+
const [validateState, setvalidateState] = useState({});
99+
const [activationFlag, setActivationFlag] = useState(false);
100+
const [prefix, setPrefix] = useState<PrefixType>("@");
101+
type PrefixType = "@" | keyof typeof mentionList;
102+
103+
// 获取提及搜索关键字
104+
const onSearch = (_: string, newPrefix: PrefixType) => {
105+
setPrefix(newPrefix);
106+
};
107+
const onChange = (value: string) => {
108+
props.value.onChange(value);
109+
props.onEvent("change");
110+
};
111+
112+
const onPressEnter = (e: any) => {
113+
if (e.shiftKey) {
114+
e.preventDefault();
115+
props.onEvent("submit");
116+
}
117+
};
118+
119+
const onSelect = (option: MentionsOptionProps) => {
120+
props.onEvent("mention");
121+
};
122+
const getValidate = (value: any): "" | "warning" | "error" | undefined => {
123+
if (
124+
value.hasOwnProperty("validateStatus") &&
125+
value["validateStatus"] === "error"
126+
)
127+
return "error";
128+
return "";
129+
};
130+
131+
const getTextInputValidate = () => {
132+
return {
133+
value: { value: props.value.value },
134+
required: props.required,
135+
minLength: props?.minLength ?? 0,
136+
maxLength: props?.maxLength ?? 0,
137+
validationType: props.validationType,
138+
regex: props.regex,
139+
customRule: props.customRule,
140+
};
141+
};
142+
143+
useEffect(() => {
144+
if (activationFlag) {
145+
const temp = textInputValidate(getTextInputValidate());
146+
setvalidateState(temp);
147+
props.invalid.onChange(temp.validateStatus !== "");
148+
}
149+
}, [
150+
props.value.value,
151+
props.required,
152+
props?.minLength,
153+
props?.maxLength,
154+
props.validationType,
155+
props.regex,
156+
props.customRule,
157+
]);
158+
return props.label({
159+
required: props.required,
160+
children: (
161+
<Wrapper $style={props.style}>
162+
<ConfigProvider
163+
theme={{
164+
token: {
165+
colorBgContainer: props.style.background,
166+
colorBorder: props.style.border,
167+
borderRadius: parseInt(props.style.radius),
168+
colorText: props.style.text,
169+
colorPrimary: props.style.accent,
170+
},
171+
}}
172+
>
173+
<Mentions
174+
prefix={Object.keys(mentionList)}
175+
onFocus={() => {
176+
setActivationFlag(true);
177+
props.onEvent("focus");
178+
}}
179+
onBlur={() => props.onEvent("blur")}
180+
onPressEnter={onPressEnter}
181+
onSearch={onSearch}
182+
onChange={onChange}
183+
onSelect={onSelect}
184+
placeholder={props.placeholder}
185+
value={props.value.value}
186+
disabled={props.disabled}
187+
status={getValidate(validateState)}
188+
options={(mentionList[prefix] || []).map((value: string) => ({
189+
key: value,
190+
value,
191+
label: value,
192+
}))}
193+
autoSize={props.autoHeight}
194+
style={{ height: "100%", maxHeight: "100%", resize: "none" }}
195+
readOnly={props.readOnly}
196+
/>
197+
</ConfigProvider>
198+
</Wrapper>
199+
),
200+
style: props.style,
201+
...validateState,
202+
});
203+
})
204+
.setPropertyViewFn((children) => (
205+
<>
206+
<Section name={sectionNames.basic}>
207+
{children.mentionList.propertyView({
208+
label: trans("mention.mentionList"),
209+
})}
210+
{children.value.propertyView({ label: trans("prop.defaultValue") })}
211+
{children.placeholder.propertyView({
212+
label: trans("prop.placeholder"),
213+
})}
214+
</Section>
215+
<FormDataPropertyView {...children} />
216+
{children.label.getPropertyView()}
217+
218+
<Section name={sectionNames.interaction}>
219+
{children.onEvent.getPropertyView()}
220+
{disabledPropertyView(children)}
221+
</Section>
222+
223+
<Section name={sectionNames.advanced}>
224+
{readOnlyPropertyView(children)}
225+
</Section>
226+
227+
<Section name={sectionNames.validation}>
228+
{requiredPropertyView(children)}
229+
{children.validationType.propertyView({
230+
label: trans("prop.textType"),
231+
})}
232+
{minLengthPropertyView(children)}
233+
{maxLengthPropertyView(children)}
234+
{children.customRule.propertyView({})}
235+
</Section>
236+
237+
<Section name={sectionNames.layout}>
238+
{children.autoHeight.getPropertyView()}
239+
{hiddenPropertyView(children)}
240+
</Section>
241+
242+
<Section name={sectionNames.style}>
243+
{children.style.getPropertyView()}
244+
</Section>
245+
</>
246+
))
247+
.build();
248+
})();
249+
250+
MentionTmpComp = class extends MentionTmpComp {
251+
override autoHeight(): boolean {
252+
return this.children.autoHeight.getView();
253+
}
254+
};
255+
256+
const TextareaTmp2Comp = withMethodExposing(
257+
MentionTmpComp,
258+
refMethods([focusWithOptions, blurMethod])
259+
);
260+
261+
export const MentionComp = withExposingConfigs(TextareaTmp2Comp, [
262+
new NameConfig("value", trans("export.inputValueDesc")),
263+
NameConfigPlaceHolder,
264+
NameConfigRequired,
265+
new NameConfig("invalid", trans("export.invalidDesc")),
266+
new NameConfig("hidden", trans("export.hiddenDesc")),
267+
new NameConfig("disabled", trans("export.disabledDesc")),
268+
]);

client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BoolControl } from "comps/controls/boolControl";
2+
import { check } from "util/convertUtils";
23
import {
34
BoolCodeControl,
45
CustomRuleControl,
@@ -268,3 +269,14 @@ export const inputRefMethods = [
268269
(comp.children.viewRef.viewRef?.input?.setRangeText as any)?.(...params),
269270
},
270271
];
272+
273+
export function checkMentionListData(data: any) {
274+
if(data === "") return {}
275+
for(const key in data) {
276+
check(data[key], ["array"], key,(node)=>{
277+
check(node, ["string"], );
278+
return node
279+
})
280+
}
281+
return data
282+
}

client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,11 @@ export const successEvent: EventConfigType = {
305305
value: "success",
306306
description: trans("event.successDesc"),
307307
};
308+
export const mentionEvent: EventConfigType = {
309+
label: trans("event.mention"),
310+
value: "mention",
311+
description: trans("event.mentionDesc"),
312+
};
308313

309314
export const InputEventHandlerControl = eventHandlerControl([
310315
changeEvent,

client/packages/lowcoder/src/comps/index.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import {
9393
VideoCompIcon,
9494
TimeLineIcon,
9595
LottieIcon,
96+
MentionIcon,
9697
} from "lowcoder-design";
9798

9899
import { defaultFormData, FormComp } from "./comps/formComp/formComp";
@@ -119,6 +120,7 @@ import { RemoteCompInfo } from "types/remoteComp";
119120
import { ScannerComp } from "./comps/buttonComp/scannerComp";
120121
import { SignatureComp } from "./comps/signatureComp";
121122
import { TimeLineComp } from "./comps/timelineComp/timelineComp";
123+
import { MentionComp } from "./comps/textInputComp/mentionComp";
122124

123125
//Added by Aqib Mirza
124126
import { JsonLottieComp } from "./comps/jsonComp/jsonLottieComp";
@@ -855,6 +857,15 @@ const uiCompMap: Registry = {
855857
h: 55,
856858
},
857859
},
860+
mention: {
861+
name: trans("uiComp.mentionCompName"),
862+
enName: "mention",
863+
description: trans("uiComp.mentionCompDesc"),
864+
categories: ["dataInputText"],
865+
icon: MentionIcon,
866+
keywords: trans("uiComp.mentionCompKeywords"),
867+
comp: MentionComp,
868+
},
858869
};
859870

860871
export function loadComps() {

client/packages/lowcoder/src/comps/uiCompRegistry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export type UICompType =
112112
| "signature"
113113
| "jsonLottie" //Added By Aqib Mirza
114114
| "timeline"
115+
| "mention"
115116

116117
export const uiCompRegistry = {} as Record<UICompType | string, UICompManifest>;
117118

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ export const en = {
262262
parseDesc: "Triggers on parse",
263263
success: "Success",
264264
successDesc: "Triggers on success",
265+
mention: "mention",
266+
mentionDesc: "Triggers on mention",
265267
},
266268
themeDetail: {
267269
primary: "Brand color",
@@ -845,6 +847,9 @@ export const en = {
845847
timelineCompName: "Time Line",
846848
timelineCompDesc: "Time Line",
847849
timelineCompKeywords: "",
850+
mentionCompName: "mention",
851+
mentionCompDesc: "mention",
852+
mentionCompKeywords: "",
848853
},
849854
comp: {
850855
menuViewDocs: "View documentation",
@@ -2467,5 +2472,8 @@ export const en = {
24672472
valueDesc: "data of timeline",
24682473
clickedObjectDesc: "clicked item data",
24692474
clickedIndexDesc: "clicked item index",
2470-
}
2475+
},
2476+
mention:{
2477+
mentionList: "mention list",
2478+
},
24712479
};

0 commit comments

Comments
 (0)