diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/ProgressBar_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/ProgressBar_spec.js
new file mode 100644
index 0000000000..de195505bc
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/ProgressBar_spec.js
@@ -0,0 +1,25 @@
+const explorer = require("../../../../locators/explorerlocators.json");
+
+describe("ProgressBar Widget Functionality", function() {
+ it("Add new Progress Bar", () => {
+ cy.get(explorer.addWidget).click();
+ cy.dragAndDropToCanvas("progressbarwidget", { x: 300, y: 300 });
+ cy.get(".t--progressbar-widget").should("exist");
+ });
+
+ it("Update Progress bar properties and validate", () => {
+ // add progress value
+ cy.testJsontext("progress", 30);
+ // show result
+ cy.get(".t--property-control-showresult .t--js-toggle").click({
+ force: true,
+ });
+ cy.testJsontext("showresult", "true");
+ cy.wait(200);
+ cy.get(`.t--progressbar-widget > div[data-cy='${30}']`).should("exist");
+
+ cy.get(".t--progressbar-widget > div")
+ .eq(1)
+ .should("have.text", "30%");
+ });
+});
diff --git a/app/client/src/assets/icons/widget/progressbar-icon.svg b/app/client/src/assets/icons/widget/progressbar-icon.svg
new file mode 100644
index 0000000000..a61c2afb7d
--- /dev/null
+++ b/app/client/src/assets/icons/widget/progressbar-icon.svg
@@ -0,0 +1,15 @@
+
diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts
index ba079d7b7a..4393bcf9a4 100644
--- a/app/client/src/constants/HelpConstants.ts
+++ b/app/client/src/constants/HelpConstants.ts
@@ -167,6 +167,10 @@ export const HelpMap: Record = {
path: "/widget-reference/audio-recorder",
searchKey: "Audio Recorder",
},
+ PROGRESSBAR_WIDGET: {
+ path: "/widget-reference/progressbar",
+ searchKey: "Progress Bar",
+ },
SWITCH_GROUP_WIDGET: {
path: "/widget-reference/switch-group",
searchKey: "Switch Group",
diff --git a/app/client/src/icons/WidgetIcons.tsx b/app/client/src/icons/WidgetIcons.tsx
index 3d3df1bd5d..3e4839943a 100644
--- a/app/client/src/icons/WidgetIcons.tsx
+++ b/app/client/src/icons/WidgetIcons.tsx
@@ -37,6 +37,7 @@ import { ReactComponent as StatboxIcon } from "assets/icons/widget/statbox.svg";
import { ReactComponent as CheckboxGroupIcon } from "assets/icons/widget/checkbox-group.svg";
import { ReactComponent as AudioRecorderIcon } from "assets/icons/widget/audio-recorder.svg";
import { ReactComponent as ButtonGroupIcon } from "assets/icons/widget/button-group.svg";
+import { ReactComponent as ProgressBarIcon } from "assets/icons/widget/progressbar-icon.svg";
import { ReactComponent as SwitchGroupIcon } from "assets/icons/widget/switch-group.svg";
import { ReactComponent as CameraIcon } from "assets/icons/widget/camera.svg";
@@ -228,6 +229,11 @@ export const WidgetIcons: {
),
+ PROGRESSBAR_WIDGET: (props: IconProps) => (
+
+
+
+ ),
SWITCH_GROUP_WIDGET: (props: IconProps) => (
diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx
index 9bd545a788..3903610ce3 100644
--- a/app/client/src/utils/WidgetRegistry.tsx
+++ b/app/client/src/utils/WidgetRegistry.tsx
@@ -108,6 +108,9 @@ import SingleSelectTreeWidget, {
import MultiSelectTreeWidget, {
CONFIG as MULTI_SELECT_TREE_WIDGET_CONFIG,
} from "widgets/MultiSelectTreeWidget";
+import ProgressBarWidget, {
+ CONFIG as PROGRESSBAR_WIDGET_CONFIG,
+} from "widgets/ProgressBarWidget";
import SwitchGroupWidget, {
CONFIG as SWITCH_GROUP_WIDGET_CONFIG,
} from "widgets/SwitchGroupWidget";
@@ -162,6 +165,7 @@ export const registerWidgets = () => {
registerWidget(SingleSelectTreeWidget, SINGLE_SELECT_TREE_WIDGET_CONFIG);
registerWidget(SwitchGroupWidget, SWITCH_GROUP_WIDGET_CONFIG);
registerWidget(AudioWidget, AUDIO_WIDGET_CONFIG);
+ registerWidget(ProgressBarWidget, PROGRESSBAR_WIDGET_CONFIG);
registerWidget(CameraWidget, CAMERA_WIDGET_CONFIG);
log.debug("Widget registration took: ", performance.now() - start, "ms");
diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts
index 7f3cfd0090..dd6671a329 100644
--- a/app/client/src/utils/autocomplete/EntityDefinitions.ts
+++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts
@@ -424,6 +424,12 @@ export const entityDefinitions: Record = {
dataURL: "string",
rawBinary: "string",
},
+ PROGRESSBAR_WIDGET: {
+ "!doc": "Progress bar is a simple UI widget used to show progress",
+ "!url": "https://docs.appsmith.com/widget-reference/progressbar",
+ isVisible: isVisible,
+ progress: "number",
+ },
SWITCH_GROUP_WIDGET: {
"!doc":
"Switch group widget allows users to create many switch components which can easily by used in a form",
diff --git a/app/client/src/utils/autocomplete/dataTypeSortRules.ts b/app/client/src/utils/autocomplete/dataTypeSortRules.ts
index f6ef0dd04c..9573f10af9 100644
--- a/app/client/src/utils/autocomplete/dataTypeSortRules.ts
+++ b/app/client/src/utils/autocomplete/dataTypeSortRules.ts
@@ -46,6 +46,7 @@ const RULES: Record> = {
"CHART_WIDGET.xAxisName",
"CHART_WIDGET.yAxisName",
"CONTAINER_WIDGET.backgroundColor",
+ "PROGRESSBAR_WIDGET.progress",
],
OBJECT: ["ACTION.data"],
ARRAY: ["ACTION.data", "TABLE_WIDGET.selectedRowIndices"],
@@ -84,6 +85,7 @@ const RULES: Record> = {
"FORM_BUTTON_WIDGET.isDisabled",
"FILE_PICKER_WIDGET.isRequired",
"MODAL_WIDGET.isOpen",
+ "PROGRESSBAR_WIDGET.isVisible",
],
FUNCTION: [
"ACTION.run()",
diff --git a/app/client/src/widgets/ProgressBarWidget/component/index.tsx b/app/client/src/widgets/ProgressBarWidget/component/index.tsx
new file mode 100644
index 0000000000..4d9295e530
--- /dev/null
+++ b/app/client/src/widgets/ProgressBarWidget/component/index.tsx
@@ -0,0 +1,122 @@
+import React from "react";
+import styled from "styled-components";
+
+import { Colors } from "constants/Colors";
+import { BarType } from "../constants";
+import { isNaN } from "lodash";
+
+const ProgressBarWrapper = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+const ProgressBar = styled.div<{ progress?: number; fillColor: string }>`
+ flex: 1;
+ height: 6px;
+ background: #e8e8e8;
+ position: relative;
+
+ &:after {
+ background: ${({ fillColor }) => fillColor};
+ width: ${({ progress }) => (progress ? `${progress}%` : "")};
+ transition: width 0.4s ease;
+ position: absolute;
+ content: "";
+ left: 0;
+ top: 0;
+ bottom: 0;
+ }
+`;
+
+const Label = styled.div`
+ font-size: 14px;
+ color: ${Colors.GREY_10};
+ padding-left: 4px;
+ line-height: 16px;
+`;
+
+const StepWrapper = styled.div`
+ display: flex;
+ flex: 1;
+ height: 6px;
+ position: relative;
+ margin: 0px -2px;
+`;
+
+const StepContainer = styled.div`
+ flex: 1;
+ background: #e8e8e8;
+ margin: 0px 1px;
+`;
+
+const getProgressPosition = (
+ percentage: number,
+ stepSize: number,
+ currentStep: number,
+) => {
+ const currStepProgress = percentage - stepSize * currentStep;
+ if (currStepProgress > stepSize) {
+ return 100;
+ } else if (currStepProgress < 0) {
+ return 0;
+ } else if (currStepProgress <= stepSize) {
+ return (currStepProgress / stepSize) * 100;
+ } else {
+ // just placeholder for typescript
+ return 0;
+ }
+};
+
+function StepProgressBar(props: ProgressBarComponentProps) {
+ const { steps } = props;
+ const stepSize = 100 / steps;
+
+ return (
+
+ {[...Array(Number(props.steps))].map((_, index) => {
+ const width = getProgressPosition(
+ Number(props.progress),
+ stepSize,
+ index,
+ );
+ return (
+
+
+
+ );
+ })}
+
+ );
+}
+
+function ProgressBarComponent(props: ProgressBarComponentProps) {
+ const isDeterminate =
+ props.barType === BarType.DETERMINATE && !isNaN(Number(props.steps));
+ return (
+
+ {isDeterminate ? (
+
+ ) : (
+
+ )}
+ {props.showResult && }
+
+ );
+}
+export interface ProgressBarComponentProps {
+ progress?: number;
+ showResult: boolean;
+ fillColor: string;
+ barType: BarType;
+ steps: number;
+}
+
+export default ProgressBarComponent;
diff --git a/app/client/src/widgets/ProgressBarWidget/constants.ts b/app/client/src/widgets/ProgressBarWidget/constants.ts
new file mode 100644
index 0000000000..ca7e69b9c1
--- /dev/null
+++ b/app/client/src/widgets/ProgressBarWidget/constants.ts
@@ -0,0 +1,7 @@
+// This file contains common constants which can be used across the widget configuration file (index.ts), widget and component folders.
+export const PROGRESSBAR_WIDGET_CONSTANT = "";
+
+export enum BarType {
+ INDETERMINATE = "indeterminate",
+ DETERMINATE = "determinate",
+}
diff --git a/app/client/src/widgets/ProgressBarWidget/icon.svg b/app/client/src/widgets/ProgressBarWidget/icon.svg
new file mode 100644
index 0000000000..a61c2afb7d
--- /dev/null
+++ b/app/client/src/widgets/ProgressBarWidget/icon.svg
@@ -0,0 +1,15 @@
+
diff --git a/app/client/src/widgets/ProgressBarWidget/index.ts b/app/client/src/widgets/ProgressBarWidget/index.ts
new file mode 100644
index 0000000000..0ef5e78b01
--- /dev/null
+++ b/app/client/src/widgets/ProgressBarWidget/index.ts
@@ -0,0 +1,33 @@
+import Widget from "./widget";
+import IconSVG from "./icon.svg";
+import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+import { Colors } from "constants/Colors";
+import { BarType } from "./constants";
+
+export const CONFIG = {
+ type: Widget.getWidgetType(),
+ name: "Progress Bar", // The display name which will be made in uppercase and show in the widgets panel ( can have spaces )
+ iconSVG: IconSVG,
+ needsMeta: false, // Defines if this widget adds any meta properties
+ isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
+ defaults: {
+ widgetName: "ProgressBar",
+ rows: 0.9 * GRID_DENSITY_MIGRATION_V1,
+ columns: 7 * GRID_DENSITY_MIGRATION_V1,
+ isVisible: true,
+ fillColor: Colors.GREEN,
+ showResult: false,
+ barType: BarType.INDETERMINATE,
+ progress: 50,
+ steps: 1,
+ version: 1,
+ },
+ properties: {
+ derived: Widget.getDerivedPropertiesMap(),
+ default: Widget.getDefaultPropertiesMap(),
+ meta: Widget.getMetaPropertiesMap(),
+ config: Widget.getPropertyPaneConfig(),
+ },
+};
+
+export default Widget;
diff --git a/app/client/src/widgets/ProgressBarWidget/widget/index.tsx b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx
new file mode 100644
index 0000000000..92cf878a29
--- /dev/null
+++ b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx
@@ -0,0 +1,150 @@
+import React from "react";
+
+import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
+import { DerivedPropertiesMap } from "utils/WidgetFactory";
+
+import ProgressBarComponent from "../component";
+
+import { ValidationTypes } from "constants/WidgetValidation";
+import { Colors } from "constants/Colors";
+import { BarType } from "../constants";
+
+class ProgressBarWidget extends BaseWidget<
+ ProgressBarWidgetProps,
+ WidgetState
+> {
+ static getPropertyPaneConfig() {
+ return [
+ {
+ sectionName: "General",
+ children: [
+ {
+ helpText: "Sets progress bar type",
+ propertyName: "barType",
+ label: "Type",
+ controlType: "DROP_DOWN",
+ options: [
+ {
+ label: "Indeterminate",
+ value: BarType.INDETERMINATE,
+ },
+ {
+ label: "Determinate",
+ value: BarType.DETERMINATE,
+ },
+ ],
+ defaultValue: BarType.INDETERMINATE,
+ isBindProperty: false,
+ isTriggerProperty: false,
+ },
+ {
+ helpText: "Provide progress value",
+ propertyName: "progress",
+ label: "Progress",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter progress value",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ isJSConvertible: true,
+ defaultValue: 50,
+ validation: {
+ type: ValidationTypes.NUMBER,
+ params: { min: 0, max: 100, default: 50 },
+ },
+ },
+ {
+ helpText: "Sets a number of steps",
+ propertyName: "steps",
+ label: "Number of steps",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter number of steps",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ isJSConvertible: true,
+ validation: {
+ type: ValidationTypes.NUMBER,
+ params: { min: 1, max: 100, default: 1, natural: true },
+ },
+ hidden: (props: ProgressBarWidgetProps) => {
+ return props.barType !== BarType.DETERMINATE;
+ },
+ dependencies: ["barType"],
+ },
+ {
+ helpText: "Controls the visibility of progress value",
+ propertyName: "showResult",
+ label: "Show result",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ helpText: "Controls the visibility of the widget",
+ propertyName: "isVisible",
+ label: "Visible",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ ],
+ },
+ {
+ sectionName: "Styles",
+ children: [
+ {
+ helpText: "Controls the progress color of progress bar",
+ propertyName: "fillColor",
+ label: "Fill Color",
+ controlType: "COLOR_PICKER",
+ defaultColor: Colors.GREEN,
+ isBindProperty: true,
+ isJSConvertible: true,
+ isTriggerProperty: false,
+ },
+ ],
+ },
+ ];
+ }
+
+ static getDerivedPropertiesMap(): DerivedPropertiesMap {
+ return {};
+ }
+
+ static getDefaultPropertiesMap(): Record {
+ return {};
+ }
+
+ static getMetaPropertiesMap(): Record {
+ return {};
+ }
+
+ getPageView() {
+ return (
+
+ );
+ }
+
+ static getWidgetType(): string {
+ return "PROGRESSBAR_WIDGET";
+ }
+}
+
+export interface ProgressBarWidgetProps extends WidgetProps {
+ progress?: number;
+ showResult: boolean;
+ fillColor: string;
+ barType: BarType;
+ steps: number;
+}
+
+export default ProgressBarWidget;