@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.
- package/README.md +44 -0
- package/dist/FieldRenderer.d.ts +13 -0
- package/dist/FieldRenderer.js +62 -0
- package/dist/FormRenderer.d.ts +7 -0
- package/dist/FormRenderer.js +78 -0
- package/dist/TableRenderer.d.ts +2 -0
- package/dist/TableRenderer.js +1 -0
- package/dist/api/actionsApi.d.ts +23 -0
- package/dist/api/actionsApi.js +46 -0
- package/dist/api/authenticatedFetch.d.ts +8 -0
- package/dist/api/authenticatedFetch.js +31 -0
- package/dist/api/exportApi.d.ts +18 -0
- package/dist/api/exportApi.js +50 -0
- package/dist/api/http.d.ts +24 -0
- package/dist/api/http.js +52 -0
- package/dist/api/resourceApi.d.ts +37 -0
- package/dist/api/resourceApi.js +52 -0
- package/dist/api/tableApi.d.ts +83 -0
- package/dist/api/tableApi.js +51 -0
- package/dist/api/urls.d.ts +19 -0
- package/dist/api/urls.js +46 -0
- package/dist/app.d.ts +101 -0
- package/dist/app.js +89 -0
- package/dist/auth/AuthContext.d.ts +22 -0
- package/dist/auth/AuthContext.js +147 -0
- package/dist/auth/LoginPage.d.ts +10 -0
- package/dist/auth/LoginPage.js +179 -0
- package/dist/auth/ProtectedRoute.d.ts +12 -0
- package/dist/auth/ProtectedRoute.js +22 -0
- package/dist/auth/authApiClient.d.ts +24 -0
- package/dist/auth/authApiClient.js +95 -0
- package/dist/auth/types.d.ts +103 -0
- package/dist/auth/types.js +1 -0
- package/dist/components/ActionFormModal.d.ts +22 -0
- package/dist/components/ActionFormModal.js +8 -0
- package/dist/components/AdminPanel.d.ts +11 -0
- package/dist/components/AdminPanel.js +194 -0
- package/dist/components/Checkbox.d.ts +10 -0
- package/dist/components/Checkbox.js +8 -0
- package/dist/components/CheckboxField.d.ts +7 -0
- package/dist/components/CheckboxField.js +26 -0
- package/dist/components/ColorPickerField.d.ts +7 -0
- package/dist/components/ColorPickerField.js +26 -0
- package/dist/components/DateTimePickerField.d.ts +7 -0
- package/dist/components/DateTimePickerField.js +64 -0
- package/dist/components/FileUploadField.d.ts +9 -0
- package/dist/components/FileUploadField.js +478 -0
- package/dist/components/GlobalSearch.d.ts +22 -0
- package/dist/components/GlobalSearch.js +181 -0
- package/dist/components/GroupField.d.ts +7 -0
- package/dist/components/GroupField.js +23 -0
- package/dist/components/HiddenField.d.ts +3 -0
- package/dist/components/HiddenField.js +10 -0
- package/dist/components/ModalBreadcrumb.d.ts +5 -0
- package/dist/components/ModalBreadcrumb.js +33 -0
- package/dist/components/ModalDrawer.d.ts +15 -0
- package/dist/components/ModalDrawer.js +40 -0
- package/dist/components/RadioField.d.ts +7 -0
- package/dist/components/RadioField.js +26 -0
- package/dist/components/RepeaterField.d.ts +3 -0
- package/dist/components/RepeaterField.js +191 -0
- package/dist/components/ResourceModalRenderer.d.ts +10 -0
- package/dist/components/ResourceModalRenderer.js +80 -0
- package/dist/components/RichEditorField.d.ts +3 -0
- package/dist/components/RichEditorField.js +655 -0
- package/dist/components/SectionField.d.ts +9 -0
- package/dist/components/SectionField.js +111 -0
- package/dist/components/SelectField.d.ts +8 -0
- package/dist/components/SelectField.js +523 -0
- package/dist/components/TabsField.d.ts +10 -0
- package/dist/components/TabsField.js +214 -0
- package/dist/components/TagsInputField.d.ts +7 -0
- package/dist/components/TagsInputField.js +172 -0
- package/dist/components/TextInputField.d.ts +7 -0
- package/dist/components/TextInputField.js +44 -0
- package/dist/components/TextareaField.d.ts +7 -0
- package/dist/components/TextareaField.js +31 -0
- package/dist/components/ToggleField.d.ts +7 -0
- package/dist/components/ToggleField.js +57 -0
- package/dist/components/ViewModal.d.ts +25 -0
- package/dist/components/ViewModal.js +159 -0
- package/dist/components/blocks/BlockRenderer.d.ts +7 -0
- package/dist/components/blocks/BlockRenderer.js +36 -0
- package/dist/components/blocks/FormBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/FormBlockRenderer.js +110 -0
- package/dist/components/blocks/TableBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/TableBlockRenderer.js +12 -0
- package/dist/components/blocks/TabsBlockRenderer.d.ts +7 -0
- package/dist/components/blocks/TabsBlockRenderer.js +11 -0
- package/dist/components/blocks/WidgetBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/WidgetBlockRenderer.js +11 -0
- package/dist/components/columns/CheckboxColumnComponent.d.ts +6 -0
- package/dist/components/columns/CheckboxColumnComponent.js +21 -0
- package/dist/components/columns/ColorColumnComponent.d.ts +3 -0
- package/dist/components/columns/ColorColumnComponent.js +11 -0
- package/dist/components/columns/DeeplinkWrapper.d.ts +15 -0
- package/dist/components/columns/DeeplinkWrapper.js +85 -0
- package/dist/components/columns/IconColumnComponent.d.ts +3 -0
- package/dist/components/columns/IconColumnComponent.js +52 -0
- package/dist/components/columns/ImageColumnComponent.d.ts +3 -0
- package/dist/components/columns/ImageColumnComponent.js +98 -0
- package/dist/components/columns/MediaColumnComponent.d.ts +3 -0
- package/dist/components/columns/MediaColumnComponent.js +160 -0
- package/dist/components/columns/SelectColumnComponent.d.ts +6 -0
- package/dist/components/columns/SelectColumnComponent.js +26 -0
- package/dist/components/columns/TagsColumnComponent.d.ts +3 -0
- package/dist/components/columns/TagsColumnComponent.js +18 -0
- package/dist/components/columns/TextColumnComponent.d.ts +11 -0
- package/dist/components/columns/TextColumnComponent.js +107 -0
- package/dist/components/columns/TextInputColumnComponent.d.ts +6 -0
- package/dist/components/columns/TextInputColumnComponent.js +18 -0
- package/dist/components/columns/ToggleColumnComponent.d.ts +6 -0
- package/dist/components/columns/ToggleColumnComponent.js +25 -0
- package/dist/components/columns/VideoColumnComponent.d.ts +3 -0
- package/dist/components/columns/VideoColumnComponent.js +125 -0
- package/dist/components/columns/ViewColumnComponent.d.ts +3 -0
- package/dist/components/columns/ViewColumnComponent.js +7 -0
- package/dist/components/errors/ErrorBoundary.d.ts +23 -0
- package/dist/components/errors/ErrorBoundary.js +33 -0
- package/dist/components/filters/CustomFilterComponent.d.ts +10 -0
- package/dist/components/filters/CustomFilterComponent.js +33 -0
- package/dist/components/filters/DateFilterComponent.d.ts +15 -0
- package/dist/components/filters/DateFilterComponent.js +132 -0
- package/dist/components/filters/QueryBuilderFilterComponent.d.ts +11 -0
- package/dist/components/filters/QueryBuilderFilterComponent.js +200 -0
- package/dist/components/layout/Header.d.ts +10 -0
- package/dist/components/layout/Header.js +70 -0
- package/dist/components/layout/PanelBrandMark.d.ts +8 -0
- package/dist/components/layout/PanelBrandMark.js +28 -0
- package/dist/components/layout/Sidebar.d.ts +35 -0
- package/dist/components/layout/Sidebar.js +125 -0
- package/dist/components/modals/RelationCreateModal.d.ts +19 -0
- package/dist/components/modals/RelationCreateModal.js +57 -0
- package/dist/components/modals/ResourceFormModal.d.ts +37 -0
- package/dist/components/modals/ResourceFormModal.js +44 -0
- package/dist/components/modals/useResourceForm.d.ts +40 -0
- package/dist/components/modals/useResourceForm.js +138 -0
- package/dist/components/modals/view/RecordActions.d.ts +17 -0
- package/dist/components/modals/view/RecordActions.js +16 -0
- package/dist/components/modals/view/RecordDetails.d.ts +13 -0
- package/dist/components/modals/view/RecordDetails.js +29 -0
- package/dist/components/modals/view/RelationPanel.d.ts +18 -0
- package/dist/components/modals/view/RelationPanel.js +16 -0
- package/dist/components/modals/view/RelationTabs.d.ts +32 -0
- package/dist/components/modals/view/RelationTabs.js +42 -0
- package/dist/components/modals/view/useRecordView.d.ts +18 -0
- package/dist/components/modals/view/useRecordView.js +114 -0
- package/dist/components/pages/PageRenderer.d.ts +6 -0
- package/dist/components/pages/PageRenderer.js +107 -0
- package/dist/components/table/ColumnTogglePopup.d.ts +11 -0
- package/dist/components/table/ColumnTogglePopup.js +16 -0
- package/dist/components/table/GridCard.d.ts +21 -0
- package/dist/components/table/GridCard.js +30 -0
- package/dist/components/table/GridView.d.ts +23 -0
- package/dist/components/table/GridView.js +49 -0
- package/dist/components/table/LayoutToggle.d.ts +7 -0
- package/dist/components/table/LayoutToggle.js +9 -0
- package/dist/components/table/TableActionsDropdown.d.ts +13 -0
- package/dist/components/table/TableActionsDropdown.js +46 -0
- package/dist/components/table/TableBulkActions.d.ts +11 -0
- package/dist/components/table/TableBulkActions.js +21 -0
- package/dist/components/table/TableHeader.d.ts +14 -0
- package/dist/components/table/TableHeader.js +23 -0
- package/dist/components/table/TablePagination.d.ts +13 -0
- package/dist/components/table/TablePagination.js +55 -0
- package/dist/components/table/TableRow.d.ts +21 -0
- package/dist/components/table/TableRow.js +32 -0
- package/dist/components/table/TableSearchBar.d.ts +11 -0
- package/dist/components/table/TableSearchBar.js +12 -0
- package/dist/components/table/TableTabs.d.ts +14 -0
- package/dist/components/table/TableTabs.js +8 -0
- package/dist/components/ui/Badge.d.ts +6 -0
- package/dist/components/ui/Badge.js +12 -0
- package/dist/components/ui/Button.d.ts +22 -0
- package/dist/components/ui/Button.js +22 -0
- package/dist/components/ui/Card.d.ts +7 -0
- package/dist/components/ui/Card.js +5 -0
- package/dist/components/ui/ConfirmDialog.d.ts +19 -0
- package/dist/components/ui/ConfirmDialog.js +45 -0
- package/dist/components/ui/EmptyState.d.ts +9 -0
- package/dist/components/ui/EmptyState.js +6 -0
- package/dist/components/ui/ErrorAlert.d.ts +7 -0
- package/dist/components/ui/ErrorAlert.js +9 -0
- package/dist/components/ui/Input.d.ts +11 -0
- package/dist/components/ui/Input.js +10 -0
- package/dist/components/ui/Label.d.ts +5 -0
- package/dist/components/ui/Label.js +5 -0
- package/dist/components/ui/PillButton.d.ts +14 -0
- package/dist/components/ui/PillButton.js +19 -0
- package/dist/components/ui/Select.d.ts +7 -0
- package/dist/components/ui/Select.js +7 -0
- package/dist/components/ui/Spinner.d.ts +8 -0
- package/dist/components/ui/Spinner.js +14 -0
- package/dist/components/ui/Toast.d.ts +21 -0
- package/dist/components/ui/Toast.js +47 -0
- package/dist/components/ui/index.d.ts +24 -0
- package/dist/components/ui/index.js +12 -0
- package/dist/components/utils/HintDisplay.d.ts +11 -0
- package/dist/components/utils/HintDisplay.js +12 -0
- package/dist/components/utils/Icon.d.ts +22 -0
- package/dist/components/utils/Icon.js +22 -0
- package/dist/components/utils/MediaPreviewModal.d.ts +14 -0
- package/dist/components/utils/MediaPreviewModal.js +32 -0
- package/dist/components/utils/ViewFieldWrapper.d.ts +11 -0
- package/dist/components/utils/ViewFieldWrapper.js +9 -0
- package/dist/components/utils/layoutHelpers.d.ts +19 -0
- package/dist/components/utils/layoutHelpers.js +257 -0
- package/dist/components/widgets/ChartWidget.d.ts +16 -0
- package/dist/components/widgets/ChartWidget.js +192 -0
- package/dist/components/widgets/StatsWidget.d.ts +16 -0
- package/dist/components/widgets/StatsWidget.js +39 -0
- package/dist/components/widgets/WidgetRenderer.d.ts +10 -0
- package/dist/components/widgets/WidgetRenderer.js +50 -0
- package/dist/components/widgets/WidgetShell.d.ts +9 -0
- package/dist/components/widgets/WidgetShell.js +7 -0
- package/dist/contexts/AuthChallengeRegistryContext.d.ts +15 -0
- package/dist/contexts/AuthChallengeRegistryContext.js +15 -0
- package/dist/contexts/BlockRegistryContext.d.ts +18 -0
- package/dist/contexts/BlockRegistryContext.js +8 -0
- package/dist/contexts/ColumnRegistryContext.d.ts +8 -0
- package/dist/contexts/ColumnRegistryContext.js +30 -0
- package/dist/contexts/FieldRegistryContext.d.ts +13 -0
- package/dist/contexts/FieldRegistryContext.js +46 -0
- package/dist/contexts/PanelMetadataContext.d.ts +26 -0
- package/dist/contexts/PanelMetadataContext.js +26 -0
- package/dist/contexts/PanelProviders.d.ts +27 -0
- package/dist/contexts/PanelProviders.js +24 -0
- package/dist/contexts/ResourceModalContext.d.ts +26 -0
- package/dist/contexts/ResourceModalContext.js +76 -0
- package/dist/contexts/SlotRegistryContext.d.ts +19 -0
- package/dist/contexts/SlotRegistryContext.js +24 -0
- package/dist/contexts/TableRefreshContext.d.ts +10 -0
- package/dist/contexts/TableRefreshContext.js +30 -0
- package/dist/contexts/WidgetRegistryContext.d.ts +17 -0
- package/dist/contexts/WidgetRegistryContext.js +14 -0
- package/dist/contexts/createRegistryContext.d.ts +19 -0
- package/dist/contexts/createRegistryContext.js +20 -0
- package/dist/hooks/useAfterStateUpdated.d.ts +6 -0
- package/dist/hooks/useAfterStateUpdated.js +62 -0
- package/dist/hooks/useValidation.d.ts +26 -0
- package/dist/hooks/useValidation.js +76 -0
- package/dist/i18n/I18nProvider.d.ts +27 -0
- package/dist/i18n/I18nProvider.js +101 -0
- package/dist/i18n/LocaleSwitcher.d.ts +10 -0
- package/dist/i18n/LocaleSwitcher.js +30 -0
- package/dist/i18n/activeLocale.d.ts +11 -0
- package/dist/i18n/activeLocale.js +34 -0
- package/dist/i18n/buildClientI18n.d.ts +28 -0
- package/dist/i18n/buildClientI18n.js +67 -0
- package/dist/i18n/index.d.ts +11 -0
- package/dist/i18n/index.js +9 -0
- package/dist/i18n/locales/core/en.d.ts +225 -0
- package/dist/i18n/locales/core/en.js +252 -0
- package/dist/i18n/locales/core/index.d.ts +2 -0
- package/dist/i18n/locales/core/index.js +4 -0
- package/dist/i18n/locales/core/sq.d.ts +253 -0
- package/dist/i18n/locales/core/sq.js +255 -0
- package/dist/i18n/useFormatter.d.ts +18 -0
- package/dist/i18n/useFormatter.js +37 -0
- package/dist/i18n/useLocale.d.ts +11 -0
- package/dist/i18n/useLocale.js +11 -0
- package/dist/i18n/useTranslation.d.ts +12 -0
- package/dist/i18n/useTranslation.js +12 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +101 -0
- package/dist/pages/ResourceListPage.d.ts +8 -0
- package/dist/pages/ResourceListPage.js +139 -0
- package/dist/plugin.d.ts +79 -0
- package/dist/plugin.js +34 -0
- package/dist/runtime/conditions.d.ts +35 -0
- package/dist/runtime/conditions.js +97 -0
- package/dist/runtime/formTraversal.d.ts +25 -0
- package/dist/runtime/formTraversal.js +37 -0
- package/dist/runtime/serializedFunctions.d.ts +41 -0
- package/dist/runtime/serializedFunctions.js +264 -0
- package/dist/slots/Slot.d.ts +24 -0
- package/dist/slots/Slot.js +29 -0
- package/dist/slots/SlotCluster.d.ts +22 -0
- package/dist/slots/SlotCluster.js +49 -0
- package/dist/slots/index.d.ts +7 -0
- package/dist/slots/index.js +4 -0
- package/dist/slots/mergeSlots.d.ts +18 -0
- package/dist/slots/mergeSlots.js +35 -0
- package/dist/slots/types.d.ts +87 -0
- package/dist/slots/types.js +30 -0
- package/dist/styles.css +1 -0
- package/dist/table/TableContext.d.ts +36 -0
- package/dist/table/TableContext.js +13 -0
- package/dist/table/TableRenderer.d.ts +29 -0
- package/dist/table/TableRenderer.js +159 -0
- package/dist/table/components/FiltersPanel.d.ts +11 -0
- package/dist/table/components/FiltersPanel.js +52 -0
- package/dist/table/components/TableToolbar.d.ts +28 -0
- package/dist/table/components/TableToolbar.js +27 -0
- package/dist/table/components/TableToolbarButton.d.ts +6 -0
- package/dist/table/components/TableToolbarButton.js +9 -0
- package/dist/table/components/TableView.d.ts +12 -0
- package/dist/table/components/TableView.js +21 -0
- package/dist/table/defaultRowActions.d.ts +21 -0
- package/dist/table/defaultRowActions.js +37 -0
- package/dist/table/hooks/useColumnVisibility.d.ts +13 -0
- package/dist/table/hooks/useColumnVisibility.js +59 -0
- package/dist/table/hooks/useEditableRows.d.ts +22 -0
- package/dist/table/hooks/useEditableRows.js +63 -0
- package/dist/table/hooks/useTableActions.d.ts +54 -0
- package/dist/table/hooks/useTableActions.js +313 -0
- package/dist/table/hooks/useTableData.d.ts +28 -0
- package/dist/table/hooks/useTableData.js +63 -0
- package/dist/table/hooks/useTableLayout.d.ts +12 -0
- package/dist/table/hooks/useTableLayout.js +31 -0
- package/dist/table/hooks/useTableQuery.d.ts +29 -0
- package/dist/table/hooks/useTableQuery.js +135 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.js +6 -0
- package/dist/utils/classNames.d.ts +7 -0
- package/dist/utils/classNames.js +9 -0
- package/dist/utils/columnMediaDimensions.d.ts +13 -0
- package/dist/utils/columnMediaDimensions.js +29 -0
- package/dist/utils/columnVisibilityStorage.d.ts +22 -0
- package/dist/utils/columnVisibilityStorage.js +56 -0
- package/dist/utils/fieldErrors.d.ts +13 -0
- package/dist/utils/fieldErrors.js +25 -0
- package/dist/utils/formatValue.d.ts +28 -0
- package/dist/utils/formatValue.js +109 -0
- package/dist/utils/layoutStorage.d.ts +23 -0
- package/dist/utils/layoutStorage.js +53 -0
- package/dist/utils/redirectHandler.d.ts +7 -0
- package/dist/utils/redirectHandler.js +25 -0
- package/dist/utils/tableFormatters.d.ts +14 -0
- package/dist/utils/tableFormatters.js +93 -0
- package/dist/utils/widgetVisibilityStorage.d.ts +11 -0
- package/dist/utils/widgetVisibilityStorage.js +39 -0
- package/package.json +101 -0
- package/src/FieldRenderer.test.tsx +44 -0
- package/src/FieldRenderer.tsx +104 -0
- package/src/FormRenderer.containers.test.tsx +121 -0
- package/src/FormRenderer.test.tsx +174 -0
- package/src/FormRenderer.tsx +140 -0
- package/src/TableRenderer.tsx +2 -0
- package/src/api/actionsApi.ts +76 -0
- package/src/api/authenticatedFetch.ts +40 -0
- package/src/api/exportApi.ts +66 -0
- package/src/api/http.test.ts +58 -0
- package/src/api/http.ts +68 -0
- package/src/api/resourceApi.ts +88 -0
- package/src/api/tableApi.test.ts +108 -0
- package/src/api/tableApi.ts +107 -0
- package/src/api/urls.ts +50 -0
- package/src/app.test.tsx +67 -0
- package/src/app.tsx +181 -0
- package/src/auth/AuthContext.tsx +188 -0
- package/src/auth/LoginPage.tsx +380 -0
- package/src/auth/ProtectedRoute.tsx +39 -0
- package/src/auth/authApiClient.ts +109 -0
- package/src/auth/authFlow.test.tsx +168 -0
- package/src/auth/types.ts +104 -0
- package/src/components/ActionFormModal.tsx +45 -0
- package/src/components/AdminPanel.tsx +368 -0
- package/src/components/Checkbox.tsx +59 -0
- package/src/components/CheckboxField.tsx +88 -0
- package/src/components/ColorPickerField.tsx +93 -0
- package/src/components/DateTimePickerField.tsx +112 -0
- package/src/components/FileUploadField.tsx +841 -0
- package/src/components/GlobalSearch.tsx +436 -0
- package/src/components/GroupField.tsx +85 -0
- package/src/components/HiddenField.tsx +14 -0
- package/src/components/ModalBreadcrumb.tsx +74 -0
- package/src/components/ModalDrawer.tsx +137 -0
- package/src/components/RadioField.tsx +80 -0
- package/src/components/RepeaterField.tsx +546 -0
- package/src/components/ResourceModalRenderer.tsx +144 -0
- package/src/components/RichEditorField.tsx +942 -0
- package/src/components/SectionField.tsx +242 -0
- package/src/components/SelectField.tsx +843 -0
- package/src/components/TabsField.test.tsx +151 -0
- package/src/components/TabsField.tsx +386 -0
- package/src/components/TagsInputField.tsx +411 -0
- package/src/components/TextInputField.tsx +91 -0
- package/src/components/TextareaField.tsx +110 -0
- package/src/components/ToggleField.tsx +126 -0
- package/src/components/ViewModal.tsx +353 -0
- package/src/components/blocks/BlockRenderer.tsx +56 -0
- package/src/components/blocks/FormBlockRenderer.tsx +160 -0
- package/src/components/blocks/TableBlockRenderer.tsx +33 -0
- package/src/components/blocks/TabsBlockRenderer.tsx +49 -0
- package/src/components/blocks/WidgetBlockRenderer.tsx +19 -0
- package/src/components/columns/CheckboxColumnComponent.tsx +38 -0
- package/src/components/columns/ColorColumnComponent.tsx +23 -0
- package/src/components/columns/CustomColumn.test.tsx +55 -0
- package/src/components/columns/DeeplinkWrapper.tsx +103 -0
- package/src/components/columns/IconColumnComponent.tsx +55 -0
- package/src/components/columns/ImageColumnComponent.tsx +220 -0
- package/src/components/columns/MediaColumnComponent.tsx +294 -0
- package/src/components/columns/SelectColumnComponent.tsx +49 -0
- package/src/components/columns/TagsColumnComponent.tsx +46 -0
- package/src/components/columns/TextColumnComponent.tsx +191 -0
- package/src/components/columns/TextInputColumnComponent.tsx +35 -0
- package/src/components/columns/ToggleColumnComponent.tsx +56 -0
- package/src/components/columns/VideoColumnComponent.tsx +236 -0
- package/src/components/columns/ViewColumnComponent.tsx +9 -0
- package/src/components/errors/ErrorBoundary.tsx +58 -0
- package/src/components/filters/CustomFilterComponent.tsx +130 -0
- package/src/components/filters/DateFilterComponent.tsx +272 -0
- package/src/components/filters/QueryBuilderFilterComponent.tsx +502 -0
- package/src/components/layout/Header.tsx +212 -0
- package/src/components/layout/PanelBrandMark.tsx +61 -0
- package/src/components/layout/Sidebar.tsx +283 -0
- package/src/components/modals/RelationCreateModal.tsx +107 -0
- package/src/components/modals/ResourceFormModal.test.tsx +119 -0
- package/src/components/modals/ResourceFormModal.tsx +128 -0
- package/src/components/modals/useResourceForm.ts +207 -0
- package/src/components/modals/view/RecordActions.tsx +69 -0
- package/src/components/modals/view/RecordDetails.tsx +60 -0
- package/src/components/modals/view/RelationPanel.tsx +76 -0
- package/src/components/modals/view/RelationTabs.tsx +145 -0
- package/src/components/modals/view/useRecordView.ts +134 -0
- package/src/components/pages/PageRenderer.tsx +173 -0
- package/src/components/table/ColumnTogglePopup.tsx +85 -0
- package/src/components/table/GridCard.tsx +155 -0
- package/src/components/table/GridView.tsx +138 -0
- package/src/components/table/LayoutToggle.tsx +24 -0
- package/src/components/table/TableActionsDropdown.tsx +114 -0
- package/src/components/table/TableBulkActions.tsx +65 -0
- package/src/components/table/TableHeader.tsx +96 -0
- package/src/components/table/TablePagination.tsx +169 -0
- package/src/components/table/TableRow.tsx +155 -0
- package/src/components/table/TableSearchBar.tsx +66 -0
- package/src/components/table/TableTabs.tsx +49 -0
- package/src/components/ui/Badge.tsx +30 -0
- package/src/components/ui/Button.test.tsx +78 -0
- package/src/components/ui/Button.tsx +102 -0
- package/src/components/ui/Card.tsx +23 -0
- package/src/components/ui/ConfirmDialog.tsx +112 -0
- package/src/components/ui/EmptyState.tsx +24 -0
- package/src/components/ui/ErrorAlert.tsx +37 -0
- package/src/components/ui/Input.tsx +48 -0
- package/src/components/ui/Label.tsx +15 -0
- package/src/components/ui/PillButton.tsx +72 -0
- package/src/components/ui/Select.tsx +33 -0
- package/src/components/ui/Spinner.tsx +39 -0
- package/src/components/ui/Toast.tsx +105 -0
- package/src/components/ui/index.ts +24 -0
- package/src/components/utils/HintDisplay.tsx +26 -0
- package/src/components/utils/Icon.tsx +36 -0
- package/src/components/utils/MediaPreviewModal.tsx +114 -0
- package/src/components/utils/ViewFieldWrapper.tsx +23 -0
- package/src/components/utils/layoutHelpers.ts +267 -0
- package/src/components/widgets/ChartWidget.tsx +247 -0
- package/src/components/widgets/StatsWidget.tsx +72 -0
- package/src/components/widgets/WidgetRenderer.tsx +108 -0
- package/src/components/widgets/WidgetShell.tsx +37 -0
- package/src/contexts/AuthChallengeRegistryContext.tsx +29 -0
- package/src/contexts/BlockRegistryContext.tsx +28 -0
- package/src/contexts/ColumnRegistryContext.tsx +38 -0
- package/src/contexts/FieldRegistryContext.tsx +56 -0
- package/src/contexts/PanelMetadataContext.tsx +60 -0
- package/src/contexts/PanelProviders.tsx +85 -0
- package/src/contexts/ResourceModalContext.tsx +137 -0
- package/src/contexts/SlotRegistryContext.tsx +35 -0
- package/src/contexts/TableRefreshContext.tsx +44 -0
- package/src/contexts/WidgetRegistryContext.tsx +34 -0
- package/src/contexts/createRegistryContext.tsx +29 -0
- package/src/hooks/useAfterStateUpdated.ts +70 -0
- package/src/hooks/useValidation.test.ts +59 -0
- package/src/hooks/useValidation.ts +95 -0
- package/src/i18n/I18nProvider.tsx +128 -0
- package/src/i18n/LocaleSwitcher.tsx +50 -0
- package/src/i18n/activeLocale.ts +39 -0
- package/src/i18n/buildClientI18n.ts +101 -0
- package/src/i18n/i18n.test.tsx +140 -0
- package/src/i18n/index.ts +12 -0
- package/src/i18n/locales/core/en.ts +274 -0
- package/src/i18n/locales/core/index.ts +5 -0
- package/src/i18n/locales/core/sq.ts +275 -0
- package/src/i18n/useFormatter.ts +42 -0
- package/src/i18n/useLocale.ts +16 -0
- package/src/i18n/useTranslation.ts +17 -0
- package/src/index.ts +244 -0
- package/src/pages/ResourceListPage.tsx +205 -0
- package/src/plugin.ts +110 -0
- package/src/runtime/conditions.test.ts +99 -0
- package/src/runtime/conditions.ts +148 -0
- package/src/runtime/formTraversal.ts +41 -0
- package/src/runtime/serializedFunctions.test.ts +59 -0
- package/src/runtime/serializedFunctions.ts +284 -0
- package/src/slots/Slot.test.tsx +89 -0
- package/src/slots/Slot.tsx +47 -0
- package/src/slots/SlotCluster.test.tsx +95 -0
- package/src/slots/SlotCluster.tsx +107 -0
- package/src/slots/index.ts +15 -0
- package/src/slots/mergeSlots.test.ts +71 -0
- package/src/slots/mergeSlots.ts +40 -0
- package/src/slots/slotNames.test.ts +21 -0
- package/src/slots/types.ts +119 -0
- package/src/styles.css +437 -0
- package/src/table/TableContext.tsx +41 -0
- package/src/table/TableRenderer.test.tsx +197 -0
- package/src/table/TableRenderer.tsx +390 -0
- package/src/table/components/FiltersPanel.tsx +193 -0
- package/src/table/components/TableToolbar.tsx +153 -0
- package/src/table/components/TableToolbarButton.tsx +14 -0
- package/src/table/components/TableView.tsx +106 -0
- package/src/table/defaultRowActions.ts +43 -0
- package/src/table/hooks/useColumnVisibility.test.ts +51 -0
- package/src/table/hooks/useColumnVisibility.ts +71 -0
- package/src/table/hooks/useEditableRows.test.ts +69 -0
- package/src/table/hooks/useEditableRows.ts +89 -0
- package/src/table/hooks/useTableActions.ts +393 -0
- package/src/table/hooks/useTableData.ts +89 -0
- package/src/table/hooks/useTableLayout.ts +45 -0
- package/src/table/hooks/useTableQuery.test.ts +116 -0
- package/src/table/hooks/useTableQuery.ts +172 -0
- package/src/test/mockFetch.ts +67 -0
- package/src/test/setup.ts +25 -0
- package/src/types/index.ts +228 -0
- package/src/utils/classNames.ts +10 -0
- package/src/utils/columnMediaDimensions.ts +45 -0
- package/src/utils/columnVisibilityStorage.ts +55 -0
- package/src/utils/fieldErrors.test.ts +35 -0
- package/src/utils/fieldErrors.ts +27 -0
- package/src/utils/formatValue.test.tsx +65 -0
- package/src/utils/formatValue.tsx +117 -0
- package/src/utils/layoutStorage.ts +52 -0
- package/src/utils/redirectHandler.ts +29 -0
- package/src/utils/tableFormatters.test.ts +54 -0
- package/src/utils/tableFormatters.ts +104 -0
- package/src/utils/widgetVisibilityStorage.ts +38 -0
- package/tailwind.config.js +9 -0
- package/vite.config.ts +17 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FieldProps } from '../types';
|
|
3
|
+
import { SerializedComponent } from '@maxal_studio/kratosjs';
|
|
4
|
+
export declare function TabsField({ schema: tabNodes, defaultTab, columns, mode, disabled, value, _recordData, description, operation, ...props }: FieldProps & {
|
|
5
|
+
/** Tab child components (each a serialized `tab` node with label/icon/schema). */
|
|
6
|
+
schema?: SerializedComponent[];
|
|
7
|
+
defaultTab?: number;
|
|
8
|
+
_recordData?: any;
|
|
9
|
+
operation?: 'create' | 'edit' | 'view';
|
|
10
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { useFormContext, useWatch } from 'react-hook-form';
|
|
4
|
+
import { getFieldError } from '../utils/fieldErrors';
|
|
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 { evaluateCondition } from '../runtime/conditions';
|
|
10
|
+
import { getChildComponents, isArrayScope, someComponent } from '../runtime/formTraversal';
|
|
11
|
+
import { translate } from '../i18n/activeLocale';
|
|
12
|
+
const isEmptyValue = (value) => value === undefined || value === null || value === '';
|
|
13
|
+
/**
|
|
14
|
+
* Recursively check if a component or its nested components have validation errors.
|
|
15
|
+
* Generic over the children contract — works for any container, including plugins.
|
|
16
|
+
*/
|
|
17
|
+
function hasComponentErrors(component, errors) {
|
|
18
|
+
return someComponent(component, node => {
|
|
19
|
+
if (!node.name)
|
|
20
|
+
return false;
|
|
21
|
+
const fieldError = getFieldError(errors, node.name);
|
|
22
|
+
if (!fieldError)
|
|
23
|
+
return false;
|
|
24
|
+
// Array-scope containers (repeaters): the array itself or any item may have errors.
|
|
25
|
+
if (isArrayScope(node)) {
|
|
26
|
+
if (Array.isArray(fieldError)) {
|
|
27
|
+
return fieldError.some(itemErrors => itemErrors && Object.keys(itemErrors).length > 0);
|
|
28
|
+
}
|
|
29
|
+
return Object.keys(fieldError).length > 0;
|
|
30
|
+
}
|
|
31
|
+
// Ignore pure "required" errors here; required-emptiness is handled separately
|
|
32
|
+
// (based on current values) so a filled field doesn't keep its tab marked.
|
|
33
|
+
return fieldError.type !== 'required';
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if a tab has any validation errors in its schema
|
|
38
|
+
*/
|
|
39
|
+
function hasTabErrors(tabSchema, errors) {
|
|
40
|
+
return tabSchema.some(component => hasComponentErrors(component, errors));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a component is required
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Check if a component is required, evaluating conditional required logic
|
|
47
|
+
*/
|
|
48
|
+
function isComponentRequired(component, formValues, operation) {
|
|
49
|
+
// Check direct required property
|
|
50
|
+
const requiredProp = component.required;
|
|
51
|
+
// If required is a boolean
|
|
52
|
+
if (typeof requiredProp === 'boolean') {
|
|
53
|
+
return requiredProp;
|
|
54
|
+
}
|
|
55
|
+
// If required is a function (serialized), evaluate it
|
|
56
|
+
if (typeof requiredProp === 'string') {
|
|
57
|
+
return evaluateCondition(requiredProp, formValues, operation);
|
|
58
|
+
}
|
|
59
|
+
// Check validation rules for 'required' on the serialized validation object
|
|
60
|
+
const validation = component.validation;
|
|
61
|
+
if (validation && Array.isArray(validation.rules)) {
|
|
62
|
+
// Check for simple 'required' string rule
|
|
63
|
+
if (validation.rules.some(rule => rule === 'required')) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
// Check for conditional required rule objects
|
|
67
|
+
for (const rule of validation.rules) {
|
|
68
|
+
if (typeof rule === 'object' && rule.rule === 'required') {
|
|
69
|
+
// If there's a condition function, evaluate it
|
|
70
|
+
const condition = rule.condition;
|
|
71
|
+
if (typeof condition === 'string') {
|
|
72
|
+
return evaluateCondition(condition, formValues, operation);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Recursively check if a component (or its descendants) is required and empty.
|
|
82
|
+
* Generic over the children contract.
|
|
83
|
+
*/
|
|
84
|
+
function hasRequiredEmptyField(component, formValues, operation) {
|
|
85
|
+
return someComponent(component, node => {
|
|
86
|
+
if (!node.name)
|
|
87
|
+
return false;
|
|
88
|
+
// Array-scope containers (repeaters): check required-empty fields within each item.
|
|
89
|
+
if (isArrayScope(node)) {
|
|
90
|
+
const items = formValues[node.name];
|
|
91
|
+
if (!Array.isArray(items))
|
|
92
|
+
return false;
|
|
93
|
+
const template = getChildComponents(node);
|
|
94
|
+
return items.some(item => template.some(field => field.name &&
|
|
95
|
+
isComponentRequired(field, item, operation) &&
|
|
96
|
+
isEmptyValue(item[field.name])));
|
|
97
|
+
}
|
|
98
|
+
if (isComponentRequired(node, formValues, operation)) {
|
|
99
|
+
return isEmptyValue(formValues[node.name]);
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if a tab has any required empty fields
|
|
106
|
+
*/
|
|
107
|
+
function hasRequiredEmptyFieldsInTab(tabSchema, formValues, operation) {
|
|
108
|
+
return tabSchema.some(component => hasRequiredEmptyField(component, formValues, operation));
|
|
109
|
+
}
|
|
110
|
+
export function TabsField({ schema: tabNodes, defaultTab = 0, columns, mode, disabled, value, _recordData, description, operation, ...props }) {
|
|
111
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
112
|
+
const isViewMode = mode === 'view';
|
|
113
|
+
// Inherit API configuration from parent so nested fields (e.g. FileUpload) work inside tabs
|
|
114
|
+
const apiBaseUrl = props.apiBaseUrl;
|
|
115
|
+
const resource = props.resource;
|
|
116
|
+
// Get form context for validation errors and values (only in edit mode)
|
|
117
|
+
const formContext = mode === 'edit' ? useFormContext() : null;
|
|
118
|
+
const errors = formContext?.formState?.errors || {};
|
|
119
|
+
const submitCount = formContext?.formState?.submitCount ?? 0;
|
|
120
|
+
// Validation indicators (red dots) only appear after a submit attempt, so they
|
|
121
|
+
// never nag while the user is still filling the form.
|
|
122
|
+
const hasSubmitted = submitCount > 0;
|
|
123
|
+
// Use useWatch to get reactive form values
|
|
124
|
+
const formValues = mode === 'edit' && formContext ? useWatch({ control: formContext.control }) || {} : {};
|
|
125
|
+
// Normalize the serialized `tab` child nodes into render-friendly tab descriptors.
|
|
126
|
+
const tabs = useMemo(() => (Array.isArray(tabNodes) ? tabNodes : []).map((node) => ({
|
|
127
|
+
label: node.label,
|
|
128
|
+
icon: node.icon,
|
|
129
|
+
schema: getChildComponents(node),
|
|
130
|
+
})), [tabNodes]);
|
|
131
|
+
// Find tabs with errors
|
|
132
|
+
const tabsWithErrors = useMemo(() => {
|
|
133
|
+
if (!tabs || isViewMode)
|
|
134
|
+
return new Set();
|
|
135
|
+
const errorTabs = new Set();
|
|
136
|
+
tabs.forEach((tab, index) => {
|
|
137
|
+
if (hasTabErrors(tab.schema || [], errors)) {
|
|
138
|
+
errorTabs.add(index);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return errorTabs;
|
|
142
|
+
}, [tabs, errors, isViewMode]);
|
|
143
|
+
// Find tabs with required empty fields
|
|
144
|
+
const tabsWithRequiredEmpty = useMemo(() => {
|
|
145
|
+
if (!tabs || isViewMode)
|
|
146
|
+
return new Set();
|
|
147
|
+
const requiredTabs = new Set();
|
|
148
|
+
tabs.forEach((tab, index) => {
|
|
149
|
+
if (hasRequiredEmptyFieldsInTab(tab.schema || [], formValues, operation)) {
|
|
150
|
+
requiredTabs.add(index);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
return requiredTabs;
|
|
154
|
+
}, [tabs, formValues, isViewMode, operation]);
|
|
155
|
+
// Auto-open the first tab with validation errors — exactly ONCE per submit attempt.
|
|
156
|
+
// Keying on submitCount (not activeTab/errors) means the jump happens when the user
|
|
157
|
+
// submits and never again, so manual navigation afterwards is never hijacked.
|
|
158
|
+
const lastHandledSubmit = useRef(0);
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (isViewMode || submitCount === 0 || submitCount === lastHandledSubmit.current)
|
|
161
|
+
return;
|
|
162
|
+
lastHandledSubmit.current = submitCount;
|
|
163
|
+
const firstErrorTab = tabs.findIndex((_tab, index) => tabsWithErrors.has(index));
|
|
164
|
+
if (firstErrorTab !== -1) {
|
|
165
|
+
setActiveTab(firstErrorTab);
|
|
166
|
+
}
|
|
167
|
+
}, [submitCount, tabs, tabsWithErrors, isViewMode]);
|
|
168
|
+
// In view mode, use _recordData if provided, otherwise use value
|
|
169
|
+
const recordData = mode === 'view' && _recordData ? _recordData : value || {};
|
|
170
|
+
// Layout classes for tab field stacks
|
|
171
|
+
const fieldLayoutClasses = getFieldLayoutClasses(columns);
|
|
172
|
+
if (!tabs || tabs.length === 0) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
// In view mode, show tabs (read-only but can switch tabs)
|
|
176
|
+
if (isViewMode) {
|
|
177
|
+
return (_jsxs("div", { className: "mb-6", children: [description && _jsx("p", { className: "mb-4 text-sm text-gray-600 dark:text-gray-400", children: description }), _jsx("div", { className: "flex border-b border-gray-200 dark:border-gray-700 mb-4 overflow-x-auto", children: tabs.map((tab, index) => (_jsx("button", { type: "button", onClick: () => setActiveTab(index), className: cn('px-4 py-2 font-medium text-sm whitespace-nowrap', 'border-b-2 -mb-px', 'focus:none', activeTab === index
|
|
178
|
+
? 'border-accent text-accent'
|
|
179
|
+
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'), "aria-selected": activeTab === index, role: "tab", children: _jsxs("span", { className: "inline-flex items-center gap-2", children: [tab.icon && _jsx(Icon, { name: tab.icon, className: "w-4 h-4" }), tab.label] }) }, index))) }), _jsx("div", { children: tabs[activeTab] && (_jsx("div", { className: fieldLayoutClasses, children: tabs[activeTab].schema?.map((field, fieldIndex) => {
|
|
180
|
+
// Determine if this nested field/layout needs full record data context
|
|
181
|
+
const needsRecordData = Boolean(field.needsRecordData) ||
|
|
182
|
+
Boolean(field.schema && Array.isArray(field.schema));
|
|
183
|
+
const nestedField = {
|
|
184
|
+
...field,
|
|
185
|
+
disabled: disabled || field.disabled,
|
|
186
|
+
mode: mode || field.mode,
|
|
187
|
+
...(needsRecordData && recordData ? { _recordData: recordData } : {}),
|
|
188
|
+
};
|
|
189
|
+
// For simple leaf fields with a name, pass their direct value in view mode
|
|
190
|
+
const nestedValue = mode === 'view' && field.name && !needsRecordData
|
|
191
|
+
? recordData[field.name]
|
|
192
|
+
: undefined;
|
|
193
|
+
return (_jsx(FieldRenderer, { field: nestedField, mode: mode, value: nestedValue, apiBaseUrl: apiBaseUrl, resource: resource, operation: operation }, fieldIndex));
|
|
194
|
+
}) })) })] }));
|
|
195
|
+
}
|
|
196
|
+
// Edit mode: render tabs with navigation
|
|
197
|
+
// Render all tabs but hide inactive ones so React Hook Form can validate them
|
|
198
|
+
return (_jsxs("div", { className: "my-6", children: [description && _jsx("p", { className: "mb-4 text-sm text-gray-600 dark:text-gray-400", children: description }), _jsx("div", { className: "flex border-b border-gray-200 dark:border-gray-700 mb-4 overflow-x-auto", children: tabs.map((tab, index) => {
|
|
199
|
+
// Only flag tabs after a submit attempt, so the indicator reflects a real
|
|
200
|
+
// failed validation rather than nagging while the user is still filling in.
|
|
201
|
+
const showIndicator = hasSubmitted && (tabsWithErrors.has(index) || tabsWithRequiredEmpty.has(index));
|
|
202
|
+
return (_jsx("button", { type: "button", onClick: () => setActiveTab(index), disabled: disabled, className: cn('px-4 py-2 font-medium text-sm transition-colors whitespace-nowrap relative', 'border-b-2 -mb-px', 'focus:none', disabled && 'opacity-60 cursor-not-allowed', activeTab === index
|
|
203
|
+
? 'border-accent text-accent '
|
|
204
|
+
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200', showIndicator && activeTab !== index && 'text-red-600 dark:text-red-400'), "aria-selected": activeTab === index, role: "tab", children: _jsxs("span", { className: "inline-flex items-center gap-2", children: [tab.icon && _jsx(Icon, { name: tab.icon, className: "w-4 h-4" }), tab.label, showIndicator && (_jsx("span", { className: "ml-1 w-2 h-2 rounded-full bg-red-500 dark:bg-red-400", "aria-label": translate('core:a11y.tab_errors') }))] }) }, index));
|
|
205
|
+
}) }), tabs.map((tab, tabIndex) => (_jsx("div", { role: "tabpanel", "aria-labelledby": `tab-${tabIndex}`, className: activeTab === tabIndex ? '' : 'hidden', children: _jsx("div", { className: fieldLayoutClasses, children: tab.schema?.map((field, fieldIndex) => {
|
|
206
|
+
// Propagate disabled state and mode to nested fields
|
|
207
|
+
const nestedField = {
|
|
208
|
+
...field,
|
|
209
|
+
disabled: disabled || field.disabled,
|
|
210
|
+
mode: mode || field.mode,
|
|
211
|
+
};
|
|
212
|
+
return (_jsx(FieldRenderer, { field: nestedField, mode: mode, apiBaseUrl: apiBaseUrl, resource: resource, operation: operation }, fieldIndex));
|
|
213
|
+
}) }) }, tabIndex)))] }));
|
|
214
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { useFormContext, Controller } from 'react-hook-form';
|
|
4
|
+
import { useValidation } from '../hooks/useValidation';
|
|
5
|
+
import { HintDisplay } from './utils/HintDisplay';
|
|
6
|
+
import { ViewFieldWrapper } from './utils/ViewFieldWrapper';
|
|
7
|
+
import { cn } from '../utils/classNames';
|
|
8
|
+
import { X, GripVertical, List } from 'lucide-react';
|
|
9
|
+
import { translate } from '../i18n/activeLocale';
|
|
10
|
+
/** View mode: trigger + popup when showInPopup is true */
|
|
11
|
+
function TagsViewPopup({ tags, renderHtml }) {
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
13
|
+
const count = tags.length;
|
|
14
|
+
const label = count === 1 ? '1 tag' : `${count} tags`;
|
|
15
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => setOpen(true), className: cn('inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium', 'bg-accent-soft text-fg', 'hover:opacity-90 transition-opacity'), children: [_jsx(List, { className: "w-4 h-4 shrink-0" }), label] }), open && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-base opacity-80", "aria-hidden": true, onClick: () => setOpen(false) }), _jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": translate('core:tags.label'), className: cn('fixed left-1/2 top-1/2 z-50 w-[min(90vw,28rem)] max-h-[70vh] -translate-x-1/2 -translate-y-1/2', 'bg-surface rounded-xl shadow-xl border border-border', 'flex flex-col overflow-hidden'), children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border shrink-0", children: [_jsx("span", { className: "text-sm font-medium text-fg", children: label }), _jsx("button", { type: "button", onClick: () => setOpen(false), className: "p-1 rounded-lg text-fg-secondary hover:bg-hover transition-colors", "aria-label": translate('core:modal.close'), children: _jsx(X, { className: "w-5 h-5" }) })] }), _jsx("div", { className: "p-4 overflow-y-auto flex flex-wrap gap-2", children: tags.map((tag, index) => (_jsx("span", { className: "inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-accent-soft text-fg", children: renderHtml ? (_jsx("span", { className: "kratosjshtml-content", dangerouslySetInnerHTML: { __html: String(tag) } })) : (String(tag)) }, index))) })] })] }))] }));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* TagsInput field component
|
|
19
|
+
* Renders a tags input for managing arrays of simple values
|
|
20
|
+
*/
|
|
21
|
+
export function TagsInputField(props) {
|
|
22
|
+
const { name, label, helperText, hint, hintIcon, hintColor, placeholder, disabled = false, mode, value, separator = ',', suggestions = [], minItems, maxItems, addable = true, deletable = true, reorderable = true, showInPopup = false, } = props;
|
|
23
|
+
// Determine if field should be treated as required (for minItems fallback)
|
|
24
|
+
let isRequired = props.required === true;
|
|
25
|
+
const validationRules = props.validation?.rules;
|
|
26
|
+
if (validationRules && Array.isArray(validationRules)) {
|
|
27
|
+
if (validationRules.some(rule => rule === 'required' || (typeof rule === 'object' && rule.rule === 'required'))) {
|
|
28
|
+
isRequired = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Effective minItems: honor explicit minItems, otherwise infer from required
|
|
32
|
+
const effectiveMinItems = minItems !== undefined ? minItems : isRequired ? 1 : undefined;
|
|
33
|
+
const [inputValue, setInputValue] = useState('');
|
|
34
|
+
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
35
|
+
const [draggedIndex, setDraggedIndex] = useState(null);
|
|
36
|
+
const [dragOverIndex, setDragOverIndex] = useState(null);
|
|
37
|
+
// View mode: render as comma-separated list or badges (or popup when showInPopup)
|
|
38
|
+
if (mode === 'view') {
|
|
39
|
+
const tags = Array.isArray(value) ? value : [];
|
|
40
|
+
if (tags.length === 0) {
|
|
41
|
+
return _jsx(ViewFieldWrapper, { label: label, children: "-" });
|
|
42
|
+
}
|
|
43
|
+
const renderHtml = props.renderHtml === true;
|
|
44
|
+
if (showInPopup) {
|
|
45
|
+
return (_jsx(ViewFieldWrapper, { label: label, children: _jsx(TagsViewPopup, { tags: tags, renderHtml: renderHtml }) }));
|
|
46
|
+
}
|
|
47
|
+
return (_jsx(ViewFieldWrapper, { label: label, children: _jsx("div", { className: "flex flex-wrap gap-2", children: tags.map((tag, index) => (_jsx("span", { className: "inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-accent-soft text-accent", children: renderHtml ? (_jsx("span", { className: "kratosjshtml-content", dangerouslySetInnerHTML: { __html: String(tag) } })) : (String(tag)) }, index))) }) }));
|
|
48
|
+
}
|
|
49
|
+
const { control } = useFormContext();
|
|
50
|
+
const validation = useValidation(props.validation?.rules || [], props.operation, props.name);
|
|
51
|
+
// Additional validation for minItems/maxItems
|
|
52
|
+
const tagsValidation = React.useMemo(() => {
|
|
53
|
+
const rules = { ...validation };
|
|
54
|
+
if (effectiveMinItems && effectiveMinItems > 0) {
|
|
55
|
+
rules.validate = {
|
|
56
|
+
...(rules.validate || {}),
|
|
57
|
+
minItems: (value) => {
|
|
58
|
+
const length = Array.isArray(value) ? value.length : 0;
|
|
59
|
+
return length >= effectiveMinItems || `At least ${effectiveMinItems} item(s) are required`;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (maxItems && maxItems > 0) {
|
|
64
|
+
rules.validate = {
|
|
65
|
+
...(rules.validate || {}),
|
|
66
|
+
maxItems: (value) => {
|
|
67
|
+
const length = Array.isArray(value) ? value.length : 0;
|
|
68
|
+
return length <= maxItems || `Maximum ${maxItems} item(s) allowed`;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return rules;
|
|
73
|
+
}, [validation, effectiveMinItems, maxItems]);
|
|
74
|
+
return (_jsx(Controller, { name: name, control: control, rules: tagsValidation, render: ({ field: { value: fieldValue, onChange }, fieldState: { error } }) => {
|
|
75
|
+
const tags = Array.isArray(fieldValue) ? fieldValue : [];
|
|
76
|
+
const hasError = !!error;
|
|
77
|
+
const addTag = (tag) => {
|
|
78
|
+
const trimmedTag = tag.trim();
|
|
79
|
+
if (!trimmedTag)
|
|
80
|
+
return;
|
|
81
|
+
if (tags.includes(trimmedTag))
|
|
82
|
+
return;
|
|
83
|
+
if (maxItems && tags.length >= maxItems)
|
|
84
|
+
return;
|
|
85
|
+
onChange([...tags, trimmedTag]);
|
|
86
|
+
setInputValue('');
|
|
87
|
+
};
|
|
88
|
+
const removeTag = (index) => {
|
|
89
|
+
if (!deletable || disabled)
|
|
90
|
+
return;
|
|
91
|
+
if (effectiveMinItems && tags.length <= effectiveMinItems)
|
|
92
|
+
return;
|
|
93
|
+
const newTags = tags.filter((_, i) => i !== index);
|
|
94
|
+
onChange(newTags);
|
|
95
|
+
};
|
|
96
|
+
const handleDragStart = (e, index) => {
|
|
97
|
+
if (!reorderable || disabled)
|
|
98
|
+
return;
|
|
99
|
+
setDraggedIndex(index);
|
|
100
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
101
|
+
};
|
|
102
|
+
const handleDragOver = (e, index) => {
|
|
103
|
+
if (!reorderable || disabled || draggedIndex === null)
|
|
104
|
+
return;
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
e.dataTransfer.dropEffect = 'move';
|
|
107
|
+
setDragOverIndex(index);
|
|
108
|
+
};
|
|
109
|
+
const handleDragLeave = () => {
|
|
110
|
+
setDragOverIndex(null);
|
|
111
|
+
};
|
|
112
|
+
const handleDrop = (e, dropIndex) => {
|
|
113
|
+
if (!reorderable || disabled || draggedIndex === null)
|
|
114
|
+
return;
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
if (draggedIndex === dropIndex) {
|
|
117
|
+
setDraggedIndex(null);
|
|
118
|
+
setDragOverIndex(null);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const newTags = [...tags];
|
|
122
|
+
const [movedTag] = newTags.splice(draggedIndex, 1);
|
|
123
|
+
newTags.splice(dropIndex, 0, movedTag);
|
|
124
|
+
onChange(newTags);
|
|
125
|
+
setDraggedIndex(null);
|
|
126
|
+
setDragOverIndex(null);
|
|
127
|
+
};
|
|
128
|
+
const handleDragEnd = () => {
|
|
129
|
+
setDraggedIndex(null);
|
|
130
|
+
setDragOverIndex(null);
|
|
131
|
+
};
|
|
132
|
+
const handleKeyDown = (e) => {
|
|
133
|
+
if (e.key === 'Enter' || e.key === separator) {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
addTag(inputValue);
|
|
136
|
+
}
|
|
137
|
+
else if (e.key === 'Backspace' && !inputValue && tags.length > 0) {
|
|
138
|
+
// Remove last tag when backspace on empty input
|
|
139
|
+
removeTag(tags.length - 1);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const handleBlur = () => {
|
|
143
|
+
// Add remaining input as tag on blur
|
|
144
|
+
if (inputValue.trim()) {
|
|
145
|
+
addTag(inputValue);
|
|
146
|
+
}
|
|
147
|
+
setShowSuggestions(false);
|
|
148
|
+
};
|
|
149
|
+
const filteredSuggestions = suggestions.filter(s => s.toLowerCase().includes(inputValue.toLowerCase()) && !tags.includes(s));
|
|
150
|
+
const canAdd = addable && !disabled && (maxItems === undefined || tags.length < maxItems);
|
|
151
|
+
return (_jsxs("div", { className: "mb-4", children: [label && (_jsxs("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: [label, validation.required && _jsx("span", { className: "text-red-500 dark:text-red-400 ml-1", children: "*" })] })), _jsxs("div", { className: cn('w-full min-h-[42px] px-3 py-2 border rounded-lg shadow-sm', 'k-input', 'focus-within:outline-none focus-within:ring-2 focus-within:ring-ring ', 'transition duration-150 ease-in-out', hasError &&
|
|
152
|
+
'border-red-500 dark:border-red-400 focus-within:ring-red-500 dark:focus-within:ring-red-400', disabled && 'opacity-60 cursor-not-allowed'), children: [_jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [tags.map((tag, index) => (_jsxs("span", { draggable: reorderable && !disabled, onDragStart: e => handleDragStart(e, index), onDragOver: e => handleDragOver(e, index), onDragLeave: handleDragLeave, onDrop: e => handleDrop(e, index), onDragEnd: handleDragEnd, className: cn('inline-flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium bg-accent-soft text-accent', 'transition-all duration-150', reorderable && !disabled && 'cursor-move hover:bg-accent/20', draggedIndex === index && 'opacity-50', dragOverIndex === index &&
|
|
153
|
+
draggedIndex !== index &&
|
|
154
|
+
'scale-105 ring-2 ring-ring'), children: [reorderable && !disabled && _jsx(GripVertical, { className: "w-3 h-3 opacity-40" }), String(tag), deletable &&
|
|
155
|
+
!disabled &&
|
|
156
|
+
(!effectiveMinItems || tags.length > effectiveMinItems) && (_jsx("button", { type: "button", onClick: () => removeTag(index), className: "ml-1 hover:bg-accent/20 rounded-full p-0.5 transition-colors", "aria-label": `Remove ${tag}`, children: _jsx(X, { className: "w-3 h-3" }) }))] }, index))), canAdd && (_jsx("input", { type: "text", value: inputValue, onChange: e => {
|
|
157
|
+
setInputValue(e.target.value);
|
|
158
|
+
if (suggestions.length > 0) {
|
|
159
|
+
setShowSuggestions(true);
|
|
160
|
+
}
|
|
161
|
+
}, onKeyDown: handleKeyDown, onBlur: handleBlur, onFocus: () => {
|
|
162
|
+
if (suggestions.length > 0) {
|
|
163
|
+
setShowSuggestions(true);
|
|
164
|
+
}
|
|
165
|
+
}, placeholder: tags.length === 0
|
|
166
|
+
? placeholder || `Enter values separated by ${separator}`
|
|
167
|
+
: '', disabled: disabled, className: "flex-1 min-w-[120px] outline-none bg-transparent" }))] }), showSuggestions && filteredSuggestions.length > 0 && (_jsx("div", { className: "relative", children: _jsx("div", { className: "absolute z-10 mt-2 w-full bg-white dark:bg-gray-800 border border-border rounded-lg shadow-lg max-h-48 overflow-y-auto", children: filteredSuggestions.map((suggestion, index) => (_jsx("button", { type: "button", onMouseDown: e => e.preventDefault(), onClick: () => {
|
|
168
|
+
addTag(suggestion);
|
|
169
|
+
setShowSuggestions(false);
|
|
170
|
+
}, className: "w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-sm transition-colors", children: suggestion }, index))) }) }))] }), hasError && (_jsx("p", { className: "mt-1 text-sm text-red-600 dark:text-red-400", children: error?.message })), helperText && _jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: helperText }), !hasError && _jsx(HintDisplay, { hint: hint, hintIcon: hintIcon, hintColor: hintColor }), (effectiveMinItems !== undefined || maxItems !== undefined) && (_jsxs("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: [effectiveMinItems !== undefined && `Minimum: ${effectiveMinItems}`, effectiveMinItems !== undefined && maxItems !== undefined && ' • ', maxItems !== undefined && `Maximum: ${maxItems}`] }))] }));
|
|
171
|
+
} }));
|
|
172
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFormContext } from 'react-hook-form';
|
|
3
|
+
import { getFieldError } from '../utils/fieldErrors';
|
|
4
|
+
import { useValidation } from '../hooks/useValidation';
|
|
5
|
+
import { cn } from '../utils/classNames';
|
|
6
|
+
import { HintDisplay } from './utils/HintDisplay';
|
|
7
|
+
import { ViewFieldWrapper } from './utils/ViewFieldWrapper';
|
|
8
|
+
import { formatNumber } from '../utils/formatValue';
|
|
9
|
+
/**
|
|
10
|
+
* TextInput field component
|
|
11
|
+
* Renders text, email, password, number, tel, and url inputs
|
|
12
|
+
*/
|
|
13
|
+
export function TextInputField(props) {
|
|
14
|
+
// View mode: render formatted display
|
|
15
|
+
if (props.mode === 'view') {
|
|
16
|
+
const value = props.value;
|
|
17
|
+
let displayValue = value;
|
|
18
|
+
// Format numbers if inputType is numeric
|
|
19
|
+
if (props.inputType === 'number' && value !== null && value !== undefined) {
|
|
20
|
+
displayValue = formatNumber(value);
|
|
21
|
+
}
|
|
22
|
+
else if (value === null || value === undefined) {
|
|
23
|
+
displayValue = '-';
|
|
24
|
+
}
|
|
25
|
+
else if (props.renderHtml && (typeof value === 'string' || typeof value === 'number')) {
|
|
26
|
+
displayValue = _jsx("div", { className: "kratosjshtml-content", dangerouslySetInnerHTML: { __html: String(value) } });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// If is object, convert it into a ul list
|
|
30
|
+
if (typeof value === 'object') {
|
|
31
|
+
displayValue = (_jsx("div", { className: "space-y-2", children: Object.entries(value).map(([key, value]) => (_jsxs("div", { children: [_jsx("b", { children: key }), " : ", typeof value === 'object' ? JSON.stringify(value) : String(value)] }, key))) }));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
displayValue = String(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return _jsx(ViewFieldWrapper, { label: props.label, children: displayValue });
|
|
38
|
+
}
|
|
39
|
+
const { register, formState: { errors }, } = useFormContext();
|
|
40
|
+
const validation = useValidation(props.validation?.rules || [], props.operation, props.name);
|
|
41
|
+
const error = getFieldError(errors, props.name);
|
|
42
|
+
const hasError = !!error;
|
|
43
|
+
return (_jsxs("div", { className: "mb-4", children: [props.label && (_jsxs("label", { htmlFor: props.name, className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: [props.label, validation.required && _jsx("span", { className: "text-red-500 dark:text-red-400 ml-1", children: "*" })] })), _jsx("input", { id: props.name, type: props.inputType || 'text', ...register(props.name, validation), placeholder: props.placeholder, disabled: props.disabled, readOnly: props.readOnly, className: cn('w-full px-3 py-2 border rounded-lg shadow-sm', 'k-input', 'focus:outline-none focus:ring-2 focus:ring-ring', 'transition duration-150 ease-in-out', hasError && 'border-red-500 dark:border-red-400 focus:ring-red-500 dark:focus:ring-red-400', props.disabled && 'opacity-60 cursor-not-allowed', props.readOnly && 'bg-muted') }), hasError && _jsx("p", { className: "mt-1 text-sm text-red-600 dark:text-red-400", children: error?.message }), props.helperText && _jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: props.helperText }), !hasError && _jsx(HintDisplay, { hint: props.hint, hintIcon: props.hintIcon, hintColor: props.hintColor })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFormContext, useWatch } from 'react-hook-form';
|
|
3
|
+
import { getFieldError } from '../utils/fieldErrors';
|
|
4
|
+
import { useValidation } from '../hooks/useValidation';
|
|
5
|
+
import { cn } from '../utils/classNames';
|
|
6
|
+
import { HintDisplay } from './utils/HintDisplay';
|
|
7
|
+
import { ViewFieldWrapper } from './utils/ViewFieldWrapper';
|
|
8
|
+
import { formatTextarea } from '../utils/formatValue';
|
|
9
|
+
/**
|
|
10
|
+
* Textarea field component
|
|
11
|
+
* Renders multi-line text input with character count
|
|
12
|
+
*/
|
|
13
|
+
export function TextareaField(props) {
|
|
14
|
+
// View mode: render formatted display
|
|
15
|
+
if (props.mode === 'view') {
|
|
16
|
+
const value = props.value;
|
|
17
|
+
const displayValue = props.renderHtml && value != null && (typeof value === 'string' || typeof value === 'number') ? (_jsx("div", { className: "kratosjshtml-content", dangerouslySetInnerHTML: { __html: String(value) } })) : (formatTextarea(value));
|
|
18
|
+
return _jsx(ViewFieldWrapper, { label: props.label, children: displayValue });
|
|
19
|
+
}
|
|
20
|
+
const { register, control, formState: { errors }, } = useFormContext();
|
|
21
|
+
const validation = useValidation(props.validation?.rules || [], props.operation, props.name);
|
|
22
|
+
const error = getFieldError(errors, props.name);
|
|
23
|
+
const hasError = !!error;
|
|
24
|
+
// Watch the field value for character count
|
|
25
|
+
const value = useWatch({ control, name: props.name, defaultValue: '' });
|
|
26
|
+
const currentLength = value?.length || 0;
|
|
27
|
+
const maxLength = props.maxLength;
|
|
28
|
+
return (_jsxs("div", { className: "mb-4", children: [props.label && (_jsxs("label", { htmlFor: props.name, className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: [props.label, validation.required && _jsx("span", { className: "text-red-500 dark:text-red-400 ml-1", children: "*" })] })), _jsx("textarea", { id: props.name, ...register(props.name, validation), placeholder: props.placeholder, disabled: props.disabled, readOnly: props.readOnly, rows: props.rows || 4, cols: props.cols, className: cn('w-full px-3 py-2 border rounded-lg shadow-sm', 'k-input', 'focus:outline-none focus:ring-2 focus:ring-ring', 'transition duration-150 ease-in-out', 'resize-y', hasError && 'border-red-500 dark:border-red-400 focus:ring-red-500 dark:focus:ring-red-400', props.disabled && 'opacity-60 cursor-not-allowed', props.readOnly && 'bg-muted') }), maxLength && (_jsxs("div", { className: "mt-1 flex justify-between items-center", children: [_jsxs("div", { className: "flex-1", children: [hasError && (_jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: error?.message })), props.helperText && !hasError && (_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: props.helperText })), !hasError && (_jsx(HintDisplay, { hint: props.hint, hintIcon: props.hintIcon, hintColor: props.hintColor }))] }), _jsxs("p", { className: cn('text-xs ml-2', currentLength > maxLength
|
|
29
|
+
? 'text-red-600 dark:text-red-400 font-medium'
|
|
30
|
+
: 'text-gray-500 dark:text-gray-400'), children: [currentLength, " / ", maxLength] })] })), !maxLength && hasError && (_jsx("p", { className: "mt-1 text-sm text-red-600 dark:text-red-400", children: error?.message })), !maxLength && props.helperText && (_jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: props.helperText })), !maxLength && !hasError && (_jsx(HintDisplay, { hint: props.hint, hintIcon: props.hintIcon, hintColor: props.hintColor }))] }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFormContext, useWatch } from 'react-hook-form';
|
|
3
|
+
import { getFieldError } from '../utils/fieldErrors';
|
|
4
|
+
import { useValidation } from '../hooks/useValidation';
|
|
5
|
+
import { cn } from '../utils/classNames';
|
|
6
|
+
import { Icon } from './utils/Icon';
|
|
7
|
+
import { HintDisplay } from './utils/HintDisplay';
|
|
8
|
+
import { ViewFieldWrapper } from './utils/ViewFieldWrapper';
|
|
9
|
+
import { formatBoolean } from '../utils/formatValue';
|
|
10
|
+
/**
|
|
11
|
+
* Toggle field component
|
|
12
|
+
* Renders a toggle switch with custom colors
|
|
13
|
+
*/
|
|
14
|
+
export function ToggleField(props) {
|
|
15
|
+
// View mode: render formatted display
|
|
16
|
+
if (props.mode === 'view') {
|
|
17
|
+
const value = props.value;
|
|
18
|
+
const displayValue = formatBoolean(value);
|
|
19
|
+
const isChecked = Boolean(value);
|
|
20
|
+
const onColor = 'text-green-600';
|
|
21
|
+
const offColor = 'text-red-600';
|
|
22
|
+
return (_jsx(ViewFieldWrapper, { label: props.label, children: _jsx("div", { className: "flex items-center gap-3", children: _jsx("span", { className: cn(isChecked ? onColor : offColor), children: displayValue }) }) }));
|
|
23
|
+
}
|
|
24
|
+
const { register, control, formState: { errors }, } = useFormContext();
|
|
25
|
+
const validation = useValidation(props.validation?.rules || [], props.operation, props.name);
|
|
26
|
+
const error = getFieldError(errors, props.name);
|
|
27
|
+
const hasError = !!error;
|
|
28
|
+
// Watch the toggle state
|
|
29
|
+
const isChecked = useWatch({ control, name: props.name, defaultValue: props.default || false });
|
|
30
|
+
// Map color names to Tailwind classes
|
|
31
|
+
const getColorClass = (color) => {
|
|
32
|
+
const colorMap = {
|
|
33
|
+
success: 'bg-green-600 dark:bg-green-500',
|
|
34
|
+
danger: 'bg-red-600 dark:bg-red-500',
|
|
35
|
+
warning: 'bg-yellow-600 dark:bg-yellow-500',
|
|
36
|
+
info: 'bg-accent',
|
|
37
|
+
primary: 'bg-accent',
|
|
38
|
+
};
|
|
39
|
+
return colorMap[color || ''] || 'bg-accent';
|
|
40
|
+
};
|
|
41
|
+
// Map semantic color names to Tailwind classes; pass through full class strings as-is
|
|
42
|
+
const resolveTrackColor = (color, fallback) => {
|
|
43
|
+
if (!color)
|
|
44
|
+
return fallback.split(/\s+/);
|
|
45
|
+
if (/\b(bg|text)-/.test(color))
|
|
46
|
+
return color.split(/\s+/);
|
|
47
|
+
return getColorClass(color).split(/\s+/);
|
|
48
|
+
};
|
|
49
|
+
const onColor = resolveTrackColor(props.onColor, 'bg-accent');
|
|
50
|
+
const offColor = resolveTrackColor(props.offColor, 'bg-gray-200 dark:bg-gray-700');
|
|
51
|
+
return (_jsxs("div", { className: "mb-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "flex-1", children: props.label && (_jsxs("label", { htmlFor: props.name, className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: [props.label, validation.required && _jsx("span", { className: "text-red-500 dark:text-red-400 ml-1", children: "*" })] })) }), _jsx("button", { type: "button", role: "switch", "aria-checked": isChecked, disabled: props.disabled, onClick: () => {
|
|
52
|
+
const checkbox = document.getElementById(props.name);
|
|
53
|
+
if (checkbox) {
|
|
54
|
+
checkbox.click();
|
|
55
|
+
}
|
|
56
|
+
}, className: cn('relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent', 'transition-colors duration-200 ease-in-out', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 dark:focus:ring-offset-gray-900', ...(isChecked ? onColor : offColor), props.disabled && 'opacity-60 cursor-not-allowed'), children: _jsxs("span", { className: cn('pointer-events-none inline-flex items-center justify-center h-5 w-5 transform rounded-full bg-white dark:bg-gray-200 shadow ring-0', 'transition duration-200 ease-in-out', isChecked ? 'translate-x-5' : 'translate-x-0'), children: [props.onIcon && isChecked && _jsx(Icon, { name: props.onIcon, size: 12, className: "text-green-600" }), props.offIcon && !isChecked && (_jsx(Icon, { name: props.offIcon, size: 12, className: "text-gray-400" }))] }) }), _jsx("input", { id: props.name, type: "checkbox", ...register(props.name, validation), className: "sr-only" })] }), hasError && _jsx("p", { className: "mt-1 text-sm text-red-600 dark:text-red-400", children: error?.message }), props.helperText && _jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: props.helperText }), !hasError && _jsx(HintDisplay, { hint: props.hint, hintIcon: props.hintIcon, hintColor: props.hintColor })] }));
|
|
57
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SerializedForm } from '@maxal_studio/kratosjs';
|
|
3
|
+
import { SerializedRelation } from '../types';
|
|
4
|
+
export type { SerializedRelation };
|
|
5
|
+
interface ViewModalProps {
|
|
6
|
+
/** Whether the modal is open */
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
/** Function to close the modal */
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
/** Resource slug (e.g., 'users', 'posts') */
|
|
11
|
+
resourceSlug: string;
|
|
12
|
+
/** Display name for the resource */
|
|
13
|
+
resourceName: string;
|
|
14
|
+
/** ID of the record to view */
|
|
15
|
+
recordId: string;
|
|
16
|
+
/** API base URL */
|
|
17
|
+
apiBaseUrl?: string;
|
|
18
|
+
/** Form schema for displaying fields */
|
|
19
|
+
formSchema: SerializedForm;
|
|
20
|
+
/** Stacking depth */
|
|
21
|
+
depth?: number;
|
|
22
|
+
/** Close all modals */
|
|
23
|
+
onCloseAll?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function ViewModal({ isOpen, onClose, resourceSlug, resourceName, recordId, apiBaseUrl, formSchema, depth, onCloseAll, }: ViewModalProps): React.JSX.Element;
|