Skip to content

Icon Component #703

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 10 commits into from
Feb 22, 2024
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
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"chalk": "4",
"number-precision": "^1.6.0",
"react-player": "^2.11.0",
"remixicon-react": "^1.0.0",
"tui-image-editor": "^3.15.3"
}
}
150 changes: 112 additions & 38 deletions client/packages/lowcoder-design/src/components/iconSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import type { IconDefinition } from "@fortawesome/free-regular-svg-icons";
import { default as Popover } from "antd/es/popover";
import { ActionType } from '@rc-component/trigger/lib/interface';
// import type { IconDefinition as IconDefinitionBrands } from "@fortawesome/free-brands-svg-icons";
import { Popover } from "antd";
import { ActionType } from "@rc-component/trigger/lib/interface";
import { TacoInput } from "components/tacoInput";
import { Tooltip } from "components/toolTip";
import { trans } from "i18n/design";
import _ from "lodash";
import { ReactNode, useEffect, useCallback, useMemo, useRef, useState } from "react";
import {
ReactNode,
useEffect,
useCallback,
useMemo,
useRef,
useState,
} from "react";
import Draggable from "react-draggable";
import { default as List, ListRowProps } from "react-virtualized/dist/es/List";
import styled from "styled-components";
import { CloseIcon, SearchIcon } from "icons";
import { ANTDICON } from "icons/antIcon";
import { Divider } from "antd-mobile";

const PopupContainer = styled.div`
width: 408px;
width: 580px;
background: #ffffff;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
border-radius: 8px;
Expand Down Expand Up @@ -76,25 +86,28 @@ const IconList = styled(List)`
background-color: rgba(139, 143, 163, 0.36);
}
`;

const IconRow = styled.div`
padding: 0 6px;
display: flex;
align-items: center;
align-items: flex-start; /* Align items to the start to allow different heights */
justify-content: space-between;

&:last-child {
gap: 8px;
justify-content: flex-start;
}
`;

const IconItemContainer = styled.div`
width: 40px;
height: 40px;
width: 60px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
align-items: center;
justify-content: flex-start;
cursor: pointer;
font-size: 20px;
font-size: 28px;
margin-bottom: 24px;

