2025-11-07 12:26:47 -06:00

37 KiB

# Form Component Documentation

## Overview

A highly flexible and configurable dynamic form component built with **PrimeVue**. This component generates forms based on field configuration objects and supports various input types including **AutoComplete with custom values**, validation, responsive layouts, and both controlled and uncontrolled form state management.

## ✨ New Features (PrimeVue Migration)

- **AutoComplete component** - Users can select from suggestions OR enter completely custom values
- **Enhanced Date/Time Pickers** - Comprehensive date handling with multiple formats, time selection, constraints, and smart defaults
- **Better accessibility** with ARIA support
- **More flexible styling** with CSS custom properties
- **Enhanced mobile responsiveness** with CSS Grid

## Basic Usage

```vue
<template>
  <Form
    :fields="formFields"
    :form-data="formData"
    @submit="handleSubmit"
    @change="handleFieldChange"
  />
</template>

<script setup>
import { ref } from "vue";
import Form from "./components/common/Form.vue";

const formData = ref({});

const formFields = [
  {
    name: "firstName",
    label: "First Name",
    type: "text",
    required: true,
    cols: 6,
  },
  {
    name: "email",
    label: "Email",
    type: "text",
    format: "email",
    required: true,
    cols: 6,
  },
  // NEW: AutoComplete with custom values!
  {
    name: "country",
    label: "Country",
    type: "autocomplete",
    placeholder: "Type or select a country",
    options: [
      { label: "United States", value: "US" },
      { label: "Canada", value: "CA" },
      { label: "United Kingdom", value: "UK" },
    ],
    optionLabel: "label",
    optionValue: "value",
    forceSelection: false, // Allows custom entries!
    cols: 6,
    helpText: "You can select from the list or enter a custom country",
  },
  {
    name: "bio",
    label: "Biography",
    type: "textarea",
    rows: 4,
    cols: 12,
  },
];

const handleSubmit = (data) => {
  console.log("Form submitted:", data);
};

const handleFieldChange = (event) => {
  console.log("Field changed:", event.fieldName, event.value);
};
</script>
```

## Props

### `fields` (Array) - Required

- **Description:** Array of field configuration objects that define the form structure
- **Type:** `Array<Object>`
- **Required:** `true`

### `formData` (Object)

- **Description:** External form data object for controlled form state
- **Type:** `Object`
- **Default:** `null`
- **Note:** When provided, the form operates in controlled mode. When null, uses internal state.

### `onChange` (Function)

- **Description:** Global change handler function called when any field changes
- **Type:** `Function`
- **Signature:** `(fieldName: string, value: any, formData: Object) => void`

### `onSubmit` (Function)

- **Description:** Submit handler function called when form is submitted
- **Type:** `Function`
- **Signature:** `(formData: Object) => Promise<void> | void`

### `showSubmitButton` (Boolean)

- **Description:** Controls visibility of the submit button
- **Type:** `Boolean`
- **Default:** `true`

### `showCancelButton` (Boolean)

- **Description:** Controls visibility of the cancel button
- **Type:** `Boolean`
- **Default:** `false`

### `submitButtonText` (String)

- **Description:** Text displayed on the submit button
- **Type:** `String`
- **Default:** `'Submit'`

### `cancelButtonText` (String)

- **Description:** Text displayed on the cancel button
- **Type:** `String`
- **Default:** `'Cancel'`

### `validateOnChange` (Boolean)

- **Description:** Enables real-time validation as fields change
- **Type:** `Boolean`
- **Default:** `true`

## Field Configuration

Each field object in the `fields` array supports the following properties:

### Basic Properties

- **`name`** (String, required) - Unique identifier for the field
- **`label`** (String, required) - Display label for the field
- **`type`** (String, required) - Field input type
- **`required`** (Boolean, default: `false`) - Makes the field mandatory
- **`disabled`** (Boolean, default: `false`) - Disables the field
- **`readonly`** (Boolean, default: `false`) - Makes the field read-only
- **`placeholder`** (String) - Placeholder text for input fields
- **`helpText`** (String) - Help text displayed below the field
- **`defaultValue`** (Any) - Initial value for the field

### Layout Properties

- **`cols`** (Number, default: `12`) - Column width on extra small screens
- **`sm`** (Number, default: `12`) - Column width on small screens
- **`md`** (Number, default: `6`) - Column width on medium screens
- **`lg`** (Number, default: `6`) - Column width on large screens

### Validation Properties

- **`validate`** (Function) - Custom validation function
  - **Signature:** `(value: any) => string | null`
  - **Returns:** Error message string or null if valid

### Field-Specific Properties

- **`onChangeOverride`** (Function) - Field-specific change handler that overrides global onChange
  - **Signature:** `(value: any, fieldName: string, formData: Object) => void`

