Skip to content

Commit b7f1d33

Browse files
Merge pull request #1818 from kamalqureshi/tags_component
Tags component
2 parents 12f2d45 + 3ede19e commit b7f1d33

File tree

11 files changed

+340
-8
lines changed

11 files changed

+340
-8
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ export { ReactComponent as VideoCameraStreamCompIconSmall } from "./v2/camera-st
355355
export { ReactComponent as VideoScreenshareCompIconSmall } from "./v2/screen-share-stream-s.svg"; // new
356356
export { ReactComponent as SignatureCompIconSmall } from "./v2/signature-s.svg";
357357
export { ReactComponent as StepCompIconSmall } from "./v2/steps-s.svg";
358+
export { ReactComponent as TagsCompIconSmall } from "./v2/tags-s.svg"
358359

359360

360361
export { ReactComponent as CandlestickChartCompIconSmall } from "./v2/candlestick-chart-s.svg"; // new
@@ -468,6 +469,7 @@ export { ReactComponent as SignatureCompIcon } from "./v2/signature-m.svg";
468469
export { ReactComponent as GanttCompIcon } from "./v2/gantt-chart-m.svg";
469470
export { ReactComponent as KanbanCompIconSmall } from "./v2/kanban-s.svg";
470471
export { ReactComponent as KanbanCompIcon } from "./v2/kanban-m.svg";
472+
export { ReactComponent as TagsCompIcon } from "./v2/tags-l.svg";
471473

472474
export { ReactComponent as CandlestickChartCompIcon } from "./v2/candlestick-chart-m.svg";
473475
export { ReactComponent as FunnelChartCompIcon } from "./v2/funnel-chart-m.svg";
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading

