@flamingo-stack/openframe-frontend-core 0.0.711 → 0.0.713

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 (528) hide show
  1. package/dist/chunk-2NASBO7O.js +1137 -0
  2. package/dist/{chunk-RQRX4YC3.cjs → chunk-2RKFMUNW.cjs} +46 -46
  3. package/dist/chunk-2Y7QZAB2.cjs +158 -0
  4. package/dist/chunk-34I6XI3N.js +136 -0
  5. package/dist/{chunk-VWFZRMQR.js → chunk-3BB3IEJI.js} +13 -7
  6. package/dist/chunk-3K5ZLZE7.js +292 -0
  7. package/dist/chunk-3LU4UFPS.cjs +14 -0
  8. package/dist/{chunk-G47AP6LX.cjs → chunk-3QGUDPIH.cjs} +35 -33
  9. package/dist/{chunk-3UNL26CD.cjs → chunk-3RKBJT6K.cjs} +8 -6
  10. package/dist/chunk-4HJRHEXD.cjs +1612 -0
  11. package/dist/{chunk-JPG7OHEG.js → chunk-4QFWGLZO.js} +1 -1
  12. package/dist/chunk-4R2L3DSA.js +14 -0
  13. package/dist/{chunk-VMMHJFHG.js → chunk-6F4QOCGB.js} +10 -8
  14. package/dist/{chunk-Q6B35Y7O.js → chunk-6MNAIQNR.js} +22 -113
  15. package/dist/{chunk-KUC673HM.js → chunk-6T3BTD6Q.js} +204 -982
  16. package/dist/{chunk-F57ECE2I.js → chunk-6YPTUHM7.js} +4 -4
  17. package/dist/chunk-AAYNCJMW.cjs +48 -0
  18. package/dist/{chunk-FXJ3WWUB.js → chunk-ADBE7QF3.js} +5 -252
  19. package/dist/chunk-AECTRMGQ.cjs +334 -0
  20. package/dist/chunk-AGVXKD4K.js +334 -0
  21. package/dist/chunk-AL2GUHAJ.cjs +18 -0
  22. package/dist/{chunk-WMX26HB5.js → chunk-APJZBDQN.js} +12 -12
  23. package/dist/chunk-AUMKE74M.cjs +16 -0
  24. package/dist/chunk-B4NCVYEQ.cjs +292 -0
  25. package/dist/{chunk-WJUIXU6Y.js → chunk-CGHOH3GN.js} +28 -22
  26. package/dist/chunk-CMGFZHCN.js +233 -0
  27. package/dist/chunk-CMZ3QTFQ.js +1207 -0
  28. package/dist/chunk-CPA2CKRZ.cjs +125 -0
  29. package/dist/chunk-CYUUHFXL.js +41 -0
  30. package/dist/chunk-DC4RTYWK.cjs +1122 -0
  31. package/dist/{chunk-NF7GNGYG.js → chunk-DDWFCWOE.js} +5 -7
  32. package/dist/{chunk-QA6TROME.cjs → chunk-EOKSKJSS.cjs} +25 -83
  33. package/dist/{chunk-6NTPY5QA.cjs → chunk-ETYLOZOB.cjs} +978 -2413
  34. package/dist/{chunk-6AMIRXKM.cjs → chunk-F2EACDOR.cjs} +93 -87
  35. package/dist/chunk-F5BPGWGT.cjs +931 -0
  36. package/dist/chunk-FFT56TYB.cjs +401 -0
  37. package/dist/chunk-FYEJTF5Y.js +125 -0
  38. package/dist/chunk-G3GRYDMS.js +16 -0
  39. package/dist/chunk-G6OHIX5C.cjs +100 -0
  40. package/dist/{chunk-UDFRUKLC.cjs → chunk-GEDY7HQI.cjs} +2 -2
  41. package/dist/chunk-GQ7JJUIT.cjs +1207 -0
  42. package/dist/chunk-H3PKYE4N.js +18 -0
  43. package/dist/{chunk-EQWD4IJE.js → chunk-H4J72PRA.js} +74 -46
  44. package/dist/{chunk-AGDVEUVL.js → chunk-H5R7U2D2.js} +206 -5
  45. package/dist/{chunk-7AM6C6XH.cjs → chunk-HETQETPV.cjs} +9641 -9678
  46. package/dist/{chunk-Y2GSOUCS.cjs → chunk-HWVUR735.cjs} +2 -2
  47. package/dist/chunk-II4QMH26.cjs +35 -0
  48. package/dist/{chunk-XWEBHS7O.cjs → chunk-IJKMILMG.cjs} +82 -54
  49. package/dist/chunk-ILNN6QRW.js +1612 -0
  50. package/dist/{chunk-6GLQLTJI.js → chunk-IU4IO3CG.js} +35 -27
  51. package/dist/chunk-IYVFSLWJ.js +931 -0
  52. package/dist/{chunk-EX6GGCAC.cjs → chunk-J7YIQJPS.cjs} +15 -15
  53. package/dist/chunk-JHQ334WS.js +655 -0
  54. package/dist/{chunk-WCVZCGK5.cjs → chunk-JO5TLJPM.cjs} +129 -907
  55. package/dist/{chunk-PRX7HVLE.cjs → chunk-JVCAK4W3.cjs} +210 -9
  56. package/dist/chunk-KBJJQFYU.js +1122 -0
  57. package/dist/chunk-KEKCQENI.js +48 -0
  58. package/dist/{chunk-P6G3JZNC.cjs → chunk-KGAFUXGZ.cjs} +57 -148
  59. package/dist/chunk-KHXZFLIN.cjs +41 -0
  60. package/dist/{chunk-C4HRRB3V.cjs → chunk-KMB7JHGG.cjs} +23 -17
  61. package/dist/{chunk-K4PTDK34.js → chunk-KPUHSVJU.js} +322 -428
  62. package/dist/chunk-KT3U3ZRP.cjs +1137 -0
  63. package/dist/chunk-L2VUINHN.js +35 -0
  64. package/dist/{chunk-PLYEKV3N.cjs → chunk-MA4Q5RBP.cjs} +6 -6
  65. package/dist/chunk-NPAQSMRY.js +2087 -0
  66. package/dist/{chunk-IFWLJ66M.js → chunk-NQWJAOHO.js} +6 -4
  67. package/dist/{chunk-FYOKS5RI.cjs → chunk-OQKRXLTZ.cjs} +4719 -6351
  68. package/dist/chunk-P3JMAMHP.js +401 -0
  69. package/dist/{chunk-62KNLDXK.js → chunk-P6QCJXLD.js} +3 -3
  70. package/dist/chunk-PPXR22YC.cjs +2087 -0
  71. package/dist/chunk-PSVSEN4J.cjs +655 -0
  72. package/dist/{chunk-CDM7OSFB.cjs → chunk-QQQ46DFT.cjs} +125 -117
  73. package/dist/{chunk-FEMAJES4.cjs → chunk-QU4AHNRL.cjs} +41 -39
  74. package/dist/{chunk-2JTGK64G.js → chunk-RBVUIOIM.js} +12 -10
  75. package/dist/{chunk-SMAAV3HL.js → chunk-ROBQ5TR7.js} +10541 -10578
  76. package/dist/{chunk-TKZXHVBG.js → chunk-RRYNHAEL.js} +5 -5
  77. package/dist/{chunk-5HB7FLCV.js → chunk-STPC2AQU.js} +8338 -9970
  78. package/dist/{chunk-6K7QZDA6.cjs → chunk-TC4W7QDC.cjs} +8 -8
  79. package/dist/{chunk-FI6GMDO7.js → chunk-TTVOLEUS.js} +3605 -5040
  80. package/dist/{chunk-LM7TXMLU.cjs → chunk-ULWVKHSI.cjs} +8 -255
  81. package/dist/chunk-WANCBTUE.js +71 -0
  82. package/dist/{chunk-BIZNSF3U.cjs → chunk-WBNAEYTQ.cjs} +2 -51
  83. package/dist/chunk-WFPCCBOM.cjs +71 -0
  84. package/dist/chunk-WI6FHKD7.js +158 -0
  85. package/dist/chunk-WMNCVLES.js +100 -0
  86. package/dist/{chunk-UT4YHQZW.js → chunk-WSDT7LYL.js} +0 -49
  87. package/dist/chunk-WVOTC7HW.cjs +136 -0
  88. package/dist/chunk-XXBLN4BP.cjs +233 -0
  89. package/dist/{chunk-4IBPRIQH.js → chunk-YBQQNDC7.js} +1 -1
  90. package/dist/{chunk-QGZPZ3RL.js → chunk-YBS5QGON.js} +70 -128
  91. package/dist/{chunk-K6Y5NX6L.cjs → chunk-YIUSW4DB.cjs} +114 -220
  92. package/dist/{chunk-YD4LJZV4.cjs → chunk-ZX7SCPLO.cjs} +6 -8
  93. package/dist/cloud-tenants-HRJYNUJM.cjs +117 -0
  94. package/dist/cloud-tenants-SM7WLEGG.js +117 -0
  95. package/dist/components/case-studies/index.cjs +45 -29
  96. package/dist/components/case-studies/index.js +32 -16
  97. package/dist/components/chat/ask-prompts.d.ts +64 -0
  98. package/dist/components/chat/ask-prompts.d.ts.map +1 -0
  99. package/dist/components/chat/entity-cards/case-study-card.d.ts +6 -3
  100. package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
  101. package/dist/components/chat/index.cjs +27 -8
  102. package/dist/components/chat/index.d.ts +1 -0
  103. package/dist/components/chat/index.d.ts.map +1 -1
  104. package/dist/components/chat/index.js +57 -38
  105. package/dist/components/chat/quick-action-chip.d.ts +7 -3
  106. package/dist/components/chat/quick-action-chip.d.ts.map +1 -1
  107. package/dist/components/contact/index.cjs +29 -13
  108. package/dist/components/contact/index.js +28 -12
  109. package/dist/components/docs/index.cjs +33 -16
  110. package/dist/components/docs/index.js +33 -16
  111. package/dist/components/docs/use-scroll-spy.d.ts +23 -1
  112. package/dist/components/docs/use-scroll-spy.d.ts.map +1 -1
  113. package/dist/components/embeds/index.cjs +29 -13
  114. package/dist/components/embeds/index.js +28 -12
  115. package/dist/components/faq/faq-section.d.ts +8 -1
  116. package/dist/components/faq/faq-section.d.ts.map +1 -1
  117. package/dist/components/faq/index.cjs +30 -14
  118. package/dist/components/faq/index.js +29 -13
  119. package/dist/components/features/approval-level.d.ts +49 -0
  120. package/dist/components/features/approval-level.d.ts.map +1 -0
  121. package/dist/components/features/array-entry-manager.d.ts +5 -1
  122. package/dist/components/features/array-entry-manager.d.ts.map +1 -1
  123. package/dist/components/features/board/board.d.ts +2 -0
  124. package/dist/components/features/board/board.d.ts.map +1 -1
  125. package/dist/components/features/board/ticket-card.d.ts.map +1 -1
  126. package/dist/components/features/board/touch-board.d.ts +1 -1
  127. package/dist/components/features/board/touch-board.d.ts.map +1 -1
  128. package/dist/components/features/board/types.d.ts +3 -0
  129. package/dist/components/features/board/types.d.ts.map +1 -1
  130. package/dist/components/features/board/use-board-collapse.d.ts +6 -1
  131. package/dist/components/features/board/use-board-collapse.d.ts.map +1 -1
  132. package/dist/components/features/capability-explorer.d.ts +94 -0
  133. package/dist/components/features/capability-explorer.d.ts.map +1 -0
  134. package/dist/components/features/cloud-tenants/index.d.ts +14 -0
  135. package/dist/components/features/cloud-tenants/index.d.ts.map +1 -0
  136. package/dist/components/features/cloud-tenants/tenant-access-cell.d.ts +9 -0
  137. package/dist/components/features/cloud-tenants/tenant-access-cell.d.ts.map +1 -0
  138. package/dist/components/features/cloud-tenants/tenant-cell.d.ts +8 -0
  139. package/dist/components/features/cloud-tenants/tenant-cell.d.ts.map +1 -0
  140. package/dist/components/features/cloud-tenants/tenant-customer-cell.d.ts +9 -0
  141. package/dist/components/features/cloud-tenants/tenant-customer-cell.d.ts.map +1 -0
  142. package/dist/components/features/cloud-tenants/tenant-presentation.d.ts +42 -0
  143. package/dist/components/features/cloud-tenants/tenant-presentation.d.ts.map +1 -0
  144. package/dist/components/features/cloud-tenants/tenant-row.d.ts +22 -0
  145. package/dist/components/features/cloud-tenants/tenant-row.d.ts.map +1 -0
  146. package/dist/components/features/cloud-tenants/tenants-table-columns.d.ts +42 -0
  147. package/dist/components/features/cloud-tenants/tenants-table-columns.d.ts.map +1 -0
  148. package/dist/components/features/cloud-tenants/tenants-table-view.d.ts +26 -0
  149. package/dist/components/features/cloud-tenants/tenants-table-view.d.ts.map +1 -0
  150. package/dist/components/features/devices/device-last-online.d.ts +3 -0
  151. package/dist/components/features/devices/device-last-online.d.ts.map +1 -0
  152. package/dist/components/features/devices/device-name.d.ts +42 -0
  153. package/dist/components/features/devices/device-name.d.ts.map +1 -0
  154. package/dist/components/features/devices/device-selector/device-selection-mode-radio.d.ts +26 -0
  155. package/dist/components/features/devices/device-selector/device-selection-mode-radio.d.ts.map +1 -0
  156. package/dist/components/features/devices/device-selector/device-selector-skeleton.d.ts +25 -0
  157. package/dist/components/features/devices/device-selector/device-selector-skeleton.d.ts.map +1 -0
  158. package/dist/components/features/devices/device-selector/device-selector.d.ts +38 -0
  159. package/dist/components/features/devices/device-selector/device-selector.d.ts.map +1 -0
  160. package/dist/components/features/devices/device-selector/device-selector.types.d.ts +133 -0
  161. package/dist/components/features/devices/device-selector/device-selector.types.d.ts.map +1 -0
  162. package/dist/components/features/devices/device-selector/narrowing.d.ts +13 -0
  163. package/dist/components/features/devices/device-selector/narrowing.d.ts.map +1 -0
  164. package/dist/components/features/devices/device-selector/use-device-selector.d.ts +17 -0
  165. package/dist/components/features/devices/device-selector/use-device-selector.d.ts.map +1 -0
  166. package/dist/components/features/devices/device-status.d.ts +23 -0
  167. package/dist/components/features/devices/device-status.d.ts.map +1 -0
  168. package/dist/components/features/devices/device-statuses.d.ts +40 -0
  169. package/dist/components/features/devices/device-statuses.d.ts.map +1 -0
  170. package/dist/components/features/devices/device-tags-filter-button.d.ts +22 -0
  171. package/dist/components/features/devices/device-tags-filter-button.d.ts.map +1 -0
  172. package/dist/components/features/devices/device-type-icon.d.ts +11 -0
  173. package/dist/components/features/devices/device-type-icon.d.ts.map +1 -0
  174. package/dist/components/features/devices/device-type-tile.d.ts +9 -0
  175. package/dist/components/features/devices/device-type-tile.d.ts.map +1 -0
  176. package/dist/components/features/devices/devices-filter-toolbar.d.ts +46 -0
  177. package/dist/components/features/devices/devices-filter-toolbar.d.ts.map +1 -0
  178. package/dist/components/features/devices/devices-grid-filters.d.ts +25 -0
  179. package/dist/components/features/devices/devices-grid-filters.d.ts.map +1 -0
  180. package/dist/components/features/devices/devices-grid.d.ts +12 -0
  181. package/dist/components/features/devices/devices-grid.d.ts.map +1 -0
  182. package/dist/components/features/devices/devices-list.d.ts +131 -0
  183. package/dist/components/features/devices/devices-list.d.ts.map +1 -0
  184. package/dist/components/features/devices/devices-panel-header.d.ts +46 -0
  185. package/dist/components/features/devices/devices-panel-header.d.ts.map +1 -0
  186. package/dist/components/features/devices/devices-panel-view.d.ts +79 -0
  187. package/dist/components/features/devices/devices-panel-view.d.ts.map +1 -0
  188. package/dist/components/features/devices/devices-table-columns.d.ts +76 -0
  189. package/dist/components/features/devices/devices-table-columns.d.ts.map +1 -0
  190. package/dist/components/features/devices/devices-view-config.d.ts +25 -0
  191. package/dist/components/features/devices/devices-view-config.d.ts.map +1 -0
  192. package/dist/components/features/devices/index.d.ts +24 -0
  193. package/dist/components/features/devices/index.d.ts.map +1 -0
  194. package/dist/components/features/devices/types.d.ts +68 -0
  195. package/dist/components/features/devices/types.d.ts.map +1 -0
  196. package/dist/components/features/devices/use-tag-filter-modal.d.ts +50 -0
  197. package/dist/components/features/devices/use-tag-filter-modal.d.ts.map +1 -0
  198. package/dist/components/features/index.cjs +235 -12
  199. package/dist/components/features/index.d.ts +8 -0
  200. package/dist/components/features/index.d.ts.map +1 -1
  201. package/dist/components/features/index.js +274 -51
  202. package/dist/components/features/knowledge-base/index.d.ts +7 -0
  203. package/dist/components/features/knowledge-base/index.d.ts.map +1 -0
  204. package/dist/components/features/knowledge-base/knowledge-base-article-view.d.ts +44 -0
  205. package/dist/components/features/knowledge-base/knowledge-base-article-view.d.ts.map +1 -0
  206. package/dist/components/features/knowledge-base/knowledge-base-item-icon.d.ts +13 -0
  207. package/dist/components/features/knowledge-base/knowledge-base-item-icon.d.ts.map +1 -0
  208. package/dist/components/features/knowledge-base/knowledge-base-status.d.ts +8 -0
  209. package/dist/components/features/knowledge-base/knowledge-base-status.d.ts.map +1 -0
  210. package/dist/components/features/knowledge-base/knowledge-base-table.d.ts +52 -0
  211. package/dist/components/features/knowledge-base/knowledge-base-table.d.ts.map +1 -0
  212. package/dist/components/features/logs/index.d.ts +12 -0
  213. package/dist/components/features/logs/index.d.ts.map +1 -0
  214. package/dist/components/features/logs/log-drawer.d.ts +29 -0
  215. package/dist/components/features/logs/log-drawer.d.ts.map +1 -0
  216. package/dist/components/features/logs/log-source-labels.d.ts +11 -0
  217. package/dist/components/features/logs/log-source-labels.d.ts.map +1 -0
  218. package/dist/components/features/logs/logs-page-view.d.ts +32 -0
  219. package/dist/components/features/logs/logs-page-view.d.ts.map +1 -0
  220. package/dist/components/features/logs/logs-table-columns.d.ts +15 -0
  221. package/dist/components/features/logs/logs-table-columns.d.ts.map +1 -0
  222. package/dist/components/features/logs/logs-table-view.d.ts +64 -0
  223. package/dist/components/features/logs/logs-table-view.d.ts.map +1 -0
  224. package/dist/components/features/logs/types.d.ts +52 -0
  225. package/dist/components/features/logs/types.d.ts.map +1 -0
  226. package/dist/components/features/policies/compute-policy-summary.d.ts +37 -0
  227. package/dist/components/features/policies/compute-policy-summary.d.ts.map +1 -0
  228. package/dist/components/features/policies/index.d.ts +8 -0
  229. package/dist/components/features/policies/index.d.ts.map +1 -0
  230. package/dist/components/features/policies/policies-table.d.ts +34 -0
  231. package/dist/components/features/policies/policies-table.d.ts.map +1 -0
  232. package/dist/components/features/policies/policy-summary-cards.d.ts +17 -0
  233. package/dist/components/features/policies/policy-summary-cards.d.ts.map +1 -0
  234. package/dist/components/features/policies/policy-table-row.d.ts +37 -0
  235. package/dist/components/features/policies/policy-table-row.d.ts.map +1 -0
  236. package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
  237. package/dist/components/features/remote-session/index.d.ts +8 -0
  238. package/dist/components/features/remote-session/index.d.ts.map +1 -1
  239. package/dist/components/features/remote-session/remote-desktop-chat-message-row.d.ts +21 -0
  240. package/dist/components/features/remote-session/remote-desktop-chat-message-row.d.ts.map +1 -0
  241. package/dist/components/features/remote-session/remote-desktop-chat-panel.d.ts +31 -0
  242. package/dist/components/features/remote-session/remote-desktop-chat-panel.d.ts.map +1 -0
  243. package/dist/components/features/remote-session/remote-desktop-fullscreen-toolbar.d.ts +21 -0
  244. package/dist/components/features/remote-session/remote-desktop-fullscreen-toolbar.d.ts.map +1 -0
  245. package/dist/components/features/remote-session/remote-desktop-view.d.ts +50 -0
  246. package/dist/components/features/remote-session/remote-desktop-view.d.ts.map +1 -0
  247. package/dist/components/features/software/index.d.ts +8 -0
  248. package/dist/components/features/software/index.d.ts.map +1 -0
  249. package/dist/components/features/software/package-managers.d.ts +30 -0
  250. package/dist/components/features/software/package-managers.d.ts.map +1 -0
  251. package/dist/components/features/software/package-search-field-placeholder.d.ts +7 -0
  252. package/dist/components/features/software/package-search-field-placeholder.d.ts.map +1 -0
  253. package/dist/components/features/software/schedule-fields.d.ts +34 -0
  254. package/dist/components/features/software/schedule-fields.d.ts.map +1 -0
  255. package/dist/components/features/software/software-action-copy.d.ts +25 -0
  256. package/dist/components/features/software/software-action-copy.d.ts.map +1 -0
  257. package/dist/components/features/software/software-action-form.d.ts +71 -0
  258. package/dist/components/features/software/software-action-form.d.ts.map +1 -0
  259. package/dist/components/features/software/software-row-fields.d.ts +23 -0
  260. package/dist/components/features/software/software-row-fields.d.ts.map +1 -0
  261. package/dist/components/features/software/software-row.d.ts +18 -0
  262. package/dist/components/features/software/software-row.d.ts.map +1 -0
  263. package/dist/components/features/sortable-list/index.d.ts +1 -0
  264. package/dist/components/features/sortable-list/index.d.ts.map +1 -1
  265. package/dist/components/features/sortable-list/sortable-drag-handle.d.ts +21 -0
  266. package/dist/components/features/sortable-list/sortable-drag-handle.d.ts.map +1 -0
  267. package/dist/components/help-center-pages/index.cjs +133 -114
  268. package/dist/components/help-center-pages/index.js +58 -39
  269. package/dist/components/index.cjs +328 -82
  270. package/dist/components/index.js +459 -213
  271. package/dist/components/layout/page-heading.d.ts +12 -3
  272. package/dist/components/layout/page-heading.d.ts.map +1 -1
  273. package/dist/components/meeting-scheduler/index.cjs +120 -98
  274. package/dist/components/meeting-scheduler/index.js +50 -28
  275. package/dist/components/navigation/index.cjs +36 -17
  276. package/dist/components/navigation/index.d.ts +3 -0
  277. package/dist/components/navigation/index.d.ts.map +1 -1
  278. package/dist/components/navigation/index.js +36 -17
  279. package/dist/components/navigation/mingo-ai-button.d.ts +23 -10
  280. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  281. package/dist/components/navigation/sticky-section-nav.d.ts +14 -1
  282. package/dist/components/navigation/sticky-section-nav.d.ts.map +1 -1
  283. package/dist/components/onboarding-guides/index.cjs +32 -16
  284. package/dist/components/onboarding-guides/index.js +31 -15
  285. package/dist/components/product-demo/cast.d.ts +58 -0
  286. package/dist/components/product-demo/cast.d.ts.map +1 -0
  287. package/dist/components/product-demo/fixtures/cloud-tenants.d.ts +10 -0
  288. package/dist/components/product-demo/fixtures/cloud-tenants.d.ts.map +1 -0
  289. package/dist/components/product-demo/fixtures/devices.d.ts +14 -0
  290. package/dist/components/product-demo/fixtures/devices.d.ts.map +1 -0
  291. package/dist/components/product-demo/fixtures/knowledge.d.ts +14 -0
  292. package/dist/components/product-demo/fixtures/knowledge.d.ts.map +1 -0
  293. package/dist/components/product-demo/fixtures/logs.d.ts +15 -0
  294. package/dist/components/product-demo/fixtures/logs.d.ts.map +1 -0
  295. package/dist/components/product-demo/fixtures/policies.d.ts +13 -0
  296. package/dist/components/product-demo/fixtures/policies.d.ts.map +1 -0
  297. package/dist/components/product-demo/fixtures/remote-session.d.ts +20 -0
  298. package/dist/components/product-demo/fixtures/remote-session.d.ts.map +1 -0
  299. package/dist/components/product-demo/fixtures/shared.d.ts +156 -0
  300. package/dist/components/product-demo/fixtures/shared.d.ts.map +1 -0
  301. package/dist/components/product-demo/fixtures/software-update.d.ts +19 -0
  302. package/dist/components/product-demo/fixtures/software-update.d.ts.map +1 -0
  303. package/dist/components/product-demo/fixtures/tickets-board.d.ts +8 -0
  304. package/dist/components/product-demo/fixtures/tickets-board.d.ts.map +1 -0
  305. package/dist/components/product-demo/index.cjs +112 -0
  306. package/dist/components/product-demo/index.d.ts +6 -0
  307. package/dist/components/product-demo/index.d.ts.map +1 -0
  308. package/dist/components/product-demo/index.js +112 -0
  309. package/dist/components/product-demo/product-screen.d.ts +22 -0
  310. package/dist/components/product-demo/product-screen.d.ts.map +1 -0
  311. package/dist/components/product-demo/registry.d.ts +13 -0
  312. package/dist/components/product-demo/registry.d.ts.map +1 -0
  313. package/dist/components/product-demo/screen-keys.cjs +25 -0
  314. package/dist/components/product-demo/screen-keys.d.ts +27 -0
  315. package/dist/components/product-demo/screen-keys.d.ts.map +1 -0
  316. package/dist/components/product-demo/screen-keys.js +20 -0
  317. package/dist/components/product-demo/screens/cloud-tenants.d.ts +8 -0
  318. package/dist/components/product-demo/screens/cloud-tenants.d.ts.map +1 -0
  319. package/dist/components/product-demo/screens/devices.d.ts +10 -0
  320. package/dist/components/product-demo/screens/devices.d.ts.map +1 -0
  321. package/dist/components/product-demo/screens/knowledge.d.ts +9 -0
  322. package/dist/components/product-demo/screens/knowledge.d.ts.map +1 -0
  323. package/dist/components/product-demo/screens/logs.d.ts +4 -0
  324. package/dist/components/product-demo/screens/logs.d.ts.map +1 -0
  325. package/dist/components/product-demo/screens/policies.d.ts +8 -0
  326. package/dist/components/product-demo/screens/policies.d.ts.map +1 -0
  327. package/dist/components/product-demo/screens/remote-desktop-picture.d.ts +11 -0
  328. package/dist/components/product-demo/screens/remote-desktop-picture.d.ts.map +1 -0
  329. package/dist/components/product-demo/screens/remote-session.d.ts +8 -0
  330. package/dist/components/product-demo/screens/remote-session.d.ts.map +1 -0
  331. package/dist/components/product-demo/screens/software-update.d.ts +12 -0
  332. package/dist/components/product-demo/screens/software-update.d.ts.map +1 -0
  333. package/dist/components/product-demo/screens/tickets-board.d.ts +4 -0
  334. package/dist/components/product-demo/screens/tickets-board.d.ts.map +1 -0
  335. package/dist/components/product-demo/types.d.ts +9 -0
  336. package/dist/components/product-demo/types.d.ts.map +1 -0
  337. package/dist/components/related-content/index.cjs +14 -8
  338. package/dist/components/related-content/index.js +13 -7
  339. package/dist/components/tickets/index.cjs +36 -20
  340. package/dist/components/tickets/index.js +35 -19
  341. package/dist/components/ui/file-manager/index.cjs +38 -34
  342. package/dist/components/ui/file-manager/index.js +8 -4
  343. package/dist/components/ui/index.cjs +57 -17
  344. package/dist/components/ui/index.d.ts +1 -0
  345. package/dist/components/ui/index.d.ts.map +1 -1
  346. package/dist/components/ui/index.js +141 -101
  347. package/dist/components/ui/product-screen-frame.d.ts +45 -0
  348. package/dist/components/ui/product-screen-frame.d.ts.map +1 -0
  349. package/dist/components/ui/radio-group.d.ts +5 -0
  350. package/dist/components/ui/radio-group.d.ts.map +1 -1
  351. package/dist/components/ui/snap-carousel.d.ts +11 -1
  352. package/dist/components/ui/snap-carousel.d.ts.map +1 -1
  353. package/dist/components/ui/tag.d.ts +1 -1
  354. package/dist/components/ui/tag.d.ts.map +1 -1
  355. package/dist/devices-6XKQTQM5.cjs +81 -0
  356. package/dist/devices-LXSFGGEZ.js +81 -0
  357. package/dist/hooks/index.cjs +17 -8
  358. package/dist/hooks/index.js +32 -23
  359. package/dist/hooks/ui/index.d.ts +1 -0
  360. package/dist/hooks/ui/index.d.ts.map +1 -1
  361. package/dist/hooks/ui/use-header-height.d.ts +12 -0
  362. package/dist/hooks/ui/use-header-height.d.ts.map +1 -1
  363. package/dist/hooks/ui/use-sticky-toolbar.d.ts +29 -0
  364. package/dist/hooks/ui/use-sticky-toolbar.d.ts.map +1 -0
  365. package/dist/hooks/use-location-hash.d.ts +2 -2
  366. package/dist/hooks/use-location-hash.d.ts.map +1 -1
  367. package/dist/hooks/use-scroll-to-hash.d.ts +7 -0
  368. package/dist/hooks/use-scroll-to-hash.d.ts.map +1 -1
  369. package/dist/index.cjs +284 -25
  370. package/dist/index.js +737 -478
  371. package/dist/knowledge-7PDZLXPO.cjs +108 -0
  372. package/dist/knowledge-Q4SPFOTN.js +108 -0
  373. package/dist/logs-SZ4VB4FE.js +216 -0
  374. package/dist/logs-TO3NELGJ.cjs +216 -0
  375. package/dist/policies-533GQVFM.cjs +121 -0
  376. package/dist/policies-LB7VW2UB.js +121 -0
  377. package/dist/remote-session-GSGZOFDX.js +196 -0
  378. package/dist/remote-session-R6VAFESR.cjs +196 -0
  379. package/dist/software-update-DT2XSG7M.cjs +179 -0
  380. package/dist/software-update-ENZNVGKP.js +179 -0
  381. package/dist/tickets-board-BHLPAVNJ.cjs +137 -0
  382. package/dist/tickets-board-PYDNELSK.js +137 -0
  383. package/dist/types/case-study.d.ts +15 -1
  384. package/dist/types/case-study.d.ts.map +1 -1
  385. package/dist/utils/date-utils.d.ts +5 -0
  386. package/dist/utils/date-utils.d.ts.map +1 -1
  387. package/dist/utils/index.cjs +45 -2
  388. package/dist/utils/index.d.ts +2 -1
  389. package/dist/utils/index.d.ts.map +1 -1
  390. package/dist/utils/index.js +41 -3
  391. package/dist/utils/open-in-new-tab.d.ts +7 -0
  392. package/dist/utils/open-in-new-tab.d.ts.map +1 -0
  393. package/dist/utils/same-page-hash-nav.d.ts +31 -0
  394. package/dist/utils/same-page-hash-nav.d.ts.map +1 -1
  395. package/dist/utils/scroll-into-view.d.ts +14 -1
  396. package/dist/utils/scroll-into-view.d.ts.map +1 -1
  397. package/package.json +13 -1
  398. package/src/components/chat/ask-prompts.tsx +226 -0
  399. package/src/components/chat/embeddable-chat.tsx +36 -2
  400. package/src/components/chat/entity-cards/case-study-card.tsx +122 -46
  401. package/src/components/chat/index.ts +1 -0
  402. package/src/components/chat/quick-action-chip.tsx +33 -9
  403. package/src/components/docs/use-scroll-spy.ts +93 -18
  404. package/src/components/faq/faq-section.tsx +82 -43
  405. package/src/components/features/approval-level.tsx +149 -0
  406. package/src/components/features/array-entry-manager.tsx +10 -1
  407. package/src/components/features/board/board.tsx +4 -1
  408. package/src/components/features/board/ticket-card.tsx +11 -1
  409. package/src/components/features/board/touch-board.tsx +2 -1
  410. package/src/components/features/board/types.ts +3 -0
  411. package/src/components/features/board/use-board-collapse.ts +7 -2
  412. package/src/components/features/capability-explorer.tsx +412 -0
  413. package/src/components/features/cloud-tenants/index.ts +28 -0
  414. package/src/components/features/cloud-tenants/tenant-access-cell.tsx +24 -0
  415. package/src/components/features/cloud-tenants/tenant-cell.tsx +26 -0
  416. package/src/components/features/cloud-tenants/tenant-customer-cell.tsx +27 -0
  417. package/src/components/features/cloud-tenants/tenant-presentation.ts +111 -0
  418. package/src/components/features/cloud-tenants/tenant-row.ts +21 -0
  419. package/src/components/features/cloud-tenants/tenants-table-columns.ts +30 -0
  420. package/src/components/features/cloud-tenants/tenants-table-view.tsx +142 -0
  421. package/src/components/features/devices/device-last-online.ts +6 -0
  422. package/src/components/features/devices/device-name.ts +51 -0
  423. package/src/components/features/devices/device-selector/device-selection-mode-radio.tsx +107 -0
  424. package/src/components/features/devices/device-selector/device-selector-skeleton.tsx +32 -0
  425. package/src/components/features/devices/device-selector/device-selector.tsx +799 -0
  426. package/src/components/features/devices/device-selector/device-selector.types.ts +138 -0
  427. package/src/components/features/devices/device-selector/narrowing.ts +33 -0
  428. package/src/components/features/devices/device-selector/use-device-selector.ts +51 -0
  429. package/src/components/features/devices/device-status.ts +102 -0
  430. package/src/components/features/devices/device-statuses.ts +88 -0
  431. package/src/components/features/devices/device-tags-filter-button.tsx +49 -0
  432. package/src/components/features/devices/device-type-icon.tsx +29 -0
  433. package/src/components/features/devices/device-type-tile.tsx +17 -0
  434. package/src/components/features/devices/devices-filter-toolbar.tsx +114 -0
  435. package/src/components/features/devices/devices-grid-filters.tsx +99 -0
  436. package/src/components/features/devices/devices-grid.tsx +86 -0
  437. package/src/components/features/devices/devices-list.tsx +357 -0
  438. package/src/components/features/devices/devices-panel-header.tsx +97 -0
  439. package/src/components/features/devices/devices-panel-view.tsx +261 -0
  440. package/src/components/features/devices/devices-table-columns.tsx +356 -0
  441. package/src/components/features/devices/devices-view-config.tsx +38 -0
  442. package/src/components/features/devices/index.ts +89 -0
  443. package/src/components/features/devices/types.ts +74 -0
  444. package/src/components/features/devices/use-tag-filter-modal.ts +109 -0
  445. package/src/components/features/index.ts +10 -0
  446. package/src/components/features/knowledge-base/index.ts +19 -0
  447. package/src/components/features/knowledge-base/knowledge-base-article-view.tsx +134 -0
  448. package/src/components/features/knowledge-base/knowledge-base-item-icon.ts +14 -0
  449. package/src/components/features/knowledge-base/knowledge-base-status.ts +12 -0
  450. package/src/components/features/knowledge-base/knowledge-base-table.tsx +180 -0
  451. package/src/components/features/logs/index.ts +20 -0
  452. package/src/components/features/logs/log-drawer.tsx +138 -0
  453. package/src/components/features/logs/log-source-labels.ts +25 -0
  454. package/src/components/features/logs/logs-page-view.tsx +100 -0
  455. package/src/components/features/logs/logs-table-columns.ts +16 -0
  456. package/src/components/features/logs/logs-table-view.tsx +509 -0
  457. package/src/components/features/logs/types.ts +71 -0
  458. package/src/components/features/policies/compute-policy-summary.ts +115 -0
  459. package/src/components/features/policies/index.ts +14 -0
  460. package/src/components/features/policies/policies-table.tsx +235 -0
  461. package/src/components/features/policies/policy-summary-cards.tsx +69 -0
  462. package/src/components/features/policies/policy-table-row.ts +37 -0
  463. package/src/components/features/policy-configuration-panel.tsx +3 -12
  464. package/src/components/features/remote-session/index.ts +8 -0
  465. package/src/components/features/remote-session/remote-desktop-chat-message-row.tsx +56 -0
  466. package/src/components/features/remote-session/remote-desktop-chat-panel.tsx +102 -0
  467. package/src/components/features/remote-session/remote-desktop-fullscreen-toolbar.tsx +119 -0
  468. package/src/components/features/remote-session/remote-desktop-view.tsx +209 -0
  469. package/src/components/features/software/index.ts +39 -0
  470. package/src/components/features/software/package-managers.ts +54 -0
  471. package/src/components/features/software/package-search-field-placeholder.tsx +23 -0
  472. package/src/components/features/software/schedule-fields.tsx +105 -0
  473. package/src/components/features/software/software-action-copy.ts +70 -0
  474. package/src/components/features/software/software-action-form.tsx +199 -0
  475. package/src/components/features/software/software-row-fields.tsx +105 -0
  476. package/src/components/features/software/software-row.ts +22 -0
  477. package/src/components/features/sortable-list/index.ts +1 -0
  478. package/src/components/features/sortable-list/sortable-drag-handle.tsx +41 -0
  479. package/src/components/layout/page-heading.tsx +23 -6
  480. package/src/components/navigation/index.ts +5 -0
  481. package/src/components/navigation/mingo-ai-button.tsx +37 -13
  482. package/src/components/navigation/sticky-section-nav.tsx +99 -1
  483. package/src/components/product-demo/cast.tsx +78 -0
  484. package/src/components/product-demo/fixtures/cloud-tenants.ts +63 -0
  485. package/src/components/product-demo/fixtures/devices.ts +87 -0
  486. package/src/components/product-demo/fixtures/knowledge.ts +48 -0
  487. package/src/components/product-demo/fixtures/logs.ts +103 -0
  488. package/src/components/product-demo/fixtures/policies.ts +77 -0
  489. package/src/components/product-demo/fixtures/remote-session.ts +65 -0
  490. package/src/components/product-demo/fixtures/shared.ts +53 -0
  491. package/src/components/product-demo/fixtures/software-update.ts +63 -0
  492. package/src/components/product-demo/fixtures/tickets-board.ts +100 -0
  493. package/src/components/product-demo/index.ts +7 -0
  494. package/src/components/product-demo/product-screen.tsx +94 -0
  495. package/src/components/product-demo/registry.ts +20 -0
  496. package/src/components/product-demo/screen-keys.ts +31 -0
  497. package/src/components/product-demo/screens/cloud-tenants.tsx +23 -0
  498. package/src/components/product-demo/screens/devices.tsx +41 -0
  499. package/src/components/product-demo/screens/knowledge.tsx +27 -0
  500. package/src/components/product-demo/screens/logs.tsx +113 -0
  501. package/src/components/product-demo/screens/policies.tsx +29 -0
  502. package/src/components/product-demo/screens/remote-desktop-picture.tsx +118 -0
  503. package/src/components/product-demo/screens/remote-session.tsx +46 -0
  504. package/src/components/product-demo/screens/software-update.tsx +97 -0
  505. package/src/components/product-demo/screens/tickets-board.tsx +28 -0
  506. package/src/components/product-demo/types.ts +10 -0
  507. package/src/components/ui/index.ts +1 -0
  508. package/src/components/ui/product-screen-frame.tsx +136 -0
  509. package/src/components/ui/radio-group.tsx +16 -4
  510. package/src/components/ui/snap-carousel.tsx +26 -1
  511. package/src/components/ui/tag.tsx +5 -0
  512. package/src/hooks/ui/index.ts +1 -0
  513. package/src/hooks/ui/use-header-height.ts +51 -2
  514. package/src/hooks/ui/use-sticky-toolbar.ts +60 -0
  515. package/src/hooks/use-location-hash.ts +9 -5
  516. package/src/hooks/use-scroll-to-hash.ts +9 -1
  517. package/src/types/case-study.ts +21 -1
  518. package/src/utils/date-utils.ts +8 -1
  519. package/src/utils/index.ts +5 -0
  520. package/src/utils/open-in-new-tab.ts +12 -0
  521. package/src/utils/same-page-hash-nav.ts +57 -0
  522. package/src/utils/scroll-into-view.ts +21 -5
  523. package/dist/chunk-BM5MVGH3.cjs +0 -499
  524. package/dist/chunk-CUME3VTQ.js +0 -144
  525. package/dist/chunk-CZ7Y7UG4.js +0 -732
  526. package/dist/chunk-FLKJYMVB.js +0 -499
  527. package/dist/chunk-OF6GEI5A.cjs +0 -144
  528. package/dist/chunk-UVTD52MD.cjs +0 -732
