@maxal_studio/kratosjs-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/README.md +44 -0
  2. package/dist/FieldRenderer.d.ts +13 -0
  3. package/dist/FieldRenderer.js +62 -0
  4. package/dist/FormRenderer.d.ts +7 -0
  5. package/dist/FormRenderer.js +78 -0
  6. package/dist/TableRenderer.d.ts +2 -0
  7. package/dist/TableRenderer.js +1 -0
  8. package/dist/api/actionsApi.d.ts +23 -0
  9. package/dist/api/actionsApi.js +46 -0
  10. package/dist/api/authenticatedFetch.d.ts +8 -0
  11. package/dist/api/authenticatedFetch.js +31 -0
  12. package/dist/api/exportApi.d.ts +18 -0
  13. package/dist/api/exportApi.js +50 -0
  14. package/dist/api/http.d.ts +24 -0
  15. package/dist/api/http.js +52 -0
  16. package/dist/api/resourceApi.d.ts +37 -0
  17. package/dist/api/resourceApi.js +52 -0
  18. package/dist/api/tableApi.d.ts +83 -0
  19. package/dist/api/tableApi.js +51 -0
  20. package/dist/api/urls.d.ts +19 -0
  21. package/dist/api/urls.js +46 -0
  22. package/dist/app.d.ts +101 -0
  23. package/dist/app.js +89 -0
  24. package/dist/auth/AuthContext.d.ts +22 -0
  25. package/dist/auth/AuthContext.js +147 -0
  26. package/dist/auth/LoginPage.d.ts +10 -0
  27. package/dist/auth/LoginPage.js +179 -0
  28. package/dist/auth/ProtectedRoute.d.ts +12 -0
  29. package/dist/auth/ProtectedRoute.js +22 -0
  30. package/dist/auth/authApiClient.d.ts +24 -0
  31. package/dist/auth/authApiClient.js +95 -0
  32. package/dist/auth/types.d.ts +103 -0
  33. package/dist/auth/types.js +1 -0
  34. package/dist/components/ActionFormModal.d.ts +22 -0
  35. package/dist/components/ActionFormModal.js +8 -0
  36. package/dist/components/AdminPanel.d.ts +11 -0
  37. package/dist/components/AdminPanel.js +194 -0
  38. package/dist/components/Checkbox.d.ts +10 -0
  39. package/dist/components/Checkbox.js +8 -0
  40. package/dist/components/CheckboxField.d.ts +7 -0
  41. package/dist/components/CheckboxField.js +26 -0
  42. package/dist/components/ColorPickerField.d.ts +7 -0
  43. package/dist/components/ColorPickerField.js +26 -0
  44. package/dist/components/DateTimePickerField.d.ts +7 -0
  45. package/dist/components/DateTimePickerField.js +64 -0
  46. package/dist/components/FileUploadField.d.ts +9 -0
  47. package/dist/components/FileUploadField.js +478 -0
  48. package/dist/components/GlobalSearch.d.ts +22 -0
  49. package/dist/components/GlobalSearch.js +181 -0
  50. package/dist/components/GroupField.d.ts +7 -0
  51. package/dist/components/GroupField.js +23 -0
  52. package/dist/components/HiddenField.d.ts +3 -0
  53. package/dist/components/HiddenField.js +10 -0
  54. package/dist/components/ModalBreadcrumb.d.ts +5 -0
  55. package/dist/components/ModalBreadcrumb.js +33 -0
  56. package/dist/components/ModalDrawer.d.ts +15 -0
  57. package/dist/components/ModalDrawer.js +40 -0
  58. package/dist/components/RadioField.d.ts +7 -0
  59. package/dist/components/RadioField.js +26 -0
  60. package/dist/components/RepeaterField.d.ts +3 -0
  61. package/dist/components/RepeaterField.js +191 -0
  62. package/dist/components/ResourceModalRenderer.d.ts +10 -0
  63. package/dist/components/ResourceModalRenderer.js +80 -0
  64. package/dist/components/RichEditorField.d.ts +3 -0
  65. package/dist/components/RichEditorField.js +655 -0
  66. package/dist/components/SectionField.d.ts +9 -0
  67. package/dist/components/SectionField.js +111 -0
  68. package/dist/components/SelectField.d.ts +8 -0
  69. package/dist/components/SelectField.js +523 -0
  70. package/dist/components/TabsField.d.ts +10 -0
  71. package/dist/components/TabsField.js +214 -0
  72. package/dist/components/TagsInputField.d.ts +7 -0
  73. package/dist/components/TagsInputField.js +172 -0
  74. package/dist/components/TextInputField.d.ts +7 -0
  75. package/dist/components/TextInputField.js +44 -0
  76. package/dist/components/TextareaField.d.ts +7 -0
  77. package/dist/components/TextareaField.js +31 -0
  78. package/dist/components/ToggleField.d.ts +7 -0
  79. package/dist/components/ToggleField.js +57 -0
  80. package/dist/components/ViewModal.d.ts +25 -0
  81. package/dist/components/ViewModal.js +159 -0
  82. package/dist/components/blocks/BlockRenderer.d.ts +7 -0
  83. package/dist/components/blocks/BlockRenderer.js +36 -0
  84. package/dist/components/blocks/FormBlockRenderer.d.ts +6 -0
  85. package/dist/components/blocks/FormBlockRenderer.js +110 -0
  86. package/dist/components/blocks/TableBlockRenderer.d.ts +6 -0
  87. package/dist/components/blocks/TableBlockRenderer.js +12 -0
  88. package/dist/components/blocks/TabsBlockRenderer.d.ts +7 -0
  89. package/dist/components/blocks/TabsBlockRenderer.js +11 -0
  90. package/dist/components/blocks/WidgetBlockRenderer.d.ts +6 -0
  91. package/dist/components/blocks/WidgetBlockRenderer.js +11 -0
  92. package/dist/components/columns/CheckboxColumnComponent.d.ts +6 -0
  93. package/dist/components/columns/CheckboxColumnComponent.js +21 -0
  94. package/dist/components/columns/ColorColumnComponent.d.ts +3 -0
  95. package/dist/components/columns/ColorColumnComponent.js +11 -0
  96. package/dist/components/columns/DeeplinkWrapper.d.ts +15 -0
  97. package/dist/components/columns/DeeplinkWrapper.js +85 -0
  98. package/dist/components/columns/IconColumnComponent.d.ts +3 -0
  99. package/dist/components/columns/IconColumnComponent.js +52 -0
  100. package/dist/components/columns/ImageColumnComponent.d.ts +3 -0
  101. package/dist/components/columns/ImageColumnComponent.js +98 -0
  102. package/dist/components/columns/MediaColumnComponent.d.ts +3 -0
  103. package/dist/components/columns/MediaColumnComponent.js +160 -0
  104. package/dist/components/columns/SelectColumnComponent.d.ts +6 -0
  105. package/dist/components/columns/SelectColumnComponent.js +26 -0
  106. package/dist/components/columns/TagsColumnComponent.d.ts +3 -0
  107. package/dist/components/columns/TagsColumnComponent.js +18 -0
  108. package/dist/components/columns/TextColumnComponent.d.ts +11 -0
  109. package/dist/components/columns/TextColumnComponent.js +107 -0
  110. package/dist/components/columns/TextInputColumnComponent.d.ts +6 -0
  111. package/dist/components/columns/TextInputColumnComponent.js +18 -0
  112. package/dist/components/columns/ToggleColumnComponent.d.ts +6 -0
  113. package/dist/components/columns/ToggleColumnComponent.js +25 -0
  114. package/dist/components/columns/VideoColumnComponent.d.ts +3 -0
  115. package/dist/components/columns/VideoColumnComponent.js +125 -0
  116. package/dist/components/columns/ViewColumnComponent.d.ts +3 -0
  117. package/dist/components/columns/ViewColumnComponent.js +7 -0
  118. package/dist/components/errors/ErrorBoundary.d.ts +23 -0
  119. package/dist/components/errors/ErrorBoundary.js +33 -0
  120. package/dist/components/filters/CustomFilterComponent.d.ts +10 -0
  121. package/dist/components/filters/CustomFilterComponent.js +33 -0
  122. package/dist/components/filters/DateFilterComponent.d.ts +15 -0
  123. package/dist/components/filters/DateFilterComponent.js +132 -0
  124. package/dist/components/filters/QueryBuilderFilterComponent.d.ts +11 -0
  125. package/dist/components/filters/QueryBuilderFilterComponent.js +200 -0
  126. package/dist/components/layout/Header.d.ts +10 -0
  127. package/dist/components/layout/Header.js +70 -0
  128. package/dist/components/layout/PanelBrandMark.d.ts +8 -0
  129. package/dist/components/layout/PanelBrandMark.js +28 -0
  130. package/dist/components/layout/Sidebar.d.ts +35 -0
  131. package/dist/components/layout/Sidebar.js +125 -0
  132. package/dist/components/modals/RelationCreateModal.d.ts +19 -0
  133. package/dist/components/modals/RelationCreateModal.js +57 -0
  134. package/dist/components/modals/ResourceFormModal.d.ts +37 -0
  135. package/dist/components/modals/ResourceFormModal.js +44 -0
  136. package/dist/components/modals/useResourceForm.d.ts +40 -0
  137. package/dist/components/modals/useResourceForm.js +138 -0
  138. package/dist/components/modals/view/RecordActions.d.ts +17 -0
  139. package/dist/components/modals/view/RecordActions.js +16 -0
  140. package/dist/components/modals/view/RecordDetails.d.ts +13 -0
  141. package/dist/components/modals/view/RecordDetails.js +29 -0
  142. package/dist/components/modals/view/RelationPanel.d.ts +18 -0
  143. package/dist/components/modals/view/RelationPanel.js +16 -0
  144. package/dist/components/modals/view/RelationTabs.d.ts +32 -0
  145. package/dist/components/modals/view/RelationTabs.js +42 -0
  146. package/dist/components/modals/view/useRecordView.d.ts +18 -0
  147. package/dist/components/modals/view/useRecordView.js +114 -0
  148. package/dist/components/pages/PageRenderer.d.ts +6 -0
  149. package/dist/components/pages/PageRenderer.js +107 -0
  150. package/dist/components/table/ColumnTogglePopup.d.ts +11 -0
  151. package/dist/components/table/ColumnTogglePopup.js +16 -0
  152. package/dist/components/table/GridCard.d.ts +21 -0
  153. package/dist/components/table/GridCard.js +30 -0
  154. package/dist/components/table/GridView.d.ts +23 -0
  155. package/dist/components/table/GridView.js +49 -0
  156. package/dist/components/table/LayoutToggle.d.ts +7 -0
  157. package/dist/components/table/LayoutToggle.js +9 -0
  158. package/dist/components/table/TableActionsDropdown.d.ts +13 -0
  159. package/dist/components/table/TableActionsDropdown.js +46 -0
  160. package/dist/components/table/TableBulkActions.d.ts +11 -0
  161. package/dist/components/table/TableBulkActions.js +21 -0
  162. package/dist/components/table/TableHeader.d.ts +14 -0
  163. package/dist/components/table/TableHeader.js +23 -0
  164. package/dist/components/table/TablePagination.d.ts +13 -0
  165. package/dist/components/table/TablePagination.js +55 -0
  166. package/dist/components/table/TableRow.d.ts +21 -0
  167. package/dist/components/table/TableRow.js +32 -0
  168. package/dist/components/table/TableSearchBar.d.ts +11 -0
  169. package/dist/components/table/TableSearchBar.js +12 -0
  170. package/dist/components/table/TableTabs.d.ts +14 -0
  171. package/dist/components/table/TableTabs.js +8 -0
  172. package/dist/components/ui/Badge.d.ts +6 -0
  173. package/dist/components/ui/Badge.js +12 -0
  174. package/dist/components/ui/Button.d.ts +22 -0
  175. package/dist/components/ui/Button.js +22 -0
  176. package/dist/components/ui/Card.d.ts +7 -0
  177. package/dist/components/ui/Card.js +5 -0
  178. package/dist/components/ui/ConfirmDialog.d.ts +19 -0
  179. package/dist/components/ui/ConfirmDialog.js +45 -0
  180. package/dist/components/ui/EmptyState.d.ts +9 -0
  181. package/dist/components/ui/EmptyState.js +6 -0
  182. package/dist/components/ui/ErrorAlert.d.ts +7 -0
  183. package/dist/components/ui/ErrorAlert.js +9 -0
  184. package/dist/components/ui/Input.d.ts +11 -0
  185. package/dist/components/ui/Input.js +10 -0
  186. package/dist/components/ui/Label.d.ts +5 -0
  187. package/dist/components/ui/Label.js +5 -0
  188. package/dist/components/ui/PillButton.d.ts +14 -0
  189. package/dist/components/ui/PillButton.js +19 -0
  190. package/dist/components/ui/Select.d.ts +7 -0
  191. package/dist/components/ui/Select.js +7 -0
  192. package/dist/components/ui/Spinner.d.ts +8 -0
  193. package/dist/components/ui/Spinner.js +14 -0
  194. package/dist/components/ui/Toast.d.ts +21 -0
  195. package/dist/components/ui/Toast.js +47 -0
  196. package/dist/components/ui/index.d.ts +24 -0
  197. package/dist/components/ui/index.js +12 -0
  198. package/dist/components/utils/HintDisplay.d.ts +11 -0
  199. package/dist/components/utils/HintDisplay.js +12 -0
  200. package/dist/components/utils/Icon.d.ts +22 -0
  201. package/dist/components/utils/Icon.js +22 -0
  202. package/dist/components/utils/MediaPreviewModal.d.ts +14 -0
  203. package/dist/components/utils/MediaPreviewModal.js +32 -0
  204. package/dist/components/utils/ViewFieldWrapper.d.ts +11 -0
  205. package/dist/components/utils/ViewFieldWrapper.js +9 -0
  206. package/dist/components/utils/layoutHelpers.d.ts +19 -0
  207. package/dist/components/utils/layoutHelpers.js +257 -0
  208. package/dist/components/widgets/ChartWidget.d.ts +16 -0
  209. package/dist/components/widgets/ChartWidget.js +192 -0
  210. package/dist/components/widgets/StatsWidget.d.ts +16 -0
  211. package/dist/components/widgets/StatsWidget.js +39 -0
  212. package/dist/components/widgets/WidgetRenderer.d.ts +10 -0
  213. package/dist/components/widgets/WidgetRenderer.js +50 -0
  214. package/dist/components/widgets/WidgetShell.d.ts +9 -0
  215. package/dist/components/widgets/WidgetShell.js +7 -0
  216. package/dist/contexts/AuthChallengeRegistryContext.d.ts +15 -0
  217. package/dist/contexts/AuthChallengeRegistryContext.js +15 -0
  218. package/dist/contexts/BlockRegistryContext.d.ts +18 -0
  219. package/dist/contexts/BlockRegistryContext.js +8 -0
  220. package/dist/contexts/ColumnRegistryContext.d.ts +8 -0
  221. package/dist/contexts/ColumnRegistryContext.js +30 -0
  222. package/dist/contexts/FieldRegistryContext.d.ts +13 -0
  223. package/dist/contexts/FieldRegistryContext.js +46 -0
  224. package/dist/contexts/PanelMetadataContext.d.ts +26 -0
  225. package/dist/contexts/PanelMetadataContext.js +26 -0
  226. package/dist/contexts/PanelProviders.d.ts +27 -0
  227. package/dist/contexts/PanelProviders.js +24 -0
  228. package/dist/contexts/ResourceModalContext.d.ts +26 -0
  229. package/dist/contexts/ResourceModalContext.js +76 -0
  230. package/dist/contexts/SlotRegistryContext.d.ts +19 -0
  231. package/dist/contexts/SlotRegistryContext.js +24 -0
  232. package/dist/contexts/TableRefreshContext.d.ts +10 -0
  233. package/dist/contexts/TableRefreshContext.js +30 -0
  234. package/dist/contexts/WidgetRegistryContext.d.ts +17 -0
  235. package/dist/contexts/WidgetRegistryContext.js +14 -0
  236. package/dist/contexts/createRegistryContext.d.ts +19 -0
  237. package/dist/contexts/createRegistryContext.js +20 -0
  238. package/dist/hooks/useAfterStateUpdated.d.ts +6 -0
  239. package/dist/hooks/useAfterStateUpdated.js +62 -0
  240. package/dist/hooks/useValidation.d.ts +26 -0
  241. package/dist/hooks/useValidation.js +76 -0
  242. package/dist/i18n/I18nProvider.d.ts +27 -0
  243. package/dist/i18n/I18nProvider.js +101 -0
  244. package/dist/i18n/LocaleSwitcher.d.ts +10 -0
  245. package/dist/i18n/LocaleSwitcher.js +30 -0
  246. package/dist/i18n/activeLocale.d.ts +11 -0
  247. package/dist/i18n/activeLocale.js +34 -0
  248. package/dist/i18n/buildClientI18n.d.ts +28 -0
  249. package/dist/i18n/buildClientI18n.js +67 -0
  250. package/dist/i18n/index.d.ts +11 -0
  251. package/dist/i18n/index.js +9 -0
  252. package/dist/i18n/locales/core/en.d.ts +225 -0
  253. package/dist/i18n/locales/core/en.js +252 -0
  254. package/dist/i18n/locales/core/index.d.ts +2 -0
  255. package/dist/i18n/locales/core/index.js +4 -0
  256. package/dist/i18n/locales/core/sq.d.ts +253 -0
  257. package/dist/i18n/locales/core/sq.js +255 -0
  258. package/dist/i18n/useFormatter.d.ts +18 -0
  259. package/dist/i18n/useFormatter.js +37 -0
  260. package/dist/i18n/useLocale.d.ts +11 -0
  261. package/dist/i18n/useLocale.js +11 -0
  262. package/dist/i18n/useTranslation.d.ts +12 -0
  263. package/dist/i18n/useTranslation.js +12 -0
  264. package/dist/index.d.ts +106 -0
  265. package/dist/index.js +101 -0
  266. package/dist/pages/ResourceListPage.d.ts +8 -0
  267. package/dist/pages/ResourceListPage.js +139 -0
  268. package/dist/plugin.d.ts +79 -0
  269. package/dist/plugin.js +34 -0
  270. package/dist/runtime/conditions.d.ts +35 -0
  271. package/dist/runtime/conditions.js +97 -0
  272. package/dist/runtime/formTraversal.d.ts +25 -0
  273. package/dist/runtime/formTraversal.js +37 -0
  274. package/dist/runtime/serializedFunctions.d.ts +41 -0
  275. package/dist/runtime/serializedFunctions.js +264 -0
  276. package/dist/slots/Slot.d.ts +24 -0
  277. package/dist/slots/Slot.js +29 -0
  278. package/dist/slots/SlotCluster.d.ts +22 -0
  279. package/dist/slots/SlotCluster.js +49 -0
  280. package/dist/slots/index.d.ts +7 -0
  281. package/dist/slots/index.js +4 -0
  282. package/dist/slots/mergeSlots.d.ts +18 -0
  283. package/dist/slots/mergeSlots.js +35 -0
  284. package/dist/slots/types.d.ts +87 -0
  285. package/dist/slots/types.js +30 -0
  286. package/dist/styles.css +1 -0
  287. package/dist/table/TableContext.d.ts +36 -0
  288. package/dist/table/TableContext.js +13 -0
  289. package/dist/table/TableRenderer.d.ts +29 -0
  290. package/dist/table/TableRenderer.js +159 -0
  291. package/dist/table/components/FiltersPanel.d.ts +11 -0
  292. package/dist/table/components/FiltersPanel.js +52 -0
  293. package/dist/table/components/TableToolbar.d.ts +28 -0
  294. package/dist/table/components/TableToolbar.js +27 -0
  295. package/dist/table/components/TableToolbarButton.d.ts +6 -0
  296. package/dist/table/components/TableToolbarButton.js +9 -0
  297. package/dist/table/components/TableView.d.ts +12 -0
  298. package/dist/table/components/TableView.js +21 -0
  299. package/dist/table/defaultRowActions.d.ts +21 -0
  300. package/dist/table/defaultRowActions.js +37 -0
  301. package/dist/table/hooks/useColumnVisibility.d.ts +13 -0
  302. package/dist/table/hooks/useColumnVisibility.js +59 -0
  303. package/dist/table/hooks/useEditableRows.d.ts +22 -0
  304. package/dist/table/hooks/useEditableRows.js +63 -0
  305. package/dist/table/hooks/useTableActions.d.ts +54 -0
  306. package/dist/table/hooks/useTableActions.js +313 -0
  307. package/dist/table/hooks/useTableData.d.ts +28 -0
  308. package/dist/table/hooks/useTableData.js +63 -0
  309. package/dist/table/hooks/useTableLayout.d.ts +12 -0
  310. package/dist/table/hooks/useTableLayout.js +31 -0
  311. package/dist/table/hooks/useTableQuery.d.ts +29 -0
  312. package/dist/table/hooks/useTableQuery.js +135 -0
  313. package/dist/types/index.d.ts +224 -0
  314. package/dist/types/index.js +6 -0
  315. package/dist/utils/classNames.d.ts +7 -0
  316. package/dist/utils/classNames.js +9 -0
  317. package/dist/utils/columnMediaDimensions.d.ts +13 -0
  318. package/dist/utils/columnMediaDimensions.js +29 -0
  319. package/dist/utils/columnVisibilityStorage.d.ts +22 -0
  320. package/dist/utils/columnVisibilityStorage.js +56 -0
  321. package/dist/utils/fieldErrors.d.ts +13 -0
  322. package/dist/utils/fieldErrors.js +25 -0
  323. package/dist/utils/formatValue.d.ts +28 -0
  324. package/dist/utils/formatValue.js +109 -0
  325. package/dist/utils/layoutStorage.d.ts +23 -0
  326. package/dist/utils/layoutStorage.js +53 -0
  327. package/dist/utils/redirectHandler.d.ts +7 -0
  328. package/dist/utils/redirectHandler.js +25 -0
  329. package/dist/utils/tableFormatters.d.ts +14 -0
  330. package/dist/utils/tableFormatters.js +93 -0
  331. package/dist/utils/widgetVisibilityStorage.d.ts +11 -0
  332. package/dist/utils/widgetVisibilityStorage.js +39 -0
  333. package/package.json +101 -0
  334. package/src/FieldRenderer.test.tsx +44 -0
  335. package/src/FieldRenderer.tsx +104 -0
  336. package/src/FormRenderer.containers.test.tsx +121 -0
  337. package/src/FormRenderer.test.tsx +174 -0
  338. package/src/FormRenderer.tsx +140 -0
  339. package/src/TableRenderer.tsx +2 -0
  340. package/src/api/actionsApi.ts +76 -0
  341. package/src/api/authenticatedFetch.ts +40 -0
  342. package/src/api/exportApi.ts +66 -0
  343. package/src/api/http.test.ts +58 -0
  344. package/src/api/http.ts +68 -0
  345. package/src/api/resourceApi.ts +88 -0
  346. package/src/api/tableApi.test.ts +108 -0
  347. package/src/api/tableApi.ts +107 -0
  348. package/src/api/urls.ts +50 -0
  349. package/src/app.test.tsx +67 -0
  350. package/src/app.tsx +181 -0
  351. package/src/auth/AuthContext.tsx +188 -0
  352. package/src/auth/LoginPage.tsx +380 -0
  353. package/src/auth/ProtectedRoute.tsx +39 -0
  354. package/src/auth/authApiClient.ts +109 -0
  355. package/src/auth/authFlow.test.tsx +168 -0
  356. package/src/auth/types.ts +104 -0
  357. package/src/components/ActionFormModal.tsx +45 -0
  358. package/src/components/AdminPanel.tsx +368 -0
  359. package/src/components/Checkbox.tsx +59 -0
  360. package/src/components/CheckboxField.tsx +88 -0
  361. package/src/components/ColorPickerField.tsx +93 -0
  362. package/src/components/DateTimePickerField.tsx +112 -0
  363. package/src/components/FileUploadField.tsx +841 -0
  364. package/src/components/GlobalSearch.tsx +436 -0
  365. package/src/components/GroupField.tsx +85 -0
  366. package/src/components/HiddenField.tsx +14 -0
  367. package/src/components/ModalBreadcrumb.tsx +74 -0
  368. package/src/components/ModalDrawer.tsx +137 -0
  369. package/src/components/RadioField.tsx +80 -0
  370. package/src/components/RepeaterField.tsx +546 -0
  371. package/src/components/ResourceModalRenderer.tsx +144 -0
  372. package/src/components/RichEditorField.tsx +942 -0
  373. package/src/components/SectionField.tsx +242 -0
  374. package/src/components/SelectField.tsx +843 -0
  375. package/src/components/TabsField.test.tsx +151 -0
  376. package/src/components/TabsField.tsx +386 -0
  377. package/src/components/TagsInputField.tsx +411 -0
  378. package/src/components/TextInputField.tsx +91 -0
  379. package/src/components/TextareaField.tsx +110 -0
  380. package/src/components/ToggleField.tsx +126 -0
  381. package/src/components/ViewModal.tsx +353 -0
  382. package/src/components/blocks/BlockRenderer.tsx +56 -0
  383. package/src/components/blocks/FormBlockRenderer.tsx +160 -0
  384. package/src/components/blocks/TableBlockRenderer.tsx +33 -0
  385. package/src/components/blocks/TabsBlockRenderer.tsx +49 -0
  386. package/src/components/blocks/WidgetBlockRenderer.tsx +19 -0
  387. package/src/components/columns/CheckboxColumnComponent.tsx +38 -0
  388. package/src/components/columns/ColorColumnComponent.tsx +23 -0
  389. package/src/components/columns/CustomColumn.test.tsx +55 -0
  390. package/src/components/columns/DeeplinkWrapper.tsx +103 -0
  391. package/src/components/columns/IconColumnComponent.tsx +55 -0
  392. package/src/components/columns/ImageColumnComponent.tsx +220 -0
  393. package/src/components/columns/MediaColumnComponent.tsx +294 -0
  394. package/src/components/columns/SelectColumnComponent.tsx +49 -0
  395. package/src/components/columns/TagsColumnComponent.tsx +46 -0
  396. package/src/components/columns/TextColumnComponent.tsx +191 -0
  397. package/src/components/columns/TextInputColumnComponent.tsx +35 -0
  398. package/src/components/columns/ToggleColumnComponent.tsx +56 -0
  399. package/src/components/columns/VideoColumnComponent.tsx +236 -0
  400. package/src/components/columns/ViewColumnComponent.tsx +9 -0
  401. package/src/components/errors/ErrorBoundary.tsx +58 -0
  402. package/src/components/filters/CustomFilterComponent.tsx +130 -0
  403. package/src/components/filters/DateFilterComponent.tsx +272 -0
  404. package/src/components/filters/QueryBuilderFilterComponent.tsx +502 -0
  405. package/src/components/layout/Header.tsx +212 -0
  406. package/src/components/layout/PanelBrandMark.tsx +61 -0
  407. package/src/components/layout/Sidebar.tsx +283 -0
  408. package/src/components/modals/RelationCreateModal.tsx +107 -0
  409. package/src/components/modals/ResourceFormModal.test.tsx +119 -0
  410. package/src/components/modals/ResourceFormModal.tsx +128 -0
  411. package/src/components/modals/useResourceForm.ts +207 -0
  412. package/src/components/modals/view/RecordActions.tsx +69 -0
  413. package/src/components/modals/view/RecordDetails.tsx +60 -0
  414. package/src/components/modals/view/RelationPanel.tsx +76 -0
  415. package/src/components/modals/view/RelationTabs.tsx +145 -0
  416. package/src/components/modals/view/useRecordView.ts +134 -0
  417. package/src/components/pages/PageRenderer.tsx +173 -0
  418. package/src/components/table/ColumnTogglePopup.tsx +85 -0
  419. package/src/components/table/GridCard.tsx +155 -0
  420. package/src/components/table/GridView.tsx +138 -0
  421. package/src/components/table/LayoutToggle.tsx +24 -0
  422. package/src/components/table/TableActionsDropdown.tsx +114 -0
  423. package/src/components/table/TableBulkActions.tsx +65 -0
  424. package/src/components/table/TableHeader.tsx +96 -0
  425. package/src/components/table/TablePagination.tsx +169 -0
  426. package/src/components/table/TableRow.tsx +155 -0
  427. package/src/components/table/TableSearchBar.tsx +66 -0
  428. package/src/components/table/TableTabs.tsx +49 -0
  429. package/src/components/ui/Badge.tsx +30 -0
  430. package/src/components/ui/Button.test.tsx +78 -0
  431. package/src/components/ui/Button.tsx +102 -0
  432. package/src/components/ui/Card.tsx +23 -0
  433. package/src/components/ui/ConfirmDialog.tsx +112 -0
  434. package/src/components/ui/EmptyState.tsx +24 -0
  435. package/src/components/ui/ErrorAlert.tsx +37 -0
  436. package/src/components/ui/Input.tsx +48 -0
  437. package/src/components/ui/Label.tsx +15 -0
  438. package/src/components/ui/PillButton.tsx +72 -0
  439. package/src/components/ui/Select.tsx +33 -0
  440. package/src/components/ui/Spinner.tsx +39 -0
  441. package/src/components/ui/Toast.tsx +105 -0
  442. package/src/components/ui/index.ts +24 -0
  443. package/src/components/utils/HintDisplay.tsx +26 -0
  444. package/src/components/utils/Icon.tsx +36 -0
  445. package/src/components/utils/MediaPreviewModal.tsx +114 -0
  446. package/src/components/utils/ViewFieldWrapper.tsx +23 -0
  447. package/src/components/utils/layoutHelpers.ts +267 -0
  448. package/src/components/widgets/ChartWidget.tsx +247 -0
  449. package/src/components/widgets/StatsWidget.tsx +72 -0
  450. package/src/components/widgets/WidgetRenderer.tsx +108 -0
  451. package/src/components/widgets/WidgetShell.tsx +37 -0
  452. package/src/contexts/AuthChallengeRegistryContext.tsx +29 -0
  453. package/src/contexts/BlockRegistryContext.tsx +28 -0
  454. package/src/contexts/ColumnRegistryContext.tsx +38 -0
  455. package/src/contexts/FieldRegistryContext.tsx +56 -0
  456. package/src/contexts/PanelMetadataContext.tsx +60 -0
  457. package/src/contexts/PanelProviders.tsx +85 -0
  458. package/src/contexts/ResourceModalContext.tsx +137 -0
  459. package/src/contexts/SlotRegistryContext.tsx +35 -0
  460. package/src/contexts/TableRefreshContext.tsx +44 -0
  461. package/src/contexts/WidgetRegistryContext.tsx +34 -0
  462. package/src/contexts/createRegistryContext.tsx +29 -0
  463. package/src/hooks/useAfterStateUpdated.ts +70 -0
  464. package/src/hooks/useValidation.test.ts +59 -0
  465. package/src/hooks/useValidation.ts +95 -0
  466. package/src/i18n/I18nProvider.tsx +128 -0
  467. package/src/i18n/LocaleSwitcher.tsx +50 -0
  468. package/src/i18n/activeLocale.ts +39 -0
  469. package/src/i18n/buildClientI18n.ts +101 -0
  470. package/src/i18n/i18n.test.tsx +140 -0
  471. package/src/i18n/index.ts +12 -0
  472. package/src/i18n/locales/core/en.ts +274 -0
  473. package/src/i18n/locales/core/index.ts +5 -0
  474. package/src/i18n/locales/core/sq.ts +275 -0
  475. package/src/i18n/useFormatter.ts +42 -0
  476. package/src/i18n/useLocale.ts +16 -0
  477. package/src/i18n/useTranslation.ts +17 -0
  478. package/src/index.ts +244 -0
  479. package/src/pages/ResourceListPage.tsx +205 -0
  480. package/src/plugin.ts +110 -0
  481. package/src/runtime/conditions.test.ts +99 -0
  482. package/src/runtime/conditions.ts +148 -0
  483. package/src/runtime/formTraversal.ts +41 -0
  484. package/src/runtime/serializedFunctions.test.ts +59 -0
  485. package/src/runtime/serializedFunctions.ts +284 -0
  486. package/src/slots/Slot.test.tsx +89 -0
  487. package/src/slots/Slot.tsx +47 -0
  488. package/src/slots/SlotCluster.test.tsx +95 -0
  489. package/src/slots/SlotCluster.tsx +107 -0
  490. package/src/slots/index.ts +15 -0
  491. package/src/slots/mergeSlots.test.ts +71 -0
  492. package/src/slots/mergeSlots.ts +40 -0
  493. package/src/slots/slotNames.test.ts +21 -0
  494. package/src/slots/types.ts +119 -0
  495. package/src/styles.css +437 -0
  496. package/src/table/TableContext.tsx +41 -0
  497. package/src/table/TableRenderer.test.tsx +197 -0
  498. package/src/table/TableRenderer.tsx +390 -0
  499. package/src/table/components/FiltersPanel.tsx +193 -0
  500. package/src/table/components/TableToolbar.tsx +153 -0
  501. package/src/table/components/TableToolbarButton.tsx +14 -0
  502. package/src/table/components/TableView.tsx +106 -0
  503. package/src/table/defaultRowActions.ts +43 -0
  504. package/src/table/hooks/useColumnVisibility.test.ts +51 -0
  505. package/src/table/hooks/useColumnVisibility.ts +71 -0
  506. package/src/table/hooks/useEditableRows.test.ts +69 -0
  507. package/src/table/hooks/useEditableRows.ts +89 -0
  508. package/src/table/hooks/useTableActions.ts +393 -0
  509. package/src/table/hooks/useTableData.ts +89 -0
  510. package/src/table/hooks/useTableLayout.ts +45 -0
  511. package/src/table/hooks/useTableQuery.test.ts +116 -0
  512. package/src/table/hooks/useTableQuery.ts +172 -0
  513. package/src/test/mockFetch.ts +67 -0
  514. package/src/test/setup.ts +25 -0
  515. package/src/types/index.ts +228 -0
  516. package/src/utils/classNames.ts +10 -0
  517. package/src/utils/columnMediaDimensions.ts +45 -0
  518. package/src/utils/columnVisibilityStorage.ts +55 -0
  519. package/src/utils/fieldErrors.test.ts +35 -0
  520. package/src/utils/fieldErrors.ts +27 -0
  521. package/src/utils/formatValue.test.tsx +65 -0
  522. package/src/utils/formatValue.tsx +117 -0
  523. package/src/utils/layoutStorage.ts +52 -0
  524. package/src/utils/redirectHandler.ts +29 -0
  525. package/src/utils/tableFormatters.test.ts +54 -0
  526. package/src/utils/tableFormatters.ts +104 -0
  527. package/src/utils/widgetVisibilityStorage.ts +38 -0
  528. package/tailwind.config.js +9 -0
  529. package/vite.config.ts +17 -0