## Field Types

### Text Input (`type: 'text'`)

Standard text input field with optional format validation.

```javascript
{
  name: 'username',
  label: 'Username',
  type: 'text',
  required: true,
  placeholder: 'Enter your username'
}
```

**Additional Properties:**

- **`format`** (String) - Input format validation (`'email'` for email validation)

### Number Input (`type: 'number'`)

Numeric input field with optional min/max constraints.

```javascript
{
  name: 'age',
  label: 'Age',
  type: 'number',
  min: 0,
  max: 120,
  step: 1
}
```

**Additional Properties:**

- **`min`** (Number) - Minimum allowed value
- **`max`** (Number) - Maximum allowed value
- **`step`** (Number) - Step increment for the input

### Textarea (`type: 'textarea'`)

Multi-line text input for longer content.

```javascript
{
  name: 'description',
  label: 'Description',
  type: 'textarea',
  rows: 4,
  placeholder: 'Enter description...'
}
```

**Additional Properties:**

- **`rows`** (Number, default: `3`) - Number of visible text lines

### Select Dropdown (`type: 'select'`)

Dropdown selection field with predefined options and built-in filtering.

```javascript
{
  name: 'country',
  label: 'Country',
  type: 'select',
  required: true,
  options: [
    { label: 'United States', value: 'us' },
    { label: 'Canada', value: 'ca' },
    { label: 'Mexico', value: 'mx' }
  ],
  filter: true,  // Enable search filtering
  showClear: true  // Allow clearing selection
}
```

**Additional Properties:**

- **`options`** (Array, required) - Array of option objects with `label` and `value` properties
- **`optionLabel`** (String, default: `'label'`) - Property name for display text
- **`optionValue`** (String, default: `'value'`) - Property name for value
- **`filter`** (Boolean, default: `true`) - Enable filtering/search
- **`showClear`** (Boolean, default: `true`) - Show clear button

### ⭐ AutoComplete (`type: 'autocomplete'`) - NEW!

**The game-changer!** AutoComplete field that allows users to select from suggestions OR enter completely custom values not in the predefined list.

```javascript
// Basic AutoComplete - allows custom entries
{
  name: 'skills',
  label: 'Skills',
  type: 'autocomplete',
  placeholder: 'Add your skills',
  options: [
    { label: 'JavaScript', value: 'js' },
    { label: 'Python', value: 'python' },
    { label: 'Vue.js', value: 'vue' }
  ],
  optionLabel: 'label',
  optionValue: 'value',
  forceSelection: false,  // KEY: Allows custom values!
  multiple: true,         // Multiple selection
  helpText: 'Select existing skills or add your own custom skills'
}

// Strict selection (only predefined options)
{
  name: 'department',
  label: 'Department',
  type: 'autocomplete',
  options: [
    { label: 'Engineering', value: 'eng' },
    { label: 'Marketing', value: 'mkt' },
    { label: 'Sales', value: 'sales' }
  ],
  forceSelection: true,   // Only allows predefined options
  dropdown: true,         // Show dropdown button
  required: true
}

// Dynamic/Remote data loading
{
  name: 'users',
  label: 'Select Users',
  type: 'autocomplete',
  options: [],  // Initially empty
  onSearch: async (query, callback) => {
    if (query.length > 2) {
      const response = await fetch(`/api/users?search=${query}`);
      const users = await response.json();
      callback(users);  // Update options dynamically
    }
  }
}
```

**Additional Properties:**

- **`options`** (Array) - Array of suggestion objects
- **`optionLabel`** (String, default: `'label'`) - Property for display text
- **`optionValue`** (String, default: `'value'`) - Property for value
- **`forceSelection`** (Boolean, default: `false`) - If true, only allows predefined options. If false, allows custom values!
- **`multiple`** (Boolean, default: `false`) - Allow multiple selections
- **`dropdown`** (Boolean, default: `true`) - Show dropdown button
- **`onSearch`** (Function) - Custom search function for dynamic data loading
  - **Signature:** `(query: string, callback: Function) => void`

### Checkbox (`type: 'checkbox'`)

Boolean checkbox input.

```javascript
{
  name: 'subscribe',
  label: 'Subscribe to newsletter',
  type: 'checkbox',
  defaultValue: false
}
```

### Radio Group (`type: 'radio'`)

Radio button group for single selection from multiple options.

```javascript
{
  name: 'gender',
  label: 'Gender',
  type: 'radio',
  required: true,
  options: [
    { label: 'Male', value: 'male' },
    { label: 'Female', value: 'female' },
    { label: 'Other', value: 'other' }
  ]
}
```

**Additional Properties:**

- **`options`** (Array, required) - Array of option objects with `label` and `value` properties

