{"_id":"@alsocoder/apna-crud","name":"@alsocoder/apna-crud","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alsocoder/apna-crud","version":"0.1.0","description":"Auto CRUD pages using ApnaTable, ApnaForm, ApnaModal, and ApnaToast.","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 ../ApnaTable && npm run build --prefix ../ApnaForm && npm run build --prefix ../ApnaModal && npm run build --prefix ../ApnaToast","build:local":"npm run build:deps && npm run build","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"peerDependencies":{"react":">=18","react-dom":">=18","@alsocoder/apna-panel":"^0.1.0","react-router-dom":">=6"},"peerDependenciesMeta":{"@alsocoder/apna-panel":{"optional":true},"react-router-dom":{"optional":true}},"dependencies":{"@alsocoder/apna-form":"^0.1.1","@alsocoder/apna-modal":"^0.1.2","@alsocoder/apna-panel":"^0.1.0","@alsocoder/apna-table":"^0.1.2","@alsocoder/apna-toast":"^0.1.1","clsx":"^2.1.1"},"devDependencies":{"@types/react":"^19.1.8","@types/react-dom":"^19.1.6","react":"^19.1.0","react-dom":"^19.1.0","react-router-dom":"^7.6.3","tsup":"^8.5.0","typescript":"^5.9.2"},"overrides":{"@types/react":"^19.1.8","@types/react-dom":"^19.1.6"},"_id":"@alsocoder/apna-crud@0.1.0","_nodeVersion":"24.16.0","_npmVersion":"12.0.1","dist":{"integrity":"sha512-tnE+FS5OHNkRnJo+3uDEgqPoo403NWYKulwTXI5efewsxn9DFoJedSMSSOnli+bYgVof+lqsqeL/s30aFJ1LqQ==","shasum":"61e1c9fc61e7ef9c5ca400785a7c920cd70f164e","tarball":"https://registry.npmjs.org/@alsocoder/apna-crud/-/apna-crud-0.1.0.tgz","fileCount":10,"unpackedSize":353689,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIApc5B6Dqc3SF6SnTx0NZy0dV5/17f8w1fkjqEFk64E/AiEA12tRUy8Kh8+T7mj8nPKVyAuzUJyU4iqR5r9SGHUbB/I="}]},"_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-crud_0.1.0_1786264069812_0.6349176425761847"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T08:27:49.670Z","0.1.0":"2026-08-09T08:27:49.969Z","modified":"2026-08-09T08:27:50.179Z"},"maintainers":[{"name":"alsocoder-org","email":"alsocoder@gmail.com"}],"description":"Auto CRUD pages using ApnaTable, ApnaForm, ApnaModal, and ApnaToast.","author":{"name":"Also Coder","url":"https://alsocoder.com"},"license":"MIT","readme":"# @alsocoder/apna-crud\r\n\r\nAuto CRUD pages built on **ApnaTable**, **ApnaForm**, **ApnaModal**, and **ApnaToast**. Drop a single `<ApnaCrud />` into your admin panel (e.g. `@alsocoder/apna-panel`) for full list/create/edit/delete flows.\r\n\r\n---\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @alsocoder/apna-crud\r\n```\r\n\r\nFor `formMode=\"page\"`, also install `react-router-dom`.\r\n\r\nApnaCrud bundles table, form, modal, and toast styles — **one CSS import**:\r\n\r\n```tsx\r\nimport \"@alsocoder/apna-crud/styles.css\"\r\n```\r\n\r\nOptional (for `type: \"media\"` fields):\r\n\r\n```bash\r\nnpm install @alsocoder/apna-media-library\r\n```\r\n\r\n---\r\n\r\n## Quick start\r\n\r\n### REST API\r\n\r\n```tsx\r\n<ApnaCrud\r\n  name=\"users\"\r\n  title=\"Users\"\r\n  endpoint=\"/api/users\"\r\n  baseUrl={import.meta.env.VITE_API_URL}\r\n  columns={[\r\n    { id: \"name\", header: \"Name\", accessorKey: \"name\" },\r\n    { id: \"email\", header: \"Email\", accessorKey: \"email\" },\r\n  ]}\r\n  formFields={[\r\n    { type: \"input\", name: \"name\", label: \"Name\", props: { required: true } },\r\n    { type: \"input\", name: \"email\", label: \"Email\", props: { format: \"email\", required: true } },\r\n  ]}\r\n  permissions={{\r\n    list: \"users.view\",\r\n    create: \"users.create\",\r\n    update: \"users.update\",\r\n    delete: \"users.delete\",\r\n  }}\r\n  features={[\"bulk\"]}\r\n/>\r\n```\r\n\r\n### Custom functions\r\n\r\n```tsx\r\n<ApnaCrud\r\n  name=\"users\"\r\n  title=\"Users\"\r\n  list={listUsers}\r\n  create={createUser}\r\n  update={updateUser}\r\n  delete={deleteUser}\r\n  get={getUser}\r\n  columns={[...]}\r\n  formFields={[...]}\r\n/>\r\n```\r\n\r\n### Inside ApnaPanel\r\n\r\n```tsx\r\nimport { ApnaPanelRoute } from \"@alsocoder/apna-panel-react-router\"\r\nimport { ApnaCrud } from \"@alsocoder/apna-crud\"\r\nimport \"@alsocoder/apna-crud/styles.css\"\r\n\r\n<Route\r\n  path=\"/users/*\"\r\n  element={\r\n    <ApnaPanelRoute permissions=\"users.view\" fallback={<ApnaPanelForbidden />}>\r\n      <ApnaCrud\r\n        formMode=\"page\"\r\n        basePath=\"/users\"\r\n        name=\"users\"\r\n        title=\"Users\"\r\n        {...usersResource}\r\n      />\r\n    </ApnaPanelRoute>\r\n  }\r\n/>\r\n```\r\n\r\n---\r\n\r\n## `ApnaCrud` props — full reference\r\n\r\n| Prop | Type | Default | Description |\r\n|------|------|---------|-------------|\r\n| `name` | `string` | **required** | Resource name (used for routes, `data-resource` attr) |\r\n| `title` | `string` | — | Page/table title |\r\n| `endpoint` | `string \\| ApnaCrudEndpoints` | — | REST endpoint(s) |\r\n| `baseUrl` | `string` | — | API base URL (with `endpoint`) |\r\n| `list` | `fn` | — | `(params) => { items, total }` |\r\n| `create` | `fn` | — | `(data) => record` |\r\n| `update` | `fn` | — | `(id, data) => record` |\r\n| `delete` | `fn` | — | `(id) => void` |\r\n| `get` | `fn` | — | `(id) => record` — required for page mode direct URL |\r\n| `columns` | `ApnaTableColumn[]` | auto | Table columns |\r\n| `formFields` | `ApnaFormFieldConfig[]` | — | Create/edit form fields |\r\n| `formColumns` | `ApnaFormColumns` | — | Responsive form grid `{ default: 1, md: 2, lg: 3 }` |\r\n| `formProps` | `ApnaCrudFormProps` | — | Forwarded to `ApnaFormBuilder` (see below) |\r\n| `formMode` | `\"modal\" \\| \"drawer\" \\| \"page\"` | `\"modal\"` | How create/edit forms open |\r\n| `basePath` | `string` | `/${name}` | Base path for page mode |\r\n| `routes` | `{ create?, edit? }` | — | Custom route segments for page mode. Default create segment is `create`; `/add` is also accepted as an alias unless `routes.create` is customized. |\r\n| `overlaySize` | `\"sm\" \\| \"md\" \\| \"lg\" \\| \"xl\"` | `\"md\"` | Modal/drawer size |\r\n| `permissions` | `ApnaCrudPermissions` | — | `list`, `create`, `update`, `delete` permission keys |\r\n| `features` | `string[]` | — | `\"bulk\"`, `\"export\"`, `\"import\"`, `\"archive\"`, `\"duplicate\"`, `\"softDelete\"` |\r\n| `idKey` | `string` | `\"id\"` | Record id field name |\r\n| `getRowId` | `fn` | — | Custom row id extractor |\r\n| `transformCreate` | `fn` | — | Transform payload before create |\r\n| `transformUpdate` | `fn` | — | Transform payload before update |\r\n| `onSuccess` | `fn` | — | `(action, record?) => void` |\r\n| `onError` | `fn` | — | `(error, action) => void` |\r\n| `className` | `string` | — | Root wrapper class |\r\n| `emptyMessage` | `string` | — | Empty table message |\r\n\r\n---\r\n\r\n## Form modes\r\n\r\n### Modal (default)\r\n\r\nCreate/edit opens in a centered modal.\r\n\r\n```tsx\r\n<ApnaCrud\r\n  formMode=\"modal\"\r\n  overlaySize=\"md\"\r\n  name=\"products\"\r\n  title=\"Products\"\r\n  formFields={[...]}\r\n  {...api}\r\n/>\r\n```\r\n\r\n### Drawer\r\n\r\nCreate/edit opens in a right-side drawer.\r\n\r\n```tsx\r\n<ApnaCrud\r\n  formMode=\"drawer\"\r\n  overlaySize=\"lg\"\r\n  name=\"orders\"\r\n  title=\"Orders\"\r\n  formFields={[...]}\r\n  {...api}\r\n/>\r\n```\r\n\r\n### Page (route-based)\r\n\r\nFull-page forms at separate routes. Mount under a **splat route**:\r\n\r\n```tsx\r\n// router\r\n<Route path=\"/users/*\" element={<UsersCrudPage />} />\r\n\r\nfunction UsersCrudPage() {\r\n  return (\r\n    <ApnaCrud\r\n      formMode=\"page\"\r\n      basePath=\"/users\"\r\n      name=\"users\"\r\n      title=\"Users\"\r\n      get={getUser}       // required for direct /users/:id/edit access\r\n      list={listUsers}\r\n      create={createUser}\r\n      update={updateUser}\r\n      delete={deleteUser}\r\n      columns={[...]}\r\n      formFields={[...]}\r\n    />\r\n  )\r\n}\r\n```\r\n\r\nAuto-created routes:\r\n\r\n| URL | Screen |\r\n|-----|--------|\r\n| `/users` | Table list |\r\n| `/users/create` | Create form |\r\n| `/users/add` | Create form (alias when default `create` segment is used) |\r\n| `/users/:id/edit` | Edit form |\r\n\r\nCustom route segments:\r\n\r\n```tsx\r\nroutes={{ create: \"new\", edit: \":id\" }}\r\n// → /users/new and /users/:id/edit only (no /add alias when create is customized)\r\n```\r\n\r\n---\r\n\r\n## Data sources\r\n\r\n### REST with `createRestCrud`\r\n\r\n```tsx\r\nimport { ApnaCrud, createRestCrud } from \"@alsocoder/apna-crud\"\r\n\r\nconst usersApi = createRestCrud<User>(import.meta.env.VITE_API_URL, \"/api/users\")\r\n\r\n<ApnaCrud\r\n  name=\"users\"\r\n  {...usersApi}\r\n  columns={[...]}\r\n  formFields={[...]}\r\n/>\r\n```\r\n\r\n### Custom endpoints object\r\n\r\n```tsx\r\nendpoint={{\r\n  list: \"/api/users\",\r\n  create: \"/api/users\",\r\n  update: \"/api/users/:id\",\r\n  delete: \"/api/users/:id\",\r\n  get: \"/api/users/:id\",\r\n}}\r\nbaseUrl=\"https://api.example.com\"\r\n```\r\n\r\n### `defineCrudResource` (reusable config)\r\n\r\n```tsx\r\nimport { defineCrudResource } from \"@alsocoder/apna-crud\"\r\n\r\nexport const usersResource = defineCrudResource({\r\n  name: \"users\",\r\n  title: \"Users\",\r\n  columns: [\r\n    { id: \"name\", header: \"Name\", accessorKey: \"name\" },\r\n    { id: \"email\", header: \"Email\", accessorKey: \"email\" },\r\n  ],\r\n  formFields: [\r\n    { type: \"input\", name: \"name\", label: \"Name\" },\r\n    { type: \"input\", name: \"email\", label: \"Email\", props: { format: \"email\" } },\r\n  ],\r\n  formColumns: { default: 1, md: 2 },\r\n  permissions: {\r\n    list: \"users.view\",\r\n    create: \"users.create\",\r\n    update: \"users.update\",\r\n    delete: \"users.delete\",\r\n  },\r\n  features: [\"bulk\"],\r\n  ...usersApi,\r\n})\r\n\r\n// usage\r\n<ApnaCrud {...usersResource} formMode=\"modal\" />\r\n```\r\n\r\n---\r\n\r\n## Forms (`formFields` + `formProps`)\r\n\r\nForms are rendered by **ApnaFormBuilder** from `@alsocoder/apna-form`. ApnaCrud handles table, modal/drawer/page shell, and API calls.\r\n\r\n### Basic fields\r\n\r\n```tsx\r\nformFields={[\r\n  { type: \"input\", name: \"title\", label: \"Title\", colSpan: { md: 8 }, props: { required: true } },\r\n  { type: \"select\", name: \"status\", label: \"Status\", colSpan: { md: 4 }, props: { options, placeholder: \"Select\" } },\r\n  { type: \"textarea\", name: \"notes\", label: \"Notes\", props: { rows: 4 } },\r\n  { type: \"date\", name: \"dueDate\", label: \"Due date\" },\r\n  { type: \"checkbox\", name: \"active\", label: \"Active\" },\r\n]}\r\nformColumns={{ default: 1, md: 2, lg: 3 }}\r\n```\r\n\r\n### Advanced fields\r\n\r\n```tsx\r\nformFields={[\r\n  { type: \"editor\", name: \"body\", label: \"Content\" },\r\n  { type: \"editor\", name: \"html\", label: \"HTML\", props: { enableHtmlMode: true } },\r\n  { type: \"upload\", name: \"resume\", label: \"Resume\", props: { accept: \".pdf\", maxSize: 5 * 1024 * 1024 } },\r\n  { type: \"media\", name: \"cover\", label: \"Cover\", props: { folder: \"blog/covers\" } },\r\n]}\r\n```\r\n\r\n### `formProps` (forwarded to ApnaFormBuilder)\r\n\r\nEverything from `ApnaFormBuilderProps` **except** `config`, `columns`, `children`, `onSubmit`, `className`, `classNames`:\r\n\r\n```tsx\r\nimport type { ApnaUploadFn } from \"@alsocoder/apna-upload\"\r\nimport { z } from \"zod\"\r\n\r\nconst schema = z.object({\r\n  name: z.string().min(1),\r\n  email: z.string().email(),\r\n})\r\n\r\nconst uploadFile: ApnaUploadFn = async (file, { signal, onProgress }) => {\r\n  const formData = new FormData()\r\n  formData.append(\"file\", file)\r\n  onProgress(30)\r\n  const res = await fetch(\"/api/upload\", { method: \"POST\", body: formData, signal })\r\n  onProgress(100)\r\n  return res.json()\r\n}\r\n\r\n<ApnaCrud\r\n  formFields={[...]}\r\n  formProps={{\r\n    schema,                              // Zod / validation schema\r\n    mode: \"onBlur\",                       // validation mode\r\n    upload: uploadFile,                   // global upload handler\r\n    transformUploadResult: (r) => r.path, // normalize upload response\r\n    defaultUploadProps: {\r\n      accept: \"image/*\",\r\n      maxSize: 5 * 1024 * 1024,\r\n    },\r\n  }}\r\n  {...resource}\r\n/>\r\n```\r\n\r\n---\r\n\r\n## Media picker setup\r\n\r\nFor `type: \"media\"` fields, wrap your app with `ApnaMediaProvider` **once** at the app level:\r\n\r\n```bash\r\nnpm install @alsocoder/apna-media-library\r\n```\r\n\r\n```tsx\r\nimport { ApnaMediaProvider } from \"@alsocoder/apna-media-library\"\r\nimport \"@alsocoder/apna-media-library/styles.css\"\r\n\r\n<ApnaMediaProvider adapter={mediaAdapter}>\r\n  <ApnaPanelRouterProvider config={config}>\r\n    <ApnaPanelShell>\r\n      <Routes>...</Routes>\r\n    </ApnaPanelShell>\r\n  </ApnaPanelRouterProvider>\r\n</ApnaMediaProvider>\r\n```\r\n\r\nPass `formProps.upload` for `type: \"upload\"` fields. See [ApnaForm README](https://github.com/alsocoders/apna-form) for full field docs.\r\n\r\n---\r\n\r\n## Permissions\r\n\r\n```tsx\r\npermissions={{\r\n  list: \"users.view\",      // hide table / page\r\n  create: \"users.create\",  // hide create button\r\n  update: \"users.update\",  // hide edit action\r\n  delete: \"users.delete\",  // hide delete action\r\n}}\r\n```\r\n\r\nWire to your panel RBAC — ApnaCrud checks permission strings you provide. Use the same keys as `@alsocoder/apna-panel` `rolePermissions`.\r\n\r\n---\r\n\r\n## Features\r\n\r\n```tsx\r\nfeatures={[\"bulk\", \"export\", \"import\", \"archive\", \"duplicate\", \"softDelete\"]}\r\n```\r\n\r\n| Feature | Description |\r\n|---------|-------------|\r\n| `bulk` | Multi-select + bulk delete |\r\n| `export` | Export table data |\r\n| `import` | Import data |\r\n| `archive` | Archive records |\r\n| `duplicate` | Duplicate row |\r\n| `softDelete` | Soft delete instead of hard delete |\r\n\r\n---\r\n\r\n## Callbacks & transforms\r\n\r\n```tsx\r\n<ApnaCrud\r\n  transformCreate={(data) => ({ ...data, createdBy: currentUser.id })}\r\n  transformUpdate={(data) => ({ ...data, updatedAt: new Date().toISOString() })}\r\n  onSuccess={(action, record) => {\r\n    if (action === \"create\") toast.success(`Created ${record?.name}`)\r\n  }}\r\n  onError={(error, action) => {\r\n    toast.error(`${action} failed: ${error.message}`)\r\n  }}\r\n  {...resource}\r\n/>\r\n```\r\n\r\n---\r\n\r\n## Complete examples\r\n\r\n### 1. Modal CRUD (products)\r\n\r\n```tsx\r\n<ApnaCrud\r\n  name=\"products\"\r\n  title=\"Products\"\r\n  formMode=\"modal\"\r\n  overlaySize=\"md\"\r\n  formColumns={{ default: 1, md: 2 }}\r\n  columns={[\r\n    { id: \"name\", header: \"Product\", accessorKey: \"name\" },\r\n    { id: \"sku\", header: \"SKU\", accessorKey: \"sku\" },\r\n    { id: \"price\", header: \"Price\", accessorKey: \"price\" },\r\n    { id: \"status\", header: \"Status\", accessorKey: \"status\" },\r\n  ]}\r\n  formFields={[\r\n    { type: \"input\", name: \"name\", label: \"Product name\", colSpan: { md: 6 }, props: { required: true } },\r\n    { type: \"input\", name: \"sku\", label: \"SKU\", colSpan: { md: 6 }, props: { required: true } },\r\n    { type: \"input\", name: \"price\", label: \"Price\", colSpan: { md: 4 }, props: { format: \"number\" } },\r\n    { type: \"select\", name: \"status\", label: \"Status\", colSpan: { md: 4 }, props: { options: statusOptions } },\r\n  ]}\r\n  permissions={{ list: \"products.view\", create: \"products.create\", update: \"products.update\", delete: \"products.delete\" }}\r\n  features={[\"bulk\"]}\r\n  list={listProducts}\r\n  create={createProduct}\r\n  update={updateProduct}\r\n  delete={deleteProduct}\r\n/>\r\n```\r\n\r\n### 2. Drawer CRUD (orders)\r\n\r\n```tsx\r\n<ApnaCrud\r\n  name=\"orders\"\r\n  title=\"Orders\"\r\n  formMode=\"drawer\"\r\n  overlaySize=\"lg\"\r\n  columns={[...]}\r\n  formFields={[...]}\r\n  {...ordersApi}\r\n/>\r\n```\r\n\r\n### 3. Page CRUD with large form (projects)\r\n\r\n```tsx\r\n<ApnaCrud\r\n  name=\"projects\"\r\n  title=\"Projects\"\r\n  formMode=\"page\"\r\n  basePath=\"/crud/projects\"\r\n  formColumns={{ default: 1, md: 2, lg: 3 }}\r\n  columns={[\r\n    { id: \"title\", header: \"Project\", accessorKey: \"title\" },\r\n    { id: \"client\", header: \"Client\", accessorKey: \"client\" },\r\n    { id: \"status\", header: \"Status\", accessorKey: \"status\" },\r\n  ]}\r\n  formFields={[\r\n    { type: \"input\", name: \"title\", label: \"Title\", colSpan: { md: 8 } },\r\n    { type: \"media\", name: \"coverImage\", label: \"Cover\", colSpan: { md: 6 }, props: { folder: \"covers\" } },\r\n    { type: \"upload\", name: \"contract\", label: \"Contract\", props: { accept: \".pdf\" } },\r\n    { type: \"editor\", name: \"brief\", label: \"Brief\" },\r\n    { type: \"editor\", name: \"contentHtml\", label: \"Scope\", props: { enableHtmlMode: true } },\r\n  ]}\r\n  formProps={{\r\n    upload: uploadFile,\r\n    defaultUploadProps: { accept: \"image/*,.pdf\", maxSize: 10 * 1024 * 1024 },\r\n  }}\r\n  get={getProject}\r\n  list={listProjects}\r\n  create={createProject}\r\n  update={updateProject}\r\n  delete={deleteProject}\r\n  features={[\"bulk\"]}\r\n/>\r\n```\r\n\r\n### 4. REST API one-liner\r\n\r\n```tsx\r\nimport { ApnaCrud, createRestCrud, defineCrudResource } from \"@alsocoder/apna-crud\"\r\n\r\nconst api = createRestCrud<Product>(\"https://api.example.com\", \"/products\")\r\n\r\nexport const productsResource = defineCrudResource({\r\n  name: \"products\",\r\n  title: \"Products\",\r\n  columns: [...],\r\n  formFields: [...],\r\n  ...api,\r\n})\r\n\r\n<ApnaCrud {...productsResource} baseUrl=\"https://api.example.com\" endpoint=\"/products\" />\r\n```\r\n\r\n---\r\n\r\n## Hooks & utilities\r\n\r\n```tsx\r\nimport { useApnaCrud, createRestCrud, fetchJson, defineCrudResource } from \"@alsocoder/apna-crud\"\r\nimport { ApnaCrudPageRoutes, getApnaCrudBasePath } from \"@alsocoder/apna-crud\"\r\n```\r\n\r\n| Export | Description |\r\n|--------|-------------|\r\n| `useApnaCrud(config, baseUrl?)` | Headless CRUD state (advanced) |\r\n| `createRestCrud(baseUrl, endpoint)` | REST list/create/update/delete/get helpers |\r\n| `fetchJson(url, options?)` | JSON fetch with error handling |\r\n| `defineCrudResource(config)` | Type-safe resource config builder |\r\n| `ApnaCrudPageRoutes` | Low-level page mode router (used internally) |\r\n| `getApnaCrudBasePath(config)` | Resolve base path for page mode |\r\n\r\n---\r\n\r\n## ApnaPanel + ApnaCrud full wiring\r\n\r\n```tsx\r\n// panel config menu\r\nmenu: defineMenu([\r\n  { id: \"dashboard\", label: \"Dashboard\", href: \"/\" },\r\n  { id: \"products\", label: \"Products\", href: \"/crud/products\", permission: \"products.view\" },\r\n  { id: \"users\", label: \"Users\", href: \"/crud/users\", permission: \"users.view\" },\r\n])\r\n\r\n// routes\r\n<Route path=\"/\" element={<ApnaPanelRoute permissions=\"dashboard.view\"><ApnaPanelHomePage /></ApnaPanelRoute>} />\r\n<Route path=\"/crud/products\" element={<ApnaPanelRoute permissions=\"products.view\"><ProductsPage /></ApnaPanelRoute>} />\r\n<Route path=\"/crud/users/*\" element={<ApnaPanelRoute permissions=\"users.view\"><UsersPage /></ApnaPanelRoute>} />\r\n\r\n// products page — modal mode\r\nfunction ProductsPage() {\r\n  return <ApnaCrud formMode=\"modal\" {...productsResource} formProps={sharedFormProps} />\r\n}\r\n\r\n// users page — page mode (needs splat route)\r\nfunction UsersPage() {\r\n  return <ApnaCrud formMode=\"page\" basePath=\"/crud/users\" {...usersResource} formProps={sharedFormProps} />\r\n}\r\n```\r\n\r\nShared `formProps` (upload, schema) at page or app level — set once, pass to every `<ApnaCrud />`.\r\n\r\n---\r\n\r\n## Playground demos\r\n\r\nSee `ApnaPanel/playground` for live examples:\r\n\r\n| Demo | `formMode` | Features |\r\n|------|------------|----------|\r\n| Products | `modal` | Basic fields, bulk delete |\r\n| Orders | `drawer` | Drawer overlay |\r\n| Users | `page` | Route-based forms |\r\n| Projects | `page` | Editor, upload, media, 18+ fields |\r\n\r\n```bash\r\ncd ApnaPanel && npm run dev:playground\r\n```\r\n\r\n---\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-e81127fc6863f01488ba275de49af459"}