izen-react-starter 1.1.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +1284 -35
  2. package/dist/{MIMHJGAX-CX0R0gR-.js → MIMHJGAX-BUG-Z2YP.js} +1 -1
  3. package/dist/{Q7LWSL4U-DisRfAmB.js → Q7LWSL4U-CGtIukTY.js} +2 -2
  4. package/dist/{VLTTJS3N-hByn9hgE.js → VLTTJS3N-DuHfwBTL.js} +2 -2
  5. package/dist/components/charts/ChartAreaInteractive.d.ts +2 -0
  6. package/dist/components/charts/ChartAreaInteractive.d.ts.map +1 -0
  7. package/dist/components/charts/index.d.ts +2 -0
  8. package/dist/components/charts/index.d.ts.map +1 -0
  9. package/dist/components/common/Header.d.ts +13 -0
  10. package/dist/components/common/Header.d.ts.map +1 -0
  11. package/dist/components/common/Heading.d.ts +7 -0
  12. package/dist/components/common/Heading.d.ts.map +1 -0
  13. package/dist/components/common/PageHead.d.ts +5 -0
  14. package/dist/components/common/PageHead.d.ts.map +1 -0
  15. package/dist/components/common/ThemeToggle.d.ts +5 -0
  16. package/dist/components/common/ThemeToggle.d.ts.map +1 -0
  17. package/dist/components/common/index.d.ts +5 -0
  18. package/dist/components/common/index.d.ts.map +1 -0
  19. package/dist/components/date-picker/DatePickerWithRange.d.ts +8 -0
  20. package/dist/components/date-picker/DatePickerWithRange.d.ts.map +1 -0
  21. package/dist/components/date-picker/DateRangeFilter.d.ts +7 -0
  22. package/dist/components/date-picker/DateRangeFilter.d.ts.map +1 -0
  23. package/dist/components/date-picker/index.d.ts +3 -0
  24. package/dist/components/date-picker/index.d.ts.map +1 -0
  25. package/dist/components/form/CheckboxGroup.d.ts +15 -0
  26. package/dist/components/form/CheckboxGroup.d.ts.map +1 -0
  27. package/dist/components/form/ComboboxSelect.d.ts +24 -0
  28. package/dist/components/form/ComboboxSelect.d.ts.map +1 -0
  29. package/dist/components/form/DatePicker.d.ts +14 -0
  30. package/dist/components/form/DatePicker.d.ts.map +1 -0
  31. package/dist/components/form/FileUploadButton.d.ts +19 -0
  32. package/dist/components/form/FileUploadButton.d.ts.map +1 -0
  33. package/dist/components/form/FormButtons.d.ts +19 -0
  34. package/dist/components/form/FormButtons.d.ts.map +1 -0
  35. package/dist/components/form/FormInput.d.ts +15 -0
  36. package/dist/components/form/FormInput.d.ts.map +1 -0
  37. package/dist/components/form/FormLayout.d.ts +21 -0
  38. package/dist/components/form/FormLayout.d.ts.map +1 -0
  39. package/dist/components/form/RadioGroup.d.ts +14 -0
  40. package/dist/components/form/RadioGroup.d.ts.map +1 -0
  41. package/dist/components/form/Select.d.ts +21 -0
  42. package/dist/components/form/Select.d.ts.map +1 -0
  43. package/dist/components/form/TextInput.d.ts +13 -0
  44. package/dist/components/form/TextInput.d.ts.map +1 -0
  45. package/dist/components/form/TimeInput.d.ts +13 -0
  46. package/dist/components/form/TimeInput.d.ts.map +1 -0
  47. package/dist/components/form/index.d.ts +23 -0
  48. package/dist/components/form/index.d.ts.map +1 -0
  49. package/dist/components/layout/AppSidebar.d.ts +24 -0
  50. package/dist/components/layout/AppSidebar.d.ts.map +1 -0
  51. package/dist/components/layout/DashboardLayout.d.ts +12 -0
  52. package/dist/components/layout/DashboardLayout.d.ts.map +1 -0
  53. package/dist/components/layout/NavDocuments.d.ts +11 -0
  54. package/dist/components/layout/NavDocuments.d.ts.map +1 -0
  55. package/dist/components/layout/NavMain.d.ts +24 -0
  56. package/dist/components/layout/NavMain.d.ts.map +1 -0
  57. package/dist/components/layout/NavSecondary.d.ts +14 -0
  58. package/dist/components/layout/NavSecondary.d.ts.map +1 -0
  59. package/dist/components/layout/NavUser.d.ts +18 -0
  60. package/dist/components/layout/NavUser.d.ts.map +1 -0
  61. package/dist/components/layout/SiteHeader.d.ts +7 -0
  62. package/dist/components/layout/SiteHeader.d.ts.map +1 -0
  63. package/dist/components/layout/index.d.ts +8 -0
  64. package/dist/components/layout/index.d.ts.map +1 -0
  65. package/dist/components/modals/AlertModal.d.ts +10 -0
  66. package/dist/components/modals/AlertModal.d.ts.map +1 -0
  67. package/dist/components/modals/DeleteDialog.d.ts +7 -0
  68. package/dist/components/modals/DeleteDialog.d.ts.map +1 -0
  69. package/dist/components/modals/PopupModal.d.ts +16 -0
  70. package/dist/components/modals/PopupModal.d.ts.map +1 -0
  71. package/dist/components/modals/index.d.ts +4 -0
  72. package/dist/components/modals/index.d.ts.map +1 -0
  73. package/dist/components/navigation/DashboardNav.d.ts +18 -0
  74. package/dist/components/navigation/DashboardNav.d.ts.map +1 -0
  75. package/dist/components/navigation/MobileSidebar.d.ts +12 -0
  76. package/dist/components/navigation/MobileSidebar.d.ts.map +1 -0
  77. package/dist/components/navigation/Sidebar.d.ts +8 -0
  78. package/dist/components/navigation/Sidebar.d.ts.map +1 -0
  79. package/dist/components/navigation/UserNav.d.ts +10 -0
  80. package/dist/components/navigation/UserNav.d.ts.map +1 -0
  81. package/dist/components/navigation/index.d.ts +5 -0
  82. package/dist/components/navigation/index.d.ts.map +1 -0
  83. package/dist/components/overlay/Overlay.d.ts +5 -0
  84. package/dist/components/overlay/Overlay.d.ts.map +1 -0
  85. package/dist/components/overlay/index.d.ts +2 -0
  86. package/dist/components/overlay/index.d.ts.map +1 -0
  87. package/dist/components/search/TableSearchInput.d.ts +5 -0
  88. package/dist/components/search/TableSearchInput.d.ts.map +1 -0
  89. package/dist/components/search/index.d.ts +2 -0
  90. package/dist/components/search/index.d.ts.map +1 -0
  91. package/dist/components/table/DataTable.d.ts +14 -0
  92. package/dist/components/table/DataTable.d.ts.map +1 -0
  93. package/dist/components/table/DataTableSkeleton.d.ts +9 -0
  94. package/dist/components/table/DataTableSkeleton.d.ts.map +1 -0
  95. package/dist/components/table/Pagination.d.ts +7 -0
  96. package/dist/components/table/Pagination.d.ts.map +1 -0
  97. package/dist/components/table/PaginationSection.d.ts +8 -0
  98. package/dist/components/table/PaginationSection.d.ts.map +1 -0
  99. package/dist/components/table/ServerDataTable.d.ts +10 -0
  100. package/dist/components/table/ServerDataTable.d.ts.map +1 -0
  101. package/dist/components/table/SpecialDaysTable.d.ts +26 -0
  102. package/dist/components/table/SpecialDaysTable.d.ts.map +1 -0
  103. package/dist/components/table/Table.d.ts +28 -0
  104. package/dist/components/table/Table.d.ts.map +1 -0
  105. package/dist/components/table/TableActions.d.ts +19 -0
  106. package/dist/components/table/TableActions.d.ts.map +1 -0
  107. package/dist/components/table/TableHeader.d.ts +6 -0
  108. package/dist/components/table/TableHeader.d.ts.map +1 -0
  109. package/dist/components/table/index.d.ts +15 -0
  110. package/dist/components/table/index.d.ts.map +1 -0
  111. package/dist/components/tabs/GenericTab.d.ts +7 -0
  112. package/dist/components/tabs/GenericTab.d.ts.map +1 -0
  113. package/dist/components/tabs/index.d.ts +2 -0
  114. package/dist/components/tabs/index.d.ts.map +1 -0
  115. package/dist/components/ui/index.d.ts +0 -2
  116. package/dist/components/ui/index.d.ts.map +1 -1
  117. package/dist/constants/urls.d.ts +2 -0
  118. package/dist/constants/urls.d.ts.map +1 -0
  119. package/dist/index-BNRXa5M-.js +62186 -0
  120. package/dist/index.d.ts +16 -2
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/izen-react-starter.css +1 -1
  123. package/dist/rbac/AccessControlWrapper.d.ts +32 -5
  124. package/dist/rbac/AccessControlWrapper.d.ts.map +1 -1
  125. package/dist/rbac/RBACProvider.d.ts +55 -0
  126. package/dist/rbac/RBACProvider.d.ts.map +1 -0
  127. package/dist/rbac/UpdateAccessControlWrapper.d.ts +14 -2
  128. package/dist/rbac/UpdateAccessControlWrapper.d.ts.map +1 -1
  129. package/dist/rbac/access-rules.d.ts +26 -45
  130. package/dist/rbac/access-rules.d.ts.map +1 -1
  131. package/dist/rbac/index.d.ts +4 -1
  132. package/dist/rbac/index.d.ts.map +1 -1
  133. package/dist/rbac/useAccessControl.d.ts +16 -3
  134. package/dist/rbac/useAccessControl.d.ts.map +1 -1
  135. package/dist/react-starter.js +324 -298
  136. package/dist/react-starter.umd.cjs +373 -199
  137. package/dist/services/apiService.d.ts +3 -4
  138. package/dist/services/apiService.d.ts.map +1 -1
  139. package/package.json +10 -2
  140. package/dist/index-BlTeKmzn.js +0 -45406
