UNPKG

ink-form

Version:

Complex user-friendly form component for React Ink

166 lines (165 loc) 7.17 kB
import React from 'react'; /** * A description for either a field or a section. If undefined or false, it will not be shown. * If its a JSX.Element, it will be rendered as description. If its a string, the string * will be rendered. If its an array of strings, they will be rendered as distinctive paragraphs. */ export type Description = string | string[] | JSX.Element | undefined | false; /** * The input structure for creating a new form, either by directly rendering it as component in * Ink, or by invoking ``openForm(form)`` with it. You have to supply a ``form`` attribute to * define the structure of the form. You should also supply a ``onSubmit`` attribute to receive * the results when the user submits the form. * * The form is uncontrolled by default, you can specify an initial value with ``initialValue``. You * can also switch to controlled mode by explicitly setting a ``value`` and reacting to ``onChange`` * events. */ export interface FormProps { /** Structure of the form, i.e. which fields are contained in which sections. */ form: FormStructure; initialValue?: object; /** Current value of the form. Omit to leave the component in uncontrolled mode. */ value?: object; onChange?: (value: object) => void; /** * ``onSubmit`` is triggered when the user has completed all required fields and triggers the * submit button at the end of the page. * * @param value the final value of the form. * */ onSubmit?: (value: object) => void; /** * You can use custom field implementations, by specifying their ``type`` attribute to a custom * value and supplying a FormFieldManager here that can handle this type. */ customManagers?: FormFieldManager<FormField | AbstractFormField<any, any>>[]; } /** * Top-level structure of the form. A form is composed of several sections, which are * displayed as distinct tabs, each containing several fields. */ export interface FormStructure { /** The title of the form is shown at the top throughout the application. */ title?: string; /** A form contains several sections, each displayed as a tab. */ sections: FormSection[]; } /** * A form is composed of several sections of which only one can be shown at once. It is * composed of several form fields. The title is shown as tab-text for the section. * A description can be supplied which will be shown at the top of the form. * * Note that all form fields are stored in a global value object, regardless of section structure. */ export interface FormSection { /** Title of the section, will be shown as its tab name. */ title: string; /** List of form fields which are visible when this section is open. */ fields: FormField[]; /** Optional text that describes this section. */ description?: Description; } /** * A form field describes a type of input, i.e. text input, number input etc. * * Included are: * - FormFieldString * - FormFieldInteger * - FormFieldFloat * - FormFieldSelect * - FormFieldBoolean * * You can add your own form field by extending ``AbstractFormField`` and implementing * an associated ``FormFieldManager<CustomFormField>``. */ export type FormField = FormFieldString | FormFieldInteger | FormFieldFloat | FormFieldSelect | FormFieldBoolean | AbstractFormField<any, any>; export type ValueOfField<T extends FormField> = T extends AbstractFormField<any, infer V> ? V : never; export type TypeOfField<T extends FormField> = T extends AbstractFormField<infer V, any> ? V : never; export type AbstractFormField<T extends string, V> = { /** * The type uniquely identifies which kind of form field is used, e.g. 'string', 'integer'. This tells * the library which form field manager should be used to handle this form field. */ type: T; /** * The name uniquely identifies the input field and is used to store the value in the output * structure, i.e. if ``field.name === 'myFieldName'``, then the value chosen by the user * is available in the output stucture in ``output['myFieldName']``. Note that the section * structure is irrelevant for the output structure, just the name uniquely identifies the * field. */ name: string; /** * A quick description of the field. If omitted, the name will be used as label. */ label?: string; /** An optional verbose description which will only be shown when the field is expanded. */ description?: Description; /** If this is set to true, the form cannot be submitted if this fields value is empty or undefined. */ required?: boolean; initialValue?: V; onChange?: (value: V, name: string) => void; }; export type FormFieldBoolean = AbstractFormField<'boolean', boolean> & {}; export type FormFieldString = AbstractFormField<'string', string> & { /** Set to e.g. ``*`` to use as password field. */ mask?: string; /** If supplied, the user cannot use a value which does not conform this regular expression. */ regex?: RegExp; placeholder?: string; }; export type FormFieldInteger = AbstractFormField<'integer', number> & { min?: number; max?: number; /** The user can use the arrow keys to increase or decrease the value by that step amount. */ step?: number; placeholder?: string; }; export type FormFieldFloat = AbstractFormField<'float', number> & { min?: number; max?: number; /** The user can use the arrow keys to increase or decrease the value by that step amount. */ step?: number; placeholder?: string; }; export type FormFieldSelect = AbstractFormField<'select', string> & { options: Array<{ label?: string; value: string; }>; }; export interface FormFieldValueRendererProps<T extends FormField> { value?: ValueOfField<T>; field: T; } /** * A FormFieldManager is responsible for handling a input kind, i.e. a ``FormField``, by specifying * how its input should be rendered. Implement a custom ``FormFieldManager`` and supply it to the * form to add custom form fields. */ export interface FormFieldManager<T extends FormField> { /** This value must match the ``FormField.type`` value you use for your custom form fields. */ type: TypeOfField<T>; needCtrlToReturnSave?: boolean; /** Render method for rendering the input component if the field is expanded. */ renderField: React.FC<SpecificFormFieldRendererProps<T>>; /** Render method for rendering the value if the field is collapsed. */ renderValue: React.FC<FormFieldValueRendererProps<T>>; } export type FormFieldRendererProps<T extends FormField> = { field: T; form: FormStructure; value?: ValueOfField<T>; onChange: (value: ValueOfField<T>) => void; onSetEditingField: (field?: string) => void; editingField?: string; customManagers?: FormFieldManager<FormField>[]; }; export type SpecificFormFieldRendererProps<T extends FormField> = FormFieldRendererProps<T> & { onError: (error: string) => void; onClearError: () => void; error?: string; onSave: (newValue?: ValueOfField<T>) => void; onCancel: () => void; };