UNPKG

@flowcsolutions/react-form-builder

Version:

A powerful, responsive form builder built with React, TypeScript, HeroUI, and TailwindCSS

1 lines 394 kB
{"version":3,"file":"index.cjs","sources":["../src/context/FormBuilderContext.tsx","../src/data/formFields.ts","../src/utils/fieldStyles.ts","../src/components/SignaturePad.tsx","../src/components/FormFieldRenderer.tsx","../src/components/SortableFormField.tsx","../src/utils/layoutUtils.ts","../src/components/FormRowRenderer.tsx","../src/components/FormRenderer.tsx","../src/utils/formExport.ts","../src/components/FormCanvas.tsx","../src/components/FieldSidebar.tsx","../src/components/PropertiesPanel.tsx","../src/components/FormBuilderToolbar.tsx","../src/examples/JsonFormRenderer.tsx","../src/pages/AnnouncementPage.tsx","../src/pages/DocumentationPage.tsx","../src/pages/WhatsNewPage.tsx","../src/App.tsx","../src/components/FormBuilderSuite.tsx"],"sourcesContent":["import React, { createContext, useContext, useReducer } from 'react';\r\nimport type { ReactNode } from 'react';\r\nimport type { FormConfig, FormField, FormSettings } from '../types/form';\r\nimport { v4 as uuidv4 } from 'uuid';\r\n\r\n// Helper function to generate unique field names with auto-numbering\r\nfunction generateUniqueFieldName(field: FormField, existingFields: FormField[]): string {\r\n // If field already has a name, use it\r\n if (field.name && field.name.trim()) {\r\n return field.name;\r\n }\r\n\r\n // Generate base name from field type\r\n const baseName = field.type.replace(/[-_]/g, '_').toLowerCase();\r\n \r\n // Check if base name already exists\r\n const existingNames = existingFields.map(f => f.name).filter(Boolean);\r\n \r\n if (!existingNames.includes(baseName)) {\r\n return baseName;\r\n }\r\n \r\n // Find the next available number\r\n let counter = 2;\r\n let uniqueName = `${baseName}${counter}`;\r\n \r\n while (existingNames.includes(uniqueName)) {\r\n counter++;\r\n uniqueName = `${baseName}${counter}`;\r\n }\r\n \r\n return uniqueName;\r\n}\r\n\r\ninterface FormBuilderState {\r\n currentForm: FormConfig;\r\n selectedFieldId: string | null;\r\n previewMode: boolean;\r\n deviceView: 'desktop' | 'tablet' | 'mobile';\r\n}\r\n\r\ntype FormBuilderAction =\r\n | { type: 'SET_FORM'; payload: FormConfig }\r\n | { type: 'ADD_FIELD'; payload: FormField }\r\n | { type: 'UPDATE_FIELD'; payload: { id: string; updates: Partial<FormField> } }\r\n | { type: 'UPDATE_FIELD_PROPERTIES'; payload: { id: string; properties: any } }\r\n | { type: 'UPDATE_FIELD_ADVANCED'; payload: { id: string; advanced: any } }\r\n | { type: 'UPDATE_FIELD_CUSTOM'; payload: { id: string; custom: any } }\r\n | { type: 'UPDATE_FIELD_EVENTS'; payload: { id: string; events: any } }\r\n | { type: 'UPDATE_FIELD_SCHEMA'; payload: { id: string; schema: any } }\r\n | { type: 'UPDATE_FIELD_LAYOUT'; payload: { id: string; layout: any } }\r\n | { type: 'DELETE_FIELD'; payload: string }\r\n | { type: 'REORDER_FIELDS'; payload: { oldIndex: number; newIndex: number } }\r\n | { type: 'SELECT_FIELD'; payload: string | null }\r\n | { type: 'SET_PREVIEW_MODE'; payload: boolean }\r\n | { type: 'SET_DEVICE_VIEW'; payload: 'desktop' | 'tablet' | 'mobile' }\r\n | { type: 'UPDATE_FORM_SETTINGS'; payload: Partial<FormSettings> }\r\n | { type: 'UPDATE_FORM_META'; payload: { title?: string; description?: string } };\r\n\r\nconst initialState: FormBuilderState = {\r\n currentForm: {\r\n id: uuidv4(),\r\n title: 'New Form',\r\n description: '',\r\n fields: [],\r\n settings: {\r\n submitButtonText: 'Submit',\r\n allowMultipleSubmissions: true,\r\n requireAuth: false,\r\n captchaEnabled: false,\r\n theme: 'auto'\r\n }\r\n },\r\n selectedFieldId: null,\r\n previewMode: false,\r\n deviceView: 'desktop'\r\n};\r\n\r\nfunction formBuilderReducer(state: FormBuilderState, action: FormBuilderAction): FormBuilderState {\r\n switch (action.type) {\r\n case 'SET_FORM':\r\n return {\r\n ...state,\r\n currentForm: action.payload,\r\n selectedFieldId: null\r\n };\r\n\r\n case 'ADD_FIELD':\r\n const fieldWithUniqueName = {\r\n ...action.payload,\r\n name: generateUniqueFieldName(action.payload, state.currentForm.fields)\r\n };\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: [...state.currentForm.fields, fieldWithUniqueName]\r\n },\r\n selectedFieldId: action.payload.id\r\n };\r\n\r\n case 'UPDATE_FIELD':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.map(field =>\r\n field.id === action.payload.id\r\n ? {\r\n ...field,\r\n ...action.payload.updates,\r\n // Deep merge nested objects to prevent component remounting\r\n properties: action.payload.updates.properties\r\n ? { ...field.properties, ...action.payload.updates.properties }\r\n : field.properties,\r\n advanced: action.payload.updates.advanced\r\n ? { ...field.advanced, ...action.payload.updates.advanced }\r\n : field.advanced,\r\n custom: action.payload.updates.custom\r\n ? { ...field.custom, ...action.payload.updates.custom }\r\n : field.custom,\r\n events: action.payload.updates.events\r\n ? { ...field.events, ...action.payload.updates.events }\r\n : field.events,\r\n schema: action.payload.updates.schema\r\n ? { ...field.schema, ...action.payload.updates.schema }\r\n : field.schema,\r\n layout: action.payload.updates.layout\r\n ? { ...field.layout, ...action.payload.updates.layout }\r\n : field.layout\r\n }\r\n : field\r\n )\r\n }\r\n };\r\n\r\n case 'UPDATE_FIELD_PROPERTIES':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.map(field =>\r\n field.id === action.payload.id\r\n ? { ...field, properties: { ...field.properties, ...action.payload.properties } }\r\n : field\r\n )\r\n }\r\n };\r\n\r\n case 'UPDATE_FIELD_ADVANCED':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.map(field =>\r\n field.id === action.payload.id\r\n ? { ...field, advanced: { ...field.advanced, ...action.payload.advanced } }\r\n : field\r\n )\r\n }\r\n };\r\n\r\n case 'UPDATE_FIELD_CUSTOM':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.map(field =>\r\n field.id === action.payload.id\r\n ? { ...field, custom: { ...field.custom, ...action.payload.custom } }\r\n : field\r\n )\r\n }\r\n };\r\n\r\n case 'UPDATE_FIELD_EVENTS':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.map(field =>\r\n field.id === action.payload.id\r\n ? { ...field, events: { ...field.events, ...action.payload.events } }\r\n : field\r\n )\r\n }\r\n };\r\n\r\n case 'UPDATE_FIELD_SCHEMA':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.map(field =>\r\n field.id === action.payload.id\r\n ? { ...field, schema: { ...field.schema, ...action.payload.schema } }\r\n : field\r\n )\r\n }\r\n };\r\n\r\n case 'UPDATE_FIELD_LAYOUT':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.map(field =>\r\n field.id === action.payload.id\r\n ? { ...field, layout: { ...field.layout, ...action.payload.layout } }\r\n : field\r\n )\r\n }\r\n };\r\n\r\n case 'DELETE_FIELD':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields: state.currentForm.fields.filter(field => field.id !== action.payload)\r\n },\r\n selectedFieldId: state.selectedFieldId === action.payload ? null : state.selectedFieldId\r\n };\r\n\r\n case 'REORDER_FIELDS':\r\n const fields = [...state.currentForm.fields];\r\n const [removed] = fields.splice(action.payload.oldIndex, 1);\r\n fields.splice(action.payload.newIndex, 0, removed);\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n fields\r\n }\r\n };\r\n\r\n case 'SELECT_FIELD':\r\n return {\r\n ...state,\r\n selectedFieldId: action.payload\r\n };\r\n\r\n case 'SET_PREVIEW_MODE':\r\n return {\r\n ...state,\r\n previewMode: action.payload,\r\n selectedFieldId: action.payload ? null : state.selectedFieldId\r\n };\r\n\r\n case 'SET_DEVICE_VIEW':\r\n return {\r\n ...state,\r\n deviceView: action.payload\r\n };\r\n\r\n case 'UPDATE_FORM_SETTINGS':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n settings: {\r\n ...state.currentForm.settings,\r\n ...action.payload\r\n }\r\n }\r\n };\r\n\r\n case 'UPDATE_FORM_META':\r\n return {\r\n ...state,\r\n currentForm: {\r\n ...state.currentForm,\r\n title: action.payload.title ?? state.currentForm.title,\r\n description: action.payload.description ?? state.currentForm.description\r\n }\r\n };\r\n\r\n default:\r\n return state;\r\n }\r\n}\r\n\r\ninterface FormBuilderContextType {\r\n state: FormBuilderState;\r\n dispatch: React.Dispatch<FormBuilderAction>;\r\n actions: {\r\n setForm: (form: FormConfig) => void;\r\n addField: (field: FormField) => void;\r\n updateField: (id: string, updates: Partial<FormField>) => void;\r\n updateFieldProperties: (id: string, properties: any) => void;\r\n updateFieldAdvanced: (id: string, advanced: any) => void;\r\n updateFieldCustom: (id: string, custom: any) => void;\r\n updateFieldEvents: (id: string, events: any) => void;\r\n updateFieldSchema: (id: string, schema: any) => void;\r\n updateFieldLayout: (id: string, layout: any) => void;\r\n deleteField: (id: string) => void;\r\n reorderFields: (oldIndex: number, newIndex: number) => void;\r\n selectField: (id: string | null) => void;\r\n setPreviewMode: (enabled: boolean) => void;\r\n setDeviceView: (view: 'desktop' | 'tablet' | 'mobile') => void;\r\n updateFormSettings: (settings: Partial<FormSettings>) => void;\r\n updateFormMeta: (meta: { title?: string; description?: string }) => void;\r\n };\r\n}\r\n\r\nconst FormBuilderContext = createContext<FormBuilderContextType | undefined>(undefined);\r\n\r\nexport function FormBuilderProvider({ children }: { children: ReactNode }) {\r\n const [state, dispatch] = useReducer(formBuilderReducer, initialState);\r\n\r\n const actions = {\r\n setForm: (form: FormConfig) => dispatch({ type: 'SET_FORM', payload: form }),\r\n addField: (field: FormField) => dispatch({ type: 'ADD_FIELD', payload: field }),\r\n updateField: (id: string, updates: Partial<FormField>) => \r\n dispatch({ type: 'UPDATE_FIELD', payload: { id, updates } }),\r\n updateFieldProperties: (id: string, properties: any) => \r\n dispatch({ type: 'UPDATE_FIELD_PROPERTIES', payload: { id, properties } }),\r\n updateFieldAdvanced: (id: string, advanced: any) => \r\n dispatch({ type: 'UPDATE_FIELD_ADVANCED', payload: { id, advanced } }),\r\n updateFieldCustom: (id: string, custom: any) => \r\n dispatch({ type: 'UPDATE_FIELD_CUSTOM', payload: { id, custom } }),\r\n updateFieldEvents: (id: string, events: any) => \r\n dispatch({ type: 'UPDATE_FIELD_EVENTS', payload: { id, events } }),\r\n updateFieldSchema: (id: string, schema: any) => \r\n dispatch({ type: 'UPDATE_FIELD_SCHEMA', payload: { id, schema } }),\r\n updateFieldLayout: (id: string, layout: any) => \r\n dispatch({ type: 'UPDATE_FIELD_LAYOUT', payload: { id, layout } }),\r\n deleteField: (id: string) => dispatch({ type: 'DELETE_FIELD', payload: id }),\r\n reorderFields: (oldIndex: number, newIndex: number) => \r\n dispatch({ type: 'REORDER_FIELDS', payload: { oldIndex, newIndex } }),\r\n selectField: (id: string | null) => dispatch({ type: 'SELECT_FIELD', payload: id }),\r\n setPreviewMode: (enabled: boolean) => dispatch({ type: 'SET_PREVIEW_MODE', payload: enabled }),\r\n setDeviceView: (view: 'desktop' | 'tablet' | 'mobile') => \r\n dispatch({ type: 'SET_DEVICE_VIEW', payload: view }),\r\n updateFormSettings: (settings: Partial<FormSettings>) => \r\n dispatch({ type: 'UPDATE_FORM_SETTINGS', payload: settings }),\r\n updateFormMeta: (meta: { title?: string; description?: string }) => \r\n dispatch({ type: 'UPDATE_FORM_META', payload: meta })\r\n };\r\n\r\n return (\r\n <FormBuilderContext.Provider value={{ state, dispatch, actions }}>\r\n {children}\r\n </FormBuilderContext.Provider>\r\n );\r\n}\r\n\r\nexport function useFormBuilder() {\r\n const context = useContext(FormBuilderContext);\r\n if (context === undefined) {\r\n throw new Error('useFormBuilder must be used within a FormBuilderProvider');\r\n }\r\n return context;\r\n}\r\n","import type { FormField, FormFieldType, DragItem } from '../types/form';\r\nimport { v4 as uuidv4 } from 'uuid';\r\n\r\n/**\r\n * Creates a new form field with default properties\r\n */\r\nexport function createFormField(type: FormFieldType): FormField {\r\n const baseField = {\r\n id: uuidv4(),\r\n type,\r\n label: getDefaultLabel(type),\r\n required: false,\r\n properties: {},\r\n advanced: {\r\n valued: true,\r\n valueType: 'string' as const,\r\n dataBindingType: 'twoWay' as const,\r\n calculable: false,\r\n localizable: false,\r\n readOnly: false,\r\n disabled: false,\r\n asyncValidation: false,\r\n deferFieldCalculation: false\r\n },\r\n layout: {\r\n columnSpan: 12,\r\n gridClass: 'col-span-12'\r\n },\r\n custom: {\r\n cssClasses: [],\r\n dataAttributes: {}\r\n },\r\n events: {},\r\n schema: {\r\n componentKind: 'component' as const,\r\n category: getCategoryForType(type),\r\n typeName: type,\r\n icon: getIconForType(type),\r\n nestingLevel: 0,\r\n builderOnly: false\r\n }\r\n };\r\n\r\n // Add type-specific properties\r\n switch (type) {\r\n case 'text':\r\n case 'email':\r\n case 'password':\r\n case 'phone':\r\n case 'url':\r\n return {\r\n ...baseField,\r\n placeholder: `Enter ${baseField.label.toLowerCase()}`,\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full',\r\n maxLength: 255,\r\n colorVariant: 'default',\r\n size: 'md',\r\n borderRadius: 'md'\r\n }\r\n };\r\n\r\n case 'textarea':\r\n return {\r\n ...baseField,\r\n placeholder: `Enter ${baseField.label.toLowerCase()}`,\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full',\r\n rows: 4,\r\n maxLength: 1000,\r\n colorVariant: 'default',\r\n size: 'md',\r\n borderRadius: 'md'\r\n }\r\n };\r\n\r\n case 'number':\r\n case 'number-format':\r\n return {\r\n ...baseField,\r\n placeholder: 'Enter number',\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valueType: 'number' as const\r\n }\r\n };\r\n\r\n case 'select':\r\n case 'radio':\r\n case 'checkbox':\r\n case 'multiselect':\r\n case 'autocomplete':\r\n return {\r\n ...baseField,\r\n options: [\r\n { label: 'Option 1', value: 'option1' },\r\n { label: 'Option 2', value: 'option2' }\r\n ],\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full',\r\n colorVariant: 'default',\r\n size: 'md',\r\n borderRadius: 'md',\r\n orientation: type === 'radio' || type === 'checkbox' ? 'vertical' : undefined,\r\n componentAlignment: type === 'radio' || type === 'checkbox' ? 'left' : undefined\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valueType: type === 'multiselect' || type === 'checkbox' ? 'array' as const : 'string' as const\r\n }\r\n };\r\n\r\n case 'date':\r\n case 'datetime':\r\n case 'time':\r\n case 'calendar':\r\n return {\r\n ...baseField,\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valueType: 'date' as const\r\n }\r\n };\r\n\r\n case 'file':\r\n return {\r\n ...baseField,\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full',\r\n accept: '',\r\n multiple: false\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valueType: 'array' as const\r\n }\r\n };\r\n\r\n case 'rating':\r\n case 'range':\r\n return {\r\n ...baseField,\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full',\r\n max: 5,\r\n componentAlignment: type === 'rating' ? 'left' : undefined\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valueType: 'number' as const\r\n }\r\n };\r\n\r\n case 'switch':\r\n return {\r\n ...baseField,\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full',\r\n size: 'md',\r\n componentAlignment: 'left'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valueType: 'boolean' as const\r\n }\r\n };\r\n\r\n case 'signature':\r\n return {\r\n ...baseField,\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valueType: 'string' as const\r\n }\r\n };\r\n\r\n // Static content fields\r\n case 'header':\r\n return {\r\n ...baseField,\r\n label: 'Header',\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valued: false\r\n }\r\n };\r\n\r\n case 'paragraph':\r\n case 'label':\r\n case 'message':\r\n return {\r\n ...baseField,\r\n label: 'Paragraph',\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valued: false\r\n }\r\n };\r\n\r\n case 'image':\r\n return {\r\n ...baseField,\r\n label: 'Image',\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valued: false\r\n }\r\n };\r\n\r\n case 'button':\r\n return {\r\n ...baseField,\r\n label: 'Button',\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full',\r\n colorVariant: 'primary',\r\n size: 'md',\r\n borderRadius: 'md',\r\n variant: 'solid'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valued: false\r\n }\r\n };\r\n\r\n // Structure fields\r\n case 'section':\r\n case 'pagebreak':\r\n return {\r\n ...baseField,\r\n label: 'Section',\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valued: false\r\n }\r\n };\r\n\r\n case 'container':\r\n case 'card':\r\n return {\r\n ...baseField,\r\n label: 'Container',\r\n properties: {\r\n ...baseField.properties,\r\n width: 'full'\r\n },\r\n advanced: {\r\n ...baseField.advanced,\r\n valued: false\r\n }\r\n };\r\n\r\n default:\r\n return baseField;\r\n }\r\n}\r\n\r\nfunction getDefaultLabel(type: FormFieldType): string {\r\n const labels: Record<FormFieldType, string> = {\r\n // Input fields\r\n text: 'Text Input',\r\n email: 'Email',\r\n password: 'Password',\r\n number: 'Number',\r\n phone: 'Phone',\r\n url: 'URL',\r\n textarea: 'Text Area',\r\n \r\n // Selection fields\r\n select: 'Select',\r\n multiselect: 'Multi Select',\r\n radio: 'Radio Group',\r\n checkbox: 'Checkbox Group',\r\n switch: 'Switch',\r\n autocomplete: 'Auto Complete',\r\n search: 'Search',\r\n \r\n // Date/Time fields\r\n date: 'Date',\r\n datetime: 'Date Time',\r\n time: 'Time',\r\n calendar: 'Calendar',\r\n \r\n // Special fields\r\n file: 'File Upload',\r\n rating: 'Rating',\r\n signature: 'Signature',\r\n range: 'Range Slider',\r\n 'rich-text': 'Rich Text',\r\n 'number-format': 'Formatted Number',\r\n 'pattern-format': 'Pattern Format',\r\n \r\n // Static content\r\n button: 'Button',\r\n label: 'Label',\r\n header: 'Header',\r\n paragraph: 'Paragraph',\r\n image: 'Image',\r\n message: 'Message',\r\n 'progress-line': 'Progress Line',\r\n 'progress-circle': 'Progress Circle',\r\n tooltip: 'Tooltip',\r\n 'qr-code': 'QR Code',\r\n html: 'HTML',\r\n \r\n // Structure\r\n container: 'Container',\r\n card: 'Card',\r\n tab: 'Tab',\r\n breadcrumb: 'Breadcrumb',\r\n section: 'Section',\r\n pagebreak: 'Page Break',\r\n repeater: 'Repeater',\r\n \r\n // Template fields\r\n slot: 'Slot',\r\n template: 'Template',\r\n \r\n // Error fields\r\n 'error-message': 'Error Message'\r\n };\r\n \r\n return labels[type] || 'Field';\r\n}\r\n\r\nfunction getCategoryForType(type: FormFieldType): string {\r\n const categories: Record<FormFieldType, string> = {\r\n // Input fields\r\n text: 'fields',\r\n email: 'fields',\r\n password: 'fields',\r\n number: 'fields',\r\n phone: 'fields',\r\n url: 'fields',\r\n textarea: 'fields',\r\n \r\n // Selection fields\r\n select: 'fields',\r\n multiselect: 'fields',\r\n radio: 'fields',\r\n checkbox: 'fields',\r\n switch: 'fields',\r\n autocomplete: 'fields',\r\n search: 'fields',\r\n \r\n // Date/Time fields\r\n date: 'fields',\r\n datetime: 'fields',\r\n time: 'fields',\r\n calendar: 'fields',\r\n \r\n // Special fields\r\n file: 'fields',\r\n rating: 'fields',\r\n signature: 'fields',\r\n range: 'fields',\r\n 'rich-text': 'fields',\r\n 'number-format': 'fields',\r\n 'pattern-format': 'fields',\r\n \r\n // Static content\r\n button: 'static',\r\n label: 'static',\r\n header: 'static',\r\n paragraph: 'static',\r\n image: 'static',\r\n message: 'static',\r\n 'progress-line': 'static',\r\n 'progress-circle': 'static',\r\n tooltip: 'static',\r\n 'qr-code': 'static',\r\n html: 'static',\r\n \r\n // Structure\r\n container: 'structure',\r\n card: 'structure',\r\n tab: 'structure',\r\n breadcrumb: 'structure',\r\n section: 'structure',\r\n pagebreak: 'structure',\r\n repeater: 'structure',\r\n \r\n // Template fields\r\n slot: 'templates',\r\n template: 'templates',\r\n \r\n // Error fields\r\n 'error-message': 'error'\r\n };\r\n \r\n return categories[type] || 'fields';\r\n}\r\n\r\nfunction getIconForType(type: FormFieldType): string {\r\n const icons: Record<FormFieldType, string> = {\r\n // Input fields\r\n text: 'Type',\r\n email: 'Mail',\r\n password: 'Key',\r\n number: 'Hash',\r\n phone: 'Phone',\r\n url: 'Link',\r\n textarea: 'FileText',\r\n \r\n // Selection fields\r\n select: 'ChevronDown',\r\n multiselect: 'List',\r\n radio: 'Circle',\r\n checkbox: 'Square',\r\n switch: 'ToggleLeft',\r\n autocomplete: 'Search',\r\n search: 'Search',\r\n \r\n // Date/Time fields\r\n date: 'Calendar',\r\n datetime: 'Clock',\r\n time: 'Clock',\r\n calendar: 'Calendar',\r\n \r\n // Special fields\r\n file: 'Upload',\r\n rating: 'Star',\r\n signature: 'PenTool',\r\n range: 'Sliders',\r\n 'rich-text': 'FileText',\r\n 'number-format': 'Hash',\r\n 'pattern-format': 'Hash',\r\n \r\n // Static content\r\n button: 'MousePointer',\r\n label: 'Tag',\r\n header: 'Heading',\r\n paragraph: 'Type',\r\n image: 'Image',\r\n message: 'MessageSquare',\r\n 'progress-line': 'TrendingUp',\r\n 'progress-circle': 'Circle',\r\n tooltip: 'Info',\r\n 'qr-code': 'QrCode',\r\n html: 'Code',\r\n \r\n // Structure\r\n container: 'Box',\r\n card: 'Square',\r\n tab: 'Tabs',\r\n breadcrumb: 'ChevronRight',\r\n section: 'Layout',\r\n pagebreak: 'Scissors',\r\n repeater: 'Copy',\r\n \r\n // Template fields\r\n slot: 'Grid',\r\n template: 'FileTemplate',\r\n \r\n // Error fields\r\n 'error-message': 'AlertCircle'\r\n };\r\n \r\n return icons[type] || 'Square';\r\n}\r\n\r\n/**\r\n * Field templates for the form builder\r\n */\r\nexport const FIELD_TEMPLATES: Record<FormFieldType, FormField> = {\r\n // Input fields\r\n text: createFormField('text'),\r\n email: createFormField('email'),\r\n password: createFormField('password'),\r\n number: createFormField('number'),\r\n phone: createFormField('phone'),\r\n url: createFormField('url'),\r\n textarea: createFormField('textarea'),\r\n \r\n // Selection fields\r\n select: createFormField('select'),\r\n multiselect: createFormField('multiselect'),\r\n radio: createFormField('radio'),\r\n checkbox: createFormField('checkbox'),\r\n switch: createFormField('switch'),\r\n autocomplete: createFormField('autocomplete'),\r\n search: createFormField('search'),\r\n \r\n // Date/Time fields\r\n date: createFormField('date'),\r\n datetime: createFormField('datetime'),\r\n time: createFormField('time'),\r\n calendar: createFormField('calendar'),\r\n \r\n // Special fields\r\n file: createFormField('file'),\r\n rating: createFormField('rating'),\r\n signature: createFormField('signature'),\r\n range: createFormField('range'),\r\n 'rich-text': createFormField('rich-text'),\r\n 'number-format': createFormField('number-format'),\r\n 'pattern-format': createFormField('pattern-format'),\r\n \r\n // Static content\r\n button: createFormField('button'),\r\n label: createFormField('label'),\r\n header: createFormField('header'),\r\n paragraph: createFormField('paragraph'),\r\n image: createFormField('image'),\r\n message: createFormField('message'),\r\n 'progress-line': createFormField('progress-line'),\r\n 'progress-circle': createFormField('progress-circle'),\r\n tooltip: createFormField('tooltip'),\r\n 'qr-code': createFormField('qr-code'),\r\n html: createFormField('html'),\r\n \r\n // Structure\r\n container: createFormField('container'),\r\n card: createFormField('card'),\r\n tab: createFormField('tab'),\r\n breadcrumb: createFormField('breadcrumb'),\r\n section: createFormField('section'),\r\n pagebreak: createFormField('pagebreak'),\r\n repeater: createFormField('repeater'),\r\n \r\n // Template fields\r\n slot: createFormField('slot'),\r\n template: createFormField('template'),\r\n \r\n // Error fields\r\n 'error-message': createFormField('error-message')\r\n};\r\n\r\n/**\r\n * Drag items for the sidebar (most commonly used components)\r\n */\r\nexport const DRAG_ITEMS: DragItem[] = [\r\n // Most common input fields\r\n { id: 'text', type: 'text', label: 'Text Input', icon: 'Type', category: 'fields' },\r\n { id: 'email', type: 'email', label: 'Email', icon: 'Mail', category: 'fields' },\r\n { id: 'password', type: 'password', label: 'Password', icon: 'Key', category: 'fields' },\r\n { id: 'number', type: 'number', label: 'Number', icon: 'Hash', category: 'fields' },\r\n { id: 'phone', type: 'phone', label: 'Phone', icon: 'Phone', category: 'fields' },\r\n { id: 'textarea', type: 'textarea', label: 'Text Area', icon: 'FileText', category: 'fields' },\r\n \r\n // Selection fields\r\n { id: 'select', type: 'select', label: 'Select', icon: 'ChevronDown', category: 'fields' },\r\n { id: 'autocomplete', type: 'autocomplete', label: 'Auto Complete', icon: 'Search', category: 'fields' },\r\n { id: 'radio', type: 'radio', label: 'Radio Group', icon: 'Circle', category: 'fields' },\r\n { id: 'checkbox', type: 'checkbox', label: 'Checkbox Group', icon: 'Square', category: 'fields' },\r\n { id: 'switch', type: 'switch', label: 'Switch', icon: 'ToggleLeft', category: 'fields' },\r\n \r\n // Date/Time fields\r\n { id: 'date', type: 'date', label: 'Date', icon: 'Calendar', category: 'fields' },\r\n { id: 'time', type: 'time', label: 'Time', icon: 'Clock', category: 'fields' },\r\n \r\n // Special fields\r\n { id: 'file', type: 'file', label: 'File Upload', icon: 'Upload', category: 'fields' },\r\n { id: 'rating', type: 'rating', label: 'Rating', icon: 'Star', category: 'fields' },\r\n \r\n // Static content\r\n { id: 'header', type: 'header', label: 'Header', icon: 'Heading', category: 'static' },\r\n { id: 'paragraph', type: 'paragraph', label: 'Paragraph', icon: 'Type', category: 'static' },\r\n { id: 'image', type: 'image', label: 'Image', icon: 'Image', category: 'static' },\r\n { id: 'button', type: 'button', label: 'Button', icon: 'MousePointer', category: 'static' },\r\n \r\n // Structure\r\n { id: 'section', type: 'section', label: 'Section', icon: 'Layout', category: 'structure' },\r\n { id: 'container', type: 'container', label: 'Container', icon: 'Box', category: 'structure' },\r\n { id: 'card', type: 'card', label: 'Card', icon: 'Square', category: 'structure' }\r\n];\r\n\r\n/**\r\n * Field categories for organizing the sidebar\r\n */\r\nexport const FIELD_CATEGORIES = [\r\n {\r\n id: 'fields',\r\n label: 'Form Fields',\r\n description: 'Input and selection fields'\r\n },\r\n {\r\n id: 'static',\r\n label: 'Static Content',\r\n description: 'Text, images, and layout elements'\r\n },\r\n {\r\n id: 'structure',\r\n label: 'Structure',\r\n description: 'Containers and layout components'\r\n },\r\n {\r\n id: 'templates',\r\n label: 'Templates',\r\n description: 'Pre-built field combinations'\r\n },\r\n {\r\n id: 'error',\r\n label: 'Error Handling',\r\n description: 'Error display components'\r\n }\r\n];\r\n","import type { FormField } from '../types/form';\r\n\r\n// Build HeroUI-specific classNames object for components\r\nexport function buildHeroUIClasses(field: FormField, isEditor: boolean = false) {\r\n const borderRadius = getBorderRadiusClass(field.properties?.borderRadius);\r\n const spacing = getSpacingClasses(field);\r\n const responsive = getResponsiveClasses(field, isEditor);\r\n \r\n // Get custom CSS classes from both legacy (properties) and new (custom) locations\r\n const legacyClasses = field.properties?.customClasses || '';\r\n const newClasses = field.custom?.cssClasses?.join(' ') || '';\r\n const allCustomClasses = [legacyClasses, newClasses].filter(Boolean).join(' ');\r\n \r\n // Parse custom classes and categorize them for appropriate HeroUI slots\r\n const customClasses = parseCustomClasses(allCustomClasses);\r\n \r\n // Get user-defined classNames from properties\r\n const userClassNames = field.properties?.classNames || {};\r\n\r\n // Helper function to ensure Tailwind classes have proper specificity\r\n const ensureTailwindPrecedence = (userClass: string) => {\r\n if (!userClass) return '';\r\n // For color classes, ensure they override HeroUI defaults\r\n if (userClass.includes('text-') || userClass.includes('bg-') || userClass.includes('border-')) {\r\n return userClass;\r\n }\r\n return userClass;\r\n };\r\n\r\n const result = {\r\n // Base wrapper/container classes - for outer spacing and visibility\r\n base: [\r\n spacing.margin,\r\n spacing.padding,\r\n !isEditor ? responsive.visibility : '', // Only apply responsive hiding in preview/renderer\r\n customClasses.wrapper,\r\n userClassNames.base || ''\r\n ].filter(Boolean).join(' '),\r\n \r\n // Label classes - ensure custom classes take precedence\r\n label: [\r\n 'text-left', // Ensure labels are left-aligned by default\r\n customClasses.label,\r\n ensureTailwindPrecedence(userClassNames.label || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Input wrapper classes - for border, shadow, background, etc.\r\n inputWrapper: [\r\n borderRadius,\r\n customClasses.inputWrapper,\r\n ensureTailwindPrecedence(userClassNames.inputWrapper || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Inner wrapper classes\r\n innerWrapper: [\r\n ensureTailwindPrecedence(userClassNames.innerWrapper || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Main wrapper classes\r\n mainWrapper: [\r\n ensureTailwindPrecedence(userClassNames.mainWrapper || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Input field classes - ensure custom text styles take precedence\r\n input: [\r\n field.properties?.alignment || '',\r\n customClasses.input,\r\n ensureTailwindPrecedence(userClassNames.input || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Clear button classes\r\n clearButton: [\r\n ensureTailwindPrecedence(userClassNames.clearButton || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Helper wrapper classes\r\n helperWrapper: [\r\n ensureTailwindPrecedence(userClassNames.helperWrapper || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Description classes\r\n description: [\r\n 'text-left', // Ensure description text is left-aligned\r\n customClasses.description,\r\n ensureTailwindPrecedence(userClassNames.description || '')\r\n ].filter(Boolean).join(' '),\r\n \r\n // Error message classes\r\n errorMessage: [\r\n ensureTailwindPrecedence(userClassNames.errorMessage || '')\r\n ].filter(Boolean).join(' ')\r\n };\r\n\r\n return result;\r\n}\r\n\r\n// Parse custom CSS classes and categorize them for appropriate HeroUI component slots\r\nfunction parseCustomClasses(customClasses: string) {\r\n if (!customClasses.trim()) {\r\n return {\r\n wrapper: '',\r\n inputWrapper: '',\r\n input: '',\r\n label: '',\r\n description: ''\r\n };\r\n }\r\n\r\n const classes = customClasses.trim().split(/\\s+/);\r\n const result = {\r\n wrapper: [] as string[],\r\n inputWrapper: [] as string[],\r\n input: [] as string[],\r\n label: [] as string[],\r\n description: [] as string[]\r\n };\r\n\r\n classes.forEach(className => {\r\n // Input wrapper styles (border, shadow, background, rounded, etc.)\r\n if (className.match(/^(border|shadow|bg-|rounded|ring)/)) {\r\n result.inputWrapper.push(className);\r\n }\r\n // Text and font styling for input content\r\n else if (className.match(/^(text-|font-|placeholder|italic|underline)/)) {\r\n result.input.push(className);\r\n }\r\n // Layout and spacing classes go to wrapper\r\n else if (className.match(/^(m[tlrb]?-|p[tlrb]?-|w-|h-|max-|min-|flex|grid|col-|row-|gap-|space-|justify-|items-|self-)/)) {\r\n result.wrapper.push(className);\r\n }\r\n // Everything else goes to input wrapper by default\r\n else {\r\n result.inputWrapper.push(className);\r\n }\r\n });\r\n\r\n return {\r\n wrapper: result.wrapper.join(' '),\r\n inputWrapper: result.inputWrapper.join(' '),\r\n input: result.input.join(' '),\r\n label: result.label.join(' '),\r\n description: result.description.join(' ')\r\n };\r\n}\r\n\r\nfunction getBorderRadiusClass(borderRadius?: string): string {\r\n switch (borderRadius) {\r\n case 'none':\r\n return 'rounded-none';\r\n case 'small':\r\n return 'rounded-sm';\r\n case 'default':\r\n return 'rounded-md';\r\n case 'large':\r\n return 'rounded-lg';\r\n case 'full':\r\n return 'rounded-full';\r\n default:\r\n return '';\r\n }\r\n}\r\n\r\nfunction getSpacingClasses(field: FormField) {\r\n const marginTop = field.properties?.marginTop;\r\n const marginBottom = field.properties?.marginBottom;\r\n const padding = field.properties?.padding;\r\n\r\n return {\r\n margin: [\r\n marginTop || '',\r\n marginBottom || ''\r\n ].filter(Boolean).join(' '),\r\n \r\n padding: padding || ''\r\n };\r\n}\r\n\r\nfunction getResponsiveClasses(field: FormField, isEditor: boolean = false) {\r\n const classes: string[] = [];\r\n \r\n // Only apply responsive hiding classes in preview/renderer, not in editor\r\n if (!isEditor) {\r\n if (field.properties?.hideOnMobile) {\r\n classes.push('hidden sm:block');\r\n }\r\n if (field.properties?.hideOnTablet) {\r\n classes.push('sm:hidden lg:block');\r\n }\r\n if (field.properties?.hideOnDesktop) {\r\n classes.push('lg:hidden');\r\n }\r\n }\r\n\r\n return {\r\n visibility: classes.join(' ')\r\n };\r\n}\r\n\r\n// Legacy function for backward compatibility\r\nexport function buildFieldClasses(field: FormField): string {\r\n const heroUIClasses = buildHeroUIClasses(field);\r\n return heroUIClasses.base;\r\n}\r\n\r\nexport function buildFieldWrapperClasses(field: FormField, isEditor: boolean = false): string {\r\n const classes: string[] = [];\r\n\r\n // Grid span classes\r\n if (field.layout?.gridClass) {\r\n classes.push(field.layout.gridClass);\r\n }\r\n\r\n // Add responsive visibility only for preview/renderer, not editor\r\n const responsive = getResponsiveClasses(field, isEditor);\r\n if (responsive.visibility) {\r\n classes.push(responsive.visibility);\r\n }\r\n\r\n return classes.join(' ');\r\n}\r\n","import React, { useRef, useEffect, useState } from 'react';\r\nimport { Button } from '@heroui/react';\r\nimport { RotateCcw, Check } from 'lucide-react';\r\n\r\ninterface SignaturePadProps {\r\n value?: string;\r\n onChange?: (signature: string) => void;\r\n width?: number;\r\n height?: number;\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function SignaturePad({ \r\n value, \r\n onChange, \r\n width = 400, \r\n height = 200, \r\n disabled = false,\r\n className = '' \r\n}: SignaturePadProps) {\r\n const canvasRef = useRef<HTMLCanvasElement>(null);\r\n const [isDrawing, setIsDrawing] = useState(false);\r\n const [isEmpty, setIsEmpty] = useState(true);\r\n const [lastPos, setLastPos] = useState({ x: 0, y: 0 });\r\n\r\n useEffect(() => {\r\n const canvas = canvasRef.current;\r\n if (!canvas) return;\r\n\r\n const ctx = canvas.getContext('2d');\r\n if (!ctx) return;\r\n\r\n // Set canvas size\r\n canvas.width = width;\r\n canvas.height = height;\r\n\r\n // Set drawing style\r\n ctx.lineCap = 'round';\r\n ctx.lineJoin = 'round';\r\n ctx.strokeStyle = '#000000';\r\n ctx.lineWidth = 2;\r\n\r\n // Clear canvas with white background\r\n ctx.fillStyle = '#ffffff';\r\n ctx.fillRect(0, 0, width, height);\r\n\r\n // Load existing signature if provided\r\n if (value && value.startsWith('data:image')) {\r\n const img = new Image();\r\n img.onload = () => {\r\n ctx.clearRect(0, 0, width, height);\r\n ctx.fillStyle = '#ffffff';\r\n ctx.fillRect(0, 0, width, height);\r\n ctx.drawImage(img, 0, 0, width, height);\r\n setIsEmpty(false);\r\n };\r\n img.src = value;\r\n }\r\n }, [width, height, value]);\r\n\r\n const getEventPos = (event: React.MouseEvent | React.TouchEvent): { x: number; y: number } => {\r\n const canvas = canvasRef.current;\r\n if (!canvas) return { x: 0, y: 0 };\r\n\r\n const rect = canvas.getBoundingClientRect();\r\n const scaleX = canvas.width / rect.width;\r\n const scaleY = canvas.height / rect.height;\r\n\r\n if ('touches' in event) {\r\n const touch = event.touches[0];\r\n return {\r\n x: (touch.clientX - rect.left) * scaleX,\r\n y: (touch.clientY - rect.top) * scaleY\r\n };\r\n } else {\r\n return {\r\n x: (event.clientX - rect.left) * scaleX,\r\n y: (event.clientY - rect.top) * scaleY\r\n };\r\n }\r\n };\r\n\r\n const startDrawing = (event: React.MouseEvent | React.TouchEvent) => {\r\n if (disabled) return;\r\n \r\n event.preventDefault();\r\n const pos = getEventPos(event);\r\n setIsDrawing(true);\r\n setLastPos(pos);\r\n setIsEmpty(false);\r\n };\r\n\r\n const draw = (event: React.MouseEvent | React.TouchEvent) => {\r\n if (!isDrawing || disabled) return;\r\n \r\n event.preventDefault();\r\n const canvas = canvasRef.current;\r\n const ctx = canvas?.getContext('2d');\r\n if (!canvas || !ctx) return;\r\n\r\n const currentPos = getEventPos(event);\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(lastPos.x, lastPos.y);\r\n ctx.lineTo(currentPos.x, currentPos.y);\r\n ctx.stroke();\r\n\r\n setLastPos(currentPos);\r\n };\r\n\r\n const stopDrawing = () => {\r\n if (!isDrawing) return;\r\n setIsDrawing(false);\r\n saveSignature();\r\n };\r\n\r\n const saveSignature = () => {\r\n const canvas = canvasRef.current;\r\n if (!canvas) return;\r\n\r\n const dataURL = canvas.toDataURL('image/png');\r\n onChange?.(dataURL);\r\n };\r\n\r\n const clearSignature = () => {\r\n const canvas = canvasRef.current;\r\n const ctx = canvas?.getContext('2d');\r\n if (!canvas || !ctx) return;\r\n\r\n ctx.fillStyle = '#ffffff';\r\n ctx.fillRect(0, 0, width, height);\r\n setIsEmpty(true);\r\n onChange?.('');\r\n };\r\n\r\n return (\r\n <div className={`signature-pad ${className}`}>\r\n <div className=\"border-2 border-default-300 rounded-lg overflow-hidden bg-white\">\r\n <canvas\r\n ref={canvasRef}\r\n className=\"block cursor-crosshair touch-none\"\r\n style={{ width: '100%', height: 'auto', maxWidth: `${width}px` }}\r\n onMouseDown={startDrawing}\r\n onMouseMove={draw}\r\n onMouseUp={stopDrawing}\r\n onMouseLeave={stopDrawing}\r\n onTouchStart={startDrawing}\r\n onTouchMove={draw}\r\n onTouchEnd={stopDrawing}\r\n />\r\n </div>\r\n \r\n <div className=\"flex justify-between items-center mt-3\">\r\n <div className=\"text-xs text-default-500\">\r\n {isEmpty ? 'Sign above' : 'Signature captured'}\r\n </div>\r\n <div className=\"flex gap-2\">\r\n <Button\r\n size=\"sm\"\r\n variant=\"flat\"\r\n color=\"warning\"\r\n startContent={<RotateCcw size={16} />}\r\n onPress={clearSignature}\r\n isDisabled={disabled || isEmpty}\r\n >\r\n Clear\r\n </Button>\r\n {!isEmpty && (\r\n <Button\r\n size=\"sm\"\r\n color=\"success\"\r\n variant=\"flat\"\r\n startContent={<Check size={16} />}\r\n onPress={saveSignature}\r\n isDisabled={disabled}\r\n >\r\n Save\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import {\r\n Input,\r\n Textarea,\r\n Select,\r\n SelectItem,\r\n Autocomplete,\r\n AutocompleteItem,\r\n Checkbox,\r\n RadioGroup,\r\n Radio,\r\n Switch,\r\n Card,\r\n CardBody,\r\n Divider,\r\n DateInput,\r\n TimeInput,\r\n Button,\r\n} from \"@heroui/react\";\r\nimport React from \"react\";\r\nimport type { FormField } from \"../types/form\";\r\nimport { Star, Upload, Smartphone, Tablet, Monitor } from \"lucide-react\";\r\nimport { buildHeroUIClasses } from \"../utils/fieldStyles\";\r\nimport { SignaturePad } from \"./SignaturePad\";\r\n\r\ninterface FormFieldRendererProps {\r\n field: FormField;\r\n value?: any;\r\n onChange?: (value: any) => void;\r\n isPreview?: boolean;\r\n}\r\n\r\nexport function FormFieldRenderer({\r\n field,\r\n value,\r\n onChange,\r\n}: FormFieldRendererProps) {\r\n const handleChange = (newValue: any) => {\r\n if (onChange) {\r\n onChange(newValue);\r\n }\r\n };\r\n\r\n // Hide field if conditional logic applies (simplified)\r\n if (field.advanced?.hidden || field.properties?.hidden) {\r\n return null;\r\n }\r\n\r\n // Build HeroUI-specific classes (pass isEditor: true to prevent responsive hiding in editor)\r\n const heroUIClasses = buildHeroUIClasses(field, true);\r\n\r\n // For complex components that need wrapper divs\r\n const wrapperClasses = heroUIClasses.base;\r\n\r\n // Generate responsive hide indicators for editor\r\n const getResponsiveIndicators = () => {\r\n const indicators = [];\r\n if (field.properties?.hideOnMobile) {\r\n indicators.push('Hidden on Mobile');\r\n }\r\n if (field.properties?.hideOnTablet) {\r\n indicators.push('Hidden on Tablet');\r\n }\r\n if (field.properties?.hideOnDesktop) {\r\n indicators.push('Hidden on Desktop');\r\n }\r\n return indicators;\r\n };\r\n\r\n const responsiveIndicators = getResponsiveIndicators();\r\n\r\n // Wrapper component that shows responsive indicators in editor\r\n const FieldWithIndicators = ({ children }: { children: React.ReactNode }) => {\r\n if (responsiveIndicators.length === 0) {\r\n return <>{children}</>;\r\n }\r\n\r\n return (\r\n <div className=\"relative\">\r\n {children}\r\n <div className=\"absolute top-0 right-0 flex items-center gap-1 -mt-2 -mr-2 z-10\">\r\n {responsiveIndicators.map((indicator) => (\r\n <span\r\n key={indicator}\r\n className=\"text-danger-700 text-xs px-1.5 py-0.5 rounded-full \"\r\n title={indicator}\r\n >\r\n {indicator.includes('Mobile') ? <Smartphone size={15} /> : indicator.includes('Tablet') ? <Tablet size={15} /> : <Monitor size={15} />}\r\n </span>\r\n ))}\r\n </div>\r\n </div>\r\n );\r\n };\r\n\r\n // Helper function to wrap any field component with indicators\r\n const wrapWithIndicators = (component: React.ReactNode) => (\r\n <FieldWithIndicators>{component}</FieldWithIndicators>\r\n );\r\n\r\n // Common props for the form components\r\n const commonProps = {\r\n label: field.label,\r\n placeholder: field.placeholder,\r\n isRequired: field.required,\r\n description: field.properties?.description,\r\n isDisabled: field.advanced?.disabled || field.properties?.disabled,\r\n isReadOnly: field.advanced?.readOnly || field.properties?.readonly,\r\n size: field.properties?.size as any,\r\n color: field.properties?.colorVariant as any,\r\n variant: field.properties?.variant as any,\r\n radius: field.properties?.borderRadius as any,\r\n classNames: {\r\n base: heroUIClasses.base,\r\n label: heroUIClasses.label,\r\n inputWrapper: heroUIClasses.inputWrapper,\r\n innerWrapper: heroUIClasses.innerWrapper,\r\n mainWrapper: heroUIClasses.mainWrapper,\r\n input: heroUIClasses.input,\r\n clearButton: heroUIClasses.clearButton,\r\n helperWrapper: heroUIClasses.helperWrapper,\r\n description: heroUIClasses.description,\r\n errorMessage: heroUIClasses.errorMessage,\r\n },\r\n };\r\n\r\n // Build HTML attributes for data attributes and accessibility\r\n const buildHtmlAttributes = () => {\r\n const attrs: Record<string, any> = {};\r\n \r\n // Add data attributes from custom properties\r\n const dataAttributes = field.custom?.dataAttributes || {};\r\n Object.entries(dataAttributes).forEach(([key, value]) => {\r\n attrs[`data-${key}`] = value;\r\n });\r\n \r\n // Add accessibility attributes\r\n if (field.custom?.role) {\r\n attrs['role'] = field.custom.role;\r\n }\r\n \r\n if (field.custom?.tabIndex !== undefined) {\r\n attrs['tabIndex'] = field.custom.tabIndex;\r\n }\r\n \r\n if (field.properties?.ariaLabel) {\r\n attrs['aria-label'] = field.properties.ariaLabel;\r\n }\r\n \r\n // Add number input specific attributes\r\n if (field.type === 'number') {\r\n if (field.properties?.min !== undefined) {\r\n attrs['min'] = field.properties.min;\r\n }\r\n if (field.properties?.max !== undefined) {\r\n attrs['max'] = field.p