### Development Server Setup Source: https://github.com/gravity-ui/dynamic-forms/blob/main/README.md Install dependencies and start the development server with Storybook using npm. ```shell npm ci npm run dev ``` -------------------------------- ### Install Playwright Browsers and Dependencies Source: https://github.com/gravity-ui/dynamic-forms/blob/main/playwright/README.md Installs the necessary Playwright browsers and dependencies for running tests. This command should be run once during initial setup. ```shell npm run playwright:install ``` -------------------------------- ### Install @gravity-ui/dynamic-forms Source: https://github.com/gravity-ui/dynamic-forms/blob/main/README.md Install the dynamic-forms package as a development dependency using npm. ```shell npm install --save-dev @gravity-ui/dynamic-forms ``` -------------------------------- ### Example Form Spec with MutationsSelect Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsMutators.mdx This is an example of a form specification that includes a 'mutate' field using the 'mutations-select' viewSpec type. This allows users to select which part of the form state to mutate. ```javascript { type: 'object', properties: { spec: { type: 'string', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'spec', }, }, value: { type: 'string', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'value', }, }, error: { type: 'string', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'error', }, }, all: { type: 'string', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'all', }, }, mutate: { type: 'string', enum: ['spec', 'value', 'error', 'all'], viewSpec: { type: 'mutations-select', layout: 'row', layoutTitle: 'mutate', }, }, }, viewSpec: { type: 'base', layout: 'accordeon', layoutTitle: 'Candidate', layoutOpen: true, }, } ``` -------------------------------- ### String Validation Specification Example Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsValidators.mdx Example of a string field specification including type, view details, length constraints, pattern matching, and validator type. ```json { "type": "string", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "Name", "placeholder": "placeholder text" }, "maxLength": 20, "minLength": 2, "pattern": "^[a-zA-Z0-9]+$", "patternError": "Only Latin letters and numbers are allowed", "validator": "base" } ``` -------------------------------- ### Number Validator Specification Example Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsValidators.mdx Example of how a number field is defined in the form specification, including validation constraints like minimum and maximum values. ```json { "type": "number", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "Age", "placeholder": "placeholder text" }, "required": true, "maximum": 12, "minimum": 2, "validator": "base" } ``` -------------------------------- ### External Mutators Example with DynamicField Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsMutators.mdx This example demonstrates how to use external mutators with the DynamicField component. It simulates fetching data and then applies mutations to the form's values, spec, and errors based on the fetched data. ```tsx import React, {useEffect, useState} from 'react'; import {DynamicField, DynamicFormMutators} from '@gravity-ui/dynamic-forms'; const ExternalMutatorsExample = () => { const [mutators, setMutators] = useState({}); const spec = { // Your form spec }; useEffect(() => { // Simulate an external event, e.g., data fetching fetchData().then((data) => { // Define the mutations you want to apply const newMutators: DynamicFormMutators = { values: { fieldName: data.value, }, spec: { fieldName: { disabled: data.disableField, }, }, errors: data.hasError ? {fieldName: 'Error message'} : {}, }; // Update the mutators state to trigger mutations setMutators(newMutators); }); }, []); return ; }; export default ExternalMutatorsExample; ``` -------------------------------- ### Basic DynamicField Usage with react-final-form Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsHowUse.mdx Demonstrates the fundamental integration of DynamicField within a react-final-form setup. Ensure you have react-final-form and @gravity-ui/dynamic-forms installed. The 'spec' object defines the form's structure and field configurations. ```tsx import React from 'react'; import {Form} from 'react-final-form'; import {DynamicField, DynamicFormConfig} from '@gravity-ui/dynamic-forms'; const MyForm = () => { const onSubmit = (formValues) => { console.log(formValues); }; const spec = { type: 'object', properties: { name: { type: 'string', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'Name', }, }, age: { type: 'number', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'Age', }, }, license: { type: 'boolean', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'License', }, }, }, viewSpec: { type: 'base', layout: 'accordion', layoutTitle: 'Candidate', layoutOpen: true, }, }; return (
{({handleSubmit}) => ( )} ); }; export default MyForm; ``` -------------------------------- ### Form State Example Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsHowUse.mdx Illustrates the expected structure of the form state managed by react-final-form when using DynamicField. The 'dynamicfield' key corresponds to the name prop passed to DynamicField. ```json { "dynamicfield": { "name": "John Doe", "age": 30, "license": true } } ``` -------------------------------- ### Example Field Spec for Custom Input Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsCustomInput.mdx Demonstrates how to use the registered custom input in a form specification. The 'type' property within 'viewSpec' is set to 'custom_text_input' to invoke the custom component. Layout and placeholder properties are also shown. ```json { "type": "string", "viewSpec": { "type": "custom_text_input", // Specify the input name in 'type' "layout": "row", "layoutTitle": "Name", "placeholder": "Enter your name" } } ``` -------------------------------- ### Object Validator Specification Example Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsValidators.mdx Example of an object field defined in the form specification. It includes nested properties and uses the 'base' validator, with 'required' set to true. ```json { "type": "object", "properties": { "name": { "type": "string", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "Name" } }, "age": { "type": "number", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "Age" } }, "license": { "type": "boolean", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "License" } } }, "viewSpec": { "type": "base", "layout": "accordeon", "layoutTitle": "Candidate", "layoutOpen": true }, "required": true, "validator": "base" } ``` -------------------------------- ### Boolean Validator Specification Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsValidators.mdx Example JSON specification for a boolean field, demonstrating the 'required' property. ```json { "type": "boolean", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "Flag" }, "required": true, "validator": "base" } ``` -------------------------------- ### Array Validator Specification Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsValidators.mdx Example JSON specification for an array field, including required, maxLength, minLength, and base validator. ```json { "type": "array", "items": { "type": "string", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "Element" } }, "viewSpec": { "type": "base", "layout": "accordeon", "layoutTitle": "Elements", "layoutOpen": true, "itemLabel": "Add element" }, "required": true, "maxLength": 5, "minLength": 2, "validator": "base" } ``` -------------------------------- ### Using a Custom Validator in Form Specification Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsValidators.mdx Example of a JSON specification for a string field that uses the 'custom_validator' for validation. ```json { "type": "string", "viewSpec": { "type": "base", "layout": "row", "layoutTitle": "Name", "placeholder": "placeholder text" }, "maxLength": 20, "minLength": 2, "pattern": "^[a-zA-Z0-9]+$", "patternError": "Only Latin letters and numbers are allowed", "validator": "custom_validator" } ``` -------------------------------- ### Use DynamicField with Custom Class Source: https://github.com/gravity-ui/dynamic-forms/blob/main/docs/styles.md Example of how to use the DynamicField component within a custom-styled div. This allows for component-specific styling overrides. ```tsx
``` -------------------------------- ### Usage for Value Overview Source: https://github.com/gravity-ui/dynamic-forms/blob/main/README.md Utilize DynamicView components to display an overview of form values. This requires importing dynamicViewConfig. ```jsx import {DynamicView, dynamicViewConfig} from '@gravity-ui/dynamic-forms'; // To get an overview of the values ; ``` -------------------------------- ### Configure Internationalization (I18N) Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/Readme.mdx Configure the library's language settings, supporting 'en' (default) and 'ru'. Use the configure function with the Lang enum. ```js import {configure, Lang} from '@gravity-ui/dynamic-forms'; configure({lang: Lang.Ru}); ``` -------------------------------- ### Mount and Assert Component Screenshot Source: https://github.com/gravity-ui/dynamic-forms/blob/main/playwright/README.md Mounts a React component and takes a screenshot for visual regression testing. Use this for basic component verification. ```typescript import React from 'react'; import {TEST_SPEC} from './helpers'; import {test} from '~playwright/core'; import {DynamicForm} from '~playwright/core/DynamicForm'; test('Name test', async ({mount, expectScreenshot}) => { //mounting a component await mount(); //screenshot await expectScreenshot(); }); ``` -------------------------------- ### Interact and Assert Component Screenshot Source: https://github.com/gravity-ui/dynamic-forms/blob/main/playwright/README.md Mounts a React component, performs an action (like clicking a button), and then takes a screenshot. Use this when testing component interactions. ```typescript import React from 'react'; import {TEST_SPEC} from './helpers'; import {test} from '~playwright/core'; import {DynamicForm} from '~playwright/core/DynamicForm'; test('Name test', async ({mount, expectScreenshot}) => { //mounting a component const component = await mount(); await component.getByRole('button').click(); //screenshot await expectScreenshot(); }); ``` -------------------------------- ### Update Playwright Screenshots Source: https://github.com/gravity-ui/dynamic-forms/blob/main/playwright/README.md Updates existing screenshots in the `__snapshots__` directory. Use this when component visuals have intentionally changed. ```shell npm run playwright:update ``` -------------------------------- ### Providing Initial Values to DynamicField Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsHowUse.mdx Shows how to pre-fill DynamicField components by setting the 'initialValues' prop on the react-final-form Form component. The structure of 'initialValues' must match the form's state, using the DynamicField's 'name' prop as the key. ```tsx import React from 'react'; import {Form} from 'react-final-form'; import {DynamicField, DynamicFormConfig} from '@gravity-ui/dynamic-forms'; const MyForm = () => { const onSubmit = (formValues) => { console.log(formValues); }; const initialValues = { dynamicfield: { name: 'John Doe', age: 30, license: true, }, }; const spec = { // ... (same as before) }; return (
{({handleSubmit}) => ( )} ); }; export default MyForm; ``` -------------------------------- ### Configure Language for I18N Source: https://github.com/gravity-ui/dynamic-forms/blob/main/README.md Configure the library to use a specific language for internationalization (i18n). The default is English, and Russian (Lang.Ru) is also supported. ```js // index.js import {configure, Lang} from '@gravity-ui/dynamic-forms'; configure({lang: Lang.Ru}); ``` -------------------------------- ### Run Playwright Tests Source: https://github.com/gravity-ui/dynamic-forms/blob/main/playwright/README.md Executes all defined Playwright visual regression tests. ```shell npm run playwright ``` -------------------------------- ### Controller Component Source: https://github.com/gravity-ui/dynamic-forms/blob/main/docs/lib.md The Controller component locates rendering elements and renders the entity. It requires specific properties for its operation. ```APIDOC ## Controller Component This component locates all required rendering elements and renders the entity. ### Properties - **name** (string) - Required - Field name. - **spec** (Spec) - Required - An [spec](./spec.md#specs) describing the entity. - **initialValue** (FieldValue) - Required - Initial value. - **parentOnChange** ((childName: string, childValue: FieldValue, childErrors: Record) => void) | null - Required - Callback for updating the parent entity's state. - **parentOnUnmount** ((childName: string) => void) | null - Required - Callback for unmount. ``` -------------------------------- ### Run Playwright Tests via Docker Source: https://github.com/gravity-ui/dynamic-forms/blob/main/playwright/README.md Executes Playwright tests within a Docker container, which is recommended for systems other than Linux. ```shell npm run playwright:docker ``` -------------------------------- ### Update Playwright Screenshots via Docker Source: https://github.com/gravity-ui/dynamic-forms/blob/main/playwright/README.md Updates existing screenshots within a Docker container. Use this when component visuals have intentionally changed and you are using Docker. ```shell npm run playwright:docker:update ``` -------------------------------- ### DynamicField Component Source: https://github.com/gravity-ui/dynamic-forms/blob/main/docs/lib.md The DynamicField component is the main entry point for rendering dynamic forms. It accepts various properties to define its behavior and appearance. ```APIDOC ## DynamicField Component This component serves as the primary entry point for drawing dynamic forms. ### Properties - **name** (string) - Required - Field name. - **spec** (Spec) - Required - A [spec](./spec.md#specs) describing the entity. - **config** (DynamicFormConfig) - Required - A [config](./config.md) containing [Inputs](./config.md#inputs), [Layouts](./config.md#layouts), and [validators](./config.md#validators) for each entity. - **Monaco** (React.ComponentType) - Optional - [MonacoEditor](https://github.com/react-monaco-editor/react-monaco-editor) component for Monaco [Input](./config.md#inputs). - **search** (string | function) - Optional - A string or function for performing a form search. - **withoutInsertFFDebounce** (boolean) - Optional - Flag that disables the delay before inserting data into the final-form store. - **destroyOnUnregister** (boolean) - Optional - If true, the value of a field will be destroyed when that field is unregistered. Defaults to true. - **generateRandomValue** (function) - Optional - Function that is necessary to generate a random value. - **storeSubscriber** ((storeValue: FieldValue) => void) - Optional - Subscriber function will be called when internal store of dynamic field is changed. ``` -------------------------------- ### Create Custom Text Input Component Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsCustomInput.mdx Defines a custom string input component for text data entry, extending TextInputBaseProps and implementing the StringInput interface. It handles value, blur, focus, and change events, and accepts disabled and placeholder properties from the spec. ```tsx import React from 'react'; import { isNil } from 'lodash'; import type { FieldRenderProps, StringInput } from '@gravity-ui/dynamic-forms'; import type { TextInputProps as TextInputBaseProps } from '@gravity-ui/uikit'; import { TextInput } from '@gravity-ui/uikit'; export interface TextProps extends Omit< TextInputBaseProps, 'value' | 'onBlur' | 'onFocus' | 'onUpdate' | 'disabled' | 'placeholder' | 'qa' > {} export const CustomTextInput: StringInput = ({ name, input: { value, onBlur, onChange, onFocus }, spec, inputProps, }) => { const props = { hasClear: true, ...inputProps, value: isNil(value) ? '' : value, // Set default value if value is null or undefined onBlur, onFocus, onUpdate: onChange as FieldRenderProps['input']['onChange'], disabled: spec.viewSpec.disabled, placeholder: spec.viewSpec.placeholder, qa: name, }; return ; }; ``` -------------------------------- ### Integrate Custom Input and View into Config Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsCustomInput.mdx Registers the custom input component and its corresponding view component within the dynamic form configuration. This allows the form builder to recognize and use the custom input by a specific name ('custom_text_input'). Cloning the base configuration prevents modification of original settings. ```typescript import _ from 'lodash'; import type { DynamicFormConfig, DynamicViewConfig } from '@gravity-ui/dynamic-forms'; import { dynamicConfig as libConfig, dynamicViewConfig as libViewConfig, } from '@gravity-ui/dynamic-forms'; import { CustomTextInput } from './CustomTextInput'; import { CustomTextInputView } from './CustomTextInputView'; const getDynamicConfig = (): DynamicFormConfig => { const dynamicConfig = _.cloneDeep(libConfig); // Register our custom input with a specific name dynamicConfig.string.inputs['custom_text_input'] = { Component: CustomTextInput }; return dynamicConfig; }; export const dynamicConfig = getDynamicConfig(); const getDynamicViewConfig = (): DynamicViewConfig => { const dynamicViewConfig = _.cloneDeep(libViewConfig); // Register the view for our custom input dynamicViewConfig.string.views['custom_text_input'] = { Component: CustomTextInputView }; return dynamicViewConfig; }; export const dynamicViewConfig = getDynamicViewConfig(); ``` -------------------------------- ### Render DynamicView Component Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsHowUse.mdx Use DynamicView to display form values. It requires a spec object defining the schema and a values object containing the data. The config prop can be used for custom configurations. ```tsx import React from 'react'; import {DynamicView, DynamicViewConfig} from '@gravity-ui/dynamic-forms'; const MyView = () => { const spec = { type: 'object', properties: { name: { type: 'string', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'Name', }, }, age: { type: 'number', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'Age', }, }, license: { type: 'boolean', viewSpec: { type: 'base', layout: 'row', layoutTitle: 'License', }, }, }, viewSpec: { type: 'base', layout: 'accordion', layoutTitle: 'Candidate', layoutOpen: true, }, }; const values = { name: 'John Doe', age: 30, license: true, }; return ; }; export default MyView; ``` -------------------------------- ### Create Custom Text Input View Component Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsCustomInput.mdx Defines a view component for the custom text input, used to display the input's value in a read-only mode. It simply renders the provided value using the Text component from @gravity-ui/uikit. ```tsx import React from 'react'; import type {StringView} from '@gravity-ui/dynamic-forms'; import {Text} from '@gravity-ui/uikit'; export const CustomTextInputView: StringView = ({value}) => { return {value}; }; ``` -------------------------------- ### Define SCSS Spacing Variables Source: https://github.com/gravity-ui/dynamic-forms/blob/main/docs/styles.md Defines SCSS variables for spacing based on a 4px base unit, following a modular grid system. These can be imported and used in your stylesheets. ```scss ... ``` -------------------------------- ### Usage with Final Form Source: https://github.com/gravity-ui/dynamic-forms/blob/main/README.md Embed DynamicField components within a final-form structure. Ensure necessary imports are included. ```jsx import {DynamicField, Spec, dynamicConfig} from '@gravity-ui/dynamic-forms'; // To embed in a final-form ; ``` -------------------------------- ### Define Simple Input Entity - TypeScript Source: https://github.com/gravity-ui/dynamic-forms/blob/main/docs/config.md Defines a simple input entity with a component and an optional independent flag. Use this for inputs that are wrapped by a layout. ```typescript type InputEntity = { Component: React.ComponentType< { spec: Spec; name: string; } & FieldRenderProps >; independent?: false; }; ``` -------------------------------- ### Define View Layout Type - TypeScript Source: https://github.com/gravity-ui/dynamic-forms/blob/main/docs/config.md Defines the type for view layout components. These components wrap view components and receive spec, name, and value props. ```typescript type ViewLayoutType = React.ComponentType<{ spec: Spec; name: string; value?: FormValue; }>; ``` -------------------------------- ### Custom Select Input with Mutator Logic Source: https://github.com/gravity-ui/dynamic-forms/blob/main/src/stories/DocsMutators.mdx A custom Select component that uses `useMutateDFState` to apply spec, value, or error mutations based on the selected option. Ensure the `removeAfterLastDot` helper function is available or adapted. ```tsx import React from 'react'; import type { BaseValidateError, FormValue, SelectProps, SpecMutator, StringInputProps, } from '@gravity-ui/dynamic-forms'; import {Select, useMutateDFState} from '@gravity-ui/dynamic-forms'; type OnChangeValue = Parameters[0]; type MutationVariants = { spec?: SpecMutator; values?: FormValue; errors?: BaseValidateError; }; const MUTATION_VARIANTS: Record = { spec: { spec: { viewSpec: { disabled: true, }, }, }, value: { values: 'mutation value', }, error: { errors: 'mutation error', }, all: { spec: { viewSpec: { disabled: true, }, }, values: 'mutation value', errors: 'mutation error', }, }; function removeAfterLastDot(str: string) { const lastDotIndex = str.lastIndexOf('.'); if (lastDotIndex === -1) { return str; } return str.substring(0, lastDotIndex); } export const MutationsSelect = (props: StringInputProps) => { const mutate = useMutateDFState(); const rowFieldName = React.useMemo(() => removeAfterLastDot(props.name), [props.name]); const handleChange = React.useCallback( (newType: OnChangeValue) => { props.input.onChange(newType); if (typeof newType !== 'string') { return; } const nameMutationField = `${rowFieldName}.${newType}`; mutate({ spec: {[nameMutationField]: {...MUTATION_VARIANTS[newType].spec}}, values: {[nameMutationField]: MUTATION_VARIANTS[newType].values}, errors: {[nameMutationField]: MUTATION_VARIANTS[newType].errors}, }); }, [props.input, rowFieldName, mutate], ); const newProps = { ...props, input: { ...props.input, onChange: (value: OnChangeValue) => handleChange(value), }, }; return