@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,120 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import type { ElementMeta } from '../../schema/Element.js'
|
|
3
|
+
import { readStoredFlag, writeStoredFlag } from '../persistedState.js'
|
|
4
|
+
import { layoutClasses, resolveIcon } from './helpers.js'
|
|
5
|
+
|
|
6
|
+
// ─── Section (stateful when collapsible) ────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `Section` chrome with optional collapse + persisted open/closed state.
|
|
10
|
+
* Renders a card-style border, header (title / description / icon / badge
|
|
11
|
+
* / after-header actions), body, and the collapse toggle when enabled.
|
|
12
|
+
*
|
|
13
|
+
* `renderElement` is injected so the body can re-enter the main switch
|
|
14
|
+
* without creating a SchemaRenderer ↔ SectionRenderer import cycle.
|
|
15
|
+
*/
|
|
16
|
+
export function SectionRenderer({
|
|
17
|
+
el,
|
|
18
|
+
index,
|
|
19
|
+
renderElement,
|
|
20
|
+
}: {
|
|
21
|
+
el: ElementMeta
|
|
22
|
+
index: number
|
|
23
|
+
renderElement: (el: ElementMeta, index: number) => React.ReactNode
|
|
24
|
+
}) {
|
|
25
|
+
const title = el['title'] ? String(el['title']) : undefined
|
|
26
|
+
const description = el['description'] ? String(el['description']) : undefined
|
|
27
|
+
const iconName = el['icon'] ? String(el['icon']) : undefined
|
|
28
|
+
const badge = el['badge'] ? String(el['badge']) : undefined
|
|
29
|
+
const columns = Number(el['columns'] ?? 1)
|
|
30
|
+
const collapsible = Boolean(el['collapsible'])
|
|
31
|
+
const compact = Boolean(el['compact'])
|
|
32
|
+
const dense = Boolean(el['dense'])
|
|
33
|
+
const secondary = Boolean(el['secondary'])
|
|
34
|
+
const afterHeader = (el['afterHeader'] as ElementMeta[] | undefined) ?? []
|
|
35
|
+
const persist = Boolean(el['persistCollapsed'])
|
|
36
|
+
const persistKey = el['persistKey']
|
|
37
|
+
? `pilotiq.section.${String(el['persistKey'])}`
|
|
38
|
+
: title
|
|
39
|
+
? `pilotiq.section.${title.toLowerCase().replace(/\s+/g, '-')}`
|
|
40
|
+
: undefined
|
|
41
|
+
|
|
42
|
+
const defaultCollapsed = Boolean(el['defaultCollapsed'])
|
|
43
|
+
const [collapsed, setCollapsed] = useState(defaultCollapsed)
|
|
44
|
+
|
|
45
|
+
// Plan #8 — persist open/closed state to localStorage. Hydration-safe:
|
|
46
|
+
// initial render uses `defaultCollapsed`; effect overrides from storage
|
|
47
|
+
// after mount so server + client first paint agree.
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!persist || !persistKey) return
|
|
50
|
+
setCollapsed(readStoredFlag(persistKey, defaultCollapsed))
|
|
51
|
+
}, [persist, persistKey, defaultCollapsed])
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!persist || !persistKey) return
|
|
55
|
+
writeStoredFlag(persistKey, collapsed)
|
|
56
|
+
}, [persist, persistKey, collapsed])
|
|
57
|
+
|
|
58
|
+
// `dense` tightens the inner spacing between the section's children
|
|
59
|
+
// (orthogonal to `compact`, which trims the section's outer padding /
|
|
60
|
+
// heading). gap-2 ≈ 8px vs gap-4 ≈ 16px.
|
|
61
|
+
const innerGap = dense ? 'gap-2' : 'gap-4'
|
|
62
|
+
const gridClass = columns === 2 ? `grid grid-cols-2 ${innerGap}` : columns === 3 ? `grid grid-cols-3 ${innerGap}` : `flex flex-col ${innerGap}`
|
|
63
|
+
const padding = compact ? 'p-3' : 'p-4'
|
|
64
|
+
const titleSize = compact ? 'text-sm' : 'text-base'
|
|
65
|
+
const Icon = resolveIcon(iconName)
|
|
66
|
+
|
|
67
|
+
// `secondary()` flips the section background to the muted token so it
|
|
68
|
+
// visually recedes beneath a primary section. The border thins to the
|
|
69
|
+
// same muted tone for the same reason — a sharp `border-input` line
|
|
70
|
+
// around a muted block looks like a typographic ledger rather than a
|
|
71
|
+
// grouping container.
|
|
72
|
+
const surfaceClass = secondary ? 'bg-muted/40 border-muted' : 'bg-card'
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<section
|
|
76
|
+
key={index}
|
|
77
|
+
className={`flex flex-col ${compact ? 'gap-2' : 'gap-3'} rounded-lg border ${surfaceClass} ${padding} ${layoutClasses(el)}`.trim()}
|
|
78
|
+
>
|
|
79
|
+
{(title || description || collapsible || badge || afterHeader.length > 0) && (
|
|
80
|
+
<header className="flex items-start justify-between gap-2">
|
|
81
|
+
<div className="flex items-start gap-2">
|
|
82
|
+
{Icon && <Icon className="size-4 mt-0.5 text-muted-foreground" aria-hidden="true" />}
|
|
83
|
+
<div>
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
{title && <h3 className={`${titleSize} font-semibold`}>{title}</h3>}
|
|
86
|
+
{badge && (
|
|
87
|
+
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
88
|
+
{badge}
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
{description && <p className="text-xs text-muted-foreground mt-0.5">{description}</p>}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="flex items-center gap-2">
|
|
96
|
+
{afterHeader.length > 0 && (
|
|
97
|
+
<div className="flex items-center gap-1">
|
|
98
|
+
{afterHeader.map((a, i) => renderElement(a, i))}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
{collapsible && (
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
onClick={() => setCollapsed(c => !c)}
|
|
105
|
+
className="text-xs text-muted-foreground hover:text-foreground"
|
|
106
|
+
>
|
|
107
|
+
{collapsed ? 'Expand' : 'Collapse'}
|
|
108
|
+
</button>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</header>
|
|
112
|
+
)}
|
|
113
|
+
{!collapsed && el.children && el.children.length > 0 && (
|
|
114
|
+
<div className={gridClass}>
|
|
115
|
+
{el.children.map((c, i) => renderElement(c, i))}
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
</section>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { ElementMeta } from '../../schema/Element.js'
|
|
3
|
+
import {
|
|
4
|
+
BADGE_COLOR_CLASSES,
|
|
5
|
+
COLUMN_COLOR_CLASSES,
|
|
6
|
+
TEXT_COLOR_CLASSES,
|
|
7
|
+
TEXT_SIZE_CLASSES,
|
|
8
|
+
TEXT_WEIGHT_CLASSES,
|
|
9
|
+
} from './constants.js'
|
|
10
|
+
import { layoutClasses, renderChildren, resolveIcon } from './helpers.js'
|
|
11
|
+
|
|
12
|
+
/** Stateless leaf renderers — every element here is a pure function of
|
|
13
|
+
* its meta. Cases that own children defer back to the caller's
|
|
14
|
+
* `renderElement` via `deps.renderElement`. Cases that mount Action
|
|
15
|
+
* triggers in header / footer slots defer to `deps.renderActionLike`.
|
|
16
|
+
*
|
|
17
|
+
* `renderSimpleElement` returns `null` only when the type is one of the
|
|
18
|
+
* handled leaves and resolves to no output (e.g. `icon` with no
|
|
19
|
+
* registered name). For unknown types it returns the sentinel
|
|
20
|
+
* `undefined` so the caller falls through to the rest of its switch. */
|
|
21
|
+
|
|
22
|
+
export interface SimpleElementDeps {
|
|
23
|
+
/** Recurse into a child element. Injected to avoid the cycle with
|
|
24
|
+
* SchemaRenderer's main dispatch. */
|
|
25
|
+
renderElement: (el: ElementMeta, index: number) => React.ReactNode
|
|
26
|
+
/** Render an action / actionGroup / slotComponent meta as a button or
|
|
27
|
+
* trigger. Used by `heading` and `emptyState` for their header /
|
|
28
|
+
* footer action slots. */
|
|
29
|
+
renderActionLike: (el: ElementMeta, index: number) => React.ReactNode
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Render the `text` element. Hot path — pulled out so the `case 'text'`
|
|
33
|
+
* branch in the main switch stays a one-liner. */
|
|
34
|
+
function renderText(el: ElementMeta, index: number): React.ReactNode {
|
|
35
|
+
const content = String(el['content'] ?? '')
|
|
36
|
+
const color = el['color'] ? String(el['color']) : undefined
|
|
37
|
+
const size = el['size'] ? String(el['size']) : undefined
|
|
38
|
+
const weight = el['weight'] ? String(el['weight']) : undefined
|
|
39
|
+
const isBadge = el['badge'] === true
|
|
40
|
+
|
|
41
|
+
if (isBadge) {
|
|
42
|
+
const badgeKey = el['badgeColor'] ? String(el['badgeColor']) : 'gray'
|
|
43
|
+
const cls = BADGE_COLOR_CLASSES[badgeKey] ?? BADGE_COLOR_CLASSES['gray']
|
|
44
|
+
return (
|
|
45
|
+
<span
|
|
46
|
+
key={index}
|
|
47
|
+
className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls}`}
|
|
48
|
+
>
|
|
49
|
+
{content}
|
|
50
|
+
</span>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Defaults match the previous bare `<p>` for back-compat: text-sm + muted.
|
|
55
|
+
const sizeCls = size ? (TEXT_SIZE_CLASSES[size] ?? '') : 'text-sm'
|
|
56
|
+
const colorCls = color ? (TEXT_COLOR_CLASSES[color] ?? '') : 'text-muted-foreground'
|
|
57
|
+
const weightCls = weight ? (TEXT_WEIGHT_CLASSES[weight] ?? '') : ''
|
|
58
|
+
return (
|
|
59
|
+
<p key={index} className={`${sizeCls} ${colorCls} ${weightCls}`.trim()}>
|
|
60
|
+
{content}
|
|
61
|
+
</p>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Dispatch helper for stateless leaf + layout-primitive element types.
|
|
66
|
+
* Returns `undefined` when the type isn't handled here, letting the
|
|
67
|
+
* caller fall through to its remaining switch arms. */
|
|
68
|
+
export function renderSimpleElement(
|
|
69
|
+
el: ElementMeta,
|
|
70
|
+
index: number,
|
|
71
|
+
deps: SimpleElementDeps,
|
|
72
|
+
): React.ReactNode | undefined {
|
|
73
|
+
const { renderElement, renderActionLike } = deps
|
|
74
|
+
|
|
75
|
+
switch (el.type) {
|
|
76
|
+
case 'text':
|
|
77
|
+
return renderText(el, index)
|
|
78
|
+
|
|
79
|
+
case 'image': {
|
|
80
|
+
const url = String(el['url'] ?? '')
|
|
81
|
+
const alt = String(el['alt'] ?? '')
|
|
82
|
+
const width = el['width'] as number | undefined
|
|
83
|
+
const height = el['height'] as number | undefined
|
|
84
|
+
const shape = String(el['shape'] ?? 'square')
|
|
85
|
+
const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'rounded' ? 'rounded-md' : ''
|
|
86
|
+
return (
|
|
87
|
+
<img
|
|
88
|
+
key={index}
|
|
89
|
+
src={url}
|
|
90
|
+
alt={alt}
|
|
91
|
+
{...(width !== undefined ? { width } : {})}
|
|
92
|
+
{...(height !== undefined ? { height } : {})}
|
|
93
|
+
className={`inline-block object-cover ${shapeCls}`}
|
|
94
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case 'icon': {
|
|
99
|
+
const name = el['name'] ? String(el['name']) : undefined
|
|
100
|
+
const size = (el['size'] as number | undefined) ?? 16
|
|
101
|
+
const color = String(el['color'] ?? 'default')
|
|
102
|
+
const label = el['label'] ? String(el['label']) : undefined
|
|
103
|
+
const Cmp = resolveIcon(name)
|
|
104
|
+
if (!Cmp) return null
|
|
105
|
+
const colorClass = COLUMN_COLOR_CLASSES[color] ?? ''
|
|
106
|
+
return (
|
|
107
|
+
<Cmp
|
|
108
|
+
key={index}
|
|
109
|
+
className={`inline ${colorClass}`}
|
|
110
|
+
{...(label ? { 'aria-label': label } : { 'aria-hidden': true })}
|
|
111
|
+
style={{ width: size, height: size }}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case 'markdown':
|
|
117
|
+
case 'html': {
|
|
118
|
+
const html = String(el['html'] ?? '')
|
|
119
|
+
const prose = el['prose'] !== false
|
|
120
|
+
const size = el['size'] ? String(el['size']) : undefined
|
|
121
|
+
const proseCls = prose
|
|
122
|
+
? `prose max-w-none ${size === 'sm' ? 'prose-sm' : size === 'lg' ? 'prose-lg' : ''}`.trim()
|
|
123
|
+
: ''
|
|
124
|
+
return (
|
|
125
|
+
<div
|
|
126
|
+
key={index}
|
|
127
|
+
className={proseCls || undefined}
|
|
128
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case 'heading': {
|
|
134
|
+
const level = (el['level'] as number) ?? 1
|
|
135
|
+
const content = String(el['content'] ?? '')
|
|
136
|
+
const description = el['description'] ? String(el['description']) : undefined
|
|
137
|
+
const headerActions = (el.children ?? []).filter(c => c.type === 'action' || c.type === 'actionGroup' || c.type === 'slotComponent')
|
|
138
|
+
const Tag = level === 1 ? 'h1' : level === 2 ? 'h2' : 'h3'
|
|
139
|
+
const sizes = { 1: 'text-2xl', 2: 'text-xl', 3: 'text-lg' } as const
|
|
140
|
+
const titleBlock = (
|
|
141
|
+
<div>
|
|
142
|
+
<Tag className={`${sizes[level as 1 | 2 | 3]} font-bold tracking-tight`}>
|
|
143
|
+
{content}
|
|
144
|
+
</Tag>
|
|
145
|
+
{description && (
|
|
146
|
+
<p className="text-sm text-muted-foreground mt-1">{description}</p>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
)
|
|
150
|
+
if (headerActions.length === 0) {
|
|
151
|
+
return <div key={index}>{titleBlock}</div>
|
|
152
|
+
}
|
|
153
|
+
return (
|
|
154
|
+
<div key={index} className="flex items-start justify-between gap-4">
|
|
155
|
+
{titleBlock}
|
|
156
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
157
|
+
{headerActions.map((a, i) => renderActionLike(a, i))}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'emptyState': {
|
|
164
|
+
const heading = String(el['heading'] ?? '')
|
|
165
|
+
const description = el['description'] ? String(el['description']) : undefined
|
|
166
|
+
const iconName = el['icon'] ? String(el['icon']) : undefined
|
|
167
|
+
const contained = el['contained'] !== false
|
|
168
|
+
const Icon = iconName ? resolveIcon(iconName) : undefined
|
|
169
|
+
const footer = (el.children ?? []).filter(c => c.type === 'action' || c.type === 'actionGroup' || c.type === 'slotComponent')
|
|
170
|
+
const wrapper = contained
|
|
171
|
+
? 'rounded-lg border border-border bg-card text-card-foreground py-12 px-6'
|
|
172
|
+
: 'py-8'
|
|
173
|
+
return (
|
|
174
|
+
<div key={index} className={`${wrapper} flex flex-col items-center text-center gap-3`}>
|
|
175
|
+
{Icon && <Icon className="size-10 text-muted-foreground" aria-hidden="true" />}
|
|
176
|
+
<h3 className="text-lg font-semibold">{heading}</h3>
|
|
177
|
+
{description && <p className="text-sm text-muted-foreground max-w-md">{description}</p>}
|
|
178
|
+
{footer.length > 0 && (
|
|
179
|
+
<div className="flex items-center gap-2 mt-2">
|
|
180
|
+
{footer.map((a, i) => renderActionLike(a, i))}
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case 'divider': {
|
|
188
|
+
const label = el['label'] ? String(el['label']) : undefined
|
|
189
|
+
return label
|
|
190
|
+
? <div key={index} className="relative py-2">
|
|
191
|
+
<div className="absolute inset-0 flex items-center"><span className="w-full border-t border-border" /></div>
|
|
192
|
+
<div className="relative flex justify-center"><span className="bg-background px-2 text-xs text-muted-foreground">{label}</span></div>
|
|
193
|
+
</div>
|
|
194
|
+
: <hr key={index} className="border-border" />
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case 'unorderedList': {
|
|
198
|
+
const items = (el['items'] as unknown[] | undefined) ?? []
|
|
199
|
+
const color = el['color'] ? String(el['color']) : undefined
|
|
200
|
+
const size = el['size'] ? String(el['size']) : undefined
|
|
201
|
+
const weight = el['weight'] ? String(el['weight']) : undefined
|
|
202
|
+
const sizeCls = size ? (TEXT_SIZE_CLASSES[size] ?? '') : 'text-sm'
|
|
203
|
+
const colorCls = color ? (TEXT_COLOR_CLASSES[color] ?? '') : ''
|
|
204
|
+
const weightCls = weight ? (TEXT_WEIGHT_CLASSES[weight] ?? '') : ''
|
|
205
|
+
return (
|
|
206
|
+
<ul key={index} className={`list-disc list-inside space-y-1 ${sizeCls} ${colorCls} ${weightCls}`.trim()}>
|
|
207
|
+
{items.map((item, i) => (
|
|
208
|
+
<li key={i}>{String(item)}</li>
|
|
209
|
+
))}
|
|
210
|
+
</ul>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
case 'card': {
|
|
215
|
+
const title = el['title'] ? String(el['title']) : undefined
|
|
216
|
+
const description = el['description'] ? String(el['description']) : undefined
|
|
217
|
+
return (
|
|
218
|
+
<div key={index} className="rounded-xl border bg-card p-6 shadow-sm">
|
|
219
|
+
{title && <h3 className="font-semibold mb-1">{title}</h3>}
|
|
220
|
+
{description && <p className="text-sm text-muted-foreground mb-4">{description}</p>}
|
|
221
|
+
{renderChildren(el.children, 'gap-4', renderElement)}
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
case 'grid': {
|
|
227
|
+
const columns = Math.max(1, Math.min(12, Number(el['columns'] ?? 2)))
|
|
228
|
+
const gapPx = el['gap'] !== undefined ? `${Number(el['gap'])}px` : undefined
|
|
229
|
+
return (
|
|
230
|
+
<div
|
|
231
|
+
key={index}
|
|
232
|
+
className={`grid gap-4 ${layoutClasses(el)}`.trim()}
|
|
233
|
+
style={{
|
|
234
|
+
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
235
|
+
...(gapPx ? { gap: gapPx } : {}),
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{(el.children ?? []).map((c, i) => renderElement(c, i))}
|
|
239
|
+
</div>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
case 'group': {
|
|
244
|
+
const layout = layoutClasses(el)
|
|
245
|
+
return (
|
|
246
|
+
<div key={index} className={layout || undefined}>
|
|
247
|
+
{renderChildren(el.children, 'gap-4', renderElement)}
|
|
248
|
+
</div>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
case 'split': {
|
|
253
|
+
const from = el['from'] === 'left' ? 'left' : 'right'
|
|
254
|
+
const gap = Math.max(0, Math.min(12, Number(el['gap'] ?? 6)))
|
|
255
|
+
const children = el.children ?? []
|
|
256
|
+
// Find the explicit aside child first; fall back to "second child is
|
|
257
|
+
// aside" so terse Split.make().schema([main, aside]) still works.
|
|
258
|
+
let asideIdx = children.findIndex(c => c['aside'] === true)
|
|
259
|
+
if (asideIdx === -1 && children.length >= 2) asideIdx = 1
|
|
260
|
+
const mainChildren = children.filter((_, i) => i !== asideIdx)
|
|
261
|
+
const asideChild = asideIdx >= 0 ? children[asideIdx] : undefined
|
|
262
|
+
|
|
263
|
+
const orderClasses = from === 'left'
|
|
264
|
+
? { aside: '@md:order-first', main: '@md:order-last' }
|
|
265
|
+
: { aside: '@md:order-last', main: '@md:order-first' }
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div
|
|
269
|
+
key={index}
|
|
270
|
+
className={`@container flex flex-col @md:flex-row gap-${gap} ${layoutClasses(el)}`.trim()}
|
|
271
|
+
>
|
|
272
|
+
<div className={`flex flex-col gap-4 flex-1 min-w-0 ${orderClasses.main}`}>
|
|
273
|
+
{mainChildren.map((c, i) => renderElement(c, i))}
|
|
274
|
+
</div>
|
|
275
|
+
{asideChild && (
|
|
276
|
+
<aside className={`flex flex-col gap-4 @md:w-80 @md:shrink-0 ${orderClasses.aside}`}>
|
|
277
|
+
{renderElement(asideChild, asideIdx)}
|
|
278
|
+
</aside>
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case 'fieldset': {
|
|
285
|
+
const label = String(el['label'] ?? '')
|
|
286
|
+
const columns = Math.max(1, Math.min(3, Number(el['columns'] ?? 1)))
|
|
287
|
+
const gridStyle = columns > 1
|
|
288
|
+
? { display: 'grid', gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, gap: '1rem' }
|
|
289
|
+
: undefined
|
|
290
|
+
return (
|
|
291
|
+
<fieldset
|
|
292
|
+
key={index}
|
|
293
|
+
className={`rounded-md border border-border px-4 pt-3 pb-4 ${layoutClasses(el)}`.trim()}
|
|
294
|
+
>
|
|
295
|
+
{label && <legend className="px-1 text-xs font-medium text-muted-foreground">{label}</legend>}
|
|
296
|
+
<div className={columns === 1 ? 'flex flex-col gap-3' : undefined} style={gridStyle}>
|
|
297
|
+
{(el.children ?? []).map((c, i) => renderElement(c, i))}
|
|
298
|
+
</div>
|
|
299
|
+
</fieldset>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
default:
|
|
304
|
+
return undefined
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { ElementMeta } from '../../schema/Element.js'
|
|
3
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs.js'
|
|
4
|
+
import { renderChildren } from './helpers.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Top-level `Tabs` element. Variants:
|
|
8
|
+
* - `'pills'` (default) — uses the shadcn Tabs primitive's stock chrome.
|
|
9
|
+
* - `'underline'` — flat tab strip with a bottom border and per-tab
|
|
10
|
+
* underline on selection.
|
|
11
|
+
*
|
|
12
|
+
* `renderElement` is injected for the body content of each tab so the
|
|
13
|
+
* renderer can recurse without re-importing the main switch.
|
|
14
|
+
*/
|
|
15
|
+
export function TabsRenderer({
|
|
16
|
+
el,
|
|
17
|
+
index,
|
|
18
|
+
renderElement,
|
|
19
|
+
}: {
|
|
20
|
+
el: ElementMeta
|
|
21
|
+
index: number
|
|
22
|
+
renderElement: (el: ElementMeta, index: number) => React.ReactNode
|
|
23
|
+
}) {
|
|
24
|
+
const tabs = (el.children ?? []).filter(c => c.type === 'tab')
|
|
25
|
+
if (tabs.length === 0) return null
|
|
26
|
+
|
|
27
|
+
const variant = el['variant'] === 'underline' ? 'underline' : 'pills'
|
|
28
|
+
const tabValues = tabs.map((_, i) => `tab-${i}`)
|
|
29
|
+
const defaultValue = tabValues[0]!
|
|
30
|
+
|
|
31
|
+
// Underline variant overrides the primitive's pill chrome with a bottom
|
|
32
|
+
// border on the list and per-trigger underline-on-selected. No
|
|
33
|
+
// `<TabsIndicator>` is rendered, so there's no sliding pill to hide.
|
|
34
|
+
const listClass = variant === 'underline'
|
|
35
|
+
? 'relative flex h-auto w-fit justify-start gap-0 rounded-none bg-transparent p-0 text-muted-foreground border-b border-border'
|
|
36
|
+
: undefined
|
|
37
|
+
const triggerClass = variant === 'underline'
|
|
38
|
+
? 'rounded-none border-0 border-b-2 border-transparent bg-transparent px-4 py-2 text-sm font-medium -mb-px data-[active]:border-primary data-[active]:text-foreground data-[active]:bg-transparent data-[active]:shadow-none'
|
|
39
|
+
: undefined
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Tabs key={index} defaultValue={defaultValue}>
|
|
43
|
+
<TabsList className={listClass}>
|
|
44
|
+
{tabs.map((tab, i) => (
|
|
45
|
+
<TabsTrigger key={i} value={tabValues[i]!} className={triggerClass}>
|
|
46
|
+
{String(tab['label'] ?? '')}
|
|
47
|
+
{tab['badge'] ? (
|
|
48
|
+
<span className="ml-2 text-xs px-1.5 py-0.5 rounded-full bg-muted">
|
|
49
|
+
{String(tab['badge'])}
|
|
50
|
+
</span>
|
|
51
|
+
) : null}
|
|
52
|
+
</TabsTrigger>
|
|
53
|
+
))}
|
|
54
|
+
</TabsList>
|
|
55
|
+
{tabs.map((tab, i) => (
|
|
56
|
+
<TabsContent key={i} value={tabValues[i]!} className="pt-2">
|
|
57
|
+
{renderChildren(tab['children'] as ElementMeta[] | undefined, 'gap-4', renderElement)}
|
|
58
|
+
</TabsContent>
|
|
59
|
+
))}
|
|
60
|
+
</Tabs>
|
|
61
|
+
)
|
|
62
|
+
}
|