Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/app-showcase/objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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],
Expand Down
1 change: 1 addition & 0 deletions examples/app-showcase/src/apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
1 change: 1 addition & 0 deletions examples/app-showcase/src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
85 changes: 85 additions & 0 deletions examples/app-showcase/src/pages/task-desk.page.ts
Original file line number Diff line number Diff line change
@@ -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
* `<ObjectForm>` — 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 (
<div className="mx-auto max-w-5xl space-y-5 p-8">
<header className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight text-slate-900">Task Desk</h1>
<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>
</div>
<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>
</header>

<div className="rounded-xl border border-slate-200 bg-white p-2">
<ListView key={reload} objectName="showcase_task"
fields={['title', 'assignee', 'status', 'priority']}
navigation={{ mode: 'none' }} onRowClick={(r) => setEditId(r.id)} />
</div>

{/* Drawer — edit an existing task */}
{editId ? (
<div className="fixed inset-0 z-40">
<div className="absolute inset-0 bg-slate-900/30" onClick={closeAll} />
<div className="absolute inset-y-0 right-0 flex w-full max-w-md flex-col bg-white shadow-2xl">
<div className="flex items-center justify-between border-b border-slate-200 px-5 py-4">
<h2 className="text-base font-semibold text-slate-900">Edit task</h2>
<button onClick={closeAll} className="rounded-md p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600" aria-label="Close">✕</button>
</div>
<div className="flex-1 overflow-y-auto p-5">
<ObjectForm key={'edit:' + editId + ':' + reload} objectName="showcase_task" mode="edit"
recordId={editId} onSuccess={afterWrite} onCancel={closeAll} />
</div>
</div>
</div>
) : null}

{/* Modal — create a new task */}
{creating ? (
<div className="fixed inset-0 z-40 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-slate-900/40" onClick={closeAll} />
<div className="relative w-full max-w-lg rounded-2xl bg-white p-6 shadow-2xl">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-slate-900">New task</h2>
<button onClick={closeAll} className="rounded-md p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600" aria-label="Close">✕</button>
</div>
<ObjectForm key={'new:' + reload} objectName="showcase_task" mode="create"
onSuccess={afterWrite} onCancel={closeAll} />
</div>
</div>
) : null}
</div>
);
}`,
});