@infeya/data-model
v1.1.0
Published
Enterprise-grade declarative form and multi-step wizard orchestration engine for React
Maintainers
Readme
@infeya/data-model
Enterprise-grade declarative form and multi-step wizard orchestration engine for React.
@infeya/data-model is an all-in-one UI engine that simplifies building complex form and wizard flows in modern React applications. It unifies modal/offcanvas/inline layouts, per-step isolated data stores, multi-stage validation strategies, deterministic asynchronous action pipelines (with live progress tracking, abort capability, and timeout guards), and pluggable design-system adapters (Bootstrap 5, Tailwind CSS, and Material UI).
📑 Table of Contents
- 📦 Installation
- ✨ Core Highlights
- 🏗️ Architectural Overview
- 🚀 Quick Start
- ⚙️ Props API Reference
- 🪝 React Hooks API
- 🔀 Step Navigation & Policies
- 🛡️ Validation Engine & Adapters
- 🔧 Asynchronous Action Pipeline
- 💬 Confirmation Dialogs & Notifications
- 🎨 Multi-Theme UI Adapters (Bootstrap, Tailwind, MUI)
- 📐 Header & Footer Customization
- 🧩 Constants & Enums Reference
- TypeScript Support
- 📄 License
📦 Installation
Install via npm:
npm install @infeya/data-model @infeya/api-centerOr via yarn:
yarn add @infeya/data-model @infeya/api-centerOr via pnpm:
pnpm add @infeya/data-model @infeya/api-centerPeer Dependencies: Ensure
reactandreact-dom(>= 16.8.0,17.x,18.x, or19.x) are installed in your project.
✨ Core Highlights
- 🚀 Declarative Orchestration: Define complex UI forms, multi-step wizards, and drawers using clean, declarative JavaScript objects.
- 🪝 Modern React Hooks: Full functional component support via
useDataModel()anduseDataModelForm(). - 🔀 Conditional Step Branching: Dynamically show, hide, or skip wizard steps based on real-time form data using
when: (formData) => boolean. - 🛡️ Async Step Transition Guards: Protect step advances with
beforeNext: async (stepData) => ...(e.g. server verification, availability checks). - 🗂️ Multi-Step Wizards: Built-in support for linear, open, and locked navigation modes (
STEP_NAV_MODE), step skipping, step validation strategies, and automatic review/overview summary screens. - ⚡ Zero-Bloat Async Validation Engine: First-class async schema validation with auto-detection for Zod, Yup, Joi, Valibot (Standard Schema v1), and Ajv.
- ♿ Accessibility & Keyboard Navigation: Native
Escapekey close support, configurablebackdropmodes, and standard ARIA dialog roles. - 🛡️ Fail-Safe Action Pipeline: Deterministic 4-stage execution (
beforeProcess$\rightarrow$ API $\rightarrow$ Custom logic $\rightarrow$afterProcess) with automatic timeout guards and double-click submission locking. - 🛑 Native Abort Signal Cancellation: In-flight network request cancellation powered by
@infeya/api-center. - 📊 Real-Time Progress Tracking: Track upload/download and action progress with animated progress bars and button loaders.
- 📐 3 Flexible Layouts: Render inline directly on a page, inside a Modal dialog, or as an Offcanvas slide-out drawer.
- 🔌 Decoupled & Pluggable UI: Built-in support for Bootstrap 5, Tailwind CSS, and Material UI (MUI), plus full support for custom CSS class tokens.
🏗️ Architectural Overview
graph TD
A[Component Props & Configuration] --> B[DataModel Orchestrator]
B --> C[DataModelProvider Context]
C --> D1[StepController / StepRegistry]
C --> D2[StepDataStore]
C --> D3[ValidationRunner]
C --> D4[ActionResolver]
C --> D5[OpenCloseController]
C --> D6[ProgressController]
D1 --> E[Layout Resolver]
D2 --> E
D3 --> E
D4 --> E
D5 --> E
D6 --> E
E --> F1[InlineLayout]
E --> F2[ModalLayout]
E --> F3[OffcanvasLayout]
F1 --> G[Body Renderer]
F2 --> G
F3 --> G
G --> H1[Single FormRenderer]
G --> H2[StepRenderer Wizard]
H1 --> I[UI Theme Adapter]
H2 --> I
I --> J1[Bootstrap 5]
I --> J2[Tailwind CSS]
I --> J3[Material UI]
I --> J4[Custom Theme]🚀 Quick Start
1. Basic Modal Form
import React, { useState } from 'react';
import Datamodel, { LAYOUT_TYPES } from '@infeya/data-model';
// Form body component using standard React class
class UserForm extends React.Component {
state = {
name: this.props.data?.name || '',
email: this.props.data?.email || '',
};
getForm() {
return this.state;
}
setErrors(errors) {
this.setState({ errors });
}
reset() {
this.setState({ name: '', email: '' });
}
render() {
return (
<div>
<div className="mb-3">
<label className="form-label">Name</label>
<input
className="form-control"
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value })}
/>
</div>
<div className="mb-3">
<label className="form-label">Email</label>
<input
className="form-control"
value={this.state.email}
onChange={(e) => this.setState({ email: e.target.value })}
/>
</div>
</div>
);
}
}
export default function App() {
const [open, setOpen] = useState(false);
return (
<div className="p-4">
<button className="btn btn-primary" onClick={() => setOpen(true)}>
Open Form
</button>
<Datamodel
name="Create User"
type={LAYOUT_TYPES.MODAL}
open={open}
onClose={() => setOpen(false)}
body={UserForm}
action={{
api: {
route: '/api/v1/users',
method: 'POST',
},
onComplete: (result) => {
console.log('Saved successfully:', result);
},
}}
/>
</div>
);
}2. Modern Functional Form Body (useDataModelForm)
The useDataModelForm hook bridges functional components directly into DataModel's validation and submission lifecycle:
import React from 'react';
import Datamodel, { LAYOUT_TYPES, useDataModelForm } from '@infeya/data-model';
function UserFormBody({ data }) {
const { values, handleChange, errors } = useDataModelForm({
initialValues: data,
});
return (
<div>
<div className="mb-3">
<label className="form-label">Username</label>
<input
name="username"
className="form-control"
value={values.username || ''}
onChange={handleChange}
/>
{errors.username && (
<div className="text-danger small mt-1">{errors.username[0]}</div>
)}
</div>
<div className="mb-3">
<label className="form-label">Email</label>
<input
name="email"
type="email"
className="form-control"
value={values.email || ''}
onChange={handleChange}
/>
{errors.email && (
<div className="text-danger small mt-1">{errors.email[0]}</div>
)}
</div>
</div>
);
}3. Enterprise Multi-Step Wizard
import React, { useState } from 'react';
import Datamodel, {
LAYOUT_TYPES,
STEP_MODES,
STEP_VALIDATION_MODE,
STEP_NAV_MODE,
} from '@infeya/data-model';
import { z } from 'zod';
const personalSchema = z.object({
fullName: z.string().min(2, 'Name is required'),
email: z.string().email('Invalid email address'),
});
const companySchema = z.object({
companyName: z.string().min(2, 'Company name is required'),
taxId: z.string().min(5, 'Valid Tax ID required'),
});
export default function OnboardingWizard() {
const [open, setOpen] = useState(true);
return (
<Datamodel
name="Business Onboarding"
type={LAYOUT_TYPES.MODAL}
mode={STEP_MODES.STEP}
size="lg"
open={open}
onClose={() => setOpen(false)}
steps={{
nav: {
mode: STEP_NAV_MODE.LINEAR, // Enforce strict sequential completion
},
validation: {
mode: STEP_VALIDATION_MODE.ON_NEXT, // Validate before allowing Next step
},
overview: {
enabled: true,
title: 'Review & Confirm',
},
steps: [
{
id: 'personal',
title: 'Account Holder',
body: PersonalStepForm,
validate: {
schema: personalSchema,
},
// Async server guard check before advancing to next step
beforeNext: async (stepData, { abortSignal }) => {
const res = await checkEmailAvailability(stepData.email, { signal: abortSignal });
if (!res.isAvailable) {
return { valid: false, message: 'This email is already registered.' };
}
return true;
},
},
{
id: 'company',
title: 'Company Details',
body: CompanyStepForm,
validate: {
schema: companySchema,
},
// Dynamically show this step only for business accounts
when: (formData) => formData.accountType === 'business',
},
{
id: 'preferences',
title: 'Preferences',
body: PreferencesStepForm,
skip: true, // Allow user to skip this step
},
],
}}
action={{
beforeProcess: async (payload) => ({
...payload,
submittedAt: new Date().toISOString(),
}),
custom: async (payload, { onProgress }) => {
onProgress({ progress: 50 });
const response = await submitRegistration(payload);
onProgress({ progress: 100 });
return response;
},
onComplete: (result) => {
alert('Onboarding complete!');
},
}}
/>
);
}⚙️ Props API Reference
<Datamodel /> Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| name | string \| ReactNode | undefined | Header title string or custom React element displayed in title bars. |
| type | 'inline' \| 'modal' \| 'offcanvas' | 'inline' | Layout container format. |
| mode | 'single' \| 'step' | 'single' | Orchestration mode: single form body vs multi-step wizard. |
| size | 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | Modal dialog width size. |
| placement | 'start' \| 'end' \| 'top' \| 'bottom' | 'end' | Offcanvas slide-in direction. |
| open | boolean | false | Controls open/visible state for Modal or Offcanvas. |
| onClose | (result?: any) => void | undefined | Callback invoked when modal/drawer closes. |
| onOpen | () => void | undefined | Callback invoked when modal/drawer opens. |
| closeButton | boolean | true | Whether to show the header '✕' close icon button. |
| body | React.ComponentType | undefined | Form body component used when mode="single". |
| bodyData | object | {} | Initial data injected into the form / step repository. |
| bodyProps | object | {} | Additional props passed directly into the body component. |
| steps | object \| array | null | Step engine configuration object or array for mode="step". |
| validate | object \| Function | null | Validation schema or custom validation function for single form mode. |
| action | object \| Function | null | Action execution pipeline configuration or custom async submit function. |
| buttons | object \| boolean | { layout: 'end' } | Footer button configuration; pass false to suppress all footer buttons. |
| resetAfterSubmit| boolean | true | Automatically reset form inputs and step stores after successful action. |
| reset | boolean | false | Reset form state on mount. |
| header | boolean \| ReactNode | true | Pass false to completely hide header bar. |
| headerRender | (props) => ReactNode | undefined | Render-prop for 100% custom header UI. |
| headerComponent| React.ComponentType | undefined | Custom React component replacing header bar. |
| footer | boolean \| ReactNode | true | Pass false to completely hide footer bar. |
| footerRender | (props) => ReactNode | undefined | Render-prop for 100% custom footer UI. |
| footerComponent| React.ComponentType | undefined | Custom React component replacing footer bar. |
| uiAdapter | 'bootstrap' \| 'tailwind' \| 'mui' \| object | 'bootstrap' | UI design token adapter or custom CSS class override object. |
| modalComponent | React.ComponentType | Built-in | Custom Modal container override component. |
| offcanvasComponent| React.ComponentType| Built-in | Custom Offcanvas container override component. |
| backdrop | boolean \| 'static' | 'static' (modal) / false (offcanvas) | Overlay behavior. 'static' prevents dismiss on outside click. |
| keyboard | boolean | true | Closes modal/offcanvas when pressing Escape. |
| hasPermission | (permission: string) => boolean | undefined | Global permission gate function evaluating step permission flags. |
Buttons Configuration Object (buttons)
The buttons prop controls action button visibility, layouts, labels, and confirmations:
buttons: {
// Placement layout
layout: 'between', // 'start' | 'center' | 'end' | 'between' | 'around' | 'block' | 'stack' | 'row'
// Submit / Primary action button
actionButton: {
text: 'Submit Application',
className: 'btn-success',
variant: 'primary',
confirmation: true, // or confirmation options object
timeout: 15000,
hidden: false,
disabled: false,
},
// Cancel button
cancelButton: {
text: 'Discard',
className: 'btn-outline-secondary',
hidden: false,
},
// Step wizard Next button
nextButton: {
text: 'Continue →',
},
// Step wizard Back button
backButton: {
text: '← Back',
},
// Step wizard Skip button
skipButton: {
text: 'Skip Step',
},
// Custom secondary buttons
custom: [
{
id: 'draft',
text: 'Save Draft',
variant: 'secondary',
onClick: async (context) => {
const data = context.getFormData();
await saveDraftToLocalStorage(data);
},
},
],
}Step System Configuration (steps)
steps: {
// Step navigation rules
nav: {
mode: 'linear', // 'linear' | 'open' | 'locked'
canNavigate: ({ fromIndex, toIndex, steps, stepState, source }) => boolean,
},
// Validation execution timing
validation: {
mode: 'onNext', // 'onSubmit' | 'onNext' | 'onJump' | 'strict' | 'none'
},
// Automatic summary / review screen
overview: {
enabled: true,
title: 'Review Summary',
body: CustomOverviewComponent, // Optional custom summary renderer
},
backHide: false, // Hide back button on wizard
nextHide: false, // Hide next button on wizard
// Array of step definitions
steps: [
/* Step definitions */
],
}Individual Step Definition Schema
| Property | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| id | string | (Required) | Unique identifier string for the step. |
| title | string | (Required) | Title displayed in tabs, headers, and overview cards. |
| body | React.ComponentType | undefined | React component rendered as the body of this step. |
| bodyProps | object | {} | Props passed directly to the step body component. |
| when | (formData, context) => boolean | undefined | Dynamic conditional predicate. Return false to omit step. |
| visible | boolean \| Function | true | Static or function determining step visibility. |
| hidden | boolean | false | Explicitly hides step if true. |
| permission | string | undefined | Permission key evaluated via hasPermission prop. |
| skip | boolean | false | When true, displays a "Skip" button allowing users to bypass this step. |
| validate | object | undefined | Step-level validation schema: { schema, adapter, options }. |
| beforeNext | async (stepData, runtime) => boolean \| object | undefined | Async transition guard before advancing. Return false or { valid: false, message } to block. |
| beforeBack | async (stepData, runtime) => boolean | undefined | Async transition guard before navigating backwards. |
🪝 React Hooks API
useDataModel()
Exposes full imperative controls and reactive state to any component rendered inside <Datamodel> or <DataModelProvider>:
import { useDataModel } from '@infeya/data-model';
function CustomStepSummary() {
const {
// Lifecycle
open,
openModal,
close,
toggleModal,
// Step state
isStepMode,
currentStep,
currentStepId,
currentStepIndex,
steps,
goToStep,
next,
back,
// Form data store
getFormData,
setFormData,
getStepData,
setStepData,
syncFormData,
// Actions & progress
runAction,
activeAction,
isSubmitting,
progress,
abort,
// UI
ui,
} = useDataModel();
const allData = getFormData();
return (
<div>
<h4>Review for {allData.fullName}</h4>
<p>Current Step: {currentStep?.title} ({currentStepIndex + 1}/{steps.length})</p>
{isSubmitting && <p>Submitting... ({progress}%)</p>}
<button onClick={() => goToStep('personal')}>Edit Personal Info</button>
</div>
);
}useDataModelForm(options)
Bridges React function component form state with DataModel's FormController:
import { useDataModelForm } from '@infeya/data-model';
function ProfileForm() {
const {
values, // Current form state object { [fieldName]: any }
setValues, // (newValues) => void (bulk replace)
setFieldValue, // (name, value) => void (single field updater)
handleChange, // (event) => void (standard HTML input change handler)
errors, // Current error map { [fieldName]: string[] }
setErrors, // (errorMap) => void (custom error setter)
clearErrors, // () => void (clears all validation errors)
reset, // () => void (resets to initial values)
isSubmitting, // boolean
context, // Underlying DataModel context object
} = useDataModelForm({
initialValues: { username: '', bio: '' },
onChange: (values) => console.log('Values changed:', values),
});
return (
<form>
<input name="username" value={values.username} onChange={handleChange} />
{errors.username && <span className="text-danger">{errors.username[0]}</span>}
</form>
);
}🔀 Step Navigation & Policies
Navigation Modes (STEP_NAV_MODE)
| Mode | Identifier | Behavior |
| :--- | :--- | :--- |
| LINEAR | 'linear' | Default. Enforces strict sequential step progression. Users cannot jump ahead until preceding steps are validated and completed. Backwards navigation is always permitted. |
| OPEN | 'open' | Allows arbitrary direct jumping to any step (via tab clicks or progress items) at any time. |
| LOCKED | 'locked' | Direct tab clicks are completely disabled. Navigation is only permitted programmatically or via "Next" / "Back" action buttons. |
| CUSTOM | 'custom' | Fully driven by custom canNavigate hook. |
<Datamodel
mode="step"
steps={{
nav: {
mode: STEP_NAV_MODE.LINEAR,
// Optional fine-grained navigation gate
canNavigate: ({ fromIndex, toIndex, steps, stepState, source }) => {
if (source === 'tab' && toIndex > fromIndex + 1) return false;
return true;
},
},
steps: [...],
}}
/>Step Guards (beforeNext and beforeBack)
Protect transitions with asynchronous verification before navigation takes place:
{
id: 'shipping_address',
title: 'Shipping',
body: ShippingForm,
beforeNext: async (stepData, { allData, abortSignal, stepController }) => {
// 1. Return false to simply block navigation:
// return false;
// 2. Return validation error object to show toast and highlight input errors:
const validation = await verifyAddressWithPostalAPI(stepData, { signal: abortSignal });
if (!validation.valid) {
return {
valid: false,
message: 'Invalid postal address',
errors: { zipCode: ['Postal code not recognized'] },
};
}
return true; // Proceed to next step
},
}Dynamic Conditional Branching (when and visible)
Steps can dynamically appear or disappear in real-time as users fill out prior steps:
{
id: 'billing_vat',
title: 'EU VAT Registration',
body: VatForm,
// Automatically included only when user is located in the EU:
when: (formData) => formData.countryCode === 'EU' && formData.isBusiness === true,
}Automated Overview & Review Screen
DataModel can automatically append an Overview Step (__overview__) summarizing all filled step data before submission:
<Datamodel
mode="step"
steps={{
overview: {
enabled: true,
title: 'Review All Information',
// Optional: override preview body renderer
body: CustomOverviewSummary,
},
steps: [...],
}}
/>🛡️ Validation Engine & Adapters
DataModel features a unified validation runner with automatic schema detection for the 5 most popular validation frameworks.
graph LR
Schema[Validation Schema] --> Detector{detectAdapter}
Detector -->|safeParse| Zod[Zod Adapter]
Detector -->|validateSync / isYup| Yup[Yup Adapter]
Detector -->|~standard| Valibot[Valibot Adapter]
Detector -->|compiled fn| Ajv[Ajv Adapter]
Detector -->|validate / isJoi| Joi[Joi Adapter]
Detector -->|Fallback| Default[Default Adapter]Validation Timing Modes (STEP_VALIDATION_MODE)
| Mode | Value | Behavior |
| :--- | :--- | :--- |
| ON_SUBMIT | 'onSubmit' | Default. Validates only when the final Action/Submit button is pressed. |
| ON_NEXT | 'onNext' | Validates the current step immediately when clicking the Next button before advancing. |
| ON_JUMP | 'onJump' | Validates before jumping across steps via tab clicks. |
| STRICT | 'strict' | Validates on every navigation event (Next, Tab, Jump). |
| NONE | 'none' | Disables automated validation checks. |
Schema Examples
1. Zod
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email('Invalid email'),
age: z.number().min(18, 'Must be at least 18'),
});
<Datamodel validate={{ schema: userSchema }} body={UserForm} />2. Yup
import * as Yup from 'yup';
const schema = Yup.object().shape({
username: Yup.string().min(3).required(),
});
<Datamodel validate={{ schema }} body={UserForm} />3. Valibot (Standard Schema v1)
import * as v from 'valibot';
const schema = v.object({
title: v.pipe(v.string(), v.minLength(1, 'Title required')),
});
<Datamodel validate={{ schema }} body={ProductForm} />4. Ajv (JSON Schema)
import Ajv from 'ajv';
const ajv = new Ajv({ allErrors: true });
const schema = ajv.compile({
type: 'object',
properties: { sku: { type: 'string', minLength: 4 } },
required: ['sku'],
});
<Datamodel validate={{ schema }} body={ItemForm} />5. Joi
import Joi from 'joi';
const schema = Joi.object({
code: Joi.string().alphanum().min(3).required(),
});
<Datamodel validate={{ schema }} body={CodeForm} />Heterogeneous Per-Step Wizard Schemas
In a multi-step wizard, different steps can use completely different schema libraries:
<Datamodel
mode="step"
steps={{
steps: [
{ id: 's1', title: 'Step 1', validate: { schema: zodSchema } },
{ id: 's2', title: 'Step 2', validate: { schema: yupSchema } },
{ id: 's3', title: 'Step 3', validate: { schema: joiSchema } },
],
}}
/>Custom Validation Adapter Registration
Register your own custom validation engine (e.g. Superstruct, TypeBox, or custom logic):
import { registerValidationAdapter } from '@infeya/data-model';
registerValidationAdapter('my-validator', async (schema, data, options) => {
const result = await schema.run(data);
if (!result.ok) {
return { value: data, error: { field: ['Validation failed'] } };
}
return { value: result.data, error: null };
});🔧 Asynchronous Action Pipeline
Actions follow a deterministic, fail-safe 4-stage execution sequence:
graph LR
A[1. beforeProcess] --> B[2. API Call]
B --> C[3. Custom Logic]
C --> D[4. afterProcess]Action Configuration Properties
action: {
actionType: 'custom', // 'custom' | 'api' | 'create' | 'update'
// 1. Stage 1: Payload Enrichment
beforeProcess: async (payload, { abortSignal }) => {
return { ...payload, clientTimestamp: Date.now() };
},
// 2. Stage 2: Built-in HTTP Transport via @infeya/api-center
api: {
route: (payload) => `/api/v1/orders/${payload.orderId || ''}`,
method: 'POST',
headers: { 'Authorization': 'Bearer ...' },
query: { verbose: true },
body: (payload) => ({ order: payload }),
},
// 3. Stage 3: Custom Business Logic
custom: async (apiResult, { onProgress, abortSignal, stepController }) => {
onProgress({ progress: 50 });
const finalData = await processCustomLogic(apiResult);
onProgress({ progress: 100 });
return finalData;
},
// 4. Stage 4: Result Transformation
afterProcess: async (result, workingPayload) => {
return { ...result, formatted: true };
},
timeout: 30000, // Guard timeout in milliseconds (default: 30s)
showProgress: true, // Render automated progress bar during execution
onComplete: (result, formData) => {
console.log('Action complete:', result);
},
}Abort Signal & Request Cancellation
When a modal is closed, cancelled, or timed out, DataModel triggers an AbortSignal automatically. You can forward this signal directly to fetch, axios, or @infeya/api-center:
action: {
custom: async (formData, { abortSignal }) => {
const response = await fetch('/api/upload', {
method: 'POST',
body: JSON.stringify(formData),
signal: abortSignal, // 👈 Automatically cancelled if user closes modal!
});
return response.json();
},
}💬 Confirmation Dialogs & Notifications
SweetAlert2 Confirmation Dialogs
<Datamodel
buttons={{
actionButton: {
text: 'Delete Account',
confirmation: {
title: 'Delete Account?',
text: 'This action cannot be undone. All data will be lost.',
icon: 'warning',
confirmButtonText: 'Yes, Delete Permanently',
cancelButtonText: 'Cancel',
confirmButtonColor: '#dc2626',
},
},
}}
/>Custom Global Dialog & Notification Handlers
Connect DataModel directly to your application's notification library (Toastr, Notistack, Sonner, React-Toastify) and confirmation dialog system:
import { setNotificationHandler, setDialogHandler } from '@infeya/data-model';
// Plug in toast notifications
setNotificationHandler((type, message, title) => {
// type: 'success' | 'error' | 'warning' | 'info'
myToastLibrary[type](message);
});
// Plug in confirmation dialogs
setDialogHandler(async (options) => {
// options: { title, text, icon, confirmButtonText, cancelButtonText, ... }
const confirmed = await myCustomModalDialog(options);
return { isConfirmed: confirmed };
});🎨 Multi-Theme UI Adapters (Bootstrap, Tailwind, MUI)
DataModel decouples business logic from presentation. Switch styling across all buttons, modal backdrops, dialogs, drawers, and tabs with zero code changes:
Global Adapter Switch
import { configureDataModel } from '@infeya/data-model';
configureDataModel({
uiAdapter: 'tailwind', // 'bootstrap' | 'tailwind' | 'mui'
});Per-Component Theme Override
<Datamodel uiAdapter="tailwind" name="Tailwind Drawer" type="offcanvas" open={isOpen} />
<Datamodel uiAdapter="mui" name="Material Wizard" mode="step" steps={steps} />Defining Custom UI Design Tokens
import { registerUiAdapter } from '@infeya/data-model';
registerUiAdapter('company-theme', {
buttons: {
base: 'font-semibold rounded-xl transition duration-200',
primary: 'bg-violet-600 hover:bg-violet-700 text-white shadow-md',
secondary: 'bg-zinc-100 hover:bg-zinc-200 text-zinc-900',
layouts: {
end: 'flex justify-end gap-3',
between: 'flex justify-between items-center',
},
},
modal: {
backdrop: 'fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4',
content: 'bg-white rounded-2xl shadow-2xl max-w-lg w-full overflow-hidden',
},
});📐 Header & Footer Customization
Hide Header or Footer
<Datamodel header={false} footer={false} body={MyBody} />Custom Render-Props
<Datamodel
headerRender={({ name, close }) => (
<div className="p-4 bg-gradient-to-r from-blue-600 to-indigo-600 text-white flex justify-between">
<h3 className="font-bold">{name}</h3>
<button onClick={() => close()}>✕</button>
</div>
)}
footerRender={({ close, runAction, isSubmitting }) => (
<div className="p-4 border-t flex justify-end gap-2">
<button onClick={() => close()} disabled={isSubmitting}>Cancel</button>
<button onClick={() => runAction()} disabled={isSubmitting}>Save Changes</button>
</div>
)}
/>🧩 Constants & Enums Reference
import {
LAYOUT_TYPES,
STEP_MODES,
ACTION_TYPES,
VALIDATION_MODES,
BUTTON_TYPES,
BUTTON_LAYOUTS,
STEP_STATUS,
NAV_SOURCE,
STEP_NAV_MODE,
STEP_VALIDATION_MODE,
} from '@infeya/data-model';LAYOUT_TYPES:INLINE: 'inline',MODAL: 'modal',OFFCANVAS: 'offcanvas'STEP_MODES:SINGLE: 'single',STEP: 'step'BUTTON_LAYOUTS:'start','center','end','between','around','block','stack','row'STEP_NAV_MODE:LINEAR: 'linear',OPEN: 'open',LOCKED: 'locked',CUSTOM: 'custom'STEP_VALIDATION_MODE:ON_SUBMIT: 'onSubmit',ON_NEXT: 'onNext',ON_JUMP: 'onJump',STRICT: 'strict',NONE: 'none'STEP_STATUS:PRISTINE: 'pristine',ACTIVE: 'active',COMPLETED: 'completed',INVALID: 'invalid',SKIPPED: 'skipped'NAV_SOURCE:NEXT: 'next',BACK: 'back',TAB: 'tab',OVERVIEW: 'overview',PROGRAM: 'program'
📘 TypeScript Support
@infeya/data-model includes full TypeScript definitions out of the box:
import Datamodel, { DatamodelProps } from '@infeya/data-model';
interface UserRegistrationData {
username: string;
email: string;
tier: 'free' | 'pro' | 'enterprise';
}
export function RegistrationModal() {
return (
<Datamodel<UserRegistrationData>
name="Register"
type="modal"
bodyData={{ username: '', email: '', tier: 'free' }}
action={{
custom: async (data) => {
// data is strictly typed as UserRegistrationData!
console.log(data.tier);
},
}}
/>
);
}📄 License
MIT © Infeya