@@ -0,0 +1,843 @@
1
+ import React, { useState, useRef, useEffect, KeyboardEvent } from 'react';
2
+ import { useFormContext, useWatch } from 'react-hook-form';
3
+ import { getFieldError } from '../utils/fieldErrors';
4
+ import { FieldProps } from '../types';
5
+ import { useValidation } from '../hooks/useValidation';
6
+ import { cn } from '../utils/classNames';
7
+ import { HintDisplay } from './utils/HintDisplay';
8
+ import { Icon } from './utils/Icon';
9
+ import { ResourceFormModal } from './modals/ResourceFormModal';
10
+ import { executeSerializedFunction } from '../runtime/serializedFunctions';
11
+ import { ViewFieldWrapper } from './utils/ViewFieldWrapper';
12
+ import { formatSelectLabel } from '../utils/formatValue';
13
+ import { authenticatedFetch } from '../api/authenticatedFetch';
14
+ import { translate } from '../i18n/activeLocale';
15
+
16
+ /**
17
+ * Component for rendering SelectField in view mode with relationship data fetching
18
+ */
19
+ function SelectFieldViewMode({
20
+ value,
21
+ isMultiple,
22
+ relationship,
23
+ label,
24
+ optionLabelFormatter,
25
+ apiBaseUrl,
26
+ }: {
27
+ value: any;
28
+ isMultiple: boolean;
29
+ relationship: NonNullable<FieldProps['relationship']>;
30
+ label?: string;
31
+ optionLabelFormatter?: any;
32
+ apiBaseUrl?: string;
33
+ }) {
34
+ const [displayValue, setDisplayValue] = useState<string>('-');
35
+ const [isLoading, setIsLoading] = useState(false);
36
+
37
+ useEffect(() => {
38
+ const fetchRelationshipData = async () => {
39
+ if (!value || (isMultiple && (!Array.isArray(value) || value.length === 0))) {
40
+ setDisplayValue('-');
41
+ return;
42
+ }
43
+
44
+ // Extract IDs from value
45
+ const valueIds = isMultiple
46
+ ? (Array.isArray(value) ? value : []).map((v: any) => {
47
+ if (typeof v === 'object' && v !== null) {
48
+ return v._id || v.id || String(v);
49
+ }
50
+ return String(v);
51
+ })
52
+ : typeof value === 'object' && value !== null
53
+ ? [value._id || value.id || String(value)]
54
+ : [String(value)];
55
+
56
+ if (valueIds.length === 0) {
57
+ setDisplayValue('-');
58
+ return;
59
+ }
60
+
61
+ // If all values are already objects with titleAttribute, use them directly
62
+ if (isMultiple && Array.isArray(value)) {
63
+ const allHaveTitle = value.every(
64
+ (v: any) => typeof v === 'object' && v !== null && v[relationship.titleAttribute],
65
+ );
66
+ if (allHaveTitle) {
67
+ const formatted = value
68
+ .map((v: any) => {
69
+ const title = v[relationship.titleAttribute] || String(v);
70
+ if (optionLabelFormatter) {
71
+ const formattedTitle = executeSerializedFunction(
72
+ optionLabelFormatter,
73
+ v._id || v.id || String(v),
74
+ v,
75
+ );
76
+ return formattedTitle !== null && formattedTitle !== undefined
77
+ ? String(formattedTitle)
78
+ : title;
79
+ }
80
+ return title;
81
+ })
82
+ .join(', ');
83
+ setDisplayValue(formatted);
84
+ return;
85
+ }
86
+ } else if (
87
+ !isMultiple &&
88
+ typeof value === 'object' &&
89
+ value !== null &&
90
+ value[relationship.titleAttribute]
91
+ ) {
92
+ const title = value[relationship.titleAttribute];
93
+ if (optionLabelFormatter) {
94
+ const formattedTitle = executeSerializedFunction(
95
+ optionLabelFormatter,
96
+ value._id || value.id || String(value),
97
+ value,
98
+ );
99
+ setDisplayValue(
100
+ formattedTitle !== null && formattedTitle !== undefined ? String(formattedTitle) : title,
101
+ );
102
+ } else {
103
+ setDisplayValue(title);
104
+ }
105
+ return;
106
+ }
107
+
108
+ // Need to fetch records
109
+ setIsLoading(true);
110
+ try {
111
+ const resourceSlug = relationship.resource || relationship.name;
112
+ const apiUrl = apiBaseUrl;
113
+ const fetchPromises = valueIds.map(async (id: string) => {
114
+ try {
115
+ const response = await authenticatedFetch(
116
+ `${apiUrl}/${resourceSlug}/${id}`,
117
+ {
118
+ headers: {},
119
+ },
120
+ apiUrl.substring(0, apiUrl.lastIndexOf('/')),
121
+ );
122
+ if (response.ok) {
123
+ const result = await response.json();
124
+ const record = result.data || result;
125
+ return { id, record };
126
+ }
127
+ } catch (error) {
128
+ console.error(`Error fetching record ${id}:`, error);
129
+ }
130
+ return null;
131
+ });
132
+
133
+ const results = await Promise.all(fetchPromises);
134
+ const validResults = results.filter((r): r is { id: string; record: any } => r !== null);
135
+
136
+ if (validResults.length === 0) {
137
+ setDisplayValue(valueIds.join(', '));
138
+ return;
139
+ }
140
+
141
+ // Format the display value
142
+ const formatted = validResults
143
+ .map(({ id, record }) => {
144
+ const title = record[relationship.titleAttribute] || id;
145
+ if (optionLabelFormatter) {
146
+ const formattedTitle = executeSerializedFunction(optionLabelFormatter, id, record);
147
+ return formattedTitle !== null && formattedTitle !== undefined
148
+ ? String(formattedTitle)
149
+ : title;
150
+ }
151
+ return title;
152
+ })
153
+ .join(', ');
154
+
155
+ setDisplayValue(formatted);
156
+ } catch (error) {
157
+ console.error('Error fetching relationship data:', error);
158
+ // Fallback to showing IDs
159
+ setDisplayValue(valueIds.join(', '));
160
+ } finally {
161
+ setIsLoading(false);
162
+ }
163
+ };
164
+
165
+ fetchRelationshipData();
166
+ }, [value, isMultiple, relationship, optionLabelFormatter, apiBaseUrl]);
167
+
168
+ if (isLoading) {
169
+ return (
170
+ <ViewFieldWrapper label={label}>
171
+ <span className="text-fg-secondary">{translate('core:select.loading')}</span>
172
+ </ViewFieldWrapper>
173
+ );
174
+ }
175
+
176
+ return (
177
+ <ViewFieldWrapper label={label}>
178
+ <span dangerouslySetInnerHTML={{ __html: displayValue || '-' }} />
179
+ </ViewFieldWrapper>
180
+ );
181
+ }
182
+
183
+ /**
184
+ * Select field component
185
+ * Renders single or multi-select dropdowns with optional creatable functionality
186
+ * Supports relationship-based data fetching from API
187
+ */
188
+ export function SelectField(props: FieldProps) {
189
+ // View mode: render formatted display
190
+ if (props.mode === 'view') {
191
+ const value = props.value;
192
+ const isMultiple = props.isMultiple || props.multiple;
193
+ const hasRelationship = !!props.relationship;
194
+
195
+ // For relationship-based selects, we need to fetch the related records if value is just an ID
196
+ if (hasRelationship && props.relationship) {
197
+ // Use a component that fetches relationship data
198
+ return (
199
+ <SelectFieldViewMode
200
+ value={value}
201
+ isMultiple={isMultiple}
202
+ relationship={props.relationship}
203
+ label={props.label}
204
+ optionLabelFormatter={props.optionLabelFormatter}
205
+ apiBaseUrl={(props as any).apiBaseUrl}
206
+ />
207
+ );
208
+ }
209
+
210
+ // For non-relationship selects, use static options
211
+ const displayValue = formatSelectLabel(value, props.options, isMultiple);
212
+ return <ViewFieldWrapper label={props.label}>{displayValue}</ViewFieldWrapper>;
213
+ }
214
+
215
+ const {
216
+ register,
217
+ setValue,
218
+ control,
219
+ formState: { errors },
220
+ } = useFormContext();
221
+
222
+ const validation = useValidation(props.validation?.rules || [], props.operation, props.name);
223
+ const error = getFieldError(errors, props.name);
224
+ const hasError = !!error;
225
+ const isMultiple = props.isMultiple || props.multiple;
226
+ const isCreatable = props.creatable;
227
+ const isNative = props.native;
228
+ const hasRelationship = !!props.relationship;
229
+
230
+ // Watch current value for multi-select - let the form handle defaults
231
+ const rawValue = useWatch({ control, name: props.name });
232
+
233
+ // Normalize value: if it's an object (from edit mode), extract the ID
234
+ const normalizeValue = (value: any): string | string[] | '' => {
235
+ if (!value) return isMultiple ? [] : '';
236
+
237
+ if (isMultiple) {
238
+ if (!Array.isArray(value)) return [];
239
+ return value.map((v: any) => {
240
+ if (typeof v === 'object' && v !== null) {
241
+ return v._id || v.id || String(v);
242
+ }
243
+ return String(v);
244
+ });
245
+ } else {
246
+ if (typeof value === 'object' && value !== null) {
247
+ return value._id || value.id || String(value);
248
+ }
249
+ return String(value);
250
+ }
251
+ };
252
+
253
+ const currentValue = normalizeValue(rawValue) || (isMultiple ? [] : '');
254
+
255
+ // Update form value if it was an object (normalize it to ID)
256
+ useEffect(() => {
257
+ if (rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue)) {
258
+ const normalized = normalizeValue(rawValue);
259
+ if (normalized !== rawValue) {
260
+ setValue(props.name, normalized, { shouldValidate: false });
261
+ }
262
+ } else if (rawValue && Array.isArray(rawValue) && rawValue.some((v: any) => typeof v === 'object')) {
263
+ const normalized = normalizeValue(rawValue);
264
+ setValue(props.name, normalized, { shouldValidate: false });
265
+ }
266
+ }, [rawValue, props.name, setValue]);
267
+
268
+ const [isOpen, setIsOpen] = useState(false);
269
+ const [searchTerm, setSearchTerm] = useState('');
270
+ const [inputValue, setInputValue] = useState('');
271
+ const [relationshipOptions, setRelationshipOptions] = useState<Record<string, string>>({});
272
+ const [relationshipRecords, setRelationshipRecords] = useState<Record<string, any>>({});
273
+ // Track which records were individually fetched (for edit mode) - these should be formatted
274
+ const [individuallyFetchedIds, setIndividuallyFetchedIds] = useState<Set<string>>(new Set());
275
+ // Cache formatted labels for individually fetched records to prevent re-rendering
276
+ const [formattedLabels, setFormattedLabels] = useState<Record<string, string>>({});
277
+ const [isLoadingRelationship, setIsLoadingRelationship] = useState(false);
278
+ const [showCreateModal, setShowCreateModal] = useState(false);
279
+ const dropdownRef = useRef<HTMLDivElement>(null);
280
+
281
+ // Format option label using custom formatter if provided
282
+ const formatOptionLabel = (value: string, label: string, record?: any): string => {
283
+ if (props.optionLabelFormatter && record) {
284
+ const result = executeSerializedFunction(props.optionLabelFormatter, value, record);
285
+ return result !== null && result !== undefined ? String(result) : label;
286
+ }
287
+ return label;
288
+ };
289
+
290
+ // Fetch relationship data when component mounts or search term changes
291
+ useEffect(() => {
292
+ if (!hasRelationship || !props.relationship) return;
293
+
294
+ const fetchRelationshipData = async () => {
295
+ setIsLoadingRelationship(true);
296
+ try {
297
+ const { resource, titleAttribute } = props.relationship!;
298
+ const resourceSlug = resource || props.relationship!.name;
299
+
300
+ if (!resourceSlug) {
301
+ throw new Error('Resource slug is required for relationship data fetching');
302
+ }
303
+
304
+ const apiBaseUrl = (props as any).apiBaseUrl;
305
+ const url = `${apiBaseUrl}/${resourceSlug}/list`;
306
+
307
+ const body: any = { perPage: 50 };
308
+ if (searchTerm) {
309
+ body.search = searchTerm;
310
+ }
311
+
312
+ const response = await authenticatedFetch(
313
+ url,
314
+ {
315
+ method: 'POST',
316
+ headers: { 'Content-Type': 'application/json' },
317
+ body: JSON.stringify(body),
318
+ },
319
+ apiBaseUrl,
320
+ );
321
+
322
+ if (!response.ok) {
323
+ throw new Error(`Failed to fetch relationship data: ${response.status}`);
324
+ }
325
+
326
+ const result = await response.json();
327
+ const data = result.data || [];
328
+
329
+ const options: Record<string, string> = {};
330
+ const records: Record<string, any> = {};
331
+ data.forEach((item: any) => {
332
+ const id = item._id || item.id;
333
+ const title = item[titleAttribute] || id;
334
+ options[id] = title;
335
+ records[id] = item;
336
+ });
337
+
338
+ // Replace with API results so the list reflects current search; preserve labels for selected value(s) so chips still display
339
+ const valueIds = isMultiple
340
+ ? Array.isArray(currentValue)
341
+ ? currentValue
342
+ : []
343
+ : currentValue
344
+ ? [currentValue]
345
+ : [];
346
+ setRelationshipOptions(prev => {
347
+ const next = { ...options };
348
+ valueIds.forEach((id: string) => {
349
+ if (prev[id]) next[id] = prev[id];
350
+ });
351
+ return next;
352
+ });
353
+
354
+ // Replace records with API results; preserve individually fetched ones so formatted labels are kept
355
+ setRelationshipRecords(prev => {
356
+ const next = { ...records };
357
+ valueIds.forEach((id: string) => {
358
+ if (individuallyFetchedIds.has(id) && prev[id]) next[id] = prev[id];
359
+ });
360
+ return next;
361
+ });
362
+ } catch (error) {
363
+ console.error('Error fetching relationship data:', error);
364
+ setRelationshipOptions({});
365
+ } finally {
366
+ setIsLoadingRelationship(false);
367
+ }
368
+ };
369
+
370
+ const timeoutId = setTimeout(fetchRelationshipData, searchTerm ? 300 : 0);
371
+ return () => clearTimeout(timeoutId);
372
+ }, [hasRelationship, searchTerm, props.relationship, individuallyFetchedIds, currentValue, isMultiple]);
373
+
374
+ // Fetch related record(s) when editing (if we have a current value but no record data)
375
+ useEffect(() => {
376
+ if (!hasRelationship || !props.relationship || !currentValue) return;
377
+
378
+ const valueIds = isMultiple ? (Array.isArray(currentValue) ? currentValue : []) : [currentValue as string];
379
+
380
+ const missingIds = valueIds.filter((id: string) => !relationshipRecords[id]);
381
+
382
+ if (missingIds.length > 0) {
383
+ const fetchRelatedRecords = async () => {
384
+ try {
385
+ const { resource, titleAttribute } = props.relationship!;
386
+ const resourceSlug = resource || props.relationship!.name;
387
+ const apiBaseUrl = (props as any).apiBaseUrl;
388
+
389
+ const fetchPromises = missingIds.map(async (id: string) => {
390
+ try {
391
+ const response = await authenticatedFetch(
392
+ `${apiBaseUrl}/${resourceSlug}/${id}`,
393
+ {
394
+ headers: {},
395
+ },
396
+ apiBaseUrl,
397
+ );
398
+ if (response.ok) {
399
+ const result = await response.json();
400
+ // Handle new response structure: { data, metadata }
401
+ const record = result.data || result;
402
+ return { id, record };
403
+ }
404
+ } catch (error) {
405
+ console.error(`Error fetching record ${id}:`, error);
406
+ }
407
+ return null;
408
+ });
409
+
410
+ const results = await Promise.all(fetchPromises);
411
+
412
+ const newOptions = { ...relationshipOptions };
413
+ const newRecords = { ...relationshipRecords };
414
+ const newFormattedLabels: Record<string, string> = {};
415
+
416
+ results.forEach(result => {
417
+ if (result) {
418
+ const { id, record } = result;
419
+ const title = record[titleAttribute] || id;
420
+ newOptions[id] = title;
421
+ newRecords[id] = record;
422
+ // Pre-format and cache the label for this individually fetched record
423
+ const formatted = formatOptionLabel(id, title, record);
424
+ newFormattedLabels[id] = formatted;
425
+ }
426
+ });
427
+
428
+ setRelationshipOptions(newOptions);
429
+ setRelationshipRecords(newRecords);
430
+ // Mark these records as individually fetched and cache their formatted labels
431
+ setIndividuallyFetchedIds(prev => {
432
+ const newSet = new Set(prev);
433
+ results.forEach(result => {
434
+ if (result) newSet.add(result.id);
435
+ });
436
+ return newSet;
437
+ });
438
+ setFormattedLabels(prev => ({ ...prev, ...newFormattedLabels }));
439
+ } catch (error) {
440
+ console.error('Error fetching related records:', error);
441
+ }
442
+ };
443
+
444
+ fetchRelatedRecords();
445
+ }
446
+ }, [hasRelationship, currentValue, relationshipRecords, props.relationship, isMultiple]);
447
+
448
+ // Close dropdown when clicking outside
449
+ useEffect(() => {
450
+ const handleClickOutside = (event: MouseEvent) => {
451
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
452
+ setIsOpen(false);
453
+ setSearchTerm('');
454
+ setInputValue('');
455
+ }
456
+ };
457
+
458
+ document.addEventListener('mousedown', handleClickOutside);
459
+ return () => document.removeEventListener('mousedown', handleClickOutside);
460
+ }, []);
461
+
462
+ // Use relationship options if available, otherwise use static options
463
+ const effectiveOptions = hasRelationship ? relationshipOptions : props.options || {};
464
+
465
+ // If native or not multiple and not creatable and not relationship, use native select
466
+ if (isNative || (!isMultiple && !isCreatable && !hasRelationship)) {
467
+ return (
468
+ <div className="mb-4">
469
+ {props.label && (
470
+ <label
471
+ htmlFor={props.name}
472
+ className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
473
+ {props.label}
474
+ {validation.required && <span className="text-red-500 dark:text-red-400 ml-1">*</span>}
475
+ </label>
476
+ )}
477
+
478
+ <select
479
+ id={props.name}
480
+ {...register(props.name, validation)}
481
+ disabled={props.disabled}
482
+ multiple={isMultiple}
483
+ className={cn(
484
+ 'w-full px-3 py-2 border rounded-lg shadow-sm',
485
+ 'k-input',
486
+ 'focus:outline-none focus:ring-2 focus:ring-ring',
487
+ 'transition duration-150 ease-in-out',
488
+ hasError && 'border-red-500 dark:border-red-400 focus:ring-red-500 dark:focus:ring-red-400',
489
+ props.disabled && 'opacity-60 cursor-not-allowed',
490
+ isMultiple && 'min-h-[120px]',
491
+ )}>
492
+ {props.placeholder && !isMultiple && props.selectablePlaceholder !== false && (
493
+ <option value="">{props.placeholder}</option>
494
+ )}
495
+
496
+ {effectiveOptions &&
497
+ Object.entries(effectiveOptions).map(([value, label]) => (
498
+ <option key={value} value={value}>
499
+ {label}
500
+ </option>
501
+ ))}
502
+ </select>
503
+
504
+ {hasError && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{error?.message as string}</p>}
505
+
506
+ {props.helperText && (
507
+ <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{props.helperText}</p>
508
+ )}
509
+
510
+ {!hasError && <HintDisplay hint={props.hint} hintIcon={props.hintIcon} hintColor={props.hintColor} />}
511
+
512
+ {isMultiple && !hasError && (
513
+ <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
514
+ Hold Ctrl/Cmd to select multiple options
515
+ </p>
516
+ )}
517
+ </div>
518
+ );
519
+ }
520
+
521
+ // Enhanced multi-select with chips
522
+ const selectedValues: string[] = isMultiple
523
+ ? Array.isArray(currentValue)
524
+ ? currentValue.filter((v): v is string => typeof v === 'string')
525
+ : []
526
+ : currentValue && typeof currentValue === 'string'
527
+ ? [currentValue]
528
+ : [];
529
+
530
+ const optionEntries = Object.entries(effectiveOptions);
531
+
532
+ // Filter options based on search term
533
+ // For relationship-based selects, the API already filters the results, so skip client-side filtering
534
+ const filteredOptions = hasRelationship
535
+ ? optionEntries
536
+ : optionEntries.filter(([value, label]) => {
537
+ const matchesSearch = !searchTerm || label.toLowerCase().includes(searchTerm.toLowerCase());
538
+ return matchesSearch;
539
+ });
540
+
541
+ // Check if we can create a new option
542
+ const canCreateNew =
543
+ isCreatable &&
544
+ searchTerm &&
545
+ !optionEntries.some(([value, label]) => label.toLowerCase() === searchTerm.toLowerCase()) &&
546
+ !selectedValues.includes(searchTerm);
547
+
548
+ // Check if we can create a new relationship record
549
+ const canCreateRelationship = hasRelationship && props.createOptionForm;
550
+
551
+ const toggleOption = (value: string) => {
552
+ // When selecting a new option, format and cache its label if we have the record
553
+ const record = relationshipRecords[value];
554
+ if (record && !formattedLabels[value]) {
555
+ const label = relationshipOptions[value] || value;
556
+ const formatted = formatOptionLabel(value, label, record);
557
+ setFormattedLabels(prev => ({ ...prev, [value]: formatted }));
558
+ // Mark as individually fetched so it stays formatted
559
+ setIndividuallyFetchedIds(prev => new Set(prev).add(value));
560
+ }
561
+
562
+ if (isMultiple) {
563
+ const newValues = selectedValues.includes(value)
564
+ ? selectedValues.filter((v: string) => v !== value)
565
+ : [...selectedValues, value];
566
+ setValue(props.name, newValues, { shouldValidate: true });
567
+ } else {
568
+ setValue(props.name, value, { shouldValidate: true });
569
+ setIsOpen(false);
570
+ setSearchTerm('');
571
+ }
572
+ };
573
+
574
+ const removeValue = (value: string) => {
575
+ if (isMultiple) {
576
+ const newValues = selectedValues.filter((v: string) => v !== value);
577
+ setValue(props.name, newValues, { shouldValidate: true });
578
+ } else {
579
+ setValue(props.name, '', { shouldValidate: true });
580
+ }
581
+ };
582
+
583
+ const createNewOption = () => {
584
+ if (!canCreateNew || !searchTerm) return;
585
+
586
+ const trimmedValue = searchTerm.trim();
587
+ if (isMultiple) {
588
+ const newValues = [...selectedValues, trimmedValue];
589
+ setValue(props.name, newValues, { shouldValidate: true });
590
+ } else {
591
+ setValue(props.name, trimmedValue, { shouldValidate: true });
592
+ setIsOpen(false);
593
+ }
594
+ setSearchTerm('');
595
+ setInputValue('');
596
+ };
597
+
598
+ const handleCreateRelationshipSuccess = (newRecord: any) => {
599
+ const id = newRecord._id || newRecord.id;
600
+ const title = newRecord[props.relationship!.titleAttribute] || id;
601
+
602
+ setRelationshipOptions(prev => ({ ...prev, [id]: title }));
603
+ setRelationshipRecords(prev => ({ ...prev, [id]: newRecord }));
604
+
605
+ // Format and cache the label for the newly created record
606
+ const formatted = formatOptionLabel(id, title, newRecord);
607
+ setFormattedLabels(prev => ({ ...prev, [id]: formatted }));
608
+ setIndividuallyFetchedIds(prev => new Set(prev).add(id));
609
+
610
+ if (isMultiple) {
611
+ const newValues = [...selectedValues, id];
612
+ setValue(props.name, newValues, { shouldValidate: true });
613
+ } else {
614
+ setValue(props.name, id, { shouldValidate: true });
615
+ }
616
+
617
+ setShowCreateModal(false);
618
+ setIsOpen(false);
619
+ setSearchTerm('');
620
+ };
621
+
622
+ const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
623
+ if (e.key === 'Enter') {
624
+ e.preventDefault();
625
+ if (canCreateNew) {
626
+ createNewOption();
627
+ } else if (filteredOptions.length === 1) {
628
+ toggleOption(filteredOptions[0][0]);
629
+ setSearchTerm('');
630
+ }
631
+ } else if (e.key === 'Backspace' && !searchTerm && selectedValues.length > 0 && isMultiple) {
632
+ removeValue(selectedValues[selectedValues.length - 1]);
633
+ }
634
+ };
635
+
636
+ const getDisplayLabel = (value: string) => {
637
+ // If we have a cached formatted label for this individually fetched record, use it
638
+ if (formattedLabels[value]) {
639
+ return formattedLabels[value];
640
+ }
641
+ // Otherwise, return the plain label
642
+ return effectiveOptions[value] || value;
643
+ };
644
+
645
+ return (
646
+ <div className="mb-4">
647
+ {props.label && (
648
+ <label htmlFor={props.name} className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
649
+ {props.label}
650
+ {validation.required && <span className="text-red-500 dark:text-red-400 ml-1">*</span>}
651
+ </label>
652
+ )}
653
+
654
+ {/* Hidden input for form registration */}
655
+ <input type="hidden" {...register(props.name, validation)} />
656
+
657
+ <div ref={dropdownRef} className="relative">
658
+ {/* Selected values display / Input */}
659
+ <div
660
+ onClick={() => !props.disabled && setIsOpen(!isOpen)}
661
+ className={cn(
662
+ 'w-full min-h-[42px] px-3 py-2 border rounded-lg shadow-sm cursor-pointer',
663
+ 'k-input',
664
+ 'focus-within:outline-none focus-within:ring-2 focus-within:ring-ring ',
665
+ 'transition duration-150 ease-in-out',
666
+ hasError &&
667
+ 'border-red-500 dark:border-red-400 focus-within:ring-red-500 dark:focus-within:ring-red-400',
668
+ props.disabled && 'opacity-60 cursor-not-allowed bg-muted',
669
+ )}>
670
+ <div className="flex flex-wrap gap-2 items-center">
671
+ {/* Display selected values as chips */}
672
+ {selectedValues.map((value: string) => {
673
+ const displayLabel = getDisplayLabel(value);
674
+ const hasHTML = /<[^>]+>/.test(displayLabel);
675
+
676
+ return (
677
+ <span
678
+ key={value}
679
+ className="inline-flex items-center gap-1 px-2 py-1 rounded text-sm font-medium bg-accent-soft dark:bg-accent-soft text-accent">
680
+ {hasHTML ? (
681
+ <span dangerouslySetInnerHTML={{ __html: displayLabel }} />
682
+ ) : (
683
+ <span>{displayLabel}</span>
684
+ )}
685
+ {!props.disabled && (
686
+ <button
687
+ type="button"
688
+ onClick={e => {
689
+ e.stopPropagation();
690
+ removeValue(value);
691
+ }}
692
+ className="hover:text-accent-hover transition-colors">
693
+ <Icon name="X" size={14} />
694
+ </button>
695
+ )}
696
+ </span>
697
+ );
698
+ })}
699
+
700
+ {/* Search/Input field */}
701
+ {!props.disabled && (
702
+ <input
703
+ type="text"
704
+ value={searchTerm}
705
+ onChange={e => {
706
+ setSearchTerm(e.target.value);
707
+ if (!isOpen) setIsOpen(true);
708
+ }}
709
+ onKeyDown={handleKeyDown}
710
+ onClick={e => {
711
+ e.stopPropagation();
712
+ setIsOpen(true);
713
+ }}
714
+ onFocus={() => setIsOpen(true)}
715
+ placeholder={
716
+ selectedValues.length === 0
717
+ ? props.placeholder || translate('core:select.placeholder')
718
+ : ''
719
+ }
720
+ className="flex-1 min-w-[120px] outline-none bg-transparent text-fg"
721
+ disabled={props.disabled}
722
+ />
723
+ )}
724
+
725
+ {/* Dropdown arrow */}
726
+ <Icon
727
+ name={isOpen ? 'ChevronUp' : 'ChevronDown'}
728
+ size={16}
729
+ className="text-gray-400 shrink-0"
730
+ />
731
+ </div>
732
+ </div>
733
+
734
+ {/* Dropdown menu */}
735
+ {isOpen && !props.disabled && (
736
+ <div className="absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-border rounded-lg shadow-lg max-h-60 overflow-auto">
737
+ {isLoadingRelationship && (
738
+ <div className="px-3 py-2 text-sm text-fg-secondary flex items-center gap-2">
739
+ <Icon name="Loader2" size={16} className="animate-spin" />
740
+ <span>{translate('core:select.loading')}</span>
741
+ </div>
742
+ )}
743
+
744
+ {!isLoadingRelationship &&
745
+ filteredOptions.length === 0 &&
746
+ !canCreateNew &&
747
+ !canCreateRelationship && (
748
+ <div className="px-3 py-2 text-sm text-fg-secondary">
749
+ {searchTerm
750
+ ? translate('core:select.no_matching')
751
+ : translate('core:filters.no_options')}
752
+ </div>
753
+ )}
754
+
755
+ {!isLoadingRelationship &&
756
+ filteredOptions.map(([value, label]) => {
757
+ const record = relationshipRecords[value];
758
+ const displayLabel = formatOptionLabel(value, label, record);
759
+ const hasHTML = /<[^>]+>/.test(displayLabel);
760
+
761
+ return (
762
+ <div
763
+ key={value}
764
+ onClick={() => {
765
+ toggleOption(value);
766
+ setSearchTerm('');
767
+ }}
768
+ className={cn(
769
+ 'px-3 py-2 cursor-pointer text-sm transition-colors text-fg',
770
+ 'hover:bg-gray-100 dark:hover:bg-gray-700',
771
+ selectedValues.includes(value) && 'bg-accent-soft text-accent',
772
+ )}>
773
+ <div className="flex items-center justify-between">
774
+ {hasHTML ? (
775
+ <span dangerouslySetInnerHTML={{ __html: displayLabel }} />
776
+ ) : (
777
+ <span>{displayLabel}</span>
778
+ )}
779
+ {selectedValues.includes(value) && <Icon name="Check" size={16} />}
780
+ </div>
781
+ </div>
782
+ );
783
+ })}
784
+
785
+ {/* Create new simple option */}
786
+ {canCreateNew && !canCreateRelationship && (
787
+ <div
788
+ onClick={createNewOption}
789
+ className={cn(
790
+ 'px-3 py-2 cursor-pointer text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 text-accent font-medium',
791
+ filteredOptions.length > 0 && 'border-t border-border',
792
+ )}>
793
+ <div className="flex items-center gap-2">
794
+ <Icon name="Plus" size={16} />
795
+ <span>{translate('core:select.create', { term: searchTerm })}</span>
796
+ </div>
797
+ </div>
798
+ )}
799
+
800
+ {/* Create new relationship record */}
801
+ {canCreateRelationship && (
802
+ <div
803
+ onClick={() => {
804
+ setShowCreateModal(true);
805
+ setIsOpen(false);
806
+ }}
807
+ className={cn(
808
+ 'px-3 py-2 cursor-pointer text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 text-accent font-medium',
809
+ filteredOptions.length > 0 && 'border-t border-border',
810
+ )}>
811
+ <div className="flex items-center gap-2">
812
+ <Icon name="Plus" size={16} />
813
+ <span>{props.createOptionModalHeading || translate('core:select.create_new')}</span>
814
+ </div>
815
+ </div>
816
+ )}
817
+ </div>
818
+ )}
819
+ </div>
820
+
821
+ {hasError && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{error?.message as string}</p>}
822
+
823
+ {props.helperText && <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{props.helperText}</p>}
824
+
825
+ {!hasError && <HintDisplay hint={props.hint} hintIcon={props.hintIcon} hintColor={props.hintColor} />}
826
+
827
+ {/* Create relationship modal */}
828
+ {canCreateRelationship && showCreateModal && (
829
+ <ResourceFormModal
830
+ isOpen={showCreateModal}
831
+ onClose={() => setShowCreateModal(false)}
832
+ mode="create"
833
+ resourceName={props.createOptionModalHeading || translate('core:common.record')}
834
+ resourceSlug={props.relationship!.resource || props.relationship!.name}
835
+ apiBaseUrl={(props as any).apiBaseUrl}
836
+ formSchema={{ type: 'form', components: props.createOptionForm || [] } as any}
837
+ onSuccess={handleCreateRelationshipSuccess}
838
+ depth={1}
839
+ />
840
+ )}
841
+ </div>
842
+ );
843
+ }