&:hover {
border: 1px solid #315efb;
Expand All @@ -108,13 +121,41 @@ const IconItemContainer = styled.div`
}
`;

const IconWrapper = styled.div`
height: auto;
display: flex;
align-items: center;
justify-content: center;
`;

const IconKeyDisplay = styled.div`
font-size: 8px;
color: #8b8fa3;
margin-top: 4px; /* Space between the icon and the text */
text-align: center;
word-wrap: break-word; /* Ensure text wraps */
width: 100%; /* Ensure the container can grow */
`;

class Icon {
readonly title: string;
constructor(readonly def: IconDefinition, readonly names: string[]) {
this.title = def.iconName.split("-").map(_.upperFirst).join(" ");
constructor(readonly def: IconDefinition | any, readonly names: string[]) {
if (def?.iconName) {
this.title = def.iconName.split("-").map(_.upperFirst).join(" ");
} else {
this.title = names[0].slice(5);
this.def = def;
}
}
getView() {
return <FontAwesomeIcon icon={this.def} style={{ width: "1em", height: "1em" }} />;
if (this.names[0]?.startsWith("antd/")) return this.def;
else
return (
<FontAwesomeIcon
icon={this.def}
style={{ width: "1em", height: "1em"}}
/>
);
}
}

Expand All @@ -127,6 +168,7 @@ async function getAllIcons() {
const [{ far }, { fas }] = await Promise.all([
import("@fortawesome/free-regular-svg-icons"),
import("@fortawesome/free-solid-svg-icons"),
// import("@fontawesome/free-brands-svg-icons"),
]);
const ret: Record<string, Icon> = {};
for (const [type, pack] of Object.entries({ solid: fas, regular: far })) {
Expand All @@ -144,14 +186,25 @@ async function getAllIcons() {
}
}
}
//append ant icon
for (let key of Object.keys(ANTDICON)) {
ret["antd/" + key] = new Icon(
ANTDICON[key.toLowerCase() as keyof typeof ANTDICON],
["antd/" + key]
);
}
allIcons = ret;
return ret;
}

export const iconPrefix = "/icon:";

export function removeQuote(value?: string) {
return value ? (value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value) : "";
return value
? value.startsWith('"') && value.endsWith('"')
? value.slice(1, -1)
: value
: "";
}

function getIconKey(value?: string) {
Expand All @@ -171,7 +224,8 @@ export function useIcon(value?: string) {
function search(
allIcons: Record<string, Icon>,
searchText: string,
searchKeywords?: Record<string, string>
searchKeywords?: Record<string, string>,
IconType?: "OnlyAntd" | "All" | "default" | undefined
) {
const tokens = searchText
.toLowerCase()
Expand All @@ -182,6 +236,8 @@ function search(
if (icon.names.length === 0) {
return false;
}
if (IconType === "OnlyAntd" && !key.startsWith("antd/")) return false;
if (IconType === "default" && key.startsWith("antd/")) return false;
let text = icon.names
.flatMap((name) => [name, searchKeywords?.[name]])
.filter((t) => t)
Expand All @@ -198,43 +254,54 @@ const IconPopup = (props: {
label?: ReactNode;
onClose: () => void;
searchKeywords?: Record<string, string>;
IconType?: "OnlyAntd" | "All" | "default" | undefined;
}) => {
const [searchText, setSearchText] = useState("");
const [allIcons, setAllIcons] = useState<Record<string, Icon>>({});
const searchResults = useMemo(
() => search(allIcons, searchText, props.searchKeywords),
() => search(allIcons, searchText, props.searchKeywords, props.IconType),
[searchText, allIcons]
);
const onChangeRef = useRef(props.onChange);
onChangeRef.current = props.onChange;
const onChangeIcon = useCallback((key: string) => onChangeRef.current(iconPrefix + key), []);
const onChangeIcon = useCallback(
(key: string) => onChangeRef.current(iconPrefix + key),
[]
);
const columnNum = 8;

useEffect(() => {
getAllIcons().then(setAllIcons);
}, []);

const smallTextStyle = {
fontSize: '8px'
};

const rowRenderer = useCallback(
(p: ListRowProps) => (
<IconRow key={p.key} style={p.style}>
{searchResults.slice(p.index * columnNum, (p.index + 1) * columnNum).map(([key, icon]) => (
<Tooltip
key={key}
title={icon.title}
placement="bottom"
align={{ offset: [0, -7, 0, 0] }}
destroyTooltipOnHide
>
<IconItemContainer
tabIndex={0}
onClick={() => {
onChangeIcon(key);
}}
{searchResults
.slice(p.index * columnNum, (p.index + 1) * columnNum)
.map(([key, icon]) => (
<Tooltip
key={key}
title={icon.title + ", Key: " + key}
placement="bottom"
align={{ offset: [0, -7, 0, 0] }}
destroyTooltipOnHide
>
{icon.getView()}
</IconItemContainer>
</Tooltip>
))}
<IconItemContainer
tabIndex={0}
onClick={() => {
onChangeIcon(key);
}}
>
<IconWrapper>{icon.getView()}</IconWrapper>
<IconKeyDisplay>{key}</IconKeyDisplay>
</IconItemContainer>
</Tooltip>
))}
</IconRow>
),
[searchResults, allIcons, onChangeIcon]
Expand All @@ -257,9 +324,9 @@ const IconPopup = (props: {
</SearchDiv>
<IconListWrapper>
<IconList
width={394}
height={312}
rowHeight={48}
width={550}
height={400}
rowHeight={80}
rowCount={Math.ceil(searchResults.length / columnNum)}
rowRenderer={rowRenderer}
/>
Expand All @@ -279,6 +346,7 @@ export const IconSelectBase = (props: {
leftOffset?: number;
parent?: HTMLElement | null;
searchKeywords?: Record<string, string>;
IconType?: "OnlyAntd" | "All" | "default" | undefined;
}) => {
const { setVisible, parent } = props;
return (
Expand All @@ -290,7 +358,11 @@ export const IconSelectBase = (props: {
onOpenChange={setVisible}
getPopupContainer={parent ? () => parent : undefined}
// hide the original background when dragging the popover is allowed
overlayInnerStyle={{ border: "none", boxShadow: "none", background: "transparent" }}
overlayInnerStyle={{
border: "none",
boxShadow: "none",
background: "transparent",
}}
// when dragging is allowed, always re-location to avoid the popover exceeds the screen
destroyTooltipOnHide
content={
Expand All @@ -299,6 +371,7 @@ export const IconSelectBase = (props: {
label={props.label}
onClose={() => setVisible?.(false)}
searchKeywords={props.searchKeywords}
IconType={props.IconType}
/>
}
>
Expand All @@ -312,6 +385,7 @@ export const IconSelect = (props: {
label?: ReactNode;
children?: ReactNode;
searchKeywords?: Record<string, string>;
IconType?: "OnlyAntd" | "All" | "default" | undefined;
}) => {
const [visible, setVisible] = useState(false);
return (
Expand Down
1 change: 1 addition & 0 deletions client/packages/lowcoder-design/src/icons/IconCompIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion client/packages/lowcoder-design/src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export { ReactComponent as SignatureIcon } from "./icon-signature.svg";
export { ReactComponent as ManualIcon } from "./icon-manual.svg";
export { ReactComponent as WarnIcon } from "./icon-warn.svg";
export { ReactComponent as SyncManualIcon } from "./icon-sync-manual.svg";
export { ReactComponent as IconCompIcon } from "icons/IconCompIcon.svg";
export { ReactComponent as DangerIcon } from "./icon-danger.svg";
export { ReactComponent as TableMinusIcon } from "./icon-table-minus.svg";
export { ReactComponent as TablePlusIcon } from "./icon-table-plus.svg";
Expand Down Expand Up @@ -614,4 +615,5 @@ export { ReactComponent as MentionIcon } from "./icon-mention-comp.svg";
export { ReactComponent as AutoCompleteCompIcon } from "./icon-autocomplete-comp.svg";
export { ReactComponent as WidthIcon } from "./icon-width.svg";
export { ReactComponent as ResponsiveLayoutCompIcon } from "./remix/layout-column-line.svg"; // Closest match for responsive layout component
export { ReactComponent as TextSizeIcon } from "./icon-text-size.svg"; */
export { ReactComponent as TextSizeIcon } from "./icon-text-size.svg"; */

9 changes: 5 additions & 4 deletions client/packages/lowcoder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"main": "src/index.sdk.ts",
"types": "src/index.sdk.ts",
"dependencies": {
"@ant-design/icons": "^4.7.0",
"@ant-design/icons": "^5.3.0",
"@codemirror/autocomplete": "^6.11.1",
"@codemirror/commands": "^6.3.2",
"@codemirror/lang-css": "^6.2.1",
Expand All @@ -19,9 +19,10 @@
"@dnd-kit/modifiers": "^5.0.0",
"@dnd-kit/sortable": "^6.0.0",
"@dnd-kit/utilities": "^3.1.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "latest",
"@manaflair/redux-batch": "^1.0.0",
"@rjsf/antd": "^5.15.1",
Expand Down
Loading