Collaboration form elements
npm install hazo_collab_forms

React form components with integrated chat collaboration, built for Next.js with TypeScript and Tailwind CSS.
- Integrated Chat Collaboration: Each form field supports real-time chat discussions via HazoChat with customizable display modes (embedded, side panel, overlay) and UI controls
- Unified Form View: Multi-mode form display (Edit, Summary, Print, Approval) with shared context
- Read-Only Summary Views: Display form data in summary format with status badges for locked/hidden fields
- Reference Data Display: Show comparison values (e.g., prior year data) below form fields with automatic formatting for currency, booleans, and custom styling
- Advanced Field Controls: Visibility toggle, lock/unlock, field duplication, soft delete, and notes
- Notes Integration: Database-backed notes on fields via hazo_notes package (optional)
- Field Tooltips: Help tooltips with hover cards for field labels and data table columns
- Template Generator: Visual drag-and-drop builder for creating form configurations (HazoTemplateGenerator)
- Dynamic Field Management: Add, edit, and delete fields at runtime with dialog components
- Field Selector: Search and select fields from templates with HazoFieldSelectorDialog
- Role-Based Icon Controls: Configure control visibility and behavior per role with icons_behaviour system
- Unified Field Controls: Consistent icon controls across form and summary views with automatic kebab consolidation
- Prop-Based Controls: Enable/disable features via enable_* props for full control
- File Upload with Validation: Built-in file upload with custom validation callbacks
- Document Fields: File-only fields with HazoCollabFormDoc component
- Mandatory File Indicator: Visual indicators (red asterisk) for required file uploads
- Custom File Validators: Implement custom validation logic before file upload
- Form Sets: JSON-based dynamic form generation with full file upload support
- Data OK Workflows: Checkbox and multi-state validation with auto-hide/auto-lock features
- Hidden States: Restrict available data OK states per field or form (e.g., hide "Pending" for agents)
- Type-Safe: Written in TypeScript with comprehensive type definitions
- Tailwind CSS Styling: Fully customizable with Tailwind CSS classes
- shadcn/ui Components: Built on top of accessible Radix UI primitives
``bash`
npm install hazo_collab_forms
`bashCore React dependencies (skip if already installed)
npm install react react-dom
$3
This package requires shadcn/ui components. If you haven't initialized shadcn/ui yet:
`bash
npx shadcn@latest init
`Then install the required components:
`bash
Core components (required for all form fields)
npx shadcn@latest add button label dialog tooltip sonnerFor HazoCollabFormCombo (dropdown/select)
npx shadcn@latest add popover commandFor HazoCollabFormDate (date picker)
npx shadcn@latest add calendarFor file upload functionality (required when using accept_files prop)
npx shadcn@latest add accordionFor HazoTemplateGenerator (visual form builder)
npx shadcn@latest add tabs resizableFor field tooltips (optional - falls back to native title if not installed)
npx shadcn@latest add hover-cardOptional but recommended
npx shadcn@latest add separator card
`$3
Add to your
next.config.js:`javascript
const nextConfig = {
transpilePackages: ['hazo_collab_forms'],
};
module.exports = nextConfig;
`$3
For Tailwind CSS v3:
Add to
tailwind.config.ts content array:`typescript
content: [
// ... your existing paths
"./node_modules/hazo_collab_forms/*/.{js,ts,jsx,tsx}",
],
`For Tailwind CSS v4:
Add the
@source directive to your globals.css (or main CSS file) after the tailwindcss import:`css
@import "tailwindcss";/ Required: Enable Tailwind to scan this package's classes /
@source "../node_modules/hazo_collab_forms/dist";
`This is required because Tailwind v4's JIT compiler does not scan
node_modules/ by default. Without this directive, hover states, colors, and layout utilities from this package will not have CSS generated, resulting in broken styling (transparent backgrounds, missing colors, etc.).$3
Copy the template config files to your project root:
`bash
mkdir -p config
cp node_modules/hazo_collab_forms/templates/config/hazo_collab_forms_config.ini ./config/
cp node_modules/hazo_collab_forms/templates/*.ini ./
`This creates:
-
config/hazo_collab_forms_config.ini - Main package config (in config/ subdirectory)
- hazo_chat_config.ini - Chat functionality config
- hazo_auth_config.ini - Authentication config$3
`bash
npx hazo-collab-forms-verify
`This checks all dependencies, config files, and shadcn components are properly installed.
---
Quick Reference
$3
`bash
npm install hazo_collab_forms react react-dom react-icons sonner lucide-react \
@radix-ui/react-dialog @radix-ui/react-label @radix-ui/react-popover hazo_chat hazo_ui hazo_auth hazo_config hazo_notes
`$3
`bash
npx shadcn@latest add button label dialog tooltip sonner popover command calendar accordion tabs resizable hover-card separator card sheet scroll-area checkbox select
`---
Usage
$3
`typescript
'use client';import { HazoCollabFormInputbox } from 'hazo_collab_forms';
import { useState } from 'react';
export default function MyForm() {
const [value, setValue] = useState('');
return (
label="Your Name"
value={value}
onChange={setValue}
field_data_id="user-name"
field_name="User Name"
hazo_chat_receiver_user_id="recipient-user-id"
/>
);
}
`$3
`typescript
'use client';import { HazoCollabFormInputbox } from 'hazo_collab_forms';
import { useState } from 'react';
export default function DocumentUpload() {
const [value, setValue] = useState('');
return (
label="Invoice Upload"
value={value}
onChange={setValue}
accept_files={true}
files_dir="public/uploads"
min_files={1} // Shows red asterisk (mandatory indicator)
max_files={5}
file_accept=".pdf"
file_validator={(file) => {
// Custom validation - filename pattern check
if (!file.name.match(/^INV-\d{4}\.pdf$/i)) {
return 'File name must match pattern: INV-0000.pdf';
}
// Size check (5MB limit)
if (file.size > 5 1024 1024) {
return 'File size must be under 5MB';
}
return null; // Accept file
}}
/>
);
}
`$3
Files are uploaded via POST to
/api/collab-forms/upload-file by default. You can customize this with the upload_endpoint prop.Request Format (multipart/form-data):
| Field | Type | Description |
|-------|------|-------------|
|
file | File | The uploaded file binary |
| field_id | string | The ID of the form field (for associating files with fields) |
| files_dir | string | Server directory path for storage (from files_dir prop) |
| visibility | string | 'public' or 'private' |
| required_permission | string | Permission required for private files (optional) |Expected Response:
`json
{
"file_path": "/uploads/document.pdf"
}
`$3
When using file uploads in
HazoCollabFormView or HazoCollabFormSet, files uploaded by one user must be persisted to your backend so other users can see them. The component provides callbacks for this purpose.How File Storage Works:
1. Files are stored in
form_data using the key pattern __files_${field_id}
2. Private files use the pattern __private_files_${field_id}
3. When a user uploads a file, on_field_files_change is called with the file metadata
4. You must save this metadata to your database
5. When loading the form for another user, include the file metadata in form_dataImplementation Example:
`typescript
'use client';import { HazoCollabFormView, FileData } from 'hazo_collab_forms';
import { useState, useEffect } from 'react';
interface FormData {
[key: string]: unknown;
}
export default function CollaborativeForm({ formId }: { formId: string }) {
const [formData, setFormData] = useState({});
// Load form data including files from your backend
useEffect(() => {
async function loadForm() {
const response = await fetch(
/api/forms/${formId});
const data = await response.json();
// data should include __files_* keys with FileData arrays
// Example: { field_name: "value", __files_document_field: [{ file_id: "...", file_name: "..." }] }
setFormData(data);
}
loadForm();
}, [formId]); // Handle file changes - save to your backend
const handleFilesChange = async (field_id: string, files: FileData[]) => {
// Update local state
setFormData(prev => ({
...prev,
[
__files_${field_id}]: files
})); // Persist to your backend
await fetch(
/api/forms/${formId}/files, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ field_id, files })
});
}; // Handle private file changes (if using file visibility feature)
const handlePrivateFilesChange = async (field_id: string, files: FileData[]) => {
setFormData(prev => ({
...prev,
[
__private_files_${field_id}]: files
})); await fetch(
/api/forms/${formId}/private-files, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ field_id, files })
});
}; return (
mode="edit"
sections={sections}
form_data={formData}
on_form_data_change={setFormData}
on_field_files_change={handleFilesChange}
on_field_private_files_change={handlePrivateFilesChange}
/>
);
}
`FileData Interface:
`typescript
interface FileData {
file_path: string; // Server path for download
file_name: string; // Display name
file_size: number; // Size in bytes
file_type: string; // MIME type
file_id: string; // Unique identifier
uploaded_at: Date; // Upload timestamp
visibility?: 'public' | 'private';
}
`Backend API Example (Next.js):
`typescript
// app/api/forms/[formId]/files/route.ts
import { NextRequest, NextResponse } from 'next/server';export async function POST(
request: NextRequest,
{ params }: { params: { formId: string } }
) {
const { field_id, files } = await request.json();
// Save to your database
// Example with Prisma:
// await prisma.formData.upsert({
// where: { formId_fieldId: { formId: params.formId, fieldId:
__files_${field_id} } },
// update: { value: JSON.stringify(files) },
// create: { formId: params.formId, fieldId: __files_${field_id}, value: JSON.stringify(files) }
// }); return NextResponse.json({ success: true });
}
`Key Points:
- Files are NOT automatically persisted - you must implement
on_field_files_change
- The form_data object must include __files_* keys when loading for other users
- Summary mode will only display files if they exist in form_data
- The actual file binary is uploaded separately (handled by the component's upload mechanism)
- Only the file metadata (FileData) needs to be persisted in your form data store$3
The package exports standalone control icons for building custom form workflows:
`typescript
'use client';import {
HazoCollabFormInputbox,
CollabFormVisibilityIcon,
CollabFormLockIcon,
CollabFormAddEntryIcon,
CollabFormDeleteEntryIcon,
CollabFormDeleteFieldIcon,
} from 'hazo_collab_forms';
import { useState } from 'react';
export default function AdvancedForm() {
const [value, setValue] = useState('');
const [visibility, setVisibility] = useState<'visible' | 'hidden'>('visible');
const [locked, setLocked] = useState(false);
return (
{/ Visibility Toggle /}
label="Company Name"
visibility={visibility}
on_visibility_change={setVisibility}
/> {/ Lock/Unlock Field /}
label="Company Name"
locked={locked}
on_lock_change={setLocked}
/>
{/ Add Entry - duplicate field /}
label="Contact Person"
field_id="contact_person"
on_add_entry={(field_id) => console.log('Add entry:', field_id)}
/>
{/ Delete Entry - remove duplicated field /}
label="Contact Person"
field_id="contact_person_2"
on_delete_entry={(field_id) => console.log('Delete entry:', field_id)}
/>
{/ Delete Field - soft delete /}
label="Optional Field"
field_id="optional_field"
on_delete={(field_id) => console.log('Delete field:', field_id)}
/>
);
}
`Icon Components:
| Icon Component | Description |
|----------------|-------------|
|
CollabFormVisibilityIcon | Toggle field visibility (show/hide) |
| CollabFormLockIcon | Toggle field lock (read-only mode) |
| CollabFormAddEntryIcon | Duplicate a field entry |
| CollabFormDeleteEntryIcon | Remove a duplicated entry |
| CollabFormDeleteFieldIcon | Soft delete a field |Note: All controls are shown based on
enable_* props. The consuming app decides which controls to render by passing the appropriate props.$3
> ⚠️ Deprecated:
HazoCollabFormSet is deprecated. Use HazoCollabFormView with mode="edit" instead.When using
HazoCollabFormSet with JSON-based field definitions, you can configure lock, visibility, and delete controls at the field level. This allows different fields to have different controls.Cascade Priority:
1. Field-level
FieldConfig properties → overrides form-level defaults
2. Form-level HazoCollabFormSetProps → applies to all fields
3. Hardcoded fallback → false (disabled)`typescript
'use client';import { HazoCollabFormSet, FieldConfig } from 'hazo_collab_forms';
const fields: FieldConfig[] = [
{
id: "customer_name",
label: "Customer Name",
field_type: "field",
component_type: "HazoCollabFormInputbox",
value: "",
// Uses form-level defaults
},
{
id: "tax_file_number",
label: "Tax File Number",
field_type: "field",
component_type: "HazoCollabFormInputbox",
value: "",
enable_lock: true,
enable_visibility_toggle: true, // Field-level override
enable_delete: false
},
{
id: "internal_notes",
label: "Internal Notes",
field_type: "field",
component_type: "HazoCollabFormTextArea",
value: "",
enable_lock: false, // Hide lock on this field
enable_visibility_toggle: true,
enable_delete: true // Show delete on this field
}
];
export default function CustomerForm() {
return (
fields_set={{ name: "Customer Form", fields }}
enable_lock={true} // Default for all fields
enable_visibility_toggle={false} // Default for all fields
enable_delete={false} // Default for all fields
/>
);
}
`Available FieldConfig Control Properties:
-
enable_lock?: boolean - Show lock icon
- enable_visibility_toggle?: boolean - Show visibility toggle icon
- enable_delete?: boolean - Show delete icon
- enable_data_ok?: boolean - Show data OK control
- enable_notes?: boolean - Show notes icon
- notes_panel_style?: 'popover' | 'slide_panel' - Notes panel style (default: 'popover')
- notes_background_color?: string - Notes panel background (Tailwind class)
- notes_save_mode?: 'explicit' | 'auto' - Notes save behavior (default: 'explicit')
- enable_chat?: boolean - Show chat icon
- chat_read_only?: boolean - View-only chat mode (no input, just viewing)
- locked?: boolean - Initial lock state
- visibility?: 'visible' | 'hidden' - Initial visibility state$3
Groups can set
enable_* props that automatically propagate to all child fields. This simplifies configuration when all fields in a group need the same controls.Cascade Priority (highest to lowest):
1. Field-level
enable_* in FieldConfig - Individual field override
2. Parent-group enable_* - Inherited from parent group
3. Form-level enable_* in HazoCollabFormSetProps - Applies to all fields
4. Hardcoded fallback - falseInheritable Control Props:
-
enable_lock - Lock/protect field control
- enable_visibility_toggle - Show/hide field control
- enable_delete - Delete field control
- enable_data_ok - Data OK validation control
- enable_notes - Notes control
- enable_chat - Chat control (requires chat_group_id at form level)`typescript
const fields: FieldConfig[] = [
{
id: "tax_details",
label: "Tax Information",
field_type: "group",
// These propagate to all children
enable_lock: true,
enable_visibility_toggle: true,
enable_notes: true,
enable_chat: true,
sub_fields: [
// Inherits all enable_* from parent
{ id: "tfn", label: "Tax File Number", component_type: "HazoCollabFormInputbox", value: "" },
// Override: explicitly disable lock and chat
{ id: "abn", label: "ABN", component_type: "HazoCollabFormInputbox", value: "", enable_lock: false, enable_chat: false },
]
}
];
`Utility Function:
`typescript
import { resolve_field_controls } from 'hazo_collab_forms';// Resolve effective controls for a field considering inheritance
const resolved = resolve_field_controls(field, parent_group, form_settings);
// Returns: { enable_lock, enable_visibility_toggle, enable_delete, enable_data_ok, enable_notes, enable_chat }
`$3
Control field metadata (visibility, locked, deleted) from a parent component without remounting the form. This enables syncing state between form and summary views.
Props:
-
field_metadata?: Record - External metadata for controlled sync
- on_field_metadata_change?: (change: FieldMetadataChange) => void - Unified callback for all metadata changesTypes:
`typescript
interface FieldMetadataInput {
visibility?: 'visible' | 'hidden'; // or hidden?: boolean
locked?: boolean;
data_ok?: boolean | DataOkState; // or dataOk
deleted?: boolean;
}interface FieldMetadataChange {
field_id: string;
visibility?: 'visible' | 'hidden';
locked?: boolean;
data_ok?: boolean | DataOkState;
deleted?: boolean;
}
`Usage Example:
`typescript
import { HazoCollabFormSet } from 'hazo_collab_forms';
import type { FieldMetadataInput, FieldMetadataChange } from 'hazo_collab_forms';
import { useState } from 'react';function MyForm({ fields }) {
const [fieldMetadata, setFieldMetadata] = useState>({
tax_file_number: { visibility: 'hidden', locked: true },
name: { visibility: 'visible', locked: false },
});
// Unified handler - called for any metadata change
const handleMetadataChange = (change: FieldMetadataChange) => {
setFieldMetadata(prev => ({
...prev,
[change.field_id]: { ...prev[change.field_id], ...change }
}));
};
return (
fields_set={fields}
enable_visibility_toggle={true}
enable_lock={true}
field_metadata={fieldMetadata}
on_field_metadata_change={handleMetadataChange}
/>
);
}
`Benefits:
- No component remount needed for metadata sync
- Parent has full control over metadata state
- Unified callback simplifies state management
- Backward compatible with existing callbacks (
on_visibility_change, on_lock_change, etc.)Normalization Helper:
`typescript
import { normalize_field_metadata_input } from 'hazo_collab_forms';// Normalize input that uses alternative naming conventions
const input = { hidden: true, dataOk: 'ok', locked: true };
const normalized = normalize_field_metadata_input(input);
// Result: { visibility: 'hidden', data_ok: 'ok', locked: true }
`$3
> ⚠️ Deprecated:
HazoCollabFormSummary is deprecated. Use HazoCollabFormView with mode="summary" instead.Display form data in a read-only summary format for review workflows:
`typescript
'use client';import { HazoCollabFormSummary, type FormSection } from 'hazo_collab_forms';
import { LuUser, LuFileText } from 'react-icons/lu';
const iconMap = {
LuUser: LuUser,
LuFileText: LuFileText,
};
const sections: FormSection[] = [
{
group_name: "Personal Details",
icon: "LuUser",
field_list: [
{ id: "name", label: "Full Name", component_type: "HazoCollabFormInputbox", value: "" },
{ id: "email", label: "Email", component_type: "HazoCollabFormInputbox", value: "" },
]
},
{
group_name: "Tax Details",
icon: "LuFileText",
field_list: [
{ id: "tfn", label: "Tax File Number", component_type: "HazoCollabFormInputbox", value: "" },
]
}
];
export default function FormSummaryPage() {
return (
sections={sections}
form_data={{
name: "John Doe",
email: "john@example.com",
tfn: "123456789"
}}
render_icon={(iconName) => {
const Icon = iconMap[iconName];
return Icon ? : null;
}}
show_edit_buttons={true}
on_edit_section={(index) => router.push(
/edit/${index})}
/>
);
}
`With Full Controls:
`typescript
sections={sections}
form_data={formData}
field_metadata={fieldMetadata} // Enable field controls - each enabled via props
// Use *_editable props to make controls clickable
enable_data_ok={true}
data_ok_mode="multi_state"
data_ok_editable={true}
enable_notes={true}
notes_panel_style="popover" // 'popover' (default) or 'slide_panel'
notes_background_color="bg-yellow-100" // Tailwind class
notes_save_mode="explicit" // 'explicit' (Save/Cancel) or 'auto' (save on blur)
enable_chat={true}
chat_group_id="review-session-123"
chat_read_only={false} // Set to true for view-only chat mode
enable_visibility_toggle={true}
visibility_editable={true}
enable_lock={true}
lock_editable={true}
enable_delete={true}
// Display settings
controls_display="popover"
show_edit_buttons={true}
on_edit_section={(index) => handleEdit(index)}
// Callbacks
on_data_ok_change={(fieldId, value) => console.log('Data OK changed:', fieldId, value)}
on_notes_change={(fieldId, notes) => console.log('Notes updated:', fieldId, notes)}
on_visibility_change={(fieldId, visibility) => console.log('Visibility changed:', fieldId, visibility)}
on_lock_change={(fieldId, locked) => console.log('Lock changed:', fieldId, locked)}
// Auto-actions
on_data_ok_hidden={true} // Auto-hide when marked OK
on_data_ok_protected={true} // Auto-lock when marked OK
/>
`Key Features:
- Composable design - enable only the features you need
- Section icons with custom rendering
- Edit navigation buttons
- Field controls: data_ok, notes, chat, lock, visibility, delete
- Two display modes: inline or popover
- Row-level controls on DataTables
- Auto-hide/auto-lock on data_ok change
- Chat read-only mode for view-only scenarios
$3
Control how chat status is polled for all fields in the form. Useful for reducing network requests and log verbosity.
Props:
-
chat_realtime_mode?: 'polling' | 'manual' - Form-level setting
- 'polling' (default): Automatically poll for chat status every 5 seconds
- 'manual': Only fetch chat status once on mount and when refresh() is called`typescript
// Disable automatic polling - only fetch once on mount
mode="edit"
sections={sections}
form_data={formData}
enable_chat={true}
chat_group_id="form-session-123"
chat_realtime_mode="manual" // No automatic polling
/>
`use_field_chat_status Hook:
For custom implementations, use the
use_field_chat_status hook directly:`typescript
import { use_field_chat_status } from 'hazo_collab_forms';const { chat_status, refresh, is_loading } = use_field_chat_status({
field_ids: ['field1', 'field2'],
chat_group_id: 'group-id',
enabled: true,
realtime_mode: 'manual', // or 'polling' (default)
poll_interval: 5000, // only used when realtime_mode is 'polling'
});
// chat_status: Record
// refresh(): manually trigger a status refresh
`$3
Chat Display and Behavior:
The package provides several props to customize chat appearance and behavior:
Props:
-
chat_read_only?: boolean - View-only mode (users can see chat but cannot send messages)
- chat_hide_references?: boolean - Hide the references section in chat
- chat_hide_sidebar?: boolean - Hide the entire document viewer sidebar (requires hazo_chat v5.2.0+)
- chat_hide_preview?: boolean - Hide message preview/attachment preview
- chat_display_mode?: 'embedded' | 'side_panel' | 'overlay' - Controls chat rendering mode (default: 'embedded')
- chat_container_element?: HTMLElement | null - Container for portal rendering with certain display modes
- chat_log_polling?: boolean - Enable polling console logs for debugging (default: false)`typescript
// HazoCollabFormView - customized chat experience
mode="edit"
sections={sections}
form_data={formData}
enable_chat={true}
chat_group_id={groupId}
chat_read_only={false}
chat_hide_references={true} // Hide references section
chat_hide_sidebar={false} // Show sidebar (requires hazo_chat v5.2.0+)
chat_hide_preview={false} // Show message previews
chat_display_mode="side_panel" // Render as side panel
chat_log_polling={false} // Disable polling logs
/>// HazoCollabFormSet - all fields with customized chat
fields_set={fields}
chat_group_id={groupId}
chat_read_only={true}
chat_hide_references={true}
chat_display_mode="overlay"
/>
// HazoCollabFormSummary - view-only chat
sections={sections}
form_data={formData}
enable_chat={true}
chat_group_id={groupId}
chat_read_only={true}
/>
// Field-level override in FieldConfig
const fields: FieldConfig[] = [
{
id: "active_discussion",
label: "Active Discussion",
component_type: "HazoCollabFormInputbox",
chat_read_only: false // Can chat on this field
},
{
id: "archived_data",
label: "Archived Data",
component_type: "HazoCollabFormInputbox",
chat_read_only: true // View-only chat
}
];
`$3
Add help tooltips to field labels and data table columns using the
tooltip prop. Tooltips display on hover using shadcn's HoverCard component, with automatic fallback to native title attribute if HoverCard is not available.Usage in Form Fields:
`typescript
import { HazoCollabFormInputbox } from 'hazo_collab_forms'; label="Tax File Number"
tooltip="Your 9-digit tax identification number (TFN)"
value={tfn}
onChange={setTfn}
/>
// Or with full TooltipConfig
label="ABN"
tooltip={{
enabled: true,
content: "Australian Business Number - 11 digits",
position: "right" // optional
}}
value={abn}
onChange={setAbn}
/>
`Usage in Data Tables:
`typescript
const columns: DataTableColumn[] = [
{
id: "amount",
label: "Amount",
type: "number",
tooltip: "Enter the invoice amount in AUD",
},
{
id: "date",
label: "Invoice Date",
type: "date",
tooltip: {
enabled: true,
content: "The date shown on the invoice document"
}
}
];
`Tooltip in FieldConfig:
`typescript
const fields: FieldConfig[] = [
{
id: "gst_amount",
label: "GST Amount",
component_type: "HazoCollabFormInputbox",
tooltip: "10% of the invoice amount (automatically calculated if empty)",
value: ""
}
];
`Optional Dependency:
- Tooltips require
hover-card shadcn component for best experience
- Automatically falls back to native HTML title attribute if hover-card is not installed
- Install with: npx shadcn@latest add hover-card$3
Visual form builder for creating HazoCollabFormSet configurations with drag-and-drop interface. Build complex form structures without writing JSON manually.
Basic Usage:
`typescript
'use client';import { HazoTemplateGenerator } from 'hazo_collab_forms';
import { useState } from 'react';
export default function TemplateBuilder() {
const [template, setTemplate] = useState({
sections: [],
icons_behaviour: {}
});
return (
initial_template={template}
on_template_change={setTemplate}
on_save={(template) => {
console.log('Saved template:', template);
// Save to database or file
}}
on_cancel={() => router.push('/templates')}
/>
);
}
`Features:
- Visual Tree Editor: Organize sections, groups, and fields hierarchically
- Live Preview: See your form render in real-time as you build
- Property Editors: Configure field properties, validation, and controls
- Data Table Editor: Visual builder for complex data table configurations
- Icons Behaviour Editor: Configure role-based control visibility
- Template Library: Load and save reusable templates
- Import/Export: JSON import/export for version control
- Full-Screen Preview: Test your form in full-screen dialog
- Undo/Redo: Complete history navigation
Props:
| Prop | Type | Description |
|------|------|-------------|
|
initial_template | Section[] | Starting template structure |
| on_template_change | callback | Called when template changes |
| on_save | callback | Save button handler |
| on_cancel | callback | Cancel button handler |
| available_templates | TemplateOption[] | Predefined templates to load |
| on_load_template | callback | Called when template is loaded |
| preview_chat_group_id | string | Chat group ID for live preview |
| readonly | boolean | Disable editing (view-only mode) |
| show_import_export | boolean | Show import/export buttons |
| show_controls_tab | boolean | Show advanced controls configuration |Template Structure:
The generator produces standard HazoCollabFormSet configuration:
`typescript
interface TemplateOutput {
sections: Section[];
icons_behaviour?: IconsBehaviour;
}
`Required shadcn Components:
- resizable (for panel layout)
- tabs (for editor interface)
- hover-card (optional, for tooltips)
Install with:
npx shadcn@latest add resizable tabs$3
A unified component that combines edit, summary, print, and approval views with shared context. Switch between view modes without remounting, preserving state and field metadata.
> Note:
HazoCollabFormView is the recommended component for new projects. It replaces the older HazoCollabFormSet and HazoCollabFormSummary components which are now deprecated.Basic Usage:
`typescript
'use client';import { HazoCollabFormView, type ViewMode } from 'hazo_collab_forms';
import { useState } from 'react';
export default function UnifiedFormPage() {
const [viewMode, setViewMode] = useState('edit');
const [formData, setFormData] = useState>({});
return (
{/ View mode switcher /}
sections={sections}
view_mode={viewMode}
form_data={formData}
on_form_data_change={setFormData}
icons_behaviour={iconsBehaviour}
active_role="tax_agent"
enable_chat={true}
chat_group_id="form-session-123"
/>
);
}
`View Modes:
| Mode | Description |
|------|-------------|
|
edit | Full form editing with all field controls |
| summary | Read-only summary view for review |
| print | Print-optimized layout |
| approval | Review with data_ok approval workflow |Key Props:
| Prop | Type | Description |
|------|------|-------------|
|
sections | ViewSection[] | Form sections (same as HazoCollabFormSet) |
| view_mode | ViewMode | Current view mode |
| form_data | Record | Form values |
| on_form_data_change | callback | Called when form data changes |
| field_metadata | Record | Field metadata (visibility, lock, etc.) |
| on_field_metadata_change | callback | Called when metadata changes |
| icons_behaviour | IconsBehaviour | Role-based control configuration |
| active_role | string | Current user's role |Context Hooks:
Access form state from nested components using hooks:
`typescript
import {
useFormViewContext,
useViewMode,
useFormData,
useFieldMetadata,
useFieldValue,
useFieldMeta,
useApprovalStats,
} from 'hazo_collab_forms';function FieldComponent({ fieldId }) {
const viewMode = useViewMode();
const value = useFieldValue(fieldId);
const { data_ok, locked, visibility } = useFieldMeta(fieldId);
const { total, approved, pending } = useApprovalStats();
// Render based on view mode and field state
}
`$3
Display reference or comparison values below form fields, such as prior year tax data or baseline values. Reference data supports automatic formatting for currency, booleans, and custom styling.
Basic Usage:
`typescript
import { HazoCollabFormView, type ReferenceData } from 'hazo_collab_forms';const referenceData: ReferenceData = {
salary_income: {
value: 82450.00, // Formatted as $82,450.00
label: "Prior Year (2023)",
background_color: "bg-blue-50" // Optional Tailwind class
},
has_dependents: {
value: true, // Formatted as "Yes"
label: "2023"
},
filing_status: {
value: "married", // String pass-through
label: "Last year"
}
};
mode="edit"
sections={sections}
form_data={formData}
reference_data={referenceData}
/>
`Value Formatting:
| Type | Formatting | Example |
|------|------------|---------|
| Boolean | "Yes" / "No" |
true → "Yes" |
| Number >= 100 | Currency (AUD) | 82450 → "$82,450.00" |
| Number < 100 | Plain | 15 → "15" |
| String | Pass-through | "text" → "text" |Supported Modes:
- ✅
mode="edit" - Edit view
- ✅ mode="summary" - Summary view
- ✅ mode="print" - Print view
- ✅ mode="approval" - Approval viewPriority:
Field-level props (
reference_value, reference_label, reference_tag_background_color) take precedence over form-level reference_data.$3
Configure which control icons are visible and enabled based on user roles. Supports automatic consolidation into kebab (3-dots) menus and extraction of active controls.
JSON Configuration Options:
You can configure icons_behaviour in two ways:
1. Embedded (original): Include icons_behaviour directly in the form JSON file
2. Separate file (recommended): Store icons_behaviour in a dedicated JSON file for reuse across forms
Separate File Approach (v1.7.0+):
`
public/data/form-sets/
tax_form_individual_full.json # Sections only
icon_behaviour.json # Icons behaviour config (reusable)
`Load both files and pass icons_behaviour as a prop:
`typescript
// Load form configuration
const sectionsResponse = await fetch('/data/form-sets/tax_form_individual_full.json');
const { sections } = await sectionsResponse.json();const behaviourResponse = await fetch('/data/form-sets/icon_behaviour.json');
const icons_behaviour = await behaviourResponse.json();
// Pass to HazoCollabFormSet
section={section}
icons_behaviour={icons_behaviour}
active_role="tax_agent"
/>
// Pass to HazoCollabFormSummary
sections={sections}
icons_behaviour={icons_behaviour}
active_role="tax_agent"
form_data={formData}
/>
`Prop Priority:
1.
icons_behaviour prop (if provided)
2. section.icons_behaviour or sections[0]?.icons_behaviour (backward compatible)This separation allows:
- Reusing the same icon behavior across multiple forms
- Changing roles without modifying form structure
- Easier maintenance of large form configurations
Configuration Structure:
`typescript
const icons_behaviour: IconsBehaviour = {
// Data OK display mode
data_ok_mode: 'multistate', // or 'checkbox' // Controls that have borders
border_on: ['control_data_ok'],
// Display order (left to right)
icon_order: [
'control_data_ok',
'control_notes',
'control_chat',
'control_visibility',
'control_lock',
'control_delete'
],
// Always shown individually (never in kebab)
always_extracted: ['control_data_ok'],
// Extracted when they have activity
extracted_on_value: ['control_notes', 'control_chat'],
// Role definitions
roles: [
{
role_id: 'tax_agent',
role_name: 'Tax Agent',
role_description: 'Full access to all controls',
controls: {
control_data_ok: { visible: true, enabled: true },
control_notes: { visible: true, enabled: true },
control_chat: { visible: true, enabled: true },
control_visibility: { visible: true, enabled: true },
control_lock: { visible: true, enabled: true },
control_delete: { visible: true, enabled: false }
}
},
{
role_id: 'client',
role_name: 'Client',
role_description: 'Limited to chat and notes',
controls: {
control_chat: { visible: true, enabled: true },
control_notes: { visible: true, enabled: true },
control_data_ok: { visible: true, enabled: false },
control_visibility: { visible: false, enabled: false },
control_lock: { visible: false, enabled: false },
control_delete: { visible: false, enabled: false }
}
}
]
};
`Usage in HazoCollabFormSet:
`typescript
fields_set={{
name: "Tax Return Form",
fields: [...],
icons_behaviour: icons_behaviour
}}
active_role="tax_agent" // Current user's role
/>
`Usage in HazoCollabFormSummary:
`typescript
sections={sections.map(section => ({
...section,
icons_behaviour: icons_behaviour
}))}
active_role="client"
form_data={formData}
/>
`Consolidation Rules (Automatic):
When 2+ non-extracted controls are visible, they consolidate into a kebab menu:
1. Data OK: Always inline (hardcoded, never in kebab)
2. Controls in always_extracted: Always inline
3. Controls in extracted_on_value with activity: Extracted from kebab (shown inline)
-
control_notes: Extracted when notes exist
- control_chat: Extracted when messages exist
4. Remaining visible controls: Kebab menu if 2+, otherwise inlineField/Group Level Override:
Set
show_icons: true on fields or groups to show icons according to the active role:`typescript
const fields: FieldConfig[] = [
{
id: "sensitive_group",
field_type: "group",
label: "Sensitive Information",
show_icons: true, // Show role-based icons for this group
sub_fields: [
{
id: "ssn",
label: "Social Security Number",
component_type: "HazoCollabFormInputbox",
show_icons: true, // Show role-based icons for this field
value: ""
}
]
}
];
`Benefits:
- Single configuration for entire form
- Consistent behavior across form and summary views
- Automatic UI optimization (kebab consolidation)
- Easy role switching without code changes
- Visual editor in Template Generator
$3
Add, edit, and manage form fields at runtime using dialog components.
HazoAddFieldDialog:
Add new fields or groups to your form dynamically:
`typescript
import { HazoAddFieldDialog, type NewFieldDefinition } from 'hazo_collab_forms';
import { useState } from 'react';export default function DynamicForm() {
const [isOpen, setIsOpen] = useState(false);
const handleAddField = (field: NewFieldDefinition) => {
console.log('New field:', field);
// Add field to your form sections
};
return (
<>
open={isOpen}
on_open_change={setIsOpen}
on_add={handleAddField}
parent_type="group" // 'section' | 'group'
parent_id="personal_info"
/>
>
);
}
`HazoEditFieldDialog:
Edit existing field configurations:
`typescript
import { HazoEditFieldDialog } from 'hazo_collab_forms'; open={isEditOpen}
on_open_change={setIsEditOpen}
field={selectedField}
on_save={(updated) => updateField(updated)}
on_delete={() => deleteField(selectedField.id)} // Optional delete callback
/>
`HazoFieldSelectorDialog:
Search and select fields from a template:
`typescript
import { HazoFieldSelectorDialog } from 'hazo_collab_forms'; open={isSelectorOpen}
on_open_change={setIsSelectorOpen}
template={formTemplate}
on_select={(selection) => {
// Handle selected fields
console.log('Selected:', selection);
}}
/>
`$3
The package exports utility functions for working with form structures:
`typescript
import {
// Field traversal
traverse_fields,
traverse_sections,
collect_field_ids,
collect_leaf_field_ids,
should_render_field,
is_field_empty,
get_field_value, // Dependency checking
parse_dependency,
check_dependency,
// Field metadata normalization
normalize_field_metadata,
normalize_field_metadata_input,
} from 'hazo_collab_forms';
// Traverse all fields in sections
traverse_sections(sections, (field, path) => {
console.log(
Field: ${field.id} at path: ${path.join('/')});
});// Check if field should render based on dependencies
const shouldRender = should_render_field(field, formData, {
check_dependencies: true,
show_empty_fields: false,
});
// Parse and check field dependencies
const dependency = parse_dependency(field.depends_on);
const isActive = check_dependency(dependency, formData);
`Components
$3
| Component | Description | Required shadcn |
|-----------|-------------|-----------------|
|
HazoCollabFormInputbox | Text input with validation | button, label, dialog, tooltip |
| HazoCollabFormTextArea | Multi-line text input | button, label, dialog, tooltip |
| HazoCollabFormCheckbox | Boolean toggle | button, label, dialog, tooltip |
| HazoCollabFormCombo | Dropdown select with search | + popover, command |
| HazoCollabFormRadio | Radio button group | button, label, dialog, tooltip |
| HazoCollabFormDate | Date or date-range picker | + calendar |
| HazoCollabFormDoc | Document/file-only field | button, label, dialog, tooltip, accordion |
| HazoCollabFormGroup | Field grouping container | button, label, dialog, tooltip |
| HazoCollabFormDataTable | Editable data table with aggregations | button, label, dialog, tooltip |$3
| Component | Description | Required shadcn |
|-----------|-------------|-----------------|
|
HazoCollabFormView | Recommended - Unified multi-mode view (Edit/Summary/Print/Approval) | all components |
| HazoCollabFormSet | ⚠️ Deprecated - Use HazoCollabFormView with mode="edit" | all components |
| HazoCollabFormSummary | ⚠️ Deprecated - Use HazoCollabFormView with mode="summary" | button, label, popover |$3
| Component | Description | Required shadcn |
|-----------|-------------|-----------------|
|
HazoTemplateGenerator | Visual form builder | button, label, dialog, tooltip, tabs, resizable |
| HazoAddFieldDialog | Add new fields dynamically | dialog, popover, command |
| HazoAddGroupDialog | Add new groups dynamically | dialog |
| HazoEditFieldDialog | Edit field configurations | dialog, popover, command |
| HazoFieldSelectorDialog | Search and select fields | dialog, command |$3
| Feature | Required shadcn |
|---------|-----------------|
| File Upload (
accept_files prop) | + accordion (required) |
| Field Tooltips (tooltip prop) | + hover-card (optional) |Import Paths
`typescript
// Default: All client-safe components and utilities
import { HazoCollabFormInputbox, cn } from 'hazo_collab_forms';// Components only
import { HazoCollabFormInputbox } from 'hazo_collab_forms/components';
// Utilities only
import { cn, use_collab_chat } from 'hazo_collab_forms/utils';
// Server-only (config functions)
import { get_config } from 'hazo_collab_forms/lib';
`Troubleshooting
$3
Different Hazo packages require different versions of
lucide-react. Add this to your package.json:`json
"overrides": {
"lucide-react": "^0.553.0"
}
`$3
If you see errors about missing components, install the specific shadcn component:
`bash
npx shadcn@latest add [component-name]
`Important: If you're using file upload functionality (
accept_files prop on any form field), you must install the accordion component:`bash
npx shadcn@latest add accordion
`$3
Run the verification tool to check for common issues:
`bash
npx hazo-collab-forms-verify
`See SETUP_CHECKLIST.md for detailed troubleshooting.
---
Development
$3
- Root: ES module npm package
- test-app: Next.js application for testing
$3
`bash
npm run build # Build the package
npm run dev:package # Watch mode for development
npm run dev:test-app # Build and run test app
npm run build:test-app # Build for production
npm run clean # Remove dist directory
`$3
-
tsconfig.json: Development (bundler module resolution)
- tsconfig.build.json: Build (Node16 for ES module output)$3
All exports use explicit
.js extensions as required for ES modules:`typescript
export * from './lib/index.js';
export * from './components/index.js';
``MIT