From f6608e99c811fbf10b7f8a1e2ec56ccf6a74029b Mon Sep 17 00:00:00 2001 From: Abhimanyu Yadav <122007096+Abhi1992002@users.noreply.github.com> Date: Thu, 4 Dec 2025 20:42:32 +0530 Subject: [PATCH] feat(frontend): add expandable text input modal for better editing experience (#11510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Text inputs in the form builder can be difficult to edit when dealing with longer content. Users need a way to expand text inputs into a larger, more comfortable editing interface, especially for multi-line text, passwords, and longer string values. https://github.com/user-attachments/assets/443bf4eb-c77c-4bf6-b34c-77091e005c6d ### Changes 🏗️ - **Added `InputExpanderModal` component**: A new modal component that provides a larger textarea (300px min-height) for editing text inputs with the following features: - Copy-to-clipboard functionality with visual feedback (checkmark icon) - Toast notification on successful copy - Auto-focus on open for better UX - Proper state management to reset values when modal opens/closes - **Enhanced `TextInputWidget`**: - Added expand button (ArrowsOutIcon) with tooltip for text, password, and textarea input types - Button appears inline next to the input field - Integrated the new `InputExpanderModal` component - Improved layout with flexbox to accommodate the expand button - Added padding-right to input when expand button is visible to prevent text overlap - **Refactored file structure**: - Moved `TextInputWidget.tsx` into `TextInputWidget/` directory - Updated import path in `widgets/index.ts` - **UX improvements**: - Expand button only shows for applicable input types (text, password, textarea) - Number and integer inputs don't show expand button (not needed) - Modal preserves schema title, description, and placeholder for context ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Test expand button appears for text input fields - [x] Test expand button appears for password input fields - [x] Test expand button appears for textarea fields - [x] Test expand button does NOT appear for number/integer inputs - [x] Test clicking expand button opens modal with current value - [x] Test editing text in modal and saving updates the input field - [x] Test cancel button closes modal without saving changes - [x] Test copy-to-clipboard button copies text and shows success state - [x] Test toast notification appears on successful copy - [x] Test modal resets to original value when reopened - [x] Test modal auto-focuses textarea on open - [x] Test expand button tooltip displays correctly - [x] Test input field layout with expand button (no text overlap) --- .../TextInputWidget/InputExpanderModal.tsx | 118 ++++++++++++++++++ .../{ => TextInputWidget}/TextInputWidget.tsx | 86 +++++++++++-- .../renderers/input-renderer/widgets/index.ts | 2 +- 3 files changed, 192 insertions(+), 14 deletions(-) create mode 100644 autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/InputExpanderModal.tsx rename autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/{ => TextInputWidget}/TextInputWidget.tsx (52%) diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/InputExpanderModal.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/InputExpanderModal.tsx new file mode 100644 index 0000000000..5b19874bfb --- /dev/null +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/InputExpanderModal.tsx @@ -0,0 +1,118 @@ +"use client"; + +import React, { FC, useEffect, useState } from "react"; +import { Button } from "@/components/atoms/Button/Button"; +import { Text } from "@/components/atoms/Text/Text"; +import { useToast } from "@/components/molecules/Toast/use-toast"; +import { CheckIcon, CopyIcon } from "@phosphor-icons/react"; +import { Dialog } from "@/components/molecules/Dialog/Dialog"; +import { cn } from "@/lib/utils"; +import { Input } from "@/components/atoms/Input/Input"; + +interface InputExpanderModalProps { + isOpen: boolean; + onClose: () => void; + onSave: (value: string) => void; + title?: string; + defaultValue: string; + description?: string; + placeholder?: string; +} + +export const InputExpanderModal: FC = ({ + isOpen, + onClose, + onSave, + title, + defaultValue, + description, + placeholder, +}) => { + const [tempValue, setTempValue] = useState(defaultValue); + const [isCopied, setIsCopied] = useState(false); + const { toast } = useToast(); + + useEffect(() => { + if (isOpen) { + setTempValue(defaultValue); + setIsCopied(false); + } + }, [isOpen, defaultValue]); + + const handleSave = () => { + onSave(tempValue); + onClose(); + }; + + const copyValue = () => { + navigator.clipboard.writeText(tempValue).then(() => { + setIsCopied(true); + toast({ + title: "Copied to clipboard!", + duration: 2000, + }); + setTimeout(() => setIsCopied(false), 2000); + }); + }; + + return ( + { + if (!open) onClose(); + }, + }} + onClose={onClose} + styling={{ maxWidth: "600px", minWidth: "600px" }} + > + +
+ + {title || "Edit Text"} + + {description} + setTempValue(e.target.value)} + placeholder={placeholder || "Enter text..."} + autoFocus + /> + +
+ +
+ + + + + +
+
+
+ ); +}; diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/TextInputWidget.tsx similarity index 52% rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/TextInputWidget.tsx index e10b09bcf9..d9fea28a8d 100644 --- a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget.tsx +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/TextInputWidget.tsx @@ -1,10 +1,21 @@ +"use client"; + +import { useState } from "react"; import { WidgetProps } from "@rjsf/utils"; import { InputType, mapJsonSchemaTypeToInputType, } from "@/app/(platform)/build/components/FlowEditor/nodes/helpers"; import { Input } from "@/components/atoms/Input/Input"; +import { Button } from "@/components/atoms/Button/Button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/atoms/Tooltip/BaseTooltip"; import { BlockUIType } from "@/lib/autogpt-server-api/types"; +import { InputExpanderModal } from "./InputExpanderModal"; +import { ArrowsOutIcon } from "@phosphor-icons/react"; export const TextInputWidget = (props: WidgetProps) => { const { schema, formContext } = props; @@ -13,6 +24,8 @@ export const TextInputWidget = (props: WidgetProps) => { size?: string; }; + const [isModalOpen, setIsModalOpen] = useState(false); + const mapped = mapJsonSchemaTypeToInputType(schema); type InputConfig = { @@ -59,9 +72,25 @@ export const TextInputWidget = (props: WidgetProps) => { return props.onChange(config.handleChange(v)); }; + const handleModalSave = (value: string) => { + props.onChange(config.handleChange(value)); + setIsModalOpen(false); + }; + + const handleModalOpen = () => { + setIsModalOpen(true); + }; + // Determine input size based on context const inputSize = size === "large" ? "medium" : "small"; + // Check if this input type should show the expand button + // Show for text and password types, not for number/integer + const showExpandButton = + config.htmlType === "text" || + config.htmlType === "password" || + config.htmlType === "textarea"; + if (uiType === BlockUIType.NOTE) { return ( { } return ( - + <> +
+ + {showExpandButton && ( + + + + + Expand input + + )} +
+ + setIsModalOpen(false)} + onSave={handleModalSave} + title={schema.title || "Edit value"} + description={schema.description || ""} + defaultValue={props.value ?? ""} + placeholder={schema.placeholder || config.placeholder} + /> + ); }; diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/index.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/index.ts index cdb02c728c..3788e74fbf 100644 --- a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/index.ts +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/index.ts @@ -1,6 +1,6 @@ import { RegistryWidgetsType } from "@rjsf/utils"; import { SelectWidget } from "./SelectWidget"; -import { TextInputWidget } from "./TextInputWidget"; +import { TextInputWidget } from "./TextInputWidget/TextInputWidget"; import { SwitchWidget } from "./SwitchWidget"; import { FileWidget } from "./FileWidget"; import { DateInputWidget } from "./DateInputWidget";