@cohesivesystems/presentation-react-shadcn 0.1.0-alpha.1

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 (262) hide show
  1. package/LICENSE +55 -0
  2. package/dist/document-workspace-surface-slot-renderers.d.ts +58 -0
  3. package/dist/document-workspace-surface-slot-renderers.d.ts.map +1 -0
  4. package/dist/document-workspace-surface-slot-renderers.js +206 -0
  5. package/dist/document-workspace-surface-slot-renderers.js.map +1 -0
  6. package/dist/index.d.ts +53 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +53 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/navigation-shell-frame-renderer-registry.d.ts +8 -0
  11. package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
  12. package/dist/navigation-shell-frame-renderer-registry.js +2 -0
  13. package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
  14. package/dist/navigation-shell-slot-renderer-registry.d.ts +9 -0
  15. package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
  16. package/dist/navigation-shell-slot-renderer-registry.js +2 -0
  17. package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
  18. package/dist/page-host-unavailable-renderer.d.ts +10 -0
  19. package/dist/page-host-unavailable-renderer.d.ts.map +1 -0
  20. package/dist/page-host-unavailable-renderer.js +27 -0
  21. package/dist/page-host-unavailable-renderer.js.map +1 -0
  22. package/dist/presentation-action-group.d.ts +99 -0
  23. package/dist/presentation-action-group.d.ts.map +1 -0
  24. package/dist/presentation-action-group.js +214 -0
  25. package/dist/presentation-action-group.js.map +1 -0
  26. package/dist/presentation-badge-target-interpreter.d.ts +26 -0
  27. package/dist/presentation-badge-target-interpreter.d.ts.map +1 -0
  28. package/dist/presentation-badge-target-interpreter.js +17 -0
  29. package/dist/presentation-badge-target-interpreter.js.map +1 -0
  30. package/dist/presentation-component-groups.d.ts +968 -0
  31. package/dist/presentation-component-groups.d.ts.map +1 -0
  32. package/dist/presentation-component-groups.js +505 -0
  33. package/dist/presentation-component-groups.js.map +1 -0
  34. package/dist/presentation-component-system-diagnostics.d.ts +27 -0
  35. package/dist/presentation-component-system-diagnostics.d.ts.map +1 -0
  36. package/dist/presentation-component-system-diagnostics.js +399 -0
  37. package/dist/presentation-component-system-diagnostics.js.map +1 -0
  38. package/dist/presentation-component-system.d.ts +31 -0
  39. package/dist/presentation-component-system.d.ts.map +1 -0
  40. package/dist/presentation-component-system.js +61 -0
  41. package/dist/presentation-component-system.js.map +1 -0
  42. package/dist/presentation-icon-diagnostics.d.ts +12 -0
  43. package/dist/presentation-icon-diagnostics.d.ts.map +1 -0
  44. package/dist/presentation-icon-diagnostics.js +17 -0
  45. package/dist/presentation-icon-diagnostics.js.map +1 -0
  46. package/dist/presentation-icon-registry.d.ts +28 -0
  47. package/dist/presentation-icon-registry.d.ts.map +1 -0
  48. package/dist/presentation-icon-registry.js +80 -0
  49. package/dist/presentation-icon-registry.js.map +1 -0
  50. package/dist/presentation-projection-trace-panel.d.ts +10 -0
  51. package/dist/presentation-projection-trace-panel.d.ts.map +1 -0
  52. package/dist/presentation-projection-trace-panel.js +48 -0
  53. package/dist/presentation-projection-trace-panel.js.map +1 -0
  54. package/dist/presentation-routed-surface-host.d.ts +19 -0
  55. package/dist/presentation-routed-surface-host.d.ts.map +1 -0
  56. package/dist/presentation-routed-surface-host.js +12 -0
  57. package/dist/presentation-routed-surface-host.js.map +1 -0
  58. package/dist/presentation-shadcn-component-system.d.ts +22 -0
  59. package/dist/presentation-shadcn-component-system.d.ts.map +1 -0
  60. package/dist/presentation-shadcn-component-system.js +2 -0
  61. package/dist/presentation-shadcn-component-system.js.map +1 -0
  62. package/dist/presentation-standard-renderers.d.ts +168 -0
  63. package/dist/presentation-standard-renderers.d.ts.map +1 -0
  64. package/dist/presentation-standard-renderers.js +440 -0
  65. package/dist/presentation-standard-renderers.js.map +1 -0
  66. package/dist/presentation-surface-renderer.d.ts +23 -0
  67. package/dist/presentation-surface-renderer.d.ts.map +1 -0
  68. package/dist/presentation-surface-renderer.js +119 -0
  69. package/dist/presentation-surface-renderer.js.map +1 -0
  70. package/dist/presentation-value-renderers.d.ts +10 -0
  71. package/dist/presentation-value-renderers.d.ts.map +1 -0
  72. package/dist/presentation-value-renderers.js +14 -0
  73. package/dist/presentation-value-renderers.js.map +1 -0
  74. package/dist/process-task-icons.d.ts +49 -0
  75. package/dist/process-task-icons.d.ts.map +1 -0
  76. package/dist/process-task-icons.js +154 -0
  77. package/dist/process-task-icons.js.map +1 -0
  78. package/dist/projected-activity-state.d.ts +17 -0
  79. package/dist/projected-activity-state.d.ts.map +1 -0
  80. package/dist/projected-activity-state.js +22 -0
  81. package/dist/projected-activity-state.js.map +1 -0
  82. package/dist/projected-collection-action-runtime-diagnostics.d.ts +4 -0
  83. package/dist/projected-collection-action-runtime-diagnostics.d.ts.map +1 -0
  84. package/dist/projected-collection-action-runtime-diagnostics.js +9 -0
  85. package/dist/projected-collection-action-runtime-diagnostics.js.map +1 -0
  86. package/dist/projected-collection-view.d.ts +90 -0
  87. package/dist/projected-collection-view.d.ts.map +1 -0
  88. package/dist/projected-collection-view.js +532 -0
  89. package/dist/projected-collection-view.js.map +1 -0
  90. package/dist/projected-document-action-status-notices.d.ts +15 -0
  91. package/dist/projected-document-action-status-notices.d.ts.map +1 -0
  92. package/dist/projected-document-action-status-notices.js +33 -0
  93. package/dist/projected-document-action-status-notices.js.map +1 -0
  94. package/dist/projected-document-badges.d.ts +24 -0
  95. package/dist/projected-document-badges.d.ts.map +1 -0
  96. package/dist/projected-document-badges.js +53 -0
  97. package/dist/projected-document-badges.js.map +1 -0
  98. package/dist/projected-document-editor-surface.d.ts +46 -0
  99. package/dist/projected-document-editor-surface.d.ts.map +1 -0
  100. package/dist/projected-document-editor-surface.js +399 -0
  101. package/dist/projected-document-editor-surface.js.map +1 -0
  102. package/dist/projected-document-workspace-notices.d.ts +24 -0
  103. package/dist/projected-document-workspace-notices.d.ts.map +1 -0
  104. package/dist/projected-document-workspace-notices.js +14 -0
  105. package/dist/projected-document-workspace-notices.js.map +1 -0
  106. package/dist/projected-document-workspace-prompt-child-view-registry.d.ts +25 -0
  107. package/dist/projected-document-workspace-prompt-child-view-registry.d.ts.map +1 -0
  108. package/dist/projected-document-workspace-prompt-child-view-registry.js +41 -0
  109. package/dist/projected-document-workspace-prompt-child-view-registry.js.map +1 -0
  110. package/dist/projected-document-workspace-prompt-child-view-renderers.d.ts +32 -0
  111. package/dist/projected-document-workspace-prompt-child-view-renderers.d.ts.map +1 -0
  112. package/dist/projected-document-workspace-prompt-child-view-renderers.js +77 -0
  113. package/dist/projected-document-workspace-prompt-child-view-renderers.js.map +1 -0
  114. package/dist/projected-document-workspace-shell.d.ts +100 -0
  115. package/dist/projected-document-workspace-shell.d.ts.map +1 -0
  116. package/dist/projected-document-workspace-shell.js +153 -0
  117. package/dist/projected-document-workspace-shell.js.map +1 -0
  118. package/dist/projected-document-workspace.d.ts +39 -0
  119. package/dist/projected-document-workspace.d.ts.map +1 -0
  120. package/dist/projected-document-workspace.js +63 -0
  121. package/dist/projected-document-workspace.js.map +1 -0
  122. package/dist/projected-field-value-rendering.d.ts +94 -0
  123. package/dist/projected-field-value-rendering.d.ts.map +1 -0
  124. package/dist/projected-field-value-rendering.js +377 -0
  125. package/dist/projected-field-value-rendering.js.map +1 -0
  126. package/dist/projected-inline-list.d.ts +20 -0
  127. package/dist/projected-inline-list.d.ts.map +1 -0
  128. package/dist/projected-inline-list.js +149 -0
  129. package/dist/projected-inline-list.js.map +1 -0
  130. package/dist/projected-input-form.d.ts +143 -0
  131. package/dist/projected-input-form.d.ts.map +1 -0
  132. package/dist/projected-input-form.js +612 -0
  133. package/dist/projected-input-form.js.map +1 -0
  134. package/dist/projected-metric-dashboard.d.ts +26 -0
  135. package/dist/projected-metric-dashboard.d.ts.map +1 -0
  136. package/dist/projected-metric-dashboard.js +20 -0
  137. package/dist/projected-metric-dashboard.js.map +1 -0
  138. package/dist/projected-metric-strip.d.ts +20 -0
  139. package/dist/projected-metric-strip.d.ts.map +1 -0
  140. package/dist/projected-metric-strip.js +131 -0
  141. package/dist/projected-metric-strip.js.map +1 -0
  142. package/dist/projected-navigation-shell-view-host.d.ts +34 -0
  143. package/dist/projected-navigation-shell-view-host.d.ts.map +1 -0
  144. package/dist/projected-navigation-shell-view-host.js +75 -0
  145. package/dist/projected-navigation-shell-view-host.js.map +1 -0
  146. package/dist/projected-navigation-shell.d.ts +33 -0
  147. package/dist/projected-navigation-shell.d.ts.map +1 -0
  148. package/dist/projected-navigation-shell.js +112 -0
  149. package/dist/projected-navigation-shell.js.map +1 -0
  150. package/dist/projected-presentation-badges.d.ts +34 -0
  151. package/dist/projected-presentation-badges.d.ts.map +1 -0
  152. package/dist/projected-presentation-badges.js +22 -0
  153. package/dist/projected-presentation-badges.js.map +1 -0
  154. package/dist/projected-presentation-diagnostics.d.ts +10 -0
  155. package/dist/projected-presentation-diagnostics.d.ts.map +1 -0
  156. package/dist/projected-presentation-diagnostics.js +40 -0
  157. package/dist/projected-presentation-diagnostics.js.map +1 -0
  158. package/dist/projected-presentation-flow-layer.d.ts +33 -0
  159. package/dist/projected-presentation-flow-layer.d.ts.map +1 -0
  160. package/dist/projected-presentation-flow-layer.js +71 -0
  161. package/dist/projected-presentation-flow-layer.js.map +1 -0
  162. package/dist/projected-process-task-notices.d.ts +37 -0
  163. package/dist/projected-process-task-notices.d.ts.map +1 -0
  164. package/dist/projected-process-task-notices.js +225 -0
  165. package/dist/projected-process-task-notices.js.map +1 -0
  166. package/dist/projected-prompt-child-view-renderer.d.ts +101 -0
  167. package/dist/projected-prompt-child-view-renderer.d.ts.map +1 -0
  168. package/dist/projected-prompt-child-view-renderer.js +442 -0
  169. package/dist/projected-prompt-child-view-renderer.js.map +1 -0
  170. package/dist/projected-prompt-document-preview.d.ts +39 -0
  171. package/dist/projected-prompt-document-preview.d.ts.map +1 -0
  172. package/dist/projected-prompt-document-preview.js +50 -0
  173. package/dist/projected-prompt-document-preview.js.map +1 -0
  174. package/dist/projected-prompt-view.d.ts +35 -0
  175. package/dist/projected-prompt-view.d.ts.map +1 -0
  176. package/dist/projected-prompt-view.js +292 -0
  177. package/dist/projected-prompt-view.js.map +1 -0
  178. package/dist/projected-query-form.d.ts +56 -0
  179. package/dist/projected-query-form.d.ts.map +1 -0
  180. package/dist/projected-query-form.js +100 -0
  181. package/dist/projected-query-form.js.map +1 -0
  182. package/dist/projected-record-details.d.ts +20 -0
  183. package/dist/projected-record-details.d.ts.map +1 -0
  184. package/dist/projected-record-details.js +60 -0
  185. package/dist/projected-record-details.js.map +1 -0
  186. package/dist/projected-routed-surface-runtime.d.ts +86 -0
  187. package/dist/projected-routed-surface-runtime.d.ts.map +1 -0
  188. package/dist/projected-routed-surface-runtime.js +332 -0
  189. package/dist/projected-routed-surface-runtime.js.map +1 -0
  190. package/dist/projected-tabs-view.d.ts +21 -0
  191. package/dist/projected-tabs-view.d.ts.map +1 -0
  192. package/dist/projected-tabs-view.js +140 -0
  193. package/dist/projected-tabs-view.js.map +1 -0
  194. package/dist/projected-view-chrome.d.ts +15 -0
  195. package/dist/projected-view-chrome.d.ts.map +1 -0
  196. package/dist/projected-view-chrome.js +67 -0
  197. package/dist/projected-view-chrome.js.map +1 -0
  198. package/dist/projected-view-surface.d.ts +34 -0
  199. package/dist/projected-view-surface.d.ts.map +1 -0
  200. package/dist/projected-view-surface.js +40 -0
  201. package/dist/projected-view-surface.js.map +1 -0
  202. package/dist/standard-collection-chrome-slot-renderers.d.ts +26 -0
  203. package/dist/standard-collection-chrome-slot-renderers.d.ts.map +1 -0
  204. package/dist/standard-collection-chrome-slot-renderers.js +250 -0
  205. package/dist/standard-collection-chrome-slot-renderers.js.map +1 -0
  206. package/dist/standard-view-chrome-slot-renderers.d.ts +57 -0
  207. package/dist/standard-view-chrome-slot-renderers.d.ts.map +1 -0
  208. package/dist/standard-view-chrome-slot-renderers.js +614 -0
  209. package/dist/standard-view-chrome-slot-renderers.js.map +1 -0
  210. package/package.json +68 -0
  211. package/src/document-workspace-surface-slot-renderers.tsx +395 -0
  212. package/src/index.ts +53 -0
  213. package/src/navigation-shell-frame-renderer-registry.ts +29 -0
  214. package/src/navigation-shell-slot-renderer-registry.ts +46 -0
  215. package/src/page-host-unavailable-renderer.tsx +79 -0
  216. package/src/presentation-action-group.tsx +475 -0
  217. package/src/presentation-badge-target-interpreter.ts +54 -0
  218. package/src/presentation-component-groups.ts +2505 -0
  219. package/src/presentation-component-system-diagnostics.ts +848 -0
  220. package/src/presentation-component-system.ts +365 -0
  221. package/src/presentation-icon-diagnostics.ts +53 -0
  222. package/src/presentation-icon-registry.tsx +181 -0
  223. package/src/presentation-projection-trace-panel.tsx +140 -0
  224. package/src/presentation-routed-surface-host.tsx +48 -0
  225. package/src/presentation-shadcn-component-system.ts +102 -0
  226. package/src/presentation-standard-renderers.tsx +1290 -0
  227. package/src/presentation-surface-renderer.tsx +263 -0
  228. package/src/presentation-value-renderers.tsx +40 -0
  229. package/src/process-task-icons.tsx +258 -0
  230. package/src/projected-activity-state.tsx +63 -0
  231. package/src/projected-collection-action-runtime-diagnostics.ts +22 -0
  232. package/src/projected-collection-view.tsx +1273 -0
  233. package/src/projected-document-action-status-notices.tsx +66 -0
  234. package/src/projected-document-badges.tsx +134 -0
  235. package/src/projected-document-editor-surface.tsx +808 -0
  236. package/src/projected-document-workspace-notices.tsx +77 -0
  237. package/src/projected-document-workspace-prompt-child-view-registry.ts +99 -0
  238. package/src/projected-document-workspace-prompt-child-view-renderers.tsx +205 -0
  239. package/src/projected-document-workspace-shell.tsx +427 -0
  240. package/src/projected-document-workspace.tsx +216 -0
  241. package/src/projected-field-value-rendering.tsx +704 -0
  242. package/src/projected-inline-list.tsx +434 -0
  243. package/src/projected-input-form.tsx +1218 -0
  244. package/src/projected-metric-dashboard.tsx +106 -0
  245. package/src/projected-metric-strip.tsx +244 -0
  246. package/src/projected-navigation-shell-view-host.tsx +177 -0
  247. package/src/projected-navigation-shell.tsx +219 -0
  248. package/src/projected-presentation-badges.tsx +95 -0
  249. package/src/projected-presentation-diagnostics.tsx +120 -0
  250. package/src/projected-presentation-flow-layer.tsx +186 -0
  251. package/src/projected-process-task-notices.tsx +501 -0
  252. package/src/projected-prompt-child-view-renderer.tsx +698 -0
  253. package/src/projected-prompt-document-preview.tsx +197 -0
  254. package/src/projected-prompt-view.tsx +592 -0
  255. package/src/projected-query-form.tsx +286 -0
  256. package/src/projected-record-details.tsx +133 -0
  257. package/src/projected-routed-surface-runtime.tsx +713 -0
  258. package/src/projected-tabs-view.tsx +308 -0
  259. package/src/projected-view-chrome.tsx +148 -0
  260. package/src/projected-view-surface.tsx +131 -0
  261. package/src/standard-collection-chrome-slot-renderers.tsx +391 -0
  262. package/src/standard-view-chrome-slot-renderers.tsx +1064 -0
