@exxatdesignux/ui 0.1.0 → 0.2.7

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 (155) hide show
  1. package/bin/cli.mjs +176 -0
  2. package/bin/init.mjs +15 -1
  3. package/bin/sync-extras.mjs +65 -0
  4. package/consumer-extras/README.md +21 -0
  5. package/consumer-extras/cursor-skills/exxat-accessibility/SKILL.md +282 -0
  6. package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +68 -0
  7. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +99 -0
  8. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +713 -0
  9. package/consumer-extras/cursor-skills/exxat-fontawesome-icons/SKILL.md +31 -0
  10. package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +36 -0
  11. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +27 -0
  12. package/consumer-extras/patterns/command-menu-pattern.md +45 -0
  13. package/consumer-extras/patterns/data-views-pattern.md +167 -0
  14. package/package.json +7 -3
  15. package/src/components/ui/sidebar.tsx +7 -2
  16. package/template/.agents/skills/shadcn/SKILL.md +242 -0
  17. package/template/.agents/skills/shadcn/agents/openai.yml +5 -0
  18. package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  19. package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
  20. package/template/.agents/skills/shadcn/cli.md +257 -0
  21. package/template/.agents/skills/shadcn/customization.md +202 -0
  22. package/template/.agents/skills/shadcn/evals/evals.json +47 -0
  23. package/template/.agents/skills/shadcn/mcp.md +94 -0
  24. package/template/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  25. package/template/.agents/skills/shadcn/rules/composition.md +195 -0
  26. package/template/.agents/skills/shadcn/rules/forms.md +192 -0
  27. package/template/.agents/skills/shadcn/rules/icons.md +101 -0
  28. package/template/.agents/skills/shadcn/rules/styling.md +162 -0
  29. package/template/.claude/skills/exxat-ds-skill/SKILL.md +712 -0
  30. package/template/.cursor/rules/exxat-accessibility.mdc +33 -0
  31. package/template/.cursor/rules/exxat-command-menu.mdc +23 -0
  32. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +53 -0
  33. package/template/.cursor/rules/exxat-data-tables.mdc +31 -0
  34. package/template/.cursor/rules/exxat-ds-agents.mdc +26 -0
  35. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +100 -0
  36. package/template/.cursor/rules/exxat-list-page-connected-views.mdc +16 -0
  37. package/template/.cursor/rules/exxat-no-toast.mdc +26 -0
  38. package/template/.cursor/rules/exxat-page-vs-drawer.mdc +22 -0
  39. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +40 -0
  40. package/template/AGENTS.md +52 -11
  41. package/template/app/(app)/dashboard/page.tsx +1 -1
  42. package/template/app/(app)/data-list/[id]/page.tsx +24 -8
  43. package/template/app/(app)/data-list/new/page.tsx +7 -4
  44. package/template/app/(app)/data-list/page.tsx +1 -1
  45. package/template/app/(app)/examples/page.tsx +41 -0
  46. package/template/app/(app)/question-bank/page.tsx +3 -3
  47. package/template/app/globals.css +1 -1
  48. package/template/components/app-sidebar.tsx +52 -35
  49. package/template/components/compliance-table.tsx +79 -0
  50. package/template/components/data-list-client.tsx +36 -25
  51. package/template/components/data-list-table.tsx +797 -10
  52. package/template/components/data-views/finder-panel-view.tsx +405 -0
  53. package/template/components/data-views/folder-grid-view.tsx +86 -0
  54. package/template/components/data-views/index.ts +59 -0
  55. package/template/components/data-views/list-page-split-details-placeholder.tsx +39 -0
  56. package/template/components/data-views/list-page-split-hub-chrome.tsx +60 -0
  57. package/template/components/data-views/list-page-split-hub-tokens.ts +16 -0
  58. package/template/components/data-views/list-page-tree-column-header.tsx +31 -0
  59. package/template/components/data-views/list-page-tree-panel-shell.tsx +91 -0
  60. package/template/components/data-views/list-page-view-frame.tsx +53 -0
  61. package/template/components/data-views/os-folder-glyph.tsx +121 -0
  62. package/template/components/folder-details-shell.tsx +230 -0
  63. package/template/components/hub-tree-panel-view.tsx +672 -0
  64. package/template/components/list-hub-status-badge.tsx +17 -3
  65. package/template/components/page-header.tsx +149 -7
  66. package/template/components/placements-page-header.tsx +14 -8
  67. package/template/components/placements-table-columns.tsx +8 -8
  68. package/template/components/question-bank-client.tsx +157 -39
  69. package/template/components/question-bank-new-folder-sheet.tsx +248 -0
  70. package/template/components/question-bank-os-folder-view.tsx +648 -0
  71. package/template/components/question-bank-page-header.tsx +31 -2
  72. package/template/components/question-bank-panel-activator.tsx +9 -0
  73. package/template/components/question-bank-secondary-nav.tsx +226 -0
  74. package/template/components/question-bank-table.tsx +707 -22
  75. package/template/components/secondary-panel.tsx +41 -107
  76. package/template/components/sites-table.tsx +66 -0
  77. package/template/components/team-client.tsx +7 -0
  78. package/template/components/team-table.tsx +156 -1
  79. package/template/components/templates/list-page.tsx +2 -2
  80. package/template/components/ui/avatar.tsx +1 -1
  81. package/template/components/ui/badge.tsx +1 -1
  82. package/template/components/ui/banner.tsx +1 -1
  83. package/template/components/ui/breadcrumb.tsx +1 -1
  84. package/template/components/ui/button.tsx +1 -1
  85. package/template/components/ui/calendar.tsx +1 -1
  86. package/template/components/ui/card.tsx +1 -1
  87. package/template/components/ui/chart.tsx +1 -1
  88. package/template/components/ui/checkbox.tsx +1 -1
  89. package/template/components/ui/coach-mark.tsx +1 -1
  90. package/template/components/ui/collapsible.tsx +1 -1
  91. package/template/components/ui/command.tsx +1 -1
  92. package/template/components/ui/date-picker-field.tsx +1 -1
  93. package/template/components/ui/dialog.tsx +1 -1
  94. package/template/components/ui/drag-handle-grip.tsx +1 -1
  95. package/template/components/ui/drawer.tsx +1 -1
  96. package/template/components/ui/dropdown-menu.tsx +1 -1
  97. package/template/components/ui/field.tsx +1 -1
  98. package/template/components/ui/form.tsx +1 -1
  99. package/template/components/ui/input-group.tsx +1 -1
  100. package/template/components/ui/input-mask.tsx +1 -1
  101. package/template/components/ui/input.tsx +1 -1
  102. package/template/components/ui/kbd.tsx +1 -1
  103. package/template/components/ui/label.tsx +1 -1
  104. package/template/components/ui/payment-card-fields.tsx +1 -1
  105. package/template/components/ui/popover.tsx +1 -1
  106. package/template/components/ui/radio-group.tsx +1 -1
  107. package/template/components/ui/resizable.tsx +68 -0
  108. package/template/components/ui/select.tsx +1 -1
  109. package/template/components/ui/selection-tile-grid.tsx +1 -1
  110. package/template/components/ui/separator.tsx +1 -1
  111. package/template/components/ui/sheet.tsx +1 -1
  112. package/template/components/ui/sidebar.tsx +1 -1
  113. package/template/components/ui/skeleton.tsx +1 -1
  114. package/template/components/ui/sonner.tsx +1 -1
  115. package/template/components/ui/status-badge.tsx +1 -1
  116. package/template/components/ui/table.tsx +1 -1
  117. package/template/components/ui/tabs.tsx +1 -1
  118. package/template/components/ui/textarea.tsx +1 -1
  119. package/template/components/ui/tip.tsx +1 -1
  120. package/template/components/ui/toggle-group.tsx +1 -1
  121. package/template/components/ui/toggle-switch.tsx +1 -1
  122. package/template/components/ui/toggle.tsx +1 -1
  123. package/template/components/ui/tooltip.tsx +1 -1
  124. package/template/components/ui/view-segmented-control.tsx +1 -1
  125. package/template/docs/data-views-pattern.md +7 -0
  126. package/template/hooks/use-app-theme.ts +1 -1
  127. package/template/hooks/use-coach-mark.ts +1 -1
  128. package/template/hooks/use-location-hash.ts +15 -0
  129. package/template/hooks/use-mobile.ts +1 -1
  130. package/template/hooks/use-mod-key-label.ts +1 -1
  131. package/template/hooks/use-sidebar-reflow-zoom.ts +40 -0
  132. package/template/lib/ask-leo-route-context.ts +25 -57
  133. package/template/lib/coach-mark-registry.ts +13 -13
  134. package/template/lib/command-menu-config.ts +28 -23
  135. package/template/lib/command-menu-search-data.ts +10 -9
  136. package/template/lib/data-list-view-surface.ts +12 -1
  137. package/template/lib/data-list-view.ts +6 -3
  138. package/template/lib/date-filter.ts +1 -1
  139. package/template/lib/mock/dashboard.ts +11 -11
  140. package/template/lib/mock/navigation.tsx +22 -63
  141. package/template/lib/mock/placements-kpi.ts +19 -19
  142. package/template/lib/mock/question-bank-folders.ts +167 -0
  143. package/template/lib/mock/question-bank-header-collaborators.ts +14 -0
  144. package/template/lib/mock/question-bank-inspector.ts +109 -0
  145. package/template/lib/mock/question-bank-kpi.ts +1 -1
  146. package/template/lib/mock/question-bank.ts +80 -0
  147. package/template/lib/question-bank-nav.ts +91 -0
  148. package/template/lib/utils.ts +1 -1
  149. package/template/next.config.mjs +8 -0
  150. package/template/package.json +1 -0
  151. package/template/public/folders/icons8-folder-windows-11.svg +1 -0
  152. package/template/app/(app)/compliance/page.tsx +0 -10
  153. package/template/app/(app)/rotations/page.tsx +0 -15
  154. package/template/app/(app)/sites/all/page.tsx +0 -13
  155. package/template/app/(app)/team/page.tsx +0 -10
