npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@msbci/form-renderer

v1.3.2

Published

React form renderer — themeable, data-source-aware, zero UI dependency

Readme

@msbci/form-renderer

npm version license peer dep

React form renderer — themeable, data-source-aware, zero UI dependency. Renders forms defined by @msbci/form-core schemas.

Installation

npm install @msbci/form-renderer @msbci/form-core

Minimal Usage

import { FormRenderer } from '@msbci/form-renderer'

<FormRenderer
  formSchema={myFormDefinition}
  onSubmit={(responses) => api.saveSubmission(responses)}
  mode="fill"
  labels={{ submit: 'Submit', next: 'Next', previous: 'Previous' }}
/>

Theme System

Inject your own colors, typography, and entire React components:

import type { IMosobiTheme } from '@msbci/form-renderer'

const myTheme: Partial<IMosobiTheme> = {
  colors: {
    primary: '#3B82F6',
    secondary: '#8B5CF6',
    background: '#FFFFFF',
    surface: '#F9FAFB',
    error: '#EF4444',
    warning: '#F59E0B',
    success: '#10B981',
    text: '#111827',
    textSecondary: '#6B7280',
    border: '#E5E7EB',
  },
  // Override built-in components with your design system
  components: {
    Button: MyCustomButton,
    Input: MyCustomInput,
    Select: MyCustomSelect,
  },
}

<FormRenderer formSchema={schema} theme={myTheme} />

If theme.components.Select is provided, it replaces the default <select>. Otherwise, the built-in HTML component is used.

DataSource Connectors

Connect selection fields to live data with inter-field filtering:

import type { IDataSourceConnector } from '@msbci/form-core'

const dataSources: Record<string, IDataSourceConnector> = {
  countries: {
    id: 'countries',
    name: 'Countries',
    fetch: async ({ search }) => api.getCountries({ search }),
  },
  cities: {
    id: 'cities',
    name: 'Cities (filtered by country)',
    fetch: async ({ dependsOn }) =>
      api.getCities({ countryId: dependsOn?.countryId }),
  },
}

<FormRenderer
  formSchema={schema}
  dataSources={dataSources}
  theme={myTheme}
  onSubmit={handleSubmit}
/>

When a dependency value changes, the connected field automatically re-fetches with 150ms debounce and clears its current selection.

Modes

| Mode | Description | |------|-------------| | fill | Default — user can enter data and submit | | readonly | All fields disabled, no submit button | | review | Read-only with submission data pre-loaded |

<FormRenderer formSchema={schema} initialResponses={savedData} mode="readonly" />

Field Registry

MOSOBI Forms ships with built-in field components for all standard variable types. For custom or domain-specific fields, use the field registry to register your own components.

Built-in types

text · textarea · number · date · datetime · time · select · multiselect ·
checkbox · radio · file · image · email · gps · rating · calculated ·
hidden · label · panel · richtext · listradio · photo

Behind the scenes 21 variable types are mapped to 13 components (e.g. text/textarea/email/calculated/hidden/label all share TextField; date/datetime/time share DateField; panel, richtext, listradio and photo each have a dedicated display-only or input component added in v1.1.0).

Registering a custom component

import { useState } from 'react'
import {
  FormRenderer,
  registerFieldComponent,
  type FieldProps,
} from '@msbci/form-renderer'
import { useFormContext } from '@msbci/form-renderer'

// 1. Create a custom field component
function PhoneField({ variable, instanceNumber }: FieldProps) {
  const { setValue, getValue, mode } = useFormContext()
  const value = (getValue(variable.code, instanceNumber) as string) ?? ''
  const readOnly = mode === 'readonly' || variable.isReadonly

  const sanitize = (input: string) => input.replace(/[^0-9+\s-]/g, '')

  return (
    <input
      type="tel"
      value={value}
      onChange={(e) => setValue(variable.code, sanitize(e.target.value), instanceNumber)}
      placeholder={variable.placeholder ?? '+1 555 0100'}
      readOnly={readOnly}
      disabled={readOnly}
      style={{ width: '100%', padding: 8, borderRadius: 4, border: '1px solid #ccc' }}
    />
  )
}

// 2. Register it once at app initialization (e.g. in your entry file)
registerFieldComponent('phone', PhoneField)

// 3. Use it in a schema like any other type
const schema = {
  id: 'F', code: 'CONTACT', name: 'Contact', version: '1.0.0', isPublished: true,
  pages: [
    {
      id: 'P1', code: 'P1', name: 'Contact details', order: 0, isRepeatable: false,
      variables: [
        {
          id: 'mobile', code: 'MOBILE', name: 'Mobile phone',
          // The custom type registered above
          type: 'phone' as const,
          order: 0, isRequired: true, isReadonly: false, isHidden: false,
        },
      ],
      rosters: [],
    },
  ],
}

function MyApp() {
  return <FormRenderer formSchema={schema} onSubmit={(data) => console.log(data)} />
}

Overriding a built-in type

registerFieldComponent overwrites whatever component is registered for the same key. Useful for replacing a built-in with a richer alternative:

import { registerFieldComponent, type FieldProps } from '@msbci/form-renderer'

function FancyDateField(props: FieldProps) {
  // Wrap your favourite date picker library here
  return <MyFavouriteDatePicker {...props} />
}