### Date Input (`type: 'date'`)

Enhanced date picker input field with comprehensive formatting and configuration options.

```javascript
// Basic date input
{
  name: 'birthDate',
  label: 'Birth Date',
  type: 'date',
  required: true,
}

// Date with custom format
{
  name: 'eventDate',
  label: 'Event Date',
  type: 'date',
  format: 'YYYY-MM-DD',  // or 'mm/dd/yyyy', 'dd/mm/yyyy', etc.
  required: true,
  placeholder: 'Select event date'
}

// Date with time picker
{
  name: 'appointmentDateTime',
  label: 'Appointment Date & Time',
  type: 'date',
  showTime: true,
  hourFormat: '12',  // '12' or '24'
  required: true,
  defaultToNow: true,  // Set to current date/time by default
}

// Time-only picker
{
  name: 'preferredTime',
  label: 'Preferred Time',
  type: 'date',
  timeOnly: true,
  hourFormat: '12',
  stepMinute: 15,  // 15-minute intervals
  defaultValue: 'now'
}

// Advanced date configuration
{
  name: 'projectDeadline',
  label: 'Project Deadline',
  type: 'date',
  format: 'dd/mm/yyyy',
  minDate: 'today',  // Can't select past dates
  maxDate: '2025-12-31',  // Maximum date
  defaultToToday: true,
  showButtonBar: true,
  yearNavigator: true,
  monthNavigator: true,
  yearRange: '2024:2030',
  helpText: 'Select a deadline for the project completion'
}

// Inline date picker (always visible)
{
  name: 'calendarDate',
  label: 'Calendar',
  type: 'date',
  inline: true,
  view: 'date',  // 'date', 'month', 'year'
  showWeek: true,
  defaultValue: 'today'
}
```

**Additional Properties:**

- **`format`** (String) - Date format: `'YYYY-MM-DD'`, `'mm/dd/yyyy'`, `'dd/mm/yyyy'`, `'dd-mm-yyyy'`, `'mm-dd-yyyy'`
- **`dateFormat`** (String) - PrimeVue-specific format string (overrides `format`)
- **`showTime`** (Boolean, default: `false`) - Include time picker
- **`timeOnly`** (Boolean, default: `false`) - Show only time picker (no date)
- **`hourFormat`** (String, default: `'24'`) - Hour format: `'12'` or `'24'`
- **`stepHour`** (Number, default: `1`) - Hour step increment
- **`stepMinute`** (Number, default: `1`) - Minute step increment
- **`showSeconds`** (Boolean, default: `false`) - Show seconds in time picker
- **`stepSecond`** (Number, default: `1`) - Second step increment
- **`minDate`** (String|Date) - Minimum selectable date
- **`maxDate`** (String|Date) - Maximum selectable date
- **`defaultToToday`** (Boolean, default: `false`) - Set default to today's date
- **`defaultToNow`** (Boolean, default: `false`) - Set default to current date/time
- **`showButtonBar`** (Boolean, default: `true`) - Show today/clear buttons
- **`todayButtonLabel`** (String, default: `'Today'`) - Today button text
- **`clearButtonLabel`** (String, default: `'Clear'`) - Clear button text
- **`showWeek`** (Boolean, default: `false`) - Show week numbers
- **`manualInput`** (Boolean, default: `true`) - Allow manual date entry
- **`yearNavigator`** (Boolean, default: `false`) - Show year dropdown
- **`monthNavigator`** (Boolean, default: `false`) - Show month dropdown
- **`yearRange`** (String, default: `'1900:2100'`) - Available year range
- **`inline`** (Boolean, default: `false`) - Display picker inline (always visible)
- **`view`** (String, default: `'date'`) - Default view: `'date'`, `'month'`, `'year'`
- **`touchUI`** (Boolean, default: `false`) - Optimize for touch devices
- **`onDateChange`** (Function) - Custom date change handler
  - **Signature:** `(dateValue: Date) => any`

**Default Value Options:**

```javascript
// String values
defaultValue: "today"; // Set to today's date
defaultValue: "now"; // Set to current date/time
defaultValue: "2024-12-25"; // Specific date string

// Boolean flags
defaultToToday: true; // Set to today (date only)
defaultToNow: true; // Set to current date/time

// Date object
defaultValue: new Date(); // Specific Date object
```

### DateTime Input (`type: 'datetime'`) - LEGACY

**⚠️ DEPRECATED:** Use `type: 'date'` with `showTime: true` instead.

Legacy date and time picker input field. This is maintained for backward compatibility.

```javascript
// LEGACY - Use date with showTime instead
{
  name: 'appointmentTime',
  label: 'Appointment Time',
  type: 'datetime',
  required: true
}

// RECOMMENDED - Use this instead
{
  name: 'appointmentTime',
  label: 'Appointment Time',
  type: 'date',
  showTime: true,
  required: true
}
```

