{"_id":"@alsocoder/apna-panel","name":"@alsocoder/apna-panel","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alsocoder/apna-panel","version":"0.1.0","description":"React admin panel framework with permissions, navigation, themes, and layout shell for the Alsocoder ecosystem.","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":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["react","admin","dashboard","panel","sidebar","permissions","rbac","apna-panel","alsocoder"],"repository":{"type":"git","url":"git+https://github.com/alsocoders/apna-panel.git"},"publishConfig":{"access":"public"},"engines":{"node":">=18"},"peerDependencies":{"react":">=18","react-dom":">=18"},"dependencies":{"@alsocoder/apna-input":"^0.1.4","@alsocoder/apna-modal":"^0.1.2","@alsocoder/apna-toast":"^0.1.1","@radix-ui/react-collapsible":"^1.1.11","@radix-ui/react-dropdown-menu":"^2.1.15","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","tsup":"^8.5.0","typescript":"^5.9.2"},"overrides":{"@types/react":"^19.1.8","@types/react-dom":"^19.1.6"},"_id":"@alsocoder/apna-panel@0.1.0","bugs":{"url":"https://github.com/alsocoders/apna-panel/issues"},"homepage":"https://github.com/alsocoders/apna-panel#readme","_nodeVersion":"24.16.0","_npmVersion":"12.0.1","dist":{"integrity":"sha512-hF8gC2EUTEH4XJZqNH6p1VH30D3cj4l8fIByIt7jaPZX2GBugEbBjPgDY6Y4AfbgzaLf5v19+dA8pCOyW1n++w==","shasum":"37667257c4492698bf2e767360e534f830ed9885","tarball":"https://registry.npmjs.org/@alsocoder/apna-panel/-/apna-panel-0.1.0.tgz","fileCount":10,"unpackedSize":771703,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFmEouizSmKRVMWUuT8Ejm+DWSROh+mQfdKhWhr1Tb0lAiBhrVmW9pB9/hMqFr2/m8pg3k4FuF3keQAp5RdyCUPyFw=="}]},"_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-panel_0.1.0_1786262570326_0.19048142391175715"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T08:02:50.206Z","0.1.0":"2026-08-09T08:02:50.481Z","modified":"2026-08-09T08:02:50.662Z"},"maintainers":[{"name":"alsocoder-org","email":"alsocoder@gmail.com"}],"description":"React admin panel framework with permissions, navigation, themes, and layout shell for the Alsocoder ecosystem.","homepage":"https://github.com/alsocoders/apna-panel#readme","keywords":["react","admin","dashboard","panel","sidebar","permissions","rbac","apna-panel","alsocoder"],"repository":{"type":"git","url":"git+https://github.com/alsocoders/apna-panel.git"},"author":{"name":"Also Coder","url":"https://alsocoder.com"},"bugs":{"url":"https://github.com/alsocoders/apna-panel/issues"},"license":"MIT","readme":"# @alsocoder/apna-panel\r\n\r\nReact admin panel **shell framework** for the Alsocoder ecosystem — layout, sidebar, header, RBAC, themes, JSON dashboards, plugins, and mobile navigation.\r\n\r\nWorks with `@alsocoder/apna-panel-react-router`, `@alsocoder/apna-panel-auth`, `@alsocoder/apna-crud`, and other ecosystem packages.\r\n\r\n---\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @alsocoder/apna-panel @alsocoder/apna-panel-react-router react-router-dom\r\n```\r\n\r\nOptional:\r\n\r\n```bash\r\nnpm install @alsocoder/apna-panel-auth   # login page + session gate\r\nnpm install @alsocoder/apna-crud         # CRUD pages inside panel\r\n```\r\n\r\nPeer dependencies: `react`, `react-dom` (>= 18).\r\n\r\n```tsx\r\nimport \"@alsocoder/apna-panel/styles.css\"\r\n```\r\n\r\n---\r\n\r\n## Full app example (Panel + CRUD + Dashboard)\r\n\r\n```tsx\r\nimport { Routes, Route } from \"react-router-dom\"\r\nimport {\r\n  ApnaPanelShell,\r\n  ApnaPanelHomePage,\r\n  ApnaPanelWidgetRegistryProvider,\r\n  ApnaPanelForbidden,\r\n  ApnaPanelNotFound,\r\n  definePanelConfig,\r\n  defineMenu,\r\n  defineDashboard,\r\n  defineDashboardWidgets,\r\n  defaultIcons,\r\n} from \"@alsocoder/apna-panel\"\r\nimport {\r\n  ApnaPanelRouterProvider,\r\n  ApnaPanelRoute,\r\n  useApnaPanelPluginRouteElements,\r\n} from \"@alsocoder/apna-panel-react-router\"\r\nimport { ApnaCrud } from \"@alsocoder/apna-crud\"\r\nimport \"@alsocoder/apna-crud/styles.css\"\r\n\r\n// ── Auth adapter ──────────────────────────────────────────────\r\nconst authAdapter = {\r\n  async getSession() {\r\n    return { user: { id: \"1\", name: \"Admin\", roles: [\"admin\"], permissions: [] } }\r\n  },\r\n  async login(credentials) {\r\n    // call your API, return session\r\n    return { user: { id: \"1\", name: \"Admin\", roles: [\"admin\"], permissions: [] } }\r\n  },\r\n  async logout() {},\r\n}\r\n\r\n// ── Dashboard widgets ─────────────────────────────────────────\r\nconst dashboard = defineDashboard({\r\n  sections: [\r\n    {\r\n      type: \"stats\",\r\n      id: \"kpis\",\r\n      items: [\r\n        { id: \"users\", title: \"Users\", value: \"1,248\", description: \"Total\", icon: \"users\", trend: { value: \"+12%\", positive: true } },\r\n        { id: \"orders\", title: \"Orders\", value: \"89\", description: \"This week\", icon: \"folder\" },\r\n      ],\r\n    },\r\n    {\r\n      type: \"row\",\r\n      id: \"charts\",\r\n      spans: [8, 4],\r\n      children: [\r\n        { type: \"slot\", id: \"chart\", widget: \"areaChart\", props: { title: \"Traffic\" } },\r\n        { type: \"slot\", id: \"list\", widget: \"recentList\", props: { title: \"Recent\" } },\r\n      ],\r\n    },\r\n  ],\r\n})\r\n\r\nconst widgets = defineDashboardWidgets({\r\n  areaChart: MyAreaChart,\r\n  recentList: MyRecentList,\r\n})\r\n\r\n// ── Panel config ──────────────────────────────────────────────\r\nconst config = definePanelConfig({\r\n  appName: \"My Admin\",\r\n  homeHref: \"/\",\r\n  dashboard,\r\n  layout: { default: \"vertical\", persistSidebar: true },\r\n  header: { showSearch: true, showNotifications: true, showUserMenu: true },\r\n  main: { showBreadcrumbs: true },\r\n  rolePermissions: {\r\n    admin: [\"dashboard.view\", \"users.view\", \"users.create\", \"users.update\", \"users.delete\"],\r\n    editor: [\"dashboard.view\", \"users.view\"],\r\n  },\r\n  superAdminRoles: [\"superadmin\"],\r\n  menu: defineMenu([\r\n    { id: \"dashboard\", label: \"Dashboard\", href: \"/\", icon: defaultIcons.dashboard, permission: \"dashboard.view\" },\r\n    {\r\n      id: \"management\",\r\n      type: \"group\",\r\n      label: \"Management\",\r\n      children: [\r\n        { id: \"users\", label: \"Users\", href: \"/users\", icon: defaultIcons.users, permission: \"users.view\" },\r\n      ],\r\n    },\r\n  ]),\r\n  notifications: {\r\n    fetch: async () => [\r\n      { id: \"1\", title: \"Welcome\", description: \"Panel loaded\", read: false },\r\n    ],\r\n    maxItems: 20,\r\n  },\r\n  userMenuItems: [\r\n    { id: \"profile\", label: \"Profile\", type: \"action\", onClick: () => {} },\r\n  ],\r\n  onUnauthorized: () => console.warn(\"Access denied\"),\r\n})\r\n\r\n// ── Routes (inside provider) ────────────────────────────────────\r\nfunction PanelRoutes() {\r\n  const pluginRoutes = useApnaPanelPluginRouteElements()\r\n  return (\r\n    <Routes>\r\n      <Route\r\n        path=\"/\"\r\n        element={\r\n          <ApnaPanelRoute permissions=\"dashboard.view\" redirectTo=\"/forbidden\">\r\n            <ApnaPanelHomePage />\r\n          </ApnaPanelRoute>\r\n        }\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              endpoint=\"/api/users\"\r\n              baseUrl={import.meta.env.VITE_API_URL}\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              permissions={{ list: \"users.view\", create: \"users.create\", update: \"users.update\", delete: \"users.delete\" }}\r\n            />\r\n          </ApnaPanelRoute>\r\n        }\r\n      />\r\n      {pluginRoutes}\r\n      <Route path=\"/forbidden\" element={<ApnaPanelForbidden />} />\r\n      <Route path=\"*\" element={<ApnaPanelNotFound />} />\r\n    </Routes>\r\n  )\r\n}\r\n\r\n// ── App root ────────────────────────────────────────────────────\r\nexport default function App() {\r\n  return (\r\n    <ApnaPanelWidgetRegistryProvider widgets={widgets}>\r\n      <ApnaPanelRouterProvider config={config} authAdapter={authAdapter}>\r\n        <ApnaPanelShell\r\n          slots={{\r\n            headerBeforeProfile: <WalletBadge />,\r\n            sidebarFooter: (ctx) => (ctx.compact ? <SupportIcon /> : <SupportCard />),\r\n          }}\r\n        >\r\n          <PanelRoutes />\r\n        </ApnaPanelShell>\r\n      </ApnaPanelRouterProvider>\r\n    </ApnaPanelWidgetRegistryProvider>\r\n  )\r\n}\r\n```\r\n\r\n---\r\n\r\n## `definePanelConfig` — all options\r\n\r\n| Option | Type | Description |\r\n|--------|------|-------------|\r\n| `appName` | `string` | App title (breadcrumbs, brand fallback) |\r\n| `brand` | `ApnaPanelBrand` | `title`, `subtitle`, `logoMark`, `logoFull`, `href` |\r\n| `homeHref` | `string` | Home link for breadcrumbs (default `/`) |\r\n| `layout` | `ApnaPanelLayoutConfig` | See layout section |\r\n| `dashboard` | `ApnaPanelDashboardConfig` | JSON dashboard used by `ApnaPanelHomePage` |\r\n| `mobileNav` | `ApnaPanelMobileNavConfig` | Bottom nav order, `moreLabel`, `maxVisible` |\r\n| `themes` | `ApnaPanelTheme[]` | Custom themes with `cssVars` |\r\n| `defaultThemeId` | `string` | Initial theme id |\r\n| `rtl` | `boolean` | Initial RTL direction |\r\n| `enableShortcuts` | `boolean` | Keyboard shortcuts |\r\n| `shortcuts` | `ApnaPanelShortcut[]` | `{ id, keys, label, action }` — built-in: `openSearch`, `toggleSidebar` |\r\n| `menu` | `ApnaPanelMenuItem[]` | Sidebar / nav items |\r\n| `menuResolver` | `fn` | Async dynamic menu: `(ctx) => items \\| Promise<items>` |\r\n| `rolePermissions` | `Record<role, permissions[]>` | Role → permission map |\r\n| `superAdminRoles` | `string[]` | Roles that bypass all permission checks |\r\n| `resolvePermissions` | `fn` | Custom permission resolver from user + role map |\r\n| `plugins` | `ApnaPanelPlugin[]` | Plugin modules (menu, routes, slots) |\r\n| `widgets` | `ApnaPanelWidget[]` | Header/sidebar widget registrations |\r\n| `headerActions` | `ApnaPanelHeaderAction[]` | Extra header buttons |\r\n| `slots` | `ApnaPanelLayoutSlots` | Static slot content (overridden by shell `slots` prop) |\r\n| `header` | `ApnaPanelHeaderOptions` | `showSearch`, `showUserMenu`, `showNotifications`, `searchPlaceholder` |\r\n| `main` | `ApnaPanelMainOptions` | `showBreadcrumbs` (default `true`) |\r\n| `sidebar` | `ApnaPanelSidebarOptions` | `showBrand` |\r\n| `userMenuItems` | `ApnaPanelMenuItem[]` | Extra user dropdown items |\r\n| `notifications` | `{ fetch, maxItems }` | Notification fetcher for header bell |\r\n| `onUnauthorized` | `fn` | Called when route guard denies access |\r\n| `onSessionChange` | `fn` | Called when auth session changes |\r\n| `classNames` | `ApnaPanelClassNames` | CSS class overrides |\r\n| `icons` | `ApnaPanelIcons` | Icon overrides (`users`, `bell`, `dashboard`, etc.) |\r\n\r\n---\r\n\r\n## Menu items (`defineMenu`)\r\n\r\n```tsx\r\ndefineMenu([\r\n  // Simple link\r\n  { id: \"home\", label: \"Dashboard\", href: \"/\", icon: defaultIcons.dashboard, permission: \"dashboard.view\" },\r\n\r\n  // Group with children\r\n  {\r\n    id: \"settings-group\",\r\n    type: \"group\",\r\n    label: \"Settings\",\r\n    children: [\r\n      { id: \"general\", label: \"General\", href: \"/settings\" },\r\n      { id: \"team\", label: \"Team\", href: \"/settings/team\", permissionAny: [\"team.view\", \"admin\"] },\r\n    ],\r\n  },\r\n\r\n  // Divider\r\n  { id: \"div-1\", type: \"divider\" },\r\n\r\n  // External link\r\n  { id: \"docs\", label: \"Docs\", href: \"https://docs.example.com\", external: true },\r\n\r\n  // Action (no navigation)\r\n  { id: \"support\", label: \"Get support\", type: \"action\", onClick: () => openChat() },\r\n\r\n  // Hidden dynamically\r\n  { id: \"beta\", label: \"Beta\", href: \"/beta\", hidden: (ctx) => !ctx.can(\"beta.view\") },\r\n])\r\n```\r\n\r\n| Menu field | Type | Description |\r\n|------------|------|-------------|\r\n| `id` | `string` | Unique id (used for active state, mobile nav order) |\r\n| `type` | `\"link\" \\| \"group\" \\| \"divider\" \\| \"external\" \\| \"action\"` | Item type |\r\n| `label` | `string` | Display text |\r\n| `href` | `string` | Route path |\r\n| `icon` | `ReactNode` | Sidebar icon |\r\n| `badge` | `string \\| number` | Badge on menu item |\r\n| `permission` | `string` | Required permission |\r\n| `permissionAny` | `string[]` | Any of these permissions |\r\n| `permissionAll` | `string[]` | All of these permissions |\r\n| `roles` / `rolesAny` | `string[]` | Role-based visibility |\r\n| `exact` | `boolean` | Exact path match for active state |\r\n| `defaultOpen` | `boolean` | Open nested group by default |\r\n| `children` | `ApnaPanelMenuItem[]` | Nested items |\r\n| `hidden` | `boolean \\| fn` | Hide item |\r\n\r\n---\r\n\r\n## Layout modes\r\n\r\n```tsx\r\nlayout: {\r\n  default: \"vertical\",    // sidebar left (default)\r\n  // \"horizontal\"  — top nav bar, no sidebar\r\n  // \"compact\"     — icon-only sidebar with flyout\r\n  persistSidebar: true,   // save collapsed state in localStorage\r\n  defaultCollapsed: false,\r\n}\r\n```\r\n\r\n| Mode | Behavior |\r\n|------|----------|\r\n| `vertical` | Standard left sidebar |\r\n| `horizontal` | Sidebar hidden; `ApnaPanelHorizontalNav` in header |\r\n| `compact` | Icon sidebar + flyout menus |\r\n| `hybrid` | Reserved for future use |\r\n\r\nResponsive behavior:\r\n- **≤768px** — sidebar hidden, tablet drawer\r\n- **≤475px** — bottom mobile nav\r\n\r\n---\r\n\r\n## Dashboard (JSON config)\r\n\r\n### Section types\r\n\r\n```tsx\r\ndefineDashboard({\r\n  sections: [\r\n    // KPI stat cards\r\n    {\r\n      type: \"stats\",\r\n      id: \"kpis\",\r\n      span: 12,\r\n      items: [\r\n        {\r\n          id: \"users\",\r\n          title: \"Users\",\r\n          value: \"1,248\",\r\n          description: \"Total registered\",\r\n          trend: { value: \"+12%\", positive: true },\r\n          icon: \"users\",           // key from mergedIcons\r\n          permission: \"dashboard.view\",\r\n          span: 3,\r\n        },\r\n      ],\r\n    },\r\n\r\n    // Custom widget slot\r\n    {\r\n      type: \"slot\",\r\n      id: \"header\",\r\n      widget: \"pageHeader\",\r\n      span: 12,\r\n      props: { title: \"Overview\", subtitle: \"Last 30 days\" },\r\n    },\r\n\r\n    // Row with column spans (12-col grid)\r\n    {\r\n      type: \"row\",\r\n      id: \"charts\",\r\n      spans: [8, 4],\r\n      children: [\r\n        { type: \"slot\", id: \"chart\", widget: \"areaChart\", props: { title: \"Traffic\" } },\r\n        { type: \"slot\", id: \"bars\", widget: \"barChart\" },\r\n      ],\r\n    },\r\n\r\n    // Nested grid\r\n    {\r\n      type: \"grid\",\r\n      id: \"lists\",\r\n      columns: 2,\r\n      children: [\r\n        { type: \"slot\", id: \"recent\", widget: \"recentList\" },\r\n        { type: \"slot\", id: \"activity\", widget: \"activityList\" },\r\n      ],\r\n    },\r\n  ],\r\n})\r\n```\r\n\r\n### Register widgets + render\r\n\r\n```tsx\r\nconst widgets = defineDashboardWidgets({\r\n  pageHeader: ({ title, subtitle }) => <h1>{title}</h1>,\r\n  areaChart: AreaChartWidget,\r\n})\r\n\r\n<ApnaPanelWidgetRegistryProvider widgets={widgets}>\r\n  <ApnaPanelDashboardRenderer config={dashboard} />\r\n  {/* or */}\r\n  <ApnaPanelHomePage />  {/* reads config.dashboard + plugin slots */}\r\n</ApnaPanelWidgetRegistryProvider>\r\n```\r\n\r\n### `ApnaPanelStatCard` (manual JSX)\r\n\r\n```tsx\r\n<ApnaPanelStatCard\r\n  title=\"Revenue\"\r\n  value=\"$12.4k\"\r\n  description=\"This month\"\r\n  trend={{ value: \"+5%\", positive: true }}\r\n  icon={defaultIcons.dashboard}\r\n/>\r\n```\r\n\r\n---\r\n\r\n## Plugins\r\n\r\nExtend the panel without editing core routes/menu:\r\n\r\n```tsx\r\nimport { definePlugin } from \"@alsocoder/apna-panel\"\r\nimport { useApnaPanelPluginRouteElements } from \"@alsocoder/apna-panel-react-router\"\r\n\r\nconst billingPlugin = definePlugin({\r\n  id: \"billing\",\r\n  setup(api) {\r\n    api.registerMenuItem({ id: \"billing\", label: \"Billing\", href: \"/billing\", permission: \"billing.view\" })\r\n    api.registerRoute({\r\n      id: \"billing-route\",\r\n      path: \"/billing\",\r\n      element: <BillingPage />,\r\n      permissions: \"billing.view\",\r\n      redirectTo: \"/forbidden\",\r\n    })\r\n    api.registerWidget({\r\n      id: \"billing-widget\",\r\n      area: \"headerBeforeProfile\",\r\n      permission: \"billing.view\",\r\n      order: 10,\r\n      render: () => <BillingBadge />,\r\n    })\r\n    api.registerHeaderAction({\r\n      id: \"billing-action\",\r\n      order: 5,\r\n      render: () => <button>New invoice</button>,\r\n    })\r\n    api.registerSlot({\r\n      id: \"billing-promo\",\r\n      slot: \"before-dashboard\",\r\n      render: () => <PromoBanner />,\r\n    })\r\n  },\r\n})\r\n\r\n// config\r\nplugins: [billingPlugin]\r\n\r\n// routes — spread inside <Routes>, before catch-all *\r\nfunction PanelRoutes() {\r\n  const pluginRoutes = useApnaPanelPluginRouteElements()\r\n  return (\r\n    <Routes>\r\n      {/* your routes */}\r\n      {pluginRoutes}\r\n      <Route path=\"*\" element={<ApnaPanelNotFound />} />\r\n    </Routes>\r\n  )\r\n}\r\n```\r\n\r\nPlugin slot names: `before-dashboard`, `after-dashboard` (or any custom string with `<ApnaPanelPluginSlot name=\"...\" />`).\r\n\r\n---\r\n\r\n## Slots (header / sidebar customization)\r\n\r\nPriority: `ApnaPanelShell` `slots` prop > `config.slots` > defaults.\r\n\r\n```tsx\r\n<ApnaPanelShell\r\n  slots={{\r\n    headerStart: <OrgSwitcher />,\r\n    headerBeforeSearch: <QuickActions />,\r\n    headerAfterSearch: <FilterToggle />,\r\n    headerBeforeProfile: <WalletBalance />,\r\n    headerEnd: <ThemeToggle />,\r\n    headerSearch: null,          // hide default search\r\n    headerProfile: <CustomUserMenu />,\r\n    sidebarHeader: null,         // hide default brand\r\n    sidebarFooter: (ctx) =>       // function receives layout context\r\n      ctx.compact ? <SupportIcon /> : <SupportCard />,\r\n  }}\r\n>\r\n```\r\n\r\n| Slot area | Position |\r\n|-----------|----------|\r\n| `headerStart` | Left of search |\r\n| `headerBeforeSearch` | Before search input |\r\n| `headerAfterSearch` | After search input |\r\n| `headerBeforeProfile` | Before user menu |\r\n| `headerEnd` | Far right |\r\n| `headerSearch` | Replace search (`null` = hide) |\r\n| `headerProfile` | Replace user menu (`null` = hide) |\r\n| `sidebarHeader` | Replace sidebar brand |\r\n| `sidebarFooter` | Sidebar bottom |\r\n\r\nWidget areas (via `registerWidget` or `config.widgets`): same header areas + `sidebarFooter`.\r\n\r\n---\r\n\r\n## Auth & permissions\r\n\r\n### Auth adapter\r\n\r\n```tsx\r\nconst authAdapter: ApnaPanelAuthAdapter = {\r\n  getSession: () => fetch(\"/api/me\").then(r => r.json()),\r\n  login: (credentials) => fetch(\"/api/login\", { method: \"POST\", body: JSON.stringify(credentials) }).then(r => r.json()),\r\n  logout: () => fetch(\"/api/logout\", { method: \"POST\" }),\r\n  refresh: () => fetch(\"/api/refresh\").then(r => r.json()),\r\n}\r\n\r\n<ApnaPanelRouterProvider config={config} authAdapter={authAdapter}>\r\n```\r\n\r\n### Route guards\r\n\r\n```tsx\r\nimport { ApnaPanelRoute, ApnaPanelRouteGuard } from \"@alsocoder/apna-panel-react-router\"\r\n\r\n<ApnaPanelRoute\r\n  permissions={[\"users.view\", \"users.create\"]}  // all required\r\n  permissionAny={[\"admin\", \"superadmin\"]}\r\n  roles=\"admin\"\r\n  redirectTo=\"/forbidden\"\r\n  fallback={<ApnaPanelForbidden />}\r\n>\r\n  <UsersPage />\r\n</ApnaPanelRoute>\r\n\r\n// Guest-only (login page)\r\n<ApnaPanelRoute guestOnly redirectTo=\"/\">\r\n  <LoginPage />\r\n</ApnaPanelRoute>\r\n```\r\n\r\n### Conditional UI\r\n\r\n```tsx\r\nimport { Can, CanAny, CanAll, CanNot } from \"@alsocoder/apna-panel\"\r\n\r\n<Can permission=\"users.create\">\r\n  <button>Add user</button>\r\n</Can>\r\n\r\n<CanAny permissions={[\"reports.view\", \"analytics.view\"]} fallback={<span>No access</span>}>\r\n  <ReportsLink />\r\n</CanAny>\r\n```\r\n\r\n### Hooks\r\n\r\n| Hook | Returns |\r\n|------|---------|\r\n| `useApnaPanel()` | `config`, `layoutMode`, `setLayoutMode`, `sidebarCollapsed`, `toggleSidebar`, `registerWidget`, etc. |\r\n| `useApnaPanelAuth()` | `session`, `isAuthenticated`, `isLoading`, `login`, `logout` |\r\n| `useApnaPanelPermissions()` | `can`, `canAny`, `canAll`, `roles`, `isSuperAdmin` |\r\n| `useApnaPanelNavigation()` | `menu`, `activeItem`, `breadcrumbs`, `setBreadcrumbs`, `navigate`, `pathname` |\r\n| `useApnaPanelTheme()` | `themeId`, `setThemeId`, `mode`, `setMode`, `rtl`, `setRtl`, `toggleMode` |\r\n| `useApnaPanelNotifications()` | `items`, `unreadCount`, `markRead`, `refresh` |\r\n| `useApnaPanelBreadcrumbs()` | `setItems` |\r\n| `useApnaPanelBreakpoint()` | `isMobileNav`, `isTablet` |\r\n\r\n---\r\n\r\n## Auth pages (`@alsocoder/apna-panel-auth`)\r\n\r\n```bash\r\nnpm install @alsocoder/apna-panel-auth @alsocoder/apna-input\r\n```\r\n\r\n```tsx\r\nimport \"@alsocoder/apna-panel-auth/styles.css\"\r\nimport { ApnaPanelLoginPage, ApnaPanelSessionGate } from \"@alsocoder/apna-panel-auth\"\r\n\r\n<Route\r\n  path=\"/login\"\r\n  element={\r\n    <ApnaPanelLoginPage\r\n      title=\"Sign in\"\r\n      subtitle=\"Access your admin panel\"\r\n      emailLabel=\"Email\"\r\n      passwordLabel=\"Password\"\r\n      submitLabel=\"Sign in\"\r\n      redirectTo=\"/\"\r\n      onNavigate={(href) => navigate(href)}\r\n      onSuccess={() => toast.success(\"Welcome back\")}\r\n    />\r\n  }\r\n/>\r\n\r\n<ApnaPanelSessionGate\r\n  loginPath=\"/login\"\r\n  onRedirectToLogin={(path) => navigate(path)}\r\n  loadingFallback={<ApnaPanelLoadingScreen />}\r\n>\r\n  <ApnaPanelShell>...</ApnaPanelShell>\r\n</ApnaPanelSessionGate>\r\n```\r\n\r\n---\r\n\r\n## Starter templates\r\n\r\nPre-built menu + dashboard configs for common panel types:\r\n\r\n```tsx\r\nimport { applyStarterTemplate, starterTemplates } from \"@alsocoder/apna-panel\"\r\n\r\nconst config = definePanelConfig(\r\n  applyStarterTemplate(\"saas\", {\r\n    appName: \"My SaaS\",\r\n    // merge any extra config\r\n  })\r\n)\r\n\r\n// Available templates:\r\nstarterTemplates.saas       // users, billing, settings\r\nstarterTemplates.cms        // posts, pages, media\r\nstarterTemplates.ecommerce  // products, orders, customers\r\n```\r\n\r\n---\r\n\r\n## Themes & RTL\r\n\r\n```tsx\r\nimport { useApnaPanelTheme } from \"@alsocoder/apna-panel\"\r\n\r\nfunction ThemeToggle() {\r\n  const { mode, setMode, toggleMode, rtl, setRtl } = useApnaPanelTheme()\r\n  return (\r\n    <>\r\n      <button onClick={toggleMode}>Toggle {mode}</button>\r\n      <button onClick={() => setRtl(!rtl)}>RTL</button>\r\n    </>\r\n  )\r\n}\r\n\r\n// Custom theme in config\r\nthemes: [\r\n  { id: \"brand\", label: \"Brand\", mode: \"light\", cssVars: { \"--primary\": \"#6366f1\" } },\r\n]\r\n```\r\n\r\n---\r\n\r\n## Mobile bottom nav\r\n\r\nAt **≤475px**, sidebar hides and a fixed bottom nav appears.\r\n\r\n```tsx\r\nmobileNav: {\r\n  order: [\"dashboard\", \"users\", \"reports\", \"settings\"],\r\n  moreLabel: \"More\",\r\n  maxVisible: 4,\r\n}\r\n```\r\n\r\n- ≤5 items: all shown\r\n- \\>5 items: first 4 + **More** overflow sheet\r\n\r\n---\r\n\r\n## Ecosystem integration\r\n\r\n| Package | Use in panel |\r\n|---------|--------------|\r\n| `@alsocoder/apna-crud` | CRUD pages inside `<ApnaPanelMain>` routes |\r\n| `@alsocoder/apna-form` | Dynamic forms (via ApnaCrud) |\r\n| `@alsocoder/apna-table` | Tables (via ApnaCrud) |\r\n| `@alsocoder/apna-modal` | Bundled in panel provider |\r\n| `@alsocoder/apna-toast` | Bundled in panel provider |\r\n| `@alsocoder/apna-upload` | Upload fields in CRUD forms |\r\n| `@alsocoder/apna-media-library` | Media picker fields |\r\n\r\n---\r\n\r\n## Playground\r\n\r\n```bash\r\ncd ApnaPanel\r\nnpm run dev:playground\r\n```\r\n\r\nOpens at `http://localhost:5180` — dashboard, CRUD (modal/drawer/page), plugins, permissions, mobile nav.\r\n\r\n---\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-4e80e3a11b668064699590adbca8c4e6"}