@multiplatform.one/forms
v6.1.0
Published
Cross-platform form components with URL state management
Readme
@multiplatform.one/forms
Cross-platform form components with URL state management for multiplatform.one applications.
Features
- 🔄 URL State Sync: Automatically sync form state with URL parameters
- 📱 Cross-Platform: Works on web, iOS, and Android
- 🎨 Tamagui Components: Beautiful, themeable form components
- 🏗️ TanStack Form: Powerful form state management
- 🔍 SSR Support: Server-side rendering with proper hydration
- 📝 TypeScript: Full type safety
Installation
npm install @multiplatform.one/forms
# or
yarn add @multiplatform.one/forms
# or
pnpm add @multiplatform.one/formsBasic Usage
import { Form, Input, Checkbox, Button } from "@multiplatform.one/forms";
function MyForm() {
return (
<Form
syncWithUrl={true}
urlDebounceMs={500}
onSubmit={async (values) => {
console.log("Form submitted:", values);
}}
>
<Input
name="email"
label="Email"
validators={{
onChange: ({ value }) => (!value?.includes("@") ? "Invalid email" : undefined),
}}
/>
<Input name="password" label="Password" type="password" />
<Checkbox name="remember" label="Remember me" />
<Button type="submit">Sign In</Button>
</Form>
);
}URL State Synchronization
Enable automatic URL state synchronization to create shareable form states:
<Form
syncWithUrl={true}
urlDebounceMs={500}
formOptions={{
defaultValues: {
search: "",
category: "all",
sortBy: "date",
},
}}
>
{/* Form fields */}
</Form>This will automatically sync form values to the URL:
?search=hello&category=tech&sortBy=dateAdvanced Usage with TanStack Form
import { useForm, Form, Input } from "@multiplatform.one/forms";
function AdvancedForm() {
const form = useForm({
defaultValues: {
name: "",
age: 0,
},
validators: {
onChange: ({ value }) => {
if (value.age < 0) {
return "Age must be positive";
}
},
},
});
return (
<Form form={form} syncWithUrl={true}>
<Input
name="name"
label="Name"
validators={{
onChange: ({ value }) => ((value?.length ?? 0) < 3 ? "Name too short" : undefined),
}}
/>
<Input name="age" label="Age" type="number" />
<Button type="submit" />
</Form>
);
}SSR Support
The forms work seamlessly with server-side rendering:
// Server-side: Forms pre-populate from URL
// Client-side: Hydration preserves user input
function SSRForm() {
return (
<Form syncWithUrl={true}>
<Input
name="search"
label="Search"
defaultValue="" // SSR-safe default
/>
</Form>
);
}ChildTable form binding
The ChildTable component lives in @multiplatform.one/table (it composes TableInput with FieldLayout from this package). Import it from @multiplatform.one/table, not from @multiplatform.one/forms.
ChildTable works as a first-class form field when used inside <Form>:
<Form formOptions={{ defaultValues: { items: [] } }}>
<ChildTable
name="items"
columns={[
{ accessorKey: "item_code", header: "Item Code" },
{ accessorKey: "qty", header: "Qty" },
]}
defaultRow={{ idx: 0, item_code: "", qty: 1 }}
/>
</Form>form.getFieldValue("items")returns the array- Add/delete updates form state
- Per-column
linkResolverfor cell-level links (e.g. item_code →/items/[id])
readOnly vs disabled
| Aspect | readOnly | disabled |
| ---------- | --------------- | --------------- |
| Editable | No | No |
| Submitted | Yes | Typically no |
| Appearance | Normal | Grayed out |
| A11y | aria-readonly | aria-disabled |
All fields support readOnly. Use when the value should be visible and submitted but not editable.
Link behavior (linkResolver)
Optional link rendering when displaying values:
- Field-level:
linkResolver?: (value) => Href | nullon Input (and similar) - ChildTable column-level:
linkResolver?: (value, row, rowIndex) => Href | nullon columns
When linkResolver returns a non-null href, the value renders as <Link href={...}>. In edit mode, normal input is shown. Link behavior applies in readOnly/display mode.
Frappe: Schema mapping (doctype → linkResolver) belongs in public/frappe-ui wrappers. Core forms stay doctype-agnostic.
Package Structure
src/
├── index.ts # Public exports
├── form/ # Form wrapper, context
├── fields/ # Field components
│ ├── datePicker/ # DatePicker, MultiDatePicker, DateRangePicker, Calendar
│ ├── colorPicker/
│ └── ...
├── childTable/ # Repeatable row table (form-integrated)
├── fieldLayout.tsx # Label, error, helper text layout
└── utils/LinkCell.tsx # Link wrapper for linkResolverComponents
Form
Main form wrapper that provides context and handles submission.
Input
Text input field with validation support.
Checkbox
Checkbox input with label.
Select
Dropdown selection field.
Button
Form submission button; use type="submit" for form submission.
Hooks
useFormContext
Access the form instance from child components.
useUrlState
Sync any state with URL parameters (re-exported from router).
useSearchParams
Read and write URL search parameters (re-exported from router).
License
Apache-2.0