**Additional Properties:**

- **`min`** (String) - Minimum allowed datetime
- **`max`** (String) - Maximum allowed datetime
- **`hourFormat`** (String, default: `'24'`) - Hour format: `'12'` or `'24'`

### File Input (`type: 'file'`)

File upload input field.

```javascript
{
  name: 'resume',
  label: 'Resume',
  type: 'file',
  accept: '.pdf,.doc,.docx',
  multiple: false
}
```

**Additional Properties:**

- **`accept`** (String) - File types to accept (MIME types or file extensions)
- **`multiple`** (Boolean, default: `false`) - Allow multiple file selection

## Events

### `update:formData`

- **Description:** Emitted when form data changes (controlled mode only)
- **Payload:** Updated form data object
- **Usage:** `@update:formData="handleFormDataUpdate"`

### `submit`

- **Description:** Emitted when form is successfully submitted
- **Payload:** Form data object
- **Usage:** `@submit="handleSubmit"`

### `cancel`

- **Description:** Emitted when cancel button is clicked
- **Usage:** `@cancel="handleCancel"`

### `change`

- **Description:** Emitted when any field value changes
- **Payload:** Object with `fieldName`, `value`, and `formData` properties
- **Usage:** `@change="handleFieldChange"`

## Exposed Methods

The component exposes several methods that can be accessed via template refs:

```vue
<template>
  <Form ref="formRef" :fields="fields" />
</template>

<script setup>
import { ref } from "vue";

const formRef = ref(null);

// Access exposed methods
const validateForm = () => formRef.value.validateForm();
const resetForm = () => formRef.value.resetForm();
</script>
```

### `validateForm()`

- **Description:** Validates the entire form and returns validation status
- **Returns:** `Boolean` - `true` if valid, `false` if invalid
- **Side Effect:** Updates form error state

### `getCurrentFormData()`

- **Description:** Gets the current form data object
- **Returns:** `Object` - Current form data

### `resetForm()`

- **Description:** Resets form to initial state and clears all errors
- **Returns:** `void`

### `setFieldError(fieldName, error)`

- **Description:** Sets an error message for a specific field
- **Parameters:**
  - `fieldName` (String) - The field name
  - `error` (String) - Error message to display

### `clearFieldError(fieldName)`

- **Description:** Clears the error for a specific field
- **Parameters:**
  - `fieldName` (String) - The field name to clear

## Usage Examples

### Basic Contact Form

```vue
<script setup>
import { ref } from "vue";

const formData = ref({});

const contactFields = [
  {
    name: "firstName",
    label: "First Name",
    type: "text",
    required: true,
    cols: 12,
    md: 6,
  },
  {
    name: "lastName",
    label: "Last Name",
    type: "text",
    required: true,
    cols: 12,
    md: 6,
  },
  {
    name: "email",
    label: "Email",
    type: "text",
    format: "email",
    required: true,
    cols: 12,
    md: 6,
  },
  {
    name: "preferredContactDate",
    label: "Preferred Contact Date",
    type: "date",
    format: "mm/dd/yyyy",
    minDate: "today",
    defaultToToday: true,
    cols: 12,
    md: 6,
    helpText: "When would you like us to contact you?",
  },
  {
    name: "message",
    label: "Message",
    type: "textarea",
    rows: 5,
    required: true,
    cols: 12,
  },
];

const handleSubmit = async (data) => {
  try {
    await sendContactForm(data);
    alert("Form submitted successfully!");
  } catch (error) {
    console.error("Submission failed:", error);
  }
};
</script>

<template>
  <Form
    :fields="contactFields"
    v-model:form-data="formData"
    @submit="handleSubmit"
    submit-button-text="Send Message"
  />
</template>
```

### AutoComplete Examples

