diff --git a/examples/app-crm/README.md b/examples/app-crm/README.md index 54aff57c28..681fe79484 100644 --- a/examples/app-crm/README.md +++ b/examples/app-crm/README.md @@ -50,6 +50,8 @@ Follows the **by-type** directory layout — the ObjectStack standard aligned wi ``` src/ ├── objects/ # 📦 10 Core Objects (account, contact, lead, opportunity, ...) +├── views/ # 👁️ UI Views (FormView & ListView configurations for all objects) +├── pages/ # 📄 Pages (Record, Home, App, Utility page layouts) ├── actions/ # ⚡ Custom Actions (lead, contact, opportunity, case, global) ├── apis/ # 🌐 REST Endpoints (pipeline-stats, lead-convert) ├── apps/ # 🚀 App Configuration (crm.app.ts) @@ -71,6 +73,24 @@ Comprehensive guides covering all aspects: 3. **[Security](./docs/05-security.md)** - Profiles, roles, sharing, permissions 4. **[AI Capabilities](./docs/08-ai-capabilities.md)** - Agents, RAG, NLQ, ML +### 🎨 UI Examples + +The CRM includes comprehensive UI examples demonstrating all form layouts and page types: + +#### FormView Examples (`src/views/`) +- **6 Form Layout Types**: Simple, Tabbed, Wizard, Split, Drawer, Modal +- **Advanced Features**: Collapsible sections, 1-4 column layouts, conditional visibility, cascading dependencies +- **5 List View Types**: Grid, Kanban, Calendar, Gallery, filtered views +- **Field Controls**: readonly, required, hidden, colSpan, visibleOn, dependsOn, custom widgets + +#### PageSchema Examples (`src/pages/`) +- **Record Page**: Comprehensive detail page with highlights, tabs, accordion, related lists, AI chat +- **Home Page**: Dashboard-style with KPIs and quick actions +- **App Page**: Application launcher grid +- **Utility Page**: Floating utility panels + +See `src/views/lead.view.ts` and `src/pages/` for complete implementations. + ### 🚀 Quick Start ```bash @@ -96,6 +116,8 @@ pnpm --filter @example/app-crm dev |----------|-------|----------| | **Objects** | 10 | Account, Opportunity, Case, Product | | **Fields** | 100+ | AutoNumber, Formula, Lookup, Address | +| **Views** | 12+ | Grid, Kanban, Calendar, Gallery, Forms (Simple, Tabbed, Wizard, Split, Drawer, Modal) | +| **Pages** | 4 | Record Detail, Home, App Launcher, Utility Bar | | **Profiles** | 5 | Admin, Sales Manager, Sales Rep | | **Sharing Rules** | 5+ | Criteria-based, Territory-based | | **AI Agents** | 5 | Sales Assistant, Service Agent | diff --git a/examples/app-crm/src/pages/app_launcher.page.ts b/examples/app-crm/src/pages/app_launcher.page.ts new file mode 100644 index 0000000000..32f4a48189 --- /dev/null +++ b/examples/app-crm/src/pages/app_launcher.page.ts @@ -0,0 +1,70 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { Page } from '@objectstack/spec/ui'; + +/** + * App Launcher Page + * + * Demonstrates an application launcher page similar to Salesforce App Launcher. + * + * Features: + * - Grid-based app icon layout + * - Global search + * - Quick access to all apps + */ +export const AppLauncherPage: Page = { + name: 'app_launcher_page', + label: 'App Launcher', + description: 'Central hub for accessing all applications', + + type: 'app', + + template: 'centered', + + regions: [ + { + name: 'header', + width: 'full', + components: [ + { + type: 'global:search', + id: 'app_search', + label: 'Search Apps', + properties: {}, + }, + ], + }, + + { + name: 'main', + width: 'large', + components: [ + { + type: 'page:header', + id: 'launcher_header', + label: 'App Launcher Header', + properties: { + title: 'App Launcher', + subtitle: 'Select an app to get started', + icon: 'th', + breadcrumb: false, + }, + }, + { + type: 'app:launcher', + id: 'app_grid', + label: 'Application Grid', + properties: {}, + }, + ], + }, + ], + + isDefault: false, + assignedProfiles: ['sales_user', 'sales_manager', 'service_user', 'system_administrator'], + + aria: { + label: 'App Launcher Page', + description: 'Central application launcher for accessing all apps', + }, +}; diff --git a/examples/app-crm/src/pages/home.page.ts b/examples/app-crm/src/pages/home.page.ts new file mode 100644 index 0000000000..a17534d73d --- /dev/null +++ b/examples/app-crm/src/pages/home.page.ts @@ -0,0 +1,189 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { Page } from '@objectstack/spec/ui'; + +/** + * Sales Home Page + * + * Demonstrates a home page layout with dashboards and quick access widgets. + * Similar to Salesforce Lightning Home Page. + * + * Features: + * - Dashboard-style layout + * - Multiple component regions + * - Global search and notifications + * - Quick action cards + */ +export const SalesHomePage: Page = { + name: 'sales_home_page', + label: 'Sales Home', + description: 'Sales team home page with key metrics and quick actions', + + type: 'home', + + template: 'three-column', + + variables: [ + { + name: 'selectedPeriod', + type: 'string', + defaultValue: 'this_month', + }, + ], + + regions: [ + { + name: 'header', + width: 'full', + components: [ + { + type: 'page:header', + id: 'home_header', + label: 'Sales Home Header', + properties: { + title: 'Sales Dashboard', + subtitle: 'Welcome back, {current_user.first_name}', + icon: 'home', + breadcrumb: false, + }, + }, + ], + }, + + { + name: 'left_sidebar', + width: 'small', + components: [ + { + type: 'page:card', + id: 'quick_create', + label: 'Quick Create', + properties: { + title: 'Quick Create', + bordered: true, + body: [ + { + type: 'nav:menu', + id: 'create_menu', + properties: {}, + }, + ], + }, + }, + { + type: 'page:card', + id: 'my_recent_items', + label: 'Recent Items', + properties: { + title: 'Recent Items', + bordered: true, + }, + }, + ], + }, + + { + name: 'main', + width: 'large', + components: [ + { + type: 'page:card', + id: 'key_metrics', + label: 'Key Metrics', + properties: { + title: 'Key Performance Indicators', + bordered: false, + body: [ + { + type: 'record:highlights', + id: 'kpi_highlights', + properties: { + fields: ['total_revenue', 'deals_won', 'pipeline_value', 'conversion_rate'], + layout: 'horizontal', + }, + }, + ], + }, + }, + { + type: 'page:tabs', + id: 'home_tabs', + label: 'Home Tabs', + properties: { + type: 'card', + position: 'top', + items: [ + { + label: 'My Leads', + icon: 'user-plus', + children: [ + { + type: 'page:section', + properties: {}, + }, + ], + }, + { + label: 'My Opportunities', + icon: 'dollar-sign', + children: [ + { + type: 'page:section', + properties: {}, + }, + ], + }, + { + label: 'My Tasks', + icon: 'tasks', + children: [ + { + type: 'page:section', + properties: {}, + }, + ], + }, + ], + }, + }, + ], + }, + + { + name: 'right_sidebar', + width: 'medium', + components: [ + { + type: 'ai:chat_window', + id: 'sales_assistant', + label: 'Sales Assistant', + properties: { + mode: 'sidebar', + agentId: 'sales_assistant', + context: { + page: 'home', + user: '{current_user.id}', + }, + }, + }, + { + type: 'page:card', + id: 'upcoming_events', + label: 'Calendar', + properties: { + title: "Today's Schedule", + bordered: true, + }, + }, + ], + }, + ], + + isDefault: true, + assignedProfiles: ['sales_user', 'sales_manager'], + + aria: { + label: 'Sales Home Page', + description: 'Sales team home page with metrics, leads, and quick actions', + }, +}; diff --git a/examples/app-crm/src/pages/index.ts b/examples/app-crm/src/pages/index.ts new file mode 100644 index 0000000000..c4e32ced5e --- /dev/null +++ b/examples/app-crm/src/pages/index.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +export { LeadDetailPage } from './lead_detail.page'; +export { SalesHomePage } from './home.page'; +export { AppLauncherPage } from './app_launcher.page'; +export { UtilityBarPage } from './utility_bar.page'; diff --git a/examples/app-crm/src/pages/lead_detail.page.ts b/examples/app-crm/src/pages/lead_detail.page.ts new file mode 100644 index 0000000000..e75e5c8366 --- /dev/null +++ b/examples/app-crm/src/pages/lead_detail.page.ts @@ -0,0 +1,285 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { Page } from '@objectstack/spec/ui'; + +/** + * Lead Detail Record Page + * + * Demonstrates a comprehensive record page layout similar to Salesforce Lightning Record Page. + * + * Features: + * - Template-based layout with named regions + * - Rich component composition (details, highlights, related lists) + * - Component visibility rules + * - Profile-based page assignment + */ +export const LeadDetailPage: Page = { + name: 'lead_detail_page', + label: 'Lead Detail', + description: 'Comprehensive lead detail page with highlights, details, and related information', + + type: 'record', + object: 'lead', + + // Template defines the overall layout structure + template: 'header-sidebar-main', + + // Page-level state variables + variables: [ + { + name: 'showHistory', + type: 'boolean', + defaultValue: false, + }, + { + name: 'activeTab', + type: 'string', + defaultValue: 'details', + }, + ], + + // Regions correspond to slots in the template + regions: [ + { + name: 'header', + width: 'full', + components: [ + { + type: 'page:header', + id: 'lead_header', + label: 'Lead Information', + properties: { + title: '{first_name} {last_name}', + subtitle: '{company}', + icon: 'user-plus', + breadcrumb: true, + actions: ['edit', 'delete', 'convert_lead', 'share'], + }, + }, + { + type: 'record:path', + id: 'lead_path', + label: 'Lead Status Path', + properties: { + statusField: 'status', + stages: [ + { value: 'new', label: 'New' }, + { value: 'contacted', label: 'Contacted' }, + { value: 'qualified', label: 'Qualified' }, + { value: 'unqualified', label: 'Unqualified' }, + ], + }, + }, + ], + }, + + { + name: 'sidebar', + width: 'medium', + components: [ + { + type: 'record:highlights', + id: 'lead_highlights', + label: 'Key Information', + properties: { + fields: ['status', 'rating', 'lead_source', 'owner', 'email', 'phone'], + layout: 'vertical', + }, + }, + { + type: 'page:card', + id: 'quick_actions_card', + label: 'Quick Actions', + properties: { + title: 'Quick Actions', + bordered: true, + actions: ['send_email', 'log_call', 'create_task', 'schedule_meeting'], + }, + }, + { + type: 'ai:chat_window', + id: 'ai_assistant', + label: 'AI Assistant', + properties: { + mode: 'sidebar', + agentId: 'sales_assistant', + context: { + recordType: 'lead', + recordId: '{record.id}', + }, + }, + // Only show AI assistant for qualified leads + visibility: 'status == "qualified" OR status == "contacted"', + }, + ], + }, + + { + name: 'main', + width: 'large', + components: [ + { + type: 'page:tabs', + id: 'main_tabs', + label: 'Lead Information Tabs', + properties: { + type: 'line', + position: 'top', + items: [ + { + label: 'Details', + icon: 'info-circle', + children: [ + { + type: 'record:details', + id: 'lead_details', + label: 'Lead Details', + properties: { + columns: '2', + layout: 'auto', + }, + }, + ], + }, + { + label: 'Related', + icon: 'link', + children: [ + { + type: 'page:accordion', + id: 'related_accordion', + label: 'Related Records', + properties: { + allowMultiple: true, + items: [ + { + label: 'Tasks', + icon: 'tasks', + collapsed: false, + children: [ + { + type: 'record:related_list', + id: 'related_tasks', + label: 'Tasks', + properties: { + objectName: 'task', + relationshipField: 'lead_id', + columns: ['subject', 'status', 'priority', 'due_date', 'assigned_to'], + sort: [ + { field: 'due_date', order: 'asc' } + ], + limit: 10, + title: 'Open Tasks', + filter: [['status', '!=', 'completed']], + showViewAll: true, + actions: ['new_task', 'edit', 'complete'], + }, + }, + ], + }, + { + label: 'Events', + icon: 'calendar', + collapsed: true, + children: [ + { + type: 'record:related_list', + id: 'related_events', + label: 'Events', + properties: { + objectName: 'event', + relationshipField: 'lead_id', + columns: ['subject', 'start_date', 'end_date', 'location'], + sort: [ + { field: 'start_date', order: 'desc' } + ], + limit: 5, + showViewAll: true, + actions: ['new_event'], + }, + }, + ], + }, + { + label: 'Notes & Attachments', + icon: 'paperclip', + collapsed: true, + children: [ + { + type: 'record:related_list', + id: 'related_files', + label: 'Files', + properties: { + objectName: 'file', + relationshipField: 'parent_id', + columns: ['title', 'file_type', 'size', 'created_by', 'created_date'], + sort: [ + { field: 'created_date', order: 'desc' } + ], + limit: 5, + showViewAll: true, + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + label: 'Activity', + icon: 'clock', + children: [ + { + type: 'record:activity', + id: 'lead_activity', + label: 'Activity Timeline', + properties: { + types: ['task', 'event', 'email', 'call', 'note'], + limit: 20, + showCompleted: false, + }, + }, + ], + }, + { + label: 'History', + icon: 'history', + children: [ + { + type: 'record:related_list', + id: 'field_history', + label: 'Field History', + properties: { + objectName: 'field_history', + relationshipField: 'record_id', + columns: ['field', 'old_value', 'new_value', 'changed_by', 'changed_date'], + sort: [ + { field: 'changed_date', order: 'desc' } + ], + limit: 25, + showViewAll: true, + }, + }, + ], + }, + ], + }, + }, + ], + }, + ], + + // Make this the default page for leads + isDefault: true, + + // Assign to specific profiles + assignedProfiles: ['sales_user', 'sales_manager', 'system_administrator'], + + // ARIA accessibility + aria: { + label: 'Lead Detail Page', + description: 'Detailed view of lead information with related records and activity', + }, +}; diff --git a/examples/app-crm/src/pages/utility_bar.page.ts b/examples/app-crm/src/pages/utility_bar.page.ts new file mode 100644 index 0000000000..d4ae710a6c --- /dev/null +++ b/examples/app-crm/src/pages/utility_bar.page.ts @@ -0,0 +1,72 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { Page } from '@objectstack/spec/ui'; + +/** + * Utility Bar Page + * + * Demonstrates a utility bar page for floating panels and quick access tools. + * Similar to Salesforce Lightning Utility Bar. + * + * Features: + * - Floating utility panels + * - Quick notes + * - Chat/messaging + * - Notifications + */ +export const UtilityBarPage: Page = { + name: 'utility_bar_page', + label: 'Utility Bar', + description: 'Quick access utility bar with floating tools', + + type: 'utility', + + template: 'utility-bar', + + regions: [ + { + name: 'utilities', + width: 'full', + components: [ + { + type: 'global:notifications', + id: 'notifications_panel', + label: 'Notifications', + properties: {}, + }, + { + type: 'ai:chat_window', + id: 'quick_chat', + label: 'Quick Chat', + properties: { + mode: 'float', + agentId: 'general_assistant', + }, + }, + { + type: 'page:card', + id: 'quick_notes', + label: 'Quick Notes', + properties: { + title: 'Quick Notes', + bordered: true, + }, + }, + { + type: 'global:search', + id: 'quick_search', + label: 'Quick Search', + properties: {}, + }, + ], + }, + ], + + isDefault: false, + assignedProfiles: ['sales_user', 'sales_manager', 'service_user', 'system_administrator'], + + aria: { + label: 'Utility Bar', + description: 'Quick access utility bar with floating tools and notifications', + }, +}; diff --git a/examples/app-crm/src/views/index.ts b/examples/app-crm/src/views/index.ts new file mode 100644 index 0000000000..87d63f6efb --- /dev/null +++ b/examples/app-crm/src/views/index.ts @@ -0,0 +1,3 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +export { LeadViews } from './lead.view'; diff --git a/examples/app-crm/src/views/lead.view.ts b/examples/app-crm/src/views/lead.view.ts new file mode 100644 index 0000000000..83a91f568e --- /dev/null +++ b/examples/app-crm/src/views/lead.view.ts @@ -0,0 +1,702 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { defineView } from '@objectstack/spec/ui'; + +/** + * Lead Views - Comprehensive UI Showcase + * + * This file demonstrates: + * 1. All 6 FormView layout types (simple, tabbed, wizard, split, drawer, modal) + * 2. Section features (collapsible, 1-4 column layouts) + * 3. Field-level controls (readonly, required, hidden, colSpan, visibleOn, dependsOn, custom widget) + * 4. Multiple named formViews for different scenarios + * 5. Various list view types + */ +export const LeadViews = defineView({ + /** + * Default List View - Grid with Advanced Features + */ + list: { + type: 'grid', + name: 'all_leads', + label: 'All Leads', + data: { + provider: 'object', + object: 'lead', + }, + + // Column Configuration with Enhanced Features + columns: [ + { + field: 'first_name', + label: 'First Name', + width: 150, + sortable: true, + link: true, // Primary navigation link + }, + { + field: 'last_name', + label: 'Last Name', + width: 150, + sortable: true, + }, + { + field: 'company', + label: 'Company', + width: 200, + sortable: true, + }, + { + field: 'email', + label: 'Email', + width: 200, + }, + { + field: 'status', + label: 'Status', + width: 120, + sortable: true, + }, + { + field: 'rating', + label: 'Score', + width: 100, + align: 'center', + }, + { + field: 'lead_source', + label: 'Source', + width: 120, + }, + { + field: 'owner', + label: 'Owner', + width: 150, + }, + ], + + sort: [ + { field: 'created_at', order: 'desc' } + ], + + // Quick Filters (Salesforce-style) + quickFilters: [ + { + field: 'status', + label: 'New', + operator: 'equals', + value: 'new', + }, + { + field: 'status', + label: 'Contacted', + operator: 'equals', + value: 'contacted', + }, + { + field: 'owner', + label: 'My Leads', + operator: 'equals', + value: '{current_user_id}', + }, + ], + + // Navigation to Form + navigation: { + mode: 'page', + view: 'detail_form', // Use named form view + }, + + // List Actions + rowActions: ['edit', 'delete', 'convert_lead'], + bulkActions: ['mass_update', 'mass_delete', 'assign_owner'], + + // Features + selection: { type: 'multiple' }, + pagination: { pageSize: 25, pageSizeOptions: [10, 25, 50, 100] }, + rowHeight: 'medium', + inlineEdit: true, + exportOptions: ['csv', 'xlsx'], + + // Empty State + emptyState: { + title: 'No Leads Yet', + message: 'Get started by creating your first lead', + icon: 'user-plus', + }, + }, + + /** + * Default Form View - SIMPLE Layout + * Basic sectioned form with collapsible sections and column layouts + */ + form: { + type: 'simple', + data: { + provider: 'object', + object: 'lead', + }, + + sections: [ + { + label: 'Contact Information', + collapsible: true, + collapsed: false, + columns: 2, // 2-column layout + fields: [ + { + field: 'salutation', + colSpan: 1, + }, + { + field: 'first_name', + required: true, + colSpan: 1, + }, + { + field: 'last_name', + required: true, + colSpan: 2, // Span both columns + }, + 'company', + 'title', + 'email', + 'phone', + 'mobile', + 'website', + ], + }, + { + label: 'Lead Classification', + collapsible: true, + collapsed: false, + columns: 3, // 3-column layout + fields: [ + { + field: 'status', + required: true, + }, + { + field: 'rating', + widget: 'star_rating', // Custom widget + }, + 'lead_source', + 'industry', + { + field: 'owner', + required: true, + }, + ], + }, + { + label: 'Company Information', + collapsible: true, + collapsed: true, // Collapsed by default + columns: 2, + fields: [ + 'annual_revenue', + 'number_of_employees', + ], + }, + { + label: 'Address', + collapsible: true, + collapsed: true, + columns: 2, + fields: [ + { + field: 'street', + colSpan: 2, // Full width + }, + 'city', + 'state', + 'postal_code', + 'country', + ], + }, + { + label: 'Additional Information', + collapsible: true, + collapsed: true, + columns: 1, // Single column for text areas + fields: [ + 'description', + 'notes', + ], + }, + { + label: 'Privacy', + collapsible: true, + collapsed: true, + columns: 2, + fields: [ + 'do_not_call', + 'email_opt_out', + ], + }, + ], + }, + + /** + * Additional Named List Views + */ + listViews: { + /** + * Kanban Board View + */ + kanban_by_status: { + name: 'kanban_by_status', + type: 'kanban', + label: 'Lead Pipeline', + data: { + provider: 'object', + object: 'lead', + }, + columns: ['first_name', 'last_name', 'company', 'email'], + kanban: { + groupByField: 'status', + summarizeField: 'annual_revenue', + columns: ['first_name', 'last_name', 'company', 'rating'], + }, + navigation: { + mode: 'drawer', // Open in drawer instead of new page + width: '600px', + }, + }, + + /** + * Calendar View + */ + calendar_by_created: { + name: 'calendar_by_created', + type: 'calendar', + label: 'Lead Calendar', + data: { + provider: 'object', + object: 'lead', + }, + columns: ['first_name', 'last_name', 'company'], + calendar: { + startDateField: 'created_at', + titleField: 'company', + colorField: 'status', + }, + }, + + /** + * Gallery/Card View + */ + gallery_view: { + name: 'gallery_view', + type: 'gallery', + label: 'Lead Cards', + data: { + provider: 'object', + object: 'lead', + }, + columns: ['first_name', 'last_name', 'company', 'email', 'status'], + gallery: { + cardSize: 'medium', + titleField: 'company', + visibleFields: ['first_name', 'last_name', 'email', 'phone', 'status', 'rating'], + }, + }, + + /** + * My Leads - Filtered View + */ + my_leads: { + name: 'my_leads', + type: 'grid', + label: 'My Leads', + data: { + provider: 'object', + object: 'lead', + }, + columns: ['first_name', 'last_name', 'company', 'email', 'status', 'rating'], + filter: [ + ['owner', '=', '{current_user_id}'] + ], + sort: [ + { field: 'rating', order: 'desc' }, + { field: 'created_at', order: 'desc' } + ], + }, + + /** + * High Priority Leads + */ + high_priority: { + name: 'high_priority', + type: 'grid', + label: 'High Priority', + data: { + provider: 'object', + object: 'lead', + }, + columns: ['first_name', 'last_name', 'company', 'email', 'status', 'rating', 'lead_source'], + filter: [ + ['rating', '>=', 4], + ['status', 'in', ['new', 'contacted']] + ], + rowColor: { + field: 'rating', + colors: { + '5': '#00AA00', + '4': '#FFA500', + }, + }, + }, + }, + + /** + * Additional Named Form Views - Demonstrating All 6 Layout Types + */ + formViews: { + /** + * 1. SIMPLE Layout (already shown as default form above) + * Basic sectioned form + */ + quick_create: { + type: 'simple', + data: { + provider: 'object', + object: 'lead', + }, + sections: [ + { + label: 'Quick Lead Creation', + columns: 2, + fields: [ + { field: 'first_name', required: true }, + { field: 'last_name', required: true }, + { field: 'company', required: true, colSpan: 2 }, + { field: 'email', required: true }, + 'phone', + 'status', + 'owner', + ], + }, + ], + }, + + /** + * 2. TABBED Layout + * Organize complex forms with tabs + */ + detail_form: { + type: 'tabbed', + data: { + provider: 'object', + object: 'lead', + }, + sections: [ + { + label: 'General', + columns: 2, + fields: [ + 'salutation', + 'first_name', + 'last_name', + 'company', + 'title', + 'email', + 'phone', + 'mobile', + ], + }, + { + label: 'Qualification', + columns: 2, + fields: [ + { field: 'status', required: true }, + { field: 'rating', widget: 'star_rating' }, + 'lead_source', + 'industry', + 'annual_revenue', + 'number_of_employees', + ], + }, + { + label: 'Address', + columns: 2, + fields: [ + { field: 'street', colSpan: 2 }, + 'city', + { + field: 'state', + dependsOn: 'country', // Cascade: country determines available states + }, + 'postal_code', + 'country', + ], + }, + { + label: 'Details', + columns: 1, + fields: [ + 'description', + 'notes', + 'do_not_call', + 'email_opt_out', + ], + }, + ], + }, + + /** + * 3. WIZARD Layout + * Step-by-step guided process + */ + lead_conversion_wizard: { + type: 'wizard', + data: { + provider: 'object', + object: 'lead', + }, + sections: [ + { + label: 'Step 1: Contact Details', + columns: 2, + fields: [ + { field: 'first_name', required: true, readonly: true }, + { field: 'last_name', required: true, readonly: true }, + { field: 'email', readonly: true, colSpan: 2 }, + 'phone', + 'mobile', + ], + }, + { + label: 'Step 2: Company Information', + columns: 2, + fields: [ + { field: 'company', required: true, readonly: true }, + 'title', + 'industry', + 'annual_revenue', + 'number_of_employees', + 'website', + ], + }, + { + label: 'Step 3: Qualification', + columns: 2, + fields: [ + { field: 'status', required: true }, + { field: 'rating', widget: 'star_rating' }, + 'lead_source', + { + field: 'owner', + visibleOn: 'rating >= 4', // Conditional visibility + }, + ], + }, + { + label: 'Step 4: Review & Convert', + columns: 1, + fields: [ + { + field: 'description', + helpText: 'Review all information before converting to Account and Contact', + }, + ], + }, + ], + }, + + /** + * 4. SPLIT Layout + * Master-detail split view + */ + split_edit: { + type: 'split', + data: { + provider: 'object', + object: 'lead', + }, + sections: [ + { + label: 'Primary Information', + columns: 1, + fields: [ + 'first_name', + 'last_name', + 'company', + 'email', + { field: 'status', required: true }, + 'owner', + ], + }, + { + label: 'Extended Details', + columns: 2, + fields: [ + 'phone', + 'mobile', + 'title', + 'industry', + 'lead_source', + { field: 'rating', widget: 'star_rating' }, + 'annual_revenue', + 'number_of_employees', + ], + }, + ], + }, + + /** + * 5. DRAWER Layout + * Side panel form (typically opened from list view) + */ + quick_edit_drawer: { + type: 'drawer', + data: { + provider: 'object', + object: 'lead', + }, + sections: [ + { + label: 'Quick Edit', + columns: 1, // Drawers typically use single column + fields: [ + { field: 'first_name', required: true }, + { field: 'last_name', required: true }, + 'company', + 'email', + 'phone', + { field: 'status', required: true }, + { field: 'rating', widget: 'star_rating' }, + 'lead_source', + { + field: 'owner', + visibleOn: 'status != "new"', // Only show owner after initial contact + }, + ], + }, + ], + }, + + /** + * 6. MODAL Layout + * Dialog-based form for quick actions + */ + status_update_modal: { + type: 'modal', + data: { + provider: 'object', + object: 'lead', + }, + sections: [ + { + label: 'Update Lead Status', + columns: 1, + fields: [ + { field: 'first_name', readonly: true }, + { field: 'last_name', readonly: true }, + { field: 'company', readonly: true }, + { + field: 'status', + required: true, + helpText: 'Select the new status for this lead', + }, + { + field: 'rating', + widget: 'star_rating', + visibleOn: 'status == "qualified"', // Only show rating for qualified leads + }, + { + field: 'notes', + placeholder: 'Add notes about this status change', + visibleOn: 'status == "unqualified"', // Require notes for unqualified + }, + ], + }, + ], + }, + + /** + * Advanced Example: Conditional Field Visibility & Dependencies + */ + advanced_conditional: { + type: 'simple', + data: { + provider: 'object', + object: 'lead', + }, + sections: [ + { + label: 'Lead Information', + columns: 2, + fields: [ + 'first_name', + 'last_name', + 'company', + 'email', + 'status', + 'lead_source', + { + field: 'rating', + widget: 'star_rating', + visibleOn: 'status != "new"', // Only show after first contact + }, + { + field: 'industry', + dependsOn: 'company', // Industry options depend on company + }, + { + field: 'annual_revenue', + visibleOn: 'rating >= 3', // Only for qualified leads + }, + { + field: 'number_of_employees', + visibleOn: 'rating >= 3', + }, + { + field: 'owner', + required: true, + visibleOn: 'status == "contacted" OR status == "qualified"', + }, + { + field: 'notes', + colSpan: 2, + required: true, + visibleOn: 'status == "unqualified"', // Require explanation for unqualified + }, + ], + }, + { + label: 'Address Information', + collapsible: true, + collapsed: true, + columns: 2, + fields: [ + { field: 'street', colSpan: 2 }, + 'city', + { + field: 'state', + dependsOn: 'country', // Country determines state options + }, + 'postal_code', + 'country', + ], + }, + { + label: 'Privacy Preferences', + collapsible: true, + collapsed: true, + columns: 2, + fields: [ + { + field: 'do_not_call', + helpText: 'Check if this lead has requested not to be called', + }, + { + field: 'email_opt_out', + helpText: 'Check if this lead has opted out of email communications', + }, + ], + }, + ], + }, + }, +}); diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index f4ce932134..d4931a6003 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -55,22 +55,77 @@ export const PageCardProps = z.object({ */ export const RecordDetailsProps = z.object({ - columns: z.enum(['1', '2', '3', '4']).default('2'), - layout: z.enum(['auto', 'custom']).default('auto'), - // If custom layout - sections: z.array(z.string()).optional().describe('Section IDs to show') + columns: z.enum(['1', '2', '3', '4']).default('2').describe('Number of columns for field layout (1-4)'), + layout: z.enum(['auto', 'custom']).default('auto').describe('Layout mode: auto uses object compactLayout, custom uses explicit sections'), + sections: z.array(z.string()).optional().describe('Section IDs to show (required when layout is "custom")'), + fields: z.array(z.string()).optional().describe('Explicit field list to display (optional, overrides compactLayout)'), + /** ARIA accessibility */ + aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), }); export const RecordRelatedListProps = z.object({ - objectName: z.string().describe('Related object name'), - relationshipField: z.string().describe('Field on related object that points to this record'), - columns: z.array(z.string()).describe('Fields to display'), - sort: z.string().optional(), - limit: z.number().default(5) + objectName: z.string().describe('Related object name (e.g., "task", "opportunity")'), + relationshipField: z.string().describe('Field on related object that points to this record (e.g., "account_id")'), + columns: z.array(z.string()).describe('Fields to display in the related list'), + sort: z.union([ + z.string(), + z.array(z.object({ + field: z.string(), + order: z.enum(['asc', 'desc']) + })) + ]).optional().describe('Sort order for related records'), + limit: z.number().int().positive().default(5).describe('Number of records to display initially'), + filter: z.array(z.unknown()).optional().describe('Additional filter criteria for related records'), + title: I18nLabelSchema.optional().describe('Custom title for the related list'), + showViewAll: z.boolean().default(true).describe('Show "View All" link to see all related records'), + actions: z.array(z.string()).optional().describe('Action IDs available for related records'), + /** ARIA accessibility */ + aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), }); export const RecordHighlightsProps = z.object({ - fields: z.array(z.string()).min(1).max(7).describe('Key fields to highlights (max 7)') + fields: z.array(z.string()).min(1).max(7).describe('Key fields to highlight (1-7 fields max, typically displayed as prominent cards)'), + layout: z.enum(['horizontal', 'vertical']).default('horizontal').describe('Layout orientation for highlight fields'), + /** ARIA accessibility */ + aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), +}); + +export const RecordActivityProps = z.object({ + types: z.array(z.enum(['task', 'event', 'email', 'call', 'note'])).optional().describe('Activity types to display'), + limit: z.number().int().positive().default(10).describe('Number of activities to show'), + showCompleted: z.boolean().default(false).describe('Include completed activities'), + /** ARIA accessibility */ + aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), +}); + +export const RecordPathProps = z.object({ + statusField: z.string().describe('Field name representing the current status/stage'), + stages: z.array(z.object({ + value: z.string(), + label: I18nLabelSchema, + })).optional().describe('Explicit stage definitions (if not using field metadata)'), + /** ARIA accessibility */ + aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), +}); + +export const PageAccordionProps = z.object({ + items: z.array(z.object({ + label: I18nLabelSchema, + icon: z.string().optional(), + collapsed: z.boolean().default(false), + children: z.array(z.unknown()).describe('Child components'), + })), + allowMultiple: z.boolean().default(false).describe('Allow multiple panels to be expanded simultaneously'), + /** ARIA accessibility */ + aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), +}); + +export const AIChatWindowProps = z.object({ + mode: z.enum(['float', 'sidebar', 'inline']).default('float').describe('Display mode for the chat window'), + agentId: z.string().optional().describe('Specific AI agent to use'), + context: z.record(z.string(), z.unknown()).optional().describe('Contextual data to pass to the AI'), + /** ARIA accessibility */ + aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), }); /** @@ -86,16 +141,16 @@ export const ComponentPropsMap = { 'page:card': PageCardProps, 'page:footer': EmptyProps, 'page:sidebar': EmptyProps, - 'page:accordion': EmptyProps, + 'page:accordion': PageAccordionProps, 'page:section': EmptyProps, // Record 'record:details': RecordDetailsProps, 'record:related_list': RecordRelatedListProps, 'record:highlights': RecordHighlightsProps, - 'record:activity': EmptyProps, + 'record:activity': RecordActivityProps, 'record:chatter': EmptyProps, - 'record:path': EmptyProps, + 'record:path': RecordPathProps, // Navigation 'app:launcher': EmptyProps, @@ -108,7 +163,7 @@ export const ComponentPropsMap = { 'user:profile': EmptyProps, // AI - 'ai:chat_window': z.object({ mode: z.enum(['float', 'sidebar', 'inline']).default('float') }), + 'ai:chat_window': AIChatWindowProps, 'ai:suggestion': z.object({ context: z.string().optional() }) } as const; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 137b9265d6..9a7c99396c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -337,6 +337,16 @@ importers: specifier: ^5.3.0 version: 5.9.3 + examples/ui-showcase: + dependencies: + '@objectstack/spec': + specifier: workspace:* + version: link:../../packages/spec + devDependencies: + typescript: + specifier: ^5.9.0 + version: 5.9.3 + packages/adapters/express: devDependencies: '@objectstack/runtime':