Skip to content

Latest commit

 

History

History
239 lines (191 loc) · 6.4 KB

File metadata and controls

239 lines (191 loc) · 6.4 KB
title Plugin Form

import { InteractiveDemo } from '@/app/components/InteractiveDemo'; import { PluginLoader } from '@/app/components/PluginLoader';

Advanced form components with validation, multi-step forms, and comprehensive field support.

Installation

npm install @object-ui/plugin-form

<PluginLoader plugins={['form']}>

Interactive Examples

Features

  • Form Builder - Create complex forms from schemas
  • Validation - Built-in validation with custom rules
  • Multi-Step Forms - Wizard-style multi-step forms
  • Field Types - All standard HTML5 input types
  • Form State - Automatic state management
  • Error Handling - Field-level and form-level errors

Schema API

Form

{
  type: 'form',
  fields: FormField[],
  submitLabel?: string,
  cancelLabel?: string,
  onSubmit?: (data) => void,
  onCancel?: () => void,
  className?: string
}

Form Field

interface FormField {
  name: string;
  type: string;                   // 'input', 'select', 'checkbox', etc.
  label: string;
  placeholder?: string;
  required?: boolean;
  validation?: ValidationRule[];
  defaultValue?: any;
  disabled?: boolean;
  className?: string;
}

Column width of a sectioned form

A sectioned form renders as ONE grid. The form view's columns (spec FormView.columns) sets how wide that grid is; a section's columns sets how densely that section fills it. The view's value wins; without it the grid takes the widest section's density, and without either it is single-column.

ObjectForm (simple), ModalForm, TabbedForm and SplitForm all resolve it that way, so the same metadata lays out identically in every host. The grid is applied to the field container inside the form, never wrapped around the <form>.

Tabbed field layout (fieldTabs)

A sectioned form stays one form: declare the tabs on it and the renderer distributes the fields into panels, instead of rendering a form per section (which strands every section but the first outside the submit, and lets an inactive tab unmount along with its values).

{
  type: 'form',
  fields: [/* every tab's fields, in one flat list */],
  fieldTabs: [
    { key: 'basics', label: 'Basics', fields: ['subject', 'status'] },
    { key: 'detail', label: 'Detail', description: 'Anything else', fields: ['description'] },
  ],
  defaultFieldTab?: 'basics',       // defaults to the first tab
  fieldTabsPosition?: 'top',        // 'top' | 'bottom' | 'left' | 'right'
}
  • Panels are force-mounted and only CSS-hidden, so a tab the user leaves keeps its values and its validation.
  • A failed submit activates the tab holding the first offending field and marks every tab with a rejected field — client rules and server fields[] alike.
  • Fields no tab claims render above the tab strip rather than disappearing.
  • Needs at least two tabs; ignored when the form uses children.

ModalForm (contentLayout: 'tabbed') and TabbedForm build on this.

Split field layout (fieldPanes)

Side-by-side panels follow the same rule: the <form> wraps the whole panel group and each pane holds only fields, so one react-hook-form instance spans the divider.

{
  type: 'form',
  fields: [/* every pane's fields, in one flat list */],
  fieldPanes: [
    { key: 'primary', fields: ['subject'], defaultSize: 50 },
    { key: 'secondary', fields: ['status', 'priority'], defaultSize: 50, minSize: 20 },
  ],
  fieldPanesOrientation?: 'horizontal',   // 'horizontal' | 'vertical'
  fieldPanesResizable?: true,             // false pins the divider
}
  • A submit from anywhere carries every pane's values, and a field rule in one pane can read a field in another — neither works with a form per panel.
  • defaultSize / minSize are percentages of the group.
  • Each pane is its own @container, so a multi-column group collapses as the divider is dragged narrower.
  • Fields no pane claims render above the panel group rather than disappearing.
  • Needs at least two panes; ignored when the form uses children or fieldTabs.

SplitForm builds on this: section 1 becomes the primary pane, the rest stack in the secondary one behind inline section headers.

Usage

Auto-registration (Side-effect Import)

import '@object-ui/plugin-form';

Manual Registration

import { formComponents } from '@object-ui/plugin-form';
import { ComponentRegistry } from '@object-ui/core';

Object.entries(formComponents).forEach(([type, component]) => {
  ComponentRegistry.register(type, component);
});

Examples

Form with Validation

{
  "type": "form",
  "fields": [
    {
      "name": "username",
      "type": "input",
      "label": "Username",
      "required": true,
      "validation": [
        { "type": "minLength", "value": 3, "message": "Min 3 characters" },
        { "type": "maxLength", "value": 20, "message": "Max 20 characters" }
      ]
    },
    {
      "name": "password",
      "type": "input",
      "inputType": "password",
      "label": "Password",
      "required": true,
      "validation": [
        { "type": "minLength", "value": 8, "message": "Min 8 characters" }
      ]
    }
  ],
  "submitLabel": "Sign Up"
}

Multi-Step Form

{
  "type": "multi-step-form",
  "steps": [
    {
      "title": "Personal Info",
      "fields": [
        { "name": "firstName", "type": "input", "label": "First Name", "required": true },
        { "name": "lastName", "type": "input", "label": "Last Name", "required": true }
      ]
    },
    {
      "title": "Contact Info",
      "fields": [
        { "name": "email", "type": "input", "inputType": "email", "label": "Email", "required": true },
        { "name": "phone", "type": "input", "inputType": "tel", "label": "Phone" }
      ]
    }
  ]
}

Field Types

The plugin supports these field types:

  • input - Text, email, password, number, tel, url, etc.
  • textarea - Multi-line text input
  • select - Dropdown select
  • checkbox - Single checkbox
  • radio-group - Radio button group
  • date-picker - Date selection
  • file-upload - File upload

TypeScript Support

import type { FormSchema, FormField } from '@object-ui/plugin-form';

const loginForm: FormSchema = {
  type: 'form',
  fields: [...],
  submitLabel: 'Sign In'
};

License

MIT