```vue
<script setup>
import { ref } from "vue";

const formData = ref({});

// Sample data
const countries = [
  { label: "United States", value: "US" },
  { label: "Canada", value: "CA" },
  { label: "United Kingdom", value: "UK" },
  { label: "Germany", value: "DE" },
  { label: "France", value: "FR" },
];

const skills = [
  { label: "JavaScript", value: "javascript" },
  { label: "Python", value: "python" },
  { label: "Vue.js", value: "vue" },
  { label: "React", value: "react" },
  { label: "Node.js", value: "node" },
];

const autoCompleteFields = [
  // Basic AutoComplete - allows custom countries
  {
    name: "country",
    label: "Country",
    type: "autocomplete",
    placeholder: "Type or select a country",
    options: countries,
    optionLabel: "label",
    optionValue: "value",
    forceSelection: false, // Users can enter "Custom Country"
    required: true,
    cols: 6,
    helpText: "Select from list or enter any country name",
  },

  // Multiple selection with custom values
  {
    name: "skills",
    label: "Technical Skills",
    type: "autocomplete",
    placeholder: "Add your skills",
    options: skills,
    optionLabel: "label",
    optionValue: "value",
    multiple: true,
    forceSelection: false, // Users can add custom skills like "Machine Learning"
    cols: 6,
    helpText: "Select existing skills or add custom ones",
  },

  // Strict selection - only predefined options
  {
    name: "department",
    label: "Department",
    type: "autocomplete",
    options: [
      { label: "Engineering", value: "eng" },
      { label: "Marketing", value: "mkt" },
      { label: "Sales", value: "sales" },
      { label: "HR", value: "hr" },
    ],
    forceSelection: true, // Only allows company departments
    dropdown: true,
    required: true,
    cols: 6,
  },

  // Dynamic search with API calls
  {
    name: "users",
    label: "Assign to User",
    type: "autocomplete",
    placeholder: "Search users...",
    options: [],
    optionLabel: "name",
    optionValue: "id",
    onSearch: async (query, callback) => {
      if (query.length > 2) {
        try {
          const response = await fetch(`/api/users?search=${query}`);
          const users = await response.json();
          callback(users);
        } catch (error) {
          console.error("Search failed:", error);
          callback([]);
        }
      } else {
        callback([]);
      }
    },
    cols: 6,
    helpText: "Type at least 3 characters to search",
  },
];
</script>

<template>
  <Form
    :fields="autoCompleteFields"
    v-model:form-data="formData"
    @submit="handleSubmit"
    submit-button-text="Save"
  />

  <!-- Show current values -->
  <div v-if="Object.keys(formData).length" class="mt-4">
    <h3>Current Values:</h3>
    <pre>{{ JSON.stringify(formData, null, 2) }}</pre>
  </div>
</template>
```

### Enhanced Date Picker Examples

```vue
<script setup>
import { ref } from "vue";

const formData = ref({});

const dateFields = [
  // Basic date picker
  {
    name: "birthDate",
    label: "Birth Date",
    type: "date",
    format: "mm/dd/yyyy",
    required: true,
    cols: 6,
  },

  // Date with time - appointment scheduling
  {
    name: "appointmentDateTime",
    label: "Appointment Date & Time",
    type: "date",
    showTime: true,
    hourFormat: "12",
    stepMinute: 15,
    defaultToNow: true,
    minDate: "today",
    cols: 6,
    helpText: "Select appointment date and time",
  },

  // Project deadline with constraints
  {
    name: "projectDeadline",
    label: "Project Deadline",
    type: "date",
    format: "YYYY-MM-DD",
    minDate: "today",
    maxDate: "2025-12-31",
    defaultToToday: true,
    yearNavigator: true,
    monthNavigator: true,
    required: true,
    cols: 6,
    helpText: "Deadline cannot be in the past",
  },

  // Time-only picker for preferences
  {
    name: "preferredTime",
    label: "Preferred Contact Time",
    type: "date",
    timeOnly: true,
    hourFormat: "12",
    stepMinute: 30,
    defaultValue: "09:00",
    cols: 6,
    helpText: "Best time to contact you",
  },

  // Event date with week numbers
  {
    name: "eventDate",
    label: "Event Date",
    type: "date",
    format: "dd/mm/yyyy",
    showWeek: true,
    yearNavigator: true,
    monthNavigator: true,
    yearRange: "2024:2026",
    cols: 6,
  },

  // Inline calendar for scheduling
  {
    name: "availableDate",
    label: "Available Dates",
    type: "date",
    inline: true,
    multiple: true, // Note: This would require custom implementation
    showButtonBar: true,
    cols: 12,
    helpText: "Click dates when you are available",
  },
];

const handleSubmit = (data) => {
  console.log("Date form submitted:", data);

  // Example of working with date values
  if (data.appointmentDateTime) {
    console.log(
      "Appointment scheduled for:",
      data.appointmentDateTime.toLocaleString(),
    );
  }

  if (data.projectDeadline) {
    const deadline = new Date(data.projectDeadline);
    const daysUntilDeadline = Math.ceil(
      (deadline - new Date()) / (1000 * 60 * 60 * 24),
    );
    console.log(`Days until deadline: ${daysUntilDeadline}`);
  }
};
</script>

<template>
  <Form
    :fields="dateFields"
    v-model:form-data="formData"
    @submit="handleSubmit"
    submit-button-text="Schedule"
  />

  <!-- Display current values -->
  <div v-if="Object.keys(formData).length" class="mt-4">
    <h3>Selected Dates:</h3>
    <div v-for="(value, key) in formData" :key="key" class="mb-2">
      <strong>{{ key }}:</strong>
      <span v-if="value instanceof Date">
        {{ value.toLocaleString() }}
      </span>
      <span v-else>{{ value }}</span>
    </div>
  </div>
</template>
```

