From 6bbc2dbe359ce9da07eed9a85b71cd463edf595e Mon Sep 17 00:00:00 2001 From: guynikan Date: Tue, 27 Jan 2026 11:00:37 +0000 Subject: [PATCH 1/2] feat(factory-react): add default components for all form-schema types - Add default input components: InputText, InputSelect, InputCheckbox, InputDate, InputPhone, InputAutocomplete, InputTextarea, InputNumber - Add default container components: FormField, FormSectionContainer, FormSectionTitle, FormSectionGroup, FormSectionGroupContainer - Export *Props and *ComponentType for each for custom implementations - Register all defaults in setFactoryDefaultComponents() - Extend HTML/React attribute types instead of [key: string]: any --- .../react/src/components/DefaultFormField.tsx | 36 +++++++ .../DefaultFormSectionContainer.tsx | 38 +++++++ .../components/DefaultFormSectionGroup.tsx | 39 +++++++ .../DefaultFormSectionGroupContainer.tsx | 38 +++++++ .../components/DefaultFormSectionTitle.tsx | 41 +++++++ .../components/DefaultInputAutocomplete.tsx | 101 ++++++++++++++++++ .../src/components/DefaultInputCheckbox.tsx | 64 +++++++++++ .../react/src/components/DefaultInputDate.tsx | 73 +++++++++++++ .../src/components/DefaultInputNumber.tsx | 82 ++++++++++++++ .../src/components/DefaultInputPhone.tsx | 75 +++++++++++++ .../src/components/DefaultInputSelect.tsx | 100 +++++++++++++++++ .../react/src/components/DefaultInputText.tsx | 73 +++++++++++++ .../src/components/DefaultInputTextarea.tsx | 77 +++++++++++++ .../factories/react/src/components/index.ts | 19 +++- packages/factories/react/src/form-factory.tsx | 86 ++++++++++++++- packages/factories/react/src/index.ts | 43 ++++++++ 16 files changed, 981 insertions(+), 4 deletions(-) create mode 100644 packages/factories/react/src/components/DefaultFormField.tsx create mode 100644 packages/factories/react/src/components/DefaultFormSectionContainer.tsx create mode 100644 packages/factories/react/src/components/DefaultFormSectionGroup.tsx create mode 100644 packages/factories/react/src/components/DefaultFormSectionGroupContainer.tsx create mode 100644 packages/factories/react/src/components/DefaultFormSectionTitle.tsx create mode 100644 packages/factories/react/src/components/DefaultInputAutocomplete.tsx create mode 100644 packages/factories/react/src/components/DefaultInputCheckbox.tsx create mode 100644 packages/factories/react/src/components/DefaultInputDate.tsx create mode 100644 packages/factories/react/src/components/DefaultInputNumber.tsx create mode 100644 packages/factories/react/src/components/DefaultInputPhone.tsx create mode 100644 packages/factories/react/src/components/DefaultInputSelect.tsx create mode 100644 packages/factories/react/src/components/DefaultInputText.tsx create mode 100644 packages/factories/react/src/components/DefaultInputTextarea.tsx diff --git a/packages/factories/react/src/components/DefaultFormField.tsx b/packages/factories/react/src/components/DefaultFormField.tsx new file mode 100644 index 0000000..f74ba9c --- /dev/null +++ b/packages/factories/react/src/components/DefaultFormField.tsx @@ -0,0 +1,36 @@ +/** + * Default FormField Component + * + * Wrapper (grid cell) for a single form field. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the FormField component. + * Use this type when customizing FormField via components.FormField. + */ +export interface FormFieldProps extends React.HTMLAttributes { + /** Form field children (typically the rendered input) */ + children?: React.ReactNode; +} + +/** + * Component type for custom FormField. Use with createComponentSpec when + * registering a custom FormField in components. + */ +export type FormFieldComponentType = React.ComponentType; + +/** + * Default form field wrapper component. + */ +export const DefaultFormField: React.FC = ({ + children, + ...props +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/factories/react/src/components/DefaultFormSectionContainer.tsx b/packages/factories/react/src/components/DefaultFormSectionContainer.tsx new file mode 100644 index 0000000..e0523d7 --- /dev/null +++ b/packages/factories/react/src/components/DefaultFormSectionContainer.tsx @@ -0,0 +1,38 @@ +/** + * Default FormSectionContainer Component + * + * Container for a form section (title + groups). Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the FormSectionContainer component. + * Use this type when customizing FormSectionContainer via components.FormSectionContainer. + */ +export interface FormSectionContainerProps + extends React.HTMLAttributes { + /** Section children (FormSectionTitle, FormSectionGroupContainer) */ + children?: React.ReactNode; +} + +/** + * Component type for custom FormSectionContainer. Use with createComponentSpec when + * registering a custom FormSectionContainer in components. + */ +export type FormSectionContainerComponentType = + React.ComponentType; + +/** + * Default form section container component. + */ +export const DefaultFormSectionContainer: React.FC = ({ + children, + ...props +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/factories/react/src/components/DefaultFormSectionGroup.tsx b/packages/factories/react/src/components/DefaultFormSectionGroup.tsx new file mode 100644 index 0000000..0f3646b --- /dev/null +++ b/packages/factories/react/src/components/DefaultFormSectionGroup.tsx @@ -0,0 +1,39 @@ +/** + * Default FormSectionGroup Component + * + * Grid layout for a group of form fields. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the FormSectionGroup component. + * Use this type when customizing FormSectionGroup via components.FormSectionGroup. + */ +export interface FormSectionGroupProps + extends React.HTMLAttributes { + /** Group children (FormField components) */ + children?: React.ReactNode; + [key: string]: any; +} + +/** + * Component type for custom FormSectionGroup. Use with createComponentSpec when + * registering a custom FormSectionGroup in components. + */ +export type FormSectionGroupComponentType = + React.ComponentType; + +/** + * Default form section group component (grid layout). + */ +export const DefaultFormSectionGroup: React.FC = ({ + children, + ...props +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/factories/react/src/components/DefaultFormSectionGroupContainer.tsx b/packages/factories/react/src/components/DefaultFormSectionGroupContainer.tsx new file mode 100644 index 0000000..3ecc13f --- /dev/null +++ b/packages/factories/react/src/components/DefaultFormSectionGroupContainer.tsx @@ -0,0 +1,38 @@ +/** + * Default FormSectionGroupContainer Component + * + * Container for FormSectionGroup(s). Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the FormSectionGroupContainer component. + * Use this type when customizing FormSectionGroupContainer via components.FormSectionGroupContainer. + */ +export interface FormSectionGroupContainerProps + extends React.HTMLAttributes { + /** Container children (FormSectionGroup components) */ + children?: React.ReactNode; +} + +/** + * Component type for custom FormSectionGroupContainer. Use with createComponentSpec when + * registering a custom FormSectionGroupContainer in components. + */ +export type FormSectionGroupContainerComponentType = + React.ComponentType; + +/** + * Default form section group container component. + */ +export const DefaultFormSectionGroupContainer: React.FC = ({ + children, + ...props +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/factories/react/src/components/DefaultFormSectionTitle.tsx b/packages/factories/react/src/components/DefaultFormSectionTitle.tsx new file mode 100644 index 0000000..522eb6c --- /dev/null +++ b/packages/factories/react/src/components/DefaultFormSectionTitle.tsx @@ -0,0 +1,41 @@ +/** + * Default FormSectionTitle Component + * + * Section title (x-content). Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the FormSectionTitle component. + * Use this type when customizing FormSectionTitle via components.FormSectionTitle. + */ +export interface FormSectionTitleProps + extends React.HTMLAttributes { + /** Title content (from schema x-content) */ + 'x-content'?: string; + /** Optional children (alternative to x-content) */ + children?: React.ReactNode; +} + +/** + * Component type for custom FormSectionTitle. Use with createComponentSpec when + * registering a custom FormSectionTitle in components. + */ +export type FormSectionTitleComponentType = + React.ComponentType; + +/** + * Default form section title component. + */ +export const DefaultFormSectionTitle: React.FC = ({ + 'x-content': content, + children, + ...props +}) => { + return ( +

+ {content ?? children} +

+ ); +}; diff --git a/packages/factories/react/src/components/DefaultInputAutocomplete.tsx b/packages/factories/react/src/components/DefaultInputAutocomplete.tsx new file mode 100644 index 0000000..9927917 --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputAutocomplete.tsx @@ -0,0 +1,101 @@ +/** + * Default InputAutocomplete Component + * + * Built-in autocomplete input for forms (uses native datalist). + * Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +export interface InputAutocompleteOption { + value: string; + label?: string; +} + +/** + * Props passed to the InputAutocomplete component. + * Use this type when customizing InputAutocomplete via components.InputAutocomplete. + */ +export interface InputAutocompleteProps + extends Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' | 'list' + > { + name: string; + value?: string; + onChange?: (value: string) => void; + label?: string; + /** List of options for autocomplete (value used for both value and label if label omitted) */ + options?: InputAutocompleteOption[] | string[]; +} + +/** + * Component type for custom InputAutocomplete. Use with createComponentSpec when + * registering a custom InputAutocomplete in components. + */ +export type InputAutocompleteComponentType = React.ComponentType; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px', + border: '1px solid #ccc', + borderRadius: '4px', +}; + +const labelStyle: React.CSSProperties = { + display: 'block', + marginBottom: '4px', + fontWeight: '500', +}; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +function normalizeOptions( + options: InputAutocompleteOption[] | string[] = [] +): { value: string; label: string }[] { + return options.map((opt) => + typeof opt === 'string' ? { value: opt, label: opt } : { value: opt.value, label: opt.label ?? opt.value } + ); +} + +/** + * Default autocomplete input component (uses native datalist). + */ +export const DefaultInputAutocomplete = React.forwardRef< + HTMLInputElement, + InputAutocompleteProps +>(({ label, name, value, onChange, placeholder, options = [], ...rest }, ref) => { + const listId = `${name}-datalist`; + const normalizedOptions = normalizeOptions(options); + + return ( +
+ {label && ( + + )} + onChange?.(e.target.value)} + style={inputStyle} + {...rest} + /> + + {normalizedOptions.map((opt) => ( + + ))} + +
+ ); +}); + +DefaultInputAutocomplete.displayName = 'DefaultInputAutocomplete'; diff --git a/packages/factories/react/src/components/DefaultInputCheckbox.tsx b/packages/factories/react/src/components/DefaultInputCheckbox.tsx new file mode 100644 index 0000000..9690f4a --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputCheckbox.tsx @@ -0,0 +1,64 @@ +/** + * Default InputCheckbox Component + * + * Built-in checkbox input for forms. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the InputCheckbox component. + * Use this type when customizing InputCheckbox via components.InputCheckbox. + */ +export interface InputCheckboxProps + extends Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' | 'checked' | 'type' + > { + name: string; + value?: boolean; + onChange?: (value: boolean) => void; + label?: string; + children?: React.ReactNode; +} + +/** + * Component type for custom InputCheckbox. Use with createComponentSpec when + * registering a custom InputCheckbox in components. + */ +export type InputCheckboxComponentType = React.ComponentType; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +const labelStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: '8px', +}; + +/** + * Default checkbox input component. + */ +export const DefaultInputCheckbox = React.forwardRef( + ({ label, name, value, onChange, children, ...rest }, ref) => { + return ( +
+ + {children} +
+ ); + } +); + +DefaultInputCheckbox.displayName = 'DefaultInputCheckbox'; diff --git a/packages/factories/react/src/components/DefaultInputDate.tsx b/packages/factories/react/src/components/DefaultInputDate.tsx new file mode 100644 index 0000000..a569388 --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputDate.tsx @@ -0,0 +1,73 @@ +/** + * Default InputDate Component + * + * Built-in date input for forms. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the InputDate component. + * Use this type when customizing InputDate via components.InputDate. + */ +export interface InputDateProps + extends Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' | 'type' + > { + name: string; + value?: string; + onChange?: (value: string) => void; + label?: string; +} + +/** + * Component type for custom InputDate. Use with createComponentSpec when + * registering a custom InputDate in components. + */ +export type InputDateComponentType = React.ComponentType; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px', + border: '1px solid #ccc', + borderRadius: '4px', +}; + +const labelStyle: React.CSSProperties = { + display: 'block', + marginBottom: '4px', + fontWeight: '500', +}; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +/** + * Default date input component. + */ +export const DefaultInputDate = React.forwardRef( + ({ label, name, value, onChange, ...rest }, ref) => { + return ( +
+ {label && ( + + )} + onChange?.(e.target.value)} + style={inputStyle} + {...rest} + /> +
+ ); + } +); + +DefaultInputDate.displayName = 'DefaultInputDate'; diff --git a/packages/factories/react/src/components/DefaultInputNumber.tsx b/packages/factories/react/src/components/DefaultInputNumber.tsx new file mode 100644 index 0000000..1e4b91d --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputNumber.tsx @@ -0,0 +1,82 @@ +/** + * Default InputNumber Component + * + * Built-in number input for forms. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the InputNumber component. + * Use this type when customizing InputNumber via components.InputNumber. + */ +export interface InputNumberProps + extends Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' | 'type' + > { + name: string; + value?: number | string; + onChange?: (value: number | string) => void; + label?: string; + min?: number; + max?: number; + step?: number | string; +} + +/** + * Component type for custom InputNumber. Use with createComponentSpec when + * registering a custom InputNumber in components. + */ +export type InputNumberComponentType = React.ComponentType; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px', + border: '1px solid #ccc', + borderRadius: '4px', +}; + +const labelStyle: React.CSSProperties = { + display: 'block', + marginBottom: '4px', + fontWeight: '500', +}; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +/** + * Default number input component. + */ +export const DefaultInputNumber = React.forwardRef( + ({ label, name, value, onChange, placeholder, min, max, step, ...rest }, ref) => { + return ( +
+ {label && ( + + )} + + onChange?.(e.target.value ? Number(e.target.value) : '') + } + style={inputStyle} + {...rest} + /> +
+ ); + } +); + +DefaultInputNumber.displayName = 'DefaultInputNumber'; diff --git a/packages/factories/react/src/components/DefaultInputPhone.tsx b/packages/factories/react/src/components/DefaultInputPhone.tsx new file mode 100644 index 0000000..3bd9d8d --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputPhone.tsx @@ -0,0 +1,75 @@ +/** + * Default InputPhone Component + * + * Built-in phone input for forms (text input with type="tel"). + * Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the InputPhone component. + * Use this type when customizing InputPhone via components.InputPhone. + */ +export interface InputPhoneProps + extends Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' | 'type' + > { + name: string; + value?: string; + onChange?: (value: string) => void; + label?: string; +} + +/** + * Component type for custom InputPhone. Use with createComponentSpec when + * registering a custom InputPhone in components. + */ +export type InputPhoneComponentType = React.ComponentType; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px', + border: '1px solid #ccc', + borderRadius: '4px', +}; + +const labelStyle: React.CSSProperties = { + display: 'block', + marginBottom: '4px', + fontWeight: '500', +}; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +/** + * Default phone input component (uses type="tel"). + */ +export const DefaultInputPhone = React.forwardRef( + ({ label, name, value, onChange, placeholder, ...rest }, ref) => { + return ( +
+ {label && ( + + )} + onChange?.(e.target.value)} + style={inputStyle} + {...rest} + /> +
+ ); + } +); + +DefaultInputPhone.displayName = 'DefaultInputPhone'; diff --git a/packages/factories/react/src/components/DefaultInputSelect.tsx b/packages/factories/react/src/components/DefaultInputSelect.tsx new file mode 100644 index 0000000..82145d4 --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputSelect.tsx @@ -0,0 +1,100 @@ +/** + * Default InputSelect Component + * + * Built-in select input for forms. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +export interface InputSelectOption { + value: string; + label: string; +} + +/** + * Props passed to the InputSelect component. + * Use this type when customizing InputSelect via components.InputSelect. + */ +export interface InputSelectProps + extends Omit< + React.SelectHTMLAttributes, + 'value' | 'onChange' + > { + name: string; + value?: string; + onChange?: (value: string) => void; + label?: string; + placeholder?: string; + options?: InputSelectOption[]; + children?: React.ReactNode; +} + +/** + * Component type for custom InputSelect. Use with createComponentSpec when + * registering a custom InputSelect in components. + */ +export type InputSelectComponentType = React.ComponentType; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px', + border: '1px solid #ccc', + borderRadius: '4px', +}; + +const labelStyle: React.CSSProperties = { + display: 'block', + marginBottom: '4px', + fontWeight: '500', +}; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +/** + * Default select input component. + */ +export const DefaultInputSelect = React.forwardRef( + ( + { + label, + name, + value, + onChange, + options = [], + placeholder = 'Select...', + children, + ...rest + }, + ref + ) => { + return ( +
+ {label && ( + + )} + + {children} +
+ ); + } +); + +DefaultInputSelect.displayName = 'DefaultInputSelect'; diff --git a/packages/factories/react/src/components/DefaultInputText.tsx b/packages/factories/react/src/components/DefaultInputText.tsx new file mode 100644 index 0000000..2afc71f --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputText.tsx @@ -0,0 +1,73 @@ +/** + * Default InputText Component + * + * Built-in text input for forms. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the InputText component. + * Use this type when customizing InputText via components.InputText. + */ +export interface InputTextProps + extends Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' | 'type' + > { + name: string; + value?: string; + onChange?: (value: string) => void; + label?: string; +} + +/** + * Component type for custom InputText. Use with createComponentSpec when + * registering a custom InputText in components. + */ +export type InputTextComponentType = React.ComponentType; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px', + border: '1px solid #ccc', + borderRadius: '4px', +}; + +const labelStyle: React.CSSProperties = { + display: 'block', + marginBottom: '4px', + fontWeight: '500', +}; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +/** + * Default text input component. + */ +export const DefaultInputText = React.forwardRef( + ({ label, name, value, onChange, placeholder, ...rest }, ref) => { + return ( +
+ {label && ( + + )} + onChange?.(e.target.value)} + style={inputStyle} + {...rest} + /> +
+ ); + } +); + +DefaultInputText.displayName = 'DefaultInputText'; diff --git a/packages/factories/react/src/components/DefaultInputTextarea.tsx b/packages/factories/react/src/components/DefaultInputTextarea.tsx new file mode 100644 index 0000000..5d04c34 --- /dev/null +++ b/packages/factories/react/src/components/DefaultInputTextarea.tsx @@ -0,0 +1,77 @@ +/** + * Default InputTextarea Component + * + * Built-in textarea input for forms. Can be overridden via createComponentSpec. + */ + +import React from 'react'; + +/** + * Props passed to the InputTextarea component. + * Use this type when customizing InputTextarea via components.InputTextarea. + */ +export interface InputTextareaProps + extends Omit< + React.TextareaHTMLAttributes, + 'value' | 'onChange' + > { + name: string; + value?: string; + onChange?: (value: string) => void; + label?: string; + rows?: number; +} + +/** + * Component type for custom InputTextarea. Use with createComponentSpec when + * registering a custom InputTextarea in components. + */ +export type InputTextareaComponentType = React.ComponentType; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px', + border: '1px solid #ccc', + borderRadius: '4px', + fontFamily: 'inherit', +}; + +const labelStyle: React.CSSProperties = { + display: 'block', + marginBottom: '4px', + fontWeight: '500', +}; + +const wrapperStyle: React.CSSProperties = { marginBottom: '16px' }; + +/** + * Default textarea input component. + */ +export const DefaultInputTextarea = React.forwardRef< + HTMLTextAreaElement, + InputTextareaProps +>(({ label, name, value, onChange, placeholder, rows = 4, ...rest }, ref) => { + return ( +
+ {label && ( + + )} +