spine-framework 0.1.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/.framework/README.md +129 -0
- package/.framework/cli/bin.cjs +14 -0
- package/.framework/cli/commands/agents.ts +153 -0
- package/.framework/cli/commands/auth.ts +94 -0
- package/.framework/cli/commands/create-app.ts +185 -0
- package/.framework/cli/commands/dev.ts +295 -0
- package/.framework/cli/commands/doctor.ts +442 -0
- package/.framework/cli/commands/generate.ts +332 -0
- package/.framework/cli/commands/init.ts +272 -0
- package/.framework/cli/commands/install-app.ts +391 -0
- package/.framework/cli/commands/items.ts +253 -0
- package/.framework/cli/commands/migrations.ts +141 -0
- package/.framework/cli/commands/pipelines.ts +166 -0
- package/.framework/cli/commands/status.ts +197 -0
- package/.framework/cli/commands/system.ts +184 -0
- package/.framework/cli/commands/test.ts +227 -0
- package/.framework/cli/commands/uninstall-app.ts +166 -0
- package/.framework/cli/context.ts +268 -0
- package/.framework/cli/env-loader.ts +36 -0
- package/.framework/cli/index.ts +106 -0
- package/.framework/cli/welcome.cjs +45 -0
- package/.framework/docs/API.md +384 -0
- package/.framework/docs/STABILITY.md +52 -0
- package/.framework/docs/admin-routes.md +76 -0
- package/.framework/docs/api-docs-progress.md +38 -0
- package/.framework/docs/api-governance.md +146 -0
- package/.framework/docs/api-testing-results.md +212 -0
- package/.framework/docs/apis/admin-configs.md +567 -0
- package/.framework/docs/apis/admin-data.md +272 -0
- package/.framework/docs/apis/index.md +231 -0
- package/.framework/docs/apis/internal.md +295 -0
- package/.framework/docs/apis/runtime.md +537 -0
- package/.framework/docs/assembly-launch-guide.md +138 -0
- package/.framework/docs/audit-results.md +590 -0
- package/.framework/docs/authorization-model.md +170 -0
- package/.framework/docs/db-api-inventory.md +95 -0
- package/.framework/docs/examples/custom-app/README.md +77 -0
- package/.framework/docs/examples/custom-function/README.md +27 -0
- package/.framework/docs/examples/custom-function/handler.ts +48 -0
- package/.framework/docs/examples/custom-webhook/README.md +68 -0
- package/.framework/docs/gap-remediation-backlog.md +103 -0
- package/.framework/docs/guides/cli-guide.md +224 -0
- package/.framework/docs/guides/getting-started.md +103 -0
- package/.framework/docs/guides/import-guide.md +193 -0
- package/.framework/docs/guides/testing-guide.md +229 -0
- package/.framework/docs/permission-examples.md +326 -0
- package/.framework/docs/ui-adoption-verification.md +111 -0
- package/.framework/docs/ui-api-coverage.md +84 -0
- package/.framework/docs/v2-compatibility-audit.md +228 -0
- package/.framework/functions/.gitkeep +1 -0
- package/.framework/functions/_shared/agent-runner.ts +1097 -0
- package/.framework/functions/_shared/app-manifest.ts +184 -0
- package/.framework/functions/_shared/audit.ts +150 -0
- package/.framework/functions/_shared/db.ts +174 -0
- package/.framework/functions/_shared/index.ts +382 -0
- package/.framework/functions/_shared/middleware.ts +490 -0
- package/.framework/functions/_shared/permissions.ts +1325 -0
- package/.framework/functions/_shared/pipeline-runner.ts +731 -0
- package/.framework/functions/_shared/principal.ts +760 -0
- package/.framework/functions/_shared/schema-utils.ts +967 -0
- package/.framework/functions/_shared/testing.ts +258 -0
- package/.framework/functions/_shared/trigger-engine.ts +425 -0
- package/.framework/functions/_shared/webhook-registration.ts +168 -0
- package/.framework/functions/_shared/webhook-registry.ts +129 -0
- package/.framework/functions/account-nodes.ts +111 -0
- package/.framework/functions/admin-data.ts +606 -0
- package/.framework/functions/ai-agents.ts +323 -0
- package/.framework/functions/api-keys.ts +376 -0
- package/.framework/functions/apps.ts +483 -0
- package/.framework/functions/auth.ts +196 -0
- package/.framework/functions/debug-auth.ts +107 -0
- package/.framework/functions/embeddings.ts +556 -0
- package/.framework/functions/integration-routes.ts +523 -0
- package/.framework/functions/integrations.ts +319 -0
- package/.framework/functions/item-progress.ts +272 -0
- package/.framework/functions/logs.ts +438 -0
- package/.framework/functions/observability.ts +275 -0
- package/.framework/functions/pipeline-executions.ts +494 -0
- package/.framework/functions/pipelines.ts +485 -0
- package/.framework/functions/prompt-configs.ts +339 -0
- package/.framework/functions/roles.ts +387 -0
- package/.framework/functions/system-cron.ts +742 -0
- package/.framework/functions/system.ts +323 -0
- package/.framework/functions/tests.ts +119 -0
- package/.framework/functions/timers.ts +357 -0
- package/.framework/functions/triggers.ts +563 -0
- package/.framework/functions/types.ts +604 -0
- package/.framework/migrations/000_foundation.sql +1256 -0
- package/.framework/migrations/001_seed.sql +92 -0
- package/.framework/migrations/002_seed_constraints.sql +13 -0
- package/.framework/migrations/003_auth_user_trigger.sql +59 -0
- package/.framework/src/App.tsx +126 -0
- package/.framework/src/apps/admin/index.tsx +173 -0
- package/.framework/src/components/AppWrapper.tsx +56 -0
- package/.framework/src/components/CustomAppLoader.tsx +116 -0
- package/.framework/src/components/admin/AdminListPage.tsx +151 -0
- package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
- package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
- package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
- package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
- package/.framework/src/components/app-sidebar.tsx +228 -0
- package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
- package/.framework/src/components/layout/AppShell.tsx +91 -0
- package/.framework/src/components/layout/Header.tsx +88 -0
- package/.framework/src/components/layout/Layout.tsx +95 -0
- package/.framework/src/components/layout/Sidebar.tsx +329 -0
- package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
- package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
- package/.framework/src/components/runtime/DataFilters.tsx +91 -0
- package/.framework/src/components/runtime/DataHeader.tsx +68 -0
- package/.framework/src/components/runtime/DataListPage.tsx +124 -0
- package/.framework/src/components/runtime/DataStats.tsx +70 -0
- package/.framework/src/components/runtime/DataTable.tsx +174 -0
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
- package/.framework/src/components/runtime/index.ts +18 -0
- package/.framework/src/components/search-form.tsx +29 -0
- package/.framework/src/components/shared/AgentView.tsx +213 -0
- package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
- package/.framework/src/components/shared/SchemaFields.tsx +226 -0
- package/.framework/src/components/ui/DataTable.tsx +343 -0
- package/.framework/src/components/ui/Form.tsx +281 -0
- package/.framework/src/components/ui/ItemCard.tsx +296 -0
- package/.framework/src/components/ui/ItemListView.tsx +308 -0
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
- package/.framework/src/components/ui/Modal.tsx +61 -0
- package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
- package/.framework/src/components/ui/accordion.tsx +82 -0
- package/.framework/src/components/ui/alert-dialog.tsx +197 -0
- package/.framework/src/components/ui/alert.tsx +76 -0
- package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
- package/.framework/src/components/ui/avatar.tsx +110 -0
- package/.framework/src/components/ui/badge.tsx +49 -0
- package/.framework/src/components/ui/breadcrumb.tsx +122 -0
- package/.framework/src/components/ui/button-group.tsx +83 -0
- package/.framework/src/components/ui/button.tsx +65 -0
- package/.framework/src/components/ui/calendar.tsx +222 -0
- package/.framework/src/components/ui/card.tsx +100 -0
- package/.framework/src/components/ui/carousel.tsx +240 -0
- package/.framework/src/components/ui/chart.tsx +373 -0
- package/.framework/src/components/ui/checkbox.tsx +31 -0
- package/.framework/src/components/ui/collapsible.tsx +33 -0
- package/.framework/src/components/ui/combobox.tsx +299 -0
- package/.framework/src/components/ui/command.tsx +193 -0
- package/.framework/src/components/ui/context-menu.tsx +261 -0
- package/.framework/src/components/ui/dialog.tsx +165 -0
- package/.framework/src/components/ui/direction.tsx +22 -0
- package/.framework/src/components/ui/drawer.tsx +132 -0
- package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
- package/.framework/src/components/ui/empty.tsx +104 -0
- package/.framework/src/components/ui/field.tsx +238 -0
- package/.framework/src/components/ui/hover-card.tsx +42 -0
- package/.framework/src/components/ui/input-group.tsx +153 -0
- package/.framework/src/components/ui/input-otp.tsx +87 -0
- package/.framework/src/components/ui/input.tsx +19 -0
- package/.framework/src/components/ui/item.tsx +196 -0
- package/.framework/src/components/ui/kbd.tsx +26 -0
- package/.framework/src/components/ui/label.tsx +22 -0
- package/.framework/src/components/ui/menubar.tsx +277 -0
- package/.framework/src/components/ui/native-select.tsx +61 -0
- package/.framework/src/components/ui/navigation-menu.tsx +164 -0
- package/.framework/src/components/ui/pagination.tsx +129 -0
- package/.framework/src/components/ui/popover.tsx +87 -0
- package/.framework/src/components/ui/progress.tsx +31 -0
- package/.framework/src/components/ui/radio-group.tsx +42 -0
- package/.framework/src/components/ui/resizable.tsx +50 -0
- package/.framework/src/components/ui/scroll-area.tsx +53 -0
- package/.framework/src/components/ui/select.tsx +195 -0
- package/.framework/src/components/ui/separator.tsx +26 -0
- package/.framework/src/components/ui/sheet.tsx +145 -0
- package/.framework/src/components/ui/sidebar.tsx +706 -0
- package/.framework/src/components/ui/skeleton.tsx +13 -0
- package/.framework/src/components/ui/slider.tsx +59 -0
- package/.framework/src/components/ui/sonner.tsx +47 -0
- package/.framework/src/components/ui/spinner.tsx +10 -0
- package/.framework/src/components/ui/switch.tsx +33 -0
- package/.framework/src/components/ui/table-primitives.tsx +141 -0
- package/.framework/src/components/ui/table.tsx +114 -0
- package/.framework/src/components/ui/tabs.tsx +90 -0
- package/.framework/src/components/ui/textarea.tsx +18 -0
- package/.framework/src/components/ui/toggle-group.tsx +89 -0
- package/.framework/src/components/ui/toggle.tsx +45 -0
- package/.framework/src/components/ui/tooltip.tsx +57 -0
- package/.framework/src/contexts/AppContext.tsx +133 -0
- package/.framework/src/contexts/AuthContext.tsx +371 -0
- package/.framework/src/hooks/use-mobile.ts +19 -0
- package/.framework/src/hooks/useApi.ts +526 -0
- package/.framework/src/hooks/useApps.ts +114 -0
- package/.framework/src/hooks/useEntityList.ts +190 -0
- package/.framework/src/hooks/useEntityRecord.ts +308 -0
- package/.framework/src/hooks/useForm.ts +307 -0
- package/.framework/src/hooks/useListSchema.ts +264 -0
- package/.framework/src/hooks/useSchemaRecord.ts +223 -0
- package/.framework/src/index.css +128 -0
- package/.framework/src/lib/api.ts +156 -0
- package/.framework/src/lib/supabase.ts +94 -0
- package/.framework/src/lib/utils.ts +317 -0
- package/.framework/src/main.tsx +27 -0
- package/.framework/src/pages/DashboardPage.tsx +181 -0
- package/.framework/src/pages/NotFoundPage.tsx +39 -0
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
- package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
- package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
- package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
- package/.framework/src/pages/admin/AppsPage.tsx +355 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
- package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
- package/.framework/src/pages/admin/LogsPage.tsx +283 -0
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
- package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
- package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
- package/.framework/src/pages/admin/RolesPage.tsx +292 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
- package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
- package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
- package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
- package/.framework/src/pages/admin/TimersPage.tsx +376 -0
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
- package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
- package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
- package/.framework/src/pages/admin/TypesPage.tsx +295 -0
- package/.framework/src/pages/auth/LoginPage.tsx +188 -0
- package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
- package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
- package/.framework/src/types/auth.ts +125 -0
- package/.framework/src/types/types.ts +407 -0
- package/STRUCTURE.md +150 -0
- package/config/components.json +25 -0
- package/config/deno.lock +108 -0
- package/config/package-lock.json +17183 -0
- package/config/postcss.config.cjs +10 -0
- package/config/tailwind.config.cjs +78 -0
- package/config/tsconfig.build.json +32 -0
- package/config/tsconfig.cli.json +18 -0
- package/config/tsconfig.json +41 -0
- package/config/tsconfig.node.json +17 -0
- package/config/tsconfig.node.tsbuildinfo +1 -0
- package/config/tsconfig.tsbuildinfo +1 -0
- package/config/typedoc.json +16 -0
- package/config/vite.config.d.ts +2 -0
- package/config/vite.config.ts +72 -0
- package/dist/cli/commands/agents.d.ts +39 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/auth.d.ts +36 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/create-app.d.ts +23 -0
- package/dist/cli/commands/create-app.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +39 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +42 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +36 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +30 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/install-app.d.ts +30 -0
- package/dist/cli/commands/install-app.d.ts.map +1 -0
- package/dist/cli/commands/items.d.ts +45 -0
- package/dist/cli/commands/items.d.ts.map +1 -0
- package/dist/cli/commands/migrations.d.ts +41 -0
- package/dist/cli/commands/migrations.d.ts.map +1 -0
- package/dist/cli/commands/pipelines.d.ts +40 -0
- package/dist/cli/commands/pipelines.d.ts.map +1 -0
- package/dist/cli/commands/status.d.ts +23 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/system.d.ts +29 -0
- package/dist/cli/commands/system.d.ts.map +1 -0
- package/dist/cli/commands/test.d.ts +46 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/uninstall-app.d.ts +23 -0
- package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
- package/dist/cli/context.d.ts +88 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/env-loader.d.ts +14 -0
- package/dist/cli/env-loader.d.ts.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/functions/_shared/agent-runner.d.ts +156 -0
- package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
- package/dist/functions/_shared/app-manifest.d.ts +68 -0
- package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
- package/dist/functions/_shared/audit.d.ts +91 -0
- package/dist/functions/_shared/audit.d.ts.map +1 -0
- package/dist/functions/_shared/db.d.ts +125 -0
- package/dist/functions/_shared/db.d.ts.map +1 -0
- package/dist/functions/_shared/index.d.ts +298 -0
- package/dist/functions/_shared/index.d.ts.map +1 -0
- package/dist/functions/_shared/middleware.d.ts +315 -0
- package/dist/functions/_shared/middleware.d.ts.map +1 -0
- package/dist/functions/_shared/permissions.d.ts +626 -0
- package/dist/functions/_shared/permissions.d.ts.map +1 -0
- package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
- package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
- package/dist/functions/_shared/principal.d.ts +284 -0
- package/dist/functions/_shared/principal.d.ts.map +1 -0
- package/dist/functions/_shared/schema-utils.d.ts +181 -0
- package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
- package/dist/functions/_shared/testing.d.ts +172 -0
- package/dist/functions/_shared/testing.d.ts.map +1 -0
- package/dist/functions/_shared/trigger-engine.d.ts +140 -0
- package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registration.d.ts +81 -0
- package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registry.d.ts +57 -0
- package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
- package/dist/functions/account-nodes.d.ts +48 -0
- package/dist/functions/account-nodes.d.ts.map +1 -0
- package/dist/functions/admin-data.d.ts +178 -0
- package/dist/functions/admin-data.d.ts.map +1 -0
- package/dist/functions/ai-agents.d.ts +125 -0
- package/dist/functions/ai-agents.d.ts.map +1 -0
- package/dist/functions/api-keys.d.ts +140 -0
- package/dist/functions/api-keys.d.ts.map +1 -0
- package/dist/functions/apps.d.ts +163 -0
- package/dist/functions/apps.d.ts.map +1 -0
- package/dist/functions/auth.d.ts +74 -0
- package/dist/functions/auth.d.ts.map +1 -0
- package/dist/functions/debug-auth.d.ts +33 -0
- package/dist/functions/debug-auth.d.ts.map +1 -0
- package/dist/functions/embeddings.d.ts +205 -0
- package/dist/functions/embeddings.d.ts.map +1 -0
- package/dist/functions/integration-routes.d.ts +45 -0
- package/dist/functions/integration-routes.d.ts.map +1 -0
- package/dist/functions/integrations.d.ts +124 -0
- package/dist/functions/integrations.d.ts.map +1 -0
- package/dist/functions/item-progress.d.ts +41 -0
- package/dist/functions/item-progress.d.ts.map +1 -0
- package/dist/functions/logs.d.ts +162 -0
- package/dist/functions/logs.d.ts.map +1 -0
- package/dist/functions/observability.d.ts +123 -0
- package/dist/functions/observability.d.ts.map +1 -0
- package/dist/functions/pipeline-executions.d.ts +190 -0
- package/dist/functions/pipeline-executions.d.ts.map +1 -0
- package/dist/functions/pipelines.d.ts +171 -0
- package/dist/functions/pipelines.d.ts.map +1 -0
- package/dist/functions/prompt-configs.d.ts +125 -0
- package/dist/functions/prompt-configs.d.ts.map +1 -0
- package/dist/functions/roles.d.ts +118 -0
- package/dist/functions/roles.d.ts.map +1 -0
- package/dist/functions/system-cron.d.ts +65 -0
- package/dist/functions/system-cron.d.ts.map +1 -0
- package/dist/functions/system.d.ts +29 -0
- package/dist/functions/system.d.ts.map +1 -0
- package/dist/functions/tests.d.ts +28 -0
- package/dist/functions/tests.d.ts.map +1 -0
- package/dist/functions/timers.d.ts +139 -0
- package/dist/functions/timers.d.ts.map +1 -0
- package/dist/functions/triggers.d.ts +203 -0
- package/dist/functions/triggers.d.ts.map +1 -0
- package/dist/functions/types.d.ts +151 -0
- package/dist/functions/types.d.ts.map +1 -0
- package/dist/src/types/types.d.ts +364 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/package.json +192 -0
- package/scripts/app-install-cli.ts +286 -0
- package/scripts/assemble-frontend.sh +79 -0
- package/scripts/assemble-functions.sh +62 -0
- package/scripts/assemble.sh +35 -0
- package/scripts/boundary-check.sh +106 -0
- package/scripts/build-manifest.sh +80 -0
- package/scripts/check-core-integrity.sh +82 -0
- package/scripts/ingest-chunks.cjs +202 -0
- package/scripts/kb-chunk-parser.cjs +312 -0
- package/scripts/kb-chunk-parser.ts +330 -0
- package/scripts/load-test-app-install.ts +484 -0
- package/scripts/netlify-dev-wrapper.sh +22 -0
- package/scripts/verify-integrity.sh +69 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/components/ui/DataTable
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-component
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Higher-level table wrapper that adds search, column filters, active-filter
|
|
8
|
+
* chips, and pagination using shadcn/ui table primitives.
|
|
9
|
+
*
|
|
10
|
+
* **Search:** when `searchable=true`, renders a text input that calls
|
|
11
|
+
* `onSearch` on each keystroke and shows an active chip while a query
|
|
12
|
+
* is present.
|
|
13
|
+
*
|
|
14
|
+
* **Column filters:** columns with `filterable=true` and `filterOptions`
|
|
15
|
+
* appear in an expandable filter panel as `<select>` dropdowns. Active
|
|
16
|
+
* filter chips are shown above the table; each chip has an inline
|
|
17
|
+
* dismiss button.
|
|
18
|
+
*
|
|
19
|
+
* **Pagination:** built-in pagination controls using shadcn Button components.
|
|
20
|
+
*
|
|
21
|
+
* @seeAlso src/components/ui/table.tsx
|
|
22
|
+
* @seeAlso src/lib/utils.ts (cn)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import React, { useState } from 'react'
|
|
26
|
+
import {
|
|
27
|
+
Table,
|
|
28
|
+
TableBody,
|
|
29
|
+
TableCell,
|
|
30
|
+
TableHead,
|
|
31
|
+
TableHeader,
|
|
32
|
+
TableRow,
|
|
33
|
+
} from './table'
|
|
34
|
+
import { Badge } from './badge'
|
|
35
|
+
import { Button } from './button'
|
|
36
|
+
import { Search, Filter } from 'lucide-react'
|
|
37
|
+
import { cn } from '../../lib/utils'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Column definition for DataTable
|
|
41
|
+
*/
|
|
42
|
+
interface TableColumn<T> {
|
|
43
|
+
key: keyof T | string
|
|
44
|
+
title: string
|
|
45
|
+
render?: (row: T) => React.ReactNode
|
|
46
|
+
sortable?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extends `TableColumn<T>` with column-level filter support.
|
|
51
|
+
*/
|
|
52
|
+
interface DataTableColumn<T> extends TableColumn<T> {
|
|
53
|
+
filterable?: boolean
|
|
54
|
+
filterOptions?: Array<{ value: string; label: string }>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Props for `DataTable<T>`.
|
|
59
|
+
*
|
|
60
|
+
* @prop data - Row data array
|
|
61
|
+
* @prop columns - Column descriptors (may include filter config)
|
|
62
|
+
* @prop loading - Shows spinner while true
|
|
63
|
+
* @prop searchable - Shows search input (default: `true`)
|
|
64
|
+
* @prop searchPlaceholder - Placeholder text for the search input
|
|
65
|
+
* @prop filterable - Shows the filter toggle button (default: `true`)
|
|
66
|
+
* @prop pagination - Pagination config; omit to hide pagination
|
|
67
|
+
* @prop onSort / sortColumn / sortDirection - Sort state and callback
|
|
68
|
+
* @prop onRowClick - Row click callback
|
|
69
|
+
* @prop onSearch - Callback invoked with the current search query
|
|
70
|
+
* @prop onFilter - Callback invoked with the active filter map
|
|
71
|
+
* @prop emptyMessage - Empty-state text
|
|
72
|
+
*/
|
|
73
|
+
interface DataTableProps<T> {
|
|
74
|
+
data: T[]
|
|
75
|
+
columns: DataTableColumn<T>[]
|
|
76
|
+
loading?: boolean
|
|
77
|
+
searchable?: boolean
|
|
78
|
+
searchPlaceholder?: string
|
|
79
|
+
filterable?: boolean
|
|
80
|
+
pagination?: {
|
|
81
|
+
currentPage: number
|
|
82
|
+
totalPages: number
|
|
83
|
+
totalItems: number
|
|
84
|
+
itemsPerPage: number
|
|
85
|
+
onPageChange: (page: number) => void
|
|
86
|
+
onItemsPerPageChange: (itemsPerPage: number) => void
|
|
87
|
+
}
|
|
88
|
+
onSort?: (column: keyof T, direction: 'asc' | 'desc') => void
|
|
89
|
+
sortColumn?: keyof T
|
|
90
|
+
sortDirection?: 'asc' | 'desc'
|
|
91
|
+
onRowClick?: (item: T) => void
|
|
92
|
+
onSearch?: (query: string) => void
|
|
93
|
+
onFilter?: (filters: Record<string, any>) => void
|
|
94
|
+
emptyMessage?: string
|
|
95
|
+
className?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Full-featured data table with search, filters, sort, and pagination.
|
|
100
|
+
*
|
|
101
|
+
* @param props - `DataTableProps<T>`
|
|
102
|
+
* @returns Search/filter bar + `Table` + optional `TablePagination`
|
|
103
|
+
* @sideEffects none (all state changes delegated to callbacks)
|
|
104
|
+
*/
|
|
105
|
+
export function DataTable<T extends Record<string, any>>({
|
|
106
|
+
data,
|
|
107
|
+
columns,
|
|
108
|
+
loading = false,
|
|
109
|
+
searchable = true,
|
|
110
|
+
searchPlaceholder = 'Search...',
|
|
111
|
+
filterable = true,
|
|
112
|
+
pagination,
|
|
113
|
+
onSort,
|
|
114
|
+
sortColumn,
|
|
115
|
+
sortDirection,
|
|
116
|
+
onRowClick,
|
|
117
|
+
onSearch,
|
|
118
|
+
onFilter,
|
|
119
|
+
emptyMessage = 'No data available',
|
|
120
|
+
className
|
|
121
|
+
}: DataTableProps<T>) {
|
|
122
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
123
|
+
const [filters, setFilters] = useState<Record<string, any>>({})
|
|
124
|
+
const [showFilters, setShowFilters] = useState(false)
|
|
125
|
+
|
|
126
|
+
const handleSearch = (query: string) => {
|
|
127
|
+
setSearchQuery(query)
|
|
128
|
+
onSearch?.(query)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const handleFilter = (key: string, value: any) => {
|
|
132
|
+
const newFilters = { ...filters, [key]: value }
|
|
133
|
+
if (value === '' || value === null || value === undefined) {
|
|
134
|
+
delete newFilters[key]
|
|
135
|
+
}
|
|
136
|
+
setFilters(newFilters)
|
|
137
|
+
onFilter?.(newFilters)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const clearFilters = () => {
|
|
141
|
+
setFilters({})
|
|
142
|
+
onFilter?.({})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const hasActiveFilters = Object.keys(filters).length > 0
|
|
146
|
+
const hasActiveSearch = searchQuery.length > 0
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div className={cn('space-y-4', className)}>
|
|
150
|
+
{/* Search and Filters */}
|
|
151
|
+
{(searchable || filterable) && (
|
|
152
|
+
<div className="bg-card shadow rounded-lg p-4">
|
|
153
|
+
<div className="flex flex-col sm:flex-row gap-4">
|
|
154
|
+
{/* Search */}
|
|
155
|
+
{searchable && (
|
|
156
|
+
<div className="flex-1">
|
|
157
|
+
<div className="relative">
|
|
158
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-muted-foreground" />
|
|
159
|
+
<input
|
|
160
|
+
type="text"
|
|
161
|
+
placeholder={searchPlaceholder}
|
|
162
|
+
value={searchQuery}
|
|
163
|
+
onChange={(e) => handleSearch(e.target.value)}
|
|
164
|
+
className="pl-10 pr-4 py-2 w-full border border-input rounded-md focus:ring-ring focus:border-ring"
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{/* Filters */}
|
|
171
|
+
{filterable && (
|
|
172
|
+
<div className="flex items-center space-x-2">
|
|
173
|
+
<Button
|
|
174
|
+
variant="outline"
|
|
175
|
+
size="sm"
|
|
176
|
+
onClick={() => setShowFilters(!showFilters)}
|
|
177
|
+
className={cn(
|
|
178
|
+
hasActiveFilters && 'bg-primary/10 border-primary text-primary'
|
|
179
|
+
)}
|
|
180
|
+
>
|
|
181
|
+
<Filter className="h-4 w-4 mr-2" />
|
|
182
|
+
Filters
|
|
183
|
+
{hasActiveFilters && (
|
|
184
|
+
<Badge variant="info">
|
|
185
|
+
{Object.keys(filters).length}
|
|
186
|
+
</Badge>
|
|
187
|
+
)}
|
|
188
|
+
</Button>
|
|
189
|
+
|
|
190
|
+
{hasActiveFilters && (
|
|
191
|
+
<Button variant="ghost" size="sm" onClick={clearFilters}>
|
|
192
|
+
Clear
|
|
193
|
+
</Button>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{/* Advanced Filters */}
|
|
200
|
+
{showFilters && filterable && (
|
|
201
|
+
<div className="mt-4 pt-4 border-t border-border">
|
|
202
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
203
|
+
{columns
|
|
204
|
+
.filter(column => column.filterable && column.filterOptions)
|
|
205
|
+
.map((column) => (
|
|
206
|
+
<div key={String(column.key)}>
|
|
207
|
+
<label className="block text-sm font-medium text-foreground mb-1">
|
|
208
|
+
{column.title}
|
|
209
|
+
</label>
|
|
210
|
+
<select
|
|
211
|
+
value={filters[String(column.key)] || ''}
|
|
212
|
+
onChange={(e) => handleFilter(String(column.key), e.target.value || null)}
|
|
213
|
+
className="w-full px-3 py-2 border border-input rounded-md focus:ring-ring focus:border-ring"
|
|
214
|
+
>
|
|
215
|
+
<option value="">All</option>
|
|
216
|
+
{column.filterOptions?.map((option) => (
|
|
217
|
+
<option key={option.value} value={option.value}>
|
|
218
|
+
{option.label}
|
|
219
|
+
</option>
|
|
220
|
+
))}
|
|
221
|
+
</select>
|
|
222
|
+
</div>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{/* Active filters display */}
|
|
231
|
+
{(hasActiveFilters || hasActiveSearch) && (
|
|
232
|
+
<div className="flex flex-wrap gap-2">
|
|
233
|
+
{hasActiveSearch && (
|
|
234
|
+
<Badge variant="info" className="flex items-center">
|
|
235
|
+
Search: "{searchQuery}"
|
|
236
|
+
<button
|
|
237
|
+
onClick={() => handleSearch('')}
|
|
238
|
+
className="ml-1 text-primary hover:text-primary/80"
|
|
239
|
+
>
|
|
240
|
+
×
|
|
241
|
+
</button>
|
|
242
|
+
</Badge>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{Object.entries(filters).map(([key, value]) => {
|
|
246
|
+
const column = columns.find(col => String(col.key) === key)
|
|
247
|
+
const option = column?.filterOptions?.find(opt => opt.value === value)
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<Badge key={key} variant="info" className="flex items-center">
|
|
251
|
+
{column?.title}: {option?.label || value}
|
|
252
|
+
<button
|
|
253
|
+
onClick={() => handleFilter(key, null)}
|
|
254
|
+
className="ml-1 text-primary hover:text-primary/80"
|
|
255
|
+
>
|
|
256
|
+
×
|
|
257
|
+
</button>
|
|
258
|
+
</Badge>
|
|
259
|
+
)
|
|
260
|
+
})}
|
|
261
|
+
</div>
|
|
262
|
+
)}
|
|
263
|
+
|
|
264
|
+
{/* Table */}
|
|
265
|
+
<div className="rounded-md border">
|
|
266
|
+
<Table>
|
|
267
|
+
<TableHeader>
|
|
268
|
+
<TableRow>
|
|
269
|
+
{columns.map((column) => (
|
|
270
|
+
<TableHead key={String(column.key)} className={column.sortable ? 'cursor-pointer' : ''}>
|
|
271
|
+
{column.title}
|
|
272
|
+
</TableHead>
|
|
273
|
+
))}
|
|
274
|
+
</TableRow>
|
|
275
|
+
</TableHeader>
|
|
276
|
+
<TableBody>
|
|
277
|
+
{loading ? (
|
|
278
|
+
<TableRow>
|
|
279
|
+
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
280
|
+
Loading...
|
|
281
|
+
</TableCell>
|
|
282
|
+
</TableRow>
|
|
283
|
+
) : data.length === 0 ? (
|
|
284
|
+
<TableRow>
|
|
285
|
+
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
286
|
+
{emptyMessage}
|
|
287
|
+
</TableCell>
|
|
288
|
+
</TableRow>
|
|
289
|
+
) : (
|
|
290
|
+
data.map((row, index) => (
|
|
291
|
+
<TableRow
|
|
292
|
+
key={index}
|
|
293
|
+
onClick={() => onRowClick?.(row)}
|
|
294
|
+
className={onRowClick ? 'cursor-pointer' : ''}
|
|
295
|
+
>
|
|
296
|
+
{columns.map((column) => (
|
|
297
|
+
<TableCell key={String(column.key)}>
|
|
298
|
+
{column.render
|
|
299
|
+
? column.render(row)
|
|
300
|
+
: String(row[column.key as keyof T] ?? '')}
|
|
301
|
+
</TableCell>
|
|
302
|
+
))}
|
|
303
|
+
</TableRow>
|
|
304
|
+
))
|
|
305
|
+
)}
|
|
306
|
+
</TableBody>
|
|
307
|
+
</Table>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
{/* Pagination */}
|
|
311
|
+
{pagination && (
|
|
312
|
+
<div className="flex items-center justify-between px-2">
|
|
313
|
+
<div className="text-sm text-muted-foreground">
|
|
314
|
+
Showing {(pagination.currentPage - 1) * pagination.itemsPerPage + 1} to{' '}
|
|
315
|
+
{Math.min(pagination.currentPage * pagination.itemsPerPage, pagination.totalItems)} of{' '}
|
|
316
|
+
{pagination.totalItems} entries
|
|
317
|
+
</div>
|
|
318
|
+
<div className="flex items-center gap-2">
|
|
319
|
+
<Button
|
|
320
|
+
variant="outline"
|
|
321
|
+
size="sm"
|
|
322
|
+
onClick={() => pagination.onPageChange(pagination.currentPage - 1)}
|
|
323
|
+
disabled={pagination.currentPage <= 1}
|
|
324
|
+
>
|
|
325
|
+
Previous
|
|
326
|
+
</Button>
|
|
327
|
+
<span className="text-sm">
|
|
328
|
+
Page {pagination.currentPage} of {pagination.totalPages}
|
|
329
|
+
</span>
|
|
330
|
+
<Button
|
|
331
|
+
variant="outline"
|
|
332
|
+
size="sm"
|
|
333
|
+
onClick={() => pagination.onPageChange(pagination.currentPage + 1)}
|
|
334
|
+
disabled={pagination.currentPage >= pagination.totalPages}
|
|
335
|
+
>
|
|
336
|
+
Next
|
|
337
|
+
</Button>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
)}
|
|
341
|
+
</div>
|
|
342
|
+
)
|
|
343
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/components/ui/Form
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-component
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Schema-driven form layout components.
|
|
8
|
+
*
|
|
9
|
+
* **`Form`** — renders a `<form>` element from an ordered `FieldDefinition[]`
|
|
10
|
+
* array via `FieldRenderer`. Manages touched-based error display (errors only
|
|
11
|
+
* shown for fields the user has interacted with). Includes Submit + optional
|
|
12
|
+
* Cancel buttons.
|
|
13
|
+
*
|
|
14
|
+
* **`FormField`** — single field wrapper delegating to `FieldRenderer` with
|
|
15
|
+
* touched-based error display.
|
|
16
|
+
*
|
|
17
|
+
* **`FormSection`** — optional titled group container for logical field
|
|
18
|
+
* groups within a form.
|
|
19
|
+
*
|
|
20
|
+
* **`FormRow`** / **`FormColumn`** — two-column responsive grid layout
|
|
21
|
+
* helpers. `FormColumn` accepts `span=2` to span both columns.
|
|
22
|
+
*
|
|
23
|
+
* @seeAlso src/components/shared/FieldRenderer.tsx
|
|
24
|
+
* @seeAlso src/hooks/useForm.ts (state management companion)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import React from 'react'
|
|
28
|
+
import { FieldDefinition } from '../../types/types'
|
|
29
|
+
import { FieldRenderer } from '../shared/FieldRenderer'
|
|
30
|
+
import { Button } from './button'
|
|
31
|
+
import { cn } from '../../lib/utils'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Props for `Form`.
|
|
35
|
+
*
|
|
36
|
+
* @prop fields - Ordered field definitions to render
|
|
37
|
+
* @prop data - Controlled field values keyed by field name
|
|
38
|
+
* @prop errors - Validation errors per field name
|
|
39
|
+
* @prop touched - Map of fields that have been interacted with
|
|
40
|
+
* @prop onChange - `(field, value)` change callback
|
|
41
|
+
* @prop onBlur - `(field)` blur callback for marking a field as touched
|
|
42
|
+
* @prop onSubmit - Form submit handler (receives the native `FormEvent`)
|
|
43
|
+
* @prop isSubmitting - Shows spinner on submit button when true
|
|
44
|
+
* @prop submitText - Submit button label (default: `'Submit'`)
|
|
45
|
+
* @prop cancelText - Cancel button label (default: `'Cancel'`)
|
|
46
|
+
* @prop onCancel - Cancel button callback
|
|
47
|
+
* @prop showCancel - Shows the cancel button (default: `false`)
|
|
48
|
+
* @prop disabled - Disables all fields and the submit button
|
|
49
|
+
* @prop className - Additional Tailwind classes for the `<form>` element
|
|
50
|
+
*/
|
|
51
|
+
interface FormProps {
|
|
52
|
+
fields: FieldDefinition[]
|
|
53
|
+
data: Record<string, any>
|
|
54
|
+
errors: Record<string, string>
|
|
55
|
+
touched: Record<string, boolean>
|
|
56
|
+
onChange: (field: string, value: any) => void
|
|
57
|
+
onBlur?: (field: string) => void
|
|
58
|
+
onSubmit?: (e: React.FormEvent) => void
|
|
59
|
+
isSubmitting?: boolean
|
|
60
|
+
submitText?: string
|
|
61
|
+
cancelText?: string
|
|
62
|
+
onCancel?: () => void
|
|
63
|
+
showCancel?: boolean
|
|
64
|
+
disabled?: boolean
|
|
65
|
+
className?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Schema-driven `<form>` with submit + optional cancel actions.
|
|
70
|
+
*
|
|
71
|
+
* @param props - `FormProps`
|
|
72
|
+
* @returns Form element with field list and action buttons
|
|
73
|
+
* @sideEffects Calls `e.preventDefault()` on submit before forwarding to `onSubmit`
|
|
74
|
+
*/
|
|
75
|
+
export function Form({
|
|
76
|
+
fields,
|
|
77
|
+
data,
|
|
78
|
+
errors,
|
|
79
|
+
touched,
|
|
80
|
+
onChange,
|
|
81
|
+
onBlur,
|
|
82
|
+
onSubmit,
|
|
83
|
+
isSubmitting = false,
|
|
84
|
+
submitText = 'Submit',
|
|
85
|
+
cancelText = 'Cancel',
|
|
86
|
+
onCancel,
|
|
87
|
+
showCancel = false,
|
|
88
|
+
disabled = false,
|
|
89
|
+
className
|
|
90
|
+
}: FormProps) {
|
|
91
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
92
|
+
e.preventDefault()
|
|
93
|
+
onSubmit?.(e)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const handleFieldChange = (fieldName: string, value: any) => {
|
|
97
|
+
onChange(fieldName, value)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const handleFieldBlur = (fieldName: string) => {
|
|
101
|
+
onBlur?.(fieldName)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<form onSubmit={handleSubmit} className={cn('space-y-6', className)}>
|
|
106
|
+
{fields.filter(f => !!f.name).map((field) => {
|
|
107
|
+
const name = field.name!
|
|
108
|
+
return (
|
|
109
|
+
<FieldRenderer
|
|
110
|
+
key={name}
|
|
111
|
+
field={field}
|
|
112
|
+
value={data[name]}
|
|
113
|
+
onChange={(value) => handleFieldChange(name, value)}
|
|
114
|
+
error={touched[name] ? errors[name] : undefined}
|
|
115
|
+
onBlur={() => handleFieldBlur(name)}
|
|
116
|
+
readonly={disabled}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
})}
|
|
120
|
+
|
|
121
|
+
{/* Form Actions */}
|
|
122
|
+
<div className="flex justify-end space-x-3">
|
|
123
|
+
{showCancel && (
|
|
124
|
+
<Button
|
|
125
|
+
type="button"
|
|
126
|
+
variant="outline"
|
|
127
|
+
onClick={onCancel}
|
|
128
|
+
disabled={isSubmitting}
|
|
129
|
+
>
|
|
130
|
+
{cancelText}
|
|
131
|
+
</Button>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
<Button
|
|
135
|
+
type="submit"
|
|
136
|
+
loading={isSubmitting}
|
|
137
|
+
disabled={disabled}
|
|
138
|
+
>
|
|
139
|
+
{submitText}
|
|
140
|
+
</Button>
|
|
141
|
+
</div>
|
|
142
|
+
</form>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Props for `FormField`.
|
|
148
|
+
*
|
|
149
|
+
* @prop field - Field definition
|
|
150
|
+
* @prop value - Current value
|
|
151
|
+
* @prop error - Validation error string
|
|
152
|
+
* @prop touched - Whether this field has been interacted with (gates error display)
|
|
153
|
+
* @prop onChange / onBlur / readonly - Passed through to `FieldRenderer`
|
|
154
|
+
*/
|
|
155
|
+
interface FormFieldProps {
|
|
156
|
+
field: FieldDefinition
|
|
157
|
+
value: any
|
|
158
|
+
error?: string
|
|
159
|
+
touched?: boolean
|
|
160
|
+
onChange: (value: any) => void
|
|
161
|
+
onBlur?: () => void
|
|
162
|
+
readonly?: boolean
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Single field with touched-gated error display.
|
|
167
|
+
*
|
|
168
|
+
* @param props - `FormFieldProps`
|
|
169
|
+
* @returns `FieldRenderer` instance with error shown only when touched
|
|
170
|
+
* @sideEffects none (delegates to `onChange` / `onBlur`)
|
|
171
|
+
*/
|
|
172
|
+
export function FormField({
|
|
173
|
+
field,
|
|
174
|
+
value,
|
|
175
|
+
error,
|
|
176
|
+
touched,
|
|
177
|
+
onChange,
|
|
178
|
+
onBlur,
|
|
179
|
+
readonly = false
|
|
180
|
+
}: FormFieldProps) {
|
|
181
|
+
return (
|
|
182
|
+
<FieldRenderer
|
|
183
|
+
field={field}
|
|
184
|
+
value={value}
|
|
185
|
+
onChange={onChange}
|
|
186
|
+
error={touched ? error : undefined}
|
|
187
|
+
onBlur={onBlur}
|
|
188
|
+
readonly={readonly}
|
|
189
|
+
/>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Props for `FormSection`.
|
|
195
|
+
*
|
|
196
|
+
* @prop title - Optional section heading
|
|
197
|
+
* @prop description - Optional subtitle text
|
|
198
|
+
*/
|
|
199
|
+
interface FormSectionProps {
|
|
200
|
+
title?: string
|
|
201
|
+
description?: string
|
|
202
|
+
children: React.ReactNode
|
|
203
|
+
className?: string
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Titled group container for a set of related form fields.
|
|
208
|
+
*
|
|
209
|
+
* @param props - `FormSectionProps`
|
|
210
|
+
* @returns `<div>` with optional title/description and `children` below
|
|
211
|
+
* @sideEffects none (pure rendering)
|
|
212
|
+
*/
|
|
213
|
+
export function FormSection({ title, description, children, className }: FormSectionProps) {
|
|
214
|
+
return (
|
|
215
|
+
<div className={cn('space-y-4', className)}>
|
|
216
|
+
{(title || description) && (
|
|
217
|
+
<div>
|
|
218
|
+
{title && (
|
|
219
|
+
<h3 className="text-lg font-medium text-slate-900">{title}</h3>
|
|
220
|
+
)}
|
|
221
|
+
{description && (
|
|
222
|
+
<p className="mt-1 text-sm text-slate-600">{description}</p>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
{children}
|
|
227
|
+
</div>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Props for `FormRow`. */
|
|
232
|
+
interface FormRowProps {
|
|
233
|
+
children: React.ReactNode
|
|
234
|
+
className?: string
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Two-column responsive grid row for form fields.
|
|
239
|
+
*
|
|
240
|
+
* @param props - children + optional className
|
|
241
|
+
* @returns `<div>` with `grid-cols-1 sm:grid-cols-2 gap-4`
|
|
242
|
+
* @sideEffects none (pure rendering)
|
|
243
|
+
*/
|
|
244
|
+
export function FormRow({ children, className }: FormRowProps) {
|
|
245
|
+
return (
|
|
246
|
+
<div className={cn('grid grid-cols-1 sm:grid-cols-2 gap-4', className)}>
|
|
247
|
+
{children}
|
|
248
|
+
</div>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Props for `FormColumn`.
|
|
254
|
+
*
|
|
255
|
+
* @prop span - Column span within a `FormRow` (1 or 2, default: 1)
|
|
256
|
+
*/
|
|
257
|
+
interface FormColumnProps {
|
|
258
|
+
children: React.ReactNode
|
|
259
|
+
span?: 1 | 2
|
|
260
|
+
className?: string
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Column within a `FormRow` grid.
|
|
265
|
+
*
|
|
266
|
+
* @param props - `FormColumnProps`
|
|
267
|
+
* @returns `<div>` with appropriate `col-span` class
|
|
268
|
+
* @sideEffects none (pure rendering)
|
|
269
|
+
*/
|
|
270
|
+
export function FormColumn({ children, span = 1, className }: FormColumnProps) {
|
|
271
|
+
const spanClasses = {
|
|
272
|
+
1: 'col-span-1',
|
|
273
|
+
2: 'col-span-2'
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div className={cn(spanClasses[span], className)}>
|
|
278
|
+
{children}
|
|
279
|
+
</div>
|
|
280
|
+
)
|
|
281
|
+
}
|