diff --git a/frontend/src/AppBuilder/Widgets/BaseComponents/BaseInput.jsx b/frontend/src/AppBuilder/Widgets/BaseComponents/BaseInput.jsx
new file mode 100644
index 0000000000..2247d11c03
--- /dev/null
+++ b/frontend/src/AppBuilder/Widgets/BaseComponents/BaseInput.jsx
@@ -0,0 +1,215 @@
+import React from 'react';
+import Label from '@/_ui/Label';
+import Loader from '@/ToolJetUI/Loader/Loader';
+import * as Icons from '@tabler/icons-react';
+const tinycolor = require('tinycolor2');
+
+export const BaseInput = ({
+ height,
+ styles,
+ properties,
+ darkMode,
+ componentName,
+ dataCy,
+ // From useInput hook
+ inputRef,
+ labelRef,
+ visibility,
+ loading,
+ labelWidth,
+ validationError,
+ showValidationError,
+ isFocused,
+ isMandatory,
+ disable,
+ value,
+ handleChange,
+ handleBlur,
+ handleFocus,
+ handleKeyUp,
+ isValid,
+ // Input specific props
+ inputType = 'text',
+ additionalInputProps = {},
+ rightIcon,
+ getCustomStyles,
+}) => {
+ const {
+ padding,
+ borderRadius,
+ borderColor,
+ backgroundColor,
+ textColor,
+ boxShadow,
+ width,
+ alignment,
+ direction,
+ color,
+ auto,
+ errTextColor,
+ iconColor,
+ accentColor,
+ iconVisibility: showLeftIcon,
+ icon,
+ } = styles;
+
+ const { label, placeholder } = properties;
+ const _width = (width / 100) * 70;
+ const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side';
+
+ const computedStyles = {
+ height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4,
+ borderRadius: `${borderRadius}px`,
+ color: !['#1B1F24', '#000', '#000000ff'].includes(textColor)
+ ? textColor
+ : disable || loading
+ ? 'var(--text-disabled)'
+ : 'var(--text-primary)',
+ borderColor: isFocused
+ ? accentColor != '4368E3'
+ ? accentColor
+ : 'var(--primary-accent-strong)'
+ : borderColor != '#CCD1D5'
+ ? borderColor
+ : disable || loading
+ ? '1px solid var(--borders-disabled-on-white)'
+ : 'var(--borders-default)',
+ '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
+ backgroundColor:
+ backgroundColor != '#fff'
+ ? backgroundColor
+ : disable || loading
+ ? darkMode
+ ? 'var(--surfaces-app-bg-default)'
+ : 'var(--surfaces-surface-03)'
+ : 'var(--surfaces-surface-01)',
+ boxShadow,
+ padding: showLeftIcon ? '8px 10px 8px 29px' : '8px 10px',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ };
+
+ const loaderStyle = {
+ right:
+ direction === 'right' &&
+ defaultAlignment === 'side' &&
+ ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0))
+ ? `${labelWidth + 11}px`
+ : '11px',
+ top:
+ defaultAlignment === 'top'
+ ? ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
+ 'calc(50% + 10px)'
+ : '',
+ transform:
+ defaultAlignment === 'top' &&
+ ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
+ ' translateY(-50%)',
+ zIndex: 3,
+ };
+
+ // eslint-disable-next-line import/namespace
+ const IconElement = Icons[icon] ?? Icons['IconHome2'];
+
+ const finalStyles = getCustomStyles ? getCustomStyles(computedStyles) : computedStyles;
+
+ return (
+ <>
+
+
+
+ {showLeftIcon && (
+ 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
+ ? `${labelWidth + 11}px`
+ : '11px',
+ position: 'absolute',
+ top:
+ defaultAlignment === 'side'
+ ? '50%'
+ : (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
+ ? 'calc(50% + 10px)'
+ : '50%',
+ transform: 'translateY(-50%)',
+ color: iconColor !== '#CFD3D859' ? iconColor : 'var(--icons-weak-disabled)',
+ zIndex: 3,
+ }}
+ stroke={1.5}
+ />
+ )}
+
+
+
+ {rightIcon}
+ {loading && }
+
+
+ {showValidationError && visibility && (
+
+ {validationError}
+
+ )}
+ >
+ );
+};
diff --git a/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js
new file mode 100644
index 0000000000..f298265cc7
--- /dev/null
+++ b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js
@@ -0,0 +1,179 @@
+import { useState, useRef, useEffect } from 'react';
+import { useGridStore } from '@/_stores/gridStore';
+
+export const useInput = ({
+ id,
+ properties,
+ styles,
+ validation,
+ validate,
+ setExposedVariable,
+ setExposedVariables,
+ fireEvent,
+}) => {
+ const isInitialRender = useRef(true);
+ const inputRef = useRef();
+ const labelRef = useRef();
+
+ const { loadingState, disabledState, label, visibility: initialVisibility } = properties;
+ const isResizing = useGridStore((state) => state.resizingComponentId === id);
+
+ const [value, setValue] = useState(properties.value ?? '');
+ const [visibility, setVisibility] = useState(initialVisibility);
+ const [loading, setLoading] = useState(loadingState);
+ const [disable, setDisable] = useState(disabledState || loadingState);
+ const [validationStatus, setValidationStatus] = useState(validate(value));
+ const [showValidationError, setShowValidationError] = useState(false);
+ const [isFocused, setIsFocused] = useState(false);
+ const [labelWidth, setLabelWidth] = useState(0);
+ const [iconVisibility, setIconVisibility] = useState(false);
+
+ const { isValid, validationError } = validationStatus;
+ const isMandatory = validation?.mandatory ?? false;
+
+ useEffect(() => {
+ if (labelRef?.current) {
+ const absolutewidth = labelRef?.current?.getBoundingClientRect()?.width;
+ setLabelWidth(absolutewidth);
+ } else setLabelWidth(0);
+ }, [
+ isResizing,
+ styles.width,
+ styles.auto,
+ styles.alignment,
+ styles.iconVisibility,
+ label?.length,
+ isMandatory,
+ styles.padding,
+ styles.direction,
+ labelRef?.current?.getBoundingClientRect()?.width,
+ ]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('label', label);
+ }, [label]);
+
+ useEffect(() => {
+ disable !== disabledState && setDisable(disabledState);
+ }, [disabledState]);
+
+ useEffect(() => {
+ visibility !== properties.visibility && setVisibility(properties.visibility);
+ }, [properties.visibility]);
+
+ useEffect(() => {
+ loading !== loadingState && setLoading(loadingState);
+ }, [loadingState]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ const validationStatus = validate(value);
+ setValidationStatus(validationStatus);
+ setExposedVariable('isValid', validationStatus?.isValid);
+ }, [validate]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setInputValue(properties.value ?? '');
+ }, [properties.value]);
+
+ useEffect(() => {
+ const exposedVariables = {
+ setText: async function (text) {
+ setInputValue(text);
+ fireEvent('onChange');
+ },
+ clear: async function () {
+ setInputValue('');
+ fireEvent('onChange');
+ },
+ setFocus: async function () {
+ inputRef.current.focus();
+ },
+ setBlur: async function () {
+ inputRef.current.blur();
+ },
+ setVisibility: async function (state) {
+ setVisibility(state);
+ setExposedVariable('isVisible', state);
+ },
+ setDisable: async function (disable) {
+ setDisable(disable);
+ setExposedVariable('isDisabled', disable);
+ },
+ setLoading: async function (loading) {
+ setLoading(loading);
+ setExposedVariable('isLoading', loading);
+ },
+ label,
+ isValid,
+ value: properties.value ?? '',
+ isMandatory,
+ isLoading: loading,
+ isVisible: visibility,
+ isDisabled: disable,
+ };
+
+ setExposedVariables(exposedVariables);
+ isInitialRender.current = false;
+ }, []);
+
+ const setInputValue = (value) => {
+ setValue(value);
+ setExposedVariable('value', value);
+ const validationStatus = validate(value);
+ setValidationStatus(validationStatus);
+ setExposedVariable('isValid', validationStatus?.isValid);
+ };
+
+ const handleChange = (e) => {
+ setInputValue(e.target.value);
+ fireEvent('onChange');
+ };
+
+ const handleBlur = (e) => {
+ setShowValidationError(true);
+ setIsFocused(false);
+ e.stopPropagation();
+ fireEvent('onBlur');
+ };
+
+ const handleFocus = (e) => {
+ setIsFocused(true);
+ e.stopPropagation();
+ setTimeout(() => {
+ fireEvent('onFocus');
+ }, 0);
+ };
+
+ const handleKeyUp = (e) => {
+ if (e.key === 'Enter') {
+ setInputValue(e.target.value);
+ fireEvent('onEnterPressed');
+ }
+ };
+
+ return {
+ inputRef,
+ labelRef,
+ value,
+ visibility,
+ loading,
+ disable,
+ validationStatus,
+ showValidationError,
+ isFocused,
+ labelWidth,
+ iconVisibility,
+ setIconVisibility,
+ isValid,
+ validationError,
+ isMandatory,
+ setInputValue,
+ handleChange,
+ handleBlur,
+ handleFocus,
+ handleKeyUp,
+ };
+};
diff --git a/frontend/src/AppBuilder/Widgets/NumberInput.jsx b/frontend/src/AppBuilder/Widgets/NumberInput.jsx
new file mode 100644
index 0000000000..0a30095621
--- /dev/null
+++ b/frontend/src/AppBuilder/Widgets/NumberInput.jsx
@@ -0,0 +1,163 @@
+import React from 'react';
+import { BaseInput } from './BaseComponents/BaseInput';
+import { useInput } from './BaseComponents/hooks/useInput';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
+
+export const NumberInput = (props) => {
+ const inputLogic = useInput({
+ ...props,
+ properties: {
+ ...props.properties,
+ value: Number(parseFloat(props.properties.value).toFixed(props.properties.decimalPlaces)),
+ },
+ });
+
+ const handleChange = (e) => {
+ if (e.target.value === '') {
+ inputLogic.setInputValue(null);
+ props.fireEvent('onChange');
+ } else {
+ const newValue = Number(parseFloat(e.target.value));
+ inputLogic.setInputValue(newValue);
+ if (!isNaN(newValue)) {
+ props.fireEvent('onChange');
+ }
+ }
+ };
+
+ const handleBlur = (e) => {
+ const value = Number(parseFloat(e.target.value).toFixed(props.properties.decimalPlaces));
+ inputLogic.setInputValue(value);
+ e.stopPropagation();
+ props.fireEvent('onBlur');
+ inputLogic.setIsFocused(false);
+ };
+
+ const handleIncrement = (e) => {
+ e.preventDefault();
+ const newValue = (inputLogic.value || 0) + 1;
+ inputLogic.setInputValue(newValue);
+ if (!isNaN(newValue)) {
+ props.fireEvent('onChange');
+ }
+ };
+
+ const handleDecrement = (e) => {
+ e.preventDefault();
+ const newValue = (inputLogic.value || 0) - 1;
+ inputLogic.setInputValue(newValue);
+ if (!isNaN(newValue)) {
+ props.fireEvent('onChange');
+ }
+ };
+
+ // Override the base input styles to account for number controls
+ const getCustomStyles = (baseStyles) => {
+ return {
+ ...baseStyles,
+ paddingRight: '20px', // Make room for number controls
+ };
+ };
+
+ const numberControls = !inputLogic.isResizing && (
+
+
+ 0 && props.styles.width > 0
+ ? '21px'
+ : '1px',
+ right: '1px',
+ borderLeft:
+ inputLogic.disable || inputLogic.loading
+ ? '1px solid var(--borders-weak-disabled)'
+ : '1px solid var(--borders-default)',
+ borderBottom:
+ inputLogic.disable || inputLogic.loading
+ ? '1px solid var(--borders-weak-disabled)'
+ : '.5px solid var(--borders-default)',
+ borderTopRightRadius: props.styles.borderRadius - 1,
+ backgroundColor: 'transparent',
+ }}
+ className="numberinput-up-arrow arrow number-input-arrow"
+ name="TriangleDownCenter"
+ />
+
+
+
+
+
+ );
+
+ return (
+
+ );
+};
diff --git a/frontend/src/AppBuilder/Widgets/PasswordInput.jsx b/frontend/src/AppBuilder/Widgets/PasswordInput.jsx
new file mode 100644
index 0000000000..6ca13f2048
--- /dev/null
+++ b/frontend/src/AppBuilder/Widgets/PasswordInput.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { BaseInput } from './BaseComponents/BaseInput';
+import { useInput } from './BaseComponents/hooks/useInput';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
+
+export const PasswordInput = (props) => {
+ const inputLogic = useInput(props);
+
+ const toggleVisibility = () => {
+ inputLogic.setIconVisibility(!inputLogic.iconVisibility);
+ };
+
+ const passwordIcon = (
+
+
+
+ );
+
+ return (
+
+ );
+};
diff --git a/frontend/src/AppBuilder/Widgets/TextInput.jsx b/frontend/src/AppBuilder/Widgets/TextInput.jsx
new file mode 100644
index 0000000000..119add300e
--- /dev/null
+++ b/frontend/src/AppBuilder/Widgets/TextInput.jsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import { BaseInput } from './BaseComponents/BaseInput';
+import { useInput } from './BaseComponents/hooks/useInput';
+
+export const TextInput = (props) => {
+ const inputLogic = useInput(props);
+
+ return ;
+};
diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js
index 38e9abd955..e2b7f3428d 100644
--- a/frontend/src/AppBuilder/_helpers/editorHelpers.js
+++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js
@@ -4,8 +4,8 @@ import { Text } from '@/Editor/Components/Text';
// import { Table } from '@/Editor/Components/Table/Table';
import { Table } from '@/AppBuilder/Widgets/Table/Table';
-import { TextInput } from '@/Editor/Components/TextInput';
-import { NumberInput } from '@/Editor/Components/NumberInput';
+import { TextInput } from '@/AppBuilder/Widgets/TextInput';
+import { NumberInput } from '@/AppBuilder/Widgets/NumberInput';
import { TextArea } from '@/Editor/Components/TextArea';
import { RichTextEditor } from '@/Editor/Components/RichTextEditor';
import { DropDown } from '@/Editor/Components/DropDown';
@@ -29,7 +29,7 @@ import { RadioButtonV2 } from '@/Editor/Components/RadioButtonV2/RadioButtonV2';
import { StarRating } from '@/Editor/Components/StarRating';
import { Divider } from '@/Editor/Components/Divider';
import { FilePicker } from '@/Editor/Components/FilePicker';
-import { PasswordInput } from '@/Editor/Components/PasswordInput';
+import { PasswordInput } from '@/AppBuilder/Widgets/PasswordInput';
// import { Calendar } from '@/Editor/Components/Calendar';
// import { Listview } from '@/Editor/Components/Listview';
import { IFrame } from '@/Editor/Components/IFrame';