Form components in Loopar Framework provide a complete set of input controls for collecting user data. All form components are built on top of React Hook Form and include validation, error handling, and designer integration.
The input component is the most versatile form element, supporting multiple formats and validation types.
packages/loopar/src/components/input.jsx
export default function Input ( props ) {
const { renderInput , data } = BaseInput ( props );
const type = props . type || inputType [( data ?. format || "data" ). toLowerCase ()] || "text" ;
return renderInput (( field ) => {
return (
<>
< FormLabel { ... props } field = { field } />
< FormControl >
< FormInput
placeholder = { data . placeholder || data . label }
{ ... field }
type = { type }
min = { data . min }
max = { data . max }
className = { field . isInvalid ? invalidClass . border : "" }
/>
</ FormControl >
{ data . description && (
< FormDescription > { data . description } </ FormDescription >
) }
</>
)
});
}
The input component supports multiple formats defined in inputType:
packages/loopar/core/global/element-definition.js
export const inputType = {
data: "text" ,
text: "text" ,
email: "email" ,
decimal: "number" ,
percent: "number" ,
currency: "text" ,
int: "number" ,
long_int: "number" ,
read_only: "text" ,
};
Properties
Field name for form submission
Display label for the input
Input format: text, email, decimal, percent, currency, int, long_int
Helper text below the input
Minimum value (for number inputs)
Maximum value (for number inputs)
Examples
Text Input
Email Input
Number Input
{
"element" : "input" ,
"data" : {
"name" : "username" ,
"label" : "Username" ,
"placeholder" : "Enter your username" ,
"required" : true
}
}
{
"element" : "input" ,
"data" : {
"name" : "email" ,
"label" : "Email Address" ,
"format" : "email" ,
"required" : true
}
}
{
"element" : "input" ,
"data" : {
"name" : "age" ,
"label" : "Age" ,
"format" : "int" ,
"min" : 0 ,
"max" : 120
}
}
Select
The select component provides dropdown selection with support for both local options and remote data sources.
packages/loopar/src/components/select.jsx
export default function MetaSelect ( props ) {
const [ rows , setRows ] = useState ([]);
const [ isLoading , setIsLoading ] = useState ( false );
const { renderInput , data } = BaseInput ( props );
const isLocal = useMemo (() => {
const options = data . options ;
return ! options || typeof options === "object" || options . includes ( " \n " );
}, [ data . options ]);
const getServerData = useCallback (( q , page = 1 , append = false ) => {
return new Promise (( resolve , reject ) => {
loopar . send ({
action: `/desk/ ${ getModel () } /search` ,
params: { q , page , limit: paginationRef . current . limit },
success : ( r ) => {
titleFields . current = r . title_fields ;
const preparedRows = getPrepareOptions (
r . rows . map ( row => ({
value: row . name ,
formattedValue: row . formattedValue ,
label: row
}))
);
setRows ( prevRows => append ? [ ... prevRows , ... preparedRows ] : preparedRows );
resolve ( preparedRows );
},
});
});
}, [ data . disabled , getModel , getPrepareOptions ]);
return renderInput (( field ) => (
<>
< FormLabel { ... props } field = { field } />
< Select
field = { field }
options = { rows }
search = { search }
loadMore = { loadMore }
data = { data }
onSelect = { field . onChange }
isLoading = { isLoading }
/>
</>
));
}
Properties
Field name for form submission
data.options
string | array | object
required
Options for the select. Can be:
String: "option1\noption2\noption3" (newline-separated)
String: "value1:Label 1\nvalue2:Label 2" (with labels)
Array: ["option1", "option2"]
Object: {"value1": "Label 1", "value2": "Label 2"}
Entity name: "User" (loads from database)
Helper text below the select
Examples
Simple Options
Key-Value Options
Entity Select
{
"element" : "select" ,
"data" : {
"name" : "status" ,
"label" : "Status" ,
"options" : "active \n inactive \n pending"
}
}
{
"element" : "select" ,
"data" : {
"name" : "country" ,
"label" : "Country" ,
"options" : "us:United States \n uk:United Kingdom \n ca:Canada"
}
}
{
"element" : "select" ,
"data" : {
"name" : "user" ,
"label" : "Select User" ,
"options" : "User"
}
}
Checkbox
The checkbox component provides boolean input with checked/unchecked states.
packages/loopar/src/components/checkbox.jsx
export default function MetaCheckbox ( props ) {
return < DefaultCheckbox { ... props } />
}
Properties
Example
{
"element" : "checkbox" ,
"data" : {
"name" : "terms_accepted" ,
"label" : "I accept the terms and conditions" ,
"required" : true
}
}
Switch
The switch component is similar to checkbox but with a toggle switch UI.
Example
{
"element" : "switch" ,
"data" : {
"name" : "notifications_enabled" ,
"label" : "Enable Notifications"
}
}
Date
The date component provides a date picker with calendar interface.
packages/loopar/src/components/date.jsx
export default function DatePicker ( props ) {
const { renderInput , data , value } = BaseInput ( props );
return renderInput ( field => {
const fieldDate = new Date ( field . value );
const setDateHandler = ( val ) => {
const newDate = dayjs ( new Date ( val ));
const [ year , month , day ] = [ newDate . year (), newDate . month () + 1 , newDate . date ()];
const date = dayjs ( field . value ). toDate ();
date . setFullYear ( year );
date . setMonth ( month - 1 );
date . setDate ( day );
value ( date );
}
return (
< FormItem className = "flex flex-col" >
< FormLabel > { data . label } </ FormLabel >
< Popover >
< PopoverTrigger asChild >
< FormControl >
< Button variant = { "outline" } >
{ field . value ? (
format ( dayjs ( fieldDate ). isValid () ? fieldDate : new Date (), "PPP HH:mm:ss a" )
) : (
< span > Pick a date </ span >
) }
< CalendarIcon className = "ml-auto h-4 w-4 opacity-50" />
</ Button >
</ FormControl >
</ PopoverTrigger >
< PopoverContent className = "w-auto p-0" align = "start" >
< Calendar
mode = "single"
selected = { fieldDate }
onSelect = { setDateHandler }
disabled = { ( date ) =>
date > new Date () || date < new Date ( "1900-01-01" )
}
initialFocus
/>
</ PopoverContent >
</ Popover >
</ FormItem >
)
});
}
Properties
data.format
string
default: "YYYY-MM-DD"
Date format string
Example
{
"element" : "date" ,
"data" : {
"name" : "birth_date" ,
"label" : "Date of Birth" ,
"required" : true
}
}
Date Time
The date_time component includes both date and time selection.
Example
{
"element" : "date_time" ,
"data" : {
"name" : "appointment" ,
"label" : "Appointment Time" ,
"format" : "YYYY-MM-DD HH:mm:ss"
}
}
Time
The time component provides time-only selection.
Example
{
"element" : "time" ,
"data" : {
"name" : "start_time" ,
"label" : "Start Time" ,
"format" : "HH:mm:ss"
}
}
Textarea
The textarea component provides multi-line text input.
Properties
Example
{
"element" : "textarea" ,
"data" : {
"name" : "description" ,
"label" : "Description" ,
"rows" : 10 ,
"placeholder" : "Enter a detailed description..."
}
}
The button component triggers actions within forms.
packages/loopar/src/components/button.jsx
export default function MetaButton ( props ) {
const data = props . data || {};
const { docRef } = useDocument ();
const handleClick = ( e ) => {
e . preventDefault ();
if ( data . action && docRef ) {
if ( ! docRef [ data . action ]) {
loopar . throw ( "Action not Defined" , `Action ${ data . action } not found in model` )
}
docRef [ data . action ]();
}
}
return (
< Button
{ ... loopar . utils . renderizableProps ( props ) }
variant = { getVariant () }
onClick = { handleClick }
>
{ data . label || "Button" }
</ Button >
);
}
Properties
Method name to call on the document model (e.g., save, delete, print)
Button style: primary, secondary, default, ghost, destructive
Example
{
"element" : "button" ,
"data" : {
"label" : "Save" ,
"action" : "save" ,
"variant" : "primary"
}
}
The file_input component handles file uploads.
Example
{
"element" : "file_input" ,
"data" : {
"name" : "attachment" ,
"label" : "Upload File" ,
"accept" : ".pdf,.doc,.docx"
}
}
The image_input component is specialized for image uploads.
Example
{
"element" : "image_input" ,
"data" : {
"name" : "profile_picture" ,
"label" : "Profile Picture" ,
"accept" : "image/*"
}
}
Color Picker
The color_picker component provides color selection.
Example
{
"element" : "color_picker" ,
"data" : {
"name" : "theme_color" ,
"label" : "Theme Color"
}
}
Radio Group
The radio_group component provides mutually exclusive options.
Example
{
"element" : "radio_group" ,
"data" : {
"name" : "gender" ,
"label" : "Gender"
},
"elements" : [
{
"element" : "radio_item" ,
"data" : {
"name" : "male" ,
"label" : "Male" ,
"value" : "male"
}
},
{
"element" : "radio_item" ,
"data" : {
"name" : "female" ,
"label" : "Female" ,
"value" : "female"
}
}
]
}
Validation
All form components support validation through the dataInterface class:
packages/loopar/core/global/element-definition.js
class DataInterface {
validatorRules () {
var type = ( this . element === INPUT ? this . data . format || this . element : this . element ) || 'text' ;
type = type . charAt ( 0 ). toUpperCase () + type . slice ( 1 );
if ( this [ 'is' + type ]) {
return this [ 'is' + type ]();
}
return { valid: true };
}
isEmail () {
var regex = / ^ (( [ ^ <>() \[\]\\ .,;:\s@" ] + ( \. [ ^ <>() \[\]\\ .,;:\s@" ] + ) * ) | ( " . + " )) @/ ;
return {
valid: regex . test ( this . value ),
message: 'Invalid email address'
}
}
validatorRequired () {
const required = [ true , 'true' , 1 , '1' ]. includes ( this . data . required );
return {
valid: ! required || ! ( typeof this . value == "undefined" || this . value . toString (). length === 0 ),
message: ` ${ this . __label () } is required`
}
}
}
All form components extend from BaseInput which provides common functionality:
Form integration with React Hook Form
Validation handling
Error display
Label rendering
Description support
Next Steps
Layout Components Structure your forms with rows and columns
Data Display Display submitted data with tables