package/README.md CHANGED
@@ -11,7 +11,7 @@ A modern React component library built with Vite, TypeScript, and best practices
11
11
  - 🎨 **Theme Provider**: Dark/light mode with system preference support
12
12
  - 🌐 **API Service**: Axios-based service for data fetching and posting
13
13
  - 🔄 **React Query Integration**: Built-in query client and provider
14
- - **RBAC System**: Role-based access control with customizable permissions
14
+ - 🔐 **RBAC System**: Fully configurable role-based access control - bring your own roles and resources
15
15
  - 🎣 **Custom Hooks**: Utility hooks like useIsMobile, useRouter, usePathname
16
16
  - 🛠️ **Utility Functions**: Helper functions for common tasks (cn, debounce, throttle, etc.)
17
17
  - 💾 **Cache Utilities**: React Query cache manipulation helpers
@@ -211,6 +211,269 @@ function MyApp() {
211
211
  }
212
212
  ```
213
213
 
214
+ ### Generic Table
215
+
216
+ ```tsx
217
+ import { GenericTable, ActionType } from 'izen-react-starter';
218
+
219
+ type SpecialDay = {
220
+ id: number;
221
+ name: string;
222
+ start: string;
223
+ end: string;
224
+ floatingHoliday?: boolean;
225
+ };
226
+
227
+ function SpecialDaysView({ rows, pagination, onAction }: { rows: SpecialDay[]; pagination: any; onAction: (row: SpecialDay, action: ActionType) => void }) {
228
+ return (
229
+ <GenericTable
230
+ rows={rows}
231
+ columns={[
232
+ { header: 'Name', render: (row) => `${row.name}${row.floatingHoliday ? ' (Floating Holiday)' : ''}` },
233
+ { header: 'Start', render: (row) => new Date(row.start).toLocaleDateString() },
234
+ { header: 'End', render: (row) => new Date(row.end).toLocaleDateString() },
235
+ ]}
236
+ getRowId={(row) => row.id}
237
+ onAction={(row, action) => onAction(row, action)}
238
+ getActionLink={(row) => `/preferences/special-days/${row.id}`}
239
+ pagination={pagination}
240
+ emptyText="No special days found."
241
+ />
242
+ );
243
+ }
244
+ ```
245
+
246
+ ### Table Utilities
247
+
248
+ ```tsx
249
+ import { TableActions, Pagination, TableHeader, GenericTab } from 'izen-react-starter';
250
+
251
+ // Actions cell example
252
+ <TableActions
253
+ link="/resource/123"
254
+ Item={{ id: 123 }}
255
+ handleAction={(row, action) => console.log(row, action)}
256
+ />;
257
+
258
+ // Pagination example (expects meta/links/url shape)
259
+ <Pagination
260
+ meta={{ currentPage: 1, itemsPerPage: 10, totalItems: 42 }}
261
+ links={{ prev: null, next: '?page=2', first: '?page=1', last: '?page=5' }}
262
+ url="/api/resources"
263
+ />;
264
+
265
+ // Table header helper (basic)
266
+ <table>
267
+ <TableHeader headers={["Name", "Email"]} />
268
+ {/* ...rows */}
269
+ </table>;
270
+
271
+ // GenericTab helper to wrap tab content and reuse TableActions
272
+ <GenericTab
273
+ key="notes"
274
+ data={[{ id: 1, text: 'Note text' }]}
275
+ handleAction={(item, action) => console.log(item, action)}
276
+ />;
277
+ ```
278
+
279
+ ### Layout System
280
+
281
+ The library provides a complete layout system with sidebar, header, and navigation components.
282
+
283
+ #### DashboardLayout (Complete Layout)
284
+
285
+ A full dashboard layout that combines sidebar and header:
286
+
287
+ ```tsx
288
+ import { DashboardLayout, Overlay } from 'izen-react-starter';
289
+ import { Home, Settings, FileText, HelpCircle } from 'lucide-react';
290
+
291
+ function App() {
292
+ return (
293
+ <DashboardLayout
294
+ sidebarProps={{
295
+ brandName: "My App",
296
+ brandIcon: Home,
297
+ navMain: [
298
+ { title: "Dashboard", url: "/dashboard", icon: Home },
299
+ {
300
+ title: "Settings",
301
+ url: "#",
302
+ icon: Settings,
303
+ items: [
304
+ { title: "Profile", url: "/settings/profile" },
305
+ { title: "Security", url: "/settings/security" },
306
+ ]
307
+ },
308
+ ],
309
+ navSecondary: [
310
+ { title: "Help", url: "/help", icon: HelpCircle },
311
+ { title: "Docs", url: "/docs", icon: FileText, target: "_blank" },
312
+ ],
313
+ user: {
314
+ name: "John Doe",
315
+ email: "john@example.com",
316
+ avatar: "/avatar.jpg",
317
+ },
318
+ onLogout: () => console.log("Logout"),
319
+ userMenuItems: [
320
+ { label: "Profile", onClick: () => {} },
321
+ { label: "Settings", onClick: () => {} },
322
+ ],
323
+ }}
324
+ headerProps={{
325
+ pageTitles: {
326
+ "/dashboard": "Dashboard",
327
+ "/settings": "Settings",
328
+ },
329
+ defaultTitle: "My App",
330
+ }}
331
+ defaultOpen={true}
332
+ showOverlay={false}
333
+ overlayComponent={<Overlay show={false} />}
334
+ >
335
+ {/* Your page content */}
336
+ </DashboardLayout>
337
+ );
338
+ }
339
+ ```
340
+
341
+ #### AppSidebar (Sidebar Component)
342
+
343
+ Collapsible sidebar with navigation menu:
344
+
345
+ ```tsx
346
+ import { AppSidebar } from 'izen-react-starter';
347
+ import { Home, Settings, Users } from 'lucide-react';
348
+
349
+ <AppSidebar
350
+ brandName="Acme Inc"
351
+ brandIcon={Home}
352
+ navMain={[
353
+ { title: "Home", url: "/", icon: Home, badge: "3" },
354
+ {
355
+ title: "Settings",
356
+ url: "#",
357
+ icon: Settings,
358
+ items: [
359
+ { title: "Profile", url: "/settings/profile" },
360
+ { title: "Security", url: "/settings/security" },
361
+ ]
362
+ },
363
+ ]}
364
+ navSecondary={[
365
+ { title: "Help", url: "/help", icon: HelpCircle },
366
+ ]}
367
+ user={{
368
+ name: "Jane Doe",
369
+ email: "jane@example.com",
370
+ avatar: "/avatar.jpg",
371
+ }}
372
+ onLogout={() => console.log("Logging out")}
373
+ userMenuItems={[
374
+ { label: "Account", onClick: () => {} },
375
+ ]}
376
+ />
377
+ ```
378
+
379
+ #### Navigation Components
380
+
381
+ **NavMain** - Main navigation menu with collapsible groups:
382
+
383
+ ```tsx
384
+ import { NavMain } from 'izen-react-starter';
385
+ import { Home, Settings } from 'lucide-react';
386
+
387
+ <NavMain
388
+ items={[
389
+ { title: "Dashboard", url: "/dashboard", icon: Home },
390
+ {
391
+ title: "Settings",
392
+ icon: Settings,
393
+ items: [
394
+ { title: "Profile", url: "/settings/profile" },
395
+ { title: "Security", url: "/settings/security", badge: "2" },
396
+ ]
397
+ },
398
+ ]}
399
+ />
400
+ ```
401
+
402
+ **NavSecondary** - Secondary navigation links:
403
+
404
+ ```tsx
405
+ import { NavSecondary } from 'izen-react-starter';
406
+ import { HelpCircle, FileText } from 'lucide-react';
407
+
408
+ <NavSecondary
409
+ items={[
410
+ { title: "Help", url: "/help", icon: HelpCircle },
411
+ { title: "Documentation", url: "https://docs.example.com", icon: FileText, target: "_blank" },
412
+ ]}
413
+ />
414
+ ```
415
+
416
+ **NavUser** - User menu dropdown:
417
+
418
+ ```tsx
419
+ import { NavUser } from 'izen-react-starter';
420
+
421
+ <NavUser
422
+ user={{
423
+ name: "John Doe",
424
+ email: "john@example.com",
425
+ avatar: "/avatar.jpg",
426
+ }}
427
+ onLogout={() => console.log("Logout")}
428
+ menuItems={[
429
+ { label: "Profile", onClick: () => {} },
430
+ { label: "Settings", onClick: () => {} },
431
+ ]}
432
+ />
433
+ ```
434
+
435
+ **NavDocuments** - Document list with actions:
436
+
437
+ ```tsx
438
+ import { NavDocuments } from 'izen-react-starter';
439
+ import { FileText } from 'lucide-react';
440
+
441
+ <NavDocuments
442
+ items={[
443
+ { name: "Report Q1", url: "/docs/q1", icon: FileText },
444
+ { name: "Budget 2024", url: "/docs/budget", icon: FileText },
445
+ ]}
446
+ />
447
+ ```
448
+
449
+ #### SiteHeader
450
+
451
+ Header with page title and sidebar trigger:
452
+
453
+ ```tsx
454
+ import { SiteHeader } from 'izen-react-starter';
455
+
456
+ <SiteHeader
457
+ pageTitles={{
458
+ "/dashboard": "Dashboard",
459
+ "/settings": "Settings",
460
+ }}
461
+ defaultTitle="My App"
462
+ />
463
+ ```
464
+
465
+ ### Charts and Overlay
466
+
467
+ ```tsx
468
+ import { ChartAreaInteractive, Overlay } from 'izen-react-starter';
469
+
470
+ // Chart with timeframe controls
471
+ <ChartAreaInteractive />;
472
+
473
+ // Simple overlay toggled by boolean
474
+ <Overlay show={isLoading} />;
475
+ ```
476
+
214
477
  ### Layout Context
215
478
 
216
479
  ```tsx
