Files
thrilltrack-explorer/src/components/ui/FormFieldWrapper.README.md
gpt-engineer-app[bot] 6fef107728 Create unified FormFieldWrapper
Introduce a new reusable form field component that automatically shows hints, validation messages, and terminology tooltips based on field type; refactor forms to demonstrate usage.
2025-11-11 23:34:57 +00:00

6.9 KiB

FormFieldWrapper Component

A unified form field component that automatically provides hints, validation messages, and terminology tooltips based on field type.

Features

  • Automatic hints based on field type (speed, height, URL, email, etc.)
  • Built-in validation display with error messages
  • Terminology tooltips on labels (hover to see definitions)
  • Character counting for textareas
  • 50% less boilerplate compared to manual field creation
  • Type-safe with TypeScript
  • Consistent styling across all forms

Quick Start

Before (Manual)

<div className="space-y-2">
  <Label htmlFor="website_url">Website URL</Label>
  <Input
    id="website_url"
    type="url"
    {...register('website_url')}
    placeholder="https://..."
  />
  <p className="text-xs text-muted-foreground">
    Official website URL (must start with https:// or http://)
  </p>
  {errors.website_url && (
    <p className="text-sm text-destructive">{errors.website_url.message}</p>
  )}
</div>

After (With FormFieldWrapper)

<FormFieldWrapper
  id="website_url"
  label="Website URL"
  fieldType="url"
  error={errors.website_url?.message as string}
  inputProps={{
    ...register('website_url'),
    placeholder: "https://..."
  }}
/>

Basic Usage

import { FormFieldWrapper } from '@/components/ui/form-field-wrapper';
import { useForm } from 'react-hook-form';

function MyForm() {
  const { register, formState: { errors } } = useForm();
  
  return (
    <form>
      {/* Basic text input with automatic hint */}
      <FormFieldWrapper
        id="email"
        label="Email Address"
        fieldType="email"
        required
        error={errors.email?.message as string}
        inputProps={{
          ...register('email', { required: 'Email is required' }),
          placeholder: "contact@example.com"
        }}
      />
      
      {/* Textarea with character count */}
      <FormFieldWrapper
        id="notes"
        label="Notes for Reviewers"
        fieldType="submission-notes"
        optional
        value={watch('notes')}
        maxLength={1000}
        error={errors.notes?.message as string}
        textareaProps={{
          ...register('notes'),
          placeholder: "Add context...",
          rows: 3
        }}
      />
    </form>
  );
}

With Terminology Tooltips

<FormFieldWrapper
  id="inversions"
  label="Inversions"
  fieldType="inversions"
  termKey="inversion"  // Adds tooltip explaining what inversions are
  error={errors.inversions?.message as string}
  inputProps={{
    ...register('inversions'),
    type: "number",
    placeholder: "e.g. 7"
  }}
/>

Using Presets

import { FormFieldWrapper, formFieldPresets } from '@/components/ui/form-field-wrapper';

<FormFieldWrapper
  {...formFieldPresets.sourceUrl({})}
  id="source_url"
  error={errors.source_url?.message as string}
  inputProps={{
    ...register('source_url'),
    placeholder: "https://..."
  }}
/>

Available Field Types

  • url - Website URLs with protocol hint
  • email - Email addresses with format hint
  • phone - Phone numbers with flexible format hint
  • slug - URL slugs with character restrictions
  • height-requirement - Height in cm with metric hint
  • age-requirement - Age requirements
  • capacity - Capacity per hour
  • duration - Duration in seconds
  • speed - Max speed (km/h)
  • height - Max height (meters)
  • length - Track length (meters)
  • inversions - Number of inversions
  • g-force - G-force values
  • source-url - Reference URL for verification
  • submission-notes - Notes for moderators (textarea with char count)

Available Presets

formFieldPresets.websiteUrl({})
formFieldPresets.email({})
formFieldPresets.phone({})
formFieldPresets.sourceUrl({})
formFieldPresets.submissionNotes({})
formFieldPresets.heightRequirement({})
formFieldPresets.capacity({})
formFieldPresets.duration({})
formFieldPresets.speed({})
formFieldPresets.height({})
formFieldPresets.length({})
formFieldPresets.inversions({})
formFieldPresets.gForce({})

Custom Hints

Override automatic hints with custom text:

<FormFieldWrapper
  id="custom"
  label="Custom Field"
  fieldType="text"
  hint="This is my custom hint that overrides any automatic hint"
  inputProps={{...register('custom')}}
/>

Hide Hints

<FormFieldWrapper
  id="no_hint"
  label="Field Without Hint"
  fieldType="url"
  hideHint
  inputProps={{...register('no_hint')}}
/>

Migration Guide

To migrate existing fields:

  1. Identify the field structure to replace
  2. Choose appropriate fieldType from the list above
  3. Add termKey if field relates to terminology
  4. Replace the entire div block with FormFieldWrapper

Example migration:

// BEFORE
<div className="space-y-2">
  <Label htmlFor="max_speed_kmh">Max Speed (km/h)</Label>
  <Input
    id="max_speed_kmh"
    type="number"
    {...register('max_speed_kmh')}
    placeholder="e.g. 193"
  />
  <p className="text-xs text-muted-foreground">
    Speed must be in km/h, between 0-500. Example: "193" for 193 km/h (120 mph)
  </p>
  {errors.max_speed_kmh && (
    <p className="text-sm text-destructive">{errors.max_speed_kmh.message}</p>
  )}
</div>

// AFTER
<FormFieldWrapper
  id="max_speed_kmh"
  label="Max Speed (km/h)"
  fieldType="speed"
  termKey="kilometers-per-hour"
  error={errors.max_speed_kmh?.message as string}
  inputProps={{
    ...register('max_speed_kmh'),
    type: "number",
    placeholder: "e.g. 193"
  }}
/>

Demo

View a live interactive demo at /examples/form-field-wrapper (in development mode) by visiting the FormFieldWrapperDemo component.

Props Reference

Prop Type Description
id string Field identifier (required)
label string Field label text (required)
fieldType FormFieldType Type for automatic hints
termKey string Terminology key for tooltip
showTermIcon boolean Show tooltip icon (default: true)
required boolean Show required asterisk
optional boolean Show optional badge
hint string Custom hint (overrides automatic)
error string Error message from validation
value string | number Current value for char counting
maxLength number Max length for char counting
inputProps InputProps Props to pass to Input
textareaProps TextareaProps Props to pass to Textarea
className string Additional wrapper classes
hideHint boolean Hide automatic hint

Benefits

  1. Consistency - All fields follow the same structure
  2. Less Code - ~50% reduction in boilerplate
  3. Smart Defaults - Automatic hints based on field type
  4. Built-in Terminology - Hover tooltips for technical terms
  5. Easy Updates - Change hints in one place, updates everywhere
  6. Type Safety - TypeScript ensures correct usage