Skip to content

Commit 928c085

Browse files
authored
Merge pull request #338 from mousheng/Icons-Expansion
Icons expansion
2 parents 83442f4 + 98d5c05 commit 928c085

File tree

13 files changed

+324
-31
lines changed

13 files changed

+324
-31
lines changed

client/packages/lowcoder-design/src/components/iconSelect/index.tsx

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
22
import type { IconDefinition } from "@fortawesome/free-regular-svg-icons";
3-
import { default as Popover } from "antd/es/popover";
4-
import { ActionType } from '@rc-component/trigger/lib/interface';
3+
import { Popover } from "antd";
4+
import { ActionType } from "@rc-component/trigger/lib/interface";
55
import { TacoInput } from "components/tacoInput";
66
import { Tooltip } from "components/toolTip";
77
import { trans } from "i18n/design";
88
import _ from "lodash";
9-
import { ReactNode, useEffect, useCallback, useMemo, useRef, useState } from "react";
9+
import {
10+
ReactNode,
11+
useEffect,
12+
useCallback,
13+
useMemo,
14+
useRef,
15+
useState,
16+
} from "react";
1017
import Draggable from "react-draggable";
1118
import { default as List, ListRowProps } from "react-virtualized/dist/es/List";
1219
import styled from "styled-components";
1320
import { CloseIcon, SearchIcon } from "icons";
21+
import { ANTDICON } from "../../../../lowcoder/src/comps/comps/timelineComp/antIcon";
1422

1523
const PopupContainer = styled.div`
1624
width: 408px;
@@ -110,11 +118,23 @@ const IconItemContainer = styled.div`
110118

111119
class Icon {
112120
readonly title: string;
113-
constructor(readonly def: IconDefinition, readonly names: string[]) {
114-
this.title = def.iconName.split("-").map(_.upperFirst).join(" ");
121+
constructor(readonly def: IconDefinition | any, readonly names: string[]) {
122+
if (def?.iconName) {
123+
this.title = def.iconName.split("-").map(_.upperFirst).join(" ");
124+
} else {
125+
this.title = names[0].slice(5);
126+
this.def = def;
127+
}
115128
}
116129
getView() {
117-
return <FontAwesomeIcon icon={this.def} style={{ width: "1em", height: "1em" }} />;
130+
if (this.names[0]?.startsWith("antd/")) return this.def;
131+
else
132+
return (
133+
<FontAwesomeIcon
134+
icon={this.def}
135+
style={{ width: "1em", height: "1em" }}
136+
/>
137+
);
118138
}
119139
}
120140

@@ -144,14 +164,25 @@ async function getAllIcons() {
144164
}
145165
}
146166
}
167+
//append ant icon
168+
for (let key of Object.keys(ANTDICON)) {
169+
ret["antd/" + key] = new Icon(
170+
ANTDICON[key.toLowerCase() as keyof typeof ANTDICON],
171+
["antd/" + key]
172+
);
173+
}
147174
allIcons = ret;
148175
return ret;
149176
}
150177

151178
export const iconPrefix = "/icon:";
152179

153180
export function removeQuote(value?: string) {
154-
return value ? (value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value) : "";
181+
return value
182+
? value.startsWith('"') && value.endsWith('"')
183+
? value.slice(1, -1)
184+
: value
185+
: "";
155186
}
156187

