hazo_data_forms
v2.0.3
Published
Dynamic form rendering from JSON schema with document link support and PDF viewer integration
Maintainers
Readme
hazo_data_forms
Dynamic form rendering from JSON schema with document link support and embedded PDF viewer integration.
Overview
hazo_data_forms is a React library that transforms JSON schema definitions into fully functional forms with rich field types, dual-mode rendering (edit/view), and integrated PDF document viewing capabilities. Build complex data collection interfaces without writing form components.
Key Features
- 17 Field Types: text, number, date, boolean, option, email, tel, currency, percentage, textarea, table, computed, masked, static_text, summary_row, abn, tfn
- Dual Mode Rendering: Switch between edit mode (editable) and view mode (read-only display)
- Document Links: Click inline doc links to open PDFs in resizable side panel
- Schema-Driven: Define forms declaratively in JSON
- Config-Based Styling: INI configuration file for consistent styling across projects
- Computed Fields: Automatic calculation based on other field values
- Table Fields: Dynamic arrays with configurable columns
- Built-in Validation: Required fields, min/max, length constraints, custom validators
- TypeScript: Full type safety and IntelliSense support
- Extensible: Register custom field renderers without forking
Installation
npm install hazo_data_forms react react-dom react-hook-form react-icons
# Required: For file management (upload/delete/storage)
npm install hazo_files
# Optional: For PDF viewer support
npm install hazo_pdf
# Optional: For server-side config loading
npm install hazo_configImportant: This library requires Tailwind CSS to be configured in your project. See the Quick Start section for setup instructions.
Peer Dependencies
- React: ^18.0.0 or ^19.0.0
- react-hook-form: ^7.0.0
- hazo_files: ^1.4.0 (required - file management service)
- hazo_config: ^1.0.0 (optional)
- hazo_pdf: ^1.6.0 (optional - for PDF viewer)
Quick Start
1. Configure Tailwind CSS
This library uses Tailwind CSS classes with semantic CSS variables compatible with shadcn/ui theming.
Tailwind v4: The library uses semantic color classes (bg-muted, text-primary, border-destructive, etc.) that work with Tailwind v4. If you experience missing utility class styles, add the package to your CSS source paths:
/* app/globals.css */
@import "tailwindcss";
/* Include hazo_data_forms for Tailwind to scan utility classes */
@source "../node_modules/hazo_data_forms/dist";Tailwind v3: Add the library's source to your Tailwind config:
// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/hazo_data_forms/dist/**/*.{js,mjs,cjs}",
],
// ... rest of your config
};Note: This library expects shadcn/ui CSS variables to be defined (e.g., --background, --foreground, --primary, --muted, --destructive, etc.). If you're using shadcn/ui, these are already configured. Otherwise, see the shadcn/ui theming documentation for the required CSS variables.
2. Import the component
import { HazoDataForm, FormSchema } from "hazo_data_forms";3. Define your form schema
const schema: FormSchema = [
{
section_name: "Personal Information",
sub_sections: [
{
sub_section_id: "basic_info",
sub_section_label: "Basic Details",
field_group: {
orientation: "vertical",
fields: [
{
id: "first_name",
label: "First Name",
field_info: {
field_type: "text",
required: true,
placeholder: "Enter your first name"
}
},
{
id: "email",
label: "Email Address",
field_info: {
field_type: "email",
required: true
}
},
{
id: "birth_date",
label: "Date of Birth",
field_info: {
field_type: "date"
}
}
]
}
}
]
}
];4. Render the form
function MyForm() {
const handle_submit = (values: FormValues) => {
console.log("Form submitted:", values);
};
return (
<HazoDataForm
schema={schema}
mode="edit"
on_submit={handle_submit}
/>
);
}Field Types
Text Fields
// Basic text
{
id: "username",
label: "Username",
field_info: {
field_type: "text",
required: true,
min_length: 3,
max_length: 20
}
}
// Email
{
id: "email",
label: "Email",
field_info: { field_type: "email" }
}
// Phone
{
id: "phone",
label: "Phone Number",
field_info: { field_type: "tel" }
}
// Textarea
{
id: "comments",
label: "Comments",
field_info: {
field_type: "textarea",
rows: 4,
max_length: 500
}
}Numeric Fields
// Number
{
id: "age",
label: "Age",
field_info: {
field_type: "number",
min: 0,
max: 120
}
}
// Currency
{
id: "salary",
label: "Annual Salary",
field_info: {
field_type: "currency",
currency_symbol: "$",
decimal_places: 2
}
}
// Percentage
{
id: "discount",
label: "Discount Rate",
field_info: {
field_type: "percentage",
decimal_places: 1,
min: 0,
max: 100
}
}Selection Fields
// Boolean (checkbox)
{
id: "agree_terms",
label: "I agree to the terms and conditions",
field_info: {
field_type: "boolean",
required: true
}
}
// Option (dropdown)
{
id: "country",
label: "Country",
field_info: {
field_type: "option",
options: [
{ label: "United States", value: "US" },
{ label: "Canada", value: "CA" },
{ label: "United Kingdom", value: "UK" }
],
required: true
}
}Date Field
{
id: "start_date",
label: "Start Date",
field_info: {
field_type: "date",
required: true
}
}Computed Field
{
id: "total",
label: "Total Amount",
field_info: {
field_type: "computed",
computed_formula: "price * quantity * (1 - discount / 100)",
computed_dependencies: ["price", "quantity", "discount"]
}
}Table Field (Arrays)
{
id: "line_items",
label: "Line Items",
field_info: {
field_type: "table",
table_columns: [
{
id: "description",
label: "Description",
field_info: { field_type: "text" },
width: "40%"
},
{
id: "quantity",
label: "Qty",
field_info: { field_type: "number", min: 1 },
width: "20%"
},
{
id: "unit_price",
label: "Unit Price",
field_info: { field_type: "currency" },
width: "20%"
},
{
id: "total",
label: "Total",
field_info: {
field_type: "computed",
computed_formula: "quantity * unit_price"
},
width: "20%"
}
],
table_min_rows: 1,
table_max_rows: 50
}
}Reference Values
Add reference annotations below fields to show prior-year values, benchmarks, or expected values:
{
id: "annual_revenue",
label: "Annual Revenue",
field_info: { field_type: "currency", decimal_places: 2 },
reference_value: "$1,250,000.00 (FY2024)"
}The reference_value displays below the field input as italic, smaller text with a grey background. It appears in both edit and view modes. Fields without reference_value render normally with no extra space.
Supported on all field types including table columns.
Reference Values in Tables
Table columns support reference_value for a static annotation on every row. For per-row/per-cell annotations, include _reference_values in your row data:
// Column-level (same reference for every row)
{
id: "amount",
label: "Amount",
field_info: { field_type: "currency" },
reference_value: "$10,000 (benchmark)"
}
// Row-level (different reference per row)
const table_data = [
{
description: "Office Rent",
amount: 14000,
_reference_values: { amount: "$12,500.00 (FY2024)" }
},
{
description: "Travel",
amount: 5600,
_reference_values: { amount: "$4,800.00 (FY2024)" }
}
];Row-level _reference_values take priority over column-level reference_value.
Document Links
Add inline document links that open PDFs in an embedded viewer:
{
id: "contract_value",
label: "Contract Value",
field_info: { field_type: "currency" },
doc_links: [
{
type: "pdf",
url: "/documents/contract.pdf",
page: 3 // Optional: Open to specific page
}
]
}PDF Panel Configuration
<HazoDataForm
schema={schema}
show_pdf_panel={true}
pdf_panel_position="right" // "right" | "left" | "bottom"
pdf_panel_width="500px"
pdf_panel_resizable={true}
/>Services
hazo_data_forms uses a service injection pattern for external integrations. Services can be passed via the services prop or via HazoServicesProvider.
File Manager Service
File upload, deletion, and PDF save operations are handled through the file_manager service from hazo_files.
import { HazoDataForm } from "hazo_data_forms";
import { createFileManager } from "hazo_files";
// Server-side: use hazo_files FileManager directly
const file_manager = createFileManager({ configPath: "./hazo_files.ini" });
await file_manager.initialize();
// Client-side: create an adapter that calls API routes
// (see test-app/lib/client_file_manager.ts for an example)
<HazoDataForm
schema={schema}
services={{ file_manager }}
enable_file_upload={true}
file_save_path="/clients/acme/tax_2024/"
pdf_save_path="/clients/acme/tax_2024/pdfs/"
/>File upload is enabled when all three conditions are met:
enable_file_upload={true}prop is setconfig.file_upload.enabledis true (from INI config)file_manager.isInitialized()returns true
Using the Services Provider
Wrap your app or form area with HazoServicesProvider to provide services via context:
import { HazoServicesProvider } from "hazo_data_forms";
<HazoServicesProvider services={{ file_manager, logger, db }}>
<HazoDataForm schema={schema} enable_file_upload={true} />
</HazoServicesProvider>Accessing Services in Custom Components
import { useHazoFileManager, useHazoLogger, useHazoDb } from "hazo_data_forms";
function MyCustomComponent() {
const file_manager = useHazoFileManager();
const logger = useHazoLogger();
const db = useHazoDb();
}Form Modes
Edit Mode (Editable)
<HazoDataForm
schema={schema}
mode="edit"
on_submit={(values) => {
// Handle form submission
console.log(values);
}}
/>View Mode (Read-Only)
const existing_data = {
first_name: "John",
email: "[email protected]",
birth_date: "1990-05-15"
};
<HazoDataForm
schema={schema}
mode="view"
values={existing_data}
/>Configuration
Using INI Configuration File
Create a configuration file (e.g., /public/config/hazo_data_forms_config.ini):
[colors]
label_color = #374151
field_border_color = #d1d5db
field_border_color_focus = #3b82f6
[fonts]
label_font_size = 14px
field_font_size = 14px
[spacing]
section_spacing = 32px
field_spacing = 16px
[formatting]
default_currency_symbol = $
date_format = MMM d, yyyyPass the path to your component:
<HazoDataForm
schema={schema}
config_path="/config/hazo_data_forms_config.ini"
/>Runtime Configuration Override
<HazoDataForm
schema={schema}
config_override={{
label_color: "#1e40af",
field_border_color: "#60a5fa",
default_currency_symbol: "€",
date_format: "dd/MM/yyyy"
}}
/>API Reference
HazoDataForm Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| schema | FormSchema | required | Form schema defining sections and fields |
| mode | "edit" \| "view" | "edit" | Form mode (edit allows input, view is read-only) |
| values | FormValues | {} | Controlled form values |
| default_values | FormValues | {} | Default values for uncontrolled form |
| on_change | (values: FormValues) => void | - | Callback when any field value changes |
| on_field_change | (field_id: string, value: unknown) => void | - | Callback when specific field changes |
| on_submit | (values: FormValues) => void | - | Callback when form is submitted |
| on_doc_link_click | (doc_link: DocLink) => void | - | Callback when document link is clicked |
| show_pdf_panel | boolean | true | Whether to show PDF panel for doc links |
| pdf_panel_position | "left" \| "right" | "right" | Position of PDF panel |
| pdf_panel_width | string | from config | Width of PDF panel |
| pdf_panel_resizable | boolean | true | Whether PDF panel is resizable |
| config_path | string | - | Path to INI configuration file |
| config_override | PartialFormConfig | - | Runtime configuration overrides |
| errors | FormErrors | - | External validation errors |
| validate_on_blur | boolean | true | Validate fields on blur |
| validate_on_change | boolean | false | Validate fields on change |
| validate | (values: FormValues) => FormErrors | - | Custom validation function |
| class_name | string | - | Additional CSS class for form container |
| show_section_headers | boolean | true | Show section headers |
| show_sub_section_headers | boolean | true | Show sub-section headers |
| collapsible_sections | boolean | false | Make sections collapsible |
| collapsed_sections | string[] | [] | IDs of initially collapsed sections |
| on_form_ready | (methods: UseFormReturn) => void | - | Callback with react-hook-form methods |
| show_submit_button | boolean | - | Show submit button at bottom of form |
| submit_button_text | string | "Submit" | Text for submit button |
| services | HazoServices | - | Service instances (file_manager, logger, db) |
| enable_file_upload | boolean | false | Enable file upload UI |
| file_save_path | string | - | Base storage path for uploaded files |
| pdf_save_path | string | - | Separate path for PDF saves (falls back to file_save_path) |
Custom Field Renderers
Register custom field types:
import { register_field_renderer, type FieldRendererProps } from "hazo_data_forms";
function CustomField({ field, value, on_change, mode, config }: FieldRendererProps) {
// Your custom field implementation
return <div>...</div>;
}
register_field_renderer("custom_type", CustomField);Then use in your schema:
{
id: "my_field",
label: "My Custom Field",
field_info: {
field_type: "custom_type" as any
}
}Exported Types
import type {
// Schema types
FormSchema,
FormSection,
SubSection,
FieldGroup,
FormField,
FieldInfo,
FieldType,
OptionItem,
TableColumn,
// Runtime types
FormValues,
FormErrors,
FormMode,
DocLink,
DocLinkClickEvent,
PdfPanelPosition,
// Configuration
FormConfig,
PartialFormConfig,
// Services
HazoServices,
HazoFilesFileManager, // Re-exported from hazo_files
HazoConnectInstance,
Logger,
// Component props
HazoDataFormProps,
FieldRendererProps,
FieldRenderer,
} from "hazo_data_forms";Exported Utilities
import {
// Styling
cn,
// Formatting
format_currency,
format_percentage,
format_date,
// Parsing
parse_boolean,
parse_number,
parse_string,
// Computed fields
evaluate_formula,
// Utilities
generate_id,
deep_merge,
sanitize_filename,
// Service hooks
useHazoFileManager,
useHazoLogger,
useHazoDb,
useHazoCustomService,
// Service provider
HazoServicesProvider,
} from "hazo_data_forms";Styling
This library uses Tailwind CSS with semantic CSS variables for styling, ensuring compatibility with both Tailwind v3 and v4. The library uses shadcn/ui-compatible color tokens:
- Background colors:
bg-background,bg-muted,bg-primary/10,bg-destructive/10 - Text colors:
text-foreground,text-muted-foreground,text-primary,text-destructive - Border colors:
border-border,border-input,border-primary,border-destructive
All CSS classes use the cls_ prefix following hazo ecosystem conventions:
cls_hazo_data_form- Main form containercls_section- Section containercls_sub_section- Sub-section containercls_field_group- Field group containercls_field_wrapper- Individual field wrappercls_field_label- Field labelcls_field_input- Field input elementcls_doc_link_button- Document link buttoncls_pdf_panel- PDF panel container
You can override these styles in your own CSS or use the configuration system to customize colors, fonts, and spacing.
Examples
Complete Form Example
See the test-app directory in the repository for a complete Next.js example with various field types and PDF integration.
View Mode
<HazoDataForm
schema={schema}
mode="view"
values={savedData}
/>Validation
<HazoDataForm
schema={schema}
validate={(values) => {
const errors: FormErrors = {};
if (!values.email?.includes("@")) {
errors.email = "Invalid email address";
}
return errors;
}}
validate_on_blur={true}
/>Accessing Form Methods
import React from "react";
import type { UseFormReturn } from "react-hook-form";
import { HazoDataForm } from "hazo_data_forms";
function MyForm() {
const formMethodsRef = React.useRef<UseFormReturn | null>(null);
return (
<HazoDataForm
schema={schema}
on_form_ready={(methods) => {
formMethodsRef.current = methods;
}}
/>
);
}Migrating to v2.0.0
v2.0.0 replaces file operation callbacks with a file_manager service. This is a breaking change.
Removed Props
on_file_upload- replaced byfile_managerserviceon_file_delete- replaced byfile_managerserviceon_pdf_save- replaced byfile_managerservice
Migration Steps
Before (v1.x):
<HazoDataForm
schema={schema}
enable_file_upload={true}
on_file_upload={async (request) => {
const result = await my_api.upload(request.file);
return { success: true, uploaded_file: result };
}}
on_file_delete={async (field_id, file_id) => {
await my_api.delete(file_id);
return true;
}}
on_pdf_save={(pdf_bytes, filename, url) => {
my_api.save_pdf(pdf_bytes, filename);
}}
/>After (v2.0.0):
const file_manager: HazoFilesFileManager = {
uploadFile: async (data, path) => {
const result = await my_api.upload(data, path);
return { success: true, data: { id: result.id, name: result.name, path, size: result.size, mimeType: result.type } };
},
deleteFile: async (path) => {
await my_api.delete(path);
return { success: true };
},
downloadFile: async (path) => ({ success: true, data: await my_api.download(path) }),
isInitialized: () => true,
exists: async (path) => my_api.exists(path),
ensureDirectory: async (path) => { await my_api.mkdir(path); return { success: true }; },
};
<HazoDataForm
schema={schema}
services={{ file_manager }}
enable_file_upload={true}
file_save_path="/uploads/my_form/"
/>Migrating to v2.1.0
v2.1.0 replaces the local HazoFileManagerInstance interface with the FileManager class imported directly from hazo_files. This is a breaking change.
What Changed
hazo_filesis now a required peer dependency (was optional)HazoFileManagerInstanceis removed — useHazoFilesFileManager(re-exported fromhazo_files)HazoServices.file_manageris typed as required — the runtime still handles missing services gracefully
Migration Steps
- Install
hazo_files:
npm install hazo_files- Update type imports:
// Before
import type { HazoFileManagerInstance } from "hazo_data_forms";
// After
import type { HazoFilesFileManager } from "hazo_data_forms";
// Or import directly:
import type { FileManager } from "hazo_files";- If using a client-side adapter (plain object), add a type assertion:
// Client adapters that implement a subset of FileManager methods
// need a type assertion since FileManager is a class
return { uploadFile, deleteFile, ... } as unknown as HazoFilesFileManager;License
MIT
Repository
https://github.com/pub12/hazo_data_forms
