@pilotiq/pilotiq 0.7.1 → 0.8.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/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +154 -0
- package/CLAUDE.md +59 -3
- package/dist/Pilotiq.d.ts +83 -0
- package/dist/Pilotiq.d.ts.map +1 -1
- package/dist/Pilotiq.js +39 -0
- package/dist/Pilotiq.js.map +1 -1
- package/dist/actions/Action.d.ts +27 -99
- package/dist/actions/Action.d.ts.map +1 -1
- package/dist/actions/Action.js +52 -754
- package/dist/actions/Action.js.map +1 -1
- package/dist/actions/bulkFactories.d.ts +46 -0
- package/dist/actions/bulkFactories.d.ts.map +1 -0
- package/dist/actions/bulkFactories.js +144 -0
- package/dist/actions/bulkFactories.js.map +1 -0
- package/dist/actions/crudFactories.d.ts +94 -0
- package/dist/actions/crudFactories.d.ts.map +1 -0
- package/dist/actions/crudFactories.js +209 -0
- package/dist/actions/crudFactories.js.map +1 -0
- package/dist/actions/factoryHelpers.d.ts +108 -0
- package/dist/actions/factoryHelpers.d.ts.map +1 -0
- package/dist/actions/factoryHelpers.js +138 -0
- package/dist/actions/factoryHelpers.js.map +1 -0
- package/dist/actions/m2mFactories.d.ts +47 -0
- package/dist/actions/m2mFactories.d.ts.map +1 -0
- package/dist/actions/m2mFactories.js +173 -0
- package/dist/actions/m2mFactories.js.map +1 -0
- package/dist/actions/relationFactories.d.ts +93 -0
- package/dist/actions/relationFactories.d.ts.map +1 -0
- package/dist/actions/relationFactories.js +321 -0
- package/dist/actions/relationFactories.js.map +1 -0
- package/dist/elements/dispatchForm.js +1 -1
- package/dist/elements/dispatchForm.js.map +1 -1
- package/dist/elements/dispatchTable.js +1 -1
- package/dist/elements/dispatchTable.js.map +1 -1
- package/dist/fields/Field.d.ts +31 -0
- package/dist/fields/Field.d.ts.map +1 -1
- package/dist/fields/Field.js +25 -0
- package/dist/fields/Field.js.map +1 -1
- package/dist/pageData/breadcrumbs.d.ts +42 -0
- package/dist/pageData/breadcrumbs.d.ts.map +1 -0
- package/dist/pageData/breadcrumbs.js +172 -0
- package/dist/pageData/breadcrumbs.js.map +1 -0
- package/dist/pageData/forms.d.ts +137 -0
- package/dist/pageData/forms.d.ts.map +1 -0
- package/dist/pageData/forms.js +427 -0
- package/dist/pageData/forms.js.map +1 -0
- package/dist/pageData/helpers.d.ts +239 -0
- package/dist/pageData/helpers.d.ts.map +1 -0
- package/dist/pageData/helpers.js +703 -0
- package/dist/pageData/helpers.js.map +1 -0
- package/dist/pageData/misc.d.ts +76 -0
- package/dist/pageData/misc.d.ts.map +1 -0
- package/dist/pageData/misc.js +263 -0
- package/dist/pageData/misc.js.map +1 -0
- package/dist/pageData/navigation.d.ts +292 -0
- package/dist/pageData/navigation.d.ts.map +1 -0
- package/dist/pageData/navigation.js +591 -0
- package/dist/pageData/navigation.js.map +1 -0
- package/dist/pageData/relationPages.d.ts +172 -0
- package/dist/pageData/relationPages.d.ts.map +1 -0
- package/dist/pageData/relationPages.js +867 -0
- package/dist/pageData/relationPages.js.map +1 -0
- package/dist/pageData/relationTabs.d.ts +65 -0
- package/dist/pageData/relationTabs.d.ts.map +1 -0
- package/dist/pageData/relationTabs.js +258 -0
- package/dist/pageData/relationTabs.js.map +1 -0
- package/dist/pageData/resourcePages.d.ts +48 -0
- package/dist/pageData/resourcePages.d.ts.map +1 -0
- package/dist/pageData/resourcePages.js +504 -0
- package/dist/pageData/resourcePages.js.map +1 -0
- package/dist/pageData.d.ts +12 -792
- package/dist/pageData.d.ts.map +1 -1
- package/dist/pageData.js +24 -3797
- package/dist/pageData.js.map +1 -1
- package/dist/react/AppShell.d.ts +8 -0
- package/dist/react/AppShell.d.ts.map +1 -1
- package/dist/react/AppShell.js +11 -1
- package/dist/react/AppShell.js.map +1 -1
- package/dist/react/CollabExtensionFactoryRegistry.d.ts +47 -0
- package/dist/react/CollabExtensionFactoryRegistry.d.ts.map +1 -0
- package/dist/react/CollabExtensionFactoryRegistry.js +14 -0
- package/dist/react/CollabExtensionFactoryRegistry.js.map +1 -0
- package/dist/react/CollabRoomContext.d.ts +37 -0
- package/dist/react/CollabRoomContext.d.ts.map +1 -0
- package/dist/react/CollabRoomContext.js +12 -0
- package/dist/react/CollabRoomContext.js.map +1 -0
- package/dist/react/FormCollabBindingRegistry.d.ts +62 -0
- package/dist/react/FormCollabBindingRegistry.d.ts.map +1 -0
- package/dist/react/FormCollabBindingRegistry.js +14 -0
- package/dist/react/FormCollabBindingRegistry.js.map +1 -0
- package/dist/react/RecordWrapperGate.d.ts +25 -0
- package/dist/react/RecordWrapperGate.d.ts.map +1 -0
- package/dist/react/RecordWrapperGate.js +30 -0
- package/dist/react/RecordWrapperGate.js.map +1 -0
- package/dist/react/RecordWrapperRegistry.d.ts +31 -0
- package/dist/react/RecordWrapperRegistry.d.ts.map +1 -0
- package/dist/react/RecordWrapperRegistry.js +15 -0
- package/dist/react/RecordWrapperRegistry.js.map +1 -0
- package/dist/react/SchemaRenderer.d.ts +17 -23
- package/dist/react/SchemaRenderer.d.ts.map +1 -1
- package/dist/react/SchemaRenderer.js +71 -3647
- package/dist/react/SchemaRenderer.js.map +1 -1
- package/dist/react/component-slots.d.ts +103 -0
- package/dist/react/component-slots.d.ts.map +1 -0
- package/dist/react/component-slots.js +18 -0
- package/dist/react/component-slots.js.map +1 -0
- package/dist/react/fields/BuilderInput.d.ts.map +1 -1
- package/dist/react/fields/BuilderInput.js +21 -117
- package/dist/react/fields/BuilderInput.js.map +1 -1
- package/dist/react/fields/MarkdownInput.d.ts.map +1 -1
- package/dist/react/fields/MarkdownInput.js +1 -3
- package/dist/react/fields/MarkdownInput.js.map +1 -1
- package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
- package/dist/react/fields/RepeaterInput.js +22 -127
- package/dist/react/fields/RepeaterInput.js.map +1 -1
- package/dist/react/fields/rowState.d.ts +40 -0
- package/dist/react/fields/rowState.d.ts.map +1 -0
- package/dist/react/fields/rowState.js +60 -0
- package/dist/react/fields/rowState.js.map +1 -0
- package/dist/react/fields/useRowReorderDnd.d.ts +28 -0
- package/dist/react/fields/useRowReorderDnd.d.ts.map +1 -0
- package/dist/react/fields/useRowReorderDnd.js +51 -0
- package/dist/react/fields/useRowReorderDnd.js.map +1 -0
- package/dist/react/index.d.ts +9 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +8 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/layouts/SidebarLayout.d.ts +1 -1
- package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
- package/dist/react/layouts/SidebarLayout.js +10 -2
- package/dist/react/layouts/SidebarLayout.js.map +1 -1
- package/dist/react/layouts/TopbarLayout.d.ts +1 -1
- package/dist/react/layouts/TopbarLayout.d.ts.map +1 -1
- package/dist/react/layouts/TopbarLayout.js +19 -11
- package/dist/react/layouts/TopbarLayout.js.map +1 -1
- package/dist/react/parseRecordEditUrl.d.ts +29 -0
- package/dist/react/parseRecordEditUrl.d.ts.map +1 -0
- package/dist/react/parseRecordEditUrl.js +25 -0
- package/dist/react/parseRecordEditUrl.js.map +1 -0
- package/dist/react/persistedState.d.ts +19 -0
- package/dist/react/persistedState.d.ts.map +1 -0
- package/dist/react/persistedState.js +51 -0
- package/dist/react/persistedState.js.map +1 -0
- package/dist/react/schemaRenderer/AlertRenderer.d.ts +12 -0
- package/dist/react/schemaRenderer/AlertRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/AlertRenderer.js +61 -0
- package/dist/react/schemaRenderer/AlertRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/EntryRenderer.d.ts +13 -0
- package/dist/react/schemaRenderer/EntryRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/EntryRenderer.js +277 -0
- package/dist/react/schemaRenderer/EntryRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/SectionRenderer.d.ts +16 -0
- package/dist/react/schemaRenderer/SectionRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/SectionRenderer.js +62 -0
- package/dist/react/schemaRenderer/SectionRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/SimpleElements.d.ts +25 -0
- package/dist/react/schemaRenderer/SimpleElements.d.ts.map +1 -0
- package/dist/react/schemaRenderer/SimpleElements.js +147 -0
- package/dist/react/schemaRenderer/SimpleElements.js.map +1 -0
- package/dist/react/schemaRenderer/TabsRenderer.d.ts +17 -0
- package/dist/react/schemaRenderer/TabsRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/TabsRenderer.js +31 -0
- package/dist/react/schemaRenderer/TabsRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/WizardRenderer.d.ts +34 -0
- package/dist/react/schemaRenderer/WizardRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/WizardRenderer.js +208 -0
- package/dist/react/schemaRenderer/WizardRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts +21 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.js +82 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.js.map +1 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts +30 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.js +182 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.js.map +1 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts +17 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.js +19 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.js.map +1 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts +16 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.js +16 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.js.map +1 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.d.ts +22 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.js +26 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.js.map +1 -0
- package/dist/react/schemaRenderer/action/buttons.d.ts +18 -0
- package/dist/react/schemaRenderer/action/buttons.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/buttons.js +74 -0
- package/dist/react/schemaRenderer/action/buttons.js.map +1 -0
- package/dist/react/schemaRenderer/action/helpers.d.ts +26 -0
- package/dist/react/schemaRenderer/action/helpers.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/helpers.js +126 -0
- package/dist/react/schemaRenderer/action/helpers.js.map +1 -0
- package/dist/react/schemaRenderer/action/renderAction.d.ts +21 -0
- package/dist/react/schemaRenderer/action/renderAction.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/renderAction.js +102 -0
- package/dist/react/schemaRenderer/action/renderAction.js.map +1 -0
- package/dist/react/schemaRenderer/columnFormat.d.ts +10 -0
- package/dist/react/schemaRenderer/columnFormat.d.ts.map +1 -0
- package/dist/react/schemaRenderer/columnFormat.js +76 -0
- package/dist/react/schemaRenderer/columnFormat.js.map +1 -0
- package/dist/react/schemaRenderer/constants.d.ts +8 -0
- package/dist/react/schemaRenderer/constants.d.ts.map +1 -0
- package/dist/react/schemaRenderer/constants.js +45 -0
- package/dist/react/schemaRenderer/constants.js.map +1 -0
- package/dist/react/schemaRenderer/form/FormRenderer.d.ts +29 -0
- package/dist/react/schemaRenderer/form/FormRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/form/FormRenderer.js +152 -0
- package/dist/react/schemaRenderer/form/FormRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/form/renderField.d.ts +6 -0
- package/dist/react/schemaRenderer/form/renderField.d.ts.map +1 -0
- package/dist/react/schemaRenderer/form/renderField.js +239 -0
- package/dist/react/schemaRenderer/form/renderField.js.map +1 -0
- package/dist/react/schemaRenderer/helpers.d.ts +32 -0
- package/dist/react/schemaRenderer/helpers.d.ts.map +1 -0
- package/dist/react/schemaRenderer/helpers.js +52 -0
- package/dist/react/schemaRenderer/helpers.js.map +1 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts +60 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.js +189 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.js.map +1 -0
- package/dist/react/schemaRenderer/table/TableRenderer.d.ts +29 -0
- package/dist/react/schemaRenderer/table/TableRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/TableRenderer.js +85 -0
- package/dist/react/schemaRenderer/table/TableRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.d.ts +18 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.js +555 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.js.map +1 -0
- package/dist/react/schemaRenderer/table/filters.d.ts +263 -0
- package/dist/react/schemaRenderer/table/filters.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/filters.js +497 -0
- package/dist/react/schemaRenderer/table/filters.js.map +1 -0
- package/dist/react/schemaRenderer/table/formatCell.d.ts +11 -0
- package/dist/react/schemaRenderer/table/formatCell.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/formatCell.js +172 -0
- package/dist/react/schemaRenderer/table/formatCell.js.map +1 -0
- package/dist/react/schemaRenderer/table/links.d.ts +42 -0
- package/dist/react/schemaRenderer/table/links.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/links.js +55 -0
- package/dist/react/schemaRenderer/table/links.js.map +1 -0
- package/dist/react/schemaRenderer/table/renderRowActions.d.ts +13 -0
- package/dist/react/schemaRenderer/table/renderRowActions.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/renderRowActions.js +25 -0
- package/dist/react/schemaRenderer/table/renderRowActions.js.map +1 -0
- package/dist/react/schemaRenderer/table/url.d.ts +41 -0
- package/dist/react/schemaRenderer/table/url.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/url.js +114 -0
- package/dist/react/schemaRenderer/table/url.js.map +1 -0
- package/dist/routes/globals.d.ts +13 -0
- package/dist/routes/globals.d.ts.map +1 -0
- package/dist/routes/globals.js +131 -0
- package/dist/routes/globals.js.map +1 -0
- package/dist/routes/helpers.d.ts +217 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +498 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/pages.d.ts +15 -0
- package/dist/routes/pages.d.ts.map +1 -0
- package/dist/routes/pages.js +145 -0
- package/dist/routes/pages.js.map +1 -0
- package/dist/routes/panel.d.ts +19 -0
- package/dist/routes/panel.d.ts.map +1 -0
- package/dist/routes/panel.js +191 -0
- package/dist/routes/panel.js.map +1 -0
- package/dist/routes/relations.d.ts +21 -0
- package/dist/routes/relations.d.ts.map +1 -0
- package/dist/routes/relations.js +1239 -0
- package/dist/routes/relations.js.map +1 -0
- package/dist/routes/resources.d.ts +28 -0
- package/dist/routes/resources.d.ts.map +1 -0
- package/dist/routes/resources.js +741 -0
- package/dist/routes/resources.js.map +1 -0
- package/dist/routes/theme.d.ts +12 -0
- package/dist/routes/theme.d.ts.map +1 -0
- package/dist/routes/theme.js +82 -0
- package/dist/routes/theme.js.map +1 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +64 -3078
- package/dist/routes.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +31 -10
- package/dist/vite.js.map +1 -1
- package/package.json +2 -1
- package/src/Pilotiq.ts +95 -0
- package/src/actions/Action.ts +79 -723
- package/src/actions/bulkFactories.ts +168 -0
- package/src/actions/crudFactories.ts +220 -0
- package/src/actions/factoryHelpers.ts +177 -0
- package/src/actions/m2mFactories.ts +193 -0
- package/src/actions/relationFactories.ts +372 -0
- package/src/elements/dispatchForm.ts +1 -1
- package/src/elements/dispatchTable.ts +1 -1
- package/src/fields/Field.ts +39 -0
- package/src/pageData/breadcrumbs.ts +288 -0
- package/src/pageData/forms.ts +578 -0
- package/src/pageData/helpers.ts +764 -0
- package/src/pageData/misc.ts +347 -0
- package/src/pageData/navigation.ts +779 -0
- package/src/pageData/relationPages.ts +1246 -0
- package/src/pageData/relationTabs.ts +286 -0
- package/src/pageData/resourcePages.ts +593 -0
- package/src/pageData.ts +122 -4731
- package/src/react/AppShell.tsx +27 -1
- package/src/react/CollabExtensionFactoryRegistry.ts +55 -0
- package/src/react/CollabRoomContext.ts +42 -0
- package/src/react/FormCollabBindingRegistry.ts +72 -0
- package/src/react/RecordWrapperGate.tsx +40 -0
- package/src/react/RecordWrapperRegistry.ts +39 -0
- package/src/react/SchemaRenderer.tsx +230 -6479
- package/src/react/component-slots.test.ts +103 -0
- package/src/react/component-slots.ts +116 -0
- package/src/react/fields/BuilderInput.tsx +29 -117
- package/src/react/fields/MarkdownInput.tsx +0 -1
- package/src/react/fields/RepeaterInput.tsx +29 -130
- package/src/react/fields/rowState.ts +106 -0
- package/src/react/fields/useRowReorderDnd.ts +78 -0
- package/src/react/index.ts +38 -0
- package/src/react/layouts/SidebarLayout.tsx +39 -28
- package/src/react/layouts/TopbarLayout.tsx +70 -57
- package/src/react/parseRecordEditUrl.test.ts +75 -0
- package/src/react/parseRecordEditUrl.ts +55 -0
- package/src/react/persistedState.ts +40 -0
- package/src/react/schemaRenderer/AlertRenderer.tsx +112 -0
- package/src/react/schemaRenderer/EntryRenderer.tsx +501 -0
- package/src/react/schemaRenderer/SectionRenderer.tsx +120 -0
- package/src/react/schemaRenderer/SimpleElements.tsx +306 -0
- package/src/react/schemaRenderer/TabsRenderer.tsx +62 -0
- package/src/react/schemaRenderer/WizardRenderer.tsx +338 -0
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +177 -0
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +273 -0
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +61 -0
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +43 -0
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +64 -0
- package/src/react/schemaRenderer/action/buttons.tsx +99 -0
- package/src/react/schemaRenderer/action/helpers.ts +140 -0
- package/src/react/schemaRenderer/action/renderAction.tsx +245 -0
- package/src/react/schemaRenderer/columnFormat.ts +65 -0
- package/src/react/schemaRenderer/constants.ts +50 -0
- package/src/react/schemaRenderer/form/FormRenderer.tsx +233 -0
- package/src/react/schemaRenderer/form/renderField.tsx +511 -0
- package/src/react/schemaRenderer/helpers.tsx +81 -0
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +308 -0
- package/src/react/schemaRenderer/table/TableRenderer.tsx +123 -0
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +974 -0
- package/src/react/schemaRenderer/table/filters.tsx +1233 -0
- package/src/react/schemaRenderer/table/formatCell.tsx +264 -0
- package/src/react/schemaRenderer/table/links.tsx +112 -0
- package/src/react/schemaRenderer/table/renderRowActions.tsx +52 -0
- package/src/react/schemaRenderer/table/url.tsx +143 -0
- package/src/routes/globals.ts +154 -0
- package/src/routes/helpers.ts +668 -0
- package/src/routes/pages.ts +173 -0
- package/src/routes/panel.ts +204 -0
- package/src/routes/relations.ts +1219 -0
- package/src/routes/resources.ts +786 -0
- package/src/routes/theme.ts +109 -0
- package/src/routes.test.ts +1 -1
- package/src/routes.ts +64 -3176
- package/src/schema/TableWidget.test.ts +2 -2
- package/src/theme/migrate.test.ts +178 -0
- package/src/vite.test.ts +184 -0
- package/src/vite.ts +31 -9
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import type { ComponentType } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
CircleAlertIcon,
|
|
5
|
+
CircleCheckIcon,
|
|
6
|
+
InfoIcon,
|
|
7
|
+
TriangleAlertIcon,
|
|
8
|
+
XIcon,
|
|
9
|
+
} from 'lucide-react'
|
|
10
|
+
import { readStoredString, writeStoredString } from '../persistedState.js'
|
|
11
|
+
import { alertStyles, TEXT_COLOR_CLASSES } from './constants.js'
|
|
12
|
+
|
|
13
|
+
// ─── Alert renderer ─────────────────────────────────────────
|
|
14
|
+
//
|
|
15
|
+
// Owns dismissal state (per-mount + optional localStorage persistence)
|
|
16
|
+
// + icon dispatch + footer-actions alignment. Lifted out of the inline
|
|
17
|
+
// `case 'alert'` branch when Alert gained `dismissible() / iconColor() /
|
|
18
|
+
// footerActionsAlignment()` setters — those need component-local state
|
|
19
|
+
// (the dismiss button, the persisted-dismissal hydration on mount), and
|
|
20
|
+
// inlining the hooks under a switch arm is fragile.
|
|
21
|
+
|
|
22
|
+
const ALERT_TYPE_ICONS: Record<string, ComponentType<{ className?: string; 'aria-hidden'?: boolean | 'true' | 'false' }>> = {
|
|
23
|
+
info: InfoIcon,
|
|
24
|
+
warning: TriangleAlertIcon,
|
|
25
|
+
success: CircleCheckIcon,
|
|
26
|
+
danger: CircleAlertIcon,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ALERT_TYPE_DEFAULT_ICON_COLOR: Record<string, string> = {
|
|
30
|
+
info: 'info',
|
|
31
|
+
warning: 'warning',
|
|
32
|
+
success: 'success',
|
|
33
|
+
danger: 'destructive',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ALERT_ACTIONS_ALIGNMENT: Record<string, string> = {
|
|
37
|
+
start: 'justify-start',
|
|
38
|
+
center: 'justify-center',
|
|
39
|
+
end: 'justify-end',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function alertPersistKey(persistKey: string): string {
|
|
43
|
+
return `pilotiq.alert.${persistKey}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function AlertRenderer(props: {
|
|
47
|
+
alertType: string
|
|
48
|
+
content: string
|
|
49
|
+
title?: string
|
|
50
|
+
dismissible?: boolean
|
|
51
|
+
persistDismissal?: string
|
|
52
|
+
iconColor?: string
|
|
53
|
+
actionsAlignment?: string
|
|
54
|
+
footer: React.ReactNode[]
|
|
55
|
+
}): React.ReactNode {
|
|
56
|
+
const {
|
|
57
|
+
alertType, content, title, dismissible,
|
|
58
|
+
persistDismissal, iconColor, actionsAlignment, footer,
|
|
59
|
+
} = props
|
|
60
|
+
const [dismissed, setDismissed] = useState(false)
|
|
61
|
+
|
|
62
|
+
// Hydrate persisted-dismissal on first paint. `useState(false)` keeps
|
|
63
|
+
// SSR + first client paint identical (Hydration safe); the effect
|
|
64
|
+
// flips to dismissed if localStorage has the flag set.
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!persistDismissal) return
|
|
67
|
+
if (readStoredString(alertPersistKey(persistDismissal)) === '1') {
|
|
68
|
+
setDismissed(true)
|
|
69
|
+
}
|
|
70
|
+
}, [persistDismissal])
|
|
71
|
+
|
|
72
|
+
if (dismissed) return null
|
|
73
|
+
|
|
74
|
+
const styles = alertStyles[alertType] ?? alertStyles['info']!
|
|
75
|
+
const Icon = ALERT_TYPE_ICONS[alertType] ?? InfoIcon
|
|
76
|
+
const iconColorKey = iconColor ?? ALERT_TYPE_DEFAULT_ICON_COLOR[alertType] ?? 'info'
|
|
77
|
+
const iconColorCls = TEXT_COLOR_CLASSES[iconColorKey] ?? ''
|
|
78
|
+
const alignCls = ALERT_ACTIONS_ALIGNMENT[actionsAlignment ?? 'start'] ?? 'justify-start'
|
|
79
|
+
|
|
80
|
+
const handleDismiss = (): void => {
|
|
81
|
+
setDismissed(true)
|
|
82
|
+
if (persistDismissal) writeStoredString(alertPersistKey(persistDismissal), '1')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={`relative rounded-lg border p-4 ${styles} ${dismissible ? 'pr-9' : ''}`}>
|
|
87
|
+
<div className="flex gap-3">
|
|
88
|
+
<Icon className={`size-5 shrink-0 mt-0.5 ${iconColorCls}`} aria-hidden="true" />
|
|
89
|
+
<div className="flex-1 min-w-0">
|
|
90
|
+
{title !== undefined && <p className="font-medium mb-1">{title}</p>}
|
|
91
|
+
<p className="text-sm">{content}</p>
|
|
92
|
+
{footer.length > 0 && (
|
|
93
|
+
<div className={`flex items-center gap-2 mt-3 ${alignCls}`}>
|
|
94
|
+
{footer}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
{dismissible && (
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
onClick={handleDismiss}
|
|
103
|
+
aria-label="Dismiss"
|
|
104
|
+
title="Dismiss"
|
|
105
|
+
className="absolute top-3 right-3 inline-flex h-6 w-6 items-center justify-center rounded opacity-70 hover:opacity-100 transition-opacity"
|
|
106
|
+
>
|
|
107
|
+
<XIcon className="size-4" aria-hidden="true" />
|
|
108
|
+
</button>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import type { ElementMeta } from '../../schema/Element.js'
|
|
3
|
+
import { CheckIcon, CircleIcon, CopyIcon } from 'lucide-react'
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip.js'
|
|
5
|
+
import { getEntryComponent } from '../../entries/registry.js'
|
|
6
|
+
import {
|
|
7
|
+
BADGE_COLOR_CLASSES,
|
|
8
|
+
COLUMN_COLOR_CLASSES,
|
|
9
|
+
TEXT_COLOR_CLASSES,
|
|
10
|
+
TEXT_SIZE_CLASSES,
|
|
11
|
+
TEXT_WEIGHT_CLASSES,
|
|
12
|
+
} from './constants.js'
|
|
13
|
+
import { resolveIcon } from './helpers.js'
|
|
14
|
+
import { applyColumnFormat } from './columnFormat.js'
|
|
15
|
+
|
|
16
|
+
// ─── Entry rendering (Plan #16 — read-only label/value pairs) ───
|
|
17
|
+
|
|
18
|
+
/** Coerce a `KeyValueEntry` state value (object | JSON string | …) into a
|
|
19
|
+
* flat record. Returns `null` when the value is empty or non-decodable. */
|
|
20
|
+
function normalizeKeyValueValue(value: unknown): Record<string, unknown> | null {
|
|
21
|
+
if (value === null || value === undefined || value === '') return null
|
|
22
|
+
if (typeof value === 'string') {
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(value) as unknown
|
|
25
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
26
|
+
return parsed as Record<string, unknown>
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// Non-JSON string — fall through to null so the renderer shows the
|
|
30
|
+
// fallback rather than misrepresenting it as a one-row map.
|
|
31
|
+
}
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) return null
|
|
35
|
+
if (typeof value === 'object') return value as Record<string, unknown>
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Render a single kv cell value — primitives become their string form;
|
|
40
|
+
* nested objects/arrays JSON-stringify for compactness. */
|
|
41
|
+
function formatKeyValueCell(value: unknown): string {
|
|
42
|
+
if (value === null || value === undefined) return ''
|
|
43
|
+
if (typeof value === 'object') return JSON.stringify(value)
|
|
44
|
+
return String(value)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Plan #16 — read-only label-value pair for `Resource.detail()` schemas.
|
|
49
|
+
* Dispatches on `meta.entryType` (`'text' | 'badge' | 'icon' | 'image' | 'keyValue' | 'color'`).
|
|
50
|
+
* Wraps the rendered value in `<EntryShell>` for the shared chrome
|
|
51
|
+
* (label / helperText / tooltip / copyable trigger).
|
|
52
|
+
*
|
|
53
|
+
* `renderElement` is injected so the `repeatable` branch can recurse into
|
|
54
|
+
* row children without re-importing the main switch.
|
|
55
|
+
*/
|
|
56
|
+
export function renderEntry(
|
|
57
|
+
el: ElementMeta,
|
|
58
|
+
index: number,
|
|
59
|
+
renderElement: (el: ElementMeta, index: number) => React.ReactNode,
|
|
60
|
+
): React.ReactNode {
|
|
61
|
+
const entryType = String(el['entryType'] ?? 'text')
|
|
62
|
+
const value = el['value']
|
|
63
|
+
const fallback = el['default'] ? String(el['default']) : '—'
|
|
64
|
+
|
|
65
|
+
let body: React.ReactNode
|
|
66
|
+
switch (entryType) {
|
|
67
|
+
case 'text': {
|
|
68
|
+
const formatted = el['_formatted'] !== undefined
|
|
69
|
+
? String(el['_formatted'])
|
|
70
|
+
: (el['format']
|
|
71
|
+
? applyColumnFormat(value, el['format'] as { kind: string; [k: string]: unknown })
|
|
72
|
+
: (value === null || value === undefined || value === '' ? '' : String(value)))
|
|
73
|
+
|
|
74
|
+
const display = formatted === '' ? fallback : formatted
|
|
75
|
+
const isFallback = formatted === ''
|
|
76
|
+
const isRichText = el['richtext'] === true && !isFallback
|
|
77
|
+
const sizeKey = el['size'] ? String(el['size']) : 'sm'
|
|
78
|
+
const colorKey = el['color'] ? String(el['color']) : (isFallback ? 'muted' : 'default')
|
|
79
|
+
const weightKey = el['weight'] ? String(el['weight']) : 'normal'
|
|
80
|
+
const sizeCls = TEXT_SIZE_CLASSES[sizeKey] ?? 'text-sm'
|
|
81
|
+
const colorCls = TEXT_COLOR_CLASSES[colorKey] ?? ''
|
|
82
|
+
const weightCls = TEXT_WEIGHT_CLASSES[weightKey] ?? ''
|
|
83
|
+
const lineClamp = el['lineClamp'] as number | undefined
|
|
84
|
+
const wrap = el['wrap'] === true
|
|
85
|
+
|
|
86
|
+
const style: React.CSSProperties = {}
|
|
87
|
+
if (lineClamp !== undefined) {
|
|
88
|
+
style.display = '-webkit-box'
|
|
89
|
+
style.WebkitLineClamp = lineClamp
|
|
90
|
+
;(style as { WebkitBoxOrient?: string }).WebkitBoxOrient = 'vertical'
|
|
91
|
+
style.overflow = 'hidden'
|
|
92
|
+
}
|
|
93
|
+
const wrapCls = wrap ? 'whitespace-pre-wrap' : (lineClamp !== undefined ? '' : 'whitespace-nowrap')
|
|
94
|
+
|
|
95
|
+
if (isRichText) {
|
|
96
|
+
// Server-rendered HTML from a registered richtext renderer (e.g.
|
|
97
|
+
// `@pilotiq/tiptap`). Wrap in `prose` for sensible default
|
|
98
|
+
// styling — matches the read-only `Markdown` / `Html` primes.
|
|
99
|
+
const proseSize = sizeKey === 'lg' || sizeKey === 'xl'
|
|
100
|
+
? 'prose-lg'
|
|
101
|
+
: sizeKey === 'sm' || sizeKey === 'xs'
|
|
102
|
+
? 'prose-sm'
|
|
103
|
+
: ''
|
|
104
|
+
body = (
|
|
105
|
+
<div
|
|
106
|
+
className={`prose max-w-none dark:prose-invert ${proseSize} ${colorCls} ${weightCls}`.trim()}
|
|
107
|
+
style={style}
|
|
108
|
+
dangerouslySetInnerHTML={{ __html: display }}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
break
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
body = (
|
|
115
|
+
<span className={`${sizeCls} ${colorCls} ${weightCls} ${wrapCls}`.trim()} style={style}>
|
|
116
|
+
{display}
|
|
117
|
+
</span>
|
|
118
|
+
)
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case 'badge': {
|
|
123
|
+
const isBlank = value === null || value === undefined || value === ''
|
|
124
|
+
if (isBlank) {
|
|
125
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
const map = (el['colors'] as Record<string, string> | undefined) ?? {}
|
|
129
|
+
const colorKey = map[String(value)] ?? 'gray'
|
|
130
|
+
const cls = BADGE_COLOR_CLASSES[colorKey] ?? BADGE_COLOR_CLASSES['gray']
|
|
131
|
+
body = (
|
|
132
|
+
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls}`}>
|
|
133
|
+
{String(value)}
|
|
134
|
+
</span>
|
|
135
|
+
)
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case 'icon': {
|
|
140
|
+
const isBlank = value === null || value === undefined || value === ''
|
|
141
|
+
const map = (el['options'] as Record<string, { icon: string; color?: string; label?: string }> | undefined) ?? {}
|
|
142
|
+
const opt = isBlank ? undefined : map[String(value)]
|
|
143
|
+
if (!opt) {
|
|
144
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
const Icon = resolveIcon(opt.icon) ?? CircleIcon
|
|
148
|
+
const colorClass = opt.color ? (COLUMN_COLOR_CLASSES[opt.color] ?? '') : ''
|
|
149
|
+
const ariaLabel = opt.label ?? String(value)
|
|
150
|
+
body = <Icon className={`inline size-5 ${colorClass}`.trim()} aria-label={ariaLabel} />
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case 'image': {
|
|
155
|
+
const isBlank = value === null || value === undefined || value === ''
|
|
156
|
+
if (isBlank) {
|
|
157
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
const url = String(value)
|
|
161
|
+
const width = (el['imageWidth'] as number | undefined) ?? (el['imageSize'] as number | undefined) ?? 64
|
|
162
|
+
const height = (el['imageHeight'] as number | undefined) ?? (el['imageSize'] as number | undefined) ?? 64
|
|
163
|
+
const shape = String(el['imageShape'] ?? 'rounded')
|
|
164
|
+
const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'square' ? '' : 'rounded-md'
|
|
165
|
+
body = (
|
|
166
|
+
<img
|
|
167
|
+
src={url}
|
|
168
|
+
alt=""
|
|
169
|
+
width={width}
|
|
170
|
+
height={height}
|
|
171
|
+
className={`inline-block object-cover ${shapeCls}`.trim()}
|
|
172
|
+
/>
|
|
173
|
+
)
|
|
174
|
+
break
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case 'keyValue': {
|
|
178
|
+
const parsed = normalizeKeyValueValue(value)
|
|
179
|
+
const keys = parsed ? Object.keys(parsed) : []
|
|
180
|
+
if (!parsed || keys.length === 0) {
|
|
181
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
182
|
+
break
|
|
183
|
+
}
|
|
184
|
+
const keyLabel = el['keyLabel'] ? String(el['keyLabel']) : 'Key'
|
|
185
|
+
const valueLabel = el['valueLabel'] ? String(el['valueLabel']) : 'Value'
|
|
186
|
+
body = (
|
|
187
|
+
<table className="w-full border border-border text-sm">
|
|
188
|
+
<thead>
|
|
189
|
+
<tr className="bg-muted/50 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
190
|
+
<th className="border-b border-border px-2 py-1">{keyLabel}</th>
|
|
191
|
+
<th className="border-b border-border px-2 py-1">{valueLabel}</th>
|
|
192
|
+
</tr>
|
|
193
|
+
</thead>
|
|
194
|
+
<tbody>
|
|
195
|
+
{keys.map(k => (
|
|
196
|
+
<tr key={k} className="border-t border-border first:border-t-0">
|
|
197
|
+
<td className="px-2 py-1 align-top font-mono text-xs">{k}</td>
|
|
198
|
+
<td className="px-2 py-1 align-top font-mono text-xs break-all">
|
|
199
|
+
{formatKeyValueCell(parsed[k])}
|
|
200
|
+
</td>
|
|
201
|
+
</tr>
|
|
202
|
+
))}
|
|
203
|
+
</tbody>
|
|
204
|
+
</table>
|
|
205
|
+
)
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
case 'color': {
|
|
210
|
+
const isBlank = value === null || value === undefined || value === ''
|
|
211
|
+
if (isBlank) {
|
|
212
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
213
|
+
break
|
|
214
|
+
}
|
|
215
|
+
const hex = String(value)
|
|
216
|
+
const width = (el['colorWidth'] as number | undefined) ?? (el['colorSize'] as number | undefined) ?? 24
|
|
217
|
+
const height = (el['colorHeight'] as number | undefined) ?? (el['colorSize'] as number | undefined) ?? 24
|
|
218
|
+
const shape = String(el['colorShape'] ?? 'rounded')
|
|
219
|
+
const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'square' ? '' : 'rounded-md'
|
|
220
|
+
const showValue = el['showValue'] !== false
|
|
221
|
+
body = (
|
|
222
|
+
<span className="inline-flex items-center gap-2">
|
|
223
|
+
<span
|
|
224
|
+
className={`inline-block border border-border ${shapeCls}`.trim()}
|
|
225
|
+
style={{ width, height, backgroundColor: hex }}
|
|
226
|
+
aria-label={hex}
|
|
227
|
+
/>
|
|
228
|
+
{showValue && (
|
|
229
|
+
<span className="font-mono text-xs text-muted-foreground">{hex}</span>
|
|
230
|
+
)}
|
|
231
|
+
</span>
|
|
232
|
+
)
|
|
233
|
+
break
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'code': {
|
|
237
|
+
const isBlank = value === null || value === undefined || value === ''
|
|
238
|
+
if (isBlank) {
|
|
239
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
const text = typeof value === 'string' ? value : String(value)
|
|
243
|
+
const lang = el['language'] ? String(el['language']) : undefined
|
|
244
|
+
body = (
|
|
245
|
+
<pre
|
|
246
|
+
className="rounded-md border border-border bg-muted/40 p-3 text-xs overflow-x-auto"
|
|
247
|
+
data-language={lang}
|
|
248
|
+
>
|
|
249
|
+
<code className="font-mono">{text}</code>
|
|
250
|
+
</pre>
|
|
251
|
+
)
|
|
252
|
+
break
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case 'component': {
|
|
256
|
+
const componentName = String(el['component'] ?? '')
|
|
257
|
+
if (!componentName) {
|
|
258
|
+
body = (
|
|
259
|
+
<EntryComponentError>
|
|
260
|
+
ComponentEntry is missing its <code className="font-mono">component</code> name —
|
|
261
|
+
set <code className="font-mono">static componentName = '...'</code> on the
|
|
262
|
+
subclass or call <code className="font-mono">.component('...')</code> in the
|
|
263
|
+
fluent form.
|
|
264
|
+
</EntryComponentError>
|
|
265
|
+
)
|
|
266
|
+
break
|
|
267
|
+
}
|
|
268
|
+
const Component = getEntryComponent(componentName)
|
|
269
|
+
if (!Component) {
|
|
270
|
+
body = (
|
|
271
|
+
<EntryComponentError>
|
|
272
|
+
No component registered under name <code className="font-mono">{componentName}</code>.
|
|
273
|
+
Register it at app boot:
|
|
274
|
+
<pre className="mt-2 overflow-x-auto rounded bg-amber-100/60 p-2 text-xs dark:bg-amber-900/30">{`import { registerEntryComponents } from '@pilotiq/pilotiq/entries'\nregisterEntryComponents({ ${componentName}: ${componentName} })`}</pre>
|
|
275
|
+
</EntryComponentError>
|
|
276
|
+
)
|
|
277
|
+
break
|
|
278
|
+
}
|
|
279
|
+
// Render-time errors propagate to React's nearest error boundary —
|
|
280
|
+
// surfacing them inline here would require wrapping every entry in
|
|
281
|
+
// its own boundary, which v1 doesn't ship. The two pre-render
|
|
282
|
+
// sentinels above (missing name / missing registration) cover the
|
|
283
|
+
// typical wiring mistakes.
|
|
284
|
+
body = <Component value={value} />
|
|
285
|
+
break
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case 'repeatable': {
|
|
289
|
+
// Read-only sibling of `Repeater`. Reads `meta.rows` (resolved by
|
|
290
|
+
// `resolveRepeatableRows`) and dispatches on the chosen layout —
|
|
291
|
+
// `table > grid > stack`. Empty / non-array state falls through to
|
|
292
|
+
// the inherited `default()` placeholder, same as every other entry.
|
|
293
|
+
const rows = (el['rows'] as Array<{ id: string; children: ElementMeta[] }> | undefined) ?? []
|
|
294
|
+
if (rows.length === 0) {
|
|
295
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
296
|
+
break
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const tableCfg = el['table'] as { columns: Array<{ label: string; alignment?: 'left' | 'center' | 'right'; width?: string }> } | undefined
|
|
300
|
+
const gridN = el['grid'] as number | undefined
|
|
301
|
+
const innerCols = el['columns'] as number | undefined
|
|
302
|
+
const contained = el['contained'] !== false
|
|
303
|
+
|
|
304
|
+
if (tableCfg && tableCfg.columns.length > 0) {
|
|
305
|
+
const cols = tableCfg.columns
|
|
306
|
+
body = (
|
|
307
|
+
<table className="w-full border border-border text-sm">
|
|
308
|
+
{cols.some(c => c.width) && (
|
|
309
|
+
<colgroup>
|
|
310
|
+
{cols.map((c, i) => (
|
|
311
|
+
<col key={i} style={c.width ? { width: c.width } : undefined} />
|
|
312
|
+
))}
|
|
313
|
+
</colgroup>
|
|
314
|
+
)}
|
|
315
|
+
<thead>
|
|
316
|
+
<tr className="bg-muted/50 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
317
|
+
{cols.map((c, i) => (
|
|
318
|
+
<th
|
|
319
|
+
key={i}
|
|
320
|
+
className={`border-b border-border px-2 py-1 ${c.alignment === 'right' ? 'text-right' : c.alignment === 'center' ? 'text-center' : ''}`.trim()}
|
|
321
|
+
>
|
|
322
|
+
{c.label}
|
|
323
|
+
</th>
|
|
324
|
+
))}
|
|
325
|
+
</tr>
|
|
326
|
+
</thead>
|
|
327
|
+
<tbody>
|
|
328
|
+
{rows.map(row => (
|
|
329
|
+
<tr key={row.id} className="border-t border-border first:border-t-0 align-top">
|
|
330
|
+
{row.children.map((child, i) => {
|
|
331
|
+
const align = cols[i]?.alignment
|
|
332
|
+
const alignCls = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : ''
|
|
333
|
+
return (
|
|
334
|
+
<td key={i} className={`px-2 py-1 ${alignCls}`.trim()}>
|
|
335
|
+
{renderElement(child, i)}
|
|
336
|
+
</td>
|
|
337
|
+
)
|
|
338
|
+
})}
|
|
339
|
+
</tr>
|
|
340
|
+
))}
|
|
341
|
+
</tbody>
|
|
342
|
+
</table>
|
|
343
|
+
)
|
|
344
|
+
break
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const cardCls = contained
|
|
348
|
+
? 'rounded-md border border-border p-3 bg-background'
|
|
349
|
+
: ''
|
|
350
|
+
const innerColsCls = innerCols && innerCols >= 2
|
|
351
|
+
? `grid gap-3 grid-cols-1 md:grid-cols-${Math.min(innerCols, 6)}`
|
|
352
|
+
: 'space-y-2'
|
|
353
|
+
|
|
354
|
+
const cards = rows.map(row => (
|
|
355
|
+
<div key={row.id} className={`${cardCls} ${innerColsCls}`.trim()}>
|
|
356
|
+
{row.children.map((child, i) => renderElement(child, i))}
|
|
357
|
+
</div>
|
|
358
|
+
))
|
|
359
|
+
|
|
360
|
+
if (gridN && gridN >= 2) {
|
|
361
|
+
const cap = Math.min(gridN, 6)
|
|
362
|
+
body = (
|
|
363
|
+
<div className={`w-full grid gap-3 grid-cols-1 md:grid-cols-${cap}`}>
|
|
364
|
+
{cards}
|
|
365
|
+
</div>
|
|
366
|
+
)
|
|
367
|
+
break
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
body = <div className="w-full space-y-3">{cards}</div>
|
|
371
|
+
break
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
default:
|
|
375
|
+
body = <span className="text-sm text-muted-foreground">{fallback}</span>
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const copyable = el['copyable'] as { label?: string } | undefined
|
|
379
|
+
const copyValue = el['_formatted'] !== undefined
|
|
380
|
+
? String(el['_formatted'])
|
|
381
|
+
: value === null || value === undefined
|
|
382
|
+
? ''
|
|
383
|
+
: typeof value === 'object'
|
|
384
|
+
? JSON.stringify(value)
|
|
385
|
+
: String(value)
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<EntryShell
|
|
389
|
+
key={index}
|
|
390
|
+
el={el}
|
|
391
|
+
copyValue={copyable !== undefined ? copyValue : undefined}
|
|
392
|
+
copyableLabel={copyable?.label}
|
|
393
|
+
>
|
|
394
|
+
{body}
|
|
395
|
+
</EntryShell>
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
interface EntryShellProps {
|
|
400
|
+
el: ElementMeta
|
|
401
|
+
copyValue?: string | undefined
|
|
402
|
+
copyableLabel?: string | undefined
|
|
403
|
+
children: React.ReactNode
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function EntryShell({ el, copyValue, copyableLabel, children }: EntryShellProps): React.ReactNode {
|
|
407
|
+
const label = String(el['label'] ?? '')
|
|
408
|
+
const helperText = el['helperText'] ? String(el['helperText']) : undefined
|
|
409
|
+
const tooltipText = el['tooltip'] ? String(el['tooltip']) : undefined
|
|
410
|
+
const inline = el['inlineLabel'] === true
|
|
411
|
+
|
|
412
|
+
const labelNode = label ? (
|
|
413
|
+
<div className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground">
|
|
414
|
+
<span>{label}</span>
|
|
415
|
+
{tooltipText && <EntryTooltip text={tooltipText} />}
|
|
416
|
+
</div>
|
|
417
|
+
) : null
|
|
418
|
+
|
|
419
|
+
const valueRow = (
|
|
420
|
+
<div className="flex items-center gap-2">
|
|
421
|
+
{children}
|
|
422
|
+
{copyValue !== undefined && (
|
|
423
|
+
<EntryCopyButton text={copyValue} label={copyableLabel ?? 'Copy'} />
|
|
424
|
+
)}
|
|
425
|
+
</div>
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if (inline) {
|
|
429
|
+
return (
|
|
430
|
+
<div className="flex items-baseline gap-3">
|
|
431
|
+
{labelNode && <div className="min-w-32">{labelNode}</div>}
|
|
432
|
+
<div className="min-w-0 flex-1">
|
|
433
|
+
{valueRow}
|
|
434
|
+
{helperText && <p className="mt-1 text-xs text-muted-foreground">{helperText}</p>}
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
<div className="space-y-1">
|
|
442
|
+
{labelNode}
|
|
443
|
+
{valueRow}
|
|
444
|
+
{helperText && <p className="text-xs text-muted-foreground">{helperText}</p>}
|
|
445
|
+
</div>
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function EntryComponentError({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
450
|
+
return (
|
|
451
|
+
<div
|
|
452
|
+
role="alert"
|
|
453
|
+
className="rounded-md border border-amber-500/40 bg-amber-50 p-3 text-sm text-amber-800 dark:bg-amber-950/30 dark:text-amber-200"
|
|
454
|
+
>
|
|
455
|
+
{children}
|
|
456
|
+
</div>
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function EntryTooltip({ text }: { text: string }): React.ReactNode {
|
|
461
|
+
const trigger = (
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
className="inline-flex h-3.5 w-3.5 items-center justify-center rounded-full border text-[10px] text-muted-foreground"
|
|
465
|
+
aria-label={text}
|
|
466
|
+
>
|
|
467
|
+
?
|
|
468
|
+
</button>
|
|
469
|
+
)
|
|
470
|
+
return (
|
|
471
|
+
<TooltipProvider>
|
|
472
|
+
<Tooltip>
|
|
473
|
+
<TooltipTrigger render={() => trigger} />
|
|
474
|
+
<TooltipContent>{text}</TooltipContent>
|
|
475
|
+
</Tooltip>
|
|
476
|
+
</TooltipProvider>
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function EntryCopyButton({ text, label }: { text: string; label: string }): React.ReactNode {
|
|
481
|
+
const [copied, setCopied] = useState(false)
|
|
482
|
+
const handleClick = () => {
|
|
483
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
484
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
485
|
+
setCopied(true)
|
|
486
|
+
setTimeout(() => setCopied(false), 1500)
|
|
487
|
+
}).catch(() => { /* ignore — older browser / permission denied */ })
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return (
|
|
491
|
+
<button
|
|
492
|
+
type="button"
|
|
493
|
+
onClick={handleClick}
|
|
494
|
+
aria-label={label}
|
|
495
|
+
title={label}
|
|
496
|
+
className="inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
497
|
+
>
|
|
498
|
+
{copied ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
|
|
499
|
+
</button>
|
|
500
|
+
)
|
|
501
|
+
}
|