@@ -1,14 +1,16 @@
1
1
  "use client"
2
2
 
3
3
  /**
4
- * DataListTable — Placements hub shell on top of the generic DataTable.
4
+ * DataListTable — list hub shell on top of the generic DataTable.
5
5
  *
6
- * Lifecycle tabs swap columns and filtered rows; column definitions and lifecycle copy
7
- * are passed in from the page (`DataListClient` + `placements-table-columns.tsx`).
6
+ * View tabs drive `view` (table | list | board | …). `lifecycleTabId` selects which **demo row
7
+ * segment** (columns + filtered rows) to use — keep in sync with each tab's `filterId`, or pass
8
+ * `"all"` for tabs that only change layout.
8
9
  */
9
10
 
10
11
  import * as React from "react"
11
12
  import dynamic from "next/dynamic"
13
+ import { cn } from "@/lib/utils"
12
14
  import { useRouter } from "next/navigation"
13
15
  import { Button } from "@/components/ui/button"
14
16
  import { Tip } from "@/components/ui/tip"
@@ -30,6 +32,15 @@ import { useCoachMark } from "@/hooks/use-coach-mark"
30
32
  import { DASHBOARD_CUSTOMIZE_COACH_STEPS } from "@/lib/dashboard-customize-coach-mark"
31
33
  import { PlacementsBoardView, type PlacementsBoardColumnMenu } from "@/components/placements-board-view"
32
34
  import { PlacementsListView } from "@/components/placements-list-view"
35
+ import { FolderGridView, ListPageTreePanelShell } from "@/components/data-views"
36
+ import { ListPageSplitHubChrome } from "@/components/data-views/list-page-split-hub-chrome"
37
+ import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header"
38
+ import { FinderPanelView, type FinderGroup } from "@/components/data-views/finder-panel-view"
39
+ import { ListPageSplitDetailsPlaceholder } from "@/components/data-views/list-page-split-details-placeholder"
40
+ import { AvatarInitials } from "@/components/ui/avatar"
41
+ import { getConditionalRowBackground } from "@/lib/conditional-rule-match"
42
+ import { isBoardFieldActive } from "@/lib/placement-board-card-layout"
43
+ import type { BoardCardLifecycleTabId } from "@/lib/placement-board-card-layout"
33
44
  import { TablePropertiesDrawerButton } from "@/components/table-properties"
34
45
  import type { FilterFieldDef } from "@/components/table-properties/types"
35
46
  import type { DataListViewType } from "@/lib/data-list-view"
