From ec4b67773c699cc1da0ec0614ecb7aad63e778a6 Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Wed, 9 Sep 2026 11:58:15 +0300 Subject: [PATCH 1/7] feat(apollo-wind): metadata-form controlled-host seam and string-list field Generic forms-engine enablers extracted from the guardrails work (apollo-ui#1107 review): controlled values/onValuesChange/errors/disableValidation/container props on MetadataForm (MetadataFormProps now exported), a string-list field type with tooltip/textarea/multiselect metadata additions, label association and custom-component ref handling in the field renderer, and a useWatch re-export so cross-package custom fields share the RHF context. Co-Authored-By: Claude Fable 5 --- .../src/components/forms/field-renderer.tsx | 90 ++++-- .../src/components/forms/form-schema.ts | 32 +- .../apollo-wind/src/components/forms/index.ts | 49 ++-- .../forms/metadata-form.stories.tsx | 60 ++++ .../components/forms/metadata-form.test.tsx | 276 ++++++++++++++++++ .../src/components/forms/metadata-form.tsx | 158 ++++++++-- .../components/forms/string-list-field.tsx | 135 +++++++++ .../components/forms/validation-converter.ts | 5 +- .../apollo-wind/src/components/ui/index.ts | 5 +- .../src/components/ui/info-tooltip.tsx | 33 +++ .../apollo-wind/src/components/ui/select.tsx | 2 +- .../src/components/ui/textarea.tsx | 2 +- 12 files changed, 780 insertions(+), 67 deletions(-) create mode 100644 packages/apollo-wind/src/components/forms/string-list-field.tsx create mode 100644 packages/apollo-wind/src/components/ui/info-tooltip.tsx diff --git a/packages/apollo-wind/src/components/forms/field-renderer.tsx b/packages/apollo-wind/src/components/forms/field-renderer.tsx index 7ee8f008d6..21e4f5422c 100644 --- a/packages/apollo-wind/src/components/forms/field-renderer.tsx +++ b/packages/apollo-wind/src/components/forms/field-renderer.tsx @@ -10,8 +10,9 @@ import { FormFieldError, FormFieldLabel, } from '@/components/ui/form-field'; +import { InfoTooltip } from '@/components/ui/info-tooltip'; import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; +import { Label, RequiredIndicator } from '@/components/ui/label'; import { MultiSelect } from '@/components/ui/multi-select'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { @@ -35,6 +36,7 @@ import type { } from './form-schema'; import { hasOptions, isCustomField } from './form-schema'; import { RulesEngine } from './rules-engine'; +import { StringListField } from './string-list-field'; /** * Field Renderer - Connects metadata to actual UI components @@ -277,7 +279,10 @@ export function FormFieldRenderer({ name={field.name} control={control} defaultValue={field.defaultValue} - render={({ field: formField, fieldState: { error } }) => ( + // `ref` is destructured away: custom components are plain function components + // (CustomFieldComponentProps declares no ref), and spreading the Controller's ref + // onto one triggers React's function-component ref warning. + render={({ field: { ref: _ref, ...formField }, fieldState: { error } }) => ( + {field.label} + {required && } + {field.tooltip && ( + + )} + + ); +} + function FieldByType({ field, formField, error, disabled, required, options }: FieldByTypeProps) { switch (field.type) { case 'text': case 'email': return ( - {field.label} + formField.onChange(e.target.value)} onBlur={formField.onBlur} @@ -358,8 +392,9 @@ function FieldByType({ field, formField, error, disabled, required, options }: F case 'number': return ( - {field.label} + - {field.label} +