@@ -0,0 +1,799 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { CheckCircleIcon, MonitorIcon, PlusCircleIcon, TrashIcon } from '../../../icons-v2-generated';
5
+ import {
6
+ Button,
7
+ type ColumnDef,
8
+ type ColumnFiltersState,
9
+ DataTable,
10
+ EntityImage,
11
+ type OnChangeFn,
12
+ type Row,
13
+ type TabItem,
14
+ TabNavigation,
15
+ Tag,
16
+ TruncateText,
17
+ useDataTable,
18
+ } from '../../../ui';
19
+ import { OSTypeBadge } from '../../os-type-badge';
20
+ import { formatLastOnline } from '../device-last-online';
21
+ import { getDeviceName } from '../device-name';
22
+ import { getDeviceStatusConfig } from '../device-status';
23
+ import { DEVICE_STATUS } from '../device-statuses';
24
+ import { DeviceTypeTile } from '../device-type-tile';
25
+ import { DevicesFilterToolbar } from '../devices-filter-toolbar';
26
+ import { getDeviceFilterColumns } from '../devices-table-columns';
27
+ import { useDeviceImageUrl } from '../devices-view-config';
28
+ import type { DeviceFilters, DeviceRow } from '../types';
29
+ import { useTagFilterModal } from '../use-tag-filter-modal';
30
+ import { DeviceSelectionModeRadio } from './device-selection-mode-radio';
31
+ import type { DeviceSelectorProps, SubTab } from './device-selector.types';
32
+ import { useDeviceSelector } from './use-device-selector';
33
+
34
+ const EMPTY_SET: ReadonlySet<string> = new Set();
35
+ const NOOP = () => {};
36
+ const DEFAULT_GET_DEVICE_KEY = (d: DeviceRow): string | undefined => d.machineId || d.id;
37
+
38
+ /** The CUSTOMER cell: the customer's image, name and primary contact. */
39
+ function CustomerCell({ device }: { device: DeviceRow }) {
40
+ const fullImageUrl = useDeviceImageUrl(device.organizationImageUrl, device.organizationImageHash);
41
+ return (
42
+ <div className="flex min-w-0 items-center gap-3">
43
+ <EntityImage
44
+ src={fullImageUrl}
45
+ alt={device.organization || 'Customer'}
46
+ className="size-10 shrink-0 content-md:size-10"
47
+ />
48
+ <div className="flex min-w-0 flex-col justify-center">
49
+ <TruncateText>{device.organization || ''}</TruncateText>
50
+ {device.organizationEmail && (
51
+ <TruncateText variant="h6" tone="secondary">
52
+ {device.organizationEmail}
53
+ </TruncateText>
54
+ )}
55
+ </div>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Selection-oriented devices list. Sits next to `DevicesPanel` (the listing
62
+ * page), but the data model is different and intentional:
63
+ *
64
+ * - **Data is owned by the parent.** The consumer passes a pre-fetched
65
+ * `devices: Device[]` and (optionally) an `infiniteScroll` config. This is
66
+ * because consumers fetch from different backends, GraphQL `devices` query
67
+ * (TestScriptModal, ScheduleAssignDevicesView), Tactical RMM REST
68
+ * (RunScriptView), Fleet MDM (monitoring queries/policies). A single
69
+ * internal `useDevices` wouldn't fit all of them.
70
+ *
71
+ * - **Filtering is client-side.** Column filter funnels (status/customer) and
72
+ * tag chips operate on the `devices` array via `Array.filter`. Filter
73
+ * options shown in the dropdowns are derived from the same array (no extra
74
+ * network call). Trade-off: if the parent passes a partial page, the
75
+ * filters see only that page, not the full dataset on the server.
76
+ *
77
+ * - **Pagination is parent-driven via `infiniteScroll`.** The component just
78
+ * renders `DataTable.InfiniteFooter` and calls `onLoadMore`. The consumer
79
+ * owns the fetching strategy. In practice all current consumers fetch the
80
+ * full list (≤100 devices) and don't pass `infiniteScroll` at all.
81
+ *
82
+ * For a server-driven listing with URL state, GraphQL pagination and filter
83
+ * counts coming from the backend, use `DevicesPanel` instead.
84
+ *
85
+ * Two opt-in modes change the picture above:
86
+ *
87
+ * - **`server`** hands search, filtering, paging and the bulk actions to the
88
+ * parent's backend, see {@link DeviceSelectorServer}.
89
+ * - **`selectionMode` + `criteriaContent`** swap the picker for the rule editor
90
+ * of "Select Devices by Criteria": `criteriaContent` replaces the card
91
+ * entirely, the search toolbar and row actions go away, and the table becomes
92
+ * a live preview of what the rule matches.
93
+ */
94
+ export function DeviceSelector<T extends DeviceRow = DeviceRow>({
95
+ devices,
96
+ loading,
97
+ selectedIds: selectedIdsProp,
98
+ onSelectionChange: onSelectionChangeProp,
99
+ getDeviceKey: getDeviceKeyProp,
100
+ infiniteScroll,
101
+ disabled = false,
102
+ showSelectionModeRadio = true,
103
+ headerContent,
104
+ addAllBehavior = 'merge',
105
+ singleSelect = false,
106
+ isDeviceDisabled,
107
+ hideColumns,
108
+ showToolbar = true,
109
+ totalCount,
110
+ server,
111
+ selectionMode,
112
+ onSelectionModeChange,
113
+ criteriaContent,
114
+ }: DeviceSelectorProps<T>) {
115
+ const selectedIds = (selectedIdsProp ?? EMPTY_SET) as Set<string>;
116
+ const onSelectionChange = onSelectionChangeProp ?? NOOP;
117
+ const getDeviceKey = getDeviceKeyProp ?? DEFAULT_GET_DEVICE_KEY;
118
+ // Criteria mode replaces the whole picker, card, tabs and search row, with
119
+ // the rule editor over a read-only preview of what that rule resolves to.
120
+ const isCriteria = selectionMode === 'criteria';
121
+ // Called unconditionally (hooks rule); in server mode its results are simply
122
+ // not the ones used, the parent's query already answered those questions.
123
+ const client = useDeviceSelector({ devices, selectedIds, getDeviceKey });
124
+
125
+ const searchTerm = server ? server.search : client.searchTerm;
126
+ const setSearchTerm = server ? server.onSearchChange : client.setSearchTerm;
127
+ const activeSubTab = server ? server.activeTab : client.activeSubTab;
128
+ const handleTabChange = useMemo(
129
+ () => (server ? (tabId: string) => server.onTabChange(tabId as SubTab) : client.handleTabChange),
130
+ [server, client.handleTabChange],
131
+ );
132
+
133
+ // Read latest selectedIds via ref so toggleDevice can stay reference-stable.
134
+ // The DataTable rows are React.memo'd; rows that don't re-render keep an old
135
+ // toggleDevice closure, and a stale closure that captured an outdated
136
+ // selectedIds would corrupt the set on the next click.
137
+ const selectedIdsRef = useRef(selectedIds);
138
+ // Latest-value refs, written after the commit rather than during render:
139
+ // a render-phase ref write is what `react-hooks/refs` forbids, and every
140
+ // reader below runs in an effect, a timer or an event handler.
141
+ useEffect(() => {
142
+ selectedIdsRef.current = selectedIds;
143
+ });
144
+
145
+ const [clientColumnFilters, setClientColumnFilters] = useState<ColumnFiltersState>([]);
146
+ const [clientTags, setClientTags] = useState<string[]>([]);
147
+
148
+ const columnFilters = server ? server.narrowing.columnFilters : clientColumnFilters;
149
+ const selectedTags = server ? server.narrowing.tags : clientTags;
150
+
151
+ const setColumnFilters = useCallback(
152
+ (next: ColumnFiltersState) => {
153
+ if (server) server.onNarrowingChange({ columnFilters: next, tags: server.narrowing.tags });
154
+ else setClientColumnFilters(next);
155
+ },
156
+ [server],
157
+ );
158
+
159
+ // TanStack hands either a value or an updater fn; resolve it against what is
160
+ // on screen before it reaches the (possibly lifted) setter above.
161
+ const handleTableColumnFiltersChange = useCallback<OnChangeFn<ColumnFiltersState>>(
162
+ updaterOrValue => {
163
+ setColumnFilters(typeof updaterOrValue === 'function' ? updaterOrValue(columnFilters) : updaterOrValue);
164
+ },
165
+ [columnFilters, setColumnFilters],
166
+ );
167
+
168
+ const setSelectedTags = useCallback(
169
+ (next: string[]) => {
170
+ if (server) server.onNarrowingChange({ columnFilters: server.narrowing.columnFilters, tags: next });
171
+ else setClientTags(next);
172
+ },
173
+ [server],
174
+ );
175
+
176
+ const clientToggleDevice = useCallback(
177
+ (device: T) => {
178
+ if (disabled) return;
179
+ if (isDeviceDisabled?.(device)) return;
180
+ const key = getDeviceKey(device);
181
+ if (key === undefined) return;
182
+
183
+ const current = selectedIdsRef.current;
184
+ if (singleSelect) {
185
+ onSelectionChange(current.has(key) ? new Set() : new Set([key]));
186
+ return;
187
+ }
188
+
189
+ const next = new Set(current);
190
+ if (next.has(key)) {
191
+ next.delete(key);
192
+ } else {
193
+ next.add(key);
194
+ }
195
+ onSelectionChange(next);
196
+ },
197
+ [disabled, isDeviceDisabled, getDeviceKey, onSelectionChange, singleSelect],
198
+ );
199
+
200
+ // In server mode the Selected tab can only ever remove. The Available tab
201
+ // holds the whole candidate set, assigned devices included, marked as such by
202
+ // the backend, so there the row does flip: a marked row removes, an unmarked
203
+ // one adds. Read through the ref for the same reason `clientToggleDevice`
204
+ // does: memoized rows keep the closure they were rendered with.
205
+ const toggleDevice = useCallback(
206
+ (device: T) => {
207
+ if (!server) {
208
+ clientToggleDevice(device);
209
+ return;
210
+ }
211
+ if (disabled) return;
212
+ if (isDeviceDisabled?.(device)) return;
213
+ if (server.activeTab === 'selected') {
214
+ server.onRemove(device);
215
+ return;
216
+ }
217
+ const key = getDeviceKey(device);
218
+ if (key !== undefined && selectedIdsRef.current.has(key)) server.onRemove(device);
219
+ else server.onAdd(device);
220
+ },
221
+ [server, clientToggleDevice, disabled, isDeviceDisabled, getDeviceKey],
222
+ );
223
+
224
+ const removeAllSelected = useCallback(() => {
225
+ if (disabled) return;
226
+ if (server) {
227
+ server.onRemoveAll();
228
+ return;
229
+ }
230
+ onSelectionChange(new Set());
231
+ }, [disabled, server, onSelectionChange]);
232
+
233
+ // Tag chips in the search bar take "key:value" form (e.g. "env:prod").
234
+ // Plain text chips are kept visually but don't filter (matches DevicesPanel behavior).
235
+ const selectedTagValues = useMemo(
236
+ () =>
237
+ selectedTags.flatMap(t => {
238
+ const i = t.indexOf(':');
239
+ return i > 0 ? [t.substring(i + 1)] : [];
240
+ }),
241
+ [selectedTags],
242
+ );
243
+
244
+ // Apply column filters + tag filters client-side on top of the search/tab-filtered list.
245
+ // singleSelect mode skips the tab split and shows all matching devices.
246
+ // In server mode there is nothing to apply: `devices` is already the answer
247
+ // to this tab, this search and these filters.
248
+ // Criteria mode is the same deal as server mode: `devices` is already the
249
+ // answer, here, the set the rule resolves to.
250
+ const passThrough = !!server || isCriteria;
251
+ const baseDevices = passThrough ? devices : singleSelect ? client.filteredDevices : client.displayDevices;
252
+ /** The column filters (status, OS, customer) and the tag chips, as one test of a device. */
253
+ const matchesVisibleFilters = useCallback(
254
+ (d: T) => {
255
+ for (const f of columnFilters) {
256
+ const values = f.value as string[];
257
+ if (!values || values.length === 0) continue;
258
+ const cell =
259
+ f.id === 'status'
260
+ ? d.status
261
+ : f.id === 'os'
262
+ ? d.osType
263
+ : f.id === 'organization'
264
+ ? // Same key the option was built from, id first, name only for
265
+ // rows that have no id.
266
+ (d.organizationId ?? d.organization)
267
+ : undefined;
268
+ if (cell === undefined || !values.includes(cell)) return false;
269
+ }
270
+ if (selectedTagValues.length > 0) {
271
+ const hasMatchingTag = (d.tags ?? []).some(tag => tag.values.some(v => selectedTagValues.includes(v)));
272
+ if (!hasMatchingTag) return false;
273
+ }
274
+ return true;
275
+ },
276
+ [columnFilters, selectedTagValues],
277
+ );
278
+ const devicesForTable = useMemo(() => {
279
+ if (passThrough || (columnFilters.length === 0 && selectedTagValues.length === 0)) return baseDevices;
280
+ return baseDevices.filter(matchesVisibleFilters);
281
+ }, [passThrough, baseDevices, columnFilters, selectedTagValues, matchesVisibleFilters]);
282
+
283
+ const addAllDevices = useCallback(() => {
284
+ if (disabled) return;
285
+ if (server) {
286
+ server.onAddAll();
287
+ return;
288
+ }
289
+ const base = addAllBehavior === 'replace' ? new Set<string>() : new Set(selectedIds);
290
+ // Only what the table can show: a device the status, OS, customer or tag filter hides is never added unseen.
291
+ for (const d of client.filteredDevices) {
292
+ if (!matchesVisibleFilters(d)) continue;
293
+ if (isDeviceDisabled?.(d)) continue;
294
+ const key = getDeviceKey(d);
295
+ if (key !== undefined) {
296
+ base.add(key);
297
+ }
298
+ }
299
+ onSelectionChange(base);
300
+ }, [
301
+ disabled,
302
+ server,
303
+ isDeviceDisabled,
304
+ addAllBehavior,
305
+ selectedIds,
306
+ client.filteredDevices,
307
+ matchesVisibleFilters,
308
+ getDeviceKey,
309
+ onSelectionChange,
310
+ ]);
311
+
312
+ // Client-side `DeviceFilters`-shaped object, built from the prop list so
313
+ // `useTagFilterModal` and `getDeviceFilterColumns` can drive the FilterModal
314
+ // without a network round-trip.
315
+ const clientDeviceFilters = useMemo<DeviceFilters>(() => {
316
+ const statusCounts = new Map<string, number>();
317
+ const osCounts = new Map<string, number>();
318
+ const orgCounts = new Map<string, { label: string; count: number }>();
319
+ const tagSeen = new Map<string, Map<string, number>>();
320
+
321
+ for (const d of devices) {
322
+ if (d.status) statusCounts.set(d.status, (statusCounts.get(d.status) ?? 0) + 1);
323
+ if (d.osType) osCounts.set(d.osType, (osCounts.get(d.osType) ?? 0) + 1);
324
+ // Keyed by id, labelled by name, the mobile FilterModal writes the VALUE
325
+ // into the same `organization` column filter the funnels above do.
326
+ const orgKey = d.organizationId ?? d.organization;
327
+ if (orgKey) {
328
+ const existing = orgCounts.get(orgKey);
329
+ orgCounts.set(orgKey, { label: d.organization ?? orgKey, count: (existing?.count ?? 0) + 1 });
330
+ }
331
+ for (const tag of d.tags ?? []) {
332
+ const vc = tagSeen.get(tag.key) ?? new Map<string, number>();
333
+ tagSeen.set(tag.key, vc);
334
+ for (const v of tag.values) vc.set(v, (vc.get(v) ?? 0) + 1);
335
+ }
336
+ }
337
+
338
+ const tagKeys: Array<{ key: string; value: string; count: number }> = [];
339
+ for (const [key, vc] of tagSeen) {
340
+ for (const [value, count] of vc) {
341
+ tagKeys.push({ key, value, count });
342
+ }
343
+ }
344
+
345
+ return {
346
+ statuses: Array.from(statusCounts, ([value, count]) => ({ value, count })),
347
+ deviceTypes: [],
348
+ osTypes: Array.from(osCounts, ([value, count]) => ({ value, count })),
349
+ organizationIds: Array.from(orgCounts, ([value, { label, count }]) => ({ value, label, count })),
350
+ tagKeys,
351
+ filteredCount: devices.length,
352
+ };
353
+ }, [devices]);
354
+
355
+ // Counting the rows on screen only describes the page the client happens to
356
+ // hold; when the server is paging, it supplies the real facets instead.
357
+ const deviceFilters = server?.filterOptions ?? clientDeviceFilters;
358
+
359
+ // Counts ride along only when the facets are the server's. The client-side
360
+ // ones describe the page in hand, which is not what a count claims to answer.
361
+ const serverFaceted = Boolean(server?.filterOptions);
362
+ const filterColumns = useMemo(() => {
363
+ const columns = getDeviceFilterColumns(deviceFilters);
364
+ if (serverFaceted) return columns;
365
+ return columns.map(column => ({
366
+ ...column,
367
+ filterOptions: column.filterOptions?.map(({ count: _count, ...option }) => option),
368
+ }));
369
+ }, [deviceFilters, serverFaceted]);
370
+
371
+ // The column funnels read `deviceFilters`, the SERVER's facets whenever the
372
+ // consumer is server-narrowed, rather than the rows in hand.
373
+ //
374
+ // They used to be derived from the `devices` prop, and in server mode that is
375
+ // the narrowed page: picking one customer left the funnel holding only that
376
+ // customer, so the second one became unpickable and the filter closed behind
377
+ // the first click. In client mode nothing changes, `deviceFilters` IS the
378
+ // row-derived object there, and those rows are the unfiltered prop list.
379
+ //
380
+ // Same source as the mobile FilterModal below, so the two can no longer offer
381
+ // different things.
382
+ const statusFilterOptions = useMemo(() => {
383
+ const options = filterColumns.find(column => column.key === 'status')?.filterOptions ?? [];
384
+ // ARCHIVED last: it is a state a device leaves the fleet in, not one anybody
385
+ // filters by first.
386
+ return [...options].sort((a, b) => {
387
+ if (a.value === DEVICE_STATUS.ARCHIVED) return 1;
388
+ if (b.value === DEVICE_STATUS.ARCHIVED) return -1;
389
+ return 0;
390
+ });
391
+ }, [filterColumns]);
392
+
393
+ // The funnel's VALUE is the customer id, that is what a server-narrowed
394
+ // consumer puts into `organizationIds`, and a name sent there matches nothing.
395
+ // `getDeviceFilterColumns` keys these by id and labels them by name, and the
396
+ // client-side matcher below compares the same id, so the two stay in step.
397
+ const orgFilterOptions = useMemo(
398
+ () => filterColumns.find(column => column.key === 'organization')?.filterOptions ?? [],
399
+ [filterColumns],
400
+ );
401
+
402
+ // Adapter: useTagFilterModal expects a single `setParams({ statuses, osTypes, organizationIds, tags })`
403
+ // call. We split it back into our local state.
404
+ const handleSetParams = useCallback(
405
+ (params: Record<string, string[]>) => {
406
+ setColumnFilters([
407
+ ...(params.statuses?.length ? [{ id: 'status', value: params.statuses }] : []),
408
+ ...(params.osTypes?.length ? [{ id: 'os', value: params.osTypes }] : []),
409
+ ...(params.organizationIds?.length ? [{ id: 'organization', value: params.organizationIds }] : []),
410
+ ]);
411
+ setSelectedTags(params.tags ?? []);
412
+ },
413
+ [setColumnFilters, setSelectedTags],
414
+ );
415
+
416
+ const {
417
+ isOpen: tagsModalOpen,
418
+ open: openTagsModal,
419
+ close: closeTagsModal,
420
+ isMdUp,
421
+ filterGroups,
422
+ tagFilterKeys,
423
+ handleFilterChange: handleModalFilterChange,
424
+ handleTagsChange: handleModalTagsChange,
425
+ } = useTagFilterModal({
426
+ tags: selectedTags,
427
+ deviceFilters,
428
+ columns: filterColumns,
429
+ setParams: handleSetParams,
430
+ });
431
+
432
+ const tagOptions = useMemo(() => selectedTags.map(t => ({ label: t, value: t })), [selectedTags]);
433
+
434
+ // Map column filters → `{ status, os, organization }` shape that FilterModal expects on mobile.
435
+ const tableFilters = useMemo(
436
+ () => ({
437
+ status: (columnFilters.find(f => f.id === 'status')?.value as string[]) ?? [],
438
+ os: (columnFilters.find(f => f.id === 'os')?.value as string[]) ?? [],
439
+ organization: (columnFilters.find(f => f.id === 'organization')?.value as string[]) ?? [],
440
+ }),
441
+ [columnFilters],
442
+ );
443
+
444
+ const handleTagRemove = useCallback(
445
+ (value: string) => {
446
+ setSelectedTags(selectedTags.filter(t => t !== value));
447
+ },
448
+ [selectedTags, setSelectedTags],
449
+ );
450
+
451
+ const handleClearAll = useCallback(() => {
452
+ setSearchTerm('');
453
+ setSelectedTags([]);
454
+ }, [setSearchTerm, setSelectedTags]);
455
+
456
+ const handleTagSubmit = useCallback(
457
+ (value: string) => {
458
+ const trimmed = value.trim();
459
+ if (!trimmed) return;
460
+ if (!selectedTags.includes(trimmed)) setSelectedTags([...selectedTags, trimmed]);
461
+ setSearchTerm('');
462
+ },
463
+ [selectedTags, setSelectedTags, setSearchTerm],
464
+ );
465
+
466
+ const columns = useMemo<ColumnDef<T>[]>(
467
+ () => [
468
+ {
469
+ id: 'device',
470
+ accessorKey: 'device',
471
+ header: 'DEVICE',
472
+ cell: ({ row }: { row: Row<T> }) => {
473
+ const device = row.original;
474
+ const lastSeen = device.last_seen || device.lastSeen;
475
+ return (
476
+ <div className="flex h-20 items-center gap-3">
477
+ <DeviceTypeTile type={device.type} />
478
+ <div className="flex min-w-0 flex-col">
479
+ <TruncateText>{getDeviceName(device)}</TruncateText>
480
+ <TruncateText variant="h6" tone="secondary">
481
+ {formatLastOnline(lastSeen)}
482
+ </TruncateText>
483
+ </div>
484
+ </div>
485
+ );
486
+ },
487
+ enableSorting: false,
488
+ },
489
+ {
490
+ id: 'organization',
491
+ accessorKey: 'organization',
492
+ header: 'CUSTOMER',
493
+ cell: ({ row }: { row: Row<T> }) => <CustomerCell device={row.original} />,
494
+ enableSorting: false,
495
+ meta: {
496
+ width: 'w-[320px]',
497
+ hideAt: 'lg',
498
+ filter: { options: orgFilterOptions, placement: 'bottom-end' },
499
+ },
500
+ },
501
+ {
502
+ id: 'os',
503
+ accessorKey: 'os',
504
+ header: 'OS',
505
+ cell: ({ row }: { row: Row<T> }) => (
506
+ <OSTypeBadge osType={row.original.osType} iconSize="w-4 h-4 content-md:w-6 content-md:h-6" />
507
+ ),
508
+ enableSorting: false,
509
+ meta: {
510
+ width: 'w-[200px] content-md:w-1/6',
511
+ hideAt: 'md',
512
+ },
513
+ },
514
+ {
515
+ id: 'status',
516
+ accessorKey: 'status',
517
+ header: 'STATUS',
518
+ cell: ({ row }: { row: Row<T> }) => {
519
+ const config = getDeviceStatusConfig(row.original.status);
520
+ return <Tag label={config.label} variant={config.variant} className="w-min" />;
521
+ },
522
+ enableSorting: false,
523
+ meta: {
524
+ width: 'w-auto shrink-0 content-md:w-[160px]',
525
+ filter: { options: statusFilterOptions },
526
+ },
527
+ },
528
+ {
529
+ id: 'actions',
530
+ cell: ({ row }: { row: Row<T> }) => {
531
+ const device = row.original;
532
+ const disabledReason = isDeviceDisabled?.(device);
533
+
534
+ if (disabledReason) {
535
+ return (
536
+ <div data-no-row-click className="pointer-events-auto flex w-full items-center justify-end gap-2">
537
+ <span className="whitespace-pre-line text-right text-ods-text-secondary text-h6 content-max-md:hidden">
538
+ {disabledReason}
539
+ </span>
540
+ <Button
541
+ variant="outline"
542
+ size="icon"
543
+ leftIcon={<PlusCircleIcon size={24} />}
544
+ className="shrink-0 text-ods-text-secondary"
545
+ disabled
546
+ />
547
+ </div>
548
+ );
549
+ }
550
+
551
+ const key = getDeviceKey(device);
552
+ if (key === undefined) return null;
553
+ // Server mode fills `selectedIds` from the connection's own
554
+ // per-row flag, so "already in?" is answered by the backend rather
555
+ // than by whichever page the client happens to hold.
556
+ const isSelected = selectedIds.has(key);
557
+
558
+ if (activeSubTab === 'selected') {
559
+ return (
560
+ <div data-no-row-click className="pointer-events-auto flex w-full items-center justify-end">
561
+ <Button
562
+ variant="outline"
563
+ size="icon"
564
+ onClick={() => toggleDevice(device)}
565
+ leftIcon={<TrashIcon size={24} />}
566
+ className="text-ods-error hover:opacity-80"
567
+ disabled={disabled}
568
+ />
569
+ </div>
570
+ );
571
+ }
572
+
573
+ return (
574
+ <div data-no-row-click className="pointer-events-auto flex w-full items-center justify-end">
575
+ <Button
576
+ variant="outline"
577
+ size="icon"
578
+ onClick={() => toggleDevice(device)}
579
+ leftIcon={isSelected ? <CheckCircleIcon size={24} /> : <PlusCircleIcon size={24} />}
580
+ // Selected is a STATE, not an affordance: the yellow fill already
581
+ // says "this one is in", so it holds still under the cursor. The
582
+ // hover repeats on both bg and border because `variant="outline"`
583
+ // ships its own `hover:bg-ods-bg-hover` / `hover:border-ods-border-hover`
584
+ //, restating them at the same value is what neutralizes them.
585
+ className={
586
+ isSelected
587
+ ? 'border-ods-accent bg-ods-open-yellow-secondary text-ods-accent hover:border-ods-accent hover:bg-ods-open-yellow-secondary'
588
+ : 'text-ods-text-secondary hover:text-ods-text-primary'
589
+ }
590
+ disabled={disabled}
591
+ />
592
+ </div>
593
+ );
594
+ },
595
+ enableSorting: false,
596
+ meta: { width: 'w-12 content-md:w-auto content-md:min-w-[130px] shrink-0 flex-none', align: 'right' },
597
+ },
598
+ ],
599
+ [
600
+ statusFilterOptions,
601
+ orgFilterOptions,
602
+ isDeviceDisabled,
603
+ getDeviceKey,
604
+ selectedIds,
605
+ activeSubTab,
606
+ toggleDevice,
607
+ disabled,
608
+ ],
609
+ );
610
+
611
+ const visibleColumns = useMemo(() => {
612
+ // Nothing on a criteria row is actionable, membership follows the rule, so
613
+ // an add/remove button there would promise an edit the mode cannot make.
614
+ const hidden = new Set([...(hideColumns ?? []), ...(isCriteria ? ['actions'] : [])]);
615
+ if (hidden.size === 0) return columns;
616
+ return columns.filter(c => !c.id || !hidden.has(c.id));
617
+ }, [columns, hideColumns, isCriteria]);
618
+
619
+ const table = useDataTable<T>({
620
+ data: devicesForTable,
621
+ columns: visibleColumns,
622
+ getRowId: row => String(getDeviceKey(row) ?? row.id),
623
+ enableSorting: false,
624
+ state: { columnFilters },
625
+ onColumnFiltersChange: handleTableColumnFiltersChange,
626
+ });
627
+
628
+ const selectedCount = server ? server.selectedCount : selectedIds.size;
629
+
630
+ // Per-row className whose value differs by selection state. DataTableRow is
631
+ // React.memo'd on `className`, so only rows whose selection actually flipped
632
+ // re-render, the rest keep their cached cells (with stable toggleDevice).
633
+ const rowClassName = useCallback(
634
+ (device: T): string => {
635
+ const key = getDeviceKey(device);
636
+ if (key === undefined) return '';
637
+ return selectedIds.has(key) ? 'is-selected' : '';
638
+ },
639
+ [selectedIds, getDeviceKey],
640
+ );
641
+
642
+ const assignTabs: TabItem[] = useMemo(
643
+ () => [
644
+ {
645
+ id: 'available',
646
+ label: 'Available Devices',
647
+ icon: MonitorIcon,
648
+ // The table is rendered outside TabContent now, these `component` slots
649
+ // exist only because TabNavigation/TabItem require them. Render nothing.
650
+ component: () => null,
651
+ },
652
+ {
653
+ id: 'selected',
654
+ // Server mode counts the whole assignment, not the page in hand.
655
+ label: singleSelect ? `Selected Device (${selectedCount})` : `Selected Devices (${selectedCount})`,
656
+ icon: CheckCircleIcon,
657
+ component: () => null,
658
+ },
659
+ ],
660
+ [selectedCount, singleSelect],
661
+ );
662
+
663
+ const availableInfiniteScroll = activeSubTab === 'available' ? infiniteScroll : undefined;
664
+
665
+ // Column headers over nothing are just noise, drop them when the list is
666
+ // empty. Except when the emptiness is the RESULT of narrowing (search, tag
667
+ // chips, column funnels): the funnels live in that header, so tearing it down
668
+ // would strip the only way to undo the filter that emptied the table. While
669
+ // loading, the skeleton rows are the content the header belongs to.
670
+ // The design (460:71435) puts the bulk action where a table normally shows its
671
+ // row count, the right end of the column header, instead of on a line of its
672
+ // own above the table. Null in single-select mode, and on the Selected tab
673
+ // until there is something to remove; the row count takes the slot back then.
674
+ const bulkAction =
675
+ singleSelect || isCriteria ? null : activeSubTab === 'available' ? (
676
+ <button
677
+ type="button"
678
+ onClick={addAllDevices}
679
+ disabled={disabled}
680
+ className="cursor-pointer border-0 bg-transparent text-ods-accent underline text-h6 hover:text-ods-accent-hover disabled:cursor-not-allowed disabled:opacity-50"
681
+ >
682
+ Add All Devices
683
+ </button>
684
+ ) : selectedCount > 0 ? (
685
+ <button
686
+ type="button"
687
+ onClick={removeAllSelected}
688
+ disabled={disabled}
689
+ className="cursor-pointer border-0 bg-transparent text-ods-error underline text-h6 hover:text-ods-error-hover disabled:cursor-not-allowed disabled:opacity-50"
690
+ >
691
+ Remove {server ? (server.totalCount ?? devicesForTable.length) : selectedIds.size} Devices
692
+ </button>
693
+ ) : null;
694
+
695
+ const hasActiveFilter = columnFilters.length > 0 || selectedTags.length > 0 || searchTerm.trim().length > 0;
696
+ // In criteria mode the header's row count is the readout of the rule ("42
697
+ // devices"), so it stays even at zero, that IS the answer the user is after.
698
+ const showHeader = isCriteria || loading || devicesForTable.length > 0 || hasActiveFilter;
699
+
700
+ const deviceTable = (
701
+ <DataTable table={table}>
702
+ {showHeader && (
703
+ <DataTable.Header
704
+ rightSlot={
705
+ bulkAction ?? <DataTable.RowCount itemName="device" totalCount={totalCount ?? server?.totalCount} />
706
+ }
707
+ />
708
+ )}
709
+ <DataTable.Body
710
+ loading={loading}
711
+ skeletonRows={8}
712
+ emptyMessage={
713
+ isCriteria
714
+ ? 'No devices match these criteria'
715
+ : activeSubTab === 'selected'
716
+ ? 'No devices selected'
717
+ : 'No devices found'
718
+ }
719
+ rowClassName={rowClassName}
720
+ />
721
+ {availableInfiniteScroll && (
722
+ <DataTable.InfiniteFooter
723
+ hasNextPage={availableInfiniteScroll.hasNextPage}
724
+ isFetchingNextPage={availableInfiniteScroll.isFetchingNextPage}
725
+ onLoadMore={availableInfiniteScroll.onLoadMore}
726
+ skeletonRows={availableInfiniteScroll.skeletonRows}
727
+ />
728
+ )}
729
+ </DataTable>
730
+ );
731
+
732
+ return (
733
+ // The page-level stack (info bar → mode picker → the picker card) uses
734
+ // `--spacing-system-l`, the token every other stacked page surface in the app
735
+ // separates its blocks with (16px mobile / 24px desktop).
736
+ <div className="flex flex-col gap-[var(--spacing-system-l)]">
737
+ {headerContent}
738
+
739
+ {showSelectionModeRadio && (
740
+ <DeviceSelectionModeRadio value={selectionMode} onChange={onSelectionModeChange} disabled={disabled} />
741
+ )}
742
+
743
+ {/* Criteria mode (design 460:85294) has no card at all: the rule's fields
744
+ and the table it previews sit straight on the page, in the same 24px
745
+ stack as everything above them. There is nothing to frame, no tab
746
+ strip to seat, and no search row to inset with it. */}
747
+ {isCriteria ? (
748
+ <>
749
+ {criteriaContent}
750
+ {deviceTable}
751
+ </>
752
+ ) : (
753
+ /* Design 460:71435 frames the picker as ONE bordered card: the tab strip
754
+ sits flush against the top edge with its own underline doubling as the
755
+ divider, and the search row and table are inset below it.
756
+ Deliberately NOT `overflow-clip` (which the Figma export puts here to
757
+ round the tab strip's corners): the column-filter dropdowns are
758
+ absolutely positioned INSIDE this box rather than portaled, so clipping
759
+ it would cut them off whenever the table is short. The tab strip clips
760
+ its own corners instead: it is the only child that reaches the radius. */
761
+ <div className="flex flex-col rounded-[6px] border border-ods-border bg-ods-bg">
762
+ {!singleSelect && (
763
+ <TabNavigation
764
+ tabs={assignTabs}
765
+ activeTab={activeSubTab}
766
+ onTabChange={handleTabChange}
767
+ className="overflow-clip rounded-t-[6px]"
768
+ />
769
+ )}
770
+
771
+ <div className="flex flex-col gap-[var(--spacing-system-m)] p-[var(--spacing-system-m)]">
772
+ {showToolbar && (
773
+ <DevicesFilterToolbar
774
+ sticky={false}
775
+ searchValue={searchTerm}
776
+ onSearchChange={setSearchTerm}
777
+ tags={tagOptions}
778
+ onTagRemove={handleTagRemove}
779
+ onClearAll={handleClearAll}
780
+ onSubmit={handleTagSubmit}
781
+ onOpenFilterModal={openTagsModal}
782
+ isFilterModalOpen={tagsModalOpen}
783
+ onCloseFilterModal={closeTagsModal}
784
+ filterGroups={filterGroups}
785
+ onFilterChange={handleModalFilterChange}
786
+ currentFilters={isMdUp === false ? tableFilters : undefined}
787
+ tagFilterKeys={tagFilterKeys}
788
+ selectedTags={selectedTags}
789
+ onTagsChange={handleModalTagsChange}
790
+ />
791
+ )}
792
+
793
+ {deviceTable}
794
+ </div>
795
+ </div>
796
+ )}
797
+ </div>
798
+ );
799
+ }