### Real-World Date Picker Scenarios

```vue
<script setup>
import { ref } from "vue";

const bookingData = ref({});

// Hotel booking form with date constraints
const hotelBookingFields = [
  {
    name: "checkIn",
    label: "Check-in Date",
    type: "date",
    format: "mm/dd/yyyy",
    minDate: "today",
    defaultToToday: true,
    required: true,
    cols: 6,
    onDateChange: (date) => {
      // Auto-update checkout date to be at least 1 day later
      if (bookingData.value.checkOut && bookingData.value.checkOut <= date) {
        const nextDay = new Date(date);
        nextDay.setDate(nextDay.getDate() + 1);
        bookingData.value.checkOut = nextDay;
      }
      return date;
    },
  },
  {
    name: "checkOut",
    label: "Check-out Date",
    type: "date",
    format: "mm/dd/yyyy",
    minDate: "today",
    required: true,
    cols: 6,
    validate: (value) => {
      if (
        value &&
        bookingData.value.checkIn &&
        value <= bookingData.value.checkIn
      ) {
        return "Check-out date must be after check-in date";
      }
      return null;
    },
  },
];

// Meeting scheduler with business hours
const meetingFields = [
  {
    name: "meetingDate",
    label: "Meeting Date",
    type: "date",
    format: "dd/mm/yyyy",
    minDate: "today",
    // Exclude weekends
    validate: (value) => {
      if (value) {
        const day = value.getDay();
        if (day === 0 || day === 6) {
          return "Please select a weekday";
        }
      }
      return null;
    },
    cols: 6,
  },
  {
    name: "meetingTime",
    label: "Meeting Time",
    type: "date",
    timeOnly: true,
    hourFormat: "12",
    stepMinute: 30,
    // Business hours only: 9 AM to 5 PM
    validate: (value) => {
      if (value) {
        const hours = value.getHours();
        if (hours < 9 || hours >= 17) {
          return "Please select a time between 9:00 AM and 5:00 PM";
        }
      }
      return null;
    },
    cols: 6,
  },
];

// Birth date with age calculation
const personalInfoFields = [
  {
    name: "birthDate",
    label: "Date of Birth",
    type: "date",
    format: "mm/dd/yyyy",
    maxDate: "today",
    yearNavigator: true,
    yearRange: "1920:2010",
    required: true,
    onDateChange: (date) => {
      // Calculate and display age
      if (date) {
        const today = new Date();
        const age = today.getFullYear() - date.getFullYear();
        const monthDiff = today.getMonth() - date.getMonth();

        if (
          monthDiff < 0 ||
          (monthDiff === 0 && today.getDate() < date.getDate())
        ) {
          age--;
        }

        console.log(`Age: ${age} years`);
      }
      return date;
    },
    cols: 12,
  },
];
</script>

<template>
  <div>
    <h3>Hotel Booking</h3>
    <Form :fields="hotelBookingFields" v-model:form-data="bookingData" />

    <h3 class="mt-6">Meeting Scheduler</h3>
    <Form :fields="meetingFields" />

    <h3 class="mt-6">Personal Information</h3>
    <Form :fields="personalInfoFields" />
  </div>
</template>
```

```vue

```

### User Registration Form

```vue
<script setup>
import { ref } from "vue";

const registrationData = ref({});

const registrationFields = [
  {
    name: "username",
    label: "Username",
    type: "text",
    required: true,
    validate: (value) => {
      if (value && value.length < 3) {
        return "Username must be at least 3 characters";
      }
      return null;
    },
  },
  {
    name: "email",
    label: "Email",
    type: "text",
    format: "email",
    required: true,
  },
  {
    name: "age",
    label: "Age",
    type: "number",
    min: 18,
    max: 100,
    required: true,
  },
  // UPDATED: Using AutoComplete instead of Select
  {
    name: "country",
    label: "Country",
    type: "autocomplete", // Changed from 'select' to 'autocomplete'
    placeholder: "Type or select your country",
    required: true,
    options: [
      { label: "United States", value: "us" },
      { label: "Canada", value: "ca" },
      { label: "United Kingdom", value: "uk" },
      { label: "Australia", value: "au" },
      { label: "Germany", value: "de" },
    ],
    optionLabel: "label",
    optionValue: "value",
    forceSelection: false, // Allows users to enter unlisted countries
    helpText: "Select from common countries or type your own",
  },
  {
    name: "terms",
    label: "I agree to the terms and conditions",
    type: "checkbox",
    required: true,
    validate: (value) => {
      if (!value) {
        return "You must agree to the terms and conditions";
      }
      return null;
    },
  },
];
</script>

<template>
  <Form
    :fields="registrationFields"
    v-model:form-data="registrationData"
    @submit="handleRegistration"
    submit-button-text="Register"
    show-cancel-button
    @cancel="handleCancel"
  />
</template>
```

