# Form Builder Technical Guide This document provides comprehensive technical implementation details for developers working with the Form Builder system, including the new smart collapsible panel, enhanced grid system, and advanced UX features. > For user documentation and usage guidelines, see the [User Guide](USER_GUIDE.md) ## Architecture Overview ### Technology Stack - **Frontend Framework**: Nuxt 3 / Vue 3 - **State Management**: Pinia - **Form Library**: FormKit - **UI Framework**: Tailwind CSS + Custom Components - **Icons**: Heroicons - **Drag & Drop**: vuedraggable - **Grid System**: CSS Grid (12-column) ### Key Dependencies ```json { "@formkit/nuxt": "^1.0.0", "@pinia/nuxt": "^0.4.11", "vuedraggable": "^4.1.0", "@vueuse/core": "^10.1.2", "tailwindcss": "^3.3.2", "@heroicons/vue": "^2.0.18" } ``` ## Project Structure ``` pages/ ├── form-builder/ │ ├── index.vue # Main builder interface with collapsible panel │ └── manage.vue # Form management components/ ├── FormBuilderComponents.vue # Enhanced component library ├── FormBuilderCanvas.vue # Grid-based canvas with resize handles ├── FormBuilderFieldSettingsModal.vue # Full settings modal with visual grid ├── ComponentPreview.vue # Improved component rendering └── RsCollapseItem.vue # Collapsible components stores/ ├── formBuilder.js # Enhanced state management with grid logic └── toast.js # Toast notifications composables/ └── useToast.js # Toast composition types/ └── form-builder.d.ts # TypeScript definitions server/ └── api/ └── forms/ ├── index.js # Form API endpoints └── [id].js # Form by ID endpoints docs/ └── grid-system.md # Grid system documentation ``` ## Enhanced UI Architecture ### Smart Collapsible Panel System The right panel system provides two interaction modes: #### Panel States ```vue ``` #### Quick Settings Implementation ```vue ``` ### Enhanced Grid System #### CSS Grid Implementation ```css .grid-container { display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-flow: row dense; /* Enables automatic gap filling */ column-gap: 16px; row-gap: 16px; width: 100%; } .form-component { grid-column: span 12; /* Default to full width */ width: 100% !important; /* Force width within grid cell */ transition: all 0.2s ease; } ``` #### Grid Column Mapping ```javascript // Precise percentage to grid column mapping const widthToGridColumns = { 25: 3, // 3/12 = 25% 33: 4, // 4/12 = 33.33% 50: 6, // 6/12 = 50% 66: 8, // 8/12 = 66.67% 75: 9, // 9/12 = 75% 100: 12 // 12/12 = 100% } const setComponentWidth = (percentage, gridColumns) => { configModel.value.width = `${percentage}%` configModel.value.gridColumn = `span ${gridColumns}` } ``` #### Smart Grid Placement Algorithm ```javascript // Optimal grid placement for new components findOptimalGridPlacement() { if (this.formComponents.length === 0) { return { gridColumn: 'span 12', rowIndex: 0, width: '100%' } } // Group components by rows and find available space const rows = [] let currentRowSpace = 12 this.formComponents.forEach(component => { const spanMatch = component.props.gridColumn?.match(/span\s+(\d+)/) || [] const columnSpan = parseInt(spanMatch[1]) || 12 if (columnSpan <= currentRowSpace) { // Add to current row currentRowSpace -= columnSpan } else { // Start new row currentRowSpace = 12 - columnSpan } }) // Return optimal placement return { gridColumn: `span ${Math.min(currentRowSpace, 12)}`, width: `${Math.round((currentRowSpace / 12) * 100)}%` } } ``` ## Component Architecture ### Enhanced FormBuilderFieldSettingsModal The modal now includes visual grid selection: ```vue {{ option.name }} Recommended {{ option.description }} {{ option.useCase }} ``` ### Enhanced Canvas with Resize Handles ```vue ``` ## State Management Enhancements ### Enhanced Pinia Store ```typescript interface FormState { formComponents: FormComponent[] selectedComponentId: string | null formName: string formDescription: string formId: string | null formUUID: string | null isDraggingOver: boolean savedForms: SavedForm[] showFieldSettingsPanel: boolean // New panel state } export const useFormBuilderStore = defineStore('formBuilder', { state: (): FormState => ({ formComponents: [], selectedComponentId: null, formName: 'New Form', formDescription: '', formId: null, formUUID: null, isDraggingOver: false, savedForms: [], showFieldSettingsPanel: true }), getters: { selectedComponent: (state) => { if (!state.selectedComponentId) return null return state.formComponents.find(c => c.id === state.selectedComponentId) }, formConfig: (state) => ({ id: state.formId, uuid: state.formUUID, name: state.formName, description: state.formDescription, components: state.formComponents }) }, actions: { // Enhanced component addition with smart grid placement addComponent(component: FormComponent) { const { gridColumn, rowIndex, width } = this.findOptimalGridPlacement() const newComponent = { ...component, id: component.id || uuidv4(), props: { ...component.defaultProps, width: width, gridColumn: gridColumn, name: component.defaultProps?.name || `${component.type}_${this.formComponents.length + 1}`, label: component.defaultProps?.label || `${component.name} ${this.formComponents.length + 1}` } } this.formComponents.push(newComponent) this.selectComponent(newComponent.id) }, // Grid layout optimization optimizeGridLayout() { const rows = this.groupComponentsByRows() rows.forEach(row => { if (row.remainingSpace > 0) { this.distributeRemainingSpace(row) } }) }, // Helper methods for grid management groupComponentsByRows() { const rows = [] let currentRowSpace = 12 this.formComponents.forEach(component => { const spanMatch = component.props.gridColumn?.match(/span\s+(\d+)/) || [] const columnSpan = parseInt(spanMatch[1]) || 12 if (columnSpan <= currentRowSpace) { currentRowSpace -= columnSpan if (!rows[rows.length - 1]) { rows.push({ components: [], remainingSpace: 0 }) } rows[rows.length - 1].components.push(component) rows[rows.length - 1].remainingSpace = currentRowSpace } else { rows.push({ components: [component], remainingSpace: 12 - columnSpan }) currentRowSpace = 12 - columnSpan } }) return rows } } }) ``` ## Component Type System ### Enhanced Component Definitions ```javascript const availableComponents = [ { type: 'text', name: 'Text Field', category: 'Basic Inputs', icon: 'heroicons:document-text', recommendedWidth: 'small', // New recommendation system defaultProps: { type: 'text', label: 'Text Field', name: '', placeholder: '', help: '', validation: '', width: '33%', // Smart default gridColumn: 'span 4' } }, { type: 'textarea', name: 'Text Area', category: 'Basic Inputs', icon: 'heroicons:document-text', recommendedWidth: 'full', defaultProps: { type: 'textarea', label: 'Text Area', name: '', placeholder: '', help: '', rows: 4, validation: '', width: '100%', gridColumn: 'span 12' } }, // ... additional components with smart defaults ] ``` ### Component Icon and Name Helpers ```javascript const getComponentIcon = (type) => { const iconMap = { text: 'heroicons:document-text', textarea: 'heroicons:document-text', number: 'heroicons:hashtag', email: 'heroicons:envelope', password: 'heroicons:key', url: 'heroicons:link', tel: 'heroicons:device-phone-mobile', mask: 'heroicons:pencil-square', hidden: 'heroicons:eye-slash', select: 'heroicons:chevron-down', checkbox: 'heroicons:check-badge', radio: 'heroicons:radio', switch: 'material-symbols:toggle-on', date: 'heroicons:calendar-days', time: 'heroicons:clock', 'datetime-local': 'heroicons:calendar', range: 'heroicons:adjustments-horizontal', color: 'heroicons:swatch', file: 'heroicons:document-arrow-up', otp: 'heroicons:key', dropzone: 'heroicons:cloud-arrow-up', button: 'heroicons:cursor-arrow-rays', heading: 'heroicons:h1', paragraph: 'heroicons:document-text', divider: 'heroicons:minus', 'info-display': 'heroicons:information-circle' } return iconMap[type] || 'heroicons:square-3-stack-3d' } // Short names for collapsed panel const getComponentTypeShort = (type) => { const shortNames = { text: 'TXT', textarea: 'TXT', number: 'NUM', email: 'EML', password: 'PWD', url: 'URL', tel: 'TEL', mask: 'MSK', hidden: 'HID', select: 'SEL', checkbox: 'CHK', radio: 'RAD', switch: 'SWT', date: 'DTE', time: 'TME', 'datetime-local': 'DTM', range: 'RNG', color: 'CLR', file: 'FIL', otp: 'OTP', dropzone: 'DRP', button: 'BTN', heading: 'H' + (component?.props?.level || '2'), paragraph: 'TXT', divider: 'DIV', 'info-display': 'INF' } return shortNames[type] || 'FLD' } ``` ## CSS Architecture ### Panel System Styles ```css /* Field Settings Panel */ .field-settings-panel { @apply relative bg-white border-l border-gray-200 flex flex-col overflow-hidden transition-all duration-300 ease-in-out; } .panel-expanded { @apply w-80; /* 320px expanded */ } .panel-collapsed { @apply w-12; /* 48px collapsed */ } /* Panel content areas */ .panel-content { @apply flex-1 flex flex-col overflow-hidden; } .panel-header { @apply px-4 py-3 border-b border-gray-200 bg-gray-50; } .panel-body { @apply flex-1 overflow-y-auto; } /* Collapsed state */ .collapsed-info { @apply h-full flex flex-col px-2 py-3; } .collapsed-header { @apply mb-4 flex justify-center; } .collapsed-content { @apply text-center w-full flex-1 flex flex-col justify-center items-center; } ``` ### Grid System Styles ```css /* Grid container */ .grid-container { display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-flow: row dense; column-gap: 16px; row-gap: 16px; width: 100%; padding: 0; box-sizing: border-box; } /* Grid preview in modal */ .grid-container-mini { @apply grid grid-cols-12 gap-1 w-32; } .grid-cell { @apply h-2 rounded-sm transition-colors duration-200; } .grid-cell.active { @apply bg-blue-500; } .grid-cell.inactive { @apply bg-gray-200; } /* Width selection */ .width-option { @apply border border-gray-200 rounded-lg p-4 transition-all duration-200 hover:border-blue-300 hover:bg-blue-50 cursor-pointer flex items-center space-x-4; } .width-option.selected { @apply border-blue-500 bg-blue-50 ring-2 ring-blue-200; } .width-option.recommended { @apply ring-2 ring-green-200 border-green-300; } .width-option.selected.recommended { @apply border-green-500 bg-green-50 ring-2 ring-green-200; } ``` ### Quick Settings Styles ```css /* Compact width buttons */ .width-selector-compact { @apply flex space-x-1; } .width-btn { @apply flex-1 px-2 py-1.5 text-xs font-medium text-center rounded-md border border-gray-200 bg-white hover:border-blue-300 hover:bg-blue-50 transition-colors duration-200; } .width-btn.active { @apply border-blue-500 bg-blue-50 text-blue-700 ring-1 ring-blue-200; } /* Toggle switches */ .setting-toggle { @apply flex items-center cursor-pointer; } .toggle-slider { @apply relative inline-block w-8 h-4 mr-2 bg-gray-200 rounded-full transition-colors duration-200 ease-in-out; } .setting-toggle input:checked + .toggle-slider { @apply bg-blue-600; } .toggle-slider::before { @apply absolute top-0.5 left-0.5 w-3 h-3 bg-white rounded-full transition-transform duration-200 ease-in-out; content: ''; } .setting-toggle input:checked + .toggle-slider::before { @apply transform translate-x-4; } ``` ## API Integration ### Form Data Structure ```typescript interface FormComponent { id: string type: string name: string category: string icon: string defaultProps: Record props: { label?: string name?: string placeholder?: string help?: string validation?: string width: string // e.g., "50%" gridColumn: string // e.g., "span 6" [key: string]: any } } interface FormConfig { id: string | null uuid: string | null name: string description: string components: FormComponent[] settings: { submitUrl?: string successMessage?: string errorMessage?: string redirectUrl?: string } } ``` ### Save/Load Operations ```javascript // Enhanced save with grid validation const saveForm = async (formConfig) => { // Validate grid layout const validatedComponents = formConfig.components.map(component => ({ ...component, props: { ...component.props, // Ensure grid properties are valid width: component.props.width || '100%', gridColumn: component.props.gridColumn || 'span 12' } })) const validatedForm = { ...formConfig, components: validatedComponents } try { const response = await $fetch('/api/forms', { method: 'POST', body: validatedForm }) return response } catch (error) { throw new Error(`Save failed: ${error.message}`) } } // Load with grid property migration const loadForm = async (formId) => { try { const form = await $fetch(`/api/forms/${formId}`) // Migrate old forms without grid properties const migratedComponents = form.components.map(component => ({ ...component, props: { ...component.props, width: component.props.width || '100%', gridColumn: component.props.gridColumn || 'span 12' } })) return { ...form, components: migratedComponents } } catch (error) { throw new Error(`Load failed: ${error.message}`) } } ``` ## Performance Optimizations ### Component Rendering ```vue ``` ### Grid Layout Optimization ```javascript // Efficient grid layout calculation const optimizeGridLayout = () => { // Use requestAnimationFrame for smooth updates requestAnimationFrame(() => { const rows = groupComponentsByRows() rows.forEach(row => { if (row.remainingSpace > 3) { // Only optimize significant gaps distributeSpace(row) } }) }) } // Virtual scrolling for large forms const useVirtualScrolling = (components, containerHeight) => { const itemHeight = 120 // Average component height const visibleCount = Math.ceil(containerHeight / itemHeight) + 2 return computed(() => { const startIndex = Math.max(0, scrollTop.value / itemHeight - 1) const endIndex = Math.min(components.length, startIndex + visibleCount) return components.slice(startIndex, endIndex) }) } ``` ## Testing Strategy ### Unit Tests ```javascript // Grid system tests describe('Grid System', () => { test('should calculate optimal placement', () => { const store = useFormBuilderStore() store.formComponents = [ { id: '1', props: { gridColumn: 'span 6' } }, { id: '2', props: { gridColumn: 'span 4' } } ] const placement = store.findOptimalGridPlacement() expect(placement.gridColumn).toBe('span 2') expect(placement.width).toBe('17%') // Approximately 2/12 }) test('should optimize layout correctly', () => { const store = useFormBuilderStore() store.formComponents = [ { id: '1', props: { gridColumn: 'span 6' } }, { id: '2', props: { gridColumn: 'span 3' } } ] store.optimizeGridLayout() expect(store.formComponents[1].props.gridColumn).toBe('span 6') }) }) // Panel system tests describe('Collapsible Panel', () => { test('should auto-open when component selected', async () => { const wrapper = mount(FormBuilder) const store = useFormBuilderStore() store.selectComponent('test-id') await nextTick() expect(wrapper.find('.panel-expanded').exists()).toBe(true) }) }) ``` ### Integration Tests ```javascript // Full workflow tests describe('Form Builder Workflow', () => { test('should complete full component lifecycle', async () => { const wrapper = mount(FormBuilder) // Add component await wrapper.find('[data-component="text"]').trigger('click') // Should auto-select and open panel expect(wrapper.find('.panel-expanded').exists()).toBe(true) // Update quick settings await wrapper.find('.width-btn[data-width="50"]').trigger('click') // Verify component updated const component = wrapper.vm.formStore.selectedComponent expect(component.props.width).toBe('50%') expect(component.props.gridColumn).toBe('span 6') }) }) ``` ## Browser Compatibility ### Supported Features - **CSS Grid**: Supported in all modern browsers (IE11+ with autoprefixer) - **Vue 3**: Modern browser requirements - **Drag & Drop**: HTML5 drag/drop with fallbacks ### Polyfills Required ```javascript // For older browsers import 'core-js/stable' import 'regenerator-runtime/runtime' // CSS Grid polyfill for IE11 import 'css-grid-polyfill' ``` ## Security Considerations ### Input Validation ```javascript // Sanitize component props const sanitizeProps = (props) => { const sanitized = {} Object.keys(props).forEach(key => { if (allowedProps.includes(key)) { sanitized[key] = sanitizeHtml(props[key]) } }) return sanitized } // Validate grid values const validateGridColumn = (gridColumn) => { const spanMatch = gridColumn?.match(/^span\s+([1-9]|1[0-2])$/) return spanMatch ? gridColumn : 'span 12' } ``` ### XSS Prevention ```javascript // Escape user input in labels and help text const escapeHtml = (text) => { const div = document.createElement('div') div.textContent = text return div.innerHTML } ``` ## Future Enhancements ### Planned Features 1. **Advanced Grid Controls**: Custom grid templates, responsive breakpoints 2. **Component Templates**: Reusable component groups 3. **Theme System**: Multiple visual themes for forms 4. **Advanced Validation**: Cross-field validation, async validation 5. **Collaboration**: Real-time multi-user editing 6. **Version Control**: Form versioning and rollback 7. **Analytics**: Form usage and completion analytics ### Technical Debt Items 1. **TypeScript Migration**: Complete TypeScript coverage 2. **Test Coverage**: Increase to 90%+ coverage 3. **Performance**: Virtual scrolling for large forms 4. **Accessibility**: Full WCAG 2.1 AA compliance --- *This documentation reflects the current implementation including all enhanced features: smart collapsible panel system, visual grid selection, intelligent width recommendations, and comprehensive UX improvements.* Last updated: December 2024