npm package: @proveanything/smartlinks-form-renderer
Description: SmartLinks form renderer and editor — render forms from JSON schema or build them with the visual editor
Author: Glenn Shoosmith
Latest version: 0.2.0

# SmartLinks Forms

A flexible React package for rendering and editing forms from JSON schema — with conditional logic, validation, table fields, storage configuration, and a visual editor.

## Installation

```bash
npm install @proveanything/smartlinks-forms
```

## Subpath Imports

```tsx
// Full package (renderer + editor + types)
import { SchemaFormRenderer, useFormEditorState } from '@proveanything/smartlinks-forms';

// Renderer only (lean, public-facing)
import { SchemaFormRenderer } from '@proveanything/smartlinks-forms/renderer';

// Editor only (admin-facing)
import { useFormEditorState, useFormEditorActions } from '@proveanything/smartlinks-forms/editor';
```

---

## Form Renderer

### Basic Usage

```tsx
import { SchemaFormRenderer, SchemaFormConfig } from '@proveanything/smartlinks-forms';

const formConfig: SchemaFormConfig = {
  title: "Contact Form",
  description: "Fill in your details",
  schema: {
    type: "object",
    properties: {
      name: { type: "string", title: "Full Name" },
      email: { type: "string", format: "email", title: "Email" },
    },
    required: ["name", "email"]
  },
  uiSchema: {
    name: { "ui:placeholder": "Enter your full name" },
    email: { "ui:placeholder": "you@example.com" },
  },
  storage: { name: "public", email: "private" },
  settings: {
    allowMultipleSubmissions: false,
    requireAuthentication: false,
    showProgressBar: false,
    submitButtonText: "Submit",
    successMessage: "Form submitted successfully!"
  },
  styling: { theme: "default", primaryColor: "#007bff", backgroundColor: "#ffffff" },
  fieldOrder: ["name", "email"]
};

function MyForm() {
  return (
    <SchemaFormRenderer
      config={formConfig}
      onSubmit={(data) => console.log('Submitted:', data)}
      initialData={{ name: "John Doe" }}
    />
  );
}
```

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `config` | `SchemaFormConfig` | Required | The form configuration object |
| `onSubmit` | `(data: Record<string, any>) => void` | Required | Called on valid submission |
| `isSubmitting` | `boolean` | `false` | Disables submit button |
| `className` | `string` | `""` | Additional CSS classes |
| `initialData` | `Record<string, any>` | `{}` | Pre-populate fields (overrides schema defaults) |
| `components` | `object` | `{}` | Custom component overrides |

---

## Field Types & Formats

### String Fields
- `text` (default), `email`, `textarea`, `select`, `radio`, `checkboxes`, `multiselect`, `combobox`, `file`, `date`, `date-time`

### Boolean Fields
- `checkbox` (default), `switch`

### Number / Integer Fields
- Standard number input with `minimum`/`maximum` validation

### Table Fields
- Dynamic table with typed columns, row add/remove, `minRows`/`maxRows`

---

## Validation

The renderer enforces validation **at submit time** with inline error messages. Both HTML attributes and JavaScript-level checks are used.

### Supported Constraints

| Constraint | Applies To | Description |
|------------|-----------|-------------|
| `required` | All types | Field must have a value (set via `schema.required[]`) |
| `minLength` | `string` | Minimum character length |
| `maxLength` | `string` | Maximum character length |
| `pattern` | `string` | Regex pattern the value must match |
| `minimum` | `number`, `integer` | Minimum numeric value |
| `maximum` | `number`, `integer` | Maximum numeric value |
| `minRows` | `table` | Minimum number of table rows |
| `maxRows` | `table` | Maximum number of table rows |

### Example

```tsx
{
  age: {
    type: "integer",
    title: "Age",
    minimum: 18,
    maximum: 120
  },
  bio: {
    type: "string",
    format: "textarea",
    title: "Bio",
    minLength: 10,
    maxLength: 500
  }
}
```

---

## Enum Labels (`enumNames`)

Use `enumNames` as a parallel array to `enum` for human-readable labels:

```tsx
{
  country: {
    type: "string",
    format: "select",
    title: "Country",
    enum: ["us", "gb", "de"],
    enumNames: ["United States", "United Kingdom", "Germany"]
  }
}
```

`enum[i]` is the stored value, `enumNames[i]` is the display label. Works with `select`, `radio`, `checkboxes`, `multiselect`, and `combobox` formats.

---

## UI Schema

Control presentation without changing the data schema:

```tsx
uiSchema: {
  bio: {
    "ui:placeholder": "Tell us about yourself...",
    "ui:help": "This will be shown on your profile",
    "ui:options": { rows: 6 },
    "ui:disabled": true  // Read-only field
  },
  avatar: {
    "ui:options": { accept: "image/*" }
  }
}
```

### Available UI Properties

| Property | Type | Description |
|----------|------|-------------|
| `ui:placeholder` | `string` | Placeholder text |
| `ui:help` | `string` | Help text below the field |
| `ui:disabled` | `boolean` | Disables the field (read-only) |
| `ui:widget` | `string` | Override auto-detected widget |
| `ui:options.rows` | `number` | Textarea row count |
| `ui:options.accept` | `string` | File input accept filter |
| `ui:options.label` | `boolean` | Show/hide label |

---

## Conditional Logic

Show/hide fields based on other field values:

```tsx
{
  preferences: {
    type: "string",
    format: "select",
    title: "Preferences",
    enum: ["daily", "weekly"],
    conditions: [
      { targetFieldId: "subscribe", operator: "equals", value: true }
    ],
    showWhen: "all" // or "any"
  }
}
```

### Operators

| Operator | Description | Needs `value`? |
|----------|-------------|---------------|
| `equals` | Strict equality | Yes |
| `not_equals` | Not equal | Yes |
| `contains` | String contains | Yes |
| `not_contains` | String doesn't contain | Yes |
| `greater_than` | Numeric comparison | Yes |
| `less_than` | Numeric comparison | Yes |
| `is_empty` | Value is empty/null | No |
| `is_not_empty` | Value has content | No |
| `is_true` | Value is `true` | No |
| `is_false` | Value is `false`/`null`/`undefined` | No |

---

## Field Ordering

```tsx
const config: SchemaFormConfig = {
  fieldOrder: ["name", "email", "phone", "address"],
  // ...
};
```

If `fieldOrder` is not specified, fields appear in `schema.properties` insertion order.

---

## Table Fields

```tsx
{
  items: {
    type: "table",
    title: "Line Items",
    columns: [
      { id: "name", title: "Item", type: "string", required: true },
      { id: "qty", title: "Quantity", type: "number", required: true },
      { id: "active", title: "Active", type: "boolean" },
      { id: "date", title: "Added", type: "date" }
    ],
    minRows: 1,
    maxRows: 10
  }
}
```

---

## Custom Components

Override default UI primitives:

```tsx
<SchemaFormRenderer
  config={formConfig}
  onSubmit={handleSubmit}
  components={{
    Button: MyButton,
    Input: MyInput,
    Textarea: MyTextarea,
    Select: MySelect,
    Checkbox: MyCheckbox,
    RadioGroup: MyRadioGroup,
    Switch: MySwitch,
    Label: MyLabel,
    TableInput: MyTableInput,
  }}
/>
```

---

## Storage Configuration

Define privacy levels per field:

```tsx
storage: {
  name: "public",     // Publicly visible
  email: "private",   // Private to user
  ssn: "proof",       // Cryptographic proof only
  notes: "personal"   // Personal notes
}
```

---

## Form Editor (Admin)

The editor hooks provide headless state management for building a form editor UI:

```tsx
import { useFormEditorState, useFormEditorActions } from '@proveanything/smartlinks-forms/editor';

function FormEditor() {
  const editor = useFormEditorState({ isCreating: true });
  const actions = useFormEditorActions({
    formConfig: editor.formConfig,
    setFormConfig: editor.setFormConfig,
  });

  // editor.addField(), editor.updateField(), editor.removeField(), etc.
  // actions.exportSchema(), actions.importSchema()
}
```

---

## Configuration Reference

### SchemaFormConfig

| Property | Type | Description |
|----------|------|-------------|
| `id` | `string?` | Unique identifier |
| `title` | `string` | Form title |
| `description` | `string` | Form description |
| `schema` | `object` | JSON Schema definition |
| `uiSchema` | `object` | UI configuration |
| `storage` | `object` | Per-field storage levels |
| `settings` | `object` | Form behavior settings |
| `styling` | `object` | Theme and colours |
| `fieldOrder` | `string[]?` | Field display order |

---

## TypeScript

```tsx
import type {
  SchemaFormConfig,
  SchemaFormField,
  SchemaFormRendererProps,
  SchemaFormUIConfig,
  SchemaFormFieldCondition,
  TableColumn,
  FormEditorState,
  FormEditorActions,
} from '@proveanything/smartlinks-forms';
```

## License

MIT