client/packages/lowcoder/src/comps/comps/selectInputComp/multiSelectComp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ let MultiSelectBasicComp = (function () {
3030
padding: PaddingControl,
3131
};
3232
return new UICompBuilder(childrenMap, (props, dispatch) => {
33-
const valueSet = new Set<any>(props.options.map((o) => o.value)); // Filter illegal default values entered by the user
33+
const valueSet = new Set<any>((props.options as any[]).map((o: any) => o.value)); // Filter illegal default values entered by the user
3434
const [
3535
validateState,
3636
handleChange,

client/packages/lowcoder/src/comps/comps/selectInputComp/selectComp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ let SelectBasicComp = (function () {
3939
const propsRef = useRef<RecordConstructorToView<typeof childrenMap>>(props);
4040
propsRef.current = props;
4141

42-
const valueSet = new Set<any>(props.options.map((o) => o.value)); // Filter illegal default values entered by the user
42+
const valueSet = new Set<any>((props.options as any[]).map((o: any) => o.value)); // Filter illegal default values entered by the user
4343

4444
return props.label({
4545
required: props.required,
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import {
2+
BoolCodeControl,
3+
ButtonEventHandlerControl,
4+
InputLikeStyle,
5+
NameConfig,
6+
Section,
7+
UICompBuilder,
8+
hiddenPropertyView,
9+
sectionNames,
10+
showDataLoadingIndicatorsPropertyView,
11+
styleControl,
12+
withExposingConfigs
13+
} from "@lowcoder-ee/index.sdk";
14+
import styled from "styled-components";
15+
import React, { useContext } from "react";
16+
import { trans } from "i18n";
17+
import { Tag } from "antd";
18+
import { EditorContext } from "comps/editorState";
19+
import { PresetStatusColorTypes } from "antd/es/_util/colors";
20+
import { hashToNum } from "util/stringUtils";
21+
import { TagsCompOptionsControl } from "comps/controls/optionsControl";
22+
import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler";
23+
24+
const colors = PresetStatusColorTypes;
25+
26+
// These functions are used for individual tag styling
27+
function getTagColor(tagText : any, tagOptions: any[]) {
28+
const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText);
29+
if (foundOption) {
30+
if (foundOption.colorType === "preset") {
31+
return foundOption.presetColor;
32+
} else if (foundOption.colorType === "custom") {
33+
return undefined;
34+
}
35+
return foundOption.color;
36+
}
37+
const index = Math.abs(hashToNum(tagText)) % colors.length;
38+
return colors[index];
39+
}
40+
41+
const getTagStyle = (tagText: any, tagOptions: any[], baseStyle: any = {}) => {
42+
const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText);
43+
if (foundOption) {
44+
const style: any = { ...baseStyle };
45+
46+
if (foundOption.colorType === "custom") {
47+
style.backgroundColor = foundOption.color;
48+
style.color = foundOption.textColor;
49+
style.border = `1px solid ${foundOption.color}`;
50+
}
51+
52+
if (foundOption.border) {
53+
style.borderColor = foundOption.border;
54+
if (!foundOption.colorType || foundOption.colorType !== "custom") {
55+
style.border = `1px solid ${foundOption.border}`;
56+
}
57+
}
58+
59+
if (foundOption.radius) {
60+
style.borderRadius = foundOption.radius;
61+
}
62+
63+
if (foundOption.margin) {
64+
style.margin = foundOption.margin;
65+
}
66+
67+
if (foundOption.padding) {
68+
style.padding = foundOption.padding;
69+
}
70+
71+
return style;
72+
}
73+
return baseStyle;
74+
};
75+
76+
function getTagIcon(tagText: any, tagOptions: any[]) {
77+
const foundOption = tagOptions.find(option => option.label === tagText);
78+
return foundOption ? foundOption.icon : undefined;
79+
}
80+
81+
const multiTags = (function () {
82+
83+
const StyledTag = styled(Tag)<{ $style: any, $bordered: boolean, $customStyle: any }>`
84+
display: flex;
85+
justify-content: center;
86+
align-items: center;
87+
width: 100%;
88+
background: ${(props) => props.$customStyle?.backgroundColor || props.$style?.background};
89+
color: ${(props) => props.$customStyle?.color || props.$style?.text};
90+
border-radius: ${(props) => props.$customStyle?.borderRadius || props.$style?.borderRadius};
91+
border: ${(props) => {
92+
if (props.$customStyle?.border) return props.$customStyle.border;
93+
return props.$bordered ? `${props.$style?.borderStyle} ${props.$style?.borderWidth} ${props.$style?.border}` : 'none';
94+
}};
95+
padding: ${(props) => props.$customStyle?.padding || props.$style?.padding};
96+
margin: ${(props) => props.$customStyle?.margin || props.$style?.margin};
97+
font-size: ${(props) => props.$style?.textSize};
98+
font-weight: ${(props) => props.$style?.fontWeight};
99+
cursor: pointer;
100+
`;
101+
102+
const StyledTagContainer = styled.div`
103+
display: flex;
104+
gap: 5px;
105+
padding: 5px;
106+
`;
107+
108+
const childrenMap = {
109+
options: TagsCompOptionsControl,
110+
style: styleControl(InputLikeStyle, 'style'),
111+
onEvent: ButtonEventHandlerControl,
112+
borderless: BoolCodeControl,
113+
enableIndividualStyling: BoolCodeControl,
114+
};
115+
116+
return new UICompBuilder(childrenMap, (props) => {
117+
const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent});
118+
119+
return (
120+
<StyledTagContainer>
121+
{props.options.map((tag, index) => {
122+
123+
// Use individual styling only if enableIndividualStyling is true
124+
const tagColor = props.enableIndividualStyling ? getTagColor(tag.label, props.options) : undefined;
125+
const tagIcon = props.enableIndividualStyling ? getTagIcon(tag.label, props.options) : tag.icon;
126+
const tagStyle = props.enableIndividualStyling ? getTagStyle(tag.label, props.options, props.style) : {};
127+
128+
return (
129+
<StyledTag
130+
key={`tag-${index}`}
131+
$style={props.style}
132+
$bordered={!props.borderless}
133+
$customStyle={tagStyle}
134+
icon={tagIcon}
135+
color={tagColor}
136+
onClick={() => handleClickEvent()}
137+
>
138+
{tag.label}
139+
</StyledTag>
140+
);
141+
})}
142+
</StyledTagContainer>
143+
);
144+
})
145+
.setPropertyViewFn((children: any) => {
146+
return (
147+
<>
148+
<Section name="Basic">
149+
{children.options.propertyView({})}
150+
</Section>
151+
152+
{["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (
153+
<Section name={sectionNames.interaction}>
154+
{children.onEvent.getPropertyView()}
155+
{hiddenPropertyView(children)}
156+
{showDataLoadingIndicatorsPropertyView(children)}
157+
</Section>
158+
)}
159+
160+
{["layout", "both"].includes(
161+
useContext(EditorContext).editorModeStatus
162+
) && (
163+
<Section name={sectionNames.style}>
164+
{children.enableIndividualStyling.propertyView({
165+
label: trans("style.individualStyling"),
166+
tooltip: trans("style.individualStylingTooltip")
167+
})}
168+
{children.borderless.propertyView({ label: trans("style.borderless") })}
169+
{children.style.getPropertyView()}
170+
</Section>
171+
)}
172+
</>
173+
)
174+
})
175+
.build();
176+
})()
177+
178+
export const MultiTagsComp = withExposingConfigs(multiTags, [new NameConfig("options", "")]);
179+

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

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ const TabsOption = new MultiCompBuilder(
557557
</>
558558
))
559559
.build();
560+
560561

561562
export const TabsOptionControl = manualOptionsControl(TabsOption, {
562563
initOptions: [
@@ -567,6 +568,37 @@ export const TabsOptionControl = manualOptionsControl(TabsOption, {
567568
autoIncField: "id",
568569
});
569570

571+
const TagsOption = new MultiCompBuilder(
572+
{
573+
id: valueComp<number>(-1),
574+
label: StringControl,
575+
icon: IconControl,
576+
iconPosition: withDefault(LeftRightControl, "left"),
577+
hidden: BoolCodeControl,
578+
},
579+
(props) => props
580+
)
581+
.setPropertyViewFn((children) => (
582+
<>
583+
{children.label.propertyView({ label: trans("label") })}
584+
{children.icon.propertyView({ label: trans("icon") })}
585+
{children.iconPosition.propertyView({
586+
label: trans("tabbedContainer.iconPosition"),
587+
radioButton: true,
588+
})}
589+
{hiddenPropertyView(children)}
590+
</>
591+
))
592+
.build();
593+
594+
export const TagsOptionControl = optionsControl(TagsOption, {
595+
initOptions: [
596+
{ id: 0, label: "Option 1" },
597+
{ id: 1, label: "Option 2" },
598+
],
599+
autoIncField: "id",
600+
});
601+
570602
const StyledIcon = styled.span`
571603
margin: 0 4px 0 14px;
572604
`;
@@ -750,14 +782,83 @@ export const StepOptionControl = optionsControl(StepOption, {
750782
uniqField: "label",
751783
});
752784

785+
let TagsCompOptions = new MultiCompBuilder(
786+
{
787+
label: StringControl,
788+
icon: IconControl,
789+
colorType: withDefault(dropdownControl([
790+
{ label: trans("style.preset"), value: "preset" },
791+
{ label: trans("style.custom"), value: "custom" },
792+
] as const, "preset"), "preset"),
793+
presetColor: withDefault(dropdownControl(TAG_PRESET_COLORS, "blue"), "blue"),
794+
color: withDefault(ColorControl, "#1890ff"),
795+
textColor: withDefault(ColorControl, "#ffffff"),
796+
border: withDefault(ColorControl, ""),
797+
radius: withDefault(RadiusControl, ""),
798+
margin: withDefault(StringControl, ""),
799+
padding: withDefault(StringControl, ""),
800+
},
801+
(props) => props
802+
).build();
803+
804+
TagsCompOptions = class extends TagsCompOptions implements OptionCompProperty {
805+
propertyView(param: { autoMap?: boolean }) {
806+
const colorType = this.children.colorType.getView();
807+
return (
808+
<>
809+
{this.children.label.propertyView({ label: trans("coloredTagOptionControl.tag") })}
810+
{this.children.icon.propertyView({ label: trans("coloredTagOptionControl.icon") })}
811+
{this.children.colorType.propertyView({
812+
label: trans("style.colorType"),
813+
radioButton: true
814+
})}
815+
{colorType === "preset" && this.children.presetColor.propertyView({
816+
label: trans("style.presetColor")
817+
})}
818+
{colorType === "custom" && (
819+
<>
820+
{this.children.color.propertyView({ label: trans("coloredTagOptionControl.color") })}
821+
{this.children.textColor.propertyView({ label: trans("style.textColor") })}
822+
</>
823+
)}
824+
{this.children.border.propertyView({
825+
label: trans('style.border')
826+
})}
827+
{this.children.radius.propertyView({
828+
label: trans('style.borderRadius'),
829+
preInputNode: <StyledIcon as={IconRadius} title="" />,
830+
placeholder: '3px',
831+
})}
832+
{this.children.margin.propertyView({
833+
label: trans('style.margin'),
834+
preInputNode: <StyledIcon as={ExpandIcon} title="" />,
835+
placeholder: '3px',
836+
})}
837+
{this.children.padding.propertyView({
838+
label: trans('style.padding'),
839+
preInputNode: <StyledIcon as={CompressIcon} title="" />,
840+
placeholder: '3px',
841+
})}
842+
</>
843+
);
844+
}
845+
};
846+
847+
export const TagsCompOptionsControl = optionsControl(TagsCompOptions, {
848+
initOptions: [
849+
{ label: "Option 1", colorType: "preset", presetColor: "blue" },
850+
{ label: "Option 2", colorType: "preset", presetColor: "green" }
851+
],
852+
uniqField: "label",
853+
});
753854

754855
let ColoredTagOption = new MultiCompBuilder(
755856
{
756857
label: StringControl,
757858
icon: IconControl,
758859
colorType: withDefault(dropdownControl([
759-
{ label: "Preset", value: "preset" },
760-
{ label: "Custom", value: "custom" },
860+
{ label: trans("style.preset"), value: "preset" },
861+
{ label: trans("style.custom"), value: "custom" },
761862
] as const, "preset"), "preset"),
762863
presetColor: withDefault(dropdownControl(TAG_PRESET_COLORS, "blue"), "blue"),
763864
color: withDefault(ColorControl, "#1890ff"),
@@ -779,16 +880,16 @@ ColoredTagOption = class extends ColoredTagOption implements OptionCompProperty
779880
{this.children.label.propertyView({ label: trans("coloredTagOptionControl.tag") })}
780881
{this.children.icon.propertyView({ label: trans("coloredTagOptionControl.icon") })}
781882
{this.children.colorType.propertyView({
782-
label: "Color Type",
883+
label: trans("style.colorType"),
783884
radioButton: true
784885
})}
785886
{colorType === "preset" && this.children.presetColor.propertyView({
786-
label: "Preset Color"
887+
label: trans("style.presetColor")
787888
})}
788889
{colorType === "custom" && (
789890
<>
790891
{this.children.color.propertyView({ label: trans("coloredTagOptionControl.color") })}
791-
{this.children.textColor.propertyView({ label: "Text Color" })}
892+
{this.children.textColor.propertyView({ label: trans("style.textColor") })}
792893
</>
793894
)}
794895
{this.children.border.propertyView({

0 commit comments

Comments
 (0)