@@ -148,7 +159,7 @@ function DataListBoardShell({
148
159
  columns={columns}
149
160
  searchable
150
161
  renderFilterOptionValue={renderFilterOptionValue}
151
- searchAriaLabel="Search placements"
162
+ searchAriaLabel="Search rows"
152
163
  toolbarSlot={(s) => (
153
164
  <TablePropertiesDrawerButton
154
165
  state={s}
@@ -248,7 +259,7 @@ function DataListListShell({
248
259
  columns={columns}
249
260
  searchable
250
261
  renderFilterOptionValue={renderFilterOptionValue}
251
- searchAriaLabel="Search placements"
262
+ searchAriaLabel="Search rows"
252
263
  toolbarSlot={s => (
253
264
  <TablePropertiesDrawerButton
254
265
  state={s}
@@ -421,10 +432,10 @@ function DataListDashboardShell({
421
432
  }, [])
422
433
 
423
434
  const dashboardCustomizeCoach = useCoachMark({
424
- flowId: "placements-dashboard-customize",
435
+ flowId: "data-list-dashboard-customize",
425
436
  steps: DASHBOARD_CUSTOMIZE_COACH_STEPS,
426
437
  delay: 700,
427
- dependsOnDismissedFlowId: "placements-views-tour",
438
+ dependsOnDismissedFlowId: "data-list-views-tour",
428
439
  })
429
440
 
430
441
  return (
@@ -436,7 +447,7 @@ function DataListDashboardShell({
436
447
  columns={columns}
437
448
  searchable={displayOptions.showToolbarSearch}
438
449
  renderFilterOptionValue={renderFilterOptionValue}
439
- searchAriaLabel="Search placements"
450
+ searchAriaLabel="Search rows"
440
451
  toolbarSlot={s => (
441
452
  <TablePropertiesDrawerButton
442
453
  state={s}
@@ -498,6 +509,680 @@ function DataListDashboardShell({
498
509
  )
499
510
  }
500
511
 
512
+ // ─── Placement-specific tile for FolderGridView ──────────────────────────────
513
+
514
+ function PlacementFolderTile({
515
+ row,
516
+ tab,
517
+ hiddenColKeys,
518
+ boardColumns,
519
+ conditionalRules,
520
+ onClick,
521
+ }: {
522
+ row: Placement
523
+ tab: BoardCardLifecycleTabId
524
+ hiddenColKeys: Set<string>
525
+ boardColumns: ColumnDef<Placement>[]
526
+ conditionalRules?: ConditionalRule[]
527
+ onClick: () => void
528
+ }) {
529
+ const ruleBg = getConditionalRowBackground(row, conditionalRules)
530
+ const showStudent = isBoardFieldActive("student", tab, hiddenColKeys, boardColumns)
531
+ const showStatus = isBoardFieldActive("status", tab, hiddenColKeys, boardColumns)
532
+ const showSite = isBoardFieldActive("site", tab, hiddenColKeys, boardColumns)
533
+ const showSpec = isBoardFieldActive("specialization", tab, hiddenColKeys, boardColumns)
534
+ const showProgram = isBoardFieldActive("program", tab, hiddenColKeys, boardColumns)
535
+ const name = showStudent ? row.student : `Placement ${row.id}`
536
+
537
+ const statusDotClass: Record<Status, string> = {
538
+ confirmed: "bg-success",
539
+ pending: "bg-warning",
540
+ "under-review": "bg-brand",
541
+ completed: "bg-muted-foreground",
542
+ rejected: "bg-destructive",
543
+ }
544
+
545
+ return (
546
+ <button
547
+ type="button"
548
+ onClick={onClick}
549
+ className={`group relative flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 text-left hover:border-interactive-hover hover:bg-interactive-hover/30 hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-all duration-100 cursor-pointer select-none w-full ${ruleBg}`}
550
+ aria-label={`Open ${name}`}
551
+ >
552
+ <div className="relative">
553
+ <AvatarInitials initials={row.initials} className="size-14 rounded-full text-lg font-semibold" />
554
+ {showStatus && (
555
+ <span className="absolute -bottom-0.5 -right-1 flex size-4 items-center justify-center rounded-full bg-card ring-2 ring-card" aria-hidden="true">
556
+ <span className={`size-2.5 rounded-full ${statusDotClass[row.status]}`} />
557
+ </span>
558
+ )}
559
+ </div>
560
+ <p className="w-full text-center text-[13px] font-medium text-foreground leading-tight line-clamp-2">{name}</p>
561
+ {showStatus && <StatusBadge status={row.status} />}
562
+ {(showSite || showSpec || showProgram) && (
563
+ <div className="flex w-full flex-col gap-0.5">
564
+ {showSite && (
565
+ <p className="truncate text-center text-[11px] text-muted-foreground leading-tight">
566
+ <i className="fa-light fa-building mr-1" aria-hidden="true" />{row.site}
567
+ </p>
568
+ )}
569
+ {showSpec && <p className="truncate text-center text-[11px] text-muted-foreground leading-tight">{row.specialization}</p>}
570
+ {showProgram && <p className="truncate text-center text-[11px] text-muted-foreground leading-tight">{row.program}</p>}
571
+ </div>
572
+ )}
573
+ </button>
574
+ )
575
+ }
576
+
577
+
578
+ // ─── Folder view shell ────────────────────────────────────────────────────────
579
+
580
+ /** Folder / icon-grid view shell */
581
+ function DataListFolderShell({
582
+ state,
583
+ openDrawerRef,
584
+ tableData,
585
+ columns,
586
+ lifecycleTabId,
587
+ view,
588
+ onViewChange,
589
+ pagination,
590
+ onPaginationChange,
591
+ conditionalRules,
592
+ onAddConditionalRule,
593
+ onRemoveConditionalRule,
594
+ onUpdateConditionalRule,
595
+ filterFields,
596
+ lifecycleDrawerLabel,
597
+ fieldDefinitionsForDrawer,
598
+ resolveColumnLabel,
599
+ renderFilterOptionValue,
600
+ displayOptions,
601
+ onDisplayOptionsChange,
602
+ listRows,
603
+ emptyTableCopy,
604
+ }: {
605
+ state: ReturnType<typeof useTableState<Placement>>
606
+ openDrawerRef: React.MutableRefObject<() => void>
607
+ tableData: Placement[]
608
+ columns: ColumnDef<Placement>[]
609
+ lifecycleTabId: PlacementLifecycleTabId
610
+ view: DataListViewType
611
+ onViewChange?: (view: DataListViewType) => void
612
+ pagination: boolean
613
+ onPaginationChange: (v: boolean) => void
614
+ conditionalRules: ConditionalRule[]
615
+ onAddConditionalRule: (rule: Omit<ConditionalRule, "id">) => void
616
+ onRemoveConditionalRule: (id: string) => void
617
+ onUpdateConditionalRule: (id: string, patch: Partial<ConditionalRule>) => void
618
+ filterFields: FilterFieldDef[]
619
+ lifecycleDrawerLabel: string
620
+ fieldDefinitionsForDrawer: { key: string; label: string; sortable?: boolean }[]
621
+ resolveColumnLabel: (key: string) => string
622
+ renderFilterOptionValue: (fieldKey: string, value: string) => React.ReactNode
623
+ displayOptions: DataListDisplayOptions
624
+ onDisplayOptionsChange: (patch: Partial<DataListDisplayOptions>) => void
625
+ listRows: Placement[]
626
+ emptyTableCopy: string
627
+ }) {
628
+ const router = useRouter()
629
+
630
+ React.useEffect(() => {
631
+ openDrawerRef.current = () => state.setSheetOpen(true)
632
+ }, [state.setSheetOpen])
633
+
634
+ const boardColumns = state.displayCols.filter(c => c.key !== "select" && c.key !== "actions")
635
+
636
+ return (
637
+ <>
638
+ <DataTableToolbar
639
+ state={state}
640
+ columns={columns}
641
+ searchable
642
+ renderFilterOptionValue={renderFilterOptionValue}
643
+ searchAriaLabel="Search rows"
644
+ toolbarSlot={s => (
645
+ <TablePropertiesDrawerButton
646
+ state={s}
647
+ totalRows={tableData.length}
648
+ pagination={pagination}
649
+ onPaginationChange={onPaginationChange}
650
+ conditionalRules={conditionalRules}
651
+ onAddConditionalRule={onAddConditionalRule}
652
+ onRemoveConditionalRule={onRemoveConditionalRule}
653
+ onUpdateConditionalRule={onUpdateConditionalRule}
654
+ filterFields={filterFields}
655
+ currentView={view}
656
+ onViewChange={onViewChange}
657
+ lifecycleTabLabel={lifecycleDrawerLabel}
658
+ fieldDefinitions={fieldDefinitionsForDrawer}
659
+ resolveColumnLabel={resolveColumnLabel}
660
+ displayOptions={displayOptions}
661
+ onDisplayOptionsChange={onDisplayOptionsChange}
662
+ renderFilterOptionValue={renderFilterOptionValue}
663
+ />
664
+ )}
665
+ />
666
+ <FolderGridView<Placement>
667
+ rows={listRows}
668
+ getRowId={r => r.id}
669
+ ariaLabel="Demo folder view"
670
+ emptyContent={<p>{emptyTableCopy}</p>}
671
+ renderTile={row => (
672
+ <PlacementFolderTile
673
+ row={row}
674
+ tab={lifecycleTabId as BoardCardLifecycleTabId}
675
+ hiddenColKeys={state.hiddenCols}
676
+ boardColumns={boardColumns}
677
+ conditionalRules={conditionalRules}
678
+ onClick={() => router.push(`/data-list/${row.id}`)}
679
+ />
680
+ )}
681
+ />
682
+ </>
683
+ )
684
+ }
685
+
686
+ // ─── Tree / outline + details shell ───────────────────────────────────────────
687
+
688
+ function DataListTreeShell({
689
+ state,
690
+ openDrawerRef,
691
+ tableData,
692
+ columns,
693
+ lifecycleTabId,
694
+ view,
695
+ onViewChange,
696
+ pagination,
697
+ onPaginationChange,
698
+ conditionalRules,
699
+ onAddConditionalRule,
700
+ onRemoveConditionalRule,
701
+ onUpdateConditionalRule,
702
+ filterFields,
703
+ lifecycleDrawerLabel,
704
+ fieldDefinitionsForDrawer,
705
+ resolveColumnLabel,
706
+ renderFilterOptionValue,
707
+ displayOptions,
708
+ onDisplayOptionsChange,
709
+ listRows,
710
+ emptyTableCopy,
711
+ }: {
712
+ state: ReturnType<typeof useTableState<Placement>>
713
+ openDrawerRef: React.MutableRefObject<() => void>
714
+ tableData: Placement[]
715
+ columns: ColumnDef<Placement>[]
716
+ lifecycleTabId: PlacementLifecycleTabId
717
+ view: DataListViewType
718
+ onViewChange?: (view: DataListViewType) => void
719
+ pagination: boolean
720
+ onPaginationChange: (v: boolean) => void
721
+ conditionalRules: ConditionalRule[]
722
+ onAddConditionalRule: (rule: Omit<ConditionalRule, "id">) => void
723
+ onRemoveConditionalRule: (id: string) => void
724
+ onUpdateConditionalRule: (id: string, patch: Partial<ConditionalRule>) => void
725
+ filterFields: FilterFieldDef[]
726
+ lifecycleDrawerLabel: string
727
+ fieldDefinitionsForDrawer: { key: string; label: string; sortable?: boolean }[]
728
+ resolveColumnLabel: (key: string) => string
729
+ renderFilterOptionValue: (fieldKey: string, value: string) => React.ReactNode
730
+ displayOptions: DataListDisplayOptions
731
+ onDisplayOptionsChange: (patch: Partial<DataListDisplayOptions>) => void
732
+ listRows: Placement[]
733
+ emptyTableCopy: string
734
+ }) {
735
+ const boardColumns = state.displayCols.filter(c => c.key !== "select" && c.key !== "actions")
736
+ const [selectedId, setSelectedId] = React.useState<number | null>(() => listRows[0]?.id ?? null)
737
+
738
+ React.useEffect(() => {
739
+ openDrawerRef.current = () => state.setSheetOpen(true)
740
+ }, [state.setSheetOpen])
741
+
742
+ React.useEffect(() => {
743
+ if (selectedId == null) {
744
+ setSelectedId(listRows[0]?.id ?? null)
745
+ return
746
+ }
747
+ if (!listRows.some(r => r.id === selectedId)) {
748
+ setSelectedId(listRows[0]?.id ?? null)
749
+ }
750
+ }, [listRows, selectedId])
751
+
752
+ const selected = listRows.find(r => r.id === selectedId) ?? null
753
+
754
+ return (
755
+ <>
756
+ <DataTableToolbar
757
+ state={state}
758
+ columns={columns}
759
+ searchable
760
+ renderFilterOptionValue={renderFilterOptionValue}
761
+ searchAriaLabel="Search rows"
762
+ toolbarSlot={s => (
763
+ <TablePropertiesDrawerButton
764
+ state={s}
765
+ totalRows={tableData.length}
766
+ pagination={pagination}
767
+ onPaginationChange={onPaginationChange}
768
+ conditionalRules={conditionalRules}
769
+ onAddConditionalRule={onAddConditionalRule}
770
+ onRemoveConditionalRule={onRemoveConditionalRule}
771
+ onUpdateConditionalRule={onUpdateConditionalRule}
772
+ filterFields={filterFields}
773
+ currentView={view}
774
+ onViewChange={onViewChange}
775
+ lifecycleTabLabel={lifecycleDrawerLabel}
776
+ fieldDefinitions={fieldDefinitionsForDrawer}
777
+ resolveColumnLabel={resolveColumnLabel}
778
+ displayOptions={displayOptions}
779
+ onDisplayOptionsChange={onDisplayOptionsChange}
780
+ renderFilterOptionValue={renderFilterOptionValue}
781
+ />
782
+ )}
783
+ />
784
+ <ListPageTreePanelShell
785
+ resizableGroupId={`data-list-tree-${lifecycleTabId}`}
786
+ ariaLabel="Record outline and details"
787
+ tree={
788
+ <div className="flex min-h-0 flex-1 flex-col">
789
+ <ListPageTreeColumnHeader title="Records" />
790
+ {listRows.length === 0 ? (
791
+ <p className="p-3 text-sm text-muted-foreground">{emptyTableCopy}</p>
792
+ ) : (
793
+ <ul
794
+ role="tree"
795
+ aria-label="Demo records"
796
+ className="min-h-0 flex-1 list-none space-y-0.5 overflow-y-auto py-1"
797
+ >
798
+ {listRows.map(row => {
799
+ const isSel = selectedId === row.id
800
+ return (
801
+ <li key={row.id} role="none" className="py-0.5">
802
+ <button
803
+ type="button"
804
+ role="treeitem"
805
+ aria-selected={isSel}
806
+ tabIndex={isSel ? 0 : -1}
807
+ onClick={() => setSelectedId(row.id)}
808
+ className={cn(
809
+ "flex w-full min-h-8 items-center rounded-md px-3 py-2 text-left text-sm transition-colors duration-75",
810
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
811
+ isSel
812
+ ? "bg-accent font-medium text-accent-foreground"
813
+ : "text-foreground hover:bg-muted/50",
814
+ )}
815
+ >
816
+ <span className="min-w-0 truncate">{row.student}</span>
817
+ </button>
818
+ </li>
819
+ )
820
+ })}
821
+ </ul>
822
+ )}
823
+ </div>
824
+ }
825
+ details={
826
+ selected ? (
827
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-card">
828
+ <ListPageTreeColumnHeader title="Details" />
829
+ <div className="min-h-0 flex-1 overflow-y-auto">
830
+ <PlacementFinderDetail
831
+ row={selected}
832
+ tab={lifecycleTabId as BoardCardLifecycleTabId}
833
+ hiddenColKeys={state.hiddenCols}
834
+ boardColumns={boardColumns}
835
+ />
836
+ </div>
837
+ </div>
838
+ ) : (
839
+ <ListPageSplitDetailsPlaceholder title="Nothing selected" />
840
+ )
841
+ }
842
+ />
843
+ </>
844
+ )
845
+ }
846
+
847
+ // ─── Placement-specific list row for FinderPanelView ─────────────────────────
848
+
849
+ function PlacementFinderListRow({
850
+ row,
851
+ isSelected,
852
+ tab,
853
+ hiddenColKeys,
854
+ boardColumns,
855
+ conditionalRules,
856
+ }: {
857
+ row: Placement
858
+ isSelected: boolean
859
+ tab: BoardCardLifecycleTabId
860
+ hiddenColKeys: Set<string>
861
+ boardColumns: ColumnDef<Placement>[]
862
+ conditionalRules?: ConditionalRule[]
863
+ }) {
864
+ const ruleBg = getConditionalRowBackground(row, conditionalRules)
865
+ const showStudent = isBoardFieldActive("student", tab, hiddenColKeys, boardColumns)
866
+ const showSite = isBoardFieldActive("site", tab, hiddenColKeys, boardColumns)
867
+ const name = showStudent ? row.student : `Placement ${row.id}`
868
+
869
+ return (
870
+ <div
871
+ className={`flex w-full min-w-0 items-center gap-3 transition-colors duration-75 ${
872
+ isSelected
873
+ ? "bg-transparent text-accent-foreground"
874
+ : cn("text-foreground", ruleBg)
875
+ }`}
876
+ >
877
+ <AvatarInitials
878
+ initials={row.initials}
879
+ className={cn(
880
+ "size-8 shrink-0 rounded-full text-[11px] font-semibold",
881
+ isSelected ? "ring-2 ring-accent-foreground/35" : "",
882
+ )}
883
+ />
884
+ <div className="min-w-0 flex-1">
885
+ <p className={cn("truncate text-[13px] font-medium leading-tight", isSelected ? "text-accent-foreground" : "text-foreground")}>
886
+ {name}
887
+ </p>
888
+ {showSite && (
889
+ <p className={cn("mt-0.5 truncate text-[11px] leading-tight", isSelected ? "text-accent-foreground/80" : "text-muted-foreground")}>
890
+ {row.site}
891
+ </p>
892
+ )}
893
+ </div>
894
+ {!isSelected && <StatusBadge status={row.status} />}
895
+ </div>
896
+ )
897
+ }
898
+
899
+ // ─── Placement-specific detail pane for FinderPanelView ──────────────────────
900
+
901
+ function PlacementFinderDetail({
902
+ row,
903
+ tab,
904
+ hiddenColKeys,
905
+ boardColumns,
906
+ }: {
907
+ row: Placement
908
+ tab: BoardCardLifecycleTabId
909
+ hiddenColKeys: Set<string>
910
+ boardColumns: ColumnDef<Placement>[]
911
+ }) {
912
+ const router = useRouter()
913
+ const show = (key: string) => isBoardFieldActive(key, tab, hiddenColKeys, boardColumns)
914
+
915
+ return (
916
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
917
+ {/* Header */}
918
+ <div className="flex shrink-0 items-start gap-4 border-b border-border px-5 py-4">
919
+ <AvatarInitials initials={row.initials} className="size-14 shrink-0 rounded-full text-lg font-semibold" />
920
+ <div className="min-w-0 flex-1">
921
+ <h2 className="text-base font-semibold text-foreground leading-tight">{row.student}</h2>
922
+ {show("program") && <p className="mt-0.5 text-[13px] text-muted-foreground">{row.program}</p>}
923
+ {show("status") && <div className="mt-2"><StatusBadge status={row.status} /></div>}
924
+ </div>
925
+ <Tip side="bottom" label="Open full detail page">
926
+ <Button type="button" variant="outline" size="sm" className="shrink-0"
927
+ onClick={() => router.push(`/data-list/${row.id}`)}
928
+ aria-label={`Open full detail for ${row.student}`}>
929
+ <i className="fa-light fa-arrow-up-right-from-square text-[12px]" aria-hidden="true" />
930
+ Open
931
+ </Button>
932
+ </Tip>
933
+ </div>
934
+
935
+ {/* Fields */}
936
+ <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
937
+ <dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">
938
+ {show("email") && (
939
+ <div className="flex flex-col gap-0.5">
940
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
941
+ <i className="fa-light fa-envelope text-[10px]" aria-hidden="true" /> Email
942
+ </dt>
943
+ <dd className="text-[13px]">
944
+ <a href={`mailto:${row.email}`} className="text-interactive-foreground hover:underline">{row.email}</a>
945
+ </dd>
946
+ </div>
947
+ )}
948
+ {show("site") && (
949
+ <div className="flex flex-col gap-0.5">
950
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
951
+ <i className="fa-light fa-building text-[10px]" aria-hidden="true" /> Site
952
+ </dt>
953
+ <dd className="text-[13px] text-foreground">{row.site}</dd>
954
+ </div>
955
+ )}
956
+ {show("internship") && (
957
+ <div className="flex flex-col gap-0.5">
958
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
959
+ <i className="fa-light fa-briefcase text-[10px]" aria-hidden="true" /> Internship
960
+ </dt>
961
+ <dd className="text-[13px] text-foreground">{row.internship}</dd>
962
+ </div>
963
+ )}
964
+ {show("specialization") && (
965
+ <div className="flex flex-col gap-0.5">
966
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
967
+ <i className="fa-light fa-stethoscope text-[10px]" aria-hidden="true" /> Specialization
968
+ </dt>
969
+ <dd className="text-[13px] text-foreground">{row.specialization}</dd>
970
+ </div>
971
+ )}
972
+ {show("supervisor") && (
973
+ <div className="flex flex-col gap-0.5">
974
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
975
+ <i className="fa-light fa-user-tie text-[10px]" aria-hidden="true" /> Supervisor
976
+ </dt>
977
+ <dd className="text-[13px] text-foreground">{row.supervisor}</dd>
978
+ </div>
979
+ )}
980
+ {show("start") && (
981
+ <div className="flex flex-col gap-0.5">
982
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
983
+ <i className="fa-light fa-calendar text-[10px]" aria-hidden="true" /> Start Date
984
+ </dt>
985
+ <dd className="text-[13px] text-foreground">{row.start}</dd>
986
+ </div>
987
+ )}
988
+ {show("duration") && (
989
+ <div className="flex flex-col gap-0.5">
990
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
991
+ <i className="fa-light fa-clock text-[10px]" aria-hidden="true" /> Duration
992
+ </dt>
993
+ <dd className="text-[13px] text-foreground">{row.duration}</dd>
994
+ </div>
995
+ )}
996
+ {tab === "ongoing" && (
997
+ <div className="flex flex-col gap-0.5 sm:col-span-2">
998
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
999
+ <i className="fa-light fa-chart-line text-[10px]" aria-hidden="true" /> Progress
1000
+ </dt>
1001
+ <dd className="text-[13px] text-foreground flex flex-col gap-1.5">
1002
+ <span>{row.progressWeeksDone} / {row.progressWeeksTotal} weeks</span>
1003
+ <div role="progressbar" aria-valuenow={row.progressWeeksDone} aria-valuemin={0} aria-valuemax={row.progressWeeksTotal}
1004
+ aria-label={`${row.progressWeeksDone} of ${row.progressWeeksTotal} weeks completed`}
1005
+ className="h-1.5 w-full overflow-hidden rounded-full bg-muted">
1006
+ <div className="h-full rounded-full bg-primary transition-all"
1007
+ style={{ width: `${Math.round((row.progressWeeksDone / Math.max(1, row.progressWeeksTotal)) * 100)}%` }} />
1008
+ </div>
1009
+ </dd>
1010
+ </div>
1011
+ )}
1012
+ {row.siteAddress && (
1013
+ <div className="flex flex-col gap-0.5 sm:col-span-2">
1014
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
1015
+ <i className="fa-light fa-location-dot text-[10px]" aria-hidden="true" /> Site Address
1016
+ </dt>
1017
+ <dd className="text-[13px] text-foreground">{row.siteAddress}</dd>
1018
+ </div>
1019
+ )}
1020
+ </dl>
1021
+ </div>
1022
+ </div>
1023
+ )
1024
+ }
1025
+
1026
+ // ─── Status groups for FinderPanelView ───────────────────────────────────────
1027
+
1028
+ const STATUS_GROUPS: Array<{ id: Status | "all"; label: string; accent: string }> = [
1029
+ { id: "all", label: "All", accent: "bg-muted-foreground" },
1030
+ { id: "confirmed", label: "Confirmed", accent: "bg-success" },
1031
+ { id: "pending", label: "Pending", accent: "bg-warning" },
1032
+ { id: "under-review", label: "Under Review", accent: "bg-brand" },
1033
+ { id: "rejected", label: "Rejected", accent: "bg-destructive" },
1034
+ { id: "completed", label: "Completed", accent: "bg-muted-foreground/50" },
1035
+ ]
1036
+
1037
+ function buildStatusGroups(rows: Placement[]): FinderGroup[] {
1038
+ return STATUS_GROUPS.map(sg => ({
1039
+ id: sg.id,
1040
+ label: sg.label,
1041
+ accent: sg.accent,
1042
+ count: sg.id === "all" ? rows.length : rows.filter(r => r.status === sg.id).length,
1043
+ }))
1044
+ }
1045
+
1046
+ // ─── Panel view shell ────────────────────────────────────────────────────────
1047
+
1048
+ /** Finder-style panel view shell with groups, list, and detail pane */
1049
+ function DataListPanelShell({
1050
+ state,
1051
+ openDrawerRef,
1052
+ tableData,
1053
+ columns,
1054
+ lifecycleTabId,
1055
+ view,
1056
+ onViewChange,
1057
+ pagination,
1058
+ onPaginationChange,
1059
+ conditionalRules,
1060
+ onAddConditionalRule,
1061
+ onRemoveConditionalRule,
1062
+ onUpdateConditionalRule,
1063
+ filterFields,
1064
+ lifecycleDrawerLabel,
1065
+ fieldDefinitionsForDrawer,
1066
+ resolveColumnLabel,
1067
+ renderFilterOptionValue,
1068
+ displayOptions,
1069
+ onDisplayOptionsChange,
1070
+ listRows,
1071
+ emptyTableCopy,
1072
+ panelGroupsBuilder,
1073
+ panelRenderListRow,
1074
+ panelRenderDetail,
1075
+ }: {
1076
+ state: ReturnType<typeof useTableState<Placement>>
1077
+ openDrawerRef: React.MutableRefObject<() => void>
1078
+ tableData: Placement[]
1079
+ columns: ColumnDef<Placement>[]
1080
+ lifecycleTabId: PlacementLifecycleTabId
1081
+ view: DataListViewType
1082
+ onViewChange?: (view: DataListViewType) => void
1083
+ pagination: boolean
1084
+ onPaginationChange: (v: boolean) => void
1085
+ conditionalRules: ConditionalRule[]
1086
+ onAddConditionalRule: (rule: Omit<ConditionalRule, "id">) => void
1087
+ onRemoveConditionalRule: (id: string) => void
1088
+ onUpdateConditionalRule: (id: string, patch: Partial<ConditionalRule>) => void
1089
+ filterFields: FilterFieldDef[]
1090
+ lifecycleDrawerLabel: string
1091
+ fieldDefinitionsForDrawer: { key: string; label: string; sortable?: boolean }[]
1092
+ resolveColumnLabel: (key: string) => string
1093
+ renderFilterOptionValue: (fieldKey: string, value: string) => React.ReactNode
1094
+ displayOptions: DataListDisplayOptions
1095
+ onDisplayOptionsChange: (patch: Partial<DataListDisplayOptions>) => void
1096
+ listRows: Placement[]
1097
+ emptyTableCopy: string
1098
+ panelGroupsBuilder?: (rows: Placement[]) => FinderGroup[]
1099
+ panelRenderListRow?: (row: Placement, isSelected: boolean) => React.ReactNode
1100
+ panelRenderDetail?: (row: Placement) => React.ReactNode
1101
+ }) {
1102
+ React.useEffect(() => {
1103
+ openDrawerRef.current = () => state.setSheetOpen(true)
1104
+ }, [state.setSheetOpen])
1105
+
1106
+ const boardColumns = state.displayCols.filter(c => c.key !== "select" && c.key !== "actions")
1107
+ const groups = React.useMemo(
1108
+ () => panelGroupsBuilder ? panelGroupsBuilder(listRows) : buildStatusGroups(listRows),
1109
+ [listRows, panelGroupsBuilder],
1110
+ )
1111
+
1112
+ return (
1113
+ <div className="flex min-h-0 flex-1 flex-col">
1114
+ <DataTableToolbar
1115
+ state={state}
1116
+ columns={columns}
1117
+ searchable
1118
+ renderFilterOptionValue={renderFilterOptionValue}
1119
+ searchAriaLabel="Search rows"
1120
+ toolbarSlot={s => (
1121
+ <TablePropertiesDrawerButton
1122
+ state={s}
1123
+ totalRows={tableData.length}
1124
+ pagination={pagination}
1125
+ onPaginationChange={onPaginationChange}
1126
+ conditionalRules={conditionalRules}
1127
+ onAddConditionalRule={onAddConditionalRule}
1128
+ onRemoveConditionalRule={onRemoveConditionalRule}
1129
+ onUpdateConditionalRule={onUpdateConditionalRule}
1130
+ filterFields={filterFields}
1131
+ currentView={view}
1132
+ onViewChange={onViewChange}
1133
+ lifecycleTabLabel={lifecycleDrawerLabel}
1134
+ fieldDefinitions={fieldDefinitionsForDrawer}
1135
+ resolveColumnLabel={resolveColumnLabel}
1136
+ displayOptions={displayOptions}
1137
+ onDisplayOptionsChange={onDisplayOptionsChange}
1138
+ renderFilterOptionValue={renderFilterOptionValue}
1139
+ />
1140
+ )}
1141
+ />
1142
+ <ListPageSplitHubChrome aria-label={lifecycleDrawerLabel}>
1143
+ <FinderPanelView<Placement>
1144
+ embedded
1145
+ groupsColumnTitle="Status"
1146
+ groups={groups}
1147
+ rows={listRows}
1148
+ getRowId={r => r.id}
1149
+ getRowGroupId={r => r.status}
1150
+ defaultGroupId="all"
1151
+ autoSaveId="finder-panel-view"
1152
+ ariaLabel="Demo panel view"
1153
+ emptyList={<p>{emptyTableCopy}</p>}
1154
+ renderListRow={
1155
+ panelRenderListRow
1156
+ ? panelRenderListRow
1157
+ : (row, isSelected) => (
1158
+ <PlacementFinderListRow
1159
+ row={row}
1160
+ isSelected={isSelected}
1161
+ tab={lifecycleTabId as BoardCardLifecycleTabId}
1162
+ hiddenColKeys={state.hiddenCols}
1163
+ boardColumns={boardColumns}
1164
+ conditionalRules={conditionalRules}
1165
+ />
1166
+ )
1167
+ }
1168
+ renderDetail={
1169
+ panelRenderDetail
1170
+ ? panelRenderDetail
1171
+ : row => (
1172
+ <PlacementFinderDetail
1173
+ row={row}
1174
+ tab={lifecycleTabId as BoardCardLifecycleTabId}
1175
+ hiddenColKeys={state.hiddenCols}
1176
+ boardColumns={boardColumns}
1177
+ />
1178
+ )
1179
+ }
1180
+ />
1181
+ </ListPageSplitHubChrome>
1182
+ </div>
1183
+ )
1184
+ }
1185
+
501
1186
  // ─────────────────────────────────────────────────────────────────────────────
502
1187
  // Props
503
1188
  // ─────────────────────────────────────────────────────────────────────────────
@@ -505,7 +1190,7 @@ function DataListDashboardShell({
505
1190
  export interface DataListTableProps {
506
1191
  view?: DataListViewType
507
1192
  onViewChange?: (view: DataListViewType) => void
508
- /** Drives column set + row filter (ids: all | upcoming | ongoing | completed) */
1193
+ /** Demo row segment: drives filtered rows + column set (`all` | `upcoming` | `ongoing` | `completed`). */
509
1194
  lifecycleTabId?: PlacementLifecycleTabId
510
1195
  /** Shared display options (persist at page level — all view types). */
511
1196
  displayOptions?: DataListDisplayOptions
@@ -516,6 +1201,12 @@ export interface DataListTableProps {
516
1201
  emptyTableCopy: string
517
1202
  /** Table Properties drawer lifecycle label — from the page. */
518
1203
  lifecycleDrawerLabel: string
1204
+ /** Panel view: custom groups builder. If not provided, uses default placement status groups. */
1205
+ panelGroupsBuilder?: (rows: Placement[]) => FinderGroup[]
1206
+ /** Panel view: custom list row renderer. If not provided, uses default placement row rendering. */
1207
+ panelRenderListRow?: (row: Placement, isSelected: boolean) => React.ReactNode
1208
+ /** Panel view: custom detail pane renderer. If not provided, uses default placement detail rendering. */
1209
+ panelRenderDetail?: (row: Placement) => React.ReactNode
519
1210
  }
520
1211
 
521
1212
  /** Imperative handle — open Table Properties (table view only). */
@@ -534,6 +1225,9 @@ export const DataListTable = React.forwardRef<DataListTableHandle, DataListTable
534
1225
  getColumnsForLifecycle,
535
1226
  emptyTableCopy,
536
1227
  lifecycleDrawerLabel,
1228
+ panelGroupsBuilder,
1229
+ panelRenderListRow,
1230
+ panelRenderDetail,
537
1231
  }, ref) {
538
1232
  const displayOptions = React.useMemo(
539
1233
  () => ({ ...DEFAULT_DATA_LIST_DISPLAY_OPTIONS, ...displayOptionsProp }),
@@ -618,7 +1312,7 @@ export const DataListTable = React.forwardRef<DataListTableHandle, DataListTable
618
1312
  const totalPages = Math.max(1, Math.ceil(filteredCount / Math.max(1, paginationPageSize)))
619
1313
  const safePage = Math.min(paginationPage, totalPages)
620
1314
  const paginationOverride =
621
- pagination && view !== "board" && view !== "dashboard"
1315
+ pagination && view !== "board" && view !== "dashboard" && view !== "folder" && view !== "panel" && view !== "tree-panel"
622
1316
  ? { page: safePage, pageSize: paginationPageSize }
623
1317
  : undefined
624
1318
 
@@ -842,6 +1536,99 @@ export const DataListTable = React.forwardRef<DataListTableHandle, DataListTable
842
1536
  )
843
1537
  }
844
1538
 
1539
+ if (view === "folder") {
1540
+ return (
1541
+ <DataListFolderShell
1542
+ key={lifecycleTabId}
1543
+ state={tableState}
1544
+ openDrawerRef={openDrawerRef}
1545
+ tableData={tableData}
1546
+ columns={columns}
1547
+ lifecycleTabId={lifecycleTabId}
1548
+ view={view}
1549
+ onViewChange={onViewChange}
1550
+ pagination={pagination}
1551
+ onPaginationChange={setPagination}
1552
+ conditionalRules={conditionalRules}
1553
+ onAddConditionalRule={addConditionalRule}
1554
+ onRemoveConditionalRule={removeConditionalRule}
1555
+ onUpdateConditionalRule={updateConditionalRule}
1556
+ filterFields={filterFields}
1557
+ lifecycleDrawerLabel={lifecycleDrawerLabel}
1558
+ fieldDefinitionsForDrawer={fieldDefinitionsForDrawer}
1559
+ resolveColumnLabel={resolveColumnLabel}
1560
+ renderFilterOptionValue={renderFilterOptionValue}
1561
+ displayOptions={displayOptions}
1562
+ onDisplayOptionsChange={patchDisplayOptions}
1563
+ listRows={tableState.rows}
1564
+ emptyTableCopy={emptyTableCopy}
1565
+ />
1566
+ )
1567
+ }
1568
+
1569
+ if (view === "tree-panel") {
1570
+ return (
1571
+ <DataListTreeShell
1572
+ key={lifecycleTabId}
1573
+ state={tableState}
1574
+ openDrawerRef={openDrawerRef}
1575
+ tableData={tableData}
1576
+ columns={columns}
1577
+ lifecycleTabId={lifecycleTabId}
1578
+ view={view}
1579
+ onViewChange={onViewChange}
1580
+ pagination={pagination}
1581
+ onPaginationChange={setPagination}
1582
+ conditionalRules={conditionalRules}
1583
+ onAddConditionalRule={addConditionalRule}
1584
+ onRemoveConditionalRule={removeConditionalRule}
1585
+ onUpdateConditionalRule={updateConditionalRule}
1586
+ filterFields={filterFields}
1587
+ lifecycleDrawerLabel={lifecycleDrawerLabel}
1588
+ fieldDefinitionsForDrawer={fieldDefinitionsForDrawer}
1589
+ resolveColumnLabel={resolveColumnLabel}
1590
+ renderFilterOptionValue={renderFilterOptionValue}
1591
+ displayOptions={displayOptions}
1592
+ onDisplayOptionsChange={patchDisplayOptions}
1593
+ listRows={tableState.rows}
1594
+ emptyTableCopy={emptyTableCopy}
1595
+ />
1596
+ )
1597
+ }
1598
+
1599
+ if (view === "panel") {
1600
+ return (
1601
+ <DataListPanelShell
1602
+ key={lifecycleTabId}
1603
+ state={tableState}
1604
+ openDrawerRef={openDrawerRef}
1605
+ tableData={tableData}
1606
+ columns={columns}
1607
+ lifecycleTabId={lifecycleTabId}
1608
+ view={view}
1609
+ onViewChange={onViewChange}
1610
+ pagination={pagination}
1611
+ onPaginationChange={setPagination}
1612
+ conditionalRules={conditionalRules}
1613
+ onAddConditionalRule={addConditionalRule}
1614
+ onRemoveConditionalRule={removeConditionalRule}
1615
+ onUpdateConditionalRule={updateConditionalRule}
1616
+ filterFields={filterFields}
1617
+ lifecycleDrawerLabel={lifecycleDrawerLabel}
1618
+ fieldDefinitionsForDrawer={fieldDefinitionsForDrawer}
1619
+ resolveColumnLabel={resolveColumnLabel}
1620
+ renderFilterOptionValue={renderFilterOptionValue}
1621
+ displayOptions={displayOptions}
1622
+ onDisplayOptionsChange={patchDisplayOptions}
1623
+ listRows={tableState.rows}
1624
+ emptyTableCopy={emptyTableCopy}
1625
+ panelGroupsBuilder={panelGroupsBuilder}
1626
+ panelRenderListRow={panelRenderListRow}
1627
+ panelRenderDetail={panelRenderDetail}
1628
+ />
1629
+ )
1630
+ }
1631
+
845
1632
  if (pagination) {
846
1633
  return (
847
1634
  <React.Fragment key={lifecycleTabId}>