diff --git a/examples/app-showcase/objectstack.config.ts b/examples/app-showcase/objectstack.config.ts index 6a6e139efe..431b36238a 100644 --- a/examples/app-showcase/objectstack.config.ts +++ b/examples/app-showcase/objectstack.config.ts @@ -22,7 +22,7 @@ import { ChartGalleryDashboard, OpsDashboard } from './src/dashboards/index.js'; import { ShowcaseTaskDataset, ShowcaseProjectDataset } from './src/datasets/index.js'; import { allReports } from './src/reports/index.js'; import { allActions } from './src/actions/index.js'; -import { ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, PageVariablesPage, ContactFormPage } from './src/pages/index.js'; +import { ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, TaskDeskPage, PageVariablesPage, ContactFormPage } from './src/pages/index.js'; import { allFlows } from './src/flows/index.js'; import { allWebhooks } from './src/webhooks/index.js'; import { allHooks } from './src/hooks/index.js'; @@ -154,7 +154,7 @@ export default defineStack({ apps: [ShowcaseApp], portals: allPortals, views: [TaskViews, ProjectViews, InquiryViews, BusinessUnitViews], - pages: [ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, PageVariablesPage, ContactFormPage], + pages: [ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, TaskDeskPage, PageVariablesPage, ContactFormPage], dashboards: [ChartGalleryDashboard, OpsDashboard], books: allBooks, datasets: [ShowcaseTaskDataset, ShowcaseProjectDataset], diff --git a/examples/app-showcase/src/apps/index.ts b/examples/app-showcase/src/apps/index.ts index 91aadc65cc..fc59bc1699 100644 --- a/examples/app-showcase/src/apps/index.ts +++ b/examples/app-showcase/src/apps/index.ts @@ -70,6 +70,7 @@ export const ShowcaseApp = App.create({ { id: 'nav_inquiry_triage', type: 'page', pageName: 'showcase_inquiry_triage', label: 'Inquiry Triage (React)', icon: 'inbox' }, { id: 'nav_account_cockpit', type: 'page', pageName: 'showcase_account_cockpit', label: 'Account Cockpit (React)', icon: 'satellite' }, { id: 'nav_invoice_console', type: 'page', pageName: 'showcase_invoice_console', label: 'Invoice Console (React)', icon: 'receipt' }, + { id: 'nav_task_desk', type: 'page', pageName: 'showcase_task_desk', label: 'Task Desk (React)', icon: 'panel-right-open' }, { id: 'nav_styling_gallery', type: 'page', pageName: 'showcase_styling_gallery', label: 'Styling (ADR-0065)', icon: 'palette' }, { id: 'nav_page_variables', type: 'page', pageName: 'showcase_page_variables', label: 'Page Variables', icon: 'mouse-pointer-click' }, { id: 'nav_contact_form', type: 'page', pageName: 'showcase_contact_form', label: 'Contact Form', icon: 'mail-plus' }, diff --git a/examples/app-showcase/src/pages/index.ts b/examples/app-showcase/src/pages/index.ts index ec48f39cd4..cf0fd72a5f 100644 --- a/examples/app-showcase/src/pages/index.ts +++ b/examples/app-showcase/src/pages/index.ts @@ -20,6 +20,7 @@ export { CrmWorkbenchPage } from './crm-workbench.page.js'; export { InquiryTriagePage } from './inquiry-triage.page.js'; export { AccountCockpitPage } from './account-cockpit.page.js'; export { InvoiceConsolePage } from './invoice-console.page.js'; +export { TaskDeskPage } from './task-desk.page.js'; export { PageVariablesPage } from './page-variables.page.js'; export { ContactFormPage } from './contact-form.page.js'; export { diff --git a/examples/app-showcase/src/pages/task-desk.page.ts b/examples/app-showcase/src/pages/task-desk.page.ts new file mode 100644 index 0000000000..f57407878d --- /dev/null +++ b/examples/app-showcase/src/pages/task-desk.page.ts @@ -0,0 +1,85 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { definePage } from '@objectstack/spec/ui'; + +/** + * Task Desk — a `kind:'react'` business scenario (ADR-0081) showing the + * popup-edit patterns real apps need: a **slide-out drawer** to edit a row, and + * a **centered modal** to create. Both are author-built React overlays (fixed + * positioning + backdrop + Esc/close state) that wrap the platform's real + * `` — demonstrating that the react tier composes drawer/modal + * interactions the in-page master/detail layout can't. + */ +export const TaskDeskPage = definePage({ + name: 'showcase_task_desk', + label: 'Task Desk (React)', + type: 'home', + kind: 'react', + source: ` +function Page() { + const adapter = useAdapter(); + const [editId, setEditId] = React.useState(null); // drawer (edit existing) + const [creating, setCreating] = React.useState(false); // modal (create new) + const [reload, setReload] = React.useState(0); + + const closeAll = () => { setEditId(null); setCreating(false); }; + const afterWrite = () => { closeAll(); setReload((k) => k + 1); }; + + // Close overlays on Escape — the kind of detail a real app needs. + React.useEffect(() => { + const onKey = (e) => { if (e.key === 'Escape') closeAll(); }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, []); + + return ( +
+
+
+

Task Desk

+

Click a task to edit in a drawer; create one in a modal — both wrap the real <ObjectForm>.

+
+ +
+ +
+ setEditId(r.id)} /> +
+ + {/* Drawer — edit an existing task */} + {editId ? ( +
+
+
+
+

Edit task

+ +
+
+ +
+
+
+ ) : null} + + {/* Modal — create a new task */} + {creating ? ( +
+
+
+
+

New task

+ +
+ +
+
+ ) : null} +
+ ); +}`, +});