{"_id":"@alsocoder/apna-form","_rev":"5-bcd0c620bf2fd29b791329ab82675162","name":"@alsocoder/apna-form","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@alsocoder/apna-form","version":"0.1.0","keywords":["react","react-component","form","react-hook-form","zod","validation","multi-step-form","field-array","form-builder","apna-form","alsocoder"],"author":{"url":"https://alsocoder.com","name":"Also Coder"},"license":"MIT","_id":"@alsocoder/apna-form@0.1.0","maintainers":[{"name":"alsocoder-org","email":"alsocoder@gmail.com"}],"homepage":"https://github.com/alsocoders/apna-form#readme","bugs":{"url":"https://github.com/alsocoders/apna-form/issues"},"dist":{"shasum":"b2c00e2cc5d0651f5ce4fba1c6ffc56fe630fe39","tarball":"https://registry.npmjs.org/@alsocoder/apna-form/-/apna-form-0.1.0.tgz","fileCount":42,"integrity":"sha512-2Dz44h9RHZoABp28URF4T4v9zzJ39BigufDrmyb2hUYmNIvkkeHHUrdxkZk+ztpmLSHAtsI8kxmmyxF9PhnsfA==","signatures":[{"sig":"MEYCIQDLXJUqegYkcTX1f/TS5UETQ5oyM20TLftGIbIsmT9lKwIhAP2oG5IvIZ6S+j03GCRnGvegfJlfRcX7nQ5xpQQx4vcC","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":355383},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./styles.css":"./dist/styles.css"},"gitHead":"bcf3cff17ee524a74f77817bc3b8e2429b717e88","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","build:deps":"npm run build --prefix ../ApnaInput && npm run build --prefix ../ApnaSelect && npm run build --prefix ../ApnaDatePicker && npm run build --prefix ../ApnaEditor && npm run build --prefix ../ApnaUpload","build:local":"npm run build:deps && npm run build","dev:playground":"npm run dev --prefix playground","prepublishOnly":"npm run build"},"_npmUser":{"name":"alsocoder-org","email":"alsocoder@gmail.com"},"overrides":{"@types/react":"^19.1.8","@types/react-dom":"^19.1.6"},"repository":{"url":"git+https://github.com/alsocoders/apna-form.git","type":"git"},"_npmVersion":"12.0.1","description":"React form layer with React Hook Form, Zod validation, and connected Apna field components.","directories":{},"sideEffects":["*.css"],"_nodeVersion":"24.16.0","browserslist":["Chrome >= 90","Firefox >= 90","Safari >= 14","Edge >= 90","iOS >= 14","Samsung >= 15","not dead"],"dependencies":{"clsx":"^2.1.1","@alsocoder/apna-input":"^0.1.3","@alsocoder/apna-editor":"^0.1.2","@alsocoder/apna-select":"^0.2.3","@alsocoder/apna-upload":"^0.1.0","@alsocoder/apna-date-picker":"^0.1.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"yup":"^1.7.0","zod":"^4.1.5","tsup":"^8.5.0","react":"^19.1.0","react-dom":"^19.1.0","typescript":"^5.9.2","@types/react":"^19.1.8","react-hook-form":"^7.62.0","@types/react-dom":"^19.1.6","@hookform/resolvers":"^5.2.2"},"peerDependencies":{"react":">=18","react-dom":">=18","react-hook-form":">=7.50.0"},"optionalDependencies":{"yup":"^1.7.0","zod":"^4.1.5","@hookform/resolvers":"^5.2.2"},"peerDependenciesMeta":{"yup":{"optional":true},"zod":{"optional":true},"@hookform/resolvers":{"optional":true},"@alsocoder/apna-media-library":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/apna-form_0.1.0_1785847218822_0.5845924795620223","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alsocoder/apna-form","version":"0.1.1","description":"React form layer with React Hook Form, Zod validation, and connected Apna field components.","license":"MIT","author":{"name":"Also Coder","url":"https://alsocoder.com"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./styles.css":"./dist/styles.css"},"sideEffects":["*.css"],"scripts":{"build:deps":"npm run build --prefix ../ApnaInput && npm run build --prefix ../ApnaSelect && npm run build --prefix ../ApnaDatePicker && npm run build --prefix ../ApnaEditor && npm run build --prefix ../ApnaUpload","build:local":"npm run build:deps && npm run build","build":"tsup","dev":"tsup --watch","dev:playground":"npm run dev --prefix playground","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["react","react-component","form","react-hook-form","zod","validation","multi-step-form","field-array","form-builder","apna-form","alsocoder"],"browserslist":["Chrome >= 90","Firefox >= 90","Safari >= 14","Edge >= 90","iOS >= 14","Samsung >= 15","not dead"],"repository":{"type":"git","url":"git+https://github.com/alsocoders/apna-form.git"},"bugs":{"url":"https://github.com/alsocoders/apna-form/issues"},"homepage":"https://github.com/alsocoders/apna-form#readme","publishConfig":{"access":"public"},"engines":{"node":">=18"},"peerDependencies":{"react":">=18","react-dom":">=18","react-hook-form":">=7.50.0"},"peerDependenciesMeta":{"@alsocoder/apna-media-library":{"optional":true},"zod":{"optional":true},"@hookform/resolvers":{"optional":true},"yup":{"optional":true}},"dependencies":{"@alsocoder/apna-date-picker":"^0.1.2","@alsocoder/apna-editor":"^0.1.2","@alsocoder/apna-input":"^0.1.3","@alsocoder/apna-select":"^0.2.3","@alsocoder/apna-upload":"^0.1.0","clsx":"^2.1.1"},"devDependencies":{"@hookform/resolvers":"^5.2.2","@types/react":"^19.1.8","@types/react-dom":"^19.1.6","react":"^19.1.0","react-dom":"^19.1.0","react-hook-form":"^7.62.0","tsup":"^8.5.0","typescript":"^5.9.2","yup":"^1.7.0","zod":"^4.1.5"},"optionalDependencies":{"@hookform/resolvers":"^5.2.2","yup":"^1.7.0","zod":"^4.1.5"},"overrides":{"@types/react":"^19.1.8","@types/react-dom":"^19.1.6"},"gitHead":"bcf3cff17ee524a74f77817bc3b8e2429b717e88","_id":"@alsocoder/apna-form@0.1.1","_nodeVersion":"24.16.0","_npmVersion":"12.0.1","dist":{"integrity":"sha512-LKTcvz8wznk2SGxBVpm4rMmbFIqylJSfIeBFXjRNPPxZ7ixIMaQVGyHgfOltslRK6+jqTEBIuCS8c/wilqnqaA==","shasum":"5b7a691279441dd4bf0a8d87b82fe6386668b584","tarball":"https://registry.npmjs.org/@alsocoder/apna-form/-/apna-form-0.1.1.tgz","fileCount":42,"unpackedSize":356324,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEGs/5GDYqesLrE/vxafgg7vrcWJT6IFSBm6GUpFhePuAiEAzra9lkSPxXN15D4LCMWr/L722GZU2LFWZP7dmPi5RTs="}]},"_npmUser":{"name":"alsocoder-org","email":"alsocoder@gmail.com"},"directories":{},"maintainers":[{"name":"alsocoder-org","email":"alsocoder@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/apna-form_0.1.1_1786261718652_0.5244901101186663"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T12:40:17.185Z","modified":"2026-08-09T07:48:38.973Z","0.2.0":"2026-08-04T03:36:23.261Z","0.1.0":"2026-08-04T12:40:18.980Z","0.1.1":"2026-08-09T07:48:38.792Z"},"bugs":{"url":"https://github.com/alsocoders/apna-form/issues"},"author":{"name":"Also Coder","url":"https://alsocoder.com"},"license":"MIT","homepage":"https://github.com/alsocoders/apna-form#readme","keywords":["react","react-component","form","react-hook-form","zod","validation","multi-step-form","field-array","form-builder","apna-form","alsocoder"],"repository":{"type":"git","url":"git+https://github.com/alsocoders/apna-form.git"},"description":"React form layer with React Hook Form, Zod validation, and connected Apna field components.","maintainers":[{"name":"alsocoder-org","email":"alsocoder@gmail.com"}],"readme":"# @alsocoder/apna-form\r\n\r\nReact form layer built on React Hook Form with optional Zod validation and connected Apna field components.\r\n\r\n## Features\r\n\r\n- `ApnaForm` with Zod schema validation\r\n- Connected fields: Input, Textarea, OTP, Select, DatePicker, DateRange, DateTime, Editor, Upload, Media (optional)\r\n- Field arrays (`ApnaFormFieldArray`)\r\n- Conditional fields (`ApnaFormWatch`, `showWhen`)\r\n- Multi-step forms (`ApnaFormSteps`)\r\n- Auto-save drafts (`enableAutoSave` + `onAutoSave`)\r\n- Dirty state (`ApnaFormStatus`, `useApnaForm`)\r\n- Async validation (Zod `.refine()`)\r\n- JSON form builder (`ApnaFormBuilder`) with smart responsive grid\r\n- Native HTML `<form>` submit support\r\n- React Hook Form compatible (`useApnaForm`, `ApnaFormField`)\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @alsocoder/apna-form react-hook-form zod @hookform/resolvers\r\n```\r\n\r\nPeer dependencies: `react`, `react-dom`, `react-hook-form`.\r\n\r\n`@alsocoder/apna-form` bundles these as dependencies — you do **not** install them separately:\r\n\r\n- `@alsocoder/apna-input`\r\n- `@alsocoder/apna-select`\r\n- `@alsocoder/apna-date-picker`\r\n- `@alsocoder/apna-editor`\r\n- `@alsocoder/apna-upload`\r\n\r\n### Optional: media picker\r\n\r\nFor `ApnaFormMediaPicker` or builder `type: \"media\"`:\r\n\r\n```bash\r\nnpm install @alsocoder/apna-media-library\r\n```\r\n\r\n## Setup\r\n\r\n```tsx\r\nimport \"@alsocoder/apna-form/styles.css\"\r\n```\r\n\r\nThis bundles Apna Input, Select, DatePicker, Editor, Upload, and form layout styles.\r\n\r\n**Important:** Import this **after** your global CSS. Avoid generic rules like `button[type=\"button\"] { padding; background; }` — they override Select/Editor/DatePicker icon buttons. The bundle includes a button guard layer for common Apna UI buttons.\r\n\r\n### With media picker\r\n\r\n```tsx\r\nimport \"@alsocoder/apna-form/styles.css\"\r\nimport \"@alsocoder/apna-media-library/styles.css\"\r\nimport { ApnaMediaProvider } from \"@alsocoder/apna-media-library\"\r\n\r\nexport function App() {\r\n  return (\r\n    <ApnaMediaProvider adapter={yourAdapter}>\r\n      <YourForm />\r\n    </ApnaMediaProvider>\r\n  )\r\n}\r\n```\r\n\r\n## Basic usage\r\n\r\n```tsx\r\nimport { z } from \"zod\"\r\nimport {\r\n  ApnaForm,\r\n  ApnaFormInput,\r\n  ApnaFormSelect,\r\n  ApnaFormDatePicker,\r\n} from \"@alsocoder/apna-form\"\r\nimport \"@alsocoder/apna-form/styles.css\"\r\n\r\nconst schema = z.object({\r\n  name: z.string().min(2),\r\n  country: z.string(),\r\n  dob: z.string(),\r\n})\r\n\r\nexport function ProfileForm() {\r\n  return (\r\n    <ApnaForm\r\n      schema={schema}\r\n      defaultValues={{ name: \"\", country: \"\", dob: \"\" }}\r\n      onSubmit={(values) => console.log(values)}\r\n    >\r\n      <ApnaFormInput name=\"name\" label=\"Name\" required />\r\n      <ApnaFormSelect name=\"country\" options={countries} label=\"Country\" />\r\n      <ApnaFormDatePicker name=\"dob\" label=\"Date of birth\" />\r\n      <button type=\"submit\">Save</button>\r\n    </ApnaForm>\r\n  )\r\n}\r\n```\r\n\r\n## Conditional fields\r\n\r\n```tsx\r\n<ApnaFormWatch names={[\"country\"]}>\r\n  {(country) =>\r\n    country === \"IN\" ? (\r\n      <ApnaFormInput name=\"pincode\" label=\"PIN code\" format=\"digits\" />\r\n    ) : null\r\n  }\r\n</ApnaFormWatch>\r\n```\r\n\r\nOr declarative:\r\n\r\n```tsx\r\n<ApnaFormInput name=\"gstin\" showWhen={(values) => values.country === \"IN\"} />\r\n```\r\n\r\n## Multi-step\r\n\r\n```tsx\r\n<ApnaForm schema={schema} onSubmit={save}>\r\n  <ApnaFormSteps>\r\n    <ApnaFormStep id=\"personal\" title=\"Personal\" fields={[\"name\", \"email\"]}>\r\n      <ApnaFormInput name=\"name\" label=\"Name\" />\r\n      <ApnaFormInput name=\"email\" label=\"Email\" format=\"email\" />\r\n    </ApnaFormStep>\r\n    <ApnaFormStep id=\"address\" title=\"Address\" fields={[\"country\"]}>\r\n      <ApnaFormSelect name=\"country\" options={countries} label=\"Country\" />\r\n    </ApnaFormStep>\r\n    <ApnaFormStepActions />\r\n  </ApnaFormSteps>\r\n</ApnaForm>\r\n```\r\n\r\n## Field arrays\r\n\r\n```tsx\r\n<ApnaFormFieldArray name=\"phones\">\r\n  {(fields, { append, remove }) => (\r\n    <>\r\n      {fields.map((field, index) => (\r\n        <ApnaFormInput\r\n          key={field.id}\r\n          name={`phones.${index}.number`}\r\n          label={`Phone ${index + 1}`}\r\n        />\r\n      ))}\r\n      <button type=\"button\" onClick={() => append({ number: \"\" })}>\r\n        Add phone\r\n      </button>\r\n    </>\r\n  )}\r\n</ApnaFormFieldArray>\r\n```\r\n\r\n## Auto-save\r\n\r\n```tsx\r\n<ApnaForm\r\n  schema={schema}\r\n  enableAutoSave\r\n  autoSaveDebounceMs={800}\r\n  onAutoSave={(values) => saveDraft(values)}\r\n  onSubmit={publish}\r\n>\r\n  ...\r\n</ApnaForm>\r\n```\r\n\r\n## Form builder\r\n\r\n`ApnaFormBuilder` renders fields from a JSON config on a **12-column responsive grid**.\r\n\r\n| Prop | Purpose |\r\n|------|---------|\r\n| `columns` | Default fields per row per breakpoint (`span = 12 / columns`) |\r\n| `field.colSpan` | Per-field override (1–12 columns) |\r\n| `field.showWhen` | Conditionally show/hide fields; hidden fields are removed from the grid (no empty cells) |\r\n| `type: \"editor\"` | Always full width (12 columns) on every breakpoint |\r\n| `type: \"upload\"` | File upload — tall field group (full width like textarea); pass `upload` on `ApnaForm` / `ApnaFormBuilder` (or per-field `props.upload`) |\r\n| `type: \"media\"` | Lazy-loaded media picker — same grid behavior as input/select (optional peer) |\r\n| `type: \"textarea\"` | Tall field group — only pairs with other tall fields (e.g. upload), otherwise full width |\r\n\r\n### Smart layout\r\n\r\nThe builder groups fields by layout type:\r\n\r\n- **compact** — input, select, date, dateRange, datetime, otp, media\r\n- **tall** — textarea, upload\r\n- **full** — editor\r\n\r\nWithin each group, fields auto-size per row: 3 → one-third each, 2 → half each, 1 → full width. Mixed types (e.g. input + textarea) never share a row.\r\n\r\nBreakpoints: `default` (mobile), `sm` 640px, `md` 768px, `lg` 1024px, `xl` 1280px.\r\n\r\n```tsx\r\nconst config = [\r\n  { type: \"input\", name: \"title\", label: \"Title\", colSpan: { md: 8 } },\r\n  { type: \"select\", name: \"role\", label: \"Role\", colSpan: { md: 4 }, props: { options: roles } },\r\n  { type: \"date\", name: \"startDate\", label: \"Start date\" },\r\n  { type: \"editor\", name: \"description\", label: \"Description\" },\r\n  {\r\n    type: \"input\",\r\n    name: \"pincode\",\r\n    label: \"PIN code\",\r\n    showWhen: (values) => values.country === \"IN\",\r\n    props: { format: \"digits\", maxLength: 6 },\r\n  },\r\n] as const\r\n\r\n<ApnaFormBuilder\r\n  columns={{ default: 1, md: 2, lg: 3 }}\r\n  config={config}\r\n  schema={schema}\r\n  onSubmit={save}\r\n/>\r\n```\r\n\r\nEditor, upload, and media field types are **lazy-loaded** in the builder — their JS chunks load only when used in `config`.\r\n\r\n### File upload (built-in)\r\n\r\nPass an `upload` function on `ApnaForm` or `ApnaFormBuilder`. Upload fields inherit it unless overridden in field `props`:\r\n\r\n```tsx\r\nimport type { ApnaUploadFn } from \"@alsocoder/apna-upload\"\r\n\r\nconst uploadFile: ApnaUploadFn = async (file, { signal, onProgress }) => {\r\n  onProgress(50)\r\n  const res = await fetch(\"/api/upload\", { method: \"POST\", body: formData, signal })\r\n  onProgress(100)\r\n  return res.json() // e.g. { file: { path: \"/uploads/...\" } }\r\n}\r\n\r\n<ApnaFormBuilder\r\n  upload={uploadFile}\r\n  transformUploadResult={(result) => result.file.path}\r\n  defaultUploadProps={{\r\n    accept: \"image/*\",\r\n    maxSize: 5 * 1024 * 1024,\r\n    resultMode: \"url\",\r\n    beforeUpload: async (file) => {\r\n      // compress, crop, watermark, etc.\r\n      return file\r\n    },\r\n  }}\r\n  config={[\r\n    { type: \"upload\", name: \"resume\", label: \"Resume\", props: { accept: \".pdf\" } },\r\n    { type: \"upload\", name: \"avatar\", label: \"Avatar\", props: { upload: otherHandler } },\r\n  ]}\r\n  schema={schema}\r\n  onSubmit={save}\r\n/>\r\n```\r\n\r\n```tsx\r\n<ApnaFormUpload name=\"resume\" label=\"Resume\" accept=\".pdf\" />\r\n```\r\n\r\n**Value shape** depends on `resultMode`:\r\n\r\n| `resultMode` | Single field | Multiple field |\r\n|--------------|--------------|----------------|\r\n| `\"url\"` (default) | `string` | `string[]` |\r\n| `\"file\"` | `File` | `File[]` |\r\n\r\nMatch your Zod schema to the chosen `resultMode`.\r\n\r\n### Media picker (optional)\r\n\r\n```bash\r\nnpm install @alsocoder/apna-media-library\r\n```\r\n\r\nWrap your app with `ApnaMediaProvider`, import media styles, then use in builder or as a connected field:\r\n\r\n```tsx\r\n{ type: \"media\", name: \"cover\", label: \"Cover\", props: { folder: \"blog/covers\" } }\r\n```\r\n\r\n```tsx\r\n<ApnaFormMediaPicker name=\"avatar\" label=\"Avatar\" folder=\"misc\" />\r\n```\r\n\r\nFor editor image insertion via media library, pass `imageUpload.pick` in editor `props`.\r\n\r\n## Advanced: custom field\r\n\r\n```tsx\r\n<ApnaFormField\r\n  name=\"email\"\r\n  render={({ field, fieldState }) => (\r\n    <ApnaInput\r\n      value={field.value}\r\n      onValueChange={field.onChange}\r\n      onBlur={field.onBlur}\r\n      error={fieldState.error?.message}\r\n    />\r\n  )}\r\n/>\r\n```\r\n\r\n## Yup (optional)\r\n\r\n```tsx\r\nimport { createYupResolver } from \"@alsocoder/apna-form\"\r\n\r\nconst resolver = await createYupResolver(yupSchema)\r\n<ApnaForm resolver={resolver} onSubmit={save}>...</ApnaForm>\r\n```\r\n\r\n## Development\r\n\r\n```bash\r\nnpm install\r\nnpm run build\r\n```\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md"}