From 616d61e3317f43f78cd026dc1720fb6a63f58f5b Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 20 Apr 2026 22:02:26 +0500 Subject: [PATCH] [Feat]: #2124 add customization for the progress circle --- .../src/comps/comps/progressCircleComp.tsx | 123 +++++++++++++++--- .../comps/comps/progressCircleConstants.ts | 22 ++++ .../packages/lowcoder/src/i18n/locales/en.ts | 33 +++++ 3 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/progressCircleConstants.ts diff --git a/client/packages/lowcoder/src/comps/comps/progressCircleComp.tsx b/client/packages/lowcoder/src/comps/comps/progressCircleComp.tsx index b2070c7c23..e4e70a6327 100644 --- a/client/packages/lowcoder/src/comps/comps/progressCircleComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/progressCircleComp.tsx @@ -3,16 +3,21 @@ import { styleControl } from "comps/controls/styleControl"; import { AnimationStyle, AnimationStyleType, CircleProgressStyle, CircleProgressType, heightCalculator, widthCalculator } from "comps/controls/styleControlConstants"; import styled, { css } from "styled-components"; import { Section, sectionNames } from "lowcoder-design"; -import { numberExposingStateControl } from "../controls/codeStateControl"; +import { numberExposingStateControl, stringExposingStateControl } from "../controls/codeStateControl"; import { UICompBuilder } from "../generators"; import { NameConfig, NameConfigHidden, withExposingConfigs } from "../generators/withExposing"; import { hiddenPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; - +import { BoolControl } from "../controls/boolControl"; +import { dropdownControl } from "../controls/dropdownControl"; +import { NumberControl } from "../controls/codeControl"; import { useContext } from "react"; import { EditorContext } from "comps/editorState"; - -// TODO: after Update of ANTd, introduce Size attribute to ProgressCircle +import { + ProgressTypeOptions, + StrokeLinecapOptions, + GapPositionOptions +} from "./progressCircleConstants"; const getStyle = (style: CircleProgressType) => { return css` @@ -20,13 +25,13 @@ const getStyle = (style: CircleProgressType) => { height: ${heightCalculator(style.margin)}; margin: ${style.margin}; padding: ${style.padding}; - border-radius:${style.radius}; + border-radius: ${style.radius}; .ant-progress-text { color: ${style.text} !important; - font-family:${style.fontFamily}; - font-style:${style.fontStyle}; - font-size:${style.textSize} !important; - font-weight:${style.textWeight}; + font-family: ${style.fontFamily}; + font-style: ${style.fontStyle}; + font-size: ${style.textSize} !important; + font-weight: ${style.textWeight}; } .ant-progress-circle-trail { stroke: ${style.track}; @@ -68,21 +73,54 @@ export const StyledProgressCircle = styled(Progress)<{ let ProgressCircleTmpComp = (function () { const childrenMap = { value: numberExposingStateControl("value", 60), - // borderRadius property hidden as it's not valid for progress circle + progressType: dropdownControl(ProgressTypeOptions, "circle"), + showInfo: BoolControl.DEFAULT_TRUE, + strokeWidth: NumberControl, + strokeLinecap: dropdownControl(StrokeLinecapOptions, "round"), + gapDegree: NumberControl, + gapPosition: dropdownControl(GapPositionOptions, "bottom"), + customFormat: stringExposingStateControl("customFormat", ""), + // Steps configuration for segmented progress + stepsEnabled: BoolControl, + stepsCount: NumberControl, + stepsGap: NumberControl, + // Style controls style: styleControl(CircleProgressStyle, 'style'), animationStyle: styleControl(AnimationStyle, 'animationStyle'), }; + return new UICompBuilder(childrenMap, (props) => { + const percent = Math.round(props.value.value); + const customFormatValue = props.customFormat.value?.trim(); + + // Simple format function - just returns the custom text if provided + const formatFunction = customFormatValue ? () => customFormatValue : undefined; + + // Build steps configuration if enabled + const stepsConfig = props.stepsEnabled && props.stepsCount > 0 + ? { count: props.stepsCount, gap: props.stepsGap || 2 } + : undefined; + return ( ); }) .setPropertyViewFn((children) => { + const progressType = children.progressType.getView(); + const stepsEnabled = children.stepsEnabled.getView(); + return ( <>
@@ -90,8 +128,62 @@ let ProgressCircleTmpComp = (function () { label: trans("progress.value"), tooltip: trans("progress.valueTooltip"), })} + {children.progressType.propertyView({ + label: trans("progressCircle.progressType"), + tooltip: trans("progressCircle.progressTypeTooltip"), + })} +
+ +
+ {children.showInfo.propertyView({ + label: trans("progress.showInfo"), + })} + {children.customFormat.propertyView({ + label: trans("progressCircle.customFormat"), + tooltip: trans("progressCircle.customFormatTooltip"), + })} + {children.strokeWidth.propertyView({ + label: trans("progressCircle.strokeWidth"), + tooltip: trans("progressCircle.strokeWidthTooltip"), + placeholder: "6", + })} + {children.strokeLinecap.propertyView({ + label: trans("progressCircle.strokeLinecap"), + tooltip: trans("progressCircle.strokeLinecapTooltip"), + })} +
+ +
+ {children.stepsEnabled.propertyView({ + label: trans("progressCircle.stepsEnabled"), + tooltip: trans("progressCircle.stepsEnabledTooltip"), + })} + {stepsEnabled && children.stepsCount.propertyView({ + label: trans("progressCircle.stepsCount"), + tooltip: trans("progressCircle.stepsCountTooltip"), + placeholder: "5", + })} + {stepsEnabled && children.stepsGap.propertyView({ + label: trans("progressCircle.stepsGap"), + tooltip: trans("progressCircle.stepsGapTooltip"), + placeholder: "2", + })}
+ {progressType === "dashboard" && ( +
+ {children.gapDegree.propertyView({ + label: trans("progressCircle.gapDegree"), + tooltip: trans("progressCircle.gapDegreeTooltip"), + placeholder: "75", + })} + {children.gapPosition.propertyView({ + label: trans("progressCircle.gapPosition"), + tooltip: trans("progressCircle.gapPositionTooltip"), + })} +
+ )} + {["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (
{hiddenPropertyView(children)} @@ -101,12 +193,12 @@ let ProgressCircleTmpComp = (function () { {["layout", "both"].includes(useContext(EditorContext).editorModeStatus) && ( <>
- {children.style.getPropertyView()} + {children.style.getPropertyView()}
- {children.animationStyle.getPropertyView()} + {children.animationStyle.getPropertyView()}
- + )} ); @@ -122,5 +214,6 @@ ProgressCircleTmpComp = class extends ProgressCircleTmpComp { export const ProgressCircleComp = withExposingConfigs(ProgressCircleTmpComp, [ new NameConfig("value", trans("progress.valueDesc")), + new NameConfig("customFormat", trans("progressCircle.customFormatDesc")), NameConfigHidden, ]); diff --git a/client/packages/lowcoder/src/comps/comps/progressCircleConstants.ts b/client/packages/lowcoder/src/comps/comps/progressCircleConstants.ts new file mode 100644 index 0000000000..31a5bb1124 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/progressCircleConstants.ts @@ -0,0 +1,22 @@ +import { trans } from "i18n"; + +// Progress type options (circle or dashboard) +export const ProgressTypeOptions = [ + { label: trans("progressCircle.circle"), value: "circle" }, + { label: trans("progressCircle.dashboard"), value: "dashboard" }, +] as const; + +// Stroke linecap options (line ending style) +export const StrokeLinecapOptions = [ + { label: trans("progressCircle.round"), value: "round" }, + { label: trans("progressCircle.butt"), value: "butt" }, + { label: trans("progressCircle.square"), value: "square" }, +] as const; + +// Gap position options (for dashboard type) +export const GapPositionOptions = [ + { label: trans("progressCircle.top"), value: "top" }, + { label: trans("progressCircle.bottom"), value: "bottom" }, + { label: trans("progressCircle.left"), value: "left" }, + { label: trans("progressCircle.right"), value: "right" }, +] as const; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index e9dcf12680..95488cc8c1 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -2257,6 +2257,39 @@ export const en = { "valueDesc": "Current Progress Value, Ranging from 0 to 100", "showInfoDesc": "Whether to Display the Current Progress Value" }, + "progressCircle": { + "circle": "Circle", + "dashboard": "Dashboard", + "progressType": "Type", + "progressTypeTooltip": "Choose between a full circle or dashboard (semi-circle) style", + "appearance": "Appearance", + "customFormat": "Custom Text", + "customFormatTooltip": "Custom text to display instead of the percentage", + "customFormatDesc": "Custom text displayed in the progress circle", + "strokeWidth": "Stroke Width", + "strokeWidthTooltip": "The width of the progress stroke line (default: 6)", + "strokeLinecap": "Line Cap Style", + "strokeLinecapTooltip": "The shape of the progress line endings", + "round": "Round", + "butt": "Flat", + "square": "Square", + "dashboardSettings": "Dashboard Settings", + "gapDegree": "Gap Degree", + "gapDegreeTooltip": "The gap degree of the dashboard, 0-295 (default: 75)", + "gapPosition": "Gap Position", + "gapPositionTooltip": "The position of the gap in the dashboard", + "top": "Top", + "bottom": "Bottom", + "left": "Left", + "right": "Right", + "segments": "Segments", + "stepsEnabled": "Enable Steps", + "stepsEnabledTooltip": "Display progress as segmented steps instead of continuous", + "stepsCount": "Step Count", + "stepsCountTooltip": "The total number of steps/segments to display", + "stepsGap": "Step Gap", + "stepsGapTooltip": "The gap between each step in pixels (default: 2)" + }, "fileViewer": { "invalidURL": "Please Enter a Valid URL or Base64 String", "src": "File URI",