### Survey Form with Custom Validation

```vue
<script setup>
import { ref } from "vue";

const surveyData = ref({});

const surveyFields = [
  {
    name: "satisfaction",
    label: "How satisfied are you with our service?",
    type: "radio",
    required: true,
    options: [
      { label: "Very Satisfied", value: "5" },
      { label: "Satisfied", value: "4" },
      { label: "Neutral", value: "3" },
      { label: "Dissatisfied", value: "2" },
      { label: "Very Dissatisfied", value: "1" },
    ],
    cols: 12,
  },
  {
    name: "feedback",
    label: "Additional Feedback",
    type: "textarea",
    rows: 4,
    placeholder: "Please share your thoughts...",
    cols: 12,
    onChangeOverride: (value, fieldName, formData) => {
      // Custom logic for this field only
      console.log(`Feedback length: ${value?.length || 0} characters`);
    },
  },
  {
    name: "recommend",
    label: "Would you recommend us to others?",
    type: "checkbox",
    cols: 12,
  },
];

const handleSurveySubmit = (data) => {
  console.log("Survey submitted:", data);
};

const handleFieldChange = (event) => {
  console.log("Global change handler:", event);
};
</script>

<template>
  <Form
    :fields="surveyFields"
    v-model:form-data="surveyData"
    @submit="handleSurveySubmit"
    @change="handleFieldChange"
    submit-button-text="Submit Survey"
  />
</template>
```

### File Upload Form

```vue
<script setup>
import { ref } from "vue";

const uploadData = ref({});

const uploadFields = [
  {
    name: "title",
    label: "Document Title",
    type: "text",
    required: true,
    cols: 12,
  },
  {
    name: "category",
    label: "Category",
    type: "select",
    required: true,
    options: [
      { label: "Reports", value: "reports" },
      { label: "Presentations", value: "presentations" },
      { label: "Documents", value: "documents" },
    ],
    cols: 12,
    md: 6,
  },
  {
    name: "uploadDate",
    label: "Upload Date",
    type: "date",
    required: true,
    cols: 12,
    md: 6,
  },
  {
    name: "files",
    label: "Select Files",
    type: "file",
    accept: ".pdf,.doc,.docx,.ppt,.pptx",
    multiple: true,
    required: true,
    cols: 12,
  },
  {
    name: "description",
    label: "Description",
    type: "textarea",
    rows: 3,
    helpText: "Optional description of the uploaded files",
    cols: 12,
  },
];
</script>

<template>
  <Form
    :fields="uploadFields"
    v-model:form-data="uploadData"
    @submit="handleFileUpload"
    submit-button-text="Upload Files"
  />
</template>
```

## Form State Management

### Controlled Mode (External Form Data)

When you provide a `formData` prop, the component operates in controlled mode:

```vue
<script setup>
import { ref } from "vue";

// External form state
const formData = ref({
  name: "John Doe",
  email: "john@example.com",
});
</script>

<template>
  <Form :fields="fields" v-model:form-data="formData" @submit="handleSubmit" />
</template>
```

### Uncontrolled Mode (Internal Form Data)

When no `formData` is provided, the component manages its own internal state:

```vue
<template>
  <Form :fields="fields" @submit="handleSubmit" />
</template>

<script setup>
const handleSubmit = (data) => {
  // Data is passed to the submit handler
  console.log("Form data:", data);
};
</script>
```

## Validation

### Built-in Validation

- **Required fields** - Validates that required fields are not empty
- **Email format** - Validates email format when `format: 'email'` is used
- **Number ranges** - Validates min/max values for number fields

### Custom Validation

Each field can have a custom validation function:

```javascript
{
  name: 'password',
  label: 'Password',
  type: 'text',
  required: true,
  validate: (value) => {
    if (value && value.length < 8) {
      return 'Password must be at least 8 characters long'
    }
    if (value && !/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
      return 'Password must contain at least one lowercase letter, one uppercase letter, and one number'
    }
    return null // Valid
  }
}
```

### Validation Timing

- **On change** - When `validateOnChange` is `true` (default)
- **On submit** - Always validates on form submission
- **Manual** - Using the exposed `validateForm()` method

## Responsive Layout

The component uses Vuetify's grid system for responsive layouts:

```javascript
{
  name: 'field',
  label: 'Field',
  type: 'text',
  cols: 12,    // Full width on extra small screens
  sm: 12,      // Full width on small screens
  md: 6,       // Half width on medium screens
  lg: 4        // One-third width on large screens
}
```

