@@ -10,7 +10,7 @@ interface FormFieldProps {
1010 helpText ?: string ;
1111
1212 // Validation props
13- type ?: "text" | "number" ;
13+ type ?: "text" | "number" | "json" ;
1414 min ?: number ;
1515 max ?: number ;
1616 suffix ?: string ;
@@ -50,6 +50,7 @@ const FormField = React.memo(
5050 const [ hasError , setHasError ] = useState ( false ) ;
5151 const [ errorMessage , setErrorMessage ] = useState ( "" ) ;
5252 const inputRef = useRef < HTMLInputElement > ( null ) ;
53+ const textareaRef = useRef < HTMLTextAreaElement > ( null ) ;
5354
5455 // Update internal state when initialValue changes (from parent)
5556 useEffect ( ( ) => {
@@ -106,6 +107,52 @@ const FormField = React.memo(
106107 return true ;
107108 }
108109
110+ if ( type === "json" ) {
111+ // Check if the string is empty skip validation
112+ if ( ! value . trim ( ) ) {
113+ setHasError ( false ) ;
114+ setErrorMessage ( "" ) ;
115+ return true ;
116+ }
117+
118+ try {
119+ // Try to parse the JSON
120+ const config = JSON . parse ( value ) ;
121+
122+ // Validate that the config is an object
123+ if ( typeof config !== 'object' || Array . isArray ( config ) || config === null ) {
124+ throw new Error ( "Configuration must be a valid JSON object" ) ;
125+ }
126+
127+ for ( const item in config ) {
128+ if ( ! Array . isArray ( config [ item ] ) ) {
129+ throw new Error ( `Key ${ item } is not valid and should be an array` ) ;
130+ }
131+ config [ item ] . forEach ( ( val ) => {
132+ if ( typeof val !== 'string' ) {
133+ throw new Error ( `Values from Key ${ item } should be string` ) ;
134+ }
135+ } ) ;
136+ }
137+
138+ // Additional validation could be added here based on expected structure
139+ // For example, checking specific properties or types
140+
141+ // If valid, update the preference
142+ setHasError ( false ) ;
143+ setErrorMessage ( "" ) ;
144+ return true
145+ } catch ( error ) {
146+ // Handle parsing errors
147+ console . error ( "Invalid JSON configuration:" , error ) ;
148+ setHasError ( true ) ;
149+ setErrorMessage ( `Invalid JSON configuration: ${ error } ` )
150+ // You could show an error message to the user here
151+ // Or reset to default/previous value
152+ return false
153+ }
154+ }
155+
109156 return true ;
110157 } ;
111158
@@ -116,6 +163,13 @@ const FormField = React.memo(
116163 setHasChanges ( newValue !== String ( initialValue ) ) ;
117164 } ;
118165
166+ const handleTextareaChange = ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
167+ const newValue = e . target . value ;
168+ setInputValue ( newValue ) ;
169+ validateInput ( newValue ) ;
170+ setHasChanges ( newValue !== String ( initialValue ) ) ;
171+ } ;
172+
119173 const applyChanges = ( ) => {
120174 if ( hasError ) return ;
121175
@@ -147,6 +201,15 @@ const FormField = React.memo(
147201 }
148202 } ;
149203
204+ const handleTextareaKeyDown = ( e : React . KeyboardEvent < HTMLTextAreaElement > ) => {
205+ // Only apply changes on Ctrl+Enter or Command+Enter for textarea
206+ if ( ( e . ctrlKey || e . metaKey ) && e . key === "Enter" ) {
207+ e . preventDefault ( ) ;
208+ applyChanges ( ) ;
209+ textareaRef . current ?. blur ( ) ;
210+ }
211+ } ;
212+
150213 // Default preview transform for text prefixes
151214 const defaultPreviewTransform = ( value : string , example : string ) => (
152215 < div className = "flex items-center gap-1.5" >
@@ -190,25 +253,46 @@ const FormField = React.memo(
190253 < div className = "flex w-full items-start gap-2" >
191254 < div className = "flex-1 flex flex-col" >
192255 < div className = "flex items-center" >
193- < input
194- ref = { inputRef }
195- type = "text"
196- value = { inputValue }
197- onChange = { handleChange }
198- onFocus = { ( ) => setIsFocused ( true ) }
199- onBlur = { handleBlur }
200- onKeyDown = { handleKeyDown }
201- placeholder = { placeholder }
202- className = { `p-1.5 px-2.5 text-sm w-full transition-all focus:outline-hidden ${
203- suffix ? "rounded-l-md" : "rounded-md"
204- } ${
205- hasError
206- ? "border border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-900/20"
207- : isFocused
208- ? "border border-green-400 dark:border-green-600 ring-1 ring-green-300 dark:ring-green-800 bg-white dark:bg-neutral-800"
209- : "border border-gray-300 dark:border-gray-600 bg-white dark:bg-neutral-800 hover:border-gray-400 dark:hover:border-gray-500"
210- } `}
211- />
256+ { type === "json" ? (
257+ < textarea
258+ ref = { textareaRef }
259+ value = { inputValue }
260+ onChange = { handleTextareaChange }
261+ onFocus = { ( ) => setIsFocused ( true ) }
262+ onBlur = { handleBlur }
263+ onKeyDown = { handleTextareaKeyDown }
264+ placeholder = { placeholder }
265+ rows = { 5 }
266+ className = { `p-1.5 px-2.5 text-sm w-full transition-all focus:outline-hidden rounded-md font-mono resize-y
267+ ${
268+ hasError
269+ ? "border border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-900/20"
270+ : isFocused
271+ ? "border border-green-400 dark:border-green-600 ring-1 ring-green-300 dark:ring-green-800 bg-white dark:bg-neutral-800"
272+ : "border border-gray-300 dark:border-gray-600 bg-white dark:bg-neutral-800 hover:border-gray-400 dark:hover:border-gray-500"
273+ } `}
274+ />
275+ ) : (
276+ < input
277+ ref = { inputRef }
278+ type = "text"
279+ value = { inputValue }
280+ onChange = { handleChange }
281+ onFocus = { ( ) => setIsFocused ( true ) }
282+ onBlur = { handleBlur }
283+ onKeyDown = { handleKeyDown }
284+ placeholder = { placeholder }
285+ className = { `p-1.5 px-2.5 text-sm w-full transition-all focus:outline-hidden ${
286+ suffix ? "rounded-l-md" : "rounded-md"
287+ } ${
288+ hasError
289+ ? "border border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-900/20"
290+ : isFocused
291+ ? "border border-green-400 dark:border-green-600 ring-1 ring-green-300 dark:ring-green-800 bg-white dark:bg-neutral-800"
292+ : "border border-gray-300 dark:border-gray-600 bg-white dark:bg-neutral-800 hover:border-gray-400 dark:hover:border-gray-500"
293+ } `}
294+ />
295+ ) }
212296
213297 { suffix && (
214298 < span
0 commit comments