157188
function getIconKey(value?: string) {
@@ -171,7 +202,8 @@ export function useIcon(value?: string) {
171202
function search(
172203
allIcons: Record<string, Icon>,
173204
searchText: string,
174-
searchKeywords?: Record<string, string>
205+
searchKeywords?: Record<string, string>,
206+
IconType?: "OnlyAntd" | "All" | "default" | undefined
175207
) {
176208
const tokens = searchText
177209
.toLowerCase()
@@ -182,6 +214,8 @@ function search(
182214
if (icon.names.length === 0) {
183215
return false;
184216
}
217+
if (IconType === "OnlyAntd" && !key.startsWith("antd/")) return false;
218+
if (IconType === "default" && key.startsWith("antd/")) return false;
185219
let text = icon.names
186220
.flatMap((name) => [name, searchKeywords?.[name]])
187221
.filter((t) => t)
@@ -198,16 +232,20 @@ const IconPopup = (props: {
198232
label?: ReactNode;
199233
onClose: () => void;
200234
searchKeywords?: Record<string, string>;
235+
IconType?: "OnlyAntd" | "All" | "default" | undefined;
201236
}) => {
202237
const [searchText, setSearchText] = useState("");
203238
const [allIcons, setAllIcons] = useState<Record<string, Icon>>({});
204239
const searchResults = useMemo(
205-
() => search(allIcons, searchText, props.searchKeywords),
240+
() => search(allIcons, searchText, props.searchKeywords, props.IconType),
206241
[searchText, allIcons]
207242
);
208243
const onChangeRef = useRef(props.onChange);
209244
onChangeRef.current = props.onChange;
210-
const onChangeIcon = useCallback((key: string) => onChangeRef.current(iconPrefix + key), []);
245+
const onChangeIcon = useCallback(
246+
(key: string) => onChangeRef.current(iconPrefix + key),
247+
[]
248+
);
211249
const columnNum = 8;
212250

213251
useEffect(() => {
@@ -217,24 +255,26 @@ const IconPopup = (props: {
217255
const rowRenderer = useCallback(
218256
(p: ListRowProps) => (
219257
<IconRow key={p.key} style={p.style}>
220-
{searchResults.slice(p.index * columnNum, (p.index + 1) * columnNum).map(([key, icon]) => (
221-
<Tooltip
222-
key={key}
223-
title={icon.title}
224-
placement="bottom"
225-
align={{ offset: [0, -7, 0, 0] }}
226-
destroyTooltipOnHide
227-
>
228-
<IconItemContainer
229-
tabIndex={0}
230-
onClick={() => {
231-
onChangeIcon(key);
232-
}}
258+
{searchResults
259+
.slice(p.index * columnNum, (p.index + 1) * columnNum)
260+
.map(([key, icon]) => (
261+
<Tooltip
262+
key={key}
263+
title={icon.title}
264+
placement="bottom"
265+
align={{ offset: [0, -7, 0, 0] }}
266+
destroyTooltipOnHide
233267
>
234-
{icon.getView()}
235-
</IconItemContainer>
236-
</Tooltip>
237-
))}
268+
<IconItemContainer
269+
tabIndex={0}
270+
onClick={() => {
271+
onChangeIcon(key);
272+
}}
273+
>
274+
{icon.getView()}
275+
</IconItemContainer>
276+
</Tooltip>
277+
))}
238278
</IconRow>
239279
),
240280
[searchResults, allIcons, onChangeIcon]
@@ -279,6 +319,7 @@ export const IconSelectBase = (props: {
279319
leftOffset?: number;
280320
parent?: HTMLElement | null;
281321
searchKeywords?: Record<string, string>;
322+
IconType?: "OnlyAntd" | "All" | "default" | undefined;
282323
}) => {
283324
const { setVisible, parent } = props;
284325
return (
@@ -290,7 +331,11 @@ export const IconSelectBase = (props: {
290331
onOpenChange={setVisible}
291332
getPopupContainer={parent ? () => parent : undefined}
292333
// hide the original background when dragging the popover is allowed
293-
overlayInnerStyle={{ border: "none", boxShadow: "none", background: "transparent" }}
334+
overlayInnerStyle={{
335+
border: "none",
336+
boxShadow: "none",
337+
background: "transparent",
338+
}}
294339
// when dragging is allowed, always re-location to avoid the popover exceeds the screen
295340
destroyTooltipOnHide
296341
content={
@@ -299,6 +344,7 @@ export const IconSelectBase = (props: {
299344
label={props.label}
300345
onClose={() => setVisible?.(false)}
301346
searchKeywords={props.searchKeywords}
347+
IconType={props.IconType}
302348
/>
303349
}
304350
>
@@ -312,6 +358,7 @@ export const IconSelect = (props: {
312358
label?: ReactNode;
313359
children?: ReactNode;
314360
searchKeywords?: Record<string, string>;
361+
IconType?: "OnlyAntd" | "All" | "default" | undefined;
315362
}) => {
316363
const [visible, setVisible] = useState(false);
317364
return (
Lines changed: 1 addition & 0 deletions
Loading

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,28 @@ export { ReactComponent as SignatureIcon } from "./icon-signature.svg";
278278
export { ReactComponent as ManualIcon } from "./icon-manual.svg";
279279
export { ReactComponent as WarnIcon } from "./icon-warn.svg";
280280
export { ReactComponent as SyncManualIcon } from "./icon-sync-manual.svg";
281+
282+
export { ReactComponent as DangerIcon } from "icons/icon-danger.svg";
283+
export { ReactComponent as TableMinusIcon } from "icons/icon-table-minus.svg";
284+
export { ReactComponent as TablePlusIcon } from "icons/icon-table-plus.svg";
285+
export { ReactComponent as MobileAppIcon } from "icons/icon-mobile-app.svg";
286+
export { ReactComponent as MobileNavIcon } from "icons/icon-navigation-mobile.svg";
287+
export { ReactComponent as PcNavIcon } from "icons/icon-navigation-pc.svg";
288+
export { ReactComponent as UnLockIcon } from "icons/icon-unlock.svg";
289+
export { ReactComponent as CalendarDeleteIcon } from "icons/icon-calendar-delete.svg";
290+
export { ReactComponent as TableCheckedIcon } from "icons/icon-table-checked.svg";
291+
export { ReactComponent as TableUnCheckedIcon } from "icons/icon-table-boolean-false.svg";
292+
export { ReactComponent as FileFolderIcon } from "icons/icon-editor-folder.svg";
293+
export { ReactComponent as ExpandIcon } from "icons/icon-expand.svg";
294+
export { ReactComponent as CompressIcon } from "icons/icon-compress.svg";
295+
export { ReactComponent as TableCellsIcon } from "icons/icon-table-cells.svg"; // Added By Aqib Mirza
296+
export { ReactComponent as TimeLineIcon } from "icons/icon-timeline-comp.svg"
297+
export { ReactComponent as LottieIcon } from "icons/icon-lottie.svg";
298+
export { ReactComponent as MentionIcon } from "icons/icon-mention-comp.svg";
299+
export { ReactComponent as AutoCompleteCompIcon } from "icons/icon-autocomplete-comp.svg";
300+
301+
export { ReactComponent as IconCompIcon } from "icons/IconCompIcon.svg";
302+
281303
export { ReactComponent as DangerIcon } from "./icon-danger.svg";
282304
export { ReactComponent as TableMinusIcon } from "./icon-table-minus.svg";
283305
export { ReactComponent as TablePlusIcon } from "./icon-table-plus.svg";
@@ -612,4 +634,5 @@ export { ReactComponent as MentionIcon } from "./icon-mention-comp.svg";
612634
export { ReactComponent as AutoCompleteCompIcon } from "./icon-autocomplete-comp.svg";
613635
export { ReactComponent as WidthIcon } from "./icon-width.svg";
614636
export { ReactComponent as ResponsiveLayoutCompIcon } from "./remix/layout-column-line.svg"; // Closest match for responsive layout component
615-
export { ReactComponent as TextSizeIcon } from "./icon-text-size.svg"; */
637+
export { ReactComponent as TextSizeIcon } from "./icon-text-size.svg"; */
638+
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { useEffect, useRef, useState } from "react";
2+
import styled, { css } from "styled-components";
3+
import { RecordConstructorToView } from "lowcoder-core";
4+
import { styleControl } from "comps/controls/styleControl";
5+
import _ from "lodash";
6+
import {
7+
IconStyle,
8+
IconStyleType,
9+
heightCalculator,
10+
widthCalculator,
11+
} from "comps/controls/styleControlConstants";
12+
import { UICompBuilder } from "comps/generators/uiCompBuilder";
13+
import { withDefault } from "../generators";
14+
import {
15+
NameConfigHidden,
16+
withExposingConfigs,
17+
} from "comps/generators/withExposing";
18+
import { Section, sectionNames } from "lowcoder-design";
19+
import { hiddenPropertyView } from "comps/utils/propertyUtils";
20+
import { trans } from "i18n";
21+
import { NumberControl } from "comps/controls/codeControl";
22+
import { IconControl } from "comps/controls/iconControl";
23+
import ReactResizeDetector from "react-resize-detector";
24+
import { AutoHeightControl } from "../controls/autoHeightControl";
25+
import {
26+
clickEvent,
27+
eventHandlerControl,
28+
} from "../controls/eventHandlerControl";
29+
30+
const Container = styled.div<{ $style: IconStyleType | undefined }>`
31+
height: 100%;
32+
width: 100%;
33+
display: flex;
34+
align-items: center;
35+
justify-content: center;
36+
svg {
37+
object-fit: contain;
38+
pointer-events: auto;
39+
}
40+
${(props) => props.$style && getStyle(props.$style)}
41+
`;
42+
43+
const getStyle = (style: IconStyleType) => {
44+
return css`
45+
svg {
46+
color: ${style.fill};
47+
}
48+
padding: ${style.padding};
49+
border: 1px solid ${style.border};
50+
border-radius: ${style.radius};
51+
margin: ${style.margin};
52+
max-width: ${widthCalculator(style.margin)};
53+
max-height: ${heightCalculator(style.margin)};
54+
`;
55+
};
56+
57+
const EventOptions = [clickEvent] as const;
58+
59+
const childrenMap = {
60+
style: styleControl(IconStyle),
61+
icon: withDefault(IconControl, "/icon:antd/homefilled"),
62+
autoHeight: withDefault(AutoHeightControl, "auto"),
63+
iconSize: withDefault(NumberControl, 20),
64+
onEvent: eventHandlerControl(EventOptions),
65+
};
66+
67+
const IconView = (props: RecordConstructorToView<typeof childrenMap>) => {
68+
const conRef = useRef<HTMLDivElement>(null);
69+
const [width, setWidth] = useState(0);
70+
const [height, setHeight] = useState(0);
71+
72+
useEffect(() => {
73+
if (height && width) {
74+
onResize();
75+
}
76+
}, [height, width]);
77+
78+
const onResize = () => {
79+
const container = conRef.current;
80+
setWidth(container?.clientWidth ?? 0);
81+
setHeight(container?.clientHeight ?? 0);
82+
};
83+
84+
return (
85+
<ReactResizeDetector onResize={onResize}>
86+
<Container
87+
ref={conRef}
88+
$style={props.style}
89+
style={{
90+
fontSize: props.autoHeight
91+
? `${height < width ? height : width}px`
92+
: props.iconSize,
93+
background: props.style.background,
94+
}}
95+
onClick={() => props.onEvent("click")}
96+
>
97+
{props.icon}
98+
</Container>
99+
</ReactResizeDetector>
100+
);
101+
};
102+
103+
let IconBasicComp = (function () {
104+
return new UICompBuilder(childrenMap, (props) => <IconView {...props} />)
105+
.setPropertyViewFn((children) => (
106+
<>
107+
<Section name={sectionNames.basic}>
108+
{children.icon.propertyView({
109+
label: trans("iconComp.icon"),
110+
IconType: "All",
111+
})}
112+
{children.autoHeight.propertyView({
113+
label: trans("iconComp.autoSize"),
114+
})}
115+
{!children.autoHeight.getView() &&
116+
children.iconSize.propertyView({
117+
label: trans("iconComp.iconSize"),
118+
})}
119+
</Section>
120+
<Section name={sectionNames.interaction}>
121+
{children.onEvent.getPropertyView()}
122+
</Section>
123+
<Section name={sectionNames.layout}>
124+
{hiddenPropertyView(children)}
125+
</Section>
126+
<Section name={sectionNames.style}>
127+
{children.style.getPropertyView()}
128+
</Section>
129+
</>
130+
))
131+
.build();
132+
})();
133+
134+
IconBasicComp = class extends IconBasicComp {
135+
override autoHeight(): boolean {
136+
return false;
137+
}
138+
};
139+
140+
export const IconComp = withExposingConfigs(IconBasicComp, [
141+
NameConfigHidden,
142+
]);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface ControlParams extends CodeEditorControlParams {
1818
preInputNode?: ReactNode;
1919
childrenWrapperStyle?: CSSProperties;
2020
extraChildren?: ReactNode;
21+
IconType?: "OnlyAntd" | "All" | "default" | undefined;
2122
}
2223

2324
export interface ControlType {

0 commit comments

Comments
 (0)