registerFieldComponent('date', FancyDateField)

To restore a built-in, re-register the original component (re-imported from the package) or import it from @msbci/form-renderer directly.

FieldProps interface

export interface FieldProps {
  /** The variable schema being rendered (code, type, options, validation, …). */
  variable: IFormVariable
  /** 1-based instance index when the variable lives in a repeatable page or roster row. */
  instanceNumber?: number
}

To access the surrounding form state inside a custom field, use the form context hook:

import { useFormContext } from '@msbci/form-renderer'

function MyField({ variable, instanceNumber }: FieldProps) {
  const { getValue, setValue, mode, errors } = useFormContext()
  const value = getValue(variable.code, instanceNumber)
  const error = errors.find((e) => e.variableCode === variable.code)
  // … render however you like
}

TypeScript support

FieldProps is the canonical type for any field component:

import type { FieldProps } from '@msbci/form-renderer'
import type { ComponentType } from 'react'

const PhoneField: ComponentType<FieldProps> = ({ variable, instanceNumber }) => {
  // …
  return null
}

If your custom type needs to be statically known to TypeScript (e.g. for autocomplete in schemas), you can broaden VariableType via module augmentation in your app:

// types/msbci-form-core.d.ts
declare module '@msbci/form-core' {
  type VariableType = 'phone' | 'currency' // your additions
}

Or simply use a built-in type (e.g. 'text') and discriminate in your component via variable.metadata.

Notes

  • Registration is global. Call registerFieldComponent once at app startup; subsequent calls for the same key overwrite the previous binding.
  • Order of imports matters. Register before mounting <FormRenderer> so the renderer sees your binding.
  • Built-in field components are also re-exported (TextField, NumberField, etc.) and can be composed inside your custom one.

Exported Components

FormRenderer · FormGroup · RosterRenderer · VariableRenderer · FieldWrapper · TextField · NumberField · DateField · CheckboxField · RadioField · SelectField · FormProgress · FormNavigation · FormSummary · ValidationErrorsModal

All components are individually importable for advanced layouts.

v1.3.2

  • FileField multi-upload — un seul composant pour type: 'file' et type: 'image'. Mode single intact (maxFiles === 1 ou absent → valeur string legacy), mode multi (maxFiles > 1) → valeur string[]. Upgrade transparent : une valeur string héritée en mode multi est affichée comme [value].
  • UX multi-fichiers : drop-zone native (drag & drop sans dépendance externe), liste cliquable des fichiers avec nom + taille estimée + bouton suppression, masquage automatique de la drop-zone à maxFiles atteint.
  • Galerie image : grille de miniatures (auto-fill, minmax(120px, 1fr)), lightbox au clic (overlay sombre + <img> centré, fermeture par × ou Escape), bouton suppression sur chaque miniature.
  • Camera capture : si imageConfig.allowCamera, un bouton dédié « 📷 Prendre une photo » déclenche un input séparé avec capture="environment" pour l'accès direct à la caméra mobile.
  • Encodage parallèle : Promise.all(files.map(fileToBase64)) — sélectionner 10 fichiers ne séquentialise pas les FileReader.
  • Override d'i18n : nouvelles props labels.file / labels.image sur FormRendererProps, propagées via le FormContext (fieldLabels). Défauts FR + EN conservés.
  • Nouveaux types publics : IFieldLabels, IFileFieldLabels, IImageFieldLabels.
  • 14 nouveaux tests RTL (fileFieldMulti.test.tsx) — 189 tests passants.

v1.3.0

  • Compatible IFormPage.items[] (variables et rosters unifiés)
  • FormGroup / FormSummary lisent désormais via getPageVariables / getPageRosters — tolérant aux schémas legacy
  • Aucun changement cassant — l'API publique de FormRenderer reste identique
  • Bundle inchangé, 169 tests toujours verts

v1.2.0

  • Prop lang + onLangChange sur FormRenderer
  • LangProvider, useLang(), LangSelector (exportés publiquement)
  • Rendu multilingue sur tous les composants (champs, options, pages, rosters)
  • Stratégie de détection : prop / browser / selector / auto
  • Sélecteur de langue intégré (strategy='selector')
  • Defaults UI en français (Soumettre, Suivant, Précédent, ...)
  • 12 nouveaux tests i18n (169 total)

What's new in v1.1.0

  • 4 new field components added to the registry: PanelField (display-only bordered section), RichTextField (interpolated HTML with regex-based sanitization), ListRadioField (Yes/No radios per option) and PhotoCaptureField (mobile rear-camera via native capture="environment").
  • RosterRenderer now supports two dynamic row layouts: rosterType: 'collection' (compact card stack) and 'collection_extend' (extended table with column headers), both with add/remove based on IFormRoster.collectionConfig.
  • Responses passed to onSubmit and onDraftSave are automatically enriched with display labels, page/roster context and interpolated variable labels (via the new @msbci/form-core/enrichResponsesWithContext).
  • FieldWrapper now bypasses label/error chrome for display-only types (panel, richtext).
  • FieldProps type is now exported publicly.
  • Built-in type count: 17 → 21 with the 4 additions above. The Field Registry section was expanded with a complete custom-component example.

License

Copyright (c) 2026 MOSOBI — All rights reserved. Commercial license required. Contact: [email protected]