@@ -0,0 +1,1273 @@
1
+ import type { ColumnDef } from '@tanstack/react-table'
2
+ import type {
3
+ PresentationCollectionDetailLayoutMode,
4
+ } from './presentation-component-groups'
5
+ import { Fragment, useCallback, useEffect, useMemo, type ReactNode } from 'react'
6
+
7
+ import {
8
+ collectionChromeIconIds,
9
+ createPresentationTestAttributes,
10
+ defaultPresentationComponentSet,
11
+ resolveCollectionChromeIconSubjects,
12
+ } from '@cohesivesystems/presentation-core'
13
+ import {
14
+ findPresentationView,
15
+ isCollectionChromeSlotKind,
16
+ isCollectionChromeSlotPlacement,
17
+ isCollectionChromeSlotPlacementValue,
18
+ projectPresentationActionRuntimeRegistry,
19
+ projectProjectedCollectionActionRuntimeBindings,
20
+ type CollectionChromeSlotDefinition,
21
+ type CollectionChromeSlotPlacement,
22
+ type FieldPresentationDefinition,
23
+ type NavigationRouteParameters,
24
+ type PresentationActionRuntimeBinding,
25
+ type ViewDefinition,
26
+ } from '@cohesivesystems/presentation-core'
27
+ import {
28
+ projectProjectedCollectionActionRuntimeDiagnostics,
29
+ } from './projected-collection-action-runtime-diagnostics'
30
+ import {
31
+ projectPresentationIconDiagnostics,
32
+ } from './presentation-icon-diagnostics'
33
+ import type {
34
+ PresentationShadcnComponentSystem as PresentationComponentSystem,
35
+ } from './presentation-shadcn-component-system'
36
+ import {
37
+ useCollectionSelectionState,
38
+ usePresentationModule,
39
+ usePresentationNavigationRuntime,
40
+ useRegisterPresentationProjectionDiagnostics,
41
+ } from '@cohesivesystems/presentation-react'
42
+ import {
43
+ renderPresentationIcon,
44
+ } from './presentation-icon-registry'
45
+ import {
46
+ renderProjectedFieldValue,
47
+ } from './projected-field-value-rendering'
48
+ import {
49
+ ProjectedRecordDetails,
50
+ type ProjectedRecordFieldRenderContext,
51
+ } from './projected-record-details'
52
+ import {
53
+ createProjectedCollectionRuntime,
54
+ isCollectionSelectionEnabled,
55
+ resolveProjectedCollectionSelectionMode,
56
+ resolveProjectedCollectionSelectionStateId,
57
+ type ProjectedCollectionActionExecutionContext,
58
+ type ProjectedCollectionActionRuntimeRegistry,
59
+ type ProjectedCollectionPaginationInput,
60
+ type ProjectedActionPlacementLike,
61
+ type ProjectedCollectionRuntime,
62
+ type ResolvedProjectedCollectionRowAction,
63
+ type ResolvedProjectedCollectionSelectionAction,
64
+ } from '@cohesivesystems/presentation-core'
65
+ import {
66
+ collectionChromeSlotKinds,
67
+ collectionChromeSlotPlacements,
68
+ } from '@cohesivesystems/presentation-contracts'
69
+
70
+ export type {
71
+ ProjectedActionPlacementLike,
72
+ ProjectedRowActionExecutionContext,
73
+ ProjectedSelectionActionExecutionContext,
74
+ } from '@cohesivesystems/presentation-core'
75
+
76
+ type ProjectedFieldDefinitionLike = FieldPresentationDefinition
77
+ type ProjectedViewDefinitionLike = ViewDefinition
78
+ type ProjectedModuleDefinitionLike = NonNullable<ReturnType<typeof usePresentationModule>>
79
+
80
+ export interface ProjectedFieldRenderContext<TData extends object> {
81
+ readonly field: ProjectedFieldDefinitionLike
82
+ readonly row: TData
83
+ readonly value: unknown
84
+ }
85
+
86
+ export interface ProjectedCollectionViewProps<TData extends object> {
87
+ readonly actionRuntimeBindings?: readonly PresentationActionRuntimeBinding<
88
+ ProjectedCollectionActionExecutionContext<TData>,
89
+ ReactNode
90
+ >[]
91
+ readonly createHref?: (
92
+ routeId: string,
93
+ parameters?: NavigationRouteParameters,
94
+ ) => string | null
95
+ readonly componentSet?: string
96
+ readonly data: readonly TData[]
97
+ readonly emptyMessage: string
98
+ readonly componentSystem: PresentationComponentSystem
99
+ readonly fieldRenderers?: Record<
100
+ string,
101
+ (context: ProjectedFieldRenderContext<TData>) => ReactNode
102
+ >
103
+ readonly footer?:
104
+ | ReactNode
105
+ | ((runtime: ProjectedCollectionRuntime<TData>) => ReactNode)
106
+ readonly detailFieldRenderers?: Record<
107
+ string,
108
+ (context: ProjectedCollectionDetailFieldRenderContext<TData>) => ReactNode
109
+ >
110
+ readonly getRowLabel?: (row: TData) => string
111
+ readonly navigateHref?: (href: string) => void
112
+ readonly pagination?: ProjectedCollectionPaginationInput | null
113
+ readonly renderActionIcon?: (placement: ProjectedActionPlacementLike) => ReactNode
114
+ readonly renderChromeSlot?: (
115
+ context: ProjectedCollectionChromeSlotRenderContext<TData>,
116
+ ) => ReactNode
117
+ readonly viewId: string
118
+ }
119
+
120
+ export interface ProjectedCollectionChromeSlotRenderContext<TData extends object> {
121
+ readonly actionRuntimes?: ProjectedCollectionActionRuntimeRegistry<TData>
122
+ readonly canExecuteAction: (
123
+ context: ProjectedCollectionActionExecutionContext<TData>,
124
+ ) => boolean
125
+ readonly collectionRuntime: ProjectedCollectionRuntime<TData>
126
+ readonly componentSet: string
127
+ readonly componentSystem: PresentationComponentSystem
128
+ readonly createHref?: (
129
+ routeId: string,
130
+ parameters?: NavigationRouteParameters,
131
+ ) => string | null
132
+ readonly executeAction: (
133
+ context: ProjectedCollectionActionExecutionContext<TData>,
134
+ ) => Promise<void> | void
135
+ readonly module?: ProjectedModuleDefinitionLike | null
136
+ readonly navigateHref?: (href: string) => void
137
+ readonly renderActionIcon?: (placement: ProjectedActionPlacementLike) => ReactNode
138
+ readonly renderBodySlot?: (slot: CollectionChromeSlotDefinition) => ReactNode
139
+ readonly renderDetailSlot?: (slot: CollectionChromeSlotDefinition) => ReactNode
140
+ readonly renderQueryFormSlot?: (slot: CollectionChromeSlotDefinition) => ReactNode
141
+ readonly renderRowActionsSlot?: (
142
+ slot: CollectionChromeSlotDefinition,
143
+ row: TData,
144
+ rowLabel: string | null,
145
+ ) => ReactNode
146
+ readonly renderSummarySlot?: (slot: CollectionChromeSlotDefinition) => ReactNode
147
+ readonly row?: TData
148
+ readonly rowLabel?: string | null
149
+ readonly slot: CollectionChromeSlotDefinition
150
+ readonly viewId: string
151
+ }
152
+
153
+ export function ProjectedCollectionView<TData extends object>({
154
+ actionRuntimeBindings,
155
+ componentSet = defaultPresentationComponentSet,
156
+ componentSystem,
157
+ createHref: createHrefOverride,
158
+ data,
159
+ detailFieldRenderers,
160
+ emptyMessage,
161
+ fieldRenderers,
162
+ footer,
163
+ getRowLabel,
164
+ navigateHref: navigateHrefOverride,
165
+ pagination,
166
+ renderActionIcon,
167
+ renderChromeSlot,
168
+ viewId,
169
+ }: ProjectedCollectionViewProps<TData>) {
170
+ const module = usePresentationModule()
171
+ const navigation = usePresentationNavigationRuntime()
172
+ const view = findPresentationView<ProjectedViewDefinitionLike>(module, viewId)
173
+ const createHref = createHrefOverride ?? navigation.createHref
174
+ const navigateHref = navigateHrefOverride ?? navigation.navigateHref
175
+ const selectionMode = resolveProjectedCollectionSelectionMode(view?.Collection)
176
+ const selectionStateId = resolveProjectedCollectionSelectionStateId(view?.Collection)
177
+ const selectionState = useCollectionSelectionState(
178
+ isCollectionSelectionEnabled(selectionMode)
179
+ ? selectionStateId
180
+ : null,
181
+ )
182
+ const collectionRuntime = useMemo(
183
+ () => createProjectedCollectionRuntime({
184
+ createHref,
185
+ data,
186
+ module,
187
+ pagination,
188
+ selectionState,
189
+ view,
190
+ }),
191
+ [createHref, data, module, pagination, selectionState, view],
192
+ )
193
+
194
+ const getTableRowId = collectionRuntime.getRowId
195
+ const isRowSelected = collectionRuntime.selection.isRowSelected
196
+ const activatedRowAction = collectionRuntime.actions.activatedRowAction
197
+ const collectionActionIds = useMemo(
198
+ () =>
199
+ [
200
+ ...collectionRuntime.actions.rowActions.map(({ action }) => action.Id),
201
+ ...collectionRuntime.actions.selectionActions.map(({ action }) => action.Id),
202
+ ],
203
+ [
204
+ collectionRuntime.actions.rowActions,
205
+ collectionRuntime.actions.selectionActions,
206
+ ],
207
+ )
208
+ const actionRuntimes = useMemo(
209
+ () =>
210
+ projectPresentationActionRuntimeRegistry<
211
+ ProjectedCollectionActionExecutionContext<TData>,
212
+ ReactNode
213
+ >({
214
+ actionIds: collectionActionIds,
215
+ module,
216
+ projections: [
217
+ ...(actionRuntimeBindings ?? []),
218
+ ...projectProjectedCollectionActionRuntimeBindings<TData, ReactNode>({
219
+ navigateHref,
220
+ }),
221
+ ],
222
+ }) as ProjectedCollectionActionRuntimeRegistry<TData>,
223
+ [
224
+ actionRuntimeBindings,
225
+ collectionActionIds,
226
+ module,
227
+ navigateHref,
228
+ ],
229
+ )
230
+ const actionRuntimeDiagnostics = useMemo(
231
+ () =>
232
+ [
233
+ ...projectProjectedCollectionActionRuntimeDiagnostics({
234
+ actionRuntimes,
235
+ collectionRuntime,
236
+ module,
237
+ view,
238
+ }),
239
+ ...projectPresentationIconDiagnostics({
240
+ icons: resolveCollectionChromeIconSubjects(collectionRuntime.chrome),
241
+ module,
242
+ source: `projected-collection-chrome-icons.${viewId}`,
243
+ surfaceId: view?.Id ?? viewId,
244
+ surfaceName: view?.Name ?? viewId,
245
+ }),
246
+ ],
247
+ [
248
+ actionRuntimes,
249
+ collectionRuntime,
250
+ module,
251
+ view,
252
+ viewId,
253
+ ],
254
+ )
255
+ useRegisterPresentationProjectionDiagnostics(
256
+ `projected-collection-action-runtime.${viewId}`,
257
+ actionRuntimeDiagnostics,
258
+ )
259
+ const bodySlot = collectionRuntime.chrome.bodySlot
260
+ const resolvedFooter =
261
+ typeof footer === 'function' ? footer(collectionRuntime) : footer
262
+ useEffect(() => {
263
+ collectionRuntime.selection.pruneToVisibleRows()
264
+ }, [collectionRuntime])
265
+ const canExecuteAction = useCallback(
266
+ (context: ProjectedCollectionActionExecutionContext<TData>) =>
267
+ collectionRuntime.actions.canInvokeAction(context, actionRuntimes),
268
+ [actionRuntimes, collectionRuntime],
269
+ )
270
+ const executeAction = useCallback(
271
+ (context: ProjectedCollectionActionExecutionContext<TData>) =>
272
+ collectionRuntime.actions.invokeAction(context, actionRuntimes),
273
+ [actionRuntimes, collectionRuntime],
274
+ )
275
+ const shouldSelectOnRowClick = Boolean(
276
+ bodySlot?.SelectOnRowClick && collectionRuntime.selection.state,
277
+ )
278
+ const handleRowClick = useCallback(
279
+ (row: TData, index: number) => {
280
+ if (shouldSelectOnRowClick) {
281
+ collectionRuntime.selection.activateRow(row, index)
282
+ }
283
+
284
+ if (!bodySlot?.ActivateOnRowClick || !activatedRowAction) {
285
+ return
286
+ }
287
+
288
+ const actionContext = collectionRuntime.actions.createRowActionContext(
289
+ activatedRowAction,
290
+ row,
291
+ )
292
+ if (canExecuteAction(actionContext)) {
293
+ void executeAction(actionContext)
294
+ }
295
+ },
296
+ [
297
+ activatedRowAction,
298
+ bodySlot?.ActivateOnRowClick,
299
+ canExecuteAction,
300
+ collectionRuntime,
301
+ executeAction,
302
+ shouldSelectOnRowClick,
303
+ ],
304
+ )
305
+ const shouldHandleRowClick = Boolean(
306
+ shouldSelectOnRowClick ||
307
+ (bodySlot?.ActivateOnRowClick && activatedRowAction),
308
+ )
309
+ const renderDetailSlot = useCallback(
310
+ (slot: CollectionChromeSlotDefinition) => createCollectionDetailProjection({
311
+ componentSystem,
312
+ componentSet,
313
+ createHref,
314
+ data: collectionRuntime.detail.data,
315
+ emptyMessage: collectionRuntime.detail.emptyMessage,
316
+ fieldRenderers: detailFieldRenderers,
317
+ navigateHref,
318
+ onClose: collectionRuntime.selection.state
319
+ ? () => collectionRuntime.selection.state?.clearSelection()
320
+ : undefined,
321
+ title: collectionRuntime.detail.title,
322
+ viewId: slot.DetailViewId ?? collectionRuntime.detail.viewId,
323
+ }),
324
+ [
325
+ collectionRuntime.detail.data,
326
+ collectionRuntime.detail.emptyMessage,
327
+ collectionRuntime.detail.title,
328
+ collectionRuntime.detail.viewId,
329
+ collectionRuntime.selection.state,
330
+ componentSet,
331
+ componentSystem,
332
+ createHref,
333
+ detailFieldRenderers,
334
+ navigateHref,
335
+ ],
336
+ )
337
+ const renderRowActionsSlot = useCallback(
338
+ (slot: CollectionChromeSlotDefinition, row: TData, rowLabel: string | null) => (
339
+ <ProjectedCollectionRowActions
340
+ canExecuteAction={canExecuteAction}
341
+ collectionRuntime={collectionRuntime}
342
+ componentSet={componentSet}
343
+ componentSystem={componentSystem}
344
+ executeAction={executeAction}
345
+ module={module}
346
+ renderActionIcon={renderActionIcon}
347
+ row={row}
348
+ rowLabel={rowLabel}
349
+ slot={slot}
350
+ viewId={viewId}
351
+ />
352
+ ),
353
+ [
354
+ canExecuteAction,
355
+ collectionRuntime,
356
+ componentSet,
357
+ componentSystem,
358
+ executeAction,
359
+ module,
360
+ renderActionIcon,
361
+ viewId,
362
+ ],
363
+ )
364
+ const columns = useMemo(
365
+ () =>
366
+ view
367
+ ? createProjectedColumns({
368
+ actionRuntimes,
369
+ canExecuteAction,
370
+ collectionRuntime,
371
+ componentSet,
372
+ componentSystem,
373
+ createHref,
374
+ executeAction,
375
+ fieldRenderers,
376
+ getRowLabel,
377
+ module,
378
+ navigateHref,
379
+ renderChromeSlot,
380
+ renderActionIcon,
381
+ renderRowActionsSlot,
382
+ viewId,
383
+ })
384
+ : [],
385
+ [
386
+ actionRuntimes,
387
+ canExecuteAction,
388
+ componentSystem,
389
+ componentSet,
390
+ createHref,
391
+ executeAction,
392
+ fieldRenderers,
393
+ getRowLabel,
394
+ collectionRuntime,
395
+ module,
396
+ navigateHref,
397
+ renderChromeSlot,
398
+ renderActionIcon,
399
+ renderRowActionsSlot,
400
+ view,
401
+ viewId,
402
+ ],
403
+ )
404
+ const chromeSlotContext = {
405
+ actionRuntimes,
406
+ canExecuteAction,
407
+ collectionRuntime,
408
+ componentSet,
409
+ componentSystem,
410
+ createHref,
411
+ executeAction,
412
+ module,
413
+ navigateHref,
414
+ renderActionIcon,
415
+ renderDetailSlot,
416
+ renderRowActionsSlot,
417
+ viewId,
418
+ } satisfies Omit<ProjectedCollectionChromeSlotRenderContext<TData>, 'slot'>
419
+ const leadingChrome = renderCollectionChromeSlots({
420
+ context: chromeSlotContext,
421
+ placements: [
422
+ collectionChromeSlotPlacements.header,
423
+ collectionChromeSlotPlacements.toolbar,
424
+ collectionChromeSlotPlacements.above,
425
+ ],
426
+ renderChromeSlot,
427
+ })
428
+ const footerChrome = renderCollectionChromeSlots({
429
+ context: chromeSlotContext,
430
+ placements: [collectionChromeSlotPlacements.footer],
431
+ renderChromeSlot,
432
+ })
433
+ const renderBodySlot = () => (
434
+ <ProjectedCollectionBody
435
+ columns={columns}
436
+ componentSystem={componentSystem}
437
+ data={data}
438
+ emptyMessage={emptyMessage}
439
+ footer={footerChrome.length > 0 ? footerChrome : resolvedFooter}
440
+ getRowId={getTableRowId}
441
+ isRowSelected={isRowSelected}
442
+ onRowClick={shouldHandleRowClick ? handleRowClick : undefined}
443
+ viewId={viewId}
444
+ />
445
+ )
446
+ const bodyChromeSlotContext = {
447
+ ...chromeSlotContext,
448
+ renderBodySlot,
449
+ } satisfies Omit<ProjectedCollectionChromeSlotRenderContext<TData>, 'slot'>
450
+ const bodyChrome = renderCollectionChromeSlots({
451
+ context: bodyChromeSlotContext,
452
+ includeSlot: (slot) => isCollectionChromeSlotKind(
453
+ slot,
454
+ collectionChromeSlotKinds.body,
455
+ ),
456
+ placements: [collectionChromeSlotPlacements.inline],
457
+ renderChromeSlot,
458
+ renderFallbackSlot: () => renderBodySlot(),
459
+ })
460
+ const collectionBody = bodyChrome.length > 0 ? bodyChrome : (
461
+ <ProjectionStatusBlock
462
+ label={
463
+ `Presentation view '${view?.Name ?? viewId}' does not declare a collection Body chrome slot.`
464
+ }
465
+ />
466
+ )
467
+ const collectionDetailSlot = collectionRuntime.chrome.detailSlot
468
+ const detailChrome = renderCollectionChromeSlots({
469
+ context: chromeSlotContext,
470
+ includeSlot: (slot) => isCollectionChromeSlotKind(
471
+ slot,
472
+ collectionChromeSlotKinds.detail,
473
+ ),
474
+ placements: [
475
+ collectionChromeSlotPlacements.inline,
476
+ collectionChromeSlotPlacements.sidePanel,
477
+ collectionChromeSlotPlacements.drawer,
478
+ ],
479
+ renderChromeSlot,
480
+ renderFallbackSlot: ({ slot }) => renderDetailSlot(slot),
481
+ })
482
+ const collectionDetail = detailChrome.length > 0 ? detailChrome : null
483
+ const table = (
484
+ <div className="grid w-full min-w-0 gap-4">
485
+ {leadingChrome}
486
+ {collectionBody}
487
+ </div>
488
+ )
489
+
490
+ if (!module) {
491
+ return <ProjectionStatusBlock label="Presentation module is not available." />
492
+ }
493
+
494
+ if (!view) {
495
+ return <ProjectionStatusBlock label={`Presentation view '${viewId}' is not available.`} />
496
+ }
497
+
498
+ if (!collectionRuntime.chrome.bodySlot) {
499
+ return (
500
+ <ProjectionStatusBlock
501
+ label={`Presentation view '${view.Name}' does not declare a collection Body chrome slot.`}
502
+ />
503
+ )
504
+ }
505
+
506
+ if (columns.length === 0) {
507
+ return <ProjectionStatusBlock label={`Presentation view '${view.Name}' has no projected fields.`} />
508
+ }
509
+
510
+ return (
511
+ <ProjectedCollectionDetailLayout
512
+ componentSystem={componentSystem}
513
+ detail={collectionDetail}
514
+ placement={collectionDetailSlot?.Placement ?? collectionChromeSlotPlacements.none}
515
+ table={table}
516
+ />
517
+ )
518
+ }
519
+
520
+ export type ProjectedCollectionDetailFieldRenderContext<TData extends object> =
521
+ ProjectedRecordFieldRenderContext<TData>
522
+
523
+ export function ProjectedCollectionBody<TData extends object>({
524
+ columns,
525
+ componentSystem,
526
+ data,
527
+ emptyMessage,
528
+ footer,
529
+ getRowId,
530
+ isRowSelected,
531
+ onRowClick,
532
+ viewId,
533
+ }: {
534
+ readonly columns: readonly ColumnDef<TData>[]
535
+ readonly componentSystem: PresentationComponentSystem
536
+ readonly data: readonly TData[]
537
+ readonly emptyMessage: string
538
+ readonly footer?: ReactNode
539
+ readonly getRowId?: (row: TData, index: number) => string
540
+ readonly isRowSelected?: (row: TData, index: number) => boolean
541
+ readonly onRowClick?: (row: TData, index: number) => void
542
+ readonly viewId: string
543
+ }) {
544
+ return componentSystem.collections.DataTable({
545
+ columns,
546
+ data,
547
+ emptyMessage,
548
+ footer,
549
+ getRowId,
550
+ isRowSelected,
551
+ onRowClick,
552
+ viewId,
553
+ })
554
+ }
555
+
556
+ function ProjectedCollectionDetail<TData extends object>({
557
+ componentSet,
558
+ componentSystem,
559
+ createHref,
560
+ data,
561
+ emptyMessage,
562
+ fieldRenderers,
563
+ navigateHref,
564
+ onClose,
565
+ title,
566
+ viewId,
567
+ }: {
568
+ readonly componentSet: string
569
+ readonly componentSystem: PresentationComponentSystem
570
+ readonly createHref?: (
571
+ routeId: string,
572
+ parameters?: NavigationRouteParameters,
573
+ ) => string | null
574
+ readonly data: TData | null
575
+ readonly emptyMessage?: string | null
576
+ readonly fieldRenderers?: Record<
577
+ string,
578
+ (context: ProjectedCollectionDetailFieldRenderContext<TData>) => ReactNode
579
+ >
580
+ readonly navigateHref?: (href: string) => void
581
+ readonly onClose?: () => void
582
+ readonly title?: string | null
583
+ readonly viewId: string
584
+ }) {
585
+ const module = usePresentationModule()
586
+ const detailView = findPresentationView<ProjectedViewDefinitionLike>(module, viewId)
587
+ const ActionButton = componentSystem.actions.ActionButton
588
+
589
+ return (
590
+ <section className="grid gap-2">
591
+ <div className="flex items-center justify-between gap-2">
592
+ {detailView || title ? (
593
+ <h2 className="text-sm font-semibold text-slate-800">{title ?? detailView?.Name}</h2>
594
+ ) : <span />}
595
+ {onClose ? (
596
+ <ActionButton
597
+ aria-label="Close details"
598
+ onClick={onClose}
599
+ size="icon-sm"
600
+ type="button"
601
+ variant="ghost"
602
+ >
603
+ {renderPresentationIcon({
604
+ className: 'size-4',
605
+ componentSet,
606
+ icon: collectionChromeIconIds.detailClose,
607
+ module,
608
+ })}
609
+ </ActionButton>
610
+ ) : null}
611
+ </div>
612
+ {data ? (
613
+ <ProjectedRecordDetails
614
+ componentSystem={componentSystem}
615
+ createHref={createHref}
616
+ data={data}
617
+ fieldRenderers={fieldRenderers}
618
+ navigateHref={navigateHref}
619
+ viewId={viewId}
620
+ />
621
+ ) : (
622
+ <ProjectionStatusBlock label={emptyMessage ?? 'Select a row to inspect it.'} />
623
+ )}
624
+ </section>
625
+ )
626
+ }
627
+
628
+ function ProjectedCollectionDetailLayout({
629
+ componentSystem,
630
+ detail,
631
+ placement,
632
+ table,
633
+ }: {
634
+ readonly componentSystem: PresentationComponentSystem
635
+ readonly detail: ReactNode
636
+ readonly placement: CollectionChromeSlotPlacement | null | undefined
637
+ readonly table: ReactNode
638
+ }) {
639
+ const CollectionDetailLayout = componentSystem.collections.CollectionDetailLayout
640
+ return (
641
+ <CollectionDetailLayout
642
+ detail={detail}
643
+ mode={resolveCollectionDetailLayoutMode(placement)}
644
+ table={table}
645
+ />
646
+ )
647
+ }
648
+
649
+ function resolveCollectionDetailLayoutMode(
650
+ placement: CollectionChromeSlotPlacement | null | undefined,
651
+ ): PresentationCollectionDetailLayoutMode {
652
+ if (
653
+ isCollectionChromeSlotPlacementValue(
654
+ placement,
655
+ collectionChromeSlotPlacements.none,
656
+ )
657
+ ) {
658
+ return 'none'
659
+ }
660
+
661
+ if (
662
+ isCollectionChromeSlotPlacementValue(
663
+ placement,
664
+ collectionChromeSlotPlacements.sidePanel,
665
+ )
666
+ ) {
667
+ return 'side-panel'
668
+ }
669
+
670
+ if (
671
+ isCollectionChromeSlotPlacementValue(
672
+ placement,
673
+ collectionChromeSlotPlacements.drawer,
674
+ )
675
+ ) {
676
+ return 'drawer'
677
+ }
678
+
679
+ return 'stack'
680
+ }
681
+
682
+ function createCollectionDetailProjection<TData extends object>({
683
+ componentSet,
684
+ componentSystem,
685
+ createHref,
686
+ data,
687
+ emptyMessage,
688
+ fieldRenderers,
689
+ navigateHref,
690
+ onClose,
691
+ title,
692
+ viewId,
693
+ }: {
694
+ readonly componentSet: string
695
+ readonly componentSystem: PresentationComponentSystem
696
+ readonly createHref?: (
697
+ routeId: string,
698
+ parameters?: NavigationRouteParameters,
699
+ ) => string | null
700
+ readonly data: TData | null
701
+ readonly emptyMessage?: string | null
702
+ readonly fieldRenderers?: Record<
703
+ string,
704
+ (context: ProjectedCollectionDetailFieldRenderContext<TData>) => ReactNode
705
+ >
706
+ readonly navigateHref?: (href: string) => void
707
+ readonly onClose?: () => void
708
+ readonly title?: string | null
709
+ readonly viewId: string | null
710
+ }) {
711
+ if (!viewId || (!data && !emptyMessage)) {
712
+ return null
713
+ }
714
+
715
+ return (
716
+ <ProjectedCollectionDetail
717
+ componentSet={componentSet}
718
+ componentSystem={componentSystem}
719
+ createHref={createHref}
720
+ data={data}
721
+ emptyMessage={emptyMessage}
722
+ fieldRenderers={fieldRenderers}
723
+ navigateHref={navigateHref}
724
+ onClose={data ? onClose : undefined}
725
+ title={title}
726
+ viewId={viewId}
727
+ />
728
+ )
729
+ }
730
+
731
+ function renderCollectionChromeSlots<TData extends object>({
732
+ context,
733
+ includeSlot,
734
+ placements,
735
+ renderFallbackSlot,
736
+ renderChromeSlot,
737
+ }: {
738
+ readonly context: Omit<ProjectedCollectionChromeSlotRenderContext<TData>, 'slot'>
739
+ readonly includeSlot?: (slot: CollectionChromeSlotDefinition) => boolean
740
+ readonly placements: readonly number[]
741
+ readonly renderFallbackSlot?: (
742
+ context: ProjectedCollectionChromeSlotRenderContext<TData>,
743
+ ) => ReactNode
744
+ readonly renderChromeSlot?: (
745
+ context: ProjectedCollectionChromeSlotRenderContext<TData>,
746
+ ) => ReactNode
747
+ }) {
748
+ if (!renderChromeSlot && !renderFallbackSlot) {
749
+ return []
750
+ }
751
+
752
+ return context.collectionRuntime.chrome.slots.flatMap((slot) => {
753
+ if (includeSlot && !includeSlot(slot)) {
754
+ return []
755
+ }
756
+
757
+ if (!placements.some((placement) => isCollectionChromeSlotPlacement(slot, placement))) {
758
+ return []
759
+ }
760
+
761
+ const slotContext = { ...context, slot }
762
+ const rendered = renderChromeSlot
763
+ ? renderChromeSlot(slotContext)
764
+ : renderFallbackSlot?.(slotContext)
765
+ if (rendered === null || rendered === undefined || rendered === false) {
766
+ return []
767
+ }
768
+
769
+ return [
770
+ <Fragment key={slot.Id}>{rendered}</Fragment>,
771
+ ]
772
+ })
773
+ }
774
+
775
+ interface CreateProjectedColumnsOptions<TData extends object> {
776
+ readonly actionRuntimes?: ProjectedCollectionActionRuntimeRegistry<TData>
777
+ readonly canExecuteAction: (
778
+ context: ProjectedCollectionActionExecutionContext<TData>,
779
+ ) => boolean
780
+ readonly collectionRuntime: ProjectedCollectionRuntime<TData>
781
+ readonly componentSet: string
782
+ readonly componentSystem: PresentationComponentSystem
783
+ readonly createHref?: (
784
+ routeId: string,
785
+ parameters?: NavigationRouteParameters,
786
+ ) => string | null
787
+ readonly executeAction: (
788
+ context: ProjectedCollectionActionExecutionContext<TData>,
789
+ ) => Promise<void> | void
790
+ readonly fieldRenderers?: Record<
791
+ string,
792
+ (context: ProjectedFieldRenderContext<TData>) => ReactNode
793
+ >
794
+ readonly getRowLabel?: (row: TData) => string
795
+ readonly module: ProjectedModuleDefinitionLike | null
796
+ readonly navigateHref?: (href: string) => void
797
+ readonly renderActionIcon?: (placement: ProjectedActionPlacementLike) => ReactNode
798
+ readonly renderChromeSlot?: (
799
+ context: ProjectedCollectionChromeSlotRenderContext<TData>,
800
+ ) => ReactNode
801
+ readonly renderRowActionsSlot?: (
802
+ slot: CollectionChromeSlotDefinition,
803
+ row: TData,
804
+ rowLabel: string | null,
805
+ ) => ReactNode
806
+ readonly viewId: string
807
+ }
808
+
809
+ function createProjectedColumns<TData extends object>({
810
+ actionRuntimes,
811
+ canExecuteAction,
812
+ collectionRuntime,
813
+ componentSet,
814
+ componentSystem,
815
+ createHref,
816
+ executeAction,
817
+ fieldRenderers,
818
+ getRowLabel,
819
+ module,
820
+ navigateHref,
821
+ renderActionIcon,
822
+ renderChromeSlot,
823
+ renderRowActionsSlot,
824
+ viewId,
825
+ }: CreateProjectedColumnsOptions<TData>): readonly ColumnDef<TData>[] {
826
+ if (!module) {
827
+ return []
828
+ }
829
+
830
+ const fieldColumns = collectionRuntime.columns.flatMap<ColumnDef<TData>>((columnRuntime) => {
831
+ return [
832
+ {
833
+ cell: ({ row }) => {
834
+ const field = columnRuntime.field
835
+ const value = columnRuntime.readValue(row.original)
836
+ const renderer =
837
+ fieldRenderers?.[field.Id] ??
838
+ fieldRenderers?.[field.Field]
839
+ return renderer
840
+ ? renderer({ field, row: row.original, value })
841
+ : renderProjectedFieldValue({
842
+ componentSystem,
843
+ createHref,
844
+ emptyValueFallback: <span className="text-slate-400">none</span>,
845
+ field,
846
+ module,
847
+ navigateHref,
848
+ resource: row.original,
849
+ value,
850
+ })
851
+ },
852
+ header: columnRuntime.header,
853
+ id: columnRuntime.id,
854
+ },
855
+ ]
856
+ })
857
+
858
+ const rowActionColumns = createProjectedRowActionChromeColumns({
859
+ actionRuntimes,
860
+ canExecuteAction,
861
+ collectionRuntime,
862
+ componentSet,
863
+ componentSystem,
864
+ createHref,
865
+ executeAction,
866
+ getRowLabel,
867
+ module,
868
+ navigateHref,
869
+ renderChromeSlot,
870
+ renderActionIcon,
871
+ renderRowActionsSlot,
872
+ viewId,
873
+ })
874
+
875
+ return [...fieldColumns, ...rowActionColumns]
876
+ }
877
+
878
+ function createProjectedRowActionChromeColumns<TData extends object>({
879
+ actionRuntimes,
880
+ canExecuteAction,
881
+ collectionRuntime,
882
+ componentSet,
883
+ componentSystem,
884
+ executeAction,
885
+ getRowLabel,
886
+ module,
887
+ navigateHref,
888
+ renderActionIcon,
889
+ renderChromeSlot,
890
+ renderRowActionsSlot,
891
+ viewId,
892
+ }: CreateProjectedColumnsOptions<TData>): readonly ColumnDef<TData>[] {
893
+ const slots = collectionRuntime.chrome.findSlots(
894
+ collectionChromeSlotKinds.rowActions,
895
+ collectionChromeSlotPlacements.inline,
896
+ )
897
+ if (slots.length === 0) {
898
+ return []
899
+ }
900
+
901
+ return slots.map((slot) => ({
902
+ cell: ({ row }) => {
903
+ const rowLabel = getRowLabel?.(row.original) ??
904
+ collectionRuntime.readRowLabel(row.original)
905
+ const slotContext = {
906
+ actionRuntimes,
907
+ canExecuteAction,
908
+ collectionRuntime,
909
+ componentSet,
910
+ componentSystem,
911
+ executeAction,
912
+ module,
913
+ navigateHref,
914
+ renderActionIcon,
915
+ renderRowActionsSlot,
916
+ row: row.original,
917
+ rowLabel,
918
+ slot,
919
+ viewId,
920
+ } satisfies ProjectedCollectionChromeSlotRenderContext<TData>
921
+ const rendered = renderChromeSlot
922
+ ? renderChromeSlot(slotContext)
923
+ : renderRowActionsSlot?.(slot, row.original, rowLabel)
924
+
925
+ return rendered ?? null
926
+ },
927
+ header: () => <span className="sr-only">{slot.Name || 'Actions'}</span>,
928
+ id: slot.Id || 'collection-row-actions',
929
+ }))
930
+ }
931
+
932
+ export function ProjectedCollectionRowActions<TData extends object>({
933
+ canExecuteAction,
934
+ collectionRuntime,
935
+ componentSet,
936
+ componentSystem,
937
+ executeAction,
938
+ module,
939
+ renderActionIcon,
940
+ row,
941
+ rowLabel,
942
+ slot,
943
+ viewId,
944
+ }: {
945
+ readonly canExecuteAction: (
946
+ context: ProjectedCollectionActionExecutionContext<TData>,
947
+ ) => boolean
948
+ readonly collectionRuntime: ProjectedCollectionRuntime<TData>
949
+ readonly componentSet: string
950
+ readonly componentSystem: PresentationComponentSystem
951
+ readonly executeAction: (
952
+ context: ProjectedCollectionActionExecutionContext<TData>,
953
+ ) => Promise<void> | void
954
+ readonly module?: ProjectedModuleDefinitionLike | null
955
+ readonly renderActionIcon?: (placement: ProjectedActionPlacementLike) => ReactNode
956
+ readonly row: TData
957
+ readonly rowLabel: string | null
958
+ readonly slot: CollectionChromeSlotDefinition
959
+ readonly viewId: string
960
+ }) {
961
+ const ActionButton = componentSystem.actions.ActionButton
962
+ const primaryRowActions = resolveSlotRowActions(
963
+ slot,
964
+ collectionRuntime.actions.primaryRowActions,
965
+ )
966
+ const contextRowActions = resolveSlotRowActions(
967
+ slot,
968
+ collectionRuntime.actions.contextRowActions,
969
+ )
970
+ const primaryItems = collectionRuntime.actions.resolveRowActionItems(
971
+ row,
972
+ primaryRowActions,
973
+ { canExecuteAction },
974
+ )
975
+
976
+ if (primaryItems.length === 0 && contextRowActions.length === 0) {
977
+ return null
978
+ }
979
+
980
+ return componentSystem.collectionChrome.CollectionRowActions({
981
+ children: (
982
+ <>
983
+ {primaryItems.map(({ action, actionContext, actionRef, isEnabled, rowAction }) => {
984
+ const label = actionRef.placement.Label ?? rowAction.Label ?? action.Name
985
+ return (
986
+ <ActionButton
987
+ aria-label={rowLabel ? `${label}: ${rowLabel}` : label}
988
+ {...createPresentationTestAttributes({
989
+ actionId: rowAction.ActionId,
990
+ collectionSlotId: slot.Id,
991
+ viewId,
992
+ })}
993
+ disabled={
994
+ !isEnabled ||
995
+ !canExecuteAction(actionContext)
996
+ }
997
+ key={actionRef.id}
998
+ onClick={(event) => {
999
+ event.stopPropagation()
1000
+ void executeAction(actionContext)
1001
+ }}
1002
+ size="icon-sm"
1003
+ type="button"
1004
+ variant="ghost"
1005
+ >
1006
+ {renderActionIcon?.(actionRef.placement) ??
1007
+ renderDefaultActionIcon(actionRef.placement, module, componentSet)}
1008
+ </ActionButton>
1009
+ )
1010
+ })}
1011
+ <ProjectedRowActionMenu
1012
+ canExecuteAction={canExecuteAction}
1013
+ collectionRuntime={collectionRuntime}
1014
+ componentSystem={componentSystem}
1015
+ componentSet={componentSet}
1016
+ executeAction={executeAction}
1017
+ module={module}
1018
+ renderActionIcon={renderActionIcon}
1019
+ row={row}
1020
+ rowActions={contextRowActions}
1021
+ rowLabel={rowLabel}
1022
+ slotId={slot.Id}
1023
+ viewId={viewId}
1024
+ />
1025
+ </>
1026
+ ),
1027
+ rowLabel,
1028
+ slotId: slot.Id,
1029
+ })
1030
+ }
1031
+
1032
+ function resolveSlotRowActions(
1033
+ slot: CollectionChromeSlotDefinition,
1034
+ rowActions: readonly ResolvedProjectedCollectionRowAction[],
1035
+ ) {
1036
+ const scopedActions = rowActions.filter((rowAction) => rowAction.slotId === slot.Id)
1037
+ if (scopedActions.length > 0) {
1038
+ return scopedActions
1039
+ }
1040
+
1041
+ const actionIds = new Set(
1042
+ slot.RowActions.length > 0
1043
+ ? slot.RowActions.flatMap((rowAction) => [rowAction.Id, rowAction.ActionId])
1044
+ : slot.ActionIds,
1045
+ )
1046
+ if (actionIds.size === 0) {
1047
+ return rowActions
1048
+ }
1049
+
1050
+ return rowActions.filter(({ action, actionRef, rowAction }) =>
1051
+ actionIds.has(action.Id) ||
1052
+ actionIds.has(actionRef.actionId) ||
1053
+ actionIds.has(actionRef.id) ||
1054
+ actionIds.has(rowAction.ActionId) ||
1055
+ actionIds.has(rowAction.Id))
1056
+ }
1057
+
1058
+ function ProjectedRowActionMenu<TData extends object>({
1059
+ canExecuteAction,
1060
+ collectionRuntime,
1061
+ componentSet,
1062
+ componentSystem,
1063
+ executeAction,
1064
+ module,
1065
+ renderActionIcon,
1066
+ row,
1067
+ rowActions,
1068
+ rowLabel,
1069
+ slotId,
1070
+ viewId,
1071
+ }: {
1072
+ readonly canExecuteAction: (
1073
+ context: ProjectedCollectionActionExecutionContext<TData>,
1074
+ ) => boolean
1075
+ readonly collectionRuntime: ProjectedCollectionRuntime<TData>
1076
+ readonly componentSet: string
1077
+ readonly componentSystem: PresentationComponentSystem
1078
+ readonly executeAction: (
1079
+ context: ProjectedCollectionActionExecutionContext<TData>,
1080
+ ) => Promise<void> | void
1081
+ readonly module?: ProjectedModuleDefinitionLike | null
1082
+ readonly renderActionIcon?: (placement: ProjectedActionPlacementLike) => ReactNode
1083
+ readonly row: TData
1084
+ readonly rowActions: readonly ResolvedProjectedCollectionRowAction[]
1085
+ readonly rowLabel: string | null
1086
+ readonly slotId: string
1087
+ readonly viewId: string
1088
+ }) {
1089
+ const RowActionMenu = componentSystem.collections.RowActionMenu
1090
+ const RowActionMenuItem = componentSystem.collections.RowActionMenuItem
1091
+ const RowActionMenuTrigger = componentSystem.collections.RowActionMenuTrigger
1092
+ const items = collectionRuntime.actions.resolveRowActionItems(
1093
+ row,
1094
+ rowActions,
1095
+ { canExecuteAction },
1096
+ )
1097
+
1098
+ if (items.length === 0) {
1099
+ return null
1100
+ }
1101
+
1102
+ return (
1103
+ <RowActionMenu
1104
+ trigger={(
1105
+ <RowActionMenuTrigger aria-label={rowLabel ? `Actions: ${rowLabel}` : 'Row actions'}>
1106
+ {renderPresentationIcon({
1107
+ className: 'size-4',
1108
+ componentSet,
1109
+ icon: collectionChromeIconIds.rowActionsMenu,
1110
+ module,
1111
+ })}
1112
+ </RowActionMenuTrigger>
1113
+ )}
1114
+ >
1115
+ {items.map(({ action, actionContext, actionRef, isEnabled, rowAction }) => {
1116
+ const label = actionRef.placement.Label ?? rowAction.Label ?? action.Name
1117
+ return (
1118
+ <RowActionMenuItem
1119
+ actionId={rowAction.ActionId}
1120
+ collectionSlotId={slotId}
1121
+ disabled={
1122
+ !isEnabled ||
1123
+ !canExecuteAction(actionContext)
1124
+ }
1125
+ key={actionRef.id}
1126
+ onClick={(event) => {
1127
+ event.stopPropagation()
1128
+ closeContainingDetails(event.currentTarget)
1129
+ void executeAction(actionContext)
1130
+ }}
1131
+ viewId={viewId}
1132
+ >
1133
+ {renderActionIcon?.(actionRef.placement) ??
1134
+ renderDefaultActionIcon(actionRef.placement, module, componentSet)}
1135
+ <span>{label}</span>
1136
+ </RowActionMenuItem>
1137
+ )
1138
+ })}
1139
+ </RowActionMenu>
1140
+ )
1141
+ }
1142
+
1143
+ export function ProjectedCollectionSelectionActionToolbar<TData extends object>({
1144
+ canExecuteAction,
1145
+ collectionRuntime,
1146
+ componentSet,
1147
+ componentSystem,
1148
+ executeAction,
1149
+ module,
1150
+ renderActionIcon,
1151
+ slot,
1152
+ }: {
1153
+ readonly canExecuteAction: (
1154
+ context: ProjectedCollectionActionExecutionContext<TData>,
1155
+ ) => boolean
1156
+ readonly collectionRuntime: ProjectedCollectionRuntime<TData>
1157
+ readonly componentSet: string
1158
+ readonly componentSystem: PresentationComponentSystem
1159
+ readonly executeAction: (
1160
+ context: ProjectedCollectionActionExecutionContext<TData>,
1161
+ ) => Promise<void> | void
1162
+ readonly module?: ProjectedModuleDefinitionLike | null
1163
+ readonly renderActionIcon?: (placement: ProjectedActionPlacementLike) => ReactNode
1164
+ readonly slot?: CollectionChromeSlotDefinition | null
1165
+ }) {
1166
+ const ActionButton = componentSystem.actions.ActionButton
1167
+ const selectedRowIds = collectionRuntime.selection.selectedRowIds
1168
+ if (selectedRowIds.length === 0) {
1169
+ return null
1170
+ }
1171
+
1172
+ const items = collectionRuntime.actions.resolveSelectionActionItems({
1173
+ canExecuteAction,
1174
+ selectionActions: slot
1175
+ ? resolveSlotSelectionActions(slot, collectionRuntime.actions.selectionActions)
1176
+ : undefined,
1177
+ })
1178
+
1179
+ if (items.length === 0) {
1180
+ return null
1181
+ }
1182
+
1183
+ const selectedLabel =
1184
+ selectedRowIds.length === 1 ? '1 row selected' : `${selectedRowIds.length} rows selected`
1185
+
1186
+ return componentSystem.collectionChrome.CollectionSelectionActionToolbar({
1187
+ actions: (
1188
+ <>
1189
+ {items.map(({ action, actionContext, actionRef, isEnabled, selectionAction }) => {
1190
+ const label = actionRef.placement.Label ?? selectionAction.Label ?? action.Name
1191
+ return (
1192
+ <ActionButton
1193
+ disabled={
1194
+ !isEnabled ||
1195
+ !canExecuteAction(actionContext)
1196
+ }
1197
+ key={actionRef.id}
1198
+ onClick={() => {
1199
+ void executeAction(actionContext)
1200
+ }}
1201
+ size="sm"
1202
+ type="button"
1203
+ variant="outline"
1204
+ >
1205
+ {renderActionIcon?.(actionRef.placement) ??
1206
+ renderDefaultActionIcon(actionRef.placement, module, componentSet)}
1207
+ {label}
1208
+ </ActionButton>
1209
+ )
1210
+ })}
1211
+ </>
1212
+ ),
1213
+ selectedCount: selectedRowIds.length,
1214
+ selectedLabel,
1215
+ slotId: slot?.Id ?? null,
1216
+ })
1217
+ }
1218
+
1219
+ function resolveSlotSelectionActions(
1220
+ slot: CollectionChromeSlotDefinition,
1221
+ selectionActions: readonly ResolvedProjectedCollectionSelectionAction[],
1222
+ ) {
1223
+ const scopedActions = selectionActions.filter((selectionAction) =>
1224
+ selectionAction.slotId === slot.Id)
1225
+ if (scopedActions.length > 0) {
1226
+ return scopedActions
1227
+ }
1228
+
1229
+ const actionIds = new Set(
1230
+ slot.SelectionActions.length > 0
1231
+ ? slot.SelectionActions.flatMap((selectionAction) => [
1232
+ selectionAction.Id,
1233
+ selectionAction.ActionId,
1234
+ ])
1235
+ : slot.ActionIds,
1236
+ )
1237
+ if (actionIds.size === 0) {
1238
+ return selectionActions
1239
+ }
1240
+
1241
+ return selectionActions.filter(({ action, actionRef, selectionAction }) =>
1242
+ actionIds.has(action.Id) ||
1243
+ actionIds.has(actionRef.actionId) ||
1244
+ actionIds.has(actionRef.id) ||
1245
+ actionIds.has(selectionAction.ActionId) ||
1246
+ actionIds.has(selectionAction.Id))
1247
+ }
1248
+
1249
+ function renderDefaultActionIcon(
1250
+ placement: ProjectedActionPlacementLike,
1251
+ module?: ProjectedModuleDefinitionLike | null,
1252
+ componentSet = defaultPresentationComponentSet,
1253
+ ) {
1254
+ return renderPresentationIcon({
1255
+ className: 'size-4',
1256
+ componentSet,
1257
+ icon: placement.Icon ?? 'square-arrow-out-up-right',
1258
+ module,
1259
+ subject: placement,
1260
+ })
1261
+ }
1262
+
1263
+ function closeContainingDetails(element: HTMLElement) {
1264
+ element.closest('details')?.removeAttribute('open')
1265
+ }
1266
+
1267
+ function ProjectionStatusBlock({ label }: { readonly label: string }) {
1268
+ return (
1269
+ <div className="rounded-2xl border border-slate-950/8 bg-white/65 px-4 py-3 text-sm text-slate-500">
1270
+ {label}
1271
+ </div>
1272
+ )
1273
+ }