@iris711/form-renderer
v1.1.0
Published
Universal form rendering component for CUBE Apply and CUBE Enforce
Downloads
190
Readme
@cube/form-renderer
Universal form rendering component for CUBE Apply and CUBE Enforce applications. This package enables both platforms to render forms identically with full feature parity while maintaining platform-specific customisations.
Features
- 20+ Field Types: text, number, textarea, email, phone, date, datetime, radio, select, checkbox, multi-select, file, photo_upload, and more
- Advanced CUBE Fields: location (Address Search), business_lookup (Companies House), activities_commodities, party_creation, choice_with_pricing, price_calculation
- Layout Fields: h1, h2, h3, paragraph, divider, spacer
- Component Injection: Fully customisable UI components, icons, and styling
- Mobile Optimised: Pre-built mobile components with touch-friendly interfaces
- Value Handling: Proper nullish coalescing to preserve falsy values (0, false, empty arrays)
- Conditional Visibility: Show/hide fields based on form state
- Input Masking: Support for formatted inputs (phone numbers, etc.)
Installation
npm install @cube/form-rendererPeer Dependencies
This package requires:
{
"react": "^18.0.0",
"react-dom": "^18.0.0"
}Basic Usage
CUBE Apply Integration (with shadcn/ui)
import { SmartFormFieldRenderer } from '@cube/form-renderer';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { HelpCircle, Search, X, AlertCircle } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';
// Import specialized CUBE components
import { ActivitiesCommoditiesField } from '@/components/ActivitiesCommoditiesField';
import { PartyCreationField } from '@/components/PartyCreationField';
import AddressEntrySelector from '@/components/AddressEntrySelector';
import { ChoiceWithPricingField } from '@/components/form-fields/ChoiceWithPricingField';
import { PriceCalculationField } from '@/components/form-fields/PriceCalculationField';
// Import utilities
import { shouldShowField, applyInputMask, getRawValue } from '@/lib/formEnhancements';
import { API } from '@/lib/constants';
function MyFormRenderer({ field, value, onChange, formValues }) {
return (
<SmartFormFieldRenderer
field={field}
value={value}
onChange={onChange}
formValues={formValues}
components={{
Input,
Label,
Button,
Tooltip,
TooltipProvider,
TooltipTrigger,
TooltipContent,
}}
icons={{
HelpIcon: HelpCircle,
SearchIcon: Search,
CloseIcon: X,
AlertIcon: AlertCircle,
}}
specializedComponents={{
ActivitiesCommoditiesField,
PartyCreationField,
AddressEntrySelector,
ChoiceWithPricingField,
PriceCalculationField,
}}
utilityFunctions={{
shouldShowField,
applyInputMask,
getRawValue,
}}
useQuery={useQuery}
apiConstants={{
BUSINESSES_BASE: API.BUSINESSES.BASE,
BUSINESSES_SEARCH: API.BUSINESSES.SEARCH,
}}
/>
);
}CUBE Enforce Integration (Mobile Optimised)
import { SmartFormFieldRenderer, MobileInput, MobileLabel, MobileButton } from '@cube/form-renderer';
import { HelpCircle, Search, X, AlertCircle } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';
// Import specialized CUBE components (these should exist in CUBE Enforce)
import { ActivitiesCommoditiesField } from '@/components/ActivitiesCommoditiesField';
import { PartyCreationField } from '@/components/PartyCreationField';
import AddressEntrySelector from '@/components/AddressEntrySelector';
function MobileFormRenderer({ field, value, onChange, formValues }) {
return (
<SmartFormFieldRenderer
field={field}
value={value}
onChange={onChange}
formValues={formValues}
components={{
Input: MobileInput,
Label: MobileLabel,
Button: MobileButton,
// Mobile doesn't use complex tooltips
Tooltip: ({ children }) => <>{children}</>,
TooltipProvider: ({ children }) => <>{children}</>,
TooltipTrigger: () => null,
TooltipContent: () => null,
}}
icons={{
HelpIcon: HelpCircle,
SearchIcon: Search,
CloseIcon: X,
AlertIcon: AlertCircle,
}}
specializedComponents={{
ActivitiesCommoditiesField,
PartyCreationField,
AddressEntrySelector,
// Pricing fields may not be needed in enforcement
}}
useQuery={useQuery}
classNames={{
container: "mb-4",
label: "block text-base font-semibold text-gray-900 mb-2",
input: "w-full px-4 py-3 border-2 border-gray-300 rounded-lg text-base",
}}
/>
);
}API Reference
SmartFormFieldRendererProps
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| field | FormField | Yes | The field definition object |
| value | string \| string[] \| boolean | Yes | Current field value |
| onChange | (value: any) => void | Yes | Value change handler |
| formValues | Record<string, any> | Yes | All form values for conditional logic |
| allFields | FormField[] | No | All form fields (needed for price_calculation) |
| components | UIComponents | Yes | UI component injection |
| icons | IconComponents | Yes | Icon component injection |
| specializedComponents | SpecializedComponents | No | Advanced field components |
| utilityFunctions | UtilityFunctions | No | Utility function injection |
| useQuery | QueryHook | No | TanStack Query hook (required for business_lookup) |
| classNames | StyleClassNames | No | Custom styling classes |
| apiConstants | object | No | API endpoint configuration |
FormField Type
interface FormField {
id: string;
label: string;
type: string;
required?: boolean;
placeholder?: string;
options?: string[];
configuration?: {
tooltip?: string;
helpText?: string;
inputMask?: string;
conditionalVisibility?: {
enabled: boolean;
conditions: Array<{
fieldValue: string;
operator: string;
value: any;
}>;
logic: 'AND' | 'OR';
};
minDate?: string;
maxDate?: string;
spacingSize?: 'small' | 'medium' | 'large' | 'custom';
customSpacing?: number;
// ... other field-specific configuration
};
}Supported Field Types
Basic Fields
text- Single-line text inputnumber- Numeric inputtextarea- Multi-line text inputemail- Email address inputphone- Phone number input with optional maskingdate- Date picker with relative date supportdatetime- Date and time pickerradio- Radio button groupselect- Dropdown selectcheckbox- Single checkbox or checkbox groupmulti-select- Multiple selection checkboxesfile- File uploadphoto_upload- Image upload with AI analysis
Layout Fields
h1,h2,h3- Headingsparagraph- Paragraph textdivider- Horizontal line separatorspacer- Vertical spacing
Advanced Fields (require specialized components)
location- Address search with gazetteer integrationbusiness_lookup- Companies House business searchactivities_commodities- UK licence activities/commodities selectorparty_creation- Configurable party formschoice_with_pricing- Radio buttons with dynamic pricingprice_calculation- Formula-based price calculation
Value Handling
The package uses nullish coalescing (??) throughout to preserve falsy values:
// ✅ Correct - preserves 0, false, empty arrays
value={value ?? ''}
// ❌ Wrong - 0 becomes '', false becomes '', [] becomes ''
value={value || ''}This ensures:
- Numeric
0is preserved in number fields and price calculations - Boolean
falseis preserved in checkbox fields - Empty arrays
[]are preserved in multi-select fields
Mobile Components
Pre-built mobile-optimised components are included:
import {
MobileInput,
MobileLabel,
MobileButton,
MobileTooltip,
MobileTooltipProvider,
MobileTooltipTrigger,
MobileTooltipContent
} from '@cube/form-renderer';Features:
- 16px font size on inputs (prevents iOS zoom on focus)
- Touch-friendly targets (larger padding, bigger hit areas)
- Simplified tooltips (not suitable for touch devices)
- Active state animations for better feedback
Utility Functions
Export utility functions for custom implementations:
import {
shouldShowField,
applyInputMask,
getRawValue,
parseRelativeDate,
formatDateForInput
} from '@cube/form-renderer';
// Check conditional visibility
if (shouldShowField(field.configuration?.conditionalVisibility, formValues)) {
// Render field
}
// Apply input mask
const masked = applyInputMask('1234567890', '(999) 999-9999');
// Result: (123) 456-7890
// Parse relative dates
const date = parseRelativeDate('today'); // Date object for today
const future = parseRelativeDate('+7 days'); // 7 days from nowTypeScript Support
Full TypeScript definitions are included. Import types as needed:
import type {
FormField,
SmartFormFieldRendererProps,
UIComponents,
IconComponents,
SpecializedComponents
} from '@cube/form-renderer';License
MIT
Support
For issues or questions, contact the CUBE development team.
