From 9572644a53bd2d11aec5c734694a632fb5cfd443 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 25 Nov 2024 14:41:56 +0530 Subject: [PATCH 01/23] Extracted the common logic of input components and moved it to the AppBuilder folder --- .../Widgets/BaseComponents/BaseInput.jsx | 215 ++++++++++++++++++ .../Widgets/BaseComponents/hooks/useInput.js | 179 +++++++++++++++ .../src/AppBuilder/Widgets/NumberInput.jsx | 163 +++++++++++++ .../src/AppBuilder/Widgets/PasswordInput.jsx | 43 ++++ frontend/src/AppBuilder/Widgets/TextInput.jsx | 9 + .../src/AppBuilder/_helpers/editorHelpers.js | 6 +- 6 files changed, 612 insertions(+), 3 deletions(-) create mode 100644 frontend/src/AppBuilder/Widgets/BaseComponents/BaseInput.jsx create mode 100644 frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js create mode 100644 frontend/src/AppBuilder/Widgets/NumberInput.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PasswordInput.jsx create mode 100644 frontend/src/AppBuilder/Widgets/TextInput.jsx 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 ( + <> +
+
+ + {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'; From 482573b2ff0df816452ee79a8a7cb0a2e86dfacc Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Sun, 8 Dec 2024 13:26:23 +0530 Subject: [PATCH 02/23] textarea 2.0 component --- .../assets/images/icons/widgets/index.jsx | 1 + .../ComponentsManagerTab.jsx | 1 + .../ComponentsManagerTab/constants.js | 10 +- .../Inspector/Components/DefaultComponent.jsx | 3 + .../RightSideBar/Inspector/Inspector.jsx | 1 + .../RightSideBar/WidgetBox/WidgetBox.jsx | 3 +- .../WidgetManager/configs/widgetConfig.js | 2 + .../AppBuilder/WidgetManager/widgets/index.js | 2 + .../WidgetManager/widgets/textarea.js | 4 +- .../WidgetManager/widgets/textareaV2.js | 302 ++++++++++++++++++ .../Widgets/BaseComponents/BaseInput.jsx | 66 ++-- .../src/AppBuilder/Widgets/TextareaV2.jsx | 9 + .../src/AppBuilder/_helpers/editorHelpers.js | 2 + .../_stores/slices/componentsSlice.js | 1 + .../src/Editor/WidgetManager/components.js | 2 +- .../src/Editor/WidgetManager/constants.js | 1 + frontend/src/_helpers/appUtils.js | 4 +- frontend/src/_styles/theme.scss | 5 + .../apps/services/widget-config/index.js | 4 +- .../apps/services/widget-config/textarea.js | 4 +- .../apps/services/widget-config/textareaV2.js | 302 ++++++++++++++++++ 21 files changed, 699 insertions(+), 30 deletions(-) create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/textareaV2.js create mode 100644 frontend/src/AppBuilder/Widgets/TextareaV2.jsx create mode 100644 server/src/modules/apps/services/widget-config/textareaV2.js diff --git a/frontend/assets/images/icons/widgets/index.jsx b/frontend/assets/images/icons/widgets/index.jsx index 7ecb678d1b..1dc13a843a 100644 --- a/frontend/assets/images/icons/widgets/index.jsx +++ b/frontend/assets/images/icons/widgets/index.jsx @@ -180,6 +180,7 @@ const WidgetIcon = (props) => { case 'text': return ; case 'textarea': + case 'textarealegacy': return - ); -}; diff --git a/frontend/src/Editor/WidgetManager/configs/textarea.js b/frontend/src/Editor/WidgetManager/configs/textarea.js index c5e71be77d..d0e0364334 100644 --- a/frontend/src/Editor/WidgetManager/configs/textarea.js +++ b/frontend/src/Editor/WidgetManager/configs/textarea.js @@ -4,7 +4,7 @@ export const textareaConfig = { description: 'Multi-line text input', component: 'TextArea', defaultSize: { - width: 6, + width: 10, height: 100, }, others: { @@ -12,82 +12,291 @@ export const textareaConfig = { showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { - value: { + label: { type: 'code', - displayName: 'Default value', - validation: { - schema: { type: 'string' }, - defaultValue: 'default text', - }, + displayName: 'Label', + validation: { schema: { type: 'string' }, defaultValue: 'Label' }, }, placeholder: { type: 'code', displayName: 'Placeholder', validation: { schema: { type: 'string' }, - defaultValue: 'Placeholder text', + defaultValue: 'Enter your input', }, }, - }, - events: {}, - styles: { + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + defaultValue: 'Default value', + }, + }, + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, visibility: { type: 'toggle', displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', }, disabledState: { type: 'toggle', displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - defaultValue: false, + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + }, + events: { + onChange: { displayName: 'On change' }, + onEnterPressed: { displayName: 'On enter pressed' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' }, defaultValue: 'side' }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'label', + isFxNotRequired: true, + }, + width: { + type: 'slider', + displayName: 'Width', + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', }, + isFxNotRequired: true, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + + backgroundColor: { + type: 'color', + displayName: 'Background', + validation: { schema: { type: 'string' }, defaultValue: '#fff' }, + accordian: 'field', + }, + borderColor: { + type: 'color', + displayName: 'Border', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + }, + accentColor: { + type: 'color', + displayName: 'Accent', + validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text', + validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, + accordian: 'field', + visibility: false, + showLabel: false, }, borderRadius: { - type: 'code', + type: 'numberInput', displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', validation: { - schema: { type: 'number' }, - defaultValue: 4, + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, }, exposedVariables: { - value: - 'ToolJet is an open-source low-code platform for building and deploying internal tools with minimal engineering efforts 🚀', + value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, }, actions: [ { handle: 'setText', - displayName: 'Set Text', - params: [{ handle: 'text', displayName: 'text', defaultValue: 'New Text' }], + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], }, { handle: 'clear', displayName: 'Clear', }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'disable', + displayName: 'Disable(deprecated)', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'visibility', + displayName: 'Visibility(deprecated)', + params: [{ handle: 'visibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, ], definition: { + validation: { + mandatory: { value: '{{false}}' }, + regex: { value: '' }, + minLength: { value: '' }, + maxLength: { value: '' }, + customRule: { value: '' }, + }, + others: { showOnDesktop: { value: '{{true}}' }, showOnMobile: { value: '{{false}}' }, }, properties: { - value: { - value: - 'ToolJet is an open-source low-code platform for building and deploying internal tools with minimal engineering efforts 🚀', - }, - placeholder: { value: 'Placeholder text' }, + value: { value: '' }, + label: { value: 'Label' }, + placeholder: { value: 'Enter your input' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, }, events: [], styles: { - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, - borderRadius: { value: '{{4}}' }, + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, + accentColor: { value: '#4368E3' }, + errTextColor: { value: '#D72D39' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + iconColor: { value: '#CFD3D859' }, + direction: { value: 'left' }, + width: { value: '{{33}}' }, + alignment: { value: 'side' }, + color: { value: '#1B1F24' }, + auto: { value: '{{true}}' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconHome2' }, + iconVisibility: { value: false }, }, }, }; diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index bfb1213701..9ce0e8527a 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -2487,7 +2487,7 @@ export const setMultipleComponentsSelected = (components) => { export const calculateMoveableBoxHeight = (componentType, layoutData, stylesDefinition, label) => { // Early return for non input components if ( - !['TextInput', 'PasswordInput', 'NumberInput', 'DropdownV2', 'MultiselectV2', 'TextareaV2'].includes(componentType) + !['TextInput', 'PasswordInput', 'NumberInput', 'DropdownV2', 'MultiselectV2', 'TextArea'].includes(componentType) ) { return layoutData?.height; } diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 1f97cc42ee..ca9f8cd9fc 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -4,7 +4,6 @@ import { Text } from '@/Editor/Components/Text'; import { Table } from '@/Editor/Components/Table/Table'; import { TextInput } from '@/Editor/Components/TextInput'; import { NumberInput } from '@/Editor/Components/NumberInput'; -import { TextArea } from '@/Editor/Components/TextArea'; import { Container } from '@/Editor/Components/Container'; import { Tabs } from '@/Editor/Components/Tabs'; import { RichTextEditor } from '@/Editor/Components/RichTextEditor'; @@ -81,7 +80,6 @@ export const AllComponents = { TextInput, NumberInput, Table, - TextArea, Container, Tabs, RichTextEditor, diff --git a/server/data-migrations/1736448327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts b/server/data-migrations/1736448327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts new file mode 100644 index 0000000000..57550a14f6 --- /dev/null +++ b/server/data-migrations/1736448327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts @@ -0,0 +1,64 @@ +import { Component } from 'src/entities/component.entity'; +import { processDataInBatches } from 'src/helpers/utils.helper'; +import { EntityManager, MigrationInterface, QueryRunner } from 'typeorm'; + +export class MoveVisibilityDisabledStatesToPropertiesTextarea1736448327127 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const batchSize = 100; + const entityManager = queryRunner.manager; + + await processDataInBatches( + entityManager, + async (entityManager: EntityManager) => { + return await entityManager.find(Component, { + where: { type: 'TextArea' }, + order: { createdAt: 'ASC' }, + }); + }, + async (entityManager: EntityManager, components: Component[]) => { + await this.processUpdates(entityManager, components); + }, + batchSize + ); + } + + private async processUpdates(entityManager, components) { + for (const component of components) { + const properties = component.properties; + const styles = component.styles; + const general = component.general; + const generalStyles = component.generalStyles; + const validation = component.validation; + + if (styles.visibility) { + properties.visibility = styles?.visibility; + delete styles?.visibility; + } + + if (styles.disabledState) { + properties.disabledState = styles?.disabledState; + delete styles?.disabledState; + } + + if (generalStyles?.boxShadow) { + styles.boxShadow = generalStyles?.boxShadow; + delete generalStyles?.boxShadow; + } + + // Label and value + if (properties.label == undefined || null) { + properties.label = ''; + } + + await entityManager.update(Component, component.id, { + properties, + styles, + general, + generalStyles, + validation, + }); + } + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/src/modules/apps/services/widget-config/index.js b/server/src/modules/apps/services/widget-config/index.js index 1e1ef14f5f..34826885e0 100644 --- a/server/src/modules/apps/services/widget-config/index.js +++ b/server/src/modules/apps/services/widget-config/index.js @@ -14,7 +14,6 @@ import { radiobuttonV2Config } from './radioButtonV2'; import { toggleswitchConfig } from './toggleswitch'; import { toggleSwitchV2Config } from './toggleswitchv2'; import { textareaConfig } from './textarea'; -import { textareaV2Config } from './textareaV2'; import { daterangepickerConfig } from './daterangepicker'; import { textConfig } from './text'; import { imageConfig } from './image'; @@ -80,7 +79,6 @@ const widgets = { toggleswitchConfig, //!Depreciated toggleSwitchV2Config, textareaConfig, //! Deprecated - textareaV2Config, daterangepickerConfig, textConfig, imageConfig, diff --git a/server/src/modules/apps/services/widget-config/textarea.js b/server/src/modules/apps/services/widget-config/textarea.js index c7d901cb00..d0e0364334 100644 --- a/server/src/modules/apps/services/widget-config/textarea.js +++ b/server/src/modules/apps/services/widget-config/textarea.js @@ -1,10 +1,10 @@ export const textareaConfig = { - name: 'TextareaLegacy', - displayName: 'Text Area (Legacy)', + name: 'Textarea', + displayName: 'Text Area', description: 'Multi-line text input', component: 'TextArea', defaultSize: { - width: 6, + width: 10, height: 100, }, others: { @@ -12,82 +12,291 @@ export const textareaConfig = { showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { - value: { + label: { type: 'code', - displayName: 'Default value', - validation: { - schema: { type: 'string' }, - defaultValue: 'default text', - }, + displayName: 'Label', + validation: { schema: { type: 'string' }, defaultValue: 'Label' }, }, placeholder: { type: 'code', displayName: 'Placeholder', validation: { schema: { type: 'string' }, - defaultValue: 'Placeholder text', + defaultValue: 'Enter your input', }, }, - }, - events: {}, - styles: { + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + defaultValue: 'Default value', + }, + }, + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, visibility: { type: 'toggle', displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', }, disabledState: { type: 'toggle', displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - defaultValue: false, + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + }, + events: { + onChange: { displayName: 'On change' }, + onEnterPressed: { displayName: 'On enter pressed' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' }, defaultValue: 'side' }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'label', + isFxNotRequired: true, + }, + width: { + type: 'slider', + displayName: 'Width', + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', }, + isFxNotRequired: true, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + + backgroundColor: { + type: 'color', + displayName: 'Background', + validation: { schema: { type: 'string' }, defaultValue: '#fff' }, + accordian: 'field', + }, + borderColor: { + type: 'color', + displayName: 'Border', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + }, + accentColor: { + type: 'color', + displayName: 'Accent', + validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text', + validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, + accordian: 'field', + visibility: false, + showLabel: false, }, borderRadius: { - type: 'code', + type: 'numberInput', displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', validation: { - schema: { type: 'number' }, - defaultValue: 4, + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, }, exposedVariables: { - value: - 'ToolJet is an open-source low-code platform for building and deploying internal tools with minimal engineering efforts 🚀', + value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, }, actions: [ { handle: 'setText', - displayName: 'Set Text', - params: [{ handle: 'text', displayName: 'text', defaultValue: 'New Text' }], + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], }, { handle: 'clear', displayName: 'Clear', }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'disable', + displayName: 'Disable(deprecated)', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'visibility', + displayName: 'Visibility(deprecated)', + params: [{ handle: 'visibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, ], definition: { + validation: { + mandatory: { value: '{{false}}' }, + regex: { value: '' }, + minLength: { value: '' }, + maxLength: { value: '' }, + customRule: { value: '' }, + }, + others: { showOnDesktop: { value: '{{true}}' }, showOnMobile: { value: '{{false}}' }, }, properties: { - value: { - value: - 'ToolJet is an open-source low-code platform for building and deploying internal tools with minimal engineering efforts 🚀', - }, - placeholder: { value: 'Placeholder text' }, + value: { value: '' }, + label: { value: 'Label' }, + placeholder: { value: 'Enter your input' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, }, events: [], styles: { - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, - borderRadius: { value: '{{4}}' }, + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, + accentColor: { value: '#4368E3' }, + errTextColor: { value: '#D72D39' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + iconColor: { value: '#CFD3D859' }, + direction: { value: 'left' }, + width: { value: '{{33}}' }, + alignment: { value: 'side' }, + color: { value: '#1B1F24' }, + auto: { value: '{{true}}' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconHome2' }, + iconVisibility: { value: false }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/textareaV2.js b/server/src/modules/apps/services/widget-config/textareaV2.js deleted file mode 100644 index 046eb83b7b..0000000000 --- a/server/src/modules/apps/services/widget-config/textareaV2.js +++ /dev/null @@ -1,302 +0,0 @@ -export const textareaV2Config = { - name: 'Textarea', - displayName: 'Text Area', - description: 'Multi-line text input', - component: 'TextareaV2', - defaultSize: { - width: 10, - height: 100, - }, - others: { - showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, - showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, - }, - properties: { - label: { - type: 'code', - displayName: 'Label', - validation: { schema: { type: 'string' }, defaultValue: 'Label' }, - }, - placeholder: { - type: 'code', - displayName: 'Placeholder', - validation: { - schema: { type: 'string' }, - defaultValue: 'Enter your input', - }, - }, - value: { - type: 'code', - displayName: 'Default value', - validation: { - schema: { - type: 'string', - }, - defaultValue: 'Default value', - }, - }, - loadingState: { - type: 'toggle', - displayName: 'Loading state', - validation: { schema: { type: 'boolean' }, defaultValue: false }, - section: 'additionalActions', - }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { schema: { type: 'boolean' }, defaultValue: true }, - section: 'additionalActions', - }, - disabledState: { - type: 'toggle', - displayName: 'Disable', - validation: { schema: { type: 'boolean' }, defaultValue: false }, - section: 'additionalActions', - }, - tooltip: { - type: 'code', - displayName: 'Tooltip', - validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, - section: 'additionalActions', - placeholder: 'Enter tooltip text', - }, - }, - validation: { - mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, - regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, - minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, - maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, - customRule: { - type: 'code', - displayName: 'Custom validation', - placeholder: `{{components.text2.text=='yes'&&'valid'}}`, - }, - }, - events: { - onChange: { displayName: 'On change' }, - onEnterPressed: { displayName: 'On enter pressed' }, - onFocus: { displayName: 'On focus' }, - onBlur: { displayName: 'On blur' }, - }, - styles: { - color: { - type: 'color', - displayName: 'Text', - validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, - accordian: 'label', - }, - alignment: { - type: 'switch', - displayName: 'Alignment', - validation: { schema: { type: 'string' }, defaultValue: 'side' }, - options: [ - { displayName: 'Side', value: 'side' }, - { displayName: 'Top', value: 'top' }, - ], - accordian: 'label', - }, - direction: { - type: 'switch', - displayName: '', - validation: { schema: { type: 'string' }, defaultValue: 'left' }, - showLabel: false, - isIcon: true, - options: [ - { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, - { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, - ], - accordian: 'label', - isFxNotRequired: true, - }, - width: { - type: 'slider', - displayName: 'Width', - accordian: 'label', - conditionallyRender: { - key: 'alignment', - value: 'side', - }, - isFxNotRequired: true, - }, - auto: { - type: 'checkbox', - displayName: 'auto', - showLabel: false, - validation: { schema: { type: 'boolean' }, defaultValue: true }, - accordian: 'label', - conditionallyRender: { - key: 'alignment', - value: 'side', - }, - isFxNotRequired: true, - }, - - backgroundColor: { - type: 'color', - displayName: 'Background', - validation: { schema: { type: 'string' }, defaultValue: '#fff' }, - accordian: 'field', - }, - borderColor: { - type: 'color', - displayName: 'Border', - validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, - accordian: 'field', - }, - accentColor: { - type: 'color', - displayName: 'Accent', - validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, - accordian: 'field', - }, - textColor: { - type: 'color', - displayName: 'Text', - validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, - accordian: 'field', - }, - errTextColor: { - type: 'color', - displayName: 'Error text', - validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, - accordian: 'field', - }, - icon: { - type: 'icon', - displayName: 'Icon', - validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, - accordian: 'field', - visibility: false, - }, - iconColor: { - type: 'color', - displayName: 'Icon color', - validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, - accordian: 'field', - visibility: false, - showLabel: false, - }, - borderRadius: { - type: 'numberInput', - displayName: 'Border radius', - validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, - accordian: 'field', - }, - boxShadow: { - type: 'boxShadow', - displayName: 'Box Shadow', - validation: { - schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, - defaultValue: '0px 0px 0px 0px #00000040', - }, - accordian: 'field', - }, - padding: { - type: 'switch', - displayName: 'Padding', - validation: { - schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, - defaultValue: 'default', - }, - isFxNotRequired: true, - options: [ - { displayName: 'Default', value: 'default' }, - { displayName: 'None', value: 'none' }, - ], - accordian: 'container', - }, - }, - exposedVariables: { - value: '', - isMandatory: false, - isVisible: true, - isDisabled: false, - isLoading: false, - }, - actions: [ - { - handle: 'setText', - displayName: 'Set text', - params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], - }, - { - handle: 'clear', - displayName: 'Clear', - }, - { - handle: 'setFocus', - displayName: 'Set focus', - }, - { - handle: 'setBlur', - displayName: 'Set blur', - }, - { - handle: 'disable', - displayName: 'Disable(deprecated)', - params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], - }, - { - handle: 'visibility', - displayName: 'Visibility(deprecated)', - params: [{ handle: 'visibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], - }, - { - handle: 'setVisibility', - displayName: 'Set visibility', - params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], - }, - { - handle: 'setDisable', - displayName: 'Set disable', - params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], - }, - { - handle: 'setLoading', - displayName: 'Set loading', - params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], - }, - ], - definition: { - validation: { - mandatory: { value: '{{false}}' }, - regex: { value: '' }, - minLength: { value: '' }, - maxLength: { value: '' }, - customRule: { value: '' }, - }, - - others: { - showOnDesktop: { value: '{{true}}' }, - showOnMobile: { value: '{{false}}' }, - }, - properties: { - value: { value: '' }, - label: { value: 'Label' }, - placeholder: { value: 'Enter your input' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, - loadingState: { value: '{{false}}' }, - tooltip: { value: '' }, - }, - events: [], - styles: { - textColor: { value: '#1B1F24' }, - borderColor: { value: '#CCD1D5' }, - accentColor: { value: '#4368E3' }, - errTextColor: { value: '#D72D39' }, - borderRadius: { value: '{{6}}' }, - backgroundColor: { value: '#fff' }, - iconColor: { value: '#CFD3D859' }, - direction: { value: 'left' }, - width: { value: '{{33}}' }, - alignment: { value: 'side' }, - color: { value: '#1B1F24' }, - auto: { value: '{{true}}' }, - padding: { value: 'default' }, - boxShadow: { value: '0px 0px 0px 0px #00000040' }, - icon: { value: 'IconHome2' }, - iconVisibility: { value: false }, - }, - }, -}; From 57b4d005609761c15afcb1b02a81055bc7a7fac7 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Mon, 10 Mar 2025 14:22:03 +0530 Subject: [PATCH 05/23] Merge conflicts resolved --- .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 2 + .../ComponentsManagerTab.jsx | 1 + .../Inspector/Components/DefaultComponent.jsx | 2 + .../RightSideBar/Inspector/Inspector.jsx | 2 + .../RightSideBar/Inspector/Utils.js | 1 + .../WidgetManager/configs/widgetConfig.js | 2 + .../WidgetManager/widgets/emailinput.js | 292 ++++++++++++++++++ .../AppBuilder/WidgetManager/widgets/index.js | 2 + .../src/AppBuilder/Widgets/EmailInput.jsx | 12 + .../src/AppBuilder/_helpers/editorHelpers.js | 2 + .../_stores/slices/componentsSlice.js | 14 +- .../apps/services/widget-config/emailinput.js | 292 ++++++++++++++++++ .../apps/services/widget-config/index.js | 2 + 13 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/emailinput.js create mode 100644 frontend/src/AppBuilder/Widgets/EmailInput.jsx create mode 100644 server/src/modules/apps/services/widget-config/emailinput.js diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index 4ab850082f..e7c2f8c61c 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -12,6 +12,7 @@ const shouldAddBoxShadowAndVisibility = [ 'TextInput', 'TextArea', 'PasswordInput', + 'EmailInput', 'NumberInput', 'Text', 'Checkbox', @@ -87,6 +88,7 @@ const RenderWidget = ({ ...{ widgetValue: value }, ...{ validationObject: unResolvedValidation }, customResolveObjects: customResolvables, + componentType, }), // eslint-disable-next-line react-hooks/exhaustive-deps [validateWidget, customResolvables, unResolvedValidation, resolvedValidation] diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx index 6c915a650e..e51a25a7da 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx @@ -119,6 +119,7 @@ export const ComponentsManagerTab = ({ darkMode }) => { 'NumberInput', 'PasswordInput', 'TextArea', + 'EmailInput', 'ToggleSwitchV2', 'DropdownV2', 'MultiselectV2', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx index 7033933aa2..b0032e1197 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx @@ -17,6 +17,7 @@ const SHOW_ADDITIONAL_ACTIONS = [ 'TextArea', 'NumberInput', 'PasswordInput', + 'EmailInput', 'ToggleSwitchV2', 'Checkbox', 'DropdownV2', @@ -123,6 +124,7 @@ export const baseComponentProperties = ( 'TextInput', 'PasswordInput', 'TextArea', + 'EmailInput', 'NumberInput', 'Text', 'Table', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx index c37881c687..60325ef372 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx @@ -36,6 +36,7 @@ import { EMPTY_ARRAY } from '@/_stores/editorStore'; import { Select } from './Components/Select'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; +// import { componentTypes } from '@/Editor/WidgetManager/components'; import { componentTypes } from '@/AppBuilder/WidgetManager/componentTypes'; import { copyComponents } from '@/AppBuilder/AppCanvas/appCanvasUtils.js'; import DatetimePickerV2 from './Components/DatetimePickerV2.jsx'; @@ -68,6 +69,7 @@ const NEW_REVAMPED_COMPONENTS = [ 'TextInput', 'TextArea', 'PasswordInput', + 'EmailInput', 'NumberInput', 'Table', 'ToggleSwitchV2', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js index 62ee032172..67d3581231 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js @@ -43,6 +43,7 @@ export function renderCustomStyles( componentConfig.component == 'TextInput' || componentConfig.component == 'NumberInput' || componentConfig.component == 'PasswordInput' || + componentConfig.component == 'EmailInput' || componentConfig.component == 'ToggleSwitchV2' || componentConfig.component == 'Checkbox' || componentConfig.component == 'Table' || diff --git a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js index be2855c476..901538bfc2 100644 --- a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js +++ b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js @@ -58,6 +58,7 @@ import { linkConfig, iconConfig, boundedBoxConfig, + emailinputConfig, } from '../widgets'; export const widgets = [ @@ -70,6 +71,7 @@ export const widgets = [ textinputConfig, numberinputConfig, passinputConfig, + emailinputConfig, datepickerConfig, datetimePickerV2Config, datePickerV2Config, diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/emailinput.js b/frontend/src/AppBuilder/WidgetManager/widgets/emailinput.js new file mode 100644 index 0000000000..825340616f --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/emailinput.js @@ -0,0 +1,292 @@ +export const emailinputConfig = { + name: 'EmailInput', + displayName: 'Email Input', + description: 'Email input field', + component: 'EmailInput', + defaultSize: { + width: 10, + height: 40, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + label: { + type: 'code', + displayName: 'Label', + validation: { schema: { type: 'string' }, defaultValue: 'Label' }, + }, + placeholder: { + type: 'code', + displayName: 'Placeholder', + validation: { + schema: { type: 'string' }, + defaultValue: 'Enter email', + }, + }, + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + defaultValue: 'Default value', + }, + }, + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + }, + events: { + onChange: { displayName: 'On change' }, + onEnterPressed: { displayName: 'On enter pressed' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' }, defaultValue: 'side' }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'label', + isFxNotRequired: true, + }, + width: { + type: 'slider', + displayName: 'Width', + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + + backgroundColor: { + type: 'color', + displayName: 'Background', + validation: { schema: { type: 'string' }, defaultValue: '#fff' }, + accordian: 'field', + }, + borderColor: { + type: 'color', + displayName: 'Border', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + }, + accentColor: { + type: 'color', + displayName: 'Accent', + validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text', + validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' }, defaultValue: 'IconMailFilled' }, + accordian: 'field', + visibility: true, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + visibility: false, + showLabel: false, + }, + borderRadius: { + type: 'numberInput', + displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', + }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', + }, + }, + exposedVariables: { + value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, + }, + actions: [ + { + handle: 'setText', + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], + }, + { + handle: 'clear', + displayName: 'Clear', + }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + ], + definition: { + validation: { + mandatory: { value: '{{false}}' }, + regex: { value: '' }, + minLength: { value: '' }, + maxLength: { value: '' }, + customRule: { value: '' }, + }, + + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + value: { value: '' }, + label: { value: 'Label' }, + placeholder: { value: 'Enter email' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, + }, + events: [], + styles: { + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, + accentColor: { value: '#4368E3' }, + errTextColor: { value: '#D72D39' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + iconColor: { value: '#CCD1D5' }, + direction: { value: 'left' }, + width: { value: '{{33}}' }, + alignment: { value: 'side' }, + color: { value: '#1B1F24' }, + auto: { value: '{{true}}' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconMailFilled' }, + iconVisibility: { value: true }, + }, + }, +}; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/index.js b/frontend/src/AppBuilder/WidgetManager/widgets/index.js index ce0e73fdf5..f916541b8f 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/index.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/index.js @@ -58,6 +58,7 @@ import { kanbanBoardConfig } from './kanbanBoard'; import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; +import { emailinputConfig } from './emailinput'; export { buttonConfig, @@ -73,6 +74,7 @@ export { datetimePickerV2Config, datePickerV2Config, timePickerConfig, + emailinputConfig, checkboxConfig, radiobuttonConfig, //!Depreciated radiobuttonV2Config, diff --git a/frontend/src/AppBuilder/Widgets/EmailInput.jsx b/frontend/src/AppBuilder/Widgets/EmailInput.jsx new file mode 100644 index 0000000000..90061b0a66 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/EmailInput.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { BaseInput } from './BaseComponents/BaseInput'; +import { useInput } from './BaseComponents/hooks/useInput'; + +export const EmailInput = (props) => { + const inputLogic = useInput(props); + const additionalInputProps = { + autocomplete: 'email', + name: 'email', + }; + return ; +}; diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 4665ed21ad..7a69d09ce9 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -30,6 +30,7 @@ import { StarRating } from '@/Editor/Components/StarRating'; import { Divider } from '@/Editor/Components/Divider'; import { FilePicker } from '@/Editor/Components/FilePicker'; import { PasswordInput } from '@/AppBuilder/Widgets/PasswordInput'; +import { EmailInput } from '@/Editor/Components/EmailInput'; // import { Calendar } from '@/Editor/Components/Calendar'; // import { Listview } from '@/Editor/Components/Listview'; import { IFrame } from '@/Editor/Components/IFrame'; @@ -118,6 +119,7 @@ export const AllComponents = { Divider, FilePicker, PasswordInput, + EmailInput, Calendar, IFrame, CodeEditor, diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index d2fbddf442..57ee6b842f 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -439,7 +439,7 @@ export const createComponentsSlice = (set, get) => ({ } }, - validateWidget: ({ validationObject, widgetValue, customResolveObjects }) => { + validateWidget: ({ validationObject, widgetValue, customResolveObjects, componentType }) => { const { getResolvedValue } = get(); let isValid = true; let validationError = null; @@ -455,6 +455,17 @@ export const createComponentsSlice = (set, get) => ({ validationRegex = typeof validationRegex === 'string' ? validationRegex : ''; const re = new RegExp(validationRegex, 'g'); + if (componentType === 'EmailInput' && widgetValue) { + const validationRegex = '^(?!.*\\.\\.)([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$'; + const emailRegex = new RegExp(validationRegex, 'g'); + if (!emailRegex.test(widgetValue)) { + return { + isValid: false, + validationError: 'Input should be a valid email', + }; + } + } + if (!re.test(widgetValue)) { return { isValid: false, @@ -1833,6 +1844,7 @@ export const createComponentsSlice = (set, get) => ({ ![ 'TextInput', 'PasswordInput', + 'EmailInput', 'NumberInput', 'DropdownV2', 'MultiselectV2', diff --git a/server/src/modules/apps/services/widget-config/emailinput.js b/server/src/modules/apps/services/widget-config/emailinput.js new file mode 100644 index 0000000000..825340616f --- /dev/null +++ b/server/src/modules/apps/services/widget-config/emailinput.js @@ -0,0 +1,292 @@ +export const emailinputConfig = { + name: 'EmailInput', + displayName: 'Email Input', + description: 'Email input field', + component: 'EmailInput', + defaultSize: { + width: 10, + height: 40, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + label: { + type: 'code', + displayName: 'Label', + validation: { schema: { type: 'string' }, defaultValue: 'Label' }, + }, + placeholder: { + type: 'code', + displayName: 'Placeholder', + validation: { + schema: { type: 'string' }, + defaultValue: 'Enter email', + }, + }, + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + defaultValue: 'Default value', + }, + }, + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + }, + events: { + onChange: { displayName: 'On change' }, + onEnterPressed: { displayName: 'On enter pressed' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' }, defaultValue: 'side' }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'label', + isFxNotRequired: true, + }, + width: { + type: 'slider', + displayName: 'Width', + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + + backgroundColor: { + type: 'color', + displayName: 'Background', + validation: { schema: { type: 'string' }, defaultValue: '#fff' }, + accordian: 'field', + }, + borderColor: { + type: 'color', + displayName: 'Border', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + }, + accentColor: { + type: 'color', + displayName: 'Accent', + validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text', + validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' }, defaultValue: 'IconMailFilled' }, + accordian: 'field', + visibility: true, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + visibility: false, + showLabel: false, + }, + borderRadius: { + type: 'numberInput', + displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', + }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', + }, + }, + exposedVariables: { + value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, + }, + actions: [ + { + handle: 'setText', + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], + }, + { + handle: 'clear', + displayName: 'Clear', + }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + ], + definition: { + validation: { + mandatory: { value: '{{false}}' }, + regex: { value: '' }, + minLength: { value: '' }, + maxLength: { value: '' }, + customRule: { value: '' }, + }, + + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + value: { value: '' }, + label: { value: 'Label' }, + placeholder: { value: 'Enter email' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, + }, + events: [], + styles: { + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, + accentColor: { value: '#4368E3' }, + errTextColor: { value: '#D72D39' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + iconColor: { value: '#CCD1D5' }, + direction: { value: 'left' }, + width: { value: '{{33}}' }, + alignment: { value: 'side' }, + color: { value: '#1B1F24' }, + auto: { value: '{{true}}' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconMailFilled' }, + iconVisibility: { value: true }, + }, + }, +}; diff --git a/server/src/modules/apps/services/widget-config/index.js b/server/src/modules/apps/services/widget-config/index.js index 34826885e0..968fafac40 100644 --- a/server/src/modules/apps/services/widget-config/index.js +++ b/server/src/modules/apps/services/widget-config/index.js @@ -58,6 +58,7 @@ import { kanbanBoardConfig } from './kanbanBoard'; import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; +import { emailinputConfig } from './emailinput'; const widgets = { buttonConfig, @@ -73,6 +74,7 @@ const widgets = { datetimePickerV2Config, datePickerV2Config, timePickerConfig, + emailinputConfig, checkboxConfig, radiobuttonConfig, //!Depreciated radiobuttonV2Config, From 0add5ccc4d9e9a73ef07374c66289bb42aaa20b6 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Tue, 11 Mar 2025 03:07:12 +0530 Subject: [PATCH 06/23] Fixed incorrect import --- frontend/src/AppBuilder/_helpers/editorHelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 7a69d09ce9..89fd4f8b4d 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -30,7 +30,7 @@ import { StarRating } from '@/Editor/Components/StarRating'; import { Divider } from '@/Editor/Components/Divider'; import { FilePicker } from '@/Editor/Components/FilePicker'; import { PasswordInput } from '@/AppBuilder/Widgets/PasswordInput'; -import { EmailInput } from '@/Editor/Components/EmailInput'; +import { EmailInput } from '@/AppBuilder/Widgets/EmailInput'; // import { Calendar } from '@/Editor/Components/Calendar'; // import { Listview } from '@/Editor/Components/Listview'; import { IFrame } from '@/Editor/Components/IFrame'; From 74c88f31695d375d366e08da909a2afbeee591c9 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 11 Mar 2025 18:30:35 +0530 Subject: [PATCH 07/23] Fixed value of value CSA when no default value is set and clear() action is used. --- frontend/src/AppBuilder/Widgets/NumberInput.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Widgets/NumberInput.jsx b/frontend/src/AppBuilder/Widgets/NumberInput.jsx index 0a30095621..f40288acd9 100644 --- a/frontend/src/AppBuilder/Widgets/NumberInput.jsx +++ b/frontend/src/AppBuilder/Widgets/NumberInput.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { BaseInput } from './BaseComponents/BaseInput'; import { useInput } from './BaseComponents/hooks/useInput'; import SolidIcon from '@/_ui/Icon/SolidIcons'; @@ -145,6 +145,12 @@ export const NumberInput = (props) => { ); + useEffect(() => { + if (isNaN(inputLogic.value) || inputLogic.value === '') { + props.setExposedVariable('value', null); + } + }, [inputLogic.value]); + return ( Date: Wed, 12 Mar 2025 12:31:02 +0530 Subject: [PATCH 08/23] Merge conflicts resolved --- ...48327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/data-migrations/1736448327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts b/server/data-migrations/1736448327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts index 57550a14f6..7fbbc29ad9 100644 --- a/server/data-migrations/1736448327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts +++ b/server/data-migrations/1736448327127-MoveVisibilityDisabledStatesToPropertiesTextarea.ts @@ -1,5 +1,5 @@ import { Component } from 'src/entities/component.entity'; -import { processDataInBatches } from 'src/helpers/utils.helper'; +import { processDataInBatches } from '@helpers/migration.helper'; import { EntityManager, MigrationInterface, QueryRunner } from 'typeorm'; export class MoveVisibilityDisabledStatesToPropertiesTextarea1736448327127 implements MigrationInterface { From 7ed8449ff927ca9275b0add9f9c10266581ad2c6 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Tue, 18 Mar 2025 21:53:08 +0530 Subject: [PATCH 09/23] Phone input base --- .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 1 + .../ComponentsManagerTab.jsx | 1 + .../Inspector/Components/DefaultComponent.jsx | 2 + .../RightSideBar/Inspector/Inspector.jsx | 1 + .../RightSideBar/Inspector/Utils.js | 1 + .../WidgetManager/configs/widgetConfig.js | 2 + .../AppBuilder/WidgetManager/widgets/index.js | 2 + .../WidgetManager/widgets/phoneinput.js | 303 ++++++++++++++++++ .../src/AppBuilder/Widgets/PhoneInput.jsx | 8 + .../src/AppBuilder/_helpers/editorHelpers.js | 2 + .../_stores/slices/componentsSlice.js | 1 + .../apps/services/widget-config/index.js | 2 + .../apps/services/widget-config/phoneinput.js | 303 ++++++++++++++++++ 13 files changed, 629 insertions(+) create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js create mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput.jsx create mode 100644 server/src/modules/apps/services/widget-config/phoneinput.js diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index 19c6b2b613..b95103bdc8 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -13,6 +13,7 @@ const shouldAddBoxShadowAndVisibility = [ 'TextArea', 'PasswordInput', 'EmailInput', + 'PhoneInput', 'NumberInput', 'Text', 'Checkbox', diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx index e51a25a7da..60eeaff396 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx @@ -120,6 +120,7 @@ export const ComponentsManagerTab = ({ darkMode }) => { 'PasswordInput', 'TextArea', 'EmailInput', + 'PhoneInput', 'ToggleSwitchV2', 'DropdownV2', 'MultiselectV2', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx index b0032e1197..a17cbde145 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx @@ -18,6 +18,7 @@ const SHOW_ADDITIONAL_ACTIONS = [ 'NumberInput', 'PasswordInput', 'EmailInput', + 'PhoneInput', 'ToggleSwitchV2', 'Checkbox', 'DropdownV2', @@ -125,6 +126,7 @@ export const baseComponentProperties = ( 'PasswordInput', 'TextArea', 'EmailInput', + 'PhoneInput', 'NumberInput', 'Text', 'Table', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx index 60325ef372..96b853486f 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx @@ -70,6 +70,7 @@ const NEW_REVAMPED_COMPONENTS = [ 'TextArea', 'PasswordInput', 'EmailInput', + 'PhoneInput', 'NumberInput', 'Table', 'ToggleSwitchV2', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js index 67d3581231..2b20c667d4 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js @@ -44,6 +44,7 @@ export function renderCustomStyles( componentConfig.component == 'NumberInput' || componentConfig.component == 'PasswordInput' || componentConfig.component == 'EmailInput' || + componentConfig.component == 'PhoneInput' || componentConfig.component == 'ToggleSwitchV2' || componentConfig.component == 'Checkbox' || componentConfig.component == 'Table' || diff --git a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js index 901538bfc2..bdc7d2482f 100644 --- a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js +++ b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js @@ -59,6 +59,7 @@ import { iconConfig, boundedBoxConfig, emailinputConfig, + phoneinputConfig, } from '../widgets'; export const widgets = [ @@ -72,6 +73,7 @@ export const widgets = [ numberinputConfig, passinputConfig, emailinputConfig, + phoneinputConfig, datepickerConfig, datetimePickerV2Config, datePickerV2Config, diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/index.js b/frontend/src/AppBuilder/WidgetManager/widgets/index.js index f916541b8f..64030765f8 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/index.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/index.js @@ -59,6 +59,7 @@ import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; import { emailinputConfig } from './emailinput'; +import { phoneinputConfig } from './phoneinput'; export { buttonConfig, @@ -75,6 +76,7 @@ export { datePickerV2Config, timePickerConfig, emailinputConfig, + phoneinputConfig, checkboxConfig, radiobuttonConfig, //!Depreciated radiobuttonV2Config, diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js b/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js new file mode 100644 index 0000000000..c1567a9b87 --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js @@ -0,0 +1,303 @@ +export const phoneinputConfig = { + name: 'PhoneInput', + displayName: 'Phone Input', + description: 'Phone input field', + component: 'PhoneInput', + defaultSize: { + width: 10, + height: 40, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + label: { + type: 'code', + displayName: 'Label', + validation: { schema: { type: 'string' }, defaultValue: 'Label' }, + }, + placeholder: { + type: 'code', + displayName: 'Placeholder', + validation: { + schema: { type: 'string' }, + defaultValue: 'Enter your input', + }, + }, + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + defaultValue: 'Default value', + }, + }, + + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + }, + events: { + onChange: { displayName: 'On change' }, + onEnterPressed: { displayName: 'On enter pressed' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' }, defaultValue: 'side' }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'label', + isFxNotRequired: true, + }, + width: { + type: 'slider', + displayName: 'Width', + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + + backgroundColor: { + type: 'color', + displayName: 'Background', + validation: { schema: { type: 'string' }, defaultValue: '#fff' }, + accordian: 'field', + }, + borderColor: { + type: 'color', + displayName: 'Border', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + }, + accentColor: { + type: 'color', + displayName: 'Accent', + validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text', + validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, + accordian: 'field', + visibility: false, + showLabel: false, + }, + borderRadius: { + type: 'numberInput', + displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', + }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', + }, + }, + exposedVariables: { + value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, + }, + actions: [ + { + handle: 'setText', + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], + }, + { + handle: 'clear', + displayName: 'Clear', + }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'disable', + displayName: 'Disable(deprecated)', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'visibility', + displayName: 'Visibility(deprecated)', + params: [{ handle: 'visibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + ], + definition: { + validation: { + mandatory: { value: '{{false}}' }, + regex: { value: '' }, + minLength: { value: '' }, + maxLength: { value: '' }, + customRule: { value: '' }, + }, + + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + value: { value: '' }, + label: { value: 'Label' }, + placeholder: { value: 'Enter your phone' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, + }, + events: [], + styles: { + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, + accentColor: { value: '#4368E3' }, + errTextColor: { value: '#D72D39' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + iconColor: { value: '#CFD3D859' }, + direction: { value: 'left' }, + width: { value: '{{33}}' }, + alignment: { value: 'side' }, + color: { value: '#1B1F24' }, + auto: { value: '{{true}}' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconHome2' }, + iconVisibility: { value: false }, + }, + }, +}; diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput.jsx new file mode 100644 index 0000000000..3a73d2209d --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/PhoneInput.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { default as ReactPhoneInput } from 'react-phone-input-2'; +import 'react-phone-input-2/lib/material.css'; + +export const PhoneInput = (props) => { + const [value, setValue] = React.useState(); + return ; +}; diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 4a065f4cb0..53906f7d4a 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -31,6 +31,7 @@ import { Divider } from '@/Editor/Components/Divider'; import { FilePicker } from '@/Editor/Components/FilePicker'; import { PasswordInput } from '@/AppBuilder/Widgets/PasswordInput'; import { EmailInput } from '@/AppBuilder/Widgets/EmailInput'; +import { PhoneInput } from '@/AppBuilder/Widgets/PhoneInput'; // import { Calendar } from '@/Editor/Components/Calendar'; // import { Listview } from '@/Editor/Components/Listview'; import { IFrame } from '@/Editor/Components/IFrame'; @@ -120,6 +121,7 @@ export const AllComponents = { FilePicker, PasswordInput, EmailInput, + PhoneInput, Calendar, IFrame, CodeEditor, diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 57ee6b842f..249c3b175f 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -1845,6 +1845,7 @@ export const createComponentsSlice = (set, get) => ({ 'TextInput', 'PasswordInput', 'EmailInput', + 'PhoneInput', 'NumberInput', 'DropdownV2', 'MultiselectV2', diff --git a/server/src/modules/apps/services/widget-config/index.js b/server/src/modules/apps/services/widget-config/index.js index 968fafac40..2e0840d7b9 100644 --- a/server/src/modules/apps/services/widget-config/index.js +++ b/server/src/modules/apps/services/widget-config/index.js @@ -59,6 +59,7 @@ import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; import { emailinputConfig } from './emailinput'; +import { phoneinputConfig } from './phoneinput'; const widgets = { buttonConfig, @@ -75,6 +76,7 @@ const widgets = { datePickerV2Config, timePickerConfig, emailinputConfig, + phoneinputConfig, checkboxConfig, radiobuttonConfig, //!Depreciated radiobuttonV2Config, diff --git a/server/src/modules/apps/services/widget-config/phoneinput.js b/server/src/modules/apps/services/widget-config/phoneinput.js new file mode 100644 index 0000000000..c1567a9b87 --- /dev/null +++ b/server/src/modules/apps/services/widget-config/phoneinput.js @@ -0,0 +1,303 @@ +export const phoneinputConfig = { + name: 'PhoneInput', + displayName: 'Phone Input', + description: 'Phone input field', + component: 'PhoneInput', + defaultSize: { + width: 10, + height: 40, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + label: { + type: 'code', + displayName: 'Label', + validation: { schema: { type: 'string' }, defaultValue: 'Label' }, + }, + placeholder: { + type: 'code', + displayName: 'Placeholder', + validation: { + schema: { type: 'string' }, + defaultValue: 'Enter your input', + }, + }, + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + defaultValue: 'Default value', + }, + }, + + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + }, + events: { + onChange: { displayName: 'On change' }, + onEnterPressed: { displayName: 'On enter pressed' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' }, defaultValue: 'side' }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'label', + isFxNotRequired: true, + }, + width: { + type: 'slider', + displayName: 'Width', + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + + backgroundColor: { + type: 'color', + displayName: 'Background', + validation: { schema: { type: 'string' }, defaultValue: '#fff' }, + accordian: 'field', + }, + borderColor: { + type: 'color', + displayName: 'Border', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + }, + accentColor: { + type: 'color', + displayName: 'Accent', + validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text', + validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, + accordian: 'field', + visibility: false, + showLabel: false, + }, + borderRadius: { + type: 'numberInput', + displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', + }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', + }, + }, + exposedVariables: { + value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, + }, + actions: [ + { + handle: 'setText', + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], + }, + { + handle: 'clear', + displayName: 'Clear', + }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'disable', + displayName: 'Disable(deprecated)', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'visibility', + displayName: 'Visibility(deprecated)', + params: [{ handle: 'visibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + ], + definition: { + validation: { + mandatory: { value: '{{false}}' }, + regex: { value: '' }, + minLength: { value: '' }, + maxLength: { value: '' }, + customRule: { value: '' }, + }, + + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + value: { value: '' }, + label: { value: 'Label' }, + placeholder: { value: 'Enter your phone' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, + }, + events: [], + styles: { + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, + accentColor: { value: '#4368E3' }, + errTextColor: { value: '#D72D39' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + iconColor: { value: '#CFD3D859' }, + direction: { value: 'left' }, + width: { value: '{{33}}' }, + alignment: { value: 'side' }, + color: { value: '#1B1F24' }, + auto: { value: '{{true}}' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconHome2' }, + iconVisibility: { value: false }, + }, + }, +}; From ccc14a030c469e46017d2ea366580f8b22467e80 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 19 Mar 2025 02:31:37 +0530 Subject: [PATCH 10/23] Styles added --- .../WidgetManager/widgets/phoneinput.js | 25 +-- .../Widgets/BaseComponents/hooks/useInput.js | 11 ++ .../src/AppBuilder/Widgets/PhoneInput.jsx | 152 +++++++++++++++++- frontend/src/_styles/theme.scss | 52 ++++++ .../apps/services/widget-config/phoneinput.js | 25 +-- 5 files changed, 224 insertions(+), 41 deletions(-) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js b/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js index c1567a9b87..b0e5fa3b72 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js @@ -35,7 +35,11 @@ export const phoneinputConfig = { defaultValue: 'Default value', }, }, - + isCountryChangeEnabled: { + type: 'toggle', + displayName: 'Enable country change', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + }, loadingState: { type: 'toggle', displayName: 'Loading state', @@ -162,21 +166,6 @@ export const phoneinputConfig = { validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, accordian: 'field', }, - icon: { - type: 'icon', - displayName: 'Icon', - validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, - accordian: 'field', - visibility: false, - }, - iconColor: { - type: 'color', - displayName: 'Icon color', - validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, - accordian: 'field', - visibility: false, - showLabel: false, - }, borderRadius: { type: 'numberInput', displayName: 'Border radius', @@ -279,6 +268,7 @@ export const phoneinputConfig = { disabledState: { value: '{{false}}' }, loadingState: { value: '{{false}}' }, tooltip: { value: '' }, + isCountryChangeEnabled: { value: '{{true}}' }, }, events: [], styles: { @@ -288,7 +278,6 @@ export const phoneinputConfig = { errTextColor: { value: '#D72D39' }, borderRadius: { value: '{{6}}' }, backgroundColor: { value: '#fff' }, - iconColor: { value: '#CFD3D859' }, direction: { value: 'left' }, width: { value: '{{33}}' }, alignment: { value: 'side' }, @@ -296,8 +285,6 @@ export const phoneinputConfig = { auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, - icon: { value: 'IconHome2' }, - iconVisibility: { value: false }, }, }, }; diff --git a/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js index f298265cc7..a623c3d412 100644 --- a/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js +++ b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js @@ -56,16 +56,27 @@ export const useInput = ({ useEffect(() => { disable !== disabledState && setDisable(disabledState); + if (isInitialRender.current) return; + setExposedVariable('isDisabled', disabledState); }, [disabledState]); useEffect(() => { visibility !== properties.visibility && setVisibility(properties.visibility); + if (isInitialRender.current) return; + setExposedVariable('isVisible', properties.visibility); }, [properties.visibility]); useEffect(() => { loading !== loadingState && setLoading(loadingState); + if (isInitialRender.current) return; + setExposedVariable('isLoading', loadingState); }, [loadingState]); + useEffect(() => { + if (isInitialRender.current) return; + setExposedVariable('isMandatory', isMandatory); + }, [isMandatory]); + useEffect(() => { if (isInitialRender.current) return; const validationStatus = validate(value); diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput.jsx index 3a73d2209d..53eabe3e0f 100644 --- a/frontend/src/AppBuilder/Widgets/PhoneInput.jsx +++ b/frontend/src/AppBuilder/Widgets/PhoneInput.jsx @@ -1,8 +1,154 @@ import React from 'react'; import { default as ReactPhoneInput } from 'react-phone-input-2'; -import 'react-phone-input-2/lib/material.css'; +import 'react-phone-input-2/lib/style.css'; +import { useInput } from './BaseComponents/hooks/useInput'; +import Loader from '@/ToolJetUI/Loader/Loader'; +import Label from '@/_ui/Label'; export const PhoneInput = (props) => { - const [value, setValue] = React.useState(); - return ; + const { properties, styles, componentName, darkMode } = props; + const inputLogic = useInput(props); + const { + inputRef, + labelRef, + value, + visibility, + loading, + disable, + validationStatus, + showValidationError, + isFocused, + labelWidth, + iconVisibility, + setIconVisibility, + isValid, + validationError, + isMandatory, + setInputValue, + handleChange, + handleBlur, + handleFocus, + handleKeyUp, + } = inputLogic; + const { label, placeholder, isCountryChangeEnabled } = properties; + const { + textColor, + backgroundColor, + alignment, + width, + direction, + auto, + color, + borderColor, + accentColor, + errTextColor, + } = styles; + const _width = (width / 100) * 70; + const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; + + const inputBorderColor = isFocused + ? accentColor != '4368E3' + ? accentColor + : 'var(--primary-accent-strong)' + : borderColor != '#CCD1D5' + ? borderColor + : disable || loading + ? '1px solid var(--borders-disabled-on-white)' + : 'var(--borders-default)'; + + const inputStyle = { + color: textColor, + backgroundColor, + border: `${isFocused ? '1.5px' : '1px'} solid ${inputBorderColor}`, + }; + + const buttonStyle = { + backgroundColor: backgroundColor, + border: `${isFocused ? '1.5px' : '1px'} solid ${inputBorderColor}`, + }; + + 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, + }; + + return ( + <> +
+
+ {showValidationError && visibility && ( +
+ {validationError} +
+ )} + + ); }; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index a3cd170f30..0259451ab9 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18629,3 +18629,55 @@ section.ai-message-prompt-input-wrapper { } } } + + +.react-tel-input { + height:100% !important; + width: 100% !important; + + .country-list { + scrollbar-width: none; + width: 208px; + height:236px; + border-radius: 8px; + margin-top: 3px !important; + } + + + .form-control { + height:100% !important; + width: 100% !important; + // border: 1px solid var(--border-weak, #E4E7EB) !important; + } + + .flag-dropdown { + border-right: 1px solid #CCD1D5 !important; + } + + .search { + width:100%; + padding: 0px !important; + + .search-emoji { + display: none; + } + + .search-box { + border: 0px; + border-bottom: 1px solid var(--border-weak, #ccd1d5); + width:100%; + margin-left: 0 !important; + height: 36px; + } + } + + .country { + min-height:32px; + font-size: 14px; + font-family: IBM Plex Sans; + font-weight: 400; + padding: 6px 6px !important; + padding-left: 22px !important; + color: #1B1F24; + } +} \ No newline at end of file diff --git a/server/src/modules/apps/services/widget-config/phoneinput.js b/server/src/modules/apps/services/widget-config/phoneinput.js index c1567a9b87..b0e5fa3b72 100644 --- a/server/src/modules/apps/services/widget-config/phoneinput.js +++ b/server/src/modules/apps/services/widget-config/phoneinput.js @@ -35,7 +35,11 @@ export const phoneinputConfig = { defaultValue: 'Default value', }, }, - + isCountryChangeEnabled: { + type: 'toggle', + displayName: 'Enable country change', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + }, loadingState: { type: 'toggle', displayName: 'Loading state', @@ -162,21 +166,6 @@ export const phoneinputConfig = { validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, accordian: 'field', }, - icon: { - type: 'icon', - displayName: 'Icon', - validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, - accordian: 'field', - visibility: false, - }, - iconColor: { - type: 'color', - displayName: 'Icon color', - validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, - accordian: 'field', - visibility: false, - showLabel: false, - }, borderRadius: { type: 'numberInput', displayName: 'Border radius', @@ -279,6 +268,7 @@ export const phoneinputConfig = { disabledState: { value: '{{false}}' }, loadingState: { value: '{{false}}' }, tooltip: { value: '' }, + isCountryChangeEnabled: { value: '{{true}}' }, }, events: [], styles: { @@ -288,7 +278,6 @@ export const phoneinputConfig = { errTextColor: { value: '#D72D39' }, borderRadius: { value: '{{6}}' }, backgroundColor: { value: '#fff' }, - iconColor: { value: '#CFD3D859' }, direction: { value: 'left' }, width: { value: '{{33}}' }, alignment: { value: 'side' }, @@ -296,8 +285,6 @@ export const phoneinputConfig = { auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, - icon: { value: 'IconHome2' }, - iconVisibility: { value: false }, }, }, }; From e9dc0f57107674a5bced1d3b654a64b0ecccca13 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 20 Mar 2025 03:08:38 +0530 Subject: [PATCH 11/23] Phone input done --- .../Components/PhoneInput/PhoneInput.jsx | 96 ++ .../Inspector/Components/PhoneInput/en.js | 239 ++++ .../RightSideBar/Inspector/Inspector.jsx | 3 + .../WidgetManager/widgets/phoneinput.js | 16 +- .../Widgets/BaseComponents/hooks/useInput.js | 4 +- .../src/AppBuilder/Widgets/PhoneInput.jsx | 50 +- frontend/src/_styles/theme.scss | 1102 ++++++++++++++++- .../Configuration.scss | 8 + .../apps/services/widget-config/phoneinput.js | 16 +- 9 files changed, 1464 insertions(+), 70 deletions(-) create mode 100644 frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx create mode 100644 frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/en.js diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx new file mode 100644 index 0000000000..65ccf82591 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react'; +import Accordion from '@/_ui/Accordion'; +import { baseComponentProperties } from '../DefaultComponent'; +import Select from '@/_ui/Select'; +import useStore from '@/AppBuilder/_stores/store'; +import { countries } from './en'; + +export const PhoneInput = ({ componentMeta, darkMode, ...restProps }) => { + const { + layoutPropertyChanged, + component, + paramUpdated, + dataQueries, + currentState, + eventsChanged, + apps, + allComponents, + } = restProps; + + const properties = Object.keys(componentMeta.properties); + const events = Object.keys(componentMeta.events); + const validations = Object.keys(componentMeta.validation || {}); + const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties); + const defaultCountry = resolvedProperties?.defaultCountry || 'None'; + + const renderCustomOption = ({ label, value: optionValue }) => { + const optionStyle = { + display: 'flex', + alignItems: 'center', + justifyContent: 'start', + height: '18px', + gap: '6px', + cursor: 'pointer', + fontFamily: 'IBM Plex Sans', + fontSize: '12px', + lineHeight: '18px', + fontWeight: '400', + color: darkMode ? '#fff' : '#1B1F24', + }; + + return ( +
+
+ {label} +
+ ); + }; + + const getCountryDropdown = () => { + return ( +
+ + { - console.log('value', value); - paramUpdated({ name: 'defaultCountry' }, 'value', value, 'properties'); - }} - /> +
+ +
+ { + paramUpdated({ name: 'dateFormat' }, 'fxActive', !isDefaultCountryFxOn, 'properties'); + }} + /> +
+
+ {isDefaultCountryFxOn ? ( + paramUpdated({ name: 'defaultCountry' }, 'value', value, 'properties')} + /> + ) : ( + { + onInputChange(e.currentTarget.value, { + action: 'input-change', + }); + }} + onMouseDown={(e) => { + e.stopPropagation(); + e.target.focus(); + }} + onTouchEnd={(e) => { + e.stopPropagation(); + e.target.focus(); + }} + /> +
+ + + {children?.length > 0 ? children :
No options
} +
+ + ); + }; + + const CountrySelect = ({ value, onChange, options, ...rest }) => { + const [menuIsOpen, setMenuIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setMenuIsOpen(false); + } + }; + + // Add event listener when dropdown is open + if (menuIsOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + // Clean up the event listener + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [menuIsOpen]); + + const customStyles = { + container: (provided) => ({ + ...provided, + minWidth: !isCountryChangeEnabled || disabledState ? '77px' : '87px', + width: !isCountryChangeEnabled || disabledState ? '77px' : '87px', + height: '100%', + }), + control: (provided, state) => ({ + ...provided, + minHeight: '0px', + height: '100%', + borderTopLeftRadius: `${borderRadius}px`, + borderBottomLeftRadius: `${borderRadius}px`, + borderTopRightRadius: '0px', + borderBottomRightRadius: '0px', + borderColor: `${ + !isValid && showValidationError ? 'var(--status-error-strong)' : computedStyles?.borderColor + } !important`, + backgroundColor: `${ + isCountryChangeEnabled + ? computedStyles?.backgroundColor + : darkMode + ? 'var(--surfaces-app-bg-default)' + : 'var(--surfaces-surface-03)' + } !important`, + }), + menu: (provided) => ({ + ...provided, + width: '208px', + height: '236px', + borderRadius: '8px', + marginTop: '2px', + }), + menuList: (provided) => ({ + ...provided, + maxHeight: '196px', + overflowY: 'auto', + scrollbarWidth: 'none', + gap: '1px', + padding: '8px', + borderRadius: '0px 0px 8px 8px', + display: 'flex', + flexDirection: 'column', + backgroundColor: 'var(--surfaces-surface-01)', + }), + option: (provided, state) => ({ + ...provided, + backgroundColor: state.isSelected ? '#4368E31A' : 'var(--surfaces-surface-01)', + ...(state.isSelected && { borderRadius: '8px' }), + '&:hover': { + backgroundColor: 'var(--interactive-overlays-fill-hover)', + borderRadius: '8px', + }, + display: 'flex', + cursor: 'pointer', + padding: '1px 14px', + }), + }; + + return ( +
setMenuIsOpen((prev) => !prev)} ref={dropdownRef}> + +
{loading && } {showValidationError && visibility && ( diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 5fffeaf6fa..8294910557 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -15750,7 +15750,7 @@ textarea.tj-text-input-widget{ background-color: var(--surfaces-surface-03); } - &:hover:not(:focus) { + &:hover:not(:focus):not([data-ignore-hover="true"]) { border: 1px solid var(--tblr-input-border-color-darker) !important; } @@ -18631,1085 +18631,7 @@ section.ai-message-prompt-input-wrapper { } .phone-input-widget { - - -.selected-flag { - padding: 0px 0px 0px 5px !important; - background-color: transparent !important; -} - - - - .selected-flag .flag { - margin-top: -11.5px; - margin-left: 4px; - transform: scale(0.9); - } - - .react-tel-input { - height:100% !important; - width: 100% !important; - - .flag-dropdown{ - width:64px; - } - - .form-control { - padding-left:74px; - } - - .arrow{ - left:38px !important; - top:60% !important; - transform: scale(1.4); - } - - - - .country-list { - scrollbar-width: none; - width: 320px; - height:236px; - border-radius: 8px; - margin-top: 3px !important; - box-shadow: 0px 8px 16px 0px var(--slate6); - } - - - - .form-control { - height:100% !important; - width: 100% !important; - // border: 1px solid var(--border-weak, #E4E7EB) !important; - } - - .flag-dropdown { - border-right: 1px solid #CCD1D5 !important; - } - - .search { - width:100%; - padding: 0px !important; - border-bottom: 1px solid var(--border-weak, #ccd1d5); - - &::before { - content: "\1F50E"; // Correct magnifying glass icon - position: absolute; - left: 20px; - top: 47%; - transform: translateY(-50%); - font-size: 16px; - color: #888; - } - - - .search-emoji { - display: none; - } - - .search-box { - border: 0px; - border-radius:0px; - width:100%; - margin-left: 0 !important; - height: 36px; - padding-left: 50px; - } - - } - - .country { - min-height:32px; - font-size: 14px; - font-family: IBM Plex Sans; - font-weight: 400; - padding: 6px 6px !important; - padding-left: 22px !important; - - } - } -} - - -.phone-input-widget,.custom-phone-input-options { - - .ad { - background-position: -48px -24px -} - - .ae { - background-position: -72px -24px -} - - .af { - background-position: -96px -24px -} - - .ag { - background-position: -120px -24px -} - - .ai { - background-position: -144px -24px -} - - .al { - background-position: -168px -24px -} - - .am { - background-position: -192px -24px -} - - .an { - background-position: -216px -24px -} - - .ao { - background-position: -240px -24px -} - - .aq { - background-position: -264px -24px -} - - .ar { - background-position: -288px -24px -} - - .as { - background-position: -312px -24px -} - - .at { - background-position: -336px -24px -} - - .au { - background-position: -360px -24px -} - - .aw { - background-position: -384px -24px -} - - .ax { - background-position: 0 -48px -} - - .az { - background-position: -24px -48px -} - - .ba { - background-position: -48px -48px -} - - .bb { - background-position: -72px -48px -} - - .bd { - background-position: -96px -48px -} - - .be { - background-position: -120px -48px -} - - .bf { - background-position: -144px -48px -} - - .bg { - background-position: -168px -48px -} - - .bh { - background-position: -192px -48px -} - - .bi { - background-position: -216px -48px -} - - .bj { - background-position: -240px -48px -} - - .bl { - background-position: -264px -48px -} - - .bm { - background-position: -288px -48px -} - - .bn { - background-position: -312px -48px -} - - .bo { - background-position: -336px -48px -} - - .br { - background-position: -360px -48px -} - - .bs { - background-position: -384px -48px -} - - .bt { - background-position: 0 -72px -} - - .bw { - background-position: -24px -72px -} - - .by { - background-position: -48px -72px -} - - .bz { - background-position: -72px -72px -} - - .ca { - background-position: -96px -72px -} - - .cc { - background-position: -120px -72px -} - - .cd { - background-position: -144px -72px -} - - .cf { - background-position: -168px -72px -} - - .cg { - background-position: -192px -72px -} - - .ch { - background-position: -216px -72px -} - - .ci { - background-position: -240px -72px -} - - .ck { - background-position: -264px -72px -} - - .cl { - background-position: -288px -72px -} - - .cm { - background-position: -312px -72px -} - - .cn { - background-position: -336px -72px -} - - .co { - background-position: -360px -72px -} - - .cr { - background-position: -384px -72px -} - - .cu { - background-position: 0 -96px -} - - .cv { - background-position: -24px -96px -} - - .cw { - background-position: -48px -96px -} - - .cx { - background-position: -72px -96px -} - - .cy { - background-position: -96px -96px -} - - .cz { - background-position: -120px -96px -} - - .de { - background-position: -144px -96px -} - - .dj { - background-position: -168px -96px -} - - .dk { - background-position: -192px -96px -} - - .dm { - background-position: -216px -96px -} - - .do { - background-position: -240px -96px -} - - .dz { - background-position: -264px -96px -} - - .ec { - background-position: -288px -96px -} - - .ee { - background-position: -312px -96px -} - - .eg { - background-position: -336px -96px -} - - .eh { - background-position: -360px -96px -} - - .er { - background-position: -384px -96px -} - - .es { - background-position: 0 -120px -} - - .et { - background-position: -24px -120px -} - - .eu { - background-position: -48px -120px -} - - .fi { - background-position: -72px -120px -} - - .fj { - background-position: -96px -120px -} - - .fk { - background-position: -120px -120px -} - - .fm { - background-position: -144px -120px -} - - .fo { - background-position: -168px -120px -} - - .fr { - background-position: -192px -120px -} - - .ga { - background-position: -216px -120px -} - - .gb { - background-position: -240px -120px -} - - .gd { - background-position: -264px -120px -} - - .ge { - background-position: -288px -120px -} - - .gg { - background-position: -312px -120px -} - - .gh { - background-position: -336px -120px -} - - .gi { - background-position: -360px -120px -} - - .gl { - background-position: -384px -120px -} - - .gm { - background-position: 0 -144px -} - - .gn { - background-position: -24px -144px -} - - .gq { - background-position: -48px -144px -} - - .gr { - background-position: -72px -144px -} - - .gs { - background-position: -96px -144px -} - - .gt { - background-position: -120px -144px -} - - .gu { - background-position: -144px -144px -} - - .gw { - background-position: -168px -144px -} - - .gy { - background-position: -192px -144px -} - - .hk { - background-position: -216px -144px -} - - .hn { - background-position: -240px -144px -} - - .hr { - background-position: -264px -144px -} - - .ht { - background-position: -288px -144px -} - - .hu { - background-position: -312px -144px -} - - .ic { - background-position: -336px -144px -} - - .id { - background-position: -360px -144px -} - - .ie { - background-position: -384px -144px -} - - .il { - background-position: 0 -168px -} - - .im { - background-position: -24px -168px -} - - .in { - background-position: -48px -168px -} - - .iq { - background-position: -72px -168px -} - - .ir { - background-position: -96px -168px -} - - .is { - background-position: -120px -168px -} - - .it { - background-position: -144px -168px -} - - .je { - background-position: -168px -168px -} - - .jm { - background-position: -192px -168px -} - - .jo { - background-position: -216px -168px -} - - .jp { - background-position: -240px -168px -} - - .ke { - background-position: -264px -168px -} - - .kg { - background-position: -288px -168px -} - - .kh { - background-position: -312px -168px -} - - .ki { - background-position: -336px -168px -} - - .km { - background-position: -360px -168px -} - - .kn { - background-position: -384px -168px -} - - .kp { - background-position: 0 -192px -} - - .kr { - background-position: -24px -192px -} - - .kw { - background-position: -48px -192px -} - - .ky { - background-position: -72px -192px -} - - .kz { - background-position: -96px -192px -} - - .la { - background-position: -120px -192px -} - - .lb { - background-position: -144px -192px -} - - .lc { - background-position: -168px -192px -} - - .li { - background-position: -192px -192px -} - - .lk { - background-position: -216px -192px -} - - .lr { - background-position: -240px -192px -} - - .ls { - background-position: -264px -192px -} - - .lt { - background-position: -288px -192px -} - - .lu { - background-position: -312px -192px -} - - .lv { - background-position: -336px -192px -} - - .ly { - background-position: -360px -192px -} - - .ma { - background-position: -384px -192px -} - - .mc { - background-position: 0 -216px -} - - .md { - background-position: -24px -216px -} - - .me { - background-position: -48px -216px -} - - .mf { - background-position: -72px -216px -} - - .mg { - background-position: -96px -216px -} - - .mh { - background-position: -120px -216px -} - - .mk { - background-position: -144px -216px -} - - .ml { - background-position: -168px -216px -} - - .mm { - background-position: -192px -216px -} - - .mn { - background-position: -216px -216px -} - - .mo { - background-position: -240px -216px -} - - .mp { - background-position: -264px -216px -} - - .mq { - background-position: -288px -216px -} - - .mr { - background-position: -312px -216px -} - - .ms { - background-position: -336px -216px -} - - .mt { - background-position: -360px -216px -} - - .mu { - background-position: -384px -216px -} - - .mv { - background-position: 0 -240px -} - - .mw { - background-position: -24px -240px -} - - .mx { - background-position: -48px -240px -} - - .my { - background-position: -72px -240px -} - - .mz { - background-position: -96px -240px -} - - .na { - background-position: -120px -240px -} - - .nc { - background-position: -144px -240px -} - - .ne { - background-position: -168px -240px -} - - .nf { - background-position: -192px -240px -} - - .ng { - background-position: -216px -240px -} - - .ni { - background-position: -240px -240px -} - - .nl { - background-position: -264px -240px -} - - .no { - background-position: -288px -240px -} - - .np { - background-position: -312px -240px -} - - .nr { - background-position: -336px -240px -} - - .nu { - background-position: -360px -240px -} - - .nz { - background-position: -384px -240px -} - - .om { - background-position: 0 -264px -} - - .pa { - background-position: -24px -264px -} - - .pe { - background-position: -48px -264px -} - - .pf { - background-position: -72px -264px -} - - .pg { - background-position: -96px -264px -} - - .ph { - background-position: -120px -264px -} - - .pk { - background-position: -192px -264px -} - - .pl { - background-position: -216px -264px -} - - .pn { - background-position: -240px -264px -} - - .pr { - background-position: -264px -264px -} - - .ps { - background-position: -288px -264px -} - - .pt { - background-position: -312px -264px -} - - .pw { - background-position: -336px -264px -} - - .py { - background-position: -360px -264px -} - - .qa { - background-position: -384px -264px -} - - .ro { - background-position: 0 -288px -} - - .rs { - background-position: -24px -288px -} - - .ru { - background-position: -48px -288px -} - - .rw { - background-position: -72px -288px -} - - .sa { - background-position: -96px -288px -} - - .sb { - background-position: -120px -288px -} - - .sc { - background-position: -144px -288px -} - - .sd { - background-position: -168px -288px -} - - .se { - background-position: -192px -288px -} - - .sg { - background-position: -216px -288px -} - - .sh { - background-position: -240px -288px -} - - .si { - background-position: -264px -288px -} - - .sk { - background-position: -288px -288px -} - - .sl { - background-position: -312px -288px -} - - .sm { - background-position: -336px -288px -} - - .sn { - background-position: -360px -288px -} - - .so { - background-position: -384px -288px -} - - .sr { - background-position: 0 -312px -} - - .ss { - background-position: -24px -312px -} - - .st { - background-position: -48px -312px -} - - .sv { - background-position: -72px -312px -} - - .sy { - background-position: -96px -312px -} - - .sz { - background-position: -120px -312px -} - - .tc { - background-position: -144px -312px -} - - .td { - background-position: -168px -312px -} - - .tf { - background-position: -192px -312px -} - - .tg { - background-position: -216px -312px -} - - .th { - background-position: -240px -312px -} - - .tj { - background-position: -264px -312px -} - - .tk { - background-position: -288px -312px -} - - .tl { - background-position: -312px -312px -} - - .tm { - background-position: -336px -312px -} - - .tn { - background-position: -360px -312px -} - - .to { - background-position: -384px -312px -} - - .tr { - background-position: 0 -336px -} - - .tt { - background-position: -24px -336px -} - - .tv { - background-position: -48px -336px -} - - .tw { - background-position: -72px -336px -} - - .tz { - background-position: -96px -336px -} - - .ua { - background-position: -120px -336px -} - - .ug { - background-position: -144px -336px -} - - .us { - background-position: -168px -336px -} - - .uy { - background-position: -192px -336px -} - - .uz { - background-position: -216px -336px -} - - .va { - background-position: -240px -336px -} - - .vc { - background-position: -264px -336px -} - - .ve { - background-position: -288px -336px -} - - .vg { - background-position: -312px -336px -} - - .vi { - background-position: -336px -336px -} - - .vn { - background-position: -360px -336px -} - - .vu { - background-position: -384px -336px -} - - .wf { - background-position: 0 -360px -} - - .ws { - background-position: -24px -360px -} - - .ye { - background-position: -48px -360px -} - - .za { - background-position: -96px -360px -} - - .zm { - background-position: -120px -360px -} - - .zw { - background-position: -144px -360px -} - - -.flag { - width: 25px !important; - height: 20px !important; - background-image: url(data:image/png;base64,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); - background-repeat: no-repeat -} -} - -.dark-theme{ - .phone-input-widget { - .country-list .country:hover { - background-color: #404246; - } - - .country-list .country.highlight { - background-color: #11181c; - } + .tj-text-input-widget.is-invalid { + border-left: none !important; } } \ No newline at end of file From 35f931bdd5f4cd6240280364487d1ca98a13ab60 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 27 Mar 2025 03:48:27 +0530 Subject: [PATCH 13/23] Fixes --- .../Components/PhoneInput/PhoneInput.jsx | 2 +- .../WidgetManager/widgets/phoneinput.js | 10 +++- .../Widgets/BaseComponents/hooks/useInput.js | 49 ++++++++++++++++--- .../src/AppBuilder/Widgets/PhoneInput.jsx | 21 +++++--- frontend/src/_ui/Icon/bulkIcons/Planet.jsx | 27 ++++++++++ frontend/src/_ui/Icon/bulkIcons/index.js | 4 +- .../apps/services/widget-config/phoneinput.js | 10 +++- 7 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 frontend/src/_ui/Icon/bulkIcons/Planet.jsx diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx index be68f2e710..a8412b39c9 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/PhoneInput.jsx @@ -27,7 +27,7 @@ export const PhoneInput = ({ componentMeta, darkMode, ...restProps }) => { const events = Object.keys(componentMeta.events); const validations = Object.keys(componentMeta.validation || {}); const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties); - const defaultCountry = resolvedProperties?.defaultCountry; + const defaultCountry = resolvedProperties?.defaultCountry || 'US'; const isDefaultCountryFxOn = componentMeta?.definition?.properties?.dateFormat?.fxActive || false; const options = useMemo( diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js b/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js index de333354a9..83e35ffc68 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/phoneinput.js @@ -207,7 +207,15 @@ export const phoneinputConfig = { { handle: 'setValue', displayName: 'Set Value', - params: [{ handle: 'value', displayName: 'value', defaultValue: '00' }], + params: [ + { handle: 'value', displayName: 'value', defaultValue: '' }, + { handle: 'country', displayName: 'country', defaultValue: '' }, + ], + }, + { + handle: 'setCountryCode', + displayName: 'Set country code', + params: [{ handle: 'countryCode', displayName: 'Country code', defaultValue: '' }], }, { handle: 'clear', diff --git a/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js index bfdca76f87..b46ab8939e 100644 --- a/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js +++ b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js @@ -1,5 +1,6 @@ import { useState, useRef, useEffect } from 'react'; import { useGridStore } from '@/_stores/gridStore'; +import { getCountryCallingCode } from 'react-phone-number-input'; export const useInput = ({ id, @@ -28,10 +29,19 @@ export const useInput = ({ const [isFocused, setIsFocused] = useState(false); const [labelWidth, setLabelWidth] = useState(0); const [iconVisibility, setIconVisibility] = useState(false); + const [country, setCountry] = useState(properties.defaultCountry); const { isValid, validationError } = validationStatus; const isMandatory = validation?.mandatory ?? false; + const getCountryCallingCodeSafe = (country) => { + try { + return getCountryCallingCode(country); + } catch (error) { + return ''; + } + }; + useEffect(() => { if (labelRef?.current) { const absolutewidth = labelRef?.current?.getBoundingClientRect()?.width; @@ -87,16 +97,41 @@ export const useInput = ({ useEffect(() => { if (isInitialRender.current) return; - setInputValue(properties.value ?? ''); + if (inputType === 'phone') { + let code = getCountryCallingCodeSafe(country); + setInputValue(`+${code}${properties.value}`); + } else { + setInputValue(properties.value ?? ''); + } }, [properties.value]); + // useEffect(() => { + // if (isInitialRender.current && inputType!=="phone") return; + + // setExposedVariable('setValue', + // async function (value,country){ + + // } + // ) + + useEffect(() => { + if (inputType !== 'phone') return; + setExposedVariable('setValue', async function (value, countryCode = country) { + const code = getCountryCallingCodeSafe(country); + setInputValue(`+${code}${value}`); + setCountry(countryCode); + fireEvent('onChange'); + }); + }, [inputType, country]); + useEffect(() => { - const setterName = inputType === 'phone' ? 'setValue' : 'setText'; const exposedVariables = { - [setterName]: async function (text) { - setInputValue(text); - fireEvent('onChange'); - }, + ...(inputType !== 'phone' && { + setText: async function (text) { + setInputValue(text); + fireEvent('onChange'); + }, + }), clear: async function () { setInputValue(''); fireEvent('onChange'); @@ -179,6 +214,8 @@ export const useInput = ({ visibility, loading, disable, + country, + setCountry, validationStatus, showValidationError, isFocused, diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput.jsx index 61d7141504..e55ec4b659 100644 --- a/frontend/src/AppBuilder/Widgets/PhoneInput.jsx +++ b/frontend/src/AppBuilder/Widgets/PhoneInput.jsx @@ -10,12 +10,17 @@ import { useInput } from './BaseComponents/hooks/useInput'; import Loader from '@/ToolJetUI/Loader/Loader'; import Label from '@/_ui/Label'; import TickV3 from '@/_ui/Icon/solidIcons/TickV3'; +import Planet from '@/_ui/Icon/bulkIcons/Planet'; const tinycolor = require('tinycolor2'); export const PhoneInput = (props) => { const { properties, styles, componentName, darkMode, height, setExposedVariable, setExposedVariables, fireEvent } = props; - const inputLogic = useInput(props); + const transformedProps = { + ...props, + inputType: 'phone', + }; + const inputLogic = useInput(transformedProps); const { inputRef, labelRef, @@ -33,6 +38,8 @@ export const PhoneInput = (props) => { handleFocus, value, handlePhoneInputChange, + country, + setCountry, } = inputLogic; const { label, placeholder, isCountryChangeEnabled, defaultCountry = 'US' } = properties; @@ -54,7 +61,6 @@ export const PhoneInput = (props) => { const _width = (width / 100) * 70; const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; const isInitialRender = useRef(true); - const [country, setCountry] = useState(defaultCountry); const getCountryCallingCodeSafe = (country) => { try { @@ -171,7 +177,7 @@ export const PhoneInput = (props) => { borderLeft: 'none', }; - const CustomValueContainer = ({ children, getValue, ...props }) => { + const CustomValueContainer = ({ getValue, ...props }) => { const selectedValue = getValue()[0]; const FlagIcon = selectedValue ? flags[selectedValue.value] : null; const countryCode = getCountryCallingCodeSafe(selectedValue.value); @@ -180,11 +186,14 @@ export const PhoneInput = (props) => { {FlagIcon ? (
- - {` +${countryCode}`} + <> + {` +${countryCode}`} +
) : ( - children +
+ +
)}
); diff --git a/frontend/src/_ui/Icon/bulkIcons/Planet.jsx b/frontend/src/_ui/Icon/bulkIcons/Planet.jsx new file mode 100644 index 0000000000..0606a7b3e1 --- /dev/null +++ b/frontend/src/_ui/Icon/bulkIcons/Planet.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +const Globe = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => ( + + + + + +); + +export default Globe; diff --git a/frontend/src/_ui/Icon/bulkIcons/index.js b/frontend/src/_ui/Icon/bulkIcons/index.js index 5936461f90..0a0930c179 100644 --- a/frontend/src/_ui/Icon/bulkIcons/index.js +++ b/frontend/src/_ui/Icon/bulkIcons/index.js @@ -118,6 +118,7 @@ import Lock from './Lock.jsx'; import AddTemplate from './AddTemplate.jsx'; import InviteCollaborator from './InviteCollabarator.jsx'; import CloseIcon from './CloseIcon.jsx'; +import Planet from './Planet.jsx'; const Icon = (props) => { switch (props.name) { @@ -361,7 +362,8 @@ const Icon = (props) => { return ; case 'checkcircle': return ; - + case 'planet': + return ; default: return ; } diff --git a/server/src/modules/apps/services/widget-config/phoneinput.js b/server/src/modules/apps/services/widget-config/phoneinput.js index de333354a9..83e35ffc68 100644 --- a/server/src/modules/apps/services/widget-config/phoneinput.js +++ b/server/src/modules/apps/services/widget-config/phoneinput.js @@ -207,7 +207,15 @@ export const phoneinputConfig = { { handle: 'setValue', displayName: 'Set Value', - params: [{ handle: 'value', displayName: 'value', defaultValue: '00' }], + params: [ + { handle: 'value', displayName: 'value', defaultValue: '' }, + { handle: 'country', displayName: 'country', defaultValue: '' }, + ], + }, + { + handle: 'setCountryCode', + displayName: 'Set country code', + params: [{ handle: 'countryCode', displayName: 'Country code', defaultValue: '' }], }, { handle: 'clear', From c066c277f5b1cad51f35b6c559f87945a903bde7 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 27 Mar 2025 12:22:09 +0530 Subject: [PATCH 14/23] Icons added --- .../images/icons/widgets/emailinput.jsx | 39 +++++++++++++++++++ .../assets/images/icons/widgets/index.jsx | 6 +++ .../images/icons/widgets/phoneinput.jsx | 36 +++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 frontend/assets/images/icons/widgets/emailinput.jsx create mode 100644 frontend/assets/images/icons/widgets/phoneinput.jsx diff --git a/frontend/assets/images/icons/widgets/emailinput.jsx b/frontend/assets/images/icons/widgets/emailinput.jsx new file mode 100644 index 0000000000..1394959b50 --- /dev/null +++ b/frontend/assets/images/icons/widgets/emailinput.jsx @@ -0,0 +1,39 @@ +import React from 'react'; + +const EmailInput = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => ( + + + + + + +); + +export default EmailInput; diff --git a/frontend/assets/images/icons/widgets/index.jsx b/frontend/assets/images/icons/widgets/index.jsx index 1dc13a843a..72760764da 100644 --- a/frontend/assets/images/icons/widgets/index.jsx +++ b/frontend/assets/images/icons/widgets/index.jsx @@ -59,6 +59,8 @@ import Upstatistics from './upstatistics.jsx'; import Verticaldivider from './verticaldivider.jsx'; import TimePicker from './timepicker.jsx'; import DatepickerV2 from './datepickerv2.jsx'; +import PhoneInput from './phoneinput.jsx'; +import EmailInput from './emailinput.jsx'; const WidgetIcon = (props) => { switch (props.name) { @@ -99,6 +101,10 @@ const WidgetIcon = (props) => { return ; case 'datetimepickerv2': return ; + case 'emailinput': + return ; + case 'phoneinput': + return ; case 'daterangepicker': return ; case 'divider': diff --git a/frontend/assets/images/icons/widgets/phoneinput.jsx b/frontend/assets/images/icons/widgets/phoneinput.jsx new file mode 100644 index 0000000000..e3ad662143 --- /dev/null +++ b/frontend/assets/images/icons/widgets/phoneinput.jsx @@ -0,0 +1,36 @@ +import React from 'react'; + +const PhoneInput = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => ( + + + + + + + + + + + + +); + +export default PhoneInput; From 0ce88d64dd7a1a3a98113eaac71811c070ca0256 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 27 Mar 2025 12:28:08 +0530 Subject: [PATCH 15/23] PhoneInput package added --- package-lock.json | 120 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0cc4d700c7..0b85013062 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "1.18.0", "dependencies": { "@typescript-eslint/eslint-plugin": "^7.12.0", - "eslint-config-prettier": "^9.1.0" + "eslint-config-prettier": "^9.1.0", + "react-phone-number-input": "^3.4.12" }, "devDependencies": { "@tooljet/cli": "^0.0.13", @@ -1244,6 +1245,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/clean-stack": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", @@ -1434,6 +1440,11 @@ "node": ">= 0.6" } }, + "node_modules/country-flag-icons": { + "version": "1.5.18", + "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.18.tgz", + "integrity": "sha512-z+Uzesi8u8IdkViqqbzzbkf3+a7WJpcET5B7sPwTg7GXqPYpVEgNlZ/FC3l8KO4mEf+mNkmzKLppKTN4PlCJEQ==" + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2389,6 +2400,26 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/input-format": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/input-format/-/input-format-0.3.14.tgz", + "integrity": "sha512-gHMrgrbCgmT4uK5Um5eVDUohuV9lcs95ZUUN9Px2Y0VIfjTzT2wF8Q3Z4fwLFm7c5Z2OXCm53FHoovj6SlOKdg==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">=18.1.0", + "react-dom": ">=18.1.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -2594,6 +2625,11 @@ "node": ">=10" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -2660,6 +2696,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz", + "integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==" + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -3144,6 +3185,17 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -3257,6 +3309,14 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-treeify": { "version": "1.1.33", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", @@ -3527,6 +3587,16 @@ "node": ">=6.0.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3554,6 +3624,48 @@ } ] }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "peer": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-phone-number-input": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/react-phone-number-input/-/react-phone-number-input-3.4.12.tgz", + "integrity": "sha512-Raob77KdtLGm49iC6nuOX9qy6Mg16idkgC7Y1mHmvG2WBYoauHpzxYNlfmFskQKeiztrJIwPhPzBhjFwjenNCA==", + "dependencies": { + "classnames": "^2.5.1", + "country-flag-icons": "^1.5.17", + "input-format": "^0.3.10", + "libphonenumber-js": "^1.11.20", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3693,6 +3805,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "peer": true + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", diff --git a/package.json b/package.json index 3e20597e28..d1cdb09c8e 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ }, "dependencies": { "@typescript-eslint/eslint-plugin": "^7.12.0", - "eslint-config-prettier": "^9.1.0" + "eslint-config-prettier": "^9.1.0", + "react-phone-number-input": "^3.4.12" } } From 0366e323698de213f48a37aa8be32fcd6f1a595a Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 27 Mar 2025 13:58:19 +0530 Subject: [PATCH 16/23] Revert "PhoneInput package added" This reverts commit 0ce88d64dd7a1a3a98113eaac71811c070ca0256. --- package-lock.json | 120 +--------------------------------------------- package.json | 3 +- 2 files changed, 2 insertions(+), 121 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b85013062..0cc4d700c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,7 @@ "version": "1.18.0", "dependencies": { "@typescript-eslint/eslint-plugin": "^7.12.0", - "eslint-config-prettier": "^9.1.0", - "react-phone-number-input": "^3.4.12" + "eslint-config-prettier": "^9.1.0" }, "devDependencies": { "@tooljet/cli": "^0.0.13", @@ -1245,11 +1244,6 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" - }, "node_modules/clean-stack": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", @@ -1440,11 +1434,6 @@ "node": ">= 0.6" } }, - "node_modules/country-flag-icons": { - "version": "1.5.18", - "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.18.tgz", - "integrity": "sha512-z+Uzesi8u8IdkViqqbzzbkf3+a7WJpcET5B7sPwTg7GXqPYpVEgNlZ/FC3l8KO4mEf+mNkmzKLppKTN4PlCJEQ==" - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2400,26 +2389,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/input-format": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/input-format/-/input-format-0.3.14.tgz", - "integrity": "sha512-gHMrgrbCgmT4uK5Um5eVDUohuV9lcs95ZUUN9Px2Y0VIfjTzT2wF8Q3Z4fwLFm7c5Z2OXCm53FHoovj6SlOKdg==", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">=18.1.0", - "react-dom": ">=18.1.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -2625,11 +2594,6 @@ "node": ">=10" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -2696,11 +2660,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libphonenumber-js": { - "version": "1.12.6", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz", - "integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==" - }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -3185,17 +3144,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -3309,14 +3257,6 @@ "node": ">=8" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-treeify": { "version": "1.1.33", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", @@ -3587,16 +3527,6 @@ "node": ">=6.0.0" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3624,48 +3554,6 @@ } ] }, - "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", - "peer": true, - "dependencies": { - "scheduler": "^0.25.0" - }, - "peerDependencies": { - "react": "^19.0.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-phone-number-input": { - "version": "3.4.12", - "resolved": "https://registry.npmjs.org/react-phone-number-input/-/react-phone-number-input-3.4.12.tgz", - "integrity": "sha512-Raob77KdtLGm49iC6nuOX9qy6Mg16idkgC7Y1mHmvG2WBYoauHpzxYNlfmFskQKeiztrJIwPhPzBhjFwjenNCA==", - "dependencies": { - "classnames": "^2.5.1", - "country-flag-icons": "^1.5.17", - "input-format": "^0.3.10", - "libphonenumber-js": "^1.11.20", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3805,12 +3693,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "peer": true - }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", diff --git a/package.json b/package.json index d1cdb09c8e..3e20597e28 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ }, "dependencies": { "@typescript-eslint/eslint-plugin": "^7.12.0", - "eslint-config-prettier": "^9.1.0", - "react-phone-number-input": "^3.4.12" + "eslint-config-prettier": "^9.1.0" } } From ce37ebe93a28d928e68ac7ca1d2bbe251c834526 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 27 Mar 2025 13:59:14 +0530 Subject: [PATCH 17/23] PhoneInput package added --- frontend/package-lock.json | 47 ++++++++++++++++++++++++++++++++++++++ frontend/package.json | 1 + 2 files changed, 48 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 16e5c46737..ae105d7aac 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -118,6 +118,7 @@ "react-multi-select-component": "^4.3.4", "react-pdf": "^6.2.2", "react-phone-input-2": "^2.15.1", + "react-phone-number-input": "^3.4.12", "react-plotly.js": "^2.6.0", "react-qr-reader": "^2.2.1", "react-rnd": "^10.4.1", @@ -29819,6 +29820,11 @@ "node": ">=10" } }, + "node_modules/country-flag-icons": { + "version": "1.5.18", + "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.18.tgz", + "integrity": "sha512-z+Uzesi8u8IdkViqqbzzbkf3+a7WJpcET5B7sPwTg7GXqPYpVEgNlZ/FC3l8KO4mEf+mNkmzKLppKTN4PlCJEQ==" + }, "node_modules/country-regex": { "version": "1.1.0", "license": "MIT", @@ -34648,6 +34654,26 @@ "version": "0.2.4", "license": "MIT" }, + "node_modules/input-format": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/input-format/-/input-format-0.3.14.tgz", + "integrity": "sha512-gHMrgrbCgmT4uK5Um5eVDUohuV9lcs95ZUUN9Px2Y0VIfjTzT2wF8Q3Z4fwLFm7c5Z2OXCm53FHoovj6SlOKdg==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">=18.1.0", + "react-dom": ">=18.1.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/internal-slot": { "version": "1.0.7", "license": "MIT", @@ -36785,6 +36811,11 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz", + "integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==" + }, "node_modules/lie": { "version": "3.1.1", "license": "MIT", @@ -42034,6 +42065,22 @@ "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" } }, + "node_modules/react-phone-number-input": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/react-phone-number-input/-/react-phone-number-input-3.4.12.tgz", + "integrity": "sha512-Raob77KdtLGm49iC6nuOX9qy6Mg16idkgC7Y1mHmvG2WBYoauHpzxYNlfmFskQKeiztrJIwPhPzBhjFwjenNCA==", + "dependencies": { + "classnames": "^2.5.1", + "country-flag-icons": "^1.5.17", + "input-format": "^0.3.10", + "libphonenumber-js": "^1.11.20", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-plotly.js": { "version": "2.6.0", "license": "MIT", diff --git a/frontend/package.json b/frontend/package.json index fbab9f7145..cf2652311c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -113,6 +113,7 @@ "react-multi-select-component": "^4.3.4", "react-pdf": "^6.2.2", "react-phone-input-2": "^2.15.1", + "react-phone-number-input": "^3.4.12", "react-plotly.js": "^2.6.0", "react-qr-reader": "^2.2.1", "react-rnd": "^10.4.1", From 8b55dfa939b7563b9fcd7483f25a51b306a1694d Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 28 Mar 2025 02:55:42 +0530 Subject: [PATCH 18/23] Minor fixes and added email and phone input to form --- .../Widgets/BaseComponents/hooks/useInput.js | 13 +- .../src/AppBuilder/Widgets/Form/FormUtils.js | 11 + .../AppBuilder/Widgets/Form/RenderSchema.jsx | 1 + .../src/AppBuilder/Widgets/PhoneInput.jsx | 476 ------------------ .../Widgets/PhoneInput/CountrySelect.jsx | 137 +++++ .../Widgets/PhoneInput/CustomMenuList.jsx | 51 ++ .../Widgets/PhoneInput/CustomOption.jsx | 37 ++ .../PhoneInput/CustomValueContainer.jsx | 28 ++ .../Widgets/PhoneInput/PhoneInput.jsx | 254 ++++++++++ .../AppBuilder/Widgets/PhoneInput/utils.js | 10 + .../src/AppBuilder/_helpers/editorHelpers.js | 2 +- 11 files changed, 532 insertions(+), 488 deletions(-) delete mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput/CountrySelect.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput/CustomMenuList.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput/CustomOption.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput/CustomValueContainer.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PhoneInput/utils.js diff --git a/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js index b46ab8939e..fff7f091ae 100644 --- a/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js +++ b/frontend/src/AppBuilder/Widgets/BaseComponents/hooks/useInput.js @@ -1,5 +1,6 @@ import { useState, useRef, useEffect } from 'react'; import { useGridStore } from '@/_stores/gridStore'; +//eslint-disable-next-line import/no-unresolved import { getCountryCallingCode } from 'react-phone-number-input'; export const useInput = ({ @@ -29,7 +30,7 @@ export const useInput = ({ const [isFocused, setIsFocused] = useState(false); const [labelWidth, setLabelWidth] = useState(0); const [iconVisibility, setIconVisibility] = useState(false); - const [country, setCountry] = useState(properties.defaultCountry); + const [country, setCountry] = useState(properties.defaultCountry || 'US'); const { isValid, validationError } = validationStatus; const isMandatory = validation?.mandatory ?? false; @@ -96,7 +97,6 @@ export const useInput = ({ }, [validate]); useEffect(() => { - if (isInitialRender.current) return; if (inputType === 'phone') { let code = getCountryCallingCodeSafe(country); setInputValue(`+${code}${properties.value}`); @@ -105,15 +105,6 @@ export const useInput = ({ } }, [properties.value]); - // useEffect(() => { - // if (isInitialRender.current && inputType!=="phone") return; - - // setExposedVariable('setValue', - // async function (value,country){ - - // } - // ) - useEffect(() => { if (inputType !== 'phone') return; setExposedVariable('setValue', async function (value, countryCode = country) { diff --git a/frontend/src/AppBuilder/Widgets/Form/FormUtils.js b/frontend/src/AppBuilder/Widgets/Form/FormUtils.js index f8366e0b29..9769257b58 100644 --- a/frontend/src/AppBuilder/Widgets/Form/FormUtils.js +++ b/frontend/src/AppBuilder/Widgets/Form/FormUtils.js @@ -97,6 +97,8 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') { if (uiComponentsDraft?.length > 0 && uiComponentsDraft[index * 2 + 1]) { switch (typeResolver(value?.type)) { case 'TextInput': + case 'EmailInput': + case 'PhoneInput': if (value?.styles?.backgroundColor) uiComponentsDraft[index * 2 + 1]['definition']['styles']['backgroundColor'] = value?.styles?.backgroundColor; @@ -127,6 +129,11 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') { if (value?.value) uiComponentsDraft[index * 2 + 1]['definition']['properties']['value'] = value?.value; if (value?.placeholder) uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder'] = value?.placeholder; + + if (value?.defaultCountry && typeResolver(value?.type) === 'PhoneInput') { + uiComponentsDraft[index * 2 + 1]['definition']['properties']['defaultCountry'] = value?.defaultCountry; + } + // prevent label from showing up in text input, because it is already shown in the text component. (Defaults to "Label" if not updated explicitly with an empty string) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label'] = ''; break; @@ -482,6 +489,10 @@ const typeResolver = (type) => { return 'DropDown'; case 'button': return 'Button'; + case 'emailinput': + return 'EmailInput'; + case 'phoneinput': + return 'PhoneInput'; case 'text': return 'Text'; case 'number': diff --git a/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx b/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx index b5bfa9e4c3..89d34b65c7 100644 --- a/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx @@ -26,6 +26,7 @@ const RenderSchema = ({ component, parent, id, onOptionChange, onOptionsChange, return validateWidget({ ...{ widgetValue: value }, ...{ validationObject: component.definition.validation }, + componentType: component?.component, }); }, [component.definition.validation] diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput.jsx deleted file mode 100644 index e55ec4b659..0000000000 --- a/frontend/src/AppBuilder/Widgets/PhoneInput.jsx +++ /dev/null @@ -1,476 +0,0 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import Input, { getCountries, getCountryCallingCode } from 'react-phone-number-input/input'; -import en from 'react-phone-number-input/locale/en'; -import flags from 'react-phone-number-input/flags'; -import 'react-phone-number-input/style.css'; -import Select, { components } from 'react-select'; -import cx from 'classnames'; -import SolidIcon from '@/_ui/Icon/SolidIcons'; -import { useInput } from './BaseComponents/hooks/useInput'; -import Loader from '@/ToolJetUI/Loader/Loader'; -import Label from '@/_ui/Label'; -import TickV3 from '@/_ui/Icon/solidIcons/TickV3'; -import Planet from '@/_ui/Icon/bulkIcons/Planet'; -const tinycolor = require('tinycolor2'); - -export const PhoneInput = (props) => { - const { properties, styles, componentName, darkMode, height, setExposedVariable, setExposedVariables, fireEvent } = - props; - const transformedProps = { - ...props, - inputType: 'phone', - }; - const inputLogic = useInput(transformedProps); - const { - inputRef, - labelRef, - visibility, - loading, - disable, - validationStatus, - showValidationError, - isFocused, - labelWidth, - isValid, - validationError, - isMandatory, - handleBlur, - handleFocus, - value, - handlePhoneInputChange, - country, - setCountry, - } = inputLogic; - const { label, placeholder, isCountryChangeEnabled, defaultCountry = 'US' } = properties; - - const { - padding, - textColor, - backgroundColor, - alignment, - width, - direction, - auto, - color, - borderColor, - accentColor, - errTextColor, - boxShadow, - borderRadius, - } = styles; - const _width = (width / 100) * 70; - const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; - const isInitialRender = useRef(true); - - const getCountryCallingCodeSafe = (country) => { - try { - return getCountryCallingCode(country); - } catch (error) { - return ''; - } - }; - - const options = useMemo( - () => - getCountries().map((country) => ({ - label: `${en[country]} +${getCountryCallingCodeSafe(country)}`, - value: country, - })), - [] - ); - - const onInputValueChange = (value) => { - setExposedVariables({ - country: country, - countryCode: getCountryCallingCodeSafe(country), - formattedValue: `+${getCountryCallingCodeSafe(country)} ${inputRef.current?.value}`, - }); - handlePhoneInputChange(value); - }; - - const handleKeyUp = (e) => { - if (e.key === 'Enter') { - onInputValueChange(value); - fireEvent('onEnterPressed'); - } - }; - - useEffect(() => { - if (isInitialRender.current) { - setExposedVariables({ - country: country, - countryCode: getCountryCallingCodeSafe(country), - formattedValue: `+${getCountryCallingCodeSafe(country)} ${inputRef.current?.value}`, - value: value, - setCountryCode: (code) => { - let value = getCountryCallingCodeSafe(code); - if (value) { - setCountry(code); - } else { - value = getCountries().find((country) => `+${getCountryCallingCode(country)}` === code); - setCountry(value ? value : ''); - } - }, - }); - isInitialRender.current = false; - } - }, []); - - useEffect(() => { - if (!isInitialRender.current) { - setCountry(defaultCountry); - } - }, [defaultCountry]); - - const disabledState = disable || loading; - - 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, - }; - - const computedStyles = { - height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4, - borderRadius: `${borderRadius}px`, - color: !['#1B1F24', '#000', '#000000ff'].includes(textColor) - ? textColor - : disabledState - ? 'var(--text-disabled)' - : 'var(--text-primary)', - borderColor: isFocused - ? accentColor != '4368E3' - ? accentColor - : 'var(--primary-accent-strong)' - : borderColor != '#CCD1D5' - ? borderColor - : disabledState - ? '1px solid var(--borders-disabled-on-white)' - : 'var(--borders-default)', - '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), - backgroundColor: - backgroundColor != '#fff' - ? backgroundColor - : disabledState - ? darkMode - ? 'var(--surfaces-app-bg-default)' - : 'var(--surfaces-surface-03)' - : 'var(--surfaces-surface-01)', - padding: '8px 10px', - overflow: 'hidden', - textOverflow: 'ellipsis', - borderBottomLeftRadius: '0px', - borderTopLeftRadius: '0px', - borderLeft: 'none', - }; - - const CustomValueContainer = ({ getValue, ...props }) => { - const selectedValue = getValue()[0]; - const FlagIcon = selectedValue ? flags[selectedValue.value] : null; - const countryCode = getCountryCallingCodeSafe(selectedValue.value); - - return ( - - {FlagIcon ? ( -
- <> - {` +${countryCode}`} - -
- ) : ( -
- -
- )} -
- ); - }; - - const CustomOption = (props) => { - const { label, value: optionValue, isSelected } = props; - const optionStyle = { - display: 'flex', - alignItems: 'center', - justifyContent: 'start', - minHeight: '32px', - gap: '6px', - cursor: 'pointer', - fontFamily: 'IBM Plex Sans', - fontSize: '12px', - lineHeight: '18px', - fontWeight: '400', - color: darkMode ? '#fff' : '#1B1F24', - width: '100%', - }; - console.log('darkMode', darkMode); - const FlagIcon = flags[optionValue]; - - return ( - -
-
{FlagIcon ? : null}
- {label} -
- -
-
-
- ); - }; - - const CustomMenuList = (props) => { - const { children, selectProps } = props; - const { onInputChange, inputValue } = selectProps; - - return ( -
e.stopPropagation()} - > -
- - - - { - onInputChange(e.currentTarget.value, { - action: 'input-change', - }); - }} - onMouseDown={(e) => { - e.stopPropagation(); - e.target.focus(); - }} - onTouchEnd={(e) => { - e.stopPropagation(); - e.target.focus(); - }} - /> -
- - - {children?.length > 0 ? children :
No options
} -
-
- ); - }; - - const CountrySelect = ({ value, onChange, options, ...rest }) => { - const [menuIsOpen, setMenuIsOpen] = useState(false); - const dropdownRef = useRef(null); - - useEffect(() => { - const handleClickOutside = (event) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { - setMenuIsOpen(false); - } - }; - - // Add event listener when dropdown is open - if (menuIsOpen) { - document.addEventListener('mousedown', handleClickOutside); - } - - // Clean up the event listener - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [menuIsOpen]); - - const customStyles = { - container: (provided) => ({ - ...provided, - minWidth: !isCountryChangeEnabled || disabledState ? '77px' : '87px', - width: !isCountryChangeEnabled || disabledState ? '77px' : '87px', - height: '100%', - }), - control: (provided, state) => ({ - ...provided, - minHeight: '0px', - height: '100%', - borderTopLeftRadius: `${borderRadius}px`, - borderBottomLeftRadius: `${borderRadius}px`, - borderTopRightRadius: '0px', - borderBottomRightRadius: '0px', - borderColor: `${ - !isValid && showValidationError ? 'var(--status-error-strong)' : computedStyles?.borderColor - } !important`, - backgroundColor: `${ - isCountryChangeEnabled - ? computedStyles?.backgroundColor - : darkMode - ? 'var(--surfaces-app-bg-default)' - : 'var(--surfaces-surface-03)' - } !important`, - }), - menu: (provided) => ({ - ...provided, - width: '208px', - height: '236px', - borderRadius: '8px', - marginTop: '2px', - }), - menuList: (provided) => ({ - ...provided, - maxHeight: '196px', - overflowY: 'auto', - scrollbarWidth: 'none', - gap: '1px', - padding: '8px', - borderRadius: '0px 0px 8px 8px', - display: 'flex', - flexDirection: 'column', - backgroundColor: 'var(--surfaces-surface-01)', - }), - option: (provided, state) => ({ - ...provided, - backgroundColor: state.isSelected ? '#4368E31A' : 'var(--surfaces-surface-01)', - ...(state.isSelected && { borderRadius: '8px' }), - '&:hover': { - backgroundColor: 'var(--interactive-overlays-fill-hover)', - borderRadius: '8px', - }, - display: 'flex', - cursor: 'pointer', - padding: '1px 14px', - }), - }; - - return ( -
setMenuIsOpen((prev) => !prev)} ref={dropdownRef}> - -
- {loading && } - - {showValidationError && visibility && ( -
- {validationError} -
- )} - - ); -}; diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/CountrySelect.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput/CountrySelect.jsx new file mode 100644 index 0000000000..240c44d0f6 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/PhoneInput/CountrySelect.jsx @@ -0,0 +1,137 @@ +import React, { useEffect, useRef, useState } from 'react'; +import Select from 'react-select'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { CustomMenuList } from './CustomMenuList'; +import { CustomOption } from './CustomOption'; +import { CustomValueContainer } from './CustomValueContainer'; + +export const CountrySelect = ({ value, onChange, options, ...rest }) => { + const { + isCountryChangeEnabled, + disabledState, + borderRadius, + isValid, + showValidationError, + computedStyles, + darkMode, + } = rest; + const [menuIsOpen, setMenuIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setMenuIsOpen(false); + } + }; + + if (menuIsOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [menuIsOpen]); + + const customStyles = { + container: (provided) => ({ + ...provided, + minWidth: !isCountryChangeEnabled || disabledState ? '77px' : '87px', + width: !isCountryChangeEnabled || disabledState ? '77px' : '87px', + height: '100%', + }), + control: (provided) => ({ + ...provided, + minHeight: '0px', + height: '100%', + borderTopLeftRadius: `${borderRadius}px`, + borderBottomLeftRadius: `${borderRadius}px`, + borderTopRightRadius: '0px', + borderBottomRightRadius: '0px', + borderColor: `${ + !isValid && showValidationError ? 'var(--status-error-strong)' : computedStyles?.borderColor + } !important`, + backgroundColor: `${ + isCountryChangeEnabled + ? computedStyles?.backgroundColor + : darkMode + ? 'var(--surfaces-app-bg-default)' + : 'var(--surfaces-surface-03)' + } !important`, + }), + menu: (provided) => ({ + ...provided, + width: '208px', + height: '236px', + borderRadius: '8px', + marginTop: '2px', + }), + menuList: (provided) => ({ + ...provided, + maxHeight: '196px', + overflowY: 'auto', + scrollbarWidth: 'none', + gap: '1px', + padding: '8px', + borderRadius: '0px 0px 8px 8px', + display: 'flex', + flexDirection: 'column', + backgroundColor: 'var(--surfaces-surface-01)', + }), + option: (provided, state) => ({ + ...provided, + backgroundColor: state.isSelected ? '#4368E31A' : 'var(--surfaces-surface-01)', + ...(state.isSelected && { borderRadius: '8px' }), + '&:hover': { + backgroundColor: 'var(--interactive-overlays-fill-hover)', + borderRadius: '8px', + }, + display: 'flex', + cursor: 'pointer', + padding: '1px 14px', + }), + }; + return ( +
{ + if (disabledState || !isCountryChangeEnabled) return; + setMenuIsOpen((prev) => !prev); + }} + ref={dropdownRef} + > + { + onInputChange(e.currentTarget.value, { + action: 'input-change', + }); + }} + onMouseDown={(e) => { + e.stopPropagation(); + e.target.focus(); + }} + onTouchEnd={(e) => { + e.stopPropagation(); + e.target.focus(); + }} + /> +
+ + + {children?.length > 0 ? children :
No options
} +
+ + ); +}; diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/CustomOption.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput/CustomOption.jsx new file mode 100644 index 0000000000..8f3b9d7dac --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/PhoneInput/CustomOption.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { components } from 'react-select'; +// eslint-disable-next-line import/no-unresolved +import flags from 'react-phone-number-input/flags'; +import TickV3 from '@/_ui/Icon/solidIcons/TickV3'; + +export const CustomOption = (props) => { + const { label, value: optionValue, isSelected, darkMode } = props; + + const optionStyle = { + display: 'flex', + alignItems: 'center', + justifyContent: 'start', + minHeight: '32px', + gap: '6px', + cursor: 'pointer', + fontFamily: 'IBM Plex Sans', + fontSize: '12px', + lineHeight: '18px', + fontWeight: '400', + color: darkMode ? '#fff' : '#1B1F24', + width: '100%', + }; + const FlagIcon = flags[optionValue]; + + return ( + +
+
{FlagIcon ? : null}
+ {label} +
+ +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/CustomValueContainer.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput/CustomValueContainer.jsx new file mode 100644 index 0000000000..bf2ad0b5b7 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/PhoneInput/CustomValueContainer.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { components } from 'react-select'; +// eslint-disable-next-line import/no-unresolved +import flags from 'react-phone-number-input/flags'; +import Planet from '@/_ui/Icon/bulkIcons/Planet'; +import { getCountryCallingCodeSafe } from './utils'; + +export const CustomValueContainer = ({ getValue, ...props }) => { + const selectedValue = getValue()[0]; + const FlagIcon = selectedValue ? flags[selectedValue.value] : null; + const countryCode = getCountryCallingCodeSafe(selectedValue.value); + + return ( + + {FlagIcon ? ( +
+ <> + {` +${countryCode}`} + +
+ ) : ( +
+ +
+ )} +
+ ); +}; diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx new file mode 100644 index 0000000000..ce7bc39fe8 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx @@ -0,0 +1,254 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +// eslint-disable-next-line import/no-unresolved +import Input, { getCountries, getCountryCallingCode } from 'react-phone-number-input/input'; +import { getCountryCallingCodeSafe } from './utils'; +// eslint-disable-next-line import/no-unresolved +import en from 'react-phone-number-input/locale/en'; +import 'react-phone-number-input/style.css'; +import { useInput } from '../BaseComponents/hooks/useInput'; +import Loader from '@/ToolJetUI/Loader/Loader'; +import Label from '@/_ui/Label'; +import { CountrySelect } from './CountrySelect'; + +const tinycolor = require('tinycolor2'); + +export const PhoneInput = (props) => { + const { properties, styles, componentName, darkMode, setExposedVariables, fireEvent } = props; + const transformedProps = { + ...props, + inputType: 'phone', + }; + const inputLogic = useInput(transformedProps); + const { + inputRef, + labelRef, + visibility, + loading, + disable, + showValidationError, + isFocused, + labelWidth, + isValid, + validationError, + isMandatory, + handleBlur, + handleFocus, + value, + handlePhoneInputChange, + country, + setCountry, + } = inputLogic; + const { label, placeholder, isCountryChangeEnabled, defaultCountry = 'US' } = properties; + + const { + textColor, + backgroundColor, + alignment, + width, + direction, + auto, + color, + borderColor, + accentColor, + errTextColor, + boxShadow, + borderRadius, + } = styles; + const _width = (width / 100) * 70; + const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; + const isInitialRender = useRef(true); + + const options = useMemo( + () => + getCountries() + .map((country) => ({ + label: `${en[country]} +${getCountryCallingCodeSafe(country)}`, + value: country, + })) + .sort((a, b) => a.label.localeCompare(b.label)), + [] + ); + + const onInputValueChange = (value) => { + setExposedVariables({ + country: country, + countryCode: `+${getCountryCallingCodeSafe(country)}`, + formattedValue: `+${getCountryCallingCodeSafe(country)} ${inputRef.current?.value}`, + }); + handlePhoneInputChange(value); + }; + + const handleKeyUp = (e) => { + if (e.key === 'Enter') { + fireEvent('onEnterPressed'); + } + }; + + useEffect(() => { + if (isInitialRender.current) { + setExposedVariables({ + country: country, + countryCode: `+${getCountryCallingCodeSafe(country)}`, + formattedValue: `+${getCountryCallingCodeSafe(country)} ${inputRef.current?.value}`, + value: value, + setCountryCode: (code) => { + let value = getCountryCallingCodeSafe(code); + if (value) { + setCountry(code); + } else { + value = getCountries().find((country) => `+${getCountryCallingCode(country)}` === code); + setCountry(value ? value : ''); + } + }, + }); + isInitialRender.current = false; + } + }, []); + + useEffect(() => { + if (!isInitialRender.current) { + setCountry(defaultCountry); + } + }, [defaultCountry]); + + const disabledState = disable || loading; + + 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, + }; + + const computedStyles = { + height: '100%', + borderRadius: `${borderRadius}px`, + color: !['#1B1F24', '#000', '#000000ff'].includes(textColor) + ? textColor + : disabledState + ? 'var(--text-disabled)' + : 'var(--text-primary)', + borderColor: isFocused + ? accentColor != '4368E3' + ? accentColor + : 'var(--primary-accent-strong)' + : borderColor != '#CCD1D5' + ? borderColor + : disabledState + ? '1px solid var(--borders-disabled-on-white)' + : 'var(--borders-default)', + '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), + backgroundColor: + backgroundColor != '#fff' + ? backgroundColor + : disabledState + ? darkMode + ? 'var(--surfaces-app-bg-default)' + : 'var(--surfaces-surface-03)' + : 'var(--surfaces-surface-01)', + padding: '8px 10px', + overflow: 'hidden', + textOverflow: 'ellipsis', + borderBottomLeftRadius: '0px', + borderTopLeftRadius: '0px', + borderLeft: 'none', + }; + + return ( + <> +
+
+ {showValidationError && visibility && ( +
+ {validationError} +
+ )} + + ); +}; diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/utils.js b/frontend/src/AppBuilder/Widgets/PhoneInput/utils.js new file mode 100644 index 0000000000..9a23b45ca5 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/PhoneInput/utils.js @@ -0,0 +1,10 @@ +// eslint-disable-next-line import/no-unresolved +import { getCountryCallingCode } from 'react-phone-number-input/input'; + +export const getCountryCallingCodeSafe = (country) => { + try { + return getCountryCallingCode(country); + } catch (error) { + return ''; + } +}; diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 53906f7d4a..504a09b92c 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -31,7 +31,7 @@ import { Divider } from '@/Editor/Components/Divider'; import { FilePicker } from '@/Editor/Components/FilePicker'; import { PasswordInput } from '@/AppBuilder/Widgets/PasswordInput'; import { EmailInput } from '@/AppBuilder/Widgets/EmailInput'; -import { PhoneInput } from '@/AppBuilder/Widgets/PhoneInput'; +import { PhoneInput } from '@/AppBuilder/Widgets/PhoneInput/PhoneInput'; // import { Calendar } from '@/Editor/Components/Calendar'; // import { Listview } from '@/Editor/Components/Listview'; import { IFrame } from '@/Editor/Components/IFrame'; From 93323976087331ce36c7a52f0578fa7d731e60f8 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Sun, 30 Mar 2025 21:16:31 +0530 Subject: [PATCH 19/23] Minor fixes and folder structure changes --- frontend/ee | 2 +- .../CountrySelect.jsx | 0 .../CustomMenuList.jsx | 0 .../CustomOption.jsx | 0 .../CustomValueContainer.jsx | 0 .../Widgets/PhoneCurrency/PhoneInput.jsx | 254 ++++++++++++++++++ .../{PhoneInput => PhoneCurrency}/utils.js | 0 .../Widgets/PhoneInput/PhoneInput.jsx | 8 +- .../src/AppBuilder/_helpers/editorHelpers.js | 2 +- frontend/src/_styles/theme.scss | 5 + server/ee | 2 +- 11 files changed, 267 insertions(+), 6 deletions(-) rename frontend/src/AppBuilder/Widgets/{PhoneInput => PhoneCurrency}/CountrySelect.jsx (100%) rename frontend/src/AppBuilder/Widgets/{PhoneInput => PhoneCurrency}/CustomMenuList.jsx (100%) rename frontend/src/AppBuilder/Widgets/{PhoneInput => PhoneCurrency}/CustomOption.jsx (100%) rename frontend/src/AppBuilder/Widgets/{PhoneInput => PhoneCurrency}/CustomValueContainer.jsx (100%) create mode 100644 frontend/src/AppBuilder/Widgets/PhoneCurrency/PhoneInput.jsx rename frontend/src/AppBuilder/Widgets/{PhoneInput => PhoneCurrency}/utils.js (100%) diff --git a/frontend/ee b/frontend/ee index d93ee7e131..715a830c7a 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit d93ee7e1318f044ef2327671b8b257648071453d +Subproject commit 715a830c7a8d75efc7f77106292d9e4499005b69 diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/CountrySelect.jsx b/frontend/src/AppBuilder/Widgets/PhoneCurrency/CountrySelect.jsx similarity index 100% rename from frontend/src/AppBuilder/Widgets/PhoneInput/CountrySelect.jsx rename to frontend/src/AppBuilder/Widgets/PhoneCurrency/CountrySelect.jsx diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/CustomMenuList.jsx b/frontend/src/AppBuilder/Widgets/PhoneCurrency/CustomMenuList.jsx similarity index 100% rename from frontend/src/AppBuilder/Widgets/PhoneInput/CustomMenuList.jsx rename to frontend/src/AppBuilder/Widgets/PhoneCurrency/CustomMenuList.jsx diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/CustomOption.jsx b/frontend/src/AppBuilder/Widgets/PhoneCurrency/CustomOption.jsx similarity index 100% rename from frontend/src/AppBuilder/Widgets/PhoneInput/CustomOption.jsx rename to frontend/src/AppBuilder/Widgets/PhoneCurrency/CustomOption.jsx diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/CustomValueContainer.jsx b/frontend/src/AppBuilder/Widgets/PhoneCurrency/CustomValueContainer.jsx similarity index 100% rename from frontend/src/AppBuilder/Widgets/PhoneInput/CustomValueContainer.jsx rename to frontend/src/AppBuilder/Widgets/PhoneCurrency/CustomValueContainer.jsx diff --git a/frontend/src/AppBuilder/Widgets/PhoneCurrency/PhoneInput.jsx b/frontend/src/AppBuilder/Widgets/PhoneCurrency/PhoneInput.jsx new file mode 100644 index 0000000000..ce7bc39fe8 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/PhoneCurrency/PhoneInput.jsx @@ -0,0 +1,254 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +// eslint-disable-next-line import/no-unresolved +import Input, { getCountries, getCountryCallingCode } from 'react-phone-number-input/input'; +import { getCountryCallingCodeSafe } from './utils'; +// eslint-disable-next-line import/no-unresolved +import en from 'react-phone-number-input/locale/en'; +import 'react-phone-number-input/style.css'; +import { useInput } from '../BaseComponents/hooks/useInput'; +import Loader from '@/ToolJetUI/Loader/Loader'; +import Label from '@/_ui/Label'; +import { CountrySelect } from './CountrySelect'; + +const tinycolor = require('tinycolor2'); + +export const PhoneInput = (props) => { + const { properties, styles, componentName, darkMode, setExposedVariables, fireEvent } = props; + const transformedProps = { + ...props, + inputType: 'phone', + }; + const inputLogic = useInput(transformedProps); + const { + inputRef, + labelRef, + visibility, + loading, + disable, + showValidationError, + isFocused, + labelWidth, + isValid, + validationError, + isMandatory, + handleBlur, + handleFocus, + value, + handlePhoneInputChange, + country, + setCountry, + } = inputLogic; + const { label, placeholder, isCountryChangeEnabled, defaultCountry = 'US' } = properties; + + const { + textColor, + backgroundColor, + alignment, + width, + direction, + auto, + color, + borderColor, + accentColor, + errTextColor, + boxShadow, + borderRadius, + } = styles; + const _width = (width / 100) * 70; + const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; + const isInitialRender = useRef(true); + + const options = useMemo( + () => + getCountries() + .map((country) => ({ + label: `${en[country]} +${getCountryCallingCodeSafe(country)}`, + value: country, + })) + .sort((a, b) => a.label.localeCompare(b.label)), + [] + ); + + const onInputValueChange = (value) => { + setExposedVariables({ + country: country, + countryCode: `+${getCountryCallingCodeSafe(country)}`, + formattedValue: `+${getCountryCallingCodeSafe(country)} ${inputRef.current?.value}`, + }); + handlePhoneInputChange(value); + }; + + const handleKeyUp = (e) => { + if (e.key === 'Enter') { + fireEvent('onEnterPressed'); + } + }; + + useEffect(() => { + if (isInitialRender.current) { + setExposedVariables({ + country: country, + countryCode: `+${getCountryCallingCodeSafe(country)}`, + formattedValue: `+${getCountryCallingCodeSafe(country)} ${inputRef.current?.value}`, + value: value, + setCountryCode: (code) => { + let value = getCountryCallingCodeSafe(code); + if (value) { + setCountry(code); + } else { + value = getCountries().find((country) => `+${getCountryCallingCode(country)}` === code); + setCountry(value ? value : ''); + } + }, + }); + isInitialRender.current = false; + } + }, []); + + useEffect(() => { + if (!isInitialRender.current) { + setCountry(defaultCountry); + } + }, [defaultCountry]); + + const disabledState = disable || loading; + + 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, + }; + + const computedStyles = { + height: '100%', + borderRadius: `${borderRadius}px`, + color: !['#1B1F24', '#000', '#000000ff'].includes(textColor) + ? textColor + : disabledState + ? 'var(--text-disabled)' + : 'var(--text-primary)', + borderColor: isFocused + ? accentColor != '4368E3' + ? accentColor + : 'var(--primary-accent-strong)' + : borderColor != '#CCD1D5' + ? borderColor + : disabledState + ? '1px solid var(--borders-disabled-on-white)' + : 'var(--borders-default)', + '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), + backgroundColor: + backgroundColor != '#fff' + ? backgroundColor + : disabledState + ? darkMode + ? 'var(--surfaces-app-bg-default)' + : 'var(--surfaces-surface-03)' + : 'var(--surfaces-surface-01)', + padding: '8px 10px', + overflow: 'hidden', + textOverflow: 'ellipsis', + borderBottomLeftRadius: '0px', + borderTopLeftRadius: '0px', + borderLeft: 'none', + }; + + return ( + <> +
+
+ {showValidationError && visibility && ( +
+ {validationError} +
+ )} + + ); +}; diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/utils.js b/frontend/src/AppBuilder/Widgets/PhoneCurrency/utils.js similarity index 100% rename from frontend/src/AppBuilder/Widgets/PhoneInput/utils.js rename to frontend/src/AppBuilder/Widgets/PhoneCurrency/utils.js diff --git a/frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx b/frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx index ce7bc39fe8..6a7dafceea 100644 --- a/frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx +++ b/frontend/src/AppBuilder/Widgets/PhoneInput/PhoneInput.jsx @@ -134,7 +134,7 @@ export const PhoneInput = (props) => { const computedStyles = { height: '100%', - borderRadius: `${borderRadius}px`, + borderRadius: `0px ${borderRadius}px ${borderRadius}px 0px`, color: !['#1B1F24', '#000', '#000000ff'].includes(textColor) ? textColor : disabledState @@ -165,7 +165,8 @@ export const PhoneInput = (props) => { borderTopLeftRadius: '0px', borderLeft: 'none', }; - + const countryCode = getCountryCallingCodeSafe(country); + console.log(countryCode); return ( <>
{ /> Date: Tue, 1 Apr 2025 03:16:46 +0530 Subject: [PATCH 20/23] Currency input added --- frontend/package-lock.json | 9 + frontend/package.json | 1 + .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 1 + .../ComponentsManagerTab.jsx | 1 + .../CurrencyInput/CurrencyInput.jsx | 132 +++++ .../Inspector/Components/DefaultComponent.jsx | 2 + .../Components/PhoneInput/PhoneInput.jsx | 1 - .../Inspector/Components/PhoneInput/en.js | 239 --------- .../RightSideBar/Inspector/Inspector.jsx | 4 + .../RightSideBar/Inspector/Utils.js | 1 + .../WidgetManager/configs/widgetConfig.js | 2 + .../WidgetManager/widgets/currencyinput.js | 314 ++++++++++++ .../AppBuilder/WidgetManager/widgets/index.js | 2 + .../Widgets/BaseComponents/hooks/useInput.js | 4 +- .../src/AppBuilder/Widgets/Form/FormUtils.js | 9 +- .../Widgets/PhoneCurrency/CountrySelect.jsx | 8 +- .../Widgets/PhoneCurrency/CurrencyInput.jsx | 243 +++++++++ .../Widgets/PhoneCurrency/CustomOption.jsx | 3 +- .../PhoneCurrency/CustomValueContainer.jsx | 4 +- .../Widgets/PhoneCurrency/PhoneInput.jsx | 4 +- .../Widgets/PhoneCurrency/constants.js | 478 ++++++++++++++++++ .../src/AppBuilder/_helpers/editorHelpers.js | 2 + .../_stores/slices/componentsSlice.js | 1 + .../services/widget-config/currencyinput.js | 315 ++++++++++++ .../apps/services/widget-config/index.js | 2 + 25 files changed, 1532 insertions(+), 250 deletions(-) create mode 100644 frontend/src/AppBuilder/RightSideBar/Inspector/Components/CurrencyInput/CurrencyInput.jsx delete mode 100644 frontend/src/AppBuilder/RightSideBar/Inspector/Components/PhoneInput/en.js create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/currencyinput.js create mode 100644 frontend/src/AppBuilder/Widgets/PhoneCurrency/CurrencyInput.jsx create mode 100644 frontend/src/AppBuilder/Widgets/PhoneCurrency/constants.js create mode 100644 server/src/modules/apps/services/widget-config/currencyinput.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ae105d7aac..b574b89a32 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -96,6 +96,7 @@ "react-circular-progressbar": "^2.1.0", "react-color": "^2.19.3", "react-copy-to-clipboard": "^5.1.0", + "react-currency-input-field": "^3.10.0", "react-datepicker": "^7.6.0", "react-dates": "^21.8.0", "react-datetime": "^3.2.0", @@ -41278,6 +41279,14 @@ "framework-utils": "^1.1.0" } }, + "node_modules/react-currency-input-field": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/react-currency-input-field/-/react-currency-input-field-3.10.0.tgz", + "integrity": "sha512-GRmZogHh1e1LrmgXg/fKHSuRLYUnj/c/AumfvfuDMA0UX1mDR6u2NR0fzDemRdq4tNHNLucJeJ2OKCr3ehqyDA==", + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-date-picker": { "version": "10.6.0", "license": "MIT", diff --git a/frontend/package.json b/frontend/package.json index cf2652311c..776bae399e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -91,6 +91,7 @@ "react-circular-progressbar": "^2.1.0", "react-color": "^2.19.3", "react-copy-to-clipboard": "^5.1.0", + "react-currency-input-field": "^3.10.0", "react-datepicker": "^7.6.0", "react-dates": "^21.8.0", "react-datetime": "^3.2.0", diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index b95103bdc8..d3f3a23f68 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -14,6 +14,7 @@ const shouldAddBoxShadowAndVisibility = [ 'PasswordInput', 'EmailInput', 'PhoneInput', + 'CurrencyInput', 'NumberInput', 'Text', 'Checkbox', diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx index 60eeaff396..ae5aadfac6 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx @@ -121,6 +121,7 @@ export const ComponentsManagerTab = ({ darkMode }) => { 'TextArea', 'EmailInput', 'PhoneInput', + 'CurrencyInput', 'ToggleSwitchV2', 'DropdownV2', 'MultiselectV2', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/CurrencyInput/CurrencyInput.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/CurrencyInput/CurrencyInput.jsx new file mode 100644 index 0000000000..9d2948c8aa --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/CurrencyInput/CurrencyInput.jsx @@ -0,0 +1,132 @@ +import React, { useMemo, useState } from 'react'; +import Accordion from '@/_ui/Accordion'; +import { baseComponentProperties } from '../DefaultComponent'; +import Select from '@/_ui/Select'; +import useStore from '@/AppBuilder/_stores/store'; +import flags from 'react-phone-number-input/flags'; +import FxButton from '@/AppBuilder/CodeBuilder/Elements/FxButton'; +import CodeHinter from '@/AppBuilder/CodeEditor'; +import cx from 'classnames'; +import { CurrencyMap } from '@/AppBuilder/Widgets/PhoneCurrency/constants'; + +export const CurrencyInput = ({ componentMeta, darkMode, ...restProps }) => { + const { + layoutPropertyChanged, + component, + paramUpdated, + dataQueries, + currentState, + eventsChanged, + apps, + allComponents, + } = restProps; + + const properties = Object.keys(componentMeta.properties); + const events = Object.keys(componentMeta.events); + const validations = Object.keys(componentMeta.validation || {}); + const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties); + const defaultCountry = resolvedProperties?.defaultCountry || 'US'; + const isDefaultCountryFxOn = componentMeta?.definition?.properties?.dateFormat?.fxActive || false; + + const options = useMemo(() => { + return Object.keys(CurrencyMap).map((country) => ({ + label: `${CurrencyMap[country].prefix} (${CurrencyMap[country].currency})`, + value: country, + })); + }, []); + + const renderCustomOption = ({ label, value: optionValue }) => { + const optionStyle = { + display: 'flex', + alignItems: 'center', + justifyContent: 'start', + height: '18px', + gap: '6px', + cursor: 'pointer', + fontFamily: 'IBM Plex Sans', + fontSize: '12px', + lineHeight: '18px', + fontWeight: '400', + color: darkMode ? '#fff' : '#1B1F24', + }; + const FlagIcon = flags[optionValue]; + + return ( +
+
{FlagIcon ? : null}
+ {label} +
+ ); + }; + + const getCountryDropdown = () => { + return ( +
+
+ +
+ { + paramUpdated({ name: 'dateFormat' }, 'fxActive', !isDefaultCountryFxOn, 'properties'); + }} + /> +
+
+ {isDefaultCountryFxOn ? ( + paramUpdated({ name: 'defaultCountry' }, 'value', value, 'properties')} + /> + ) : ( +