@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,151 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import React from 'react';
3
+ import { render, screen, waitFor, within } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { FormRenderer } from '../FormRenderer';
6
+
7
+ const DOT_LABEL = 'Has validation errors or required fields';
8
+
9
+ const tab = (label: string, schema: any[]) => ({
10
+ type: 'tab',
11
+ isLayout: true,
12
+ childScope: 'inherit',
13
+ label,
14
+ schema,
15
+ });
16
+
17
+ const tabsSchema: any = {
18
+ type: 'form',
19
+ components: [
20
+ {
21
+ type: 'tabs',
22
+ isLayout: true,
23
+ childScope: 'inherit',
24
+ schema: [
25
+ tab('General', [
26
+ {
27
+ type: 'text-input',
28
+ name: 'email',
29
+ label: 'Email',
30
+ validation: { rules: [{ rule: 'email' }] },
31
+ },
32
+ ]),
33
+ tab('Content', [{ type: 'text-input', name: 'body', label: 'Body' }]),
34
+ ],
35
+ },
36
+ ],
37
+ };
38
+
39
+ function renderTabsForm() {
40
+ return render(<FormRenderer schema={tabsSchema} onSubmit={vi.fn()} />);
41
+ }
42
+
43
+ describe('TabsField navigation', () => {
44
+ it('does not snap back to an error tab while editing (before submit)', async () => {
45
+ const user = userEvent.setup();
46
+ renderTabsForm();
47
+
48
+ // Create a (non-required) validation error in the first tab.
49
+ await user.type(screen.getByLabelText('Email'), 'not-an-email');
50
+
51
+ // Manually navigate to the second tab — must stay there.
52
+ await user.click(screen.getByRole('tab', { name: /content/i }));
53
+
54
+ expect(screen.getByRole('tab', { name: /content/i })).toHaveAttribute('aria-selected', 'true');
55
+ expect(screen.getByRole('tab', { name: /general/i })).toHaveAttribute('aria-selected', 'false');
56
+ });
57
+
58
+ it('jumps to the first tab with errors after a failed submit', async () => {
59
+ const user = userEvent.setup();
60
+ renderTabsForm();
61
+
62
+ await user.type(screen.getByLabelText('Email'), 'not-an-email');
63
+ await user.click(screen.getByRole('tab', { name: /content/i }));
64
+ expect(screen.getByRole('tab', { name: /content/i })).toHaveAttribute('aria-selected', 'true');
65
+
66
+ await user.click(screen.getByRole('button', { name: /^submit$/i }));
67
+
68
+ await waitFor(() =>
69
+ expect(screen.getByRole('tab', { name: /general/i })).toHaveAttribute('aria-selected', 'true'),
70
+ );
71
+ });
72
+
73
+ it('lets the user navigate freely again after the post-submit jump', async () => {
74
+ const user = userEvent.setup();
75
+ renderTabsForm();
76
+
77
+ await user.type(screen.getByLabelText('Email'), 'not-an-email');
78
+ await user.click(screen.getByRole('button', { name: /^submit$/i }));
79
+
80
+ // Jumped to the error tab...
81
+ await waitFor(() =>
82
+ expect(screen.getByRole('tab', { name: /general/i })).toHaveAttribute('aria-selected', 'true'),
83
+ );
84
+
85
+ // ...but the user can still move away and stay there (not locked).
86
+ await user.click(screen.getByRole('tab', { name: /content/i }));
87
+ expect(screen.getByRole('tab', { name: /content/i })).toHaveAttribute('aria-selected', 'true');
88
+ });
89
+ });
90
+
91
+ const requiredDotSchema: any = {
92
+ type: 'form',
93
+ components: [
94
+ {
95
+ type: 'tabs',
96
+ isLayout: true,
97
+ childScope: 'inherit',
98
+ schema: [
99
+ tab('General', [
100
+ {
101
+ type: 'text-input',
102
+ name: 'title',
103
+ label: 'Title',
104
+ validation: { rules: [{ rule: 'required' }] },
105
+ },
106
+ {
107
+ type: 'select',
108
+ name: 'status',
109
+ label: 'Status',
110
+ default: 'draft',
111
+ options: { draft: 'Draft', published: 'Published' },
112
+ validation: { rules: [{ rule: 'required' }] },
113
+ },
114
+ ]),
115
+ tab('Advanced', [
116
+ {
117
+ type: 'text-input',
118
+ name: 'notes',
119
+ label: 'Notes',
120
+ validation: { rules: [{ rule: 'required' }] },
121
+ },
122
+ ]),
123
+ ],
124
+ },
125
+ ],
126
+ };
127
+
128
+ describe('TabsField required indicator', () => {
129
+ it('shows required dots only after submit, and clears them reactively as fields are filled', async () => {
130
+ const user = userEvent.setup();
131
+ render(<FormRenderer schema={requiredDotSchema} onSubmit={vi.fn()} />);
132
+
133
+ const general = () => screen.getByRole('tab', { name: /general/i });
134
+ const advanced = () => screen.getByRole('tab', { name: /advanced/i });
135
+
136
+ // Before any submit: no nagging dots, even though required fields are empty.
137
+ expect(within(general()).queryByLabelText(DOT_LABEL)).not.toBeInTheDocument();
138
+ expect(within(advanced()).queryByLabelText(DOT_LABEL)).not.toBeInTheDocument();
139
+
140
+ // Submit with everything empty → both tabs flagged (title + notes missing).
141
+ await user.click(screen.getByRole('button', { name: /^submit$/i }));
142
+ await waitFor(() => expect(within(general()).queryByLabelText(DOT_LABEL)).toBeInTheDocument());
143
+ expect(within(advanced()).queryByLabelText(DOT_LABEL)).toBeInTheDocument();
144
+
145
+ // Fill General's required field → its dot clears (status already has a default);
146
+ // the Advanced tab keeps its dot because `notes` is still empty.
147
+ await user.type(screen.getByLabelText(/title/i), 'Hello');
148
+ await waitFor(() => expect(within(general()).queryByLabelText(DOT_LABEL)).not.toBeInTheDocument());
149
+ expect(within(advanced()).queryByLabelText(DOT_LABEL)).toBeInTheDocument();
150
+ });
151
+ });
@@ -0,0 +1,386 @@
1
+ import React, { useState, useEffect, useMemo, useRef } from 'react';
2
+ import { useFormContext, FieldErrors, useWatch } from 'react-hook-form';
3
+ import { getFieldError } from '../utils/fieldErrors';
4
+ import { FieldProps } from '../types';
5
+ import { FieldRenderer } from '../FieldRenderer';
6
+ import { Icon } from './utils/Icon';
7
+ import { getFieldLayoutClasses } from './utils/layoutHelpers';
8
+ import { cn } from '../utils/classNames';
9
+ import { SerializedComponent } from '@maxal_studio/kratosjs';
10
+ import { evaluateCondition } from '../runtime/conditions';
11
+ import { getChildComponents, isArrayScope, someComponent } from '../runtime/formTraversal';
12
+ import { translate } from '../i18n/activeLocale';
13
+
14
+ const isEmptyValue = (value: any): boolean => value === undefined || value === null || value === '';
15
+
16
+ /**
17
+ * Recursively check if a component or its nested components have validation errors.
18
+ * Generic over the children contract — works for any container, including plugins.
19
+ */
20
+ function hasComponentErrors(component: SerializedComponent, errors: FieldErrors): boolean {
21
+ return someComponent(component, node => {
22
+ if (!node.name) return false;
23
+ const fieldError: any = getFieldError(errors, node.name as string);
24
+ if (!fieldError) return false;
25
+
26
+ // Array-scope containers (repeaters): the array itself or any item may have errors.
27
+ if (isArrayScope(node)) {
28
+ if (Array.isArray(fieldError)) {
29
+ return fieldError.some(itemErrors => itemErrors && Object.keys(itemErrors).length > 0);
30
+ }
31
+ return Object.keys(fieldError).length > 0;
32
+ }
33
+
34
+ // Ignore pure "required" errors here; required-emptiness is handled separately
35
+ // (based on current values) so a filled field doesn't keep its tab marked.
36
+ return fieldError.type !== 'required';
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Check if a tab has any validation errors in its schema
42
+ */
43
+ function hasTabErrors(tabSchema: SerializedComponent[], errors: FieldErrors): boolean {
44
+ return tabSchema.some(component => hasComponentErrors(component, errors));
45
+ }
46
+
47
+ /**
48
+ * Check if a component is required
49
+ */
50
+ /**
51
+ * Check if a component is required, evaluating conditional required logic
52
+ */
53
+ function isComponentRequired(
54
+ component: SerializedComponent,
55
+ formValues: Record<string, any>,
56
+ operation?: 'create' | 'edit' | 'view',
57
+ ): boolean {
58
+ // Check direct required property
59
+ const requiredProp = (component as any).required;
60
+
61
+ // If required is a boolean
62
+ if (typeof requiredProp === 'boolean') {
63
+ return requiredProp;
64
+ }
65
+
66
+ // If required is a function (serialized), evaluate it
67
+ if (typeof requiredProp === 'string') {
68
+ return evaluateCondition(requiredProp, formValues, operation);
69
+ }
70
+
71
+ // Check validation rules for 'required' on the serialized validation object
72
+ const validation = (component as any).validation as { rules?: Array<string | { rule?: string }> } | undefined;
73
+ if (validation && Array.isArray(validation.rules)) {
74
+ // Check for simple 'required' string rule
75
+ if (validation.rules.some(rule => rule === 'required')) {
76
+ return true;
77
+ }
78
+
79
+ // Check for conditional required rule objects
80
+ for (const rule of validation.rules) {
81
+ if (typeof rule === 'object' && (rule as any).rule === 'required') {
82
+ // If there's a condition function, evaluate it
83
+ const condition = (rule as any).condition;
84
+ if (typeof condition === 'string') {
85
+ return evaluateCondition(condition, formValues, operation);
86
+ }
87
+ return true;
88
+ }
89
+ }
90
+ }
91
+
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * Recursively check if a component (or its descendants) is required and empty.
97
+ * Generic over the children contract.
98
+ */
99
+ function hasRequiredEmptyField(
100
+ component: SerializedComponent,
101
+ formValues: Record<string, any>,
102
+ operation?: 'create' | 'edit' | 'view',
103
+ ): boolean {
104
+ return someComponent(component, node => {
105
+ if (!node.name) return false;
106
+
107
+ // Array-scope containers (repeaters): check required-empty fields within each item.
108
+ if (isArrayScope(node)) {
109
+ const items = formValues[node.name as string];
110
+ if (!Array.isArray(items)) return false;
111
+ const template = getChildComponents(node);
112
+ return items.some(item =>
113
+ template.some(
114
+ field =>
115
+ field.name &&
116
+ isComponentRequired(field, item, operation) &&
117
+ isEmptyValue(item[field.name as string]),
118
+ ),
119
+ );
120
+ }
121
+
122
+ if (isComponentRequired(node, formValues, operation)) {
123
+ return isEmptyValue(formValues[node.name as string]);
124
+ }
125
+ return false;
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Check if a tab has any required empty fields
131
+ */
132
+ function hasRequiredEmptyFieldsInTab(
133
+ tabSchema: SerializedComponent[],
134
+ formValues: Record<string, any>,
135
+ operation?: 'create' | 'edit' | 'view',
136
+ ): boolean {
137
+ return tabSchema.some(component => hasRequiredEmptyField(component, formValues, operation));
138
+ }
139
+
140
+ export function TabsField({
141
+ schema: tabNodes,
142
+ defaultTab = 0,
143
+ columns,
144
+ mode,
145
+ disabled,
146
+ value,
147
+ _recordData,
148
+ description,
149
+ operation,
150
+ ...props
151
+ }: FieldProps & {
152
+ /** Tab child components (each a serialized `tab` node with label/icon/schema). */
153
+ schema?: SerializedComponent[];
154
+ defaultTab?: number;
155
+ _recordData?: any;
156
+ operation?: 'create' | 'edit' | 'view';
157
+ }) {
158
+ const [activeTab, setActiveTab] = useState(defaultTab);
159
+ const isViewMode = mode === 'view';
160
+
161
+ // Inherit API configuration from parent so nested fields (e.g. FileUpload) work inside tabs
162
+ const apiBaseUrl = (props as any).apiBaseUrl as string | undefined;
163
+ const resource = (props as any).resource as string | undefined;
164
+
165
+ // Get form context for validation errors and values (only in edit mode)
166
+ const formContext = mode === 'edit' ? useFormContext() : null;
167
+ const errors = formContext?.formState?.errors || {};
168
+ const submitCount = formContext?.formState?.submitCount ?? 0;
169
+ // Validation indicators (red dots) only appear after a submit attempt, so they
170
+ // never nag while the user is still filling the form.
171
+ const hasSubmitted = submitCount > 0;
172
+ // Use useWatch to get reactive form values
173
+ const formValues = mode === 'edit' && formContext ? useWatch({ control: formContext.control }) || {} : {};
174
+
175
+ // Normalize the serialized `tab` child nodes into render-friendly tab descriptors.
176
+ const tabs = useMemo(
177
+ () =>
178
+ (Array.isArray(tabNodes) ? tabNodes : []).map((node: any) => ({
179
+ label: node.label as string,
180
+ icon: node.icon as string | undefined,
181
+ schema: getChildComponents(node),
182
+ })),
183
+ [tabNodes],
184
+ );
185
+
186
+ // Find tabs with errors
187
+ const tabsWithErrors = useMemo(() => {
188
+ if (!tabs || isViewMode) return new Set<number>();
189
+
190
+ const errorTabs = new Set<number>();
191
+ tabs.forEach((tab, index) => {
192
+ if (hasTabErrors(tab.schema || [], errors)) {
193
+ errorTabs.add(index);
194
+ }
195
+ });
196
+ return errorTabs;
197
+ }, [tabs, errors, isViewMode]);
198
+
199
+ // Find tabs with required empty fields
200
+ const tabsWithRequiredEmpty = useMemo(() => {
201
+ if (!tabs || isViewMode) return new Set<number>();
202
+
203
+ const requiredTabs = new Set<number>();
204
+ tabs.forEach((tab, index) => {
205
+ if (hasRequiredEmptyFieldsInTab(tab.schema || [], formValues, operation)) {
206
+ requiredTabs.add(index);
207
+ }
208
+ });
209
+ return requiredTabs;
210
+ }, [tabs, formValues, isViewMode, operation]);
211
+
212
+ // Auto-open the first tab with validation errors — exactly ONCE per submit attempt.
213
+ // Keying on submitCount (not activeTab/errors) means the jump happens when the user
214
+ // submits and never again, so manual navigation afterwards is never hijacked.
215
+ const lastHandledSubmit = useRef(0);
216
+ useEffect(() => {
217
+ if (isViewMode || submitCount === 0 || submitCount === lastHandledSubmit.current) return;
218
+ lastHandledSubmit.current = submitCount;
219
+
220
+ const firstErrorTab = tabs.findIndex((_tab, index) => tabsWithErrors.has(index));
221
+ if (firstErrorTab !== -1) {
222
+ setActiveTab(firstErrorTab);
223
+ }
224
+ }, [submitCount, tabs, tabsWithErrors, isViewMode]);
225
+
226
+ // In view mode, use _recordData if provided, otherwise use value
227
+ const recordData = mode === 'view' && _recordData ? _recordData : value || {};
228
+
229
+ // Layout classes for tab field stacks
230
+ const fieldLayoutClasses = getFieldLayoutClasses(columns);
231
+
232
+ if (!tabs || tabs.length === 0) {
233
+ return null;
234
+ }
235
+
236
+ // In view mode, show tabs (read-only but can switch tabs)
237
+ if (isViewMode) {
238
+ return (
239
+ <div className="mb-6">
240
+ {description && <p className="mb-4 text-sm text-gray-600 dark:text-gray-400">{description}</p>}
241
+
242
+ {/* Tab Headers */}
243
+ <div className="flex border-b border-gray-200 dark:border-gray-700 mb-4 overflow-x-auto">
244
+ {tabs.map((tab, index) => (
245
+ <button
246
+ key={index}
247
+ type="button"
248
+ onClick={() => setActiveTab(index)}
249
+ className={cn(
250
+ 'px-4 py-2 font-medium text-sm whitespace-nowrap',
251
+ 'border-b-2 -mb-px',
252
+ 'focus:none',
253
+ activeTab === index
254
+ ? 'border-accent text-accent'
255
+ : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200',
256
+ )}
257
+ aria-selected={activeTab === index}
258
+ role="tab">
259
+ <span className="inline-flex items-center gap-2">
260
+ {tab.icon && <Icon name={tab.icon} className="w-4 h-4" />}
261
+ {tab.label}
262
+ </span>
263
+ </button>
264
+ ))}
265
+ </div>
266
+
267
+ {/* Tab Content */}
268
+ <div>
269
+ {tabs[activeTab] && (
270
+ <div className={fieldLayoutClasses}>
271
+ {tabs[activeTab].schema?.map((field: any, fieldIndex: number) => {
272
+ // Determine if this nested field/layout needs full record data context
273
+ const needsRecordData =
274
+ Boolean((field as any).needsRecordData) ||
275
+ Boolean(field.schema && Array.isArray(field.schema));
276
+
277
+ const nestedField = {
278
+ ...field,
279
+ disabled: disabled || field.disabled,
280
+ mode: mode || field.mode,
281
+ ...(needsRecordData && recordData ? { _recordData: recordData } : {}),
282
+ };
283
+
284
+ // For simple leaf fields with a name, pass their direct value in view mode
285
+ const nestedValue =
286
+ mode === 'view' && field.name && !needsRecordData
287
+ ? recordData[field.name]
288
+ : undefined;
289
+
290
+ return (
291
+ <FieldRenderer
292
+ key={fieldIndex}
293
+ field={nestedField}
294
+ mode={mode}
295
+ value={nestedValue}
296
+ apiBaseUrl={apiBaseUrl}
297
+ resource={resource}
298
+ operation={operation}
299
+ />
300
+ );
301
+ })}
302
+ </div>
303
+ )}
304
+ </div>
305
+ </div>
306
+ );
307
+ }
308
+
309
+ // Edit mode: render tabs with navigation
310
+ // Render all tabs but hide inactive ones so React Hook Form can validate them
311
+ return (
312
+ <div className="my-6">
313
+ {description && <p className="mb-4 text-sm text-gray-600 dark:text-gray-400">{description}</p>}
314
+
315
+ {/* Tab Headers */}
316
+ <div className="flex border-b border-gray-200 dark:border-gray-700 mb-4 overflow-x-auto">
317
+ {tabs.map((tab, index) => {
318
+ // Only flag tabs after a submit attempt, so the indicator reflects a real
319
+ // failed validation rather than nagging while the user is still filling in.
320
+ const showIndicator =
321
+ hasSubmitted && (tabsWithErrors.has(index) || tabsWithRequiredEmpty.has(index));
322
+ return (
323
+ <button
324
+ key={index}
325
+ type="button"
326
+ onClick={() => setActiveTab(index)}
327
+ disabled={disabled}
328
+ className={cn(
329
+ 'px-4 py-2 font-medium text-sm transition-colors whitespace-nowrap relative',
330
+ 'border-b-2 -mb-px',
331
+ 'focus:none',
332
+ disabled && 'opacity-60 cursor-not-allowed',
333
+ activeTab === index
334
+ ? 'border-accent text-accent '
335
+ : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200',
336
+ showIndicator && activeTab !== index && 'text-red-600 dark:text-red-400',
337
+ )}
338
+ aria-selected={activeTab === index}
339
+ role="tab">
340
+ <span className="inline-flex items-center gap-2">
341
+ {tab.icon && <Icon name={tab.icon} className="w-4 h-4" />}
342
+ {tab.label}
343
+ {showIndicator && (
344
+ <span
345
+ className="ml-1 w-2 h-2 rounded-full bg-red-500 dark:bg-red-400"
346
+ aria-label={translate('core:a11y.tab_errors')}
347
+ />
348
+ )}
349
+ </span>
350
+ </button>
351
+ );
352
+ })}
353
+ </div>
354
+
355
+ {/* Tab Content - Render all tabs but hide inactive ones for validation */}
356
+ {tabs.map((tab, tabIndex) => (
357
+ <div
358
+ key={tabIndex}
359
+ role="tabpanel"
360
+ aria-labelledby={`tab-${tabIndex}`}
361
+ className={activeTab === tabIndex ? '' : 'hidden'}>
362
+ <div className={fieldLayoutClasses}>
363
+ {tab.schema?.map((field: any, fieldIndex: number) => {
364
+ // Propagate disabled state and mode to nested fields
365
+ const nestedField = {
366
+ ...field,
367
+ disabled: disabled || field.disabled,
368
+ mode: mode || field.mode,
369
+ };
370
+ return (
371
+ <FieldRenderer
372
+ key={fieldIndex}
373
+ field={nestedField}
374
+ mode={mode}
375
+ apiBaseUrl={apiBaseUrl}
376
+ resource={resource}
377
+ operation={operation}
378
+ />
379
+ );
380
+ })}
381
+ </div>
382
+ </div>
383
+ ))}
384
+ </div>
385
+ );
386
+ }