Skip to content

Commit fbe8658

Browse files
committed
feat(examples): Task Desk — kind:'react' page with drawer + modal popup edit
Adds the popup-edit patterns the in-page master/detail scenarios were missing: a slide-out drawer to edit a task and a centered modal to create one, both author-built React overlays (fixed positioning, backdrop, Esc-to-close) wrapping the real ObjectForm. Browser-verified. 29 pages.
1 parent f02ea5b commit fbe8658

4 files changed

Lines changed: 89 additions & 2 deletions

File tree

examples/app-showcase/objectstack.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ChartGalleryDashboard, OpsDashboard } from './src/dashboards/index.js';
2222
import { ShowcaseTaskDataset, ShowcaseProjectDataset } from './src/datasets/index.js';
2323
import { allReports } from './src/reports/index.js';
2424
import { allActions } from './src/actions/index.js';
25-
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';
25+
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';
2626
import { allFlows } from './src/flows/index.js';
2727
import { allWebhooks } from './src/webhooks/index.js';
2828
import { allHooks } from './src/hooks/index.js';
@@ -154,7 +154,7 @@ export default defineStack({
154154
apps: [ShowcaseApp],
155155
portals: allPortals,
156156
views: [TaskViews, ProjectViews, InquiryViews, BusinessUnitViews],
157-
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],
157+
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],
158158
dashboards: [ChartGalleryDashboard, OpsDashboard],
159159
books: allBooks,
160160
datasets: [ShowcaseTaskDataset, ShowcaseProjectDataset],

examples/app-showcase/src/apps/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const ShowcaseApp = App.create({
7070
{ id: 'nav_inquiry_triage', type: 'page', pageName: 'showcase_inquiry_triage', label: 'Inquiry Triage (React)', icon: 'inbox' },
7171
{ id: 'nav_account_cockpit', type: 'page', pageName: 'showcase_account_cockpit', label: 'Account Cockpit (React)', icon: 'satellite' },
7272
{ id: 'nav_invoice_console', type: 'page', pageName: 'showcase_invoice_console', label: 'Invoice Console (React)', icon: 'receipt' },
73+
{ id: 'nav_task_desk', type: 'page', pageName: 'showcase_task_desk', label: 'Task Desk (React)', icon: 'panel-right-open' },
7374
{ id: 'nav_styling_gallery', type: 'page', pageName: 'showcase_styling_gallery', label: 'Styling (ADR-0065)', icon: 'palette' },
7475
{ id: 'nav_page_variables', type: 'page', pageName: 'showcase_page_variables', label: 'Page Variables', icon: 'mouse-pointer-click' },
7576
{ id: 'nav_contact_form', type: 'page', pageName: 'showcase_contact_form', label: 'Contact Form', icon: 'mail-plus' },

examples/app-showcase/src/pages/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export { CrmWorkbenchPage } from './crm-workbench.page.js';
2020
export { InquiryTriagePage } from './inquiry-triage.page.js';
2121
export { AccountCockpitPage } from './account-cockpit.page.js';
2222
export { InvoiceConsolePage } from './invoice-console.page.js';
23+
export { TaskDeskPage } from './task-desk.page.js';
2324
export { PageVariablesPage } from './page-variables.page.js';
2425
export { ContactFormPage } from './contact-form.page.js';
2526
export {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { definePage } from '@objectstack/spec/ui';
4+
5+
/**
6+
* Task Desk — a `kind:'react'` business scenario (ADR-0081) showing the
7+
* popup-edit patterns real apps need: a **slide-out drawer** to edit a row, and
8+
* a **centered modal** to create. Both are author-built React overlays (fixed
9+
* positioning + backdrop + Esc/close state) that wrap the platform's real
10+
* `<ObjectForm>` — demonstrating that the react tier composes drawer/modal
11+
* interactions the in-page master/detail layout can't.
12+
*/
13+
export const TaskDeskPage = definePage({
14+
name: 'showcase_task_desk',
15+
label: 'Task Desk (React)',
16+
type: 'home',
17+
kind: 'react',
18+
source: `
19+
function Page() {
20+
const adapter = useAdapter();
21+
const [editId, setEditId] = React.useState(null); // drawer (edit existing)
22+
const [creating, setCreating] = React.useState(false); // modal (create new)
23+
const [reload, setReload] = React.useState(0);
24+
25+
const closeAll = () => { setEditId(null); setCreating(false); };
26+
const afterWrite = () => { closeAll(); setReload((k) => k + 1); };
27+
28+
// Close overlays on Escape — the kind of detail a real app needs.
29+
React.useEffect(() => {
30+
const onKey = (e) => { if (e.key === 'Escape') closeAll(); };
31+
window.addEventListener('keydown', onKey);
32+
return () => window.removeEventListener('keydown', onKey);
33+
}, []);
34+
35+
return (
36+
<div className="mx-auto max-w-5xl space-y-5 p-8">
37+
<header className="flex items-center justify-between">
38+
<div>
39+
<h1 className="text-2xl font-bold tracking-tight text-slate-900">Task Desk</h1>
40+
<p className="mt-1 text-sm text-slate-500">Click a task to edit in a <strong>drawer</strong>; create one in a <strong>modal</strong> — both wrap the real <code>&lt;ObjectForm&gt;</code>.</p>
41+
</div>
42+
<button onClick={() => setCreating(true)} className="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500">+ New task</button>
43+
</header>
44+
45+
<div className="rounded-xl border border-slate-200 bg-white p-2">
46+
<ListView key={reload} objectName="showcase_task"
47+
fields={['title', 'assignee', 'status', 'priority']}
48+
navigation={{ mode: 'none' }} onRowClick={(r) => setEditId(r.id)} />
49+
</div>
50+
51+
{/* Drawer — edit an existing task */}
52+
{editId ? (
53+
<div className="fixed inset-0 z-40">
54+
<div className="absolute inset-0 bg-slate-900/30" onClick={closeAll} />
55+
<div className="absolute inset-y-0 right-0 flex w-full max-w-md flex-col bg-white shadow-2xl">
56+
<div className="flex items-center justify-between border-b border-slate-200 px-5 py-4">
57+
<h2 className="text-base font-semibold text-slate-900">Edit task</h2>
58+
<button onClick={closeAll} className="rounded-md p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600" aria-label="Close">✕</button>
59+
</div>
60+
<div className="flex-1 overflow-y-auto p-5">
61+
<ObjectForm key={'edit:' + editId + ':' + reload} objectName="showcase_task" mode="edit"
62+
recordId={editId} onSuccess={afterWrite} onCancel={closeAll} />
63+
</div>
64+
</div>
65+
</div>
66+
) : null}
67+
68+
{/* Modal — create a new task */}
69+
{creating ? (
70+
<div className="fixed inset-0 z-40 flex items-center justify-center p-4">
71+
<div className="absolute inset-0 bg-slate-900/40" onClick={closeAll} />
72+
<div className="relative w-full max-w-lg rounded-2xl bg-white p-6 shadow-2xl">
73+
<div className="mb-4 flex items-center justify-between">
74+
<h2 className="text-lg font-semibold text-slate-900">New task</h2>
75+
<button onClick={closeAll} className="rounded-md p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600" aria-label="Close">✕</button>
76+
</div>
77+
<ObjectForm key={'new:' + reload} objectName="showcase_task" mode="create"
78+
onSuccess={afterWrite} onCancel={closeAll} />
79+
</div>
80+
</div>
81+
) : null}
82+
</div>
83+
);
84+
}`,
85+
});

0 commit comments

Comments
 (0)