Files
WooNooW/admin-spa/src/components/forms/SchemaField.tsx

178 lines
5.1 KiB
TypeScript

import React from 'react';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Switch } from '@/components/ui/switch';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
export interface FieldSchema {
type: 'text' | 'textarea' | 'email' | 'url' | 'number' | 'toggle' | 'checkbox' | 'select' | 'multiselect';
label: string;
description?: string;
placeholder?: string;
required?: boolean;
default?: any;
options?: Record<string, string>;
min?: number;
max?: number;
disabled?: boolean;
}
interface SchemaFieldProps {
name: string;
schema: FieldSchema;
value: any;
onChange: (value: any) => void;
error?: string;
}
export function SchemaField({ name, schema, value, onChange, error }: SchemaFieldProps) {
const renderField = () => {
switch (schema.type) {
case 'text':
case 'email':
case 'url':
return (
<Input
type={schema.type}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
placeholder={schema.placeholder}
required={schema.required}
/>
);
case 'number':
return (
<Input
type="number"
value={value ?? ''}
onChange={(e) => onChange(e.target.value === '' ? 0 : parseFloat(e.target.value))}
placeholder={schema.placeholder}
required={schema.required}
min={schema.min}
max={schema.max}
/>
);
case 'textarea':
return (
<Textarea
value={value || ''}
onChange={(e) => onChange(e.target.value)}
placeholder={schema.placeholder}
required={schema.required}
rows={4}
/>
);
case 'toggle':
return (
<div className="flex items-center gap-2">
<Switch
checked={!!value}
onCheckedChange={onChange}
disabled={schema.disabled}
/>
<span className="text-sm text-muted-foreground">
{value ? 'Enabled' : 'Disabled'}
</span>
</div>
);
case 'checkbox':
return (
<div className="flex items-center gap-2">
<Checkbox
checked={!!value}
onCheckedChange={onChange}
/>
<Label className="text-sm font-normal cursor-pointer">
{schema.label}
</Label>
</div>
);
case 'select':
return (
<Select value={value || ''} onValueChange={onChange}>
<SelectTrigger>
<SelectValue placeholder={schema.placeholder || 'Select an option'} />
</SelectTrigger>
<SelectContent>
{schema.options && Object.entries(schema.options).map(([key, label]) => (
<SelectItem key={key} value={key}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
);
case 'multiselect':
const selectedValues = Array.isArray(value) ? value : [];
return (
<div className="flex flex-wrap gap-2">
{schema.options && Object.entries(schema.options).map(([key, label]) => (
<label
key={key}
className={`flex items-center gap-2 px-3 py-2 rounded-lg border cursor-pointer transition-colors ${
selectedValues.includes(key)
? 'bg-primary/10 border-primary text-primary'
: 'bg-background border-input hover:bg-muted'
}`}
>
<Checkbox
checked={selectedValues.includes(key)}
onCheckedChange={(checked) => {
if (checked) {
onChange([...selectedValues, key]);
} else {
onChange(selectedValues.filter((v: string) => v !== key));
}
}}
/>
<span className="text-sm">{label}</span>
</label>
))}
</div>
);
default:
return (
<Input
value={value || ''}
onChange={(e) => onChange(e.target.value)}
placeholder={schema.placeholder}
/>
);
}
};
return (
<div className="space-y-2">
{schema.type !== 'checkbox' && (
<Label htmlFor={name}>
{schema.label}
{schema.required && <span className="text-destructive ml-1">*</span>}
</Label>
)}
{renderField()}
{schema.description && (
<p className="text-xs text-muted-foreground">
{schema.description}
</p>
)}
{error && (
<p className="text-xs text-destructive">
{error}
</p>
)}
</div>
);
}