text-field.tsx 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. 'use client';
  2. import { type ComponentRenderProps } from '@json-render/react';
  3. import { useData, useFieldValidation } from '@json-render/react';
  4. import { getByPath } from '@json-render/core';
  5. export function TextField({ element }: ComponentRenderProps) {
  6. const { label, valuePath, placeholder, type, checks, validateOn } = element.props as {
  7. label: string;
  8. valuePath: string;
  9. placeholder?: string | null;
  10. type?: string | null;
  11. checks?: Array<{ fn: string; message: string }> | null;
  12. validateOn?: string | null;
  13. };
  14. const { data, set } = useData();
  15. const value = getByPath(data, valuePath) as string | undefined;
  16. const { errors, validate, touch } = useFieldValidation(valuePath, {
  17. checks: checks ?? undefined,
  18. validateOn: (validateOn as 'change' | 'blur' | 'submit') ?? 'blur',
  19. });
  20. return (
  21. <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
  22. <label style={{ fontSize: 14, fontWeight: 500 }}>{label}</label>
  23. <input
  24. type={type || 'text'}
  25. value={value ?? ''}
  26. onChange={(e) => {
  27. set(valuePath, e.target.value);
  28. if (validateOn === 'change') validate();
  29. }}
  30. onBlur={() => {
  31. touch();
  32. if (validateOn === 'blur' || !validateOn) validate();
  33. }}
  34. placeholder={placeholder ?? ''}
  35. style={{
  36. padding: '8px 12px',
  37. borderRadius: 'var(--radius)',
  38. border: errors.length > 0 ? '1px solid #ef4444' : '1px solid var(--border)',
  39. background: 'var(--card)',
  40. color: 'var(--foreground)',
  41. fontSize: 14,
  42. outline: 'none',
  43. }}
  44. />
  45. {errors.map((error, i) => (
  46. <span key={i} style={{ fontSize: 12, color: '#ef4444' }}>{error}</span>
  47. ))}
  48. </div>
  49. );
  50. }