Библиотека react‑jsonschema‑form (RJSF) предназначена для автоматической генерации форм на основе JSON‑схемы. Вы задаёте схему, а RJSF берёт на себя остальное: отображение полей ввода, валидацию и обработку данных. Это удобный и простой в использовании инструмент, тем не менее, у библиотеки есть определённые ограничения. Одно из них — отсутствие поддержки многоколоночных макетов «из коробки».
В этой статье я покажу, как можно добавить гибкость в структуру формы, используя кастомные шаблоны.
Проблема
По умолчанию RJSF располагает все поля формы в одну колонку, растягивая их на всю ширину контейнера. Это может быть приемлемо для простых форм, но в реальных проектах зачастую возникает необходимость в более сложной структуре.
Как это выглядит без кастомного лэйаута
Допустим, у нас есть JSON-схема, описывающая простую форму:
{ "title": "Заполните информацию о пользователе", "type": "object", "required": ["name", "username"], "properties": { "name": { "type": "string", "title": "ФИО" }, "username": { "type": "string", "title": "Логин" }, "email": { "type": "string", "title": "E-mail" }, "telephone": { "type": "string", "title": "Телефон" }, "telegram": { "type": "string", "title": "Telegram" }, "date": { "type": "string", "format": "date", "title": "Дата рождения" }, "bio": { "type": "string", "title": "О себе" }, "city": { "type": "string", "title": "Город" } } }
Если использовать её без дополнительных настроек, форма будет выглядеть следующим образом:

Как видно, все поля выстроены в один длинный список, что далеко не всегда удобно.
Для демонстрации я буду использовать RJSF совместно с Ant Design, но предложенный подход можно адаптировать для любой библиотеки компонентов с минимальными изменениями.
Решение
Чтобы реализовать многоколоночную структуру, сначала определимся со схемой, которая будет описывать расположение полей:
{ "sections": [ // массив секций { "id": "string", "header": { // заголовок секции "title": "string", "align": "string", "heading_size": "number" }, "blocks": [ // массив блоков в секции { "id": "string", "fields": { // список полей в блоке "fieldName": { "width": "number" // Ширина поля относительно блока. Не обязательный параметр, если не указана будет во всю ширину } }, "width": "number" // Ширина блока. Не обязательный параметр, если не указана - 100%/число блоков в секции } ] } ] }
RJSF поддерживает кастомные шаблоны (templates), которые позволяют изменять макет формы под свои нужды. Я буду использовать ObjectFieldTemplate, который отвечает за рендеринг контейнера для всех полей объекта. Он позволяет переопределять стандартное расположение элементов, задавая кастомную разметку.
Код ObjectFieldTemplate.tsx
import React, { useState, useEffect, useMemo } from 'react'; import { Typography, Col } from 'antd'; import './ObjectFieldTemplate.css'; type PropertyType = { content: any; name: string; }; function ObjectFieldTemplate(props: any) { // properties нужны нам для вывода в конце формы полей, которые мы могли забыть перечислить в layout const [properties, setProperties] = useState<Record<string, PropertyType>>( {} ); const layout = props.formContext.getLayout(); useEffect(() => { const obj = (props.properties || []).reduce( (acc: any, curr: any) => ({ ...acc, [curr.name]: { content: curr.content, name: curr.name }, }), {} ); setProperties(obj); }, [props.properties]); const gridLayout = useMemo( () => layout ? layout.sections.map((section: any) => { return ( <div key={section.id}> {section.header && ( <Typography.Title level={section.header.heading_size || 4} style={{ textAlign: section.header.align || 'center', }} > {section.header.title} </Typography.Title> )} <div className="layout__section"> {section.blocks.map((block: any) => { return ( <div key={`${section.id}-${block.id}`} className="layout__block" style={{ width: `${ block.width ? 100 / (24 / block.width) : 100 }%`, }} > {Object.keys(block.fields).map((el: any) => { const field = properties[el]; delete properties[el]; return field ? ( <Col key={field.name} data-field={field.name} span={block.fields[el].width || 24} style={{ padding: '0 8px' }} > {field.content} </Col> ) : null; })} </div> ); })} </div> </div> ); }) : null, [properties, layout] ); return ( <div> {props.title ? ( <Typography.Title level={3}>{props.title}</Typography.Title> ) : null} {props.description ? ( <Typography.Text>{props.description}</Typography.Text> ) : null} {gridLayout} {/* поля, которые могли быть не указаны в layout */} {props.properties.map((el: any) => properties[el.name] ? ( <div key={el.name} style={{ padding: '0 8px' }}> {el.content} </div> ) : null )} </div> ); } export default ObjectFieldTemplate;
Стили для ObjectFieldTemplate
.layout { display: flex; flex-wrap: wrap; }
Вот как будет выглядеть лэйаут для моей формы:
{ "sections": [ { "id": "section1", "header": { "title": "Основная информация", "align": "center", "heading_size": 4 }, "blocks": [ { "id": "block1", "fields": { "name": { "width": 16 }, "username": { "width": 8 }, "telegram": { "width": 8 }, "email": { "width": 8 }, "telephone": { "width": 8 } } } ] }, { "id": "section2", "header": { "title": "Общие сведения", "align": "center" }, "blocks": [ { "id": "block2", "fields": { "date": {}, "city": {} }, "width": 8 }, { "id": "block3", "fields": { "bio": {} }, "width": 16 } ] } ] }
После применения кастомного шаблона форма выглядит совершенно по-другому:

В данном примере я использую Ant Design, в котором грид-система построена на 24 колонках. Для каждого элемента формы задаётся ширина в рамках 24-колоночной сетки и мы можем легко менять количество колонок для каждого блока, управляя этим параметром.
Итог
Используя react-jsonschema-form совместно с кастомными шаблонами, мы можем значительно расширить его возможности. Теперь наша форма больше не ограничена одной колонкой, а её макет можно легко настроить, изменяя всего лишь схему лэйаута.
ссылка на оригинал статьи https://habr.com/ru/articles/884862/
Добавить комментарий