mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-29 14:27:06 -05:00
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.
265 lines
6.9 KiB
Markdown
265 lines
6.9 KiB
Markdown
# 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)
|
|
```tsx
|
|
<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)
|
|
```tsx
|
|
<FormFieldWrapper
|
|
id="website_url"
|
|
label="Website URL"
|
|
fieldType="url"
|
|
error={errors.website_url?.message as string}
|
|
inputProps={{
|
|
...register('website_url'),
|
|
placeholder: "https://..."
|
|
}}
|
|
/>
|
|
```
|
|
|
|
## Basic Usage
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
<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
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
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:
|
|
|
|
```tsx
|
|
<FormFieldWrapper
|
|
id="custom"
|
|
label="Custom Field"
|
|
fieldType="text"
|
|
hint="This is my custom hint that overrides any automatic hint"
|
|
inputProps={{...register('custom')}}
|
|
/>
|
|
```
|
|
|
|
## Hide Hints
|
|
|
|
```tsx
|
|
<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:
|
|
|
|
```tsx
|
|
// 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
|