@@ -243,58 +506,116 @@ function MyComponent() {
243
506
  ### API Service
244
507
 
245
508
  ```tsx
246
- import { apiService } from 'izen-react-starter';
509
+ import React, { useEffect } from 'react';
510
+ import { useApiService } from 'izen-react-starter';
247
511
 
248
- // Configure base URL
249
- apiService.setBaseURL('https://api.example.com');
512
+ // Set the base URL once, typically in your app root
513
+ export function App() {
514
+ const apiService = useApiService();
250
515
 
251
- // Set auth token
252
- apiService.setAuthToken('your-token-here');
516
+ useEffect(() => {
517
+ apiService.setBaseURL('https://api.example.com');
518
+ }, [apiService]);
253
519
 
254
- // Make API calls
255
- async function fetchData() {
256
- try {
257
- const data = await apiService.get('/users');
258
- console.log(data);
259
- } catch (error) {
260
- console.error('Error fetching data:', error);
261
- }
520
+ return <UsersPage />;
262
521
  }
263
522
 
264
- async function postData() {
265
- try {
266
- const response = await apiService.post('/users', {
267
- name: 'John Doe',
268
- email: 'john@example.com'
269
- });
270
- console.log(response);
271
- } catch (error) {
272
- console.error('Error posting data:', error);
273
- }
523
+ // Usage in a page/component
524
+ export function UsersPage() {
525
+ const apiService = useApiService();
526
+
527
+ // Fetch users example
528
+ const fetchUsers = async () => {
529
+ try {
530
+ const users = await apiService.get('/users');
531
+ console.log(users);
532
+ } catch (error) {
533
+ console.error('Error fetching users:', error);
534
+ }
535
+ };
536
+
537
+ // Create user example
538
+ const createUser = async () => {
539
+ try {
540
+ const response = await apiService.post('/users', {
541
+ name: 'John Doe',
542
+ email: 'john@example.com',
543
+ });
544
+ console.log(response);
545
+ } catch (error) {
546
+ console.error('Error creating user:', error);
547
+ }
548
+ };
549
+
550
+ return (
551
+ <div>
552
+ <button onClick={fetchUsers}>Fetch Users</button>
553
+ <button onClick={createUser}>Create User</button>
554
+ </div>
555
+ );
274
556
  }
275
557
  ```
558
+ > **Tip:** You only need to set the baseURL once per app session. After that, all requests will use this base URL automatically. The token will always be injected from AuthProvider for every request.
276
559
 
277
560
  ### Role-Based Access Control (RBAC)
278
561
 
562
+ The RBAC system is now fully configurable! Define your own roles, resources, and rules.
563
+
279
564
  ```tsx
280
565
  import {
566
+ RBACProvider,
281
567
  useAccessControl,
282
568
  AccessControlWrapper,
283
569
  withAccessControl,
284
- Action,
285
- Resource
570
+ CommonActions,
571
+ RBACConfig
286
572
  } from 'izen-react-starter';
287
573
 
288
- // Using the hook
574
+ // 1. Define your RBAC configuration
575
+ const rbacConfig: RBACConfig = {
576
+ roles: ['admin', 'editor', 'viewer'],
577
+ resources: ['posts', 'comments', 'users'],
578
+ rules: {
579
+ admin: {
580
+ [CommonActions.Manage]: { can: 'all' },
581
+ [CommonActions.Create]: { can: 'all' },
582
+ [CommonActions.Read]: { can: 'all' },
583
+ [CommonActions.Update]: { can: 'all' },
584
+ [CommonActions.Delete]: { can: 'all' },
585
+ },
586
+ editor: {
587
+ [CommonActions.Create]: { can: ['posts', 'comments'] },
588
+ [CommonActions.Read]: { can: 'all' },
589
+ [CommonActions.Update]: { can: ['posts', 'comments'] },
590
+ [CommonActions.Delete]: { can: ['comments'] },
591
+ },
592
+ viewer: {
593
+ [CommonActions.Read]: { can: 'all' },
594
+ },
595
+ },
596
+ };
597
+
598
+ // 2. Wrap your app with RBACProvider
599
+ function App() {
600
+ return (
601
+ <AuthProvider>
602
+ <RBACProvider config={rbacConfig}>
603
+ <YourApp />
604
+ </RBACProvider>
605
+ </AuthProvider>
606
+ );
607
+ }
608
+
609
+ // 3. Use access control in your components
289
610
  function AdminPanel() {
290
611
  const { isAllowed } = useAccessControl();
291
612
 
292
613
  return (
293
614
  <div>
294
- {isAllowed(Action.Create, Resource.Users) && (
615
+ {isAllowed('create', 'users') && (
295
616
  <button>Create User</button>
296
617
  )}
297
- {isAllowed(Action.Delete, Resource.Users) && (
618
+ {isAllowed('delete', 'users') && (
298
619
  <button>Delete User</button>
299
620
  )}
300
621
  </div>
@@ -304,8 +625,8 @@ function AdminPanel() {
304
625
  // Using the wrapper component
305
626
  function Dashboard() {
306
627
  return (
307
- <AccessControlWrapper resource={Resource.Reports} action={Action.Read}>
308
- <ReportsPanel />
628
+ <AccessControlWrapper resource="posts" action="create">
629
+ <CreatePostButton />
309
630
  </AccessControlWrapper>
310
631
  );
311
632
  }
@@ -314,12 +635,15 @@ function Dashboard() {
314
635
  const ProtectedComponent = withAccessControl(MyComponent);
315
636
 
316
637
  <ProtectedComponent
317
- accessedResource={Resource.Users}
318
- accessAction={Action.Update}
638
+ accessedResource="users"
639
+ accessAction="update"
319
640
  otherProp="value"
320
641
  />
321
642
  ```
322
643
 
644
+ > 📖 **See [RBAC.md](./RBAC.md) for comprehensive documentation** including custom actions, multiple roles, migration guide, and advanced examples.
645
+ ```
646
+
323
647
  ### Utility Functions
324
648
 
325
649
  ```tsx
@@ -565,8 +889,933 @@ Methods:
565
889
  - `patch<T>(url, data?, config?)`: Promise<T>
566
890
  - `delete<T>(url, config?)`: Promise<T>
567
891
  - `setBaseURL(baseURL)`: void
568
- - `setAuthToken(token)`: void
569
- - `removeAuthToken()`: void
892
+
893
+ > **Note:** The API service now always uses the latest token from the AuthProvider. Use the `useApiService` hook in your components. You no longer need to call `setAuthToken` or `removeAuthToken` manually.
894
+ ### Organized Component Library
895
+
896
+ The library includes a comprehensive set of components organized by functionality:
897
+
898
+ #### Modals
899
+
900
+ Reusable modal dialogs for common user interactions.
901
+
902
+ ```tsx
903
+ import { AlertModal, DeleteDialog, PopupModal } from 'izen-react-starter';
904
+
905
+ // Alert Modal - Generic confirmation dialog
906
+ function MyComponent() {
907
+ const [isOpen, setIsOpen] = useState(false);
908
+
909
+ return (
910
+ <AlertModal
911
+ isOpen={isOpen}
912
+ onClose={() => setIsOpen(false)}
913
+ onConfirm={handleConfirm}
914
+ loading={loading}
915
+ title="Are you sure?"
916
+ description="This action cannot be undone."
917
+ />
918
+ );
919
+ }
920
+
921
+ // Delete Dialog - Specific delete confirmation
922
+ <DeleteDialog
923
+ openDeleteDialog={isDeleteOpen}
924
+ setIsOpenDeleteDialog={setIsDeleteOpen}
925
+ onDelete={handleDelete}
926
+ />
927
+
928
+ // Popup Modal - Complex modal with scroll area and optional "Add New" button
929
+ <PopupModal
930
+ isOpen={modalName === 'create'}
931
+ setIsOpen={setModalName}
932
+ modalName="create"
933
+ title="Add New Item"
934
+ showAddBtn={true}
935
+ isAllowedCreate={true}
936
+ renderModal={(onClose) => (
937
+ <YourForm
938
+ onSubmit={(data) => {
939
+ handleSubmit(data);
940
+ onClose();
941
+ }}
942
+ />
943
+ )}
944
+ extraBtns={() => (
945
+ <Button onClick={handleExtraAction}>Custom Action</Button>
946
+ )}
947
+ />
948
+ ```
949
+
950
+ #### Navigation Components
951
+
952
+ Complete navigation system with responsive design.
953
+
954
+ ```tsx
955
+ import { UserNav, DashboardNav, Sidebar, MobileSidebar, Header } from 'izen-react-starter';
956
+
957
+ // User navigation dropdown with avatar
958
+ <UserNav
959
+ user={{
960
+ name: 'John Doe',
961
+ email: 'john@example.com',
962
+ avatar: 'https://example.com/avatar.jpg'
963
+ }}
964
+ onLogout={handleLogout}
965
+ />
966
+
967
+ // Dashboard navigation list with icons
968
+ const navItems = [
969
+ { href: '/dashboard', label: 'Dashboard', icon: 'home', isShow: true },
970
+ { href: '/settings', label: 'Settings', icon: 'settings', isShow: true },
971
+ { href: '/users', label: 'Users', icon: 'users', isShow: false } // Hidden item
972
+ ];
973
+
974
+ <DashboardNav
975
+ items={navItems}
976
+ setOpen={setIsSidebarOpen} // Optional - for mobile
977
+ />
978
+
979
+ // Desktop sidebar with branding
980
+ <Sidebar
981
+ navItems={navItems}
982
+ logoText="My App"
983
+ logoHref="/"
984
+ />
985
+
986
+ // Mobile sidebar with sheet overlay
987
+ <MobileSidebar
988
+ sidebarOpen={isMobileOpen}
989
+ setSidebarOpen={setIsMobileOpen}
990
+ navItems={navItems}
991
+ logoText="My App"
992
+ logoHref="/"
993
+ />
994
+
995
+ // Complete header with user nav and theme toggle
996
+ <Header
997
+ title="My Application"
998
+ user={{ name: 'John Doe', email: 'john@example.com' }}
999
+ onLogout={handleLogout}
1000
+ setTheme={setTheme}
1001
+ extraContent={
1002
+ <Button>Custom Button</Button>
1003
+ }
1004
+ />
1005
+ ```
1006
+
1007
+ #### Date Picker Components
1008
+
1009
+ Date selection and filtering with URL parameter integration.
1010
+
1011
+ ```tsx
1012
+ import { DatePickerWithRange, DateRangeFilter } from 'izen-react-starter';
1013
+
1014
+ // Date range picker with calendar (3 months view)
1015
+ <DatePickerWithRange
1016
+ startDate={new Date('2024-01-01')}
1017
+ endDate={new Date('2024-01-31')}
1018
+ onChange={(from, to) => {
1019
+ console.log('Selected range:', from, to);
1020
+ // Update your state or make API calls
1021
+ }}
1022
+ className="my-custom-class"
1023
+ />
1024
+
1025
+ // Date range filter with URL params (great for tables/reports)
1026
+ // Automatically syncs with URL query parameters
1027
+ <DateRangeFilter
1028
+ startDateParamName="startDate" // URL param name, defaults to 'filterStartDate'
1029
+ endDateParamName="endDate" // URL param name, defaults to 'filterEndDate'
1030
+ className="mb-4"
1031
+ />
1032
+ // URL will be updated to: ?startDate=2024-01-01&endDate=2024-01-31
1033
+ // Default range: 2 days ago to 3 days ahead
1034
+ ```
1035
+
1036
+ #### Search Components
1037
+
1038
+ Search input with debouncing and URL parameter integration.
1039
+
1040
+ ```tsx
1041
+ import { TableSearchInput } from 'izen-react-starter';
1042
+
1043
+ // Debounced search input (1 second delay)
1044
+ // Automatically updates URL search params
1045
+ <TableSearchInput
1046
+ placeholder="Search users..."
1047
+ />
1048
+ // Updates URL to: ?search=query&page=1
1049
+ // Integrates with react-router-dom's useSearchParams
1050
+ ```
1051
+
1052
+ #### Common UI Components
1053
+
1054
+ Basic UI building blocks used across the application.
1055
+
1056
+ ```tsx
1057
+ import { Heading, PageHead, ThemeToggle, Header } from 'izen-react-starter';
1058
+
1059
+ // Page heading with optional description
1060
+ <Heading
1061
+ title="Dashboard"
1062
+ description="Welcome to your dashboard"
1063
+ className="mb-4"
1064
+ />
1065
+
1066
+ // Document head/title (uses react-helmet-async)
1067
+ <PageHead title="Dashboard | My App" />
1068
+
1069
+ // Theme toggle dropdown (light/dark/pink/system)
1070
+ <ThemeToggle setTheme={(theme) => {
1071
+ // theme: 'light' | 'dark' | 'pink' | 'system'
1072
+ console.log('Theme changed to:', theme);
1073
+ }} />
1074
+ ```
1075
+
1076
+ #### Enhanced Table Components
1077
+
1078
+ Advanced table utilities with server-side features.
1079
+
1080
+ ```tsx
1081
+ import {
1082
+ DataTableSkeleton,
1083
+ PaginationSection,
1084
+ ServerDataTable,
1085
+ TableSearchInput
1086
+ } from 'izen-react-starter';
1087
+
1088
+ // Loading skeleton while data is fetching
1089
+ <DataTableSkeleton
1090
+ columnCount={5}
1091
+ rowCount={10}
1092
+ searchableColumnCount={1}
1093
+ filterableColumnCount={2}
1094
+ showViewOptions={true}
1095
+ />
1096
+
1097
+ // Client-side pagination component
1098
+ <PaginationSection
1099
+ totalPosts={100}
1100
+ postsPerPage={10}
1101
+ currentPage={currentPage}
1102
+ setCurrentPage={setCurrentPage}
1103
+ />
1104
+
1105
+ // Server-side data table with TanStack Table
1106
+ // Automatically syncs with URL params (?page=1&limit=10)
1107
+ const columns: ColumnDef<User>[] = [
1108
+ {
1109
+ accessorKey: 'name',
1110
+ header: 'Name',
1111
+ },
1112
+ {
1113
+ accessorKey: 'email',
1114
+ header: 'Email',
1115
+ },
1116
+ ];
1117
+
1118
+ <ServerDataTable
1119
+ columns={columns}
1120
+ data={users}
1121
+ pageCount={totalPages}
1122
+ pageSizeOptions={[10, 20, 50, 100]}
1123
+ />
1124
+ // Features:
1125
+ // - Automatic URL sync (?page=2&limit=20)
1126
+ // - Server-side pagination
1127
+ // - Row selection
1128
+ // - Sorting support
1129
+ // - Custom page size options
1130
+ ```
1131
+
1132
+ **Complete Table Example with Search and Filters:**
1133
+
1134
+ ```tsx
1135
+ import {
1136
+ ServerDataTable,
1137
+ TableSearchInput,
1138
+ DateRangeFilter,
1139
+ DataTableSkeleton
1140
+ } from 'izen-react-starter';
1141
+
1142
+ function UsersTable() {
1143
+ const { data, isLoading } = useQuery({
1144
+ queryKey: ['users', searchParams.toString()],
1145
+ queryFn: () => fetchUsers(searchParams)
1146
+ });
1147
+
1148
+ if (isLoading) {
1149
+ return <DataTableSkeleton columnCount={4} />;
1150
+ }
1151
+
1152
+ return (
1153
+ <div>
1154
+ {/* Search and filters */}
1155
+ <div className="flex gap-4 mb-4">
1156
+ <TableSearchInput placeholder="Search users..." />
1157
+ <DateRangeFilter />
1158
+ </div>
1159
+
1160
+ {/* Data table */}
1161
+ <ServerDataTable
1162
+ columns={columns}
1163
+ data={data.users}
1164
+ pageCount={data.totalPages}
1165
+ />
1166
+ </div>
1167
+ );
1168
+ }
1169
+ ```
1170
+
1171
+ ### Form Components
1172
+
1173
+ The library provides a complete set of form components that are library-ready without tight coupling to app-specific contexts.
1174
+
1175
+ #### FileUploadButton
1176
+
1177
+ Flexible file upload button with validation:
1178
+
1179
+ ```tsx
1180
+ import { FileUploadButton } from 'izen-react-starter';
1181
+
1182
+ <FileUploadButton
1183
+ title="Upload File"
1184
+ name="document"
1185
+ accept={{
1186
+ 'application/pdf': ['.pdf'],
1187
+ 'image/*': ['.png', '.jpg', '.jpeg']
1188
+ }}
1189
+ maxSize={5 * 1024 * 1024} // 5MB
1190
+ disabled={false}
1191
+ onValidationError={(error) => {
1192
+ console.error('Validation error:', error);
1193
+ // Show toast or alert
1194
+ }}
1195
+ onSuccess={(file) => {
1196
+ console.log('File uploaded:', file);
1197
+ // Handle file upload
1198
+ }}
1199
+ className="my-4"
1200
+ />
1201
+ ```
1202
+
1203
+ #### DatePicker
1204
+
1205
+ Calendar-based date picker with popover:
1206
+
1207
+ ```tsx
1208
+ import { DatePicker } from 'izen-react-starter';
1209
+
1210
+ <DatePicker
1211
+ title="Select Date"
1212
+ name="eventDate"
1213
+ value={selectedDate}
1214
+ onChange={(date) => setSelectedDate(date)}
1215
+ placeholder="Pick a date"
1216
+ error={errors.eventDate}
1217
+ disabled={false}
1218
+ />
1219
+ ```
1220
+
1221
+ #### TimeInput
1222
+
1223
+ Time input with hours, minutes, and optional seconds:
1224
+
1225
+ ```tsx
1226
+ import { TimeInput } from 'izen-react-starter';
1227
+
1228
+ <TimeInput
1229
+ title="Event Time"
1230
+ name="startTime"
1231
+ value="14:30"
1232
+ onChange={(time) => console.log('Time:', time)}
1233
+ showSeconds={false}
1234
+ error={errors.startTime}
1235
+ disabled={false}
1236
+ />
1237
+ ```
1238
+
1239
+ #### TextInput
1240
+
1241
+ Versatile text input supporting multiple types and textarea:
1242
+
1243
+ ```tsx
1244
+ import { TextInput } from 'izen-react-starter';
1245
+ import { Mail, Lock } from 'lucide-react';
1246
+
1247
+ // Text input with icon
1248
+ <TextInput
1249
+ title="Email"
1250
+ name="email"
1251
+ type="email"
1252
+ icon={<Mail />}
1253
+ placeholder="Enter your email"
1254
+ error={errors.email}
1255
+ />
1256
+
1257
+ // Password input
1258
+ <TextInput
1259
+ title="Password"
1260
+ name="password"
1261
+ type="password"
1262
+ icon={<Lock />}
1263
+ placeholder="Enter password"
1264
+ error={errors.password}
1265
+ />
1266
+
1267
+ // Textarea
1268
+ <TextInput
1269
+ title="Description"
1270
+ name="description"
1271
+ type="textarea"
1272
+ rows={5}
1273
+ placeholder="Enter description"
1274
+ error={errors.description}
1275
+ />
1276
+
1277
+ // Number input
1278
+ <TextInput
1279
+ title="Age"
1280
+ name="age"
1281
+ type="number"
1282
+ min={0}
1283
+ max={120}
1284
+ placeholder="Enter age"
1285
+ />
1286
+ ```
1287
+
1288
+ #### CheckboxGroup
1289
+
1290
+ Checkbox input supporting single or multiple items:
1291
+
1292
+ ```tsx
1293
+ import { CheckboxGroup } from 'izen-react-starter';
1294
+
1295
+ // Single checkbox
1296
+ <CheckboxGroup
1297
+ title="Accept Terms"
1298
+ name="terms"
1299
+ items={[
1300
+ { id: 'terms', name: 'terms', displayName: 'I accept the terms and conditions' }
1301
+ ]}
1302
+ error={errors.terms}
1303
+ />
1304
+
1305
+ // Multiple checkboxes
1306
+ <CheckboxGroup
1307
+ title="Select Features"
1308
+ name="features"
1309
+ items={[
1310
+ { id: 'feature1', name: 'features', displayName: 'Email Notifications', checked: true },
1311
+ { id: 'feature2', name: 'features', displayName: 'SMS Alerts', checked: false },
1312
+ { id: 'feature3', name: 'features', displayName: 'Push Notifications', checked: true },
1313
+ ]}
1314
+ onChange={(e) => {
1315
+ const checked = e.target.checked;
1316
+ const id = e.target.id;
1317
+ console.log(`Checkbox ${id} is ${checked ? 'checked' : 'unchecked'}`);
1318
+ }}
1319
+ />
1320
+ ```
1321
+
1322
+ #### RadioGroup
1323
+
1324
+ Radio button group with horizontal or vertical layout:
1325
+
1326
+ ```tsx
1327
+ import { RadioGroup } from 'izen-react-starter';
1328
+
1329
+ // Horizontal layout
1330
+ <RadioGroup
1331
+ title="Select Plan"
1332
+ name="plan"
1333
+ items={[
1334
+ { value: 'free', title: 'Free Plan' },
1335
+ { value: 'pro', title: 'Pro Plan' },
1336
+ { value: 'enterprise', title: 'Enterprise Plan' },
1337
+ ]}
1338
+ onChange={(e) => console.log('Selected:', e.target.value)}
1339
+ error={errors.plan}
1340
+ />
1341
+
1342
+ // Vertical layout
1343
+ <RadioGroup
1344
+ title="Notification Preference"
1345
+ name="notifications"
1346
+ vertical={true}
1347
+ items={[
1348
+ { value: 'all', title: 'All Notifications' },
1349
+ { value: 'important', title: 'Important Only' },
1350
+ { value: 'none', title: 'None' },
1351
+ ]}
1352
+ onChange={(e) => setNotificationPref(e.target.value)}
1353
+ />
1354
+ ```
1355
+
1356
+ **Complete Form Example:**
1357
+
1358
+ ```tsx
1359
+ import {
1360
+ TextInput,
1361
+ DatePicker,
1362
+ TimeInput,
1363
+ CheckboxGroup,
1364
+ RadioGroup,
1365
+ FileUploadButton
1366
+ } from 'izen-react-starter';
1367
+ import { useState } from 'react';
1368
+
1369
+ function EventForm() {
1370
+ const [formData, setFormData] = useState({
1371
+ name: '',
1372
+ date: undefined,
1373
+ time: '09:00',
1374
+ type: 'public',
1375
+ features: [],
1376
+ document: null
1377
+ });
1378
+
1379
+ const [errors, setErrors] = useState({});
1380
+
1381
+ const handleSubmit = (e: React.FormEvent) => {
1382
+ e.preventDefault();
1383
+ console.log('Form data:', formData);
1384
+ };
1385
+
1386
+ return (
1387
+ <form onSubmit={handleSubmit} className="space-y-4">
1388
+ <TextInput
1389
+ title="Event Name"
1390
+ name="name"
1391
+ value={formData.name}
1392
+ onChange={(e) => setFormData({ ...formData, name: e.target.value })}
1393
+ placeholder="Enter event name"
1394
+ error={errors.name}
1395
+ />
1396
+
1397
+ <DatePicker
1398
+ title="Event Date"
1399
+ name="date"
1400
+ value={formData.date}
1401
+ onChange={(date) => setFormData({ ...formData, date })}
1402
+ error={errors.date}
1403
+ />
1404
+
1405
+ <TimeInput
1406
+ title="Start Time"
1407
+ name="time"
1408
+ value={formData.time}
1409
+ onChange={(time) => setFormData({ ...formData, time })}
1410
+ error={errors.time}
1411
+ />
1412
+
1413
+ <RadioGroup
1414
+ title="Event Type"
1415
+ name="type"
1416
+ items={[
1417
+ { value: 'public', title: 'Public Event' },
1418
+ { value: 'private', title: 'Private Event' },
1419
+ ]}
1420
+ onChange={(e) => setFormData({ ...formData, type: e.target.value })}
1421
+ />
1422
+
1423
+ <CheckboxGroup
1424
+ title="Event Features"
1425
+ name="features"
1426
+ items={[
1427
+ { id: 'catering', name: 'features', displayName: 'Catering' },
1428
+ { id: 'parking', name: 'features', displayName: 'Parking' },
1429
+ { id: 'wifi', name: 'features', displayName: 'WiFi' },
1430
+ ]}
1431
+ />
1432
+
1433
+ <FileUploadButton
1434
+ title="Event Document"
1435
+ name="document"
1436
+ accept={{ 'application/pdf': ['.pdf'] }}
1437
+ onSuccess={(file) => setFormData({ ...formData, document: file })}
1438
+ onValidationError={(error) => setErrors({ ...errors, document: error })}
1439
+ />
1440
+
1441
+ <button type="submit" className="btn btn-primary">
1442
+ Create Event
1443
+ </button>
1444
+ </form>
1445
+ );
1446
+ }
1447
+ ```
1448
+
1449
+ #### FormInput
1450
+
1451
+ Enhanced input component with card number formatting and date display:
1452
+
1453
+ ```tsx
1454
+ import { FormInput } from 'izen-react-starter';
1455
+ import { User, Mail, CreditCard } from 'lucide-react';
1456
+
1457
+ // Text input with icon
1458
+ <FormInput
1459
+ title="Full Name"
1460
+ name="fullName"
1461
+ type="text"
1462
+ icon={<User />}
1463
+ placeholder="Enter your name"
1464
+ value={name}
1465
+ onValueChange={(value) => setName(value)}
1466
+ error={errors.name}
1467
+ />
1468
+
1469
+ // Card number with auto-formatting
1470
+ <FormInput
1471
+ title="Card Number"
1472
+ name="cardNumber"
1473
+ type="cardNumber"
1474
+ icon={<CreditCard />}
1475
+ placeholder="0000 0000 0000 0000"
1476
+ value={cardNumber}
1477
+ onValueChange={(value) => setCardNumber(value)}
1478
+ />
1479
+
1480
+ // Date input with formatted label
1481
+ <FormInput
1482
+ title="Birth Date"
1483
+ name="birthDate"
1484
+ type="date"
1485
+ value={birthDate}
1486
+ onChange={(e) => setBirthDate(e.target.value)}
1487
+ showDateLabel={true} // Shows formatted date like "(Jan 15)"
1488
+ />
1489
+
1490
+ // Textarea
1491
+ <FormInput
1492
+ title="Comments"
1493
+ name="comments"
1494
+ type="textarea"
1495
+ rows={4}
1496
+ placeholder="Enter your comments"
1497
+ value={comments}
1498
+ onValueChange={(value) => setComments(value)}
1499
+ />
1500
+ ```
1501
+
1502
+ #### FormSelect
1503
+
1504
+ Standard dropdown select component:
1505
+
1506
+ > **Note:** This component is exported as `FormSelect` to avoid naming conflict with shadcn/ui's `Select` component.
1507
+
1508
+ ```tsx
1509
+ import { FormSelect } from 'izen-react-starter';
1510
+
1511
+ <FormSelect
1512
+ title="Country"
1513
+ name="country"
1514
+ placeholder="Select country"
1515
+ value={country}
1516
+ options={[
1517
+ { value: 'us', label: 'United States' },
1518
+ { value: 'uk', label: 'United Kingdom' },
1519
+ { value: 'ca', label: 'Canada' },
1520
+ ]}
1521
+ onChange={(value) => setCountry(value)}
1522
+ error={errors.country}
1523
+ />
1524
+
1525
+ // With "Please Select" option
1526
+ <FormSelect
1527
+ title="Category"
1528
+ name="category"
1529
+ showOtherOption={true}
1530
+ otherOptionLabel="Please Select"
1531
+ options={categories}
1532
+ onChange={(value) => setCategory(value)}
1533
+ />
1534
+ ```
1535
+
1536
+ #### ComboboxSelect
1537
+
1538
+ Advanced searchable select with Command palette:
1539
+
1540
+ ```tsx
1541
+ import { ComboboxSelect } from 'izen-react-starter';
1542
+ import { Building } from 'lucide-react';
1543
+
1544
+ <ComboboxSelect
1545
+ title="Company"
1546
+ name="company"
1547
+ icon={<Building />}
1548
+ placeholder="Search companies..."
1549
+ value={selectedCompany}
1550
+ options={companies}
1551
+ onChange={(value) => setSelectedCompany(value)}
1552
+ onSearch={(searchTerm) => {
1553
+ // Trigger API call to search companies
1554
+ fetchCompanies(searchTerm);
1555
+ }}
1556
+ showOtherOption={true}
1557
+ otherOptionLabel="No Company"
1558
+ emptyMessage="No companies found."
1559
+ error={errors.company}
1560
+ />
1561
+ ```
1562
+
1563
+ **Features:**
1564
+ - Searchable with Command palette UI
1565
+ - Toggle selection (click again to deselect)
1566
+ - Optional "other" option
1567
+ - Custom empty state message
1568
+ - Search callback for dynamic loading
1569
+
1570
+ #### FormButtons
1571
+
1572
+ Reusable form action buttons:
1573
+
1574
+ ```tsx
1575
+ import { FormButtons } from 'izen-react-starter';
1576
+
1577
+ <FormButtons
1578
+ loading={isSubmitting}
1579
+ showSubmit={true}
1580
+ showCancel={true}
1581
+ showReset={true}
1582
+ submitText="Save"
1583
+ cancelText="Cancel"
1584
+ resetText="Clear"
1585
+ onCancel={() => router.back()}
1586
+ onReset={() => form.reset()}
1587
+ onSubmit={() => form.handleSubmit(onSubmit)()}
1588
+ />
1589
+ ```
1590
+
1591
+ **Props:**
1592
+ - `loading`: Shows "Please wait..." text
1593
+ - `showSubmit/showCancel/showReset`: Toggle button visibility
1594
+ - `submitText/cancelText/resetText`: Custom button labels
1595
+ - `onCancel/onReset/onSubmit`: Click handlers
1596
+
1597
+ #### FormLayout
1598
+
1599
+ Complete form wrapper with error display and action buttons:
1600
+
1601
+ ```tsx
1602
+ import { FormLayout, FormInput, FormSelect } from 'izen-react-starter';
1603
+ import { useState } from 'react';
1604
+
1605
+ function CreateUser() {
1606
+ const [loading, setLoading] = useState(false);
1607
+ const [error, setError] = useState('');
1608
+
1609
+ const handleSubmit = async (data: any) => {
1610
+ setLoading(true);
1611
+ setError('');
1612
+
1613
+ try {
1614
+ await createUser(data);
1615
+ router.push('/users');
1616
+ } catch (err) {
1617
+ setError(err.message);
1618
+ } finally {
1619
+ setLoading(false);
1620
+ }
1621
+ };
1622
+
1623
+ return (
1624
+ <FormLayout
1625
+ onSubmit={handleSubmit}
1626
+ error={error}
1627
+ loading={loading}
1628
+ showSubmit={true}
1629
+ showCancel={true}
1630
+ showReset={true}
1631
+ submitText="Create User"
1632
+ onCancel={() => router.back()}
1633
+ onReset={() => {
1634
+ // Reset form logic
1635
+ }}
1636
+ fullHeight={false}
1637
+ >
1638
+ <FormInput
1639
+ title="Name"
1640
+ name="name"
1641
+ placeholder="Enter name"
1642
+ />
1643
+
1644
+ <FormInput
1645
+ title="Email"
1646
+ name="email"
1647
+ type="email"
1648
+ placeholder="Enter email"
1649
+ />
1650
+
1651
+ <FormSelect
1652
+ title="Role"
1653
+ name="role"
1654
+ options={[
1655
+ { value: 'admin', label: 'Admin' },
1656
+ { value: 'user', label: 'User' },
1657
+ ]}
1658
+ />
1659
+ </FormLayout>
1660
+ );
1661
+ }
1662
+ ```
1663
+
1664
+ **Features:**
1665
+ - Automatic form submission handling
1666
+ - Error display with Alert component
1667
+ - Integrated FormButtons
1668
+ - Card wrapper with styling
1669
+ - Loading state management
1670
+ - Sticky button footer
1671
+
1672
+ **Complete Advanced Form Example:**
1673
+
1674
+ ```tsx
1675
+ import {
1676
+ FormLayout,
1677
+ FormInput,
1678
+ Select,
1679
+ ComboboxSelect,
1680
+ DatePicker,
1681
+ TimeInput,
1682
+ CheckboxGroup,
1683
+ RadioGroup,
1684
+ FileUploadButton
1685
+ } from 'izen-react-starter';
1686
+ import { useState } from 'react';
1687
+
1688
+ function AdvancedForm() {
1689
+ const [loading, setLoading] = useState(false);
1690
+ const [error, setError] = useState('');
1691
+ const [formData, setFormData] = useState({
1692
+ name: '',
1693
+ email: '',
1694
+ cardNumber: '',
1695
+ country: '',
1696
+ company: '',
1697
+ eventDate: undefined,
1698
+ startTime: '09:00',
1699
+ plan: 'free',
1700
+ features: [],
1701
+ document: null
1702
+ });
1703
+
1704
+ const handleSubmit = async (data: any) => {
1705
+ setLoading(true);
1706
+ try {
1707
+ // Process form data
1708
+ await submitForm({ ...formData, ...data });
1709
+ } catch (err) {
1710
+ setError(err.message);
1711
+ } finally {
1712
+ setLoading(false);
1713
+ }
1714
+ };
1715
+
1716
+ return (
1717
+ <FormLayout
1718
+ onSubmit={handleSubmit}
1719
+ error={error}
1720
+ loading={loading}
1721
+ submitText="Submit Registration"
1722
+ fullHeight={false}
1723
+ >
1724
+ <div className="grid grid-cols-2 gap-4">
1725
+ <FormInput
1726
+ title="Full Name"
1727
+ name="name"
1728
+ placeholder="John Doe"
1729
+ value={formData.name}
1730
+ onValueChange={(value) => setFormData({ ...formData, name: value })}
1731
+ />
1732
+
1733
+ <FormInput
1734
+ title="Email"
1735
+ name="email"
1736
+ type="email"
1737
+ placeholder="john@example.com"
1738
+ value={formData.email}
1739
+ onValueChange={(value) => setFormData({ ...formData, email: value })}
1740
+ />
1741
+ </div>
1742
+
1743
+ <FormInput
1744
+ title="Card Number"
1745
+ name="cardNumber"
1746
+ type="cardNumber"
1747
+ placeholder="0000 0000 0000 0000"
1748
+ value={formData.cardNumber}
1749
+ onValueChange={(value) => setFormData({ ...formData, cardNumber: value })}
1750
+ />
1751
+
1752
+ <FormSelect
1753
+ title="Country"
1754
+ name="country"
1755
+ placeholder="Select country"
1756
+ value={formData.country}
1757
+ options={countries}
1758
+ onChange={(value) => setFormData({ ...formData, country: value })}
1759
+ />
1760
+
1761
+ <ComboboxSelect
1762
+ title="Company"
1763
+ name="company"
1764
+ placeholder="Search companies..."
1765
+ value={formData.company}
1766
+ options={companies}
1767
+ onChange={(value) => setFormData({ ...formData, company: value })}
1768
+ onSearch={(term) => searchCompanies(term)}
1769
+ />
1770
+
1771
+ <div className="grid grid-cols-2 gap-4">
1772
+ <DatePicker
1773
+ title="Event Date"
1774
+ name="eventDate"
1775
+ value={formData.eventDate}
1776
+ onChange={(date) => setFormData({ ...formData, eventDate: date })}
1777
+ />
1778
+
1779
+ <TimeInput
1780
+ title="Start Time"
1781
+ name="startTime"
1782
+ value={formData.startTime}
1783
+ onChange={(time) => setFormData({ ...formData, startTime: time })}
1784
+ />
1785
+ </div>
1786
+
1787
+ <RadioGroup
1788
+ title="Subscription Plan"
1789
+ name="plan"
1790
+ items={[
1791
+ { value: 'free', title: 'Free' },
1792
+ { value: 'pro', title: 'Pro' },
1793
+ { value: 'enterprise', title: 'Enterprise' },
1794
+ ]}
1795
+ onChange={(e) => setFormData({ ...formData, plan: e.target.value })}
1796
+ />
1797
+
1798
+ <CheckboxGroup
1799
+ title="Features"
1800
+ name="features"
1801
+ items={[
1802
+ { id: 'email', name: 'features', displayName: 'Email Notifications' },
1803
+ { id: 'sms', name: 'features', displayName: 'SMS Alerts' },
1804
+ { id: 'push', name: 'features', displayName: 'Push Notifications' },
1805
+ ]}
1806
+ />
1807
+
1808
+ <FileUploadButton
1809
+ title="Upload Document"
1810
+ name="document"
1811
+ accept={{ 'application/pdf': ['.pdf'] }}
1812
+ maxSize={5 * 1024 * 1024}
1813
+ onSuccess={(file) => setFormData({ ...formData, document: file })}
1814
+ />
1815
+ </FormLayout>
1816
+ );
1817
+ }
1818
+ ```
570
1819
 
571
1820
  ### RBAC System
572
1821