@gsb-core/react-forms
v0.1.0
Published
GSB react-forms package
Readme
@gsb-core/react-forms
Schema-driven forms for GSB entity definitions. A property is rendered by the control its widget template declares — not by a guess from its data type.
Usage
import { GsbForm, GsbFormProvider } from "@gsb-core/react-forms";
<GsbFormProvider
environment={{
languages: ["en", "tr"],
defaultLanguage: "en",
uploadFile: (file) => storage.upload(file),
}}
>
<GsbForm
entityDefName="GsbUser"
entityId={rowId}
formMode="update"
onSubmit={(values) => entityService.save(values)}
/>
</GsbFormProvider>;FormGenerator is kept as a deprecated wrapper around GsbForm for existing
call sites, including its string-keyed fieldRenderers map.
How a control is chosen
resolveControl(property) walks the schema in this order and stops at the first
answer:
- a caller override (
controlOverrides={{ body: "richText" }}) property.controlComponent/multiControlComponentproperty.definition.defaultControlComponent/…Multiproperty.defUiCtrlNameproperty.definition.name— the mapping every tenant ships (Password→ password control,MlText→ multilingual editor,timeSpan→ duration, …)dataType+refType+isMultiple
Widgets that share a backend component are split by the same signal the backend
uses: GsbEditControlComponent by inputType (password, number, color,
email), GsbMonacoCodeEditorComponent by language, and
GsbEnumControlComponent by property.isMultiple — the live Multi Toggle
template branches on exactly that.
A widget from a non-input family (container, chart, page) is not treated as a
value control; resolution continues past it, and if nothing else fits the field
renders UnsupportedControl, which names the mismatch rather than hiding it
behind a text box.
Covered widgets
Every widgetBase: 10 (value) and widgetBase: 3 (mapped list) template has a
control:
| Backend component | Control kind |
| ------------------------------------- | -------------------------------------------------- |
| GsbEditControlComponent | text / password / number / color / email |
| GsbMultilineTextControlComponent | multilineText |
| GsbMlEditControlComponent | multilangText |
| GsbContentControlComponent | richText |
| GsbMlContentControlComponent | multilangRichText |
| GsbMonacoCodeEditorComponent | code (language as variant) / json |
| GsbBoolControlComponent | boolean |
| GsbDateControlComponent | date / datetime |
| GsbTimespanComponent | timespan |
| GsbEnumControlComponent | enumSelect / enumMultiToggle |
| GsbRefControlComponent | reference |
| GsbEnumeratedRefControlComponent | referenceList |
| GsbChipsAutoCompleteMappedComponent | multiReference (chips) |
| GsbMappedDataTableComponent | multiReference (table) |
| GsbEntityDataCardMappedComponent | multiReference (cards) |
| GsbIconThumbSelectorComponent | multiReference (iconThumb) |
| GsbIconControlComponent | icon |
| GsbImageComponent | image |
| GsbFileUploadComponent | file |
| GsbMultiFileUploadComponent | multiFile |
| GsbCategoryPickerComponent | categoryPicker |
| GsbPropertySelectorComponent | propertySelector |
| GsbValuePickerControlComponent | valuePicker |
| GsbValidationErrorsComponent | validationErrors |
Replacing a control
The registry is the seam. code and json ship as dependency-free source
editors; swap in Monaco, or a WYSIWYG for richText, without touching the
resolver:
const registry = createControlRegistry({
code: MonacoControl,
json: MonacoJsonControl,
});
<GsbForm entityDefName="GsbFunction" registry={registry} />;Validation
buildFieldSchema derives a zod schema from the property metadata GSB already
enforces: required, max length, regex, email format, integer-ness and bounds.
Client validation is a usability layer — the backend re-validates on save.
Notes
- Rich text previews render inside an iframe with
sandbox="", so stored markup cannot execute against the app. - Multilingual values are stored as a map keyed by language code; a legacy plain string is adopted as the default language's value.
- A timespan is a scale-0 decimal counted in whole seconds.