## Styling

The component uses Vuetify's design system with:

- **Outlined variants** for consistent appearance
- **Comfortable density** for optimal spacing
- **Error state styling** for validation feedback
- **Required field indicators** with red asterisks
- **Responsive design** that adapts to screen size

## Best Practices

1. **Use meaningful field names** that reflect the data structure
2. **Provide clear labels and help text** for better user experience
3. **Implement proper validation** for data integrity
4. **Consider responsive layout** for different screen sizes
5. **Handle form submission errors** gracefully
6. **Use controlled mode** when form data needs to be managed externally
7. **Leverage custom validation** for business-specific rules
8. **Test with various field combinations** to ensure proper behavior
9. **Use appropriate field types** for better user experience
10. **Provide meaningful default values** when appropriate

## Accessibility

The component includes:

- **Proper form semantics** with native HTML form elements
- **Label associations** for screen readers
- **Error message announcements** for validation feedback
- **Keyboard navigation** support throughout the form
- **Focus management** for better usability
- **Required field indicators** for clarity

## Browser Support

Compatible with all modern browsers that support:

- Vue 3 Composition API
- Vuetify 3 components
- ES6+ features
- CSS Grid and Flexbox

## Migration from Vuetify

The component has been completely migrated from Vuetify to PrimeVue. Here's what you need to know:

### ✨ What's New

- **AutoComplete component** - The star feature! Users can select from suggestions OR enter completely custom values
- **Enhanced Date/Time Pickers** - Multiple format support (`YYYY-MM-DD`, `mm/dd/yyyy`, etc.), smart defaults (`today`, `now`), time-only mode, inline calendars, business hour constraints, and comprehensive validation
- **Better file uploads** - Drag & drop, better validation, file preview
- **Improved accessibility** - Full ARIA support, better keyboard navigation
- **Flexible styling** - CSS custom properties, easier theming
- **Mobile-first responsive** - Better grid system, improved mobile UX

### 🔄 Migration Changes

#### Layout System

```javascript
// OLD (Vuetify):
{ cols: 12, sm: 12, md: 6, lg: 6 }

// NEW (CSS Grid): Same properties, better responsive behavior
{ cols: 12, md: 6, lg: 4 }
```

#### Component Mapping

| Vuetify         | PrimeVue       | Notes                    |
| --------------- | -------------- | ------------------------ |
| `v-text-field`  | `InputText`    | Same functionality       |
| `v-textarea`    | `Textarea`     | Added autoResize         |
| `v-select`      | `Select`       | Added filtering          |
| `v-checkbox`    | `Checkbox`     | Improved styling         |
| `v-radio-group` | `RadioButton`  | Better layout            |
| `v-file-input`  | `FileUpload`   | Enhanced features        |
| `v-btn`         | `Button`       | Same functionality       |
| **New!**        | `AutoComplete` | 🚀 Custom values support |
| **New!**        | `DatePicker`   | Calendar popup           |

#### Event Changes

```javascript
// OLD (Vuetify):
@update:model-value="handleChange"

// NEW (PrimeVue):
@update:model-value="handleChange"  // Same!
// But also enhanced with better event data
```

#### Styling Changes

```css
/* OLD: Vuetify classes */
.v-form { ... }
.v-text-field { ... }

/* NEW: Custom CSS with variables */
.dynamic-form { ... }
.field-wrapper { ... }

/* Theming with CSS custom properties */
:root {
  --text-color: #374151;
  --red-500: #ef4444;
}
```

### 🚀 Upgrade Benefits

1. **AutoComplete with Custom Values** - Users are no longer limited to predefined options
2. **Better Performance** - PrimeVue components are more lightweight
3. **Enhanced Accessibility** - Full WCAG compliance out of the box
4. **Improved Developer Experience** - Better TypeScript support, clearer APIs
5. **Modern Styling** - CSS Grid, custom properties, better mobile support
6. **Rich Components** - Better date pickers, file uploads, and form controls

### 📝 Quick Migration Checklist

- [ ] Update imports from Vuetify to PrimeVue components
- [ ] Replace `v-select` with `AutoComplete` where custom values are needed
- [ ] Update CSS classes from `v-*` to custom classes
- [ ] Test responsive layout (should work better!)
- [ ] Enjoy the new AutoComplete functionality! 🎉

## Dependencies

- **Vue 3** with Composition API
- **PrimeVue 4.4+** components (InputText, InputNumber, Textarea, Select, AutoComplete, Checkbox, RadioButton, DatePicker, FileUpload, Button, Message)
- **Modern JavaScript** features (ES6+)
- **CSS Grid** and **Flexbox** support