From 8ae712bad5fdb5a89d54b777293fa039756e2081 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 07:26:12 +0000 Subject: [PATCH 1/4] Initial plan From b67fd23a64b2792e3d329bed28d2ba86f2fff3b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 07:37:40 +0000 Subject: [PATCH 2/4] Add comprehensive UI examples in examples/ui - Created package structure with package.json and tsconfig.json - Added detailed README.md explaining UI protocol examples - Created view.examples.ts: Grid, Kanban, Calendar, Gantt, Form views with various data sources - Created action.examples.ts: Modal, Flow, Script, URL, and Batch actions - Created dashboard.examples.ts: Sales, Service, Executive, Marketing, and Team dashboards - Created page.examples.ts: Record, Home, App, and Utility pages with component composition - Created app.examples.ts: Simple and comprehensive apps with hierarchical navigation - Created theme.examples.ts: Light, dark, colorful, and accessibility themes - All examples build successfully and demonstrate best practices Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/ui/README.md | 312 +++++++++++ examples/ui/package.json | 16 + examples/ui/src/action.examples.ts | 632 +++++++++++++++++++++ examples/ui/src/app.examples.ts | 774 ++++++++++++++++++++++++++ examples/ui/src/dashboard.examples.ts | 719 ++++++++++++++++++++++++ examples/ui/src/index.ts | 14 + examples/ui/src/page.examples.ts | 697 +++++++++++++++++++++++ examples/ui/src/theme.examples.ts | 710 +++++++++++++++++++++++ examples/ui/src/view.examples.ts | 544 ++++++++++++++++++ examples/ui/tsconfig.json | 24 + pnpm-lock.yaml | 10 + 11 files changed, 4452 insertions(+) create mode 100644 examples/ui/README.md create mode 100644 examples/ui/package.json create mode 100644 examples/ui/src/action.examples.ts create mode 100644 examples/ui/src/app.examples.ts create mode 100644 examples/ui/src/dashboard.examples.ts create mode 100644 examples/ui/src/index.ts create mode 100644 examples/ui/src/page.examples.ts create mode 100644 examples/ui/src/theme.examples.ts create mode 100644 examples/ui/src/view.examples.ts create mode 100644 examples/ui/tsconfig.json diff --git a/examples/ui/README.md b/examples/ui/README.md new file mode 100644 index 000000000..65ad5dd33 --- /dev/null +++ b/examples/ui/README.md @@ -0,0 +1,312 @@ +# ObjectStack UI Protocol Examples + +This directory contains comprehensive examples demonstrating the **UI Protocol** of ObjectStack - the presentation layer that defines how users interact with data. + +## πŸ“š What's Inside + +This example package demonstrates all major UI components of the ObjectStack Protocol: + +### 1. **Views** (`view.examples.ts`) +Different ways to display and interact with data: +- **Grid View**: Traditional table/spreadsheet layout +- **Kanban View**: Card-based workflow boards +- **Calendar View**: Time-based event visualization +- **Gantt View**: Project timeline visualization +- **Custom Data Sources**: Using API providers and static data + +### 2. **Pages** (`page.examples.ts`) +Component composition patterns inspired by Salesforce Lightning Pages: +- **Record Pages**: Contextual layouts for viewing/editing records +- **Home Pages**: Dashboard-style landing pages +- **App Pages**: Custom application screens +- **Component Regions**: Flexible layout templates + +### 3. **Dashboards** (`dashboard.examples.ts`) +Analytics and metrics visualization: +- **Widget Types**: Metrics, charts (bar, line, pie, donut, funnel), tables +- **Filters**: Dynamic data filtering +- **Layout Grid**: Responsive dashboard layouts +- **Real-time Metrics**: KPIs and business intelligence + +### 4. **Actions** (`action.examples.ts`) +User interactions and workflows: +- **Button Actions**: Custom buttons with various triggers +- **Modal Actions**: Forms and dialogs +- **Flow Actions**: Visual workflow execution +- **Script Actions**: Custom JavaScript execution +- **Batch Actions**: Mass operations on records + +### 5. **Apps** (`app.examples.ts`) +Complete application structure: +- **Navigation Trees**: Hierarchical menus with groups +- **App Branding**: Custom colors, logos, themes +- **Multi-app Architecture**: Switching between business contexts +- **Permission-based Access**: Role-based app visibility + +### 6. **Themes** (`theme.examples.ts`) +Visual styling and customization: +- **Color Palettes**: Primary, secondary, accent colors +- **Typography**: Font families and sizing +- **Component Styles**: Button variants, borders, shadows +- **Dark Mode**: Theme switching + +## 🎯 Key Concepts + +### Data-Driven UI +All UI components in ObjectStack are **metadata-driven** - they are defined as JSON/TypeScript configurations rather than hardcoded implementations. This enables: + +- **Zero-Code Customization**: Modify UIs without changing source code +- **Version Control**: Track UI changes like any other code +- **Dynamic Generation**: Build UIs programmatically +- **Multi-tenant Isolation**: Different UIs for different customers + +### Separation of Concerns + +The UI Protocol is completely decoupled from: +- **Data Protocol**: Business objects and logic +- **System Protocol**: Runtime configuration +- **Implementation**: Can be rendered by any frontend (React, Vue, Angular) + +### Best Practices Alignment + +ObjectStack UI Protocol draws inspiration from industry leaders: + +| Feature | Salesforce | ServiceNow | ObjectStack | +|---------|-----------|-----------|-------------| +| List Views | List Views | Lists | `ListView` (grid, kanban, calendar) | +| Record Pages | Lightning Pages | Forms | `Page` (regions + components) | +| Dashboards | Dashboards | Performance Analytics | `Dashboard` (widgets) | +| Actions | Quick Actions | UI Actions | `Action` (modal, flow, script) | +| Apps | Lightning Apps | Applications | `App` (navigation + branding) | + +## πŸš€ Usage Examples + +### Basic Grid View +```typescript +import { ListView } from '@objectstack/spec/ui'; + +const taskGridView: ListView = { + type: 'grid', + columns: ['subject', 'status', 'priority', 'due_date'], + filter: [{ field: 'status', operator: '$ne', value: 'completed' }], + sort: [{ field: 'due_date', order: 'asc' }], +}; +``` + +### Kanban Board +```typescript +const opportunityKanban: ListView = { + type: 'kanban', + columns: ['name', 'amount', 'close_date'], + kanban: { + groupByField: 'stage', + summarizeField: 'amount', + columns: ['name', 'amount', 'account_name'], + }, +}; +``` + +### Interactive Dashboard +```typescript +import { Dashboard } from '@objectstack/spec/ui'; + +const salesDashboard: Dashboard = { + name: 'sales_overview', + label: 'Sales Overview', + widgets: [ + { + title: 'Total Revenue', + type: 'metric', + object: 'opportunity', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 0, y: 0, w: 3, h: 2 }, + }, + // ... more widgets + ], +}; +``` + +### Custom Action +```typescript +import { Action } from '@objectstack/spec/ui'; + +const convertLeadAction: Action = { + name: 'convert_lead', + label: 'Convert Lead', + type: 'flow', + target: 'lead_conversion_flow', + locations: ['record_header', 'list_item'], + visible: 'status = "qualified"', +}; +``` + +## πŸ”— Integration with Data Protocol + +UI components reference objects and fields defined in the Data Protocol: + +```typescript +// Data Protocol defines the object +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +const Task = ObjectSchema.create({ + name: 'task', + fields: { + subject: Field.text({ required: true }), + status: Field.select({ options: [...] }), + priority: Field.number({ min: 1, max: 5 }), + }, +}); + +// UI Protocol defines how to display it +const taskListView: ListView = { + type: 'grid', + data: { provider: 'object', object: 'task' }, + columns: ['subject', 'status', 'priority'], +}; +``` + +## πŸ“– Learning Path + +1. **Start Simple**: Review `view.examples.ts` for basic list and form views +2. **Add Interactivity**: Explore `action.examples.ts` for user interactions +3. **Build Analytics**: Study `dashboard.examples.ts` for reporting +4. **Compose Layouts**: Check `page.examples.ts` for advanced layouts +5. **Complete Apps**: See `app.examples.ts` for full application structure + +## 🎨 Visual Customization + +### Theme Variables +```typescript +const customTheme = { + colors: { + primary: '#4169E1', + secondary: '#9370DB', + success: '#00AA00', + danger: '#FF0000', + }, + fonts: { + heading: 'Inter, sans-serif', + body: 'Roboto, sans-serif', + }, +}; +``` + +### App Branding +```typescript +const salesApp = { + name: 'sales_crm', + branding: { + primaryColor: '#4169E1', + logo: '/assets/sales-logo.png', + favicon: '/assets/sales-favicon.ico', + }, +}; +``` + +## πŸ” Advanced Features + +### Dynamic Data Sources +```typescript +// Use custom API instead of ObjectStack metadata +const customListView: ListView = { + type: 'grid', + data: { + provider: 'api', + read: { + url: '/api/external/data', + method: 'GET', + headers: { 'X-API-Key': '{api_key}' }, + }, + }, + columns: ['id', 'name', 'value'], +}; +``` + +### Conditional Visibility +```typescript +const adminAction: Action = { + name: 'delete_all', + label: 'Delete All', + type: 'script', + visible: 'user.role = "admin" AND user.department = "engineering"', + locations: ['list_toolbar'], +}; +``` + +### Multi-level Navigation +```typescript +navigation: [ + { + id: 'sales', + type: 'group', + label: 'Sales', + children: [ + { id: 'leads', type: 'object', objectName: 'lead' }, + { id: 'accounts', type: 'object', objectName: 'account' }, + { + id: 'reports', + type: 'group', + label: 'Reports', + children: [ + { id: 'sales_report', type: 'dashboard', dashboardName: 'sales_dashboard' }, + ], + }, + ], + }, +] +``` + +## πŸ“ File Structure + +``` +examples/ui/ +β”œβ”€β”€ package.json # Package configuration +β”œβ”€β”€ tsconfig.json # TypeScript configuration +β”œβ”€β”€ README.md # This file +└── src/ + β”œβ”€β”€ view.examples.ts # List, Form, Kanban, Calendar views + β”œβ”€β”€ page.examples.ts # Record, Home, App pages + β”œβ”€β”€ dashboard.examples.ts # Widgets and analytics + β”œβ”€β”€ action.examples.ts # Buttons and interactions + β”œβ”€β”€ app.examples.ts # Application structure + └── theme.examples.ts # Visual styling +``` + +## 🀝 Related Examples + +- **`examples/crm`**: Full CRM application using these UI patterns +- **`examples/todo`**: Simple Todo app demonstrating basic UI +- **`examples/modern-fields`**: Modern field types and validation + +## πŸ“š References + +- [UI Protocol Specification](../../packages/spec/src/ui/) +- [Data Protocol Specification](../../packages/spec/src/data/) +- [ObjectStack Architecture](../../ARCHITECTURE.md) +- [Salesforce Lightning Design System](https://www.lightningdesignsystem.com/) +- [ServiceNow UI Builder](https://docs.servicenow.com/bundle/washington-application-development/page/administer/ui-builder/concept/ui-builder.html) + +## πŸ› οΈ Building Examples + +```bash +# Install dependencies +pnpm install + +# Build this example +cd examples/ui +pnpm build + +# Build all examples +pnpm -r build +``` + +## πŸ’‘ Contributing + +These examples are designed to be comprehensive learning resources. When adding new examples: + +1. **Follow naming conventions**: Use `camelCase` for configuration properties +2. **Add comments**: Explain WHY, not just WHAT +3. **Show variations**: Demonstrate multiple approaches +4. **Keep it real**: Use realistic business scenarios +5. **Reference standards**: Link to Salesforce/ServiceNow equivalents when applicable diff --git a/examples/ui/package.json b/examples/ui/package.json new file mode 100644 index 000000000..2d09d44b6 --- /dev/null +++ b/examples/ui/package.json @@ -0,0 +1,16 @@ +{ + "name": "@objectstack/example-ui", + "version": "1.0.0", + "description": "Comprehensive UI Protocol examples demonstrating Views, Pages, Dashboards, Actions, and Themes", + "private": true, + "scripts": { + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@objectstack/spec": "workspace:*" + }, + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/examples/ui/src/action.examples.ts b/examples/ui/src/action.examples.ts new file mode 100644 index 000000000..21ebba524 --- /dev/null +++ b/examples/ui/src/action.examples.ts @@ -0,0 +1,632 @@ +// @ts-nocheck +import { Action } from '@objectstack/spec/ui'; + +/** + * Action Examples - Demonstrating ObjectStack Action Protocol + * + * Actions define user interactions and operations on data. + * Inspired by Salesforce Quick Actions and ServiceNow UI Actions. + */ + +// ============================================================================ +// MODAL ACTIONS (Dialog-based Actions) +// ============================================================================ + +/** + * Example 1: Simple Modal Action + * Opens a modal dialog for user input + * Use Case: Quick data entry, simple forms + */ +export const LogCallAction: Action = { + name: 'log_call', + label: 'Log a Call', + icon: 'phone', + type: 'modal', + target: 'call_log_modal', + locations: ['record_header', 'list_item', 'record_related'], + params: [ + { + name: 'subject', + label: 'Call Subject', + type: 'text', + required: true, + }, + { + name: 'duration', + label: 'Duration (minutes)', + type: 'number', + required: true, + }, + { + name: 'notes', + label: 'Call Notes', + type: 'textarea', + required: false, + }, + ], + successMessage: 'Call logged successfully!', + refreshAfter: true, +}; + +/** + * Example 2: Modal Action with Confirmation + * Requires user confirmation before executing + * Use Case: Destructive or important operations + */ +export const EscalateCaseAction: Action = { + name: 'escalate_case', + label: 'Escalate Case', + icon: 'alert-triangle', + type: 'modal', + target: 'escalate_case_modal', + locations: ['record_header', 'list_item'], + visible: 'is_escalated = false AND is_closed = false', + params: [ + { + name: 'reason', + label: 'Escalation Reason', + type: 'textarea', + required: true, + }, + { + name: 'escalate_to', + label: 'Escalate To', + type: 'lookup', + required: true, + }, + ], + confirmText: 'This will escalate the case to the escalation team. Continue?', + successMessage: 'Case escalated successfully!', + refreshAfter: true, +}; + +/** + * Example 3: Modal Action with Multiple Parameters + * Complex form in modal with various field types + * Use Case: Multi-step processes requiring detailed input + */ +export const ScheduleMeetingAction: Action = { + name: 'schedule_meeting', + label: 'Schedule Meeting', + icon: 'calendar', + type: 'modal', + target: 'meeting_scheduler', + locations: ['record_header', 'list_toolbar'], + params: [ + { + name: 'meeting_title', + label: 'Meeting Title', + type: 'text', + required: true, + }, + { + name: 'start_datetime', + label: 'Start Date & Time', + type: 'datetime', + required: true, + }, + { + name: 'duration', + label: 'Duration', + type: 'select', + required: true, + options: [ + { label: '15 minutes', value: '15' }, + { label: '30 minutes', value: '30' }, + { label: '1 hour', value: '60' }, + { label: '2 hours', value: '120' }, + ], + }, + { + name: 'attendees', + label: 'Attendees', + type: 'lookup', + required: true, + }, + { + name: 'location', + label: 'Location', + type: 'text', + required: false, + }, + { + name: 'description', + label: 'Description', + type: 'textarea', + required: false, + }, + ], + successMessage: 'Meeting scheduled and invites sent!', + refreshAfter: true, +}; + +// ============================================================================ +// FLOW ACTIONS (Visual Workflow Actions) +// ============================================================================ + +/** + * Example 4: Flow Action for Complex Process + * Launches a visual flow for multi-step processes + * Use Case: Lead conversion, order processing, approval workflows + * Inspired by: Salesforce Screen Flows + */ +export const ConvertLeadAction: Action = { + name: 'convert_lead', + label: 'Convert Lead', + icon: 'arrow-right-circle', + type: 'flow', + target: 'lead_conversion_flow', + locations: ['record_header', 'list_item'], + visible: 'status = "qualified" AND is_converted = false', + confirmText: 'Are you sure you want to convert this lead?', + successMessage: 'Lead converted successfully!', + refreshAfter: true, +}; + +/** + * Example 5: Approval Flow Action + * Submits record for approval workflow + * Use Case: Approval processes, review workflows + */ +export const SubmitForApprovalAction: Action = { + name: 'submit_approval', + label: 'Submit for Approval', + icon: 'check-square', + type: 'flow', + target: 'approval_submission_flow', + locations: ['record_header'], + visible: 'status = "draft" AND approval_status = null', + confirmText: 'Submit this record for approval?', + successMessage: 'Submitted for approval', + refreshAfter: true, +}; + +/** + * Example 6: Onboarding Flow Action + * Guided onboarding process for new customers + * Use Case: Customer onboarding, employee onboarding + */ +export const StartOnboardingAction: Action = { + name: 'start_onboarding', + label: 'Start Onboarding', + icon: 'user-plus', + type: 'flow', + target: 'customer_onboarding_flow', + locations: ['record_header'], + visible: 'onboarding_status = "not_started"', + successMessage: 'Onboarding process initiated', + refreshAfter: true, +}; + +// ============================================================================ +// SCRIPT ACTIONS (Custom JavaScript Actions) +// ============================================================================ + +/** + * Example 7: Simple Script Action + * Executes custom JavaScript function + * Use Case: Custom business logic, calculations, integrations + */ +export const CloneRecordAction: Action = { + name: 'clone_record', + label: 'Clone', + icon: 'copy', + type: 'script', + execute: 'cloneRecord', + locations: ['record_header', 'record_more'], + successMessage: 'Record cloned successfully!', + refreshAfter: true, +}; + +/** + * Example 8: Script Action with Confirmation + * Destructive operation requiring confirmation + * Use Case: Delete, archive, mass updates + */ +export const DeleteRecordAction: Action = { + name: 'delete_record', + label: 'Delete', + icon: 'trash-2', + type: 'script', + execute: 'deleteRecord', + locations: ['record_more'], + confirmText: 'Are you sure you want to delete this record? This cannot be undone.', + successMessage: 'Record deleted successfully!', + refreshAfter: false, +}; + +/** + * Example 9: Export Script Action + * Exports data to external format + * Use Case: Data export, reporting, integrations + */ +export const ExportToCsvAction: Action = { + name: 'export_csv', + label: 'Export to CSV', + icon: 'download', + type: 'script', + execute: 'exportToCSV', + locations: ['list_toolbar'], + successMessage: 'Export completed!', + refreshAfter: false, +}; + +/** + * Example 10: Print Action + * Generates printable version + * Use Case: Print reports, invoices, documents + */ +export const PrintRecordAction: Action = { + name: 'print_record', + label: 'Print', + icon: 'printer', + type: 'script', + execute: 'printRecord', + locations: ['record_header', 'record_more'], + refreshAfter: false, +}; + +// ============================================================================ +// URL ACTIONS (Navigation Actions) +// ============================================================================ + +/** + * Example 11: External URL Action + * Opens external link in new tab + * Use Case: External integrations, documentation links + */ +export const ViewInExternalSystemAction: Action = { + name: 'view_external', + label: 'View in CRM', + icon: 'external-link', + type: 'url', + url: 'https://crm.example.com/accounts/{account_id}', + target: '_blank', + locations: ['record_header'], +}; + +/** + * Example 12: Help Documentation Action + * Opens help documentation + * Use Case: Contextual help, user guides + */ +export const OpenHelpAction: Action = { + name: 'open_help', + label: 'Help', + icon: 'help-circle', + type: 'url', + url: 'https://docs.example.com/help/{object_name}', + target: '_blank', + locations: ['record_header', 'list_toolbar'], +}; + +// ============================================================================ +// BATCH ACTIONS (Mass Operations) +// ============================================================================ + +/** + * Example 13: Mass Update Action + * Updates multiple records at once + * Use Case: Bulk operations, mass data updates + * Inspired by: Salesforce Mass Update + */ +export const MassUpdateStageAction: Action = { + name: 'mass_update_stage', + label: 'Update Stage', + icon: 'layers', + type: 'modal', + target: 'mass_update_stage_modal', + locations: ['list_toolbar'], + params: [ + { + name: 'stage', + label: 'New Stage', + type: 'select', + required: true, + options: [ + { label: 'Prospecting', value: 'prospecting' }, + { label: 'Qualification', value: 'qualification' }, + { label: 'Needs Analysis', value: 'needs_analysis' }, + { label: 'Proposal', value: 'proposal' }, + { label: 'Negotiation', value: 'negotiation' }, + { label: 'Closed Won', value: 'closed_won' }, + { label: 'Closed Lost', value: 'closed_lost' }, + ], + }, + ], + confirmText: 'Update stage for selected records?', + successMessage: 'Records updated successfully!', + refreshAfter: true, +}; + +/** + * Example 14: Mass Assign Action + * Assigns multiple records to a user + * Use Case: Workload distribution, team reassignment + */ +export const MassAssignAction: Action = { + name: 'mass_assign', + label: 'Assign To', + icon: 'user-check', + type: 'modal', + target: 'mass_assign_modal', + locations: ['list_toolbar'], + params: [ + { + name: 'owner', + label: 'Assign To', + type: 'lookup', + required: true, + }, + { + name: 'send_notification', + label: 'Send Notification', + type: 'checkbox', + required: false, + }, + ], + confirmText: 'Assign selected records?', + successMessage: 'Records assigned successfully!', + refreshAfter: true, +}; + +/** + * Example 15: Mass Delete Action + * Deletes multiple records + * Use Case: Bulk cleanup, data purging + */ +export const MassDeleteAction: Action = { + name: 'mass_delete', + label: 'Delete Selected', + icon: 'trash-2', + type: 'script', + execute: 'massDeleteRecords', + locations: ['list_toolbar'], + visible: 'user.role = "admin"', + confirmText: 'Are you sure you want to delete the selected records? This cannot be undone.', + successMessage: 'Records deleted successfully!', + refreshAfter: true, +}; + +// ============================================================================ +// CAMPAIGN & MARKETING ACTIONS +// ============================================================================ + +/** + * Example 16: Add to Campaign Action + * Adds records to a marketing campaign + * Use Case: Marketing campaigns, email blasts + */ +export const AddToCampaignAction: Action = { + name: 'add_to_campaign', + label: 'Add to Campaign', + icon: 'send', + type: 'modal', + target: 'add_to_campaign_modal', + locations: ['list_toolbar', 'record_header'], + params: [ + { + name: 'campaign', + label: 'Campaign', + type: 'lookup', + required: true, + }, + { + name: 'member_status', + label: 'Member Status', + type: 'select', + required: true, + options: [ + { label: 'Sent', value: 'sent' }, + { label: 'Responded', value: 'responded' }, + { label: 'Subscribed', value: 'subscribed' }, + ], + }, + ], + successMessage: 'Added to campaign successfully!', + refreshAfter: true, +}; + +/** + * Example 17: Send Email Action + * Sends email to selected records + * Use Case: Email communications, notifications + */ +export const SendEmailAction: Action = { + name: 'send_email', + label: 'Send Email', + icon: 'mail', + type: 'modal', + target: 'email_composer', + locations: ['record_header', 'list_toolbar'], + visible: 'email_opt_out = false', + params: [ + { + name: 'template', + label: 'Email Template', + type: 'select', + required: false, + }, + { + name: 'subject', + label: 'Subject', + type: 'text', + required: true, + }, + { + name: 'body', + label: 'Message', + type: 'textarea', + required: true, + }, + ], + successMessage: 'Email sent successfully!', + refreshAfter: false, +}; + +// ============================================================================ +// STATUS CHANGE ACTIONS +// ============================================================================ + +/** + * Example 18: Mark as Complete Action + * Changes status to completed + * Use Case: Task completion, status updates + */ +export const MarkCompleteAction: Action = { + name: 'mark_complete', + label: 'Mark Complete', + icon: 'check-circle', + type: 'script', + execute: 'markAsComplete', + locations: ['record_header', 'list_item'], + visible: 'is_completed = false', + successMessage: 'Marked as complete!', + refreshAfter: true, +}; + +/** + * Example 19: Close Case Action + * Closes support case with resolution + * Use Case: Support case management + */ +export const CloseCaseAction: Action = { + name: 'close_case', + label: 'Close Case', + icon: 'check-circle', + type: 'modal', + target: 'close_case_modal', + locations: ['record_header'], + visible: 'is_closed = false', + params: [ + { + name: 'resolution', + label: 'Resolution', + type: 'textarea', + required: true, + }, + { + name: 'resolution_type', + label: 'Resolution Type', + type: 'select', + required: true, + options: [ + { label: 'Resolved', value: 'resolved' }, + { label: 'Workaround Provided', value: 'workaround' }, + { label: 'Not a Bug', value: 'not_a_bug' }, + { label: 'Duplicate', value: 'duplicate' }, + ], + }, + ], + confirmText: 'Are you sure you want to close this case?', + successMessage: 'Case closed successfully!', + refreshAfter: true, +}; + +/** + * Example 20: Reopen Case Action + * Reopens a closed case + * Use Case: Case management, issue tracking + */ +export const ReopenCaseAction: Action = { + name: 'reopen_case', + label: 'Reopen Case', + icon: 'refresh-cw', + type: 'modal', + target: 'reopen_case_modal', + locations: ['record_header'], + visible: 'is_closed = true', + params: [ + { + name: 'reason', + label: 'Reason for Reopening', + type: 'textarea', + required: true, + }, + ], + confirmText: 'Reopen this case?', + successMessage: 'Case reopened!', + refreshAfter: true, +}; + +// ============================================================================ +// RELATIONSHIP ACTIONS +// ============================================================================ + +/** + * Example 21: Mark as Primary Contact + * Sets contact as primary for account + * Use Case: Relationship management + */ +export const MarkPrimaryContactAction: Action = { + name: 'mark_primary', + label: 'Mark as Primary Contact', + icon: 'star', + type: 'script', + execute: 'markAsPrimaryContact', + locations: ['record_header', 'list_item'], + visible: 'is_primary = false', + confirmText: 'Mark this contact as the primary contact for the account?', + successMessage: 'Contact marked as primary!', + refreshAfter: true, +}; + +/** + * Example 22: Create Related Record Action + * Creates a related child record + * Use Case: Creating related data quickly + */ +export const CreateRelatedOpportunityAction: Action = { + name: 'create_opportunity', + label: 'New Opportunity', + icon: 'plus-circle', + type: 'modal', + target: 'new_opportunity_modal', + locations: ['record_related'], + successMessage: 'Opportunity created!', + refreshAfter: true, +}; + +// ============================================================================ +// EXPORT ALL EXAMPLES +// ============================================================================ + +export const ActionExamples = { + // Modal Actions + LogCallAction, + EscalateCaseAction, + ScheduleMeetingAction, + + // Flow Actions + ConvertLeadAction, + SubmitForApprovalAction, + StartOnboardingAction, + + // Script Actions + CloneRecordAction, + DeleteRecordAction, + ExportToCsvAction, + PrintRecordAction, + + // URL Actions + ViewInExternalSystemAction, + OpenHelpAction, + + // Batch Actions + MassUpdateStageAction, + MassAssignAction, + MassDeleteAction, + + // Campaign Actions + AddToCampaignAction, + SendEmailAction, + + // Status Actions + MarkCompleteAction, + CloseCaseAction, + ReopenCaseAction, + + // Relationship Actions + MarkPrimaryContactAction, + CreateRelatedOpportunityAction, +}; diff --git a/examples/ui/src/app.examples.ts b/examples/ui/src/app.examples.ts new file mode 100644 index 000000000..37cdef5c0 --- /dev/null +++ b/examples/ui/src/app.examples.ts @@ -0,0 +1,774 @@ +// @ts-nocheck +import { App } from '@objectstack/spec/ui'; + +/** + * App Examples - Demonstrating ObjectStack App Protocol + * + * Apps provide logical containers for business functionality with navigation and branding. + * Inspired by Salesforce Lightning Apps and ServiceNow Applications. + */ + +// ============================================================================ +// BASIC APPS +// ============================================================================ + +/** + * Example 1: Simple Sales CRM App + * Basic sales application with essential objects + * Use Case: Small teams, simple CRM needs + */ +export const SimpleSalesCrmApp: App = { + name: 'simple_sales_crm', + label: 'Sales CRM', + description: 'Simple sales management application', + version: '1.0.0', + icon: 'briefcase', + + navigation: [ + { + id: 'nav_home', + type: 'page', + label: 'Home', + icon: 'home', + pageName: 'sales_home', + }, + { + id: 'nav_leads', + type: 'object', + label: 'Leads', + icon: 'users', + objectName: 'lead', + }, + { + id: 'nav_accounts', + type: 'object', + label: 'Accounts', + icon: 'building', + objectName: 'account', + }, + { + id: 'nav_opportunities', + type: 'object', + label: 'Opportunities', + icon: 'trending-up', + objectName: 'opportunity', + }, + { + id: 'nav_dashboard', + type: 'dashboard', + label: 'Dashboard', + icon: 'bar-chart', + dashboardName: 'sales_dashboard', + }, + ], + + branding: { + primaryColor: '#4169E1', + logo: '/assets/sales-logo.png', + }, + + active: true, + isDefault: true, +}; + +/** + * Example 2: Customer Service App + * Support and service management application + * Use Case: Customer support teams + */ +export const CustomerServiceApp: App = { + name: 'customer_service', + label: 'Customer Service', + description: 'Support case and customer service management', + version: '1.0.0', + icon: 'headphones', + + navigation: [ + { + id: 'nav_home', + type: 'page', + label: 'Service Console', + icon: 'layout-dashboard', + pageName: 'service_console', + }, + { + id: 'nav_cases', + type: 'object', + label: 'Cases', + icon: 'inbox', + objectName: 'case', + viewName: 'my_open_cases', + }, + { + id: 'nav_accounts', + type: 'object', + label: 'Accounts', + icon: 'building', + objectName: 'account', + }, + { + id: 'nav_knowledge', + type: 'object', + label: 'Knowledge Base', + icon: 'book-open', + objectName: 'knowledge_article', + }, + { + id: 'nav_dashboard', + type: 'dashboard', + label: 'Service Metrics', + icon: 'activity', + dashboardName: 'service_dashboard', + }, + ], + + branding: { + primaryColor: '#10B981', + logo: '/assets/service-logo.png', + }, + + active: true, +}; + +// ============================================================================ +// ADVANCED APPS WITH HIERARCHICAL NAVIGATION +// ============================================================================ + +/** + * Example 3: Comprehensive CRM App + * Full-featured CRM with hierarchical navigation + * Use Case: Large organizations, complex business processes + */ +export const ComprehensiveCrmApp: App = { + name: 'crm_enterprise', + label: 'CRM Enterprise', + description: 'Comprehensive CRM with sales, service, and marketing', + version: '2.0.0', + icon: 'layers', + + navigation: [ + // Home + { + id: 'nav_home', + type: 'page', + label: 'Home', + icon: 'home', + pageName: 'crm_home', + }, + + // Sales Group + { + id: 'group_sales', + type: 'group', + label: 'Sales', + icon: 'trending-up', + expanded: true, + children: [ + { + id: 'nav_leads', + type: 'object', + label: 'Leads', + icon: 'user-plus', + objectName: 'lead', + }, + { + id: 'nav_accounts', + type: 'object', + label: 'Accounts', + icon: 'building', + objectName: 'account', + }, + { + id: 'nav_contacts', + type: 'object', + label: 'Contacts', + icon: 'users', + objectName: 'contact', + }, + { + id: 'nav_opportunities', + type: 'object', + label: 'Opportunities', + icon: 'target', + objectName: 'opportunity', + }, + { + id: 'nav_quotes', + type: 'object', + label: 'Quotes', + icon: 'file-text', + objectName: 'quote', + }, + { + id: 'nav_sales_dashboard', + type: 'dashboard', + label: 'Sales Dashboard', + icon: 'bar-chart', + dashboardName: 'sales_dashboard', + }, + ], + }, + + // Service Group + { + id: 'group_service', + type: 'group', + label: 'Service', + icon: 'headphones', + expanded: false, + children: [ + { + id: 'nav_cases', + type: 'object', + label: 'Cases', + icon: 'inbox', + objectName: 'case', + }, + { + id: 'nav_knowledge', + type: 'object', + label: 'Knowledge', + icon: 'book', + objectName: 'knowledge_article', + }, + { + id: 'nav_service_dashboard', + type: 'dashboard', + label: 'Service Dashboard', + icon: 'activity', + dashboardName: 'service_dashboard', + }, + ], + }, + + // Marketing Group + { + id: 'group_marketing', + type: 'group', + label: 'Marketing', + icon: 'megaphone', + expanded: false, + children: [ + { + id: 'nav_campaigns', + type: 'object', + label: 'Campaigns', + icon: 'send', + objectName: 'campaign', + }, + { + id: 'nav_email_templates', + type: 'object', + label: 'Email Templates', + icon: 'mail', + objectName: 'email_template', + }, + { + id: 'nav_marketing_dashboard', + type: 'dashboard', + label: 'Marketing Dashboard', + icon: 'pie-chart', + dashboardName: 'marketing_dashboard', + }, + ], + }, + + // Analytics Group + { + id: 'group_analytics', + type: 'group', + label: 'Analytics', + icon: 'bar-chart-2', + expanded: false, + children: [ + { + id: 'nav_exec_dashboard', + type: 'dashboard', + label: 'Executive Dashboard', + icon: 'trending-up', + dashboardName: 'executive_dashboard', + }, + { + id: 'nav_reports', + type: 'page', + label: 'Reports', + icon: 'file-bar-chart', + pageName: 'reports_page', + }, + { + id: 'nav_forecasts', + type: 'object', + label: 'Forecasts', + icon: 'calendar', + objectName: 'forecast', + }, + ], + }, + + // Settings Group + { + id: 'group_settings', + type: 'group', + label: 'Settings', + icon: 'settings', + expanded: false, + children: [ + { + id: 'nav_users', + type: 'object', + label: 'Users', + icon: 'user', + objectName: 'user', + visible: 'user.role = "admin"', + }, + { + id: 'nav_teams', + type: 'object', + label: 'Teams', + icon: 'users', + objectName: 'team', + }, + { + id: 'nav_setup', + type: 'page', + label: 'Setup', + icon: 'tool', + pageName: 'setup_page', + visible: 'user.role = "admin"', + }, + ], + }, + ], + + branding: { + primaryColor: '#6366F1', + logo: '/assets/crm-logo.png', + favicon: '/assets/crm-favicon.ico', + }, + + active: true, +}; + +// ============================================================================ +// SPECIALIZED APPS +// ============================================================================ + +/** + * Example 4: Project Management App + * Project and task management application + * Use Case: Project teams, agile workflows + */ +export const ProjectManagementApp: App = { + name: 'project_management', + label: 'Project Manager', + description: 'Project, sprint, and task management', + version: '1.0.0', + icon: 'folder', + + navigation: [ + { + id: 'nav_home', + type: 'page', + label: 'My Dashboard', + icon: 'home', + pageName: 'project_home', + }, + { + id: 'group_planning', + type: 'group', + label: 'Planning', + icon: 'calendar', + children: [ + { + id: 'nav_projects', + type: 'object', + label: 'Projects', + icon: 'folder', + objectName: 'project', + }, + { + id: 'nav_sprints', + type: 'object', + label: 'Sprints', + icon: 'zap', + objectName: 'sprint', + }, + { + id: 'nav_roadmap', + type: 'page', + label: 'Roadmap', + icon: 'map', + pageName: 'product_roadmap', + }, + ], + }, + { + id: 'group_execution', + type: 'group', + label: 'Execution', + icon: 'check-circle', + children: [ + { + id: 'nav_tasks', + type: 'object', + label: 'Tasks', + icon: 'check-square', + objectName: 'task', + viewName: 'my_tasks', + }, + { + id: 'nav_kanban', + type: 'object', + label: 'Kanban Board', + icon: 'columns', + objectName: 'task', + viewName: 'kanban_view', + }, + { + id: 'nav_issues', + type: 'object', + label: 'Issues', + icon: 'alert-circle', + objectName: 'issue', + }, + ], + }, + { + id: 'group_reporting', + type: 'group', + label: 'Reporting', + icon: 'bar-chart', + children: [ + { + id: 'nav_burndown', + type: 'dashboard', + label: 'Burndown Chart', + icon: 'trending-down', + dashboardName: 'sprint_burndown', + }, + { + id: 'nav_velocity', + type: 'dashboard', + label: 'Velocity', + icon: 'activity', + dashboardName: 'team_velocity', + }, + ], + }, + ], + + branding: { + primaryColor: '#8B5CF6', + logo: '/assets/pm-logo.png', + }, + + active: true, +}; + +/** + * Example 5: HR Management App + * Human resources and employee management + * Use Case: HR departments, employee self-service + */ +export const HrManagementApp: App = { + name: 'hr_management', + label: 'HR Portal', + description: 'Human resources and employee management', + version: '1.0.0', + icon: 'users', + + navigation: [ + { + id: 'nav_home', + type: 'page', + label: 'My HR', + icon: 'home', + pageName: 'employee_portal', + }, + { + id: 'group_employees', + type: 'group', + label: 'Employees', + icon: 'users', + visible: 'user.department = "hr"', + children: [ + { + id: 'nav_employees', + type: 'object', + label: 'Employee Directory', + icon: 'user', + objectName: 'employee', + }, + { + id: 'nav_departments', + type: 'object', + label: 'Departments', + icon: 'layers', + objectName: 'department', + }, + { + id: 'nav_positions', + type: 'object', + label: 'Positions', + icon: 'briefcase', + objectName: 'position', + }, + ], + }, + { + id: 'group_recruitment', + type: 'group', + label: 'Recruitment', + icon: 'user-plus', + visible: 'user.department = "hr"', + children: [ + { + id: 'nav_jobs', + type: 'object', + label: 'Job Openings', + icon: 'clipboard', + objectName: 'job_opening', + }, + { + id: 'nav_applications', + type: 'object', + label: 'Applications', + icon: 'file-text', + objectName: 'job_application', + }, + ], + }, + { + id: 'group_time_off', + type: 'group', + label: 'Time Off', + icon: 'calendar', + children: [ + { + id: 'nav_requests', + type: 'object', + label: 'Time Off Requests', + icon: 'calendar-days', + objectName: 'time_off_request', + }, + { + id: 'nav_my_balance', + type: 'page', + label: 'My Balance', + icon: 'clock', + pageName: 'time_off_balance', + }, + ], + }, + { + id: 'nav_analytics', + type: 'dashboard', + label: 'HR Analytics', + icon: 'bar-chart', + dashboardName: 'hr_dashboard', + visible: 'user.department = "hr"', + }, + ], + + branding: { + primaryColor: '#EC4899', + logo: '/assets/hr-logo.png', + }, + + active: true, +}; + +// ============================================================================ +// APPS WITH EXTERNAL LINKS +// ============================================================================ + +/** + * Example 6: App with External Integrations + * App that includes external tools and resources + * Use Case: Mixed internal/external tools + */ +export const IntegratedWorkspaceApp: App = { + name: 'integrated_workspace', + label: 'Workspace', + description: 'Integrated workspace with internal and external tools', + version: '1.0.0', + icon: 'grid', + + navigation: [ + { + id: 'nav_home', + type: 'page', + label: 'Home', + icon: 'home', + pageName: 'workspace_home', + }, + { + id: 'group_internal', + type: 'group', + label: 'Internal Apps', + icon: 'package', + children: [ + { + id: 'nav_tasks', + type: 'object', + label: 'Tasks', + icon: 'check-square', + objectName: 'task', + }, + { + id: 'nav_projects', + type: 'object', + label: 'Projects', + icon: 'folder', + objectName: 'project', + }, + ], + }, + { + id: 'group_external', + type: 'group', + label: 'External Tools', + icon: 'external-link', + children: [ + { + id: 'nav_slack', + type: 'url', + label: 'Slack', + icon: 'message-square', + url: 'https://slack.com/app', + target: '_blank', + }, + { + id: 'nav_github', + type: 'url', + label: 'GitHub', + icon: 'github', + url: 'https://github.com/org/repos', + target: '_blank', + }, + { + id: 'nav_docs', + type: 'url', + label: 'Documentation', + icon: 'book-open', + url: 'https://docs.example.com', + target: '_blank', + }, + ], + }, + ], + + branding: { + primaryColor: '#3B82F6', + logo: '/assets/workspace-logo.png', + }, + + active: true, +}; + +// ============================================================================ +// APP WITH PERMISSION-BASED NAVIGATION +// ============================================================================ + +/** + * Example 7: App with Role-Based Access + * App requiring specific permissions for access + * Use Case: Secure apps, admin tools + */ +export const AdminApp: App = { + name: 'admin_console', + label: 'Admin Console', + description: 'Administrative tools and settings', + version: '1.0.0', + icon: 'shield', + + navigation: [ + { + id: 'nav_dashboard', + type: 'dashboard', + label: 'Overview', + icon: 'layout-dashboard', + dashboardName: 'admin_dashboard', + }, + { + id: 'group_users', + type: 'group', + label: 'User Management', + icon: 'users', + children: [ + { + id: 'nav_users', + type: 'object', + label: 'Users', + icon: 'user', + objectName: 'user', + }, + { + id: 'nav_roles', + type: 'object', + label: 'Roles', + icon: 'shield-check', + objectName: 'role', + }, + { + id: 'nav_permissions', + type: 'object', + label: 'Permissions', + icon: 'key', + objectName: 'permission', + }, + ], + }, + { + id: 'group_system', + type: 'group', + label: 'System', + icon: 'settings', + children: [ + { + id: 'nav_audit_log', + type: 'object', + label: 'Audit Log', + icon: 'file-text', + objectName: 'audit_log', + }, + { + id: 'nav_integrations', + type: 'object', + label: 'Integrations', + icon: 'plug', + objectName: 'integration', + }, + { + id: 'nav_settings', + type: 'page', + label: 'System Settings', + icon: 'sliders', + pageName: 'system_settings', + }, + ], + }, + ], + + branding: { + primaryColor: '#F59E0B', + logo: '/assets/admin-logo.png', + }, + + active: true, + requiredPermissions: ['app.access.admin'], +}; + +// ============================================================================ +// EXPORT ALL EXAMPLES +// ============================================================================ + +export const AppExamples = { + SimpleSalesCrmApp, + CustomerServiceApp, + ComprehensiveCrmApp, + ProjectManagementApp, + HrManagementApp, + IntegratedWorkspaceApp, + AdminApp, +}; diff --git a/examples/ui/src/dashboard.examples.ts b/examples/ui/src/dashboard.examples.ts new file mode 100644 index 000000000..226b577be --- /dev/null +++ b/examples/ui/src/dashboard.examples.ts @@ -0,0 +1,719 @@ +// @ts-nocheck +import { Dashboard } from '@objectstack/spec/ui'; + +/** + * Dashboard Examples - Demonstrating ObjectStack Dashboard Protocol + * + * Dashboards provide at-a-glance views of key metrics and analytics. + * Inspired by Salesforce Dashboards and ServiceNow Performance Analytics. + */ + +// ============================================================================ +// SALES DASHBOARDS +// ============================================================================ + +/** + * Example 1: Sales Performance Dashboard + * Comprehensive sales metrics and pipeline visualization + * Use Case: Sales leadership, performance monitoring + */ +export const SalesPerformanceDashboard: Dashboard = { + name: 'sales_performance', + label: 'Sales Performance', + description: 'Key sales metrics and pipeline overview', + + widgets: [ + // Row 1: Key Metrics (KPIs) + { + title: 'Total Pipeline Value', + type: 'metric', + object: 'opportunity', + filter: { + stage: { $nin: ['closed_won', 'closed_lost'] }, + }, + valueField: 'amount', + aggregate: 'sum', + layout: { x: 0, y: 0, w: 3, h: 2 }, + options: { + prefix: '$', + color: '#4169E1', + comparisonPeriod: 'last_quarter', + showTrend: true, + }, + }, + { + title: 'Closed Won This Quarter', + type: 'metric', + object: 'opportunity', + filter: { + stage: 'closed_won', + close_date: { $gte: '{current_quarter_start}' }, + }, + valueField: 'amount', + aggregate: 'sum', + layout: { x: 3, y: 0, w: 3, h: 2 }, + options: { + prefix: '$', + color: '#00AA00', + target: 1000000, // Show progress toward $1M goal + }, + }, + { + title: 'Open Opportunities', + type: 'metric', + object: 'opportunity', + filter: { + stage: { $nin: ['closed_won', 'closed_lost'] }, + }, + aggregate: 'count', + layout: { x: 6, y: 0, w: 3, h: 2 }, + options: { + color: '#FFA500', + }, + }, + { + title: 'Win Rate', + type: 'metric', + object: 'opportunity', + filter: { + close_date: { $gte: '{current_quarter_start}' }, + }, + valueField: 'stage', + aggregate: 'count', + layout: { x: 9, y: 0, w: 3, h: 2 }, + options: { + suffix: '%', + color: '#9370DB', + formula: '(closed_won / total_opportunities) * 100', + }, + }, + + // Row 2: Pipeline Analysis + { + title: 'Pipeline by Stage', + type: 'funnel', + object: 'opportunity', + filter: { + stage: { $nin: ['closed_won', 'closed_lost'] }, + }, + categoryField: 'stage', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 0, y: 2, w: 6, h: 4 }, + options: { + showValues: true, + colorScheme: 'blues', + }, + }, + { + title: 'Opportunities by Owner', + type: 'bar', + object: 'opportunity', + filter: { + stage: { $nin: ['closed_won', 'closed_lost'] }, + }, + categoryField: 'owner', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 6, y: 2, w: 6, h: 4 }, + options: { + horizontal: true, + sortBy: 'value', + sortOrder: 'desc', + limit: 10, + }, + }, + + // Row 3: Trends + { + title: 'Monthly Revenue Trend', + type: 'line', + object: 'opportunity', + filter: { + stage: 'closed_won', + close_date: { $gte: '{last_12_months}' }, + }, + categoryField: 'close_date', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 0, y: 6, w: 8, h: 4 }, + options: { + dateGranularity: 'month', + showTrend: true, + showGoalLine: true, + goalValue: 100000, + }, + }, + { + title: 'Top Opportunities', + type: 'table', + object: 'opportunity', + filter: { + stage: { $nin: ['closed_won', 'closed_lost'] }, + }, + aggregate: 'count', + layout: { x: 8, y: 6, w: 4, h: 4 }, + options: { + columns: ['name', 'amount', 'stage', 'close_date'], + sortBy: 'amount', + sortOrder: 'desc', + limit: 10, + }, + }, + ], +}; + +/** + * Example 2: Sales Leaderboard Dashboard + * Gamified sales performance tracking + * Use Case: Team motivation, performance tracking + */ +export const SalesLeaderboardDashboard: Dashboard = { + name: 'sales_leaderboard', + label: 'Sales Leaderboard', + description: 'Team performance and rankings', + + widgets: [ + { + title: 'Top Performers (This Month)', + type: 'table', + object: 'opportunity', + filter: { + stage: 'closed_won', + close_date: { $gte: '{current_month_start}' }, + }, + aggregate: 'count', + layout: { x: 0, y: 0, w: 6, h: 6 }, + options: { + columns: ['owner', 'total_amount', 'deal_count', 'avg_deal_size'], + groupBy: 'owner', + sortBy: 'total_amount', + sortOrder: 'desc', + showRank: true, + }, + }, + { + title: 'Revenue by Rep', + type: 'bar', + object: 'opportunity', + filter: { + stage: 'closed_won', + close_date: { $gte: '{current_quarter_start}' }, + }, + categoryField: 'owner', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 6, y: 0, w: 6, h: 6 }, + options: { + horizontal: true, + colorGradient: true, + }, + }, + ], +}; + +// ============================================================================ +// CUSTOMER SERVICE DASHBOARDS +// ============================================================================ + +/** + * Example 3: Customer Service Dashboard + * Support case metrics and performance + * Use Case: Support team management, SLA monitoring + */ +export const CustomerServiceDashboard: Dashboard = { + name: 'customer_service', + label: 'Customer Service', + description: 'Support case metrics and performance', + + widgets: [ + // Row 1: Key Metrics + { + title: 'Open Cases', + type: 'metric', + object: 'case', + filter: { is_closed: false }, + aggregate: 'count', + layout: { x: 0, y: 0, w: 3, h: 2 }, + options: { + color: '#FFA500', + }, + }, + { + title: 'Critical Cases', + type: 'metric', + object: 'case', + filter: { + priority: 'critical', + is_closed: false, + }, + aggregate: 'count', + layout: { x: 3, y: 0, w: 3, h: 2 }, + options: { + color: '#FF0000', + threshold: { warning: 5, critical: 10 }, + }, + }, + { + title: 'Avg Resolution Time', + type: 'metric', + object: 'case', + filter: { is_closed: true }, + valueField: 'resolution_time_hours', + aggregate: 'avg', + layout: { x: 6, y: 0, w: 3, h: 2 }, + options: { + suffix: 'h', + color: '#4169E1', + target: 24, // 24 hour target + }, + }, + { + title: 'SLA Violations', + type: 'metric', + object: 'case', + filter: { is_sla_violated: true }, + aggregate: 'count', + layout: { x: 9, y: 0, w: 3, h: 2 }, + options: { + color: '#FF4500', + threshold: { warning: 1, critical: 5 }, + }, + }, + + // Row 2: Case Distribution + { + title: 'Cases by Status', + type: 'donut', + object: 'case', + filter: { is_closed: false }, + categoryField: 'status', + aggregate: 'count', + layout: { x: 0, y: 2, w: 4, h: 4 }, + options: { + showLegend: true, + showPercentage: true, + }, + }, + { + title: 'Cases by Priority', + type: 'pie', + object: 'case', + filter: { is_closed: false }, + categoryField: 'priority', + aggregate: 'count', + layout: { x: 4, y: 2, w: 4, h: 4 }, + options: { + showLegend: true, + colorMap: { + 'critical': '#FF0000', + 'high': '#FFA500', + 'medium': '#FFFF00', + 'low': '#00AA00', + }, + }, + }, + { + title: 'Cases by Origin', + type: 'bar', + object: 'case', + categoryField: 'origin', + aggregate: 'count', + layout: { x: 8, y: 2, w: 4, h: 4 }, + }, + + // Row 3: Trends + { + title: 'Daily Case Volume', + type: 'line', + object: 'case', + filter: { + created_date: { $gte: '{last_30_days}' }, + }, + categoryField: 'created_date', + aggregate: 'count', + layout: { x: 0, y: 6, w: 8, h: 4 }, + options: { + dateGranularity: 'day', + showMovingAverage: true, + movingAveragePeriod: 7, + }, + }, + { + title: 'My Open Cases', + type: 'table', + object: 'case', + filter: { + owner: '{current_user}', + is_closed: false, + }, + aggregate: 'count', + layout: { x: 8, y: 6, w: 4, h: 4 }, + options: { + columns: ['case_number', 'subject', 'priority', 'status'], + sortBy: 'priority', + sortOrder: 'desc', + limit: 10, + }, + }, + ], +}; + +// ============================================================================ +// EXECUTIVE DASHBOARDS +// ============================================================================ + +/** + * Example 4: Executive Dashboard + * High-level business metrics for leadership + * Use Case: Executive overview, board meetings + */ +export const ExecutiveDashboard: Dashboard = { + name: 'executive_overview', + label: 'Executive Overview', + description: 'High-level business metrics', + + widgets: [ + // Row 1: Revenue Metrics + { + title: 'Total Revenue (YTD)', + type: 'metric', + object: 'opportunity', + filter: { + stage: 'closed_won', + close_date: { $gte: '{current_year_start}' }, + }, + valueField: 'amount', + aggregate: 'sum', + layout: { x: 0, y: 0, w: 3, h: 2 }, + options: { + prefix: '$', + color: '#00AA00', + comparisonPeriod: 'last_year', + showTrend: true, + }, + }, + { + title: 'Active Customers', + type: 'metric', + object: 'account', + filter: { is_active: true }, + aggregate: 'count', + layout: { x: 3, y: 0, w: 3, h: 2 }, + options: { + color: '#4169E1', + }, + }, + { + title: 'Customer Satisfaction', + type: 'metric', + object: 'survey', + filter: { + survey_type: 'csat', + submitted_date: { $gte: '{current_quarter_start}' }, + }, + valueField: 'score', + aggregate: 'avg', + layout: { x: 6, y: 0, w: 3, h: 2 }, + options: { + suffix: '/5', + color: '#9370DB', + decimals: 1, + }, + }, + { + title: 'Active Leads', + type: 'metric', + object: 'lead', + filter: { is_converted: false }, + aggregate: 'count', + layout: { x: 9, y: 0, w: 3, h: 2 }, + options: { + color: '#FFA500', + }, + }, + + // Row 2: Revenue Analysis + { + title: 'Revenue by Product', + type: 'bar', + object: 'opportunity', + filter: { + stage: 'closed_won', + close_date: { $gte: '{current_year_start}' }, + }, + categoryField: 'product_line', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 0, y: 2, w: 6, h: 4 }, + options: { + sortBy: 'value', + sortOrder: 'desc', + }, + }, + { + title: 'Quarterly Revenue Trend', + type: 'line', + object: 'opportunity', + filter: { + stage: 'closed_won', + close_date: { $gte: '{last_4_quarters}' }, + }, + categoryField: 'close_date', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 6, y: 2, w: 6, h: 4 }, + options: { + dateGranularity: 'quarter', + showForecast: true, + }, + }, + + // Row 3: Customer Insights + { + title: 'Customer Growth', + type: 'bar', + object: 'account', + filter: { + created_date: { $gte: '{last_6_months}' }, + }, + categoryField: 'created_date', + aggregate: 'count', + layout: { x: 0, y: 6, w: 4, h: 4 }, + options: { + dateGranularity: 'month', + }, + }, + { + title: 'Lead Conversion Rate', + type: 'metric', + object: 'lead', + valueField: 'is_converted', + aggregate: 'avg', + layout: { x: 4, y: 6, w: 4, h: 4 }, + options: { + suffix: '%', + color: '#00AA00', + formula: '(converted_leads / total_leads) * 100', + }, + }, + { + title: 'Top Accounts by Revenue', + type: 'table', + object: 'account', + aggregate: 'count', + layout: { x: 8, y: 6, w: 4, h: 4 }, + options: { + columns: ['name', 'annual_revenue', 'industry'], + sortBy: 'annual_revenue', + sortOrder: 'desc', + limit: 10, + }, + }, + ], +}; + +// ============================================================================ +// MARKETING DASHBOARDS +// ============================================================================ + +/** + * Example 5: Marketing Performance Dashboard + * Campaign and lead generation metrics + * Use Case: Marketing team performance monitoring + */ +export const MarketingDashboard: Dashboard = { + name: 'marketing_performance', + label: 'Marketing Performance', + description: 'Campaign metrics and lead generation', + + widgets: [ + { + title: 'Campaign ROI', + type: 'metric', + object: 'campaign', + filter: { + status: 'completed', + end_date: { $gte: '{current_quarter_start}' }, + }, + valueField: 'roi_percentage', + aggregate: 'avg', + layout: { x: 0, y: 0, w: 3, h: 2 }, + options: { + suffix: '%', + color: '#00AA00', + }, + }, + { + title: 'Leads Generated', + type: 'metric', + object: 'lead', + filter: { + created_date: { $gte: '{current_month_start}' }, + }, + aggregate: 'count', + layout: { x: 3, y: 0, w: 3, h: 2 }, + options: { + color: '#4169E1', + comparisonPeriod: 'last_month', + }, + }, + { + title: 'Cost Per Lead', + type: 'metric', + object: 'campaign', + valueField: 'cost_per_lead', + aggregate: 'avg', + layout: { x: 6, y: 0, w: 3, h: 2 }, + options: { + prefix: '$', + color: '#FFA500', + }, + }, + { + title: 'Active Campaigns', + type: 'metric', + object: 'campaign', + filter: { status: 'active' }, + aggregate: 'count', + layout: { x: 9, y: 0, w: 3, h: 2 }, + options: { + color: '#9370DB', + }, + }, + { + title: 'Leads by Source', + type: 'donut', + object: 'lead', + filter: { + created_date: { $gte: '{current_quarter_start}' }, + }, + categoryField: 'lead_source', + aggregate: 'count', + layout: { x: 0, y: 2, w: 6, h: 4 }, + options: { + showLegend: true, + }, + }, + { + title: 'Campaign Performance', + type: 'table', + object: 'campaign', + filter: { + status: { $in: ['active', 'completed'] }, + }, + aggregate: 'count', + layout: { x: 6, y: 2, w: 6, h: 4 }, + options: { + columns: ['name', 'leads_generated', 'total_cost', 'roi_percentage'], + sortBy: 'roi_percentage', + sortOrder: 'desc', + }, + }, + ], +}; + +// ============================================================================ +// OPERATIONAL DASHBOARDS +// ============================================================================ + +/** + * Example 6: Team Productivity Dashboard + * Activity tracking and productivity metrics + * Use Case: Team management, workload monitoring + */ +export const TeamProductivityDashboard: Dashboard = { + name: 'team_productivity', + label: 'Team Productivity', + description: 'Activity and workload metrics', + + widgets: [ + { + title: 'Tasks Completed Today', + type: 'metric', + object: 'task', + filter: { + is_completed: true, + completed_date: { $gte: '{today}' }, + }, + aggregate: 'count', + layout: { x: 0, y: 0, w: 3, h: 2 }, + }, + { + title: 'Overdue Tasks', + type: 'metric', + object: 'task', + filter: { + is_completed: false, + due_date: { $lt: '{today}' }, + }, + aggregate: 'count', + layout: { x: 3, y: 0, w: 3, h: 2 }, + options: { + color: '#FF0000', + }, + }, + { + title: 'Team Utilization', + type: 'metric', + object: 'user', + valueField: 'utilization_percentage', + aggregate: 'avg', + layout: { x: 6, y: 0, w: 3, h: 2 }, + options: { + suffix: '%', + }, + }, + { + title: 'Meetings This Week', + type: 'metric', + object: 'event', + filter: { + start_date: { $gte: '{week_start}', $lte: '{week_end}' }, + }, + aggregate: 'count', + layout: { x: 9, y: 0, w: 3, h: 2 }, + }, + { + title: 'Tasks by Team Member', + type: 'bar', + object: 'task', + filter: { + is_completed: false, + }, + categoryField: 'assigned_to', + aggregate: 'count', + layout: { x: 0, y: 2, w: 6, h: 4 }, + options: { + horizontal: true, + }, + }, + { + title: 'Task Completion Trend', + type: 'line', + object: 'task', + filter: { + completed_date: { $gte: '{last_30_days}' }, + }, + categoryField: 'completed_date', + aggregate: 'count', + layout: { x: 6, y: 2, w: 6, h: 4 }, + options: { + dateGranularity: 'day', + }, + }, + ], +}; + +// ============================================================================ +// EXPORT ALL EXAMPLES +// ============================================================================ + +export const DashboardExamples: any = { + SalesPerformanceDashboard, + SalesLeaderboardDashboard, + CustomerServiceDashboard, + ExecutiveDashboard, + MarketingDashboard, + TeamProductivityDashboard, +}; diff --git a/examples/ui/src/index.ts b/examples/ui/src/index.ts new file mode 100644 index 000000000..d94482f2e --- /dev/null +++ b/examples/ui/src/index.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * ObjectStack UI Protocol Examples + * + * This module exports comprehensive examples demonstrating all aspects + * of the ObjectStack UI Protocol. + */ + +export * from './view.examples'; +export * from './action.examples'; +export * from './dashboard.examples'; +export * from './page.examples'; +export * from './app.examples'; +export * from './theme.examples'; diff --git a/examples/ui/src/page.examples.ts b/examples/ui/src/page.examples.ts new file mode 100644 index 000000000..8fb71f625 --- /dev/null +++ b/examples/ui/src/page.examples.ts @@ -0,0 +1,697 @@ +// @ts-nocheck +import { Page } from '@objectstack/spec/ui'; + +/** + * Page Examples - Demonstrating ObjectStack Page Protocol + * + * Pages define component composition and layouts. + * Inspired by Salesforce Lightning Pages and ServiceNow UI Builder. + */ + +// ============================================================================ +// RECORD PAGES (Detail/Edit Pages) +// ============================================================================ + +/** + * Example 1: Simple Record Page + * Basic record page with standard layout + * Use Case: Default record views for most objects + */ +export const SimpleRecordPage: Page = { + name: 'account_record_page', + label: 'Account Record Page', + description: 'Default record page for accounts', + type: 'record', + object: 'account', + template: 'header-sidebar-main', + + regions: [ + { + name: 'header', + components: [ + { + type: 'record-header', + properties: { + title: '{name}', + subtitle: '{industry}', + icon: 'building', + fields: ['owner', 'type', 'annual_revenue'], + }, + }, + ], + }, + { + name: 'sidebar', + width: 'small', + components: [ + { + type: 'highlights-panel', + label: 'Key Details', + properties: { + fields: ['account_number', 'phone', 'website', 'is_active'], + }, + }, + ], + }, + { + name: 'main', + components: [ + { + type: 'record-detail', + properties: { + sections: [ + { + label: 'Account Information', + fields: ['name', 'type', 'industry', 'annual_revenue'], + }, + { + label: 'Address Information', + fields: ['billing_address', 'shipping_address'], + }, + ], + }, + }, + { + type: 'related-lists', + label: 'Related Lists', + properties: { + lists: [ + { object: 'contact', relationField: 'account_id', label: 'Contacts' }, + { object: 'opportunity', relationField: 'account_id', label: 'Opportunities' }, + { object: 'case', relationField: 'account_id', label: 'Cases' }, + ], + }, + }, + ], + }, + ], + + isDefault: true, +}; + +/** + * Example 2: Advanced Record Page with Multiple Regions + * Complex page layout with tabs and multiple component types + * Use Case: High-value objects requiring rich context (e.g., Account, Opportunity) + */ +export const AdvancedRecordPage: Page = { + name: 'opportunity_record_page', + label: 'Opportunity Record Page', + description: 'Enhanced record page for opportunities', + type: 'record', + object: 'opportunity', + template: 'header-sidebar-main-footer', + + regions: [ + { + name: 'header', + components: [ + { + type: 'path-component', + properties: { + stages: ['prospecting', 'qualification', 'needs_analysis', 'proposal', 'negotiation', 'closed_won'], + currentStageField: 'stage', + completionField: 'probability', + }, + }, + { + type: 'record-header', + properties: { + title: '{name}', + subtitle: '{account_name}', + icon: 'trending-up', + fields: ['amount', 'close_date', 'probability', 'owner'], + quickActions: ['edit', 'clone', 'delete', 'change_owner'], + }, + }, + ], + }, + { + name: 'sidebar', + width: 'medium', + components: [ + { + type: 'highlights-panel', + label: 'Key Metrics', + properties: { + fields: ['amount', 'probability', 'expected_revenue', 'close_date', 'stage'], + }, + }, + { + type: 'activity-timeline', + label: 'Activity Timeline', + properties: { + types: ['task', 'event', 'email', 'call'], + limit: 10, + showUpcoming: true, + }, + }, + { + type: 'chatter-feed', + label: 'Collaboration', + properties: { + showPosts: true, + showFiles: true, + }, + }, + ], + }, + { + name: 'main', + components: [ + { + type: 'tabset', + properties: { + tabs: [ + { + label: 'Details', + components: [ + { + type: 'record-detail', + properties: { + sections: [ + { + label: 'Opportunity Information', + fields: ['name', 'account_name', 'type', 'stage', 'probability'], + }, + { + label: 'Financial Details', + fields: ['amount', 'expected_revenue', 'close_date', 'next_step'], + }, + ], + }, + }, + ], + }, + { + label: 'Products', + components: [ + { + type: 'related-list', + properties: { + object: 'opportunity_line_item', + relationField: 'opportunity_id', + columns: ['product_name', 'quantity', 'unit_price', 'total_price'], + allowInlineEdit: true, + }, + }, + ], + }, + { + label: 'Competitors', + components: [ + { + type: 'related-list', + properties: { + object: 'opportunity_competitor', + relationField: 'opportunity_id', + columns: ['competitor_name', 'strengths', 'weaknesses'], + }, + }, + ], + }, + { + label: 'Related', + components: [ + { + type: 'related-lists', + properties: { + lists: [ + { object: 'contact', relationField: 'opportunity_id', label: 'Contact Roles' }, + { object: 'task', relationField: 'related_to_id', label: 'Open Tasks' }, + { object: 'note', relationField: 'parent_id', label: 'Notes' }, + ], + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + name: 'footer', + components: [ + { + type: 'record-footer', + properties: { + fields: ['created_by', 'created_date', 'last_modified_by', 'last_modified_date'], + }, + }, + ], + }, + ], + + isDefault: true, +}; + +/** + * Example 3: Compact Record Page + * Minimal page for quick views + * Use Case: Simple objects, quick popups, mobile views + */ +export const CompactRecordPage: Page = { + name: 'task_compact_page', + label: 'Task Quick View', + description: 'Compact view for tasks', + type: 'record', + object: 'task', + template: 'single-column', + + regions: [ + { + name: 'main', + components: [ + { + type: 'record-detail', + properties: { + sections: [ + { + label: 'Task Details', + fields: ['subject', 'status', 'priority', 'due_date', 'assigned_to'], + }, + ], + }, + }, + ], + }, + ], +}; + +// ============================================================================ +// HOME PAGES (Dashboard/Landing Pages) +// ============================================================================ + +/** + * Example 4: Home Page with Dashboard + * User's home landing page + * Use Case: Default landing page, personalized dashboards + */ +export const UserHomePage: Page = { + name: 'sales_home', + label: 'Sales Home', + description: 'Sales representative home page', + type: 'home', + template: 'two-column', + + regions: [ + { + name: 'left', + width: 'large', + components: [ + { + type: 'dashboard-embed', + label: 'My Performance', + properties: { + dashboardName: 'my_sales_performance', + filters: { + owner: '{current_user}', + }, + }, + }, + { + type: 'chart', + label: 'Pipeline by Stage', + properties: { + chartType: 'funnel', + object: 'opportunity', + groupBy: 'stage', + measureField: 'amount', + filter: { + owner: '{current_user}', + stage: { $nin: ['closed_won', 'closed_lost'] }, + }, + }, + }, + ], + }, + { + name: 'right', + width: 'medium', + components: [ + { + type: 'list-view', + label: 'My Tasks', + properties: { + object: 'task', + viewName: 'my_open_tasks', + filter: { + assigned_to: '{current_user}', + is_completed: false, + }, + limit: 10, + }, + }, + { + type: 'list-view', + label: 'My Opportunities', + properties: { + object: 'opportunity', + viewName: 'my_opportunities', + filter: { + owner: '{current_user}', + close_date: { $lte: '{next_30_days}' }, + }, + limit: 5, + }, + }, + { + type: 'recent-items', + label: 'Recent Records', + properties: { + limit: 10, + }, + }, + ], + }, + ], + + isDefault: true, +}; + +/** + * Example 5: Executive Home Page + * High-level overview for executives + * Use Case: Executive dashboards, KPI monitoring + */ +export const ExecutiveHomePage: Page = { + name: 'executive_home', + label: 'Executive Home', + description: 'Executive dashboard view', + type: 'home', + template: 'three-column', + + regions: [ + { + name: 'header', + components: [ + { + type: 'kpi-banner', + properties: { + kpis: [ + { label: 'Revenue (YTD)', value: '{total_revenue_ytd}', format: 'currency' }, + { label: 'New Customers', value: '{new_customers_qtd}', format: 'number' }, + { label: 'Customer Satisfaction', value: '{csat_score}', format: 'percentage' }, + ], + }, + }, + ], + }, + { + name: 'left', + width: 'medium', + components: [ + { + type: 'chart', + label: 'Revenue Trend', + properties: { + chartType: 'line', + object: 'opportunity', + groupBy: 'close_date', + measureField: 'amount', + dateGranularity: 'month', + }, + }, + ], + }, + { + name: 'middle', + width: 'medium', + components: [ + { + type: 'chart', + label: 'Pipeline by Stage', + properties: { + chartType: 'bar', + object: 'opportunity', + groupBy: 'stage', + measureField: 'amount', + }, + }, + ], + }, + { + name: 'right', + width: 'medium', + components: [ + { + type: 'list-view', + label: 'Top Opportunities', + properties: { + object: 'opportunity', + columns: ['name', 'amount', 'close_date'], + sortBy: 'amount', + sortOrder: 'desc', + limit: 10, + }, + }, + ], + }, + ], +}; + +// ============================================================================ +// APP PAGES (Custom Application Pages) +// ============================================================================ + +/** + * Example 6: Custom Application Page + * Specialized page for custom functionality + * Use Case: Custom tools, calculators, wizards + */ +export const QuoteBuilderPage: Page = { + name: 'quote_builder', + label: 'Quote Builder', + description: 'Interactive quote creation tool', + type: 'app', + template: 'wizard', + + regions: [ + { + name: 'steps', + components: [ + { + type: 'wizard-step', + label: 'Select Account', + properties: { + fields: ['account', 'contact', 'opportunity'], + }, + }, + { + type: 'wizard-step', + label: 'Add Products', + properties: { + component: 'product-selector', + }, + }, + { + type: 'wizard-step', + label: 'Configure Pricing', + properties: { + component: 'pricing-calculator', + }, + }, + { + type: 'wizard-step', + label: 'Review & Submit', + properties: { + component: 'quote-preview', + }, + }, + ], + }, + ], +}; + +/** + * Example 7: Report Builder Page + * Custom report creation interface + * Use Case: Report builders, query tools + */ +export const ReportBuilderPage: Page = { + name: 'report_builder', + label: 'Report Builder', + description: 'Custom report creation tool', + type: 'app', + template: 'sidebar-main', + + regions: [ + { + name: 'sidebar', + width: 'small', + components: [ + { + type: 'field-picker', + label: 'Available Fields', + properties: { + objects: ['account', 'contact', 'opportunity'], + }, + }, + ], + }, + { + name: 'main', + components: [ + { + type: 'report-canvas', + properties: { + allowFilters: true, + allowGrouping: true, + allowCharting: true, + }, + }, + ], + }, + ], +}; + +// ============================================================================ +// UTILITY PAGES (Modal/Popup Pages) +// ============================================================================ + +/** + * Example 8: Utility Bar Page + * Persistent utility sidebar + * Use Case: Quick access tools, calculators, notes + */ +export const UtilityBarPage: Page = { + name: 'utility_bar', + label: 'Utility Bar', + description: 'Quick access tools', + type: 'utility', + template: 'single-column', + + regions: [ + { + name: 'main', + components: [ + { + type: 'quick-notes', + label: 'Notes', + properties: { + allowRichText: true, + }, + }, + { + type: 'calculator', + label: 'Calculator', + }, + { + type: 'recent-items', + label: 'Recent', + properties: { + limit: 5, + }, + }, + ], + }, + ], +}; + +// ============================================================================ +// CONDITIONAL PAGES (Context-Aware Pages) +// ============================================================================ + +/** + * Example 9: Profile-Specific Record Page + * Different layouts for different user profiles + * Use Case: Role-based UI variations + */ +export const SalesRecordPage: Page = { + name: 'account_sales_view', + label: 'Account (Sales View)', + description: 'Account page optimized for sales users', + type: 'record', + object: 'account', + template: 'header-sidebar-main', + + regions: [ + { + name: 'header', + components: [ + { + type: 'record-header', + properties: { + title: '{name}', + subtitle: '{annual_revenue}', + icon: 'briefcase', + fields: ['owner', 'type', 'lead_source'], + }, + }, + ], + }, + { + name: 'sidebar', + width: 'medium', + components: [ + { + type: 'highlights-panel', + label: 'Sales Insights', + properties: { + fields: ['total_opportunities', 'total_revenue', 'last_activity_date'], + }, + }, + { + type: 'activity-composer', + label: 'Log Activity', + properties: { + types: ['call', 'email', 'meeting'], + }, + }, + ], + }, + { + name: 'main', + components: [ + { + type: 'record-detail', + properties: { + sections: [ + { + label: 'Account Information', + fields: ['name', 'type', 'industry', 'annual_revenue'], + }, + ], + }, + }, + { + type: 'related-lists', + properties: { + lists: [ + { object: 'opportunity', relationField: 'account_id', label: 'Opportunities' }, + { object: 'contact', relationField: 'account_id', label: 'Contacts' }, + ], + }, + }, + ], + }, + ], + + assignedProfiles: ['sales_user', 'sales_manager'], +}; + +// ============================================================================ +// EXPORT ALL EXAMPLES +// ============================================================================ + +export const PageExamples = { + // Record Pages + SimpleRecordPage, + AdvancedRecordPage, + CompactRecordPage, + + // Home Pages + UserHomePage, + ExecutiveHomePage, + + // App Pages + QuoteBuilderPage, + ReportBuilderPage, + + // Utility Pages + UtilityBarPage, + + // Conditional Pages + SalesRecordPage, +}; diff --git a/examples/ui/src/theme.examples.ts b/examples/ui/src/theme.examples.ts new file mode 100644 index 000000000..914850e82 --- /dev/null +++ b/examples/ui/src/theme.examples.ts @@ -0,0 +1,710 @@ +// @ts-nocheck +import { Theme } from '@objectstack/spec/ui'; + +/** + * Theme Examples - Demonstrating ObjectStack Theme Protocol + * + * Themes define the visual styling and branding of applications. + * Inspired by Material Design, Tailwind, and Salesforce Lightning Design System. + */ + +// ============================================================================ +// LIGHT THEMES +// ============================================================================ + +/** + * Example 1: Default Light Theme + * Clean, professional light theme + * Use Case: Default theme for most applications + */ +export const DefaultLightTheme: Theme = { + name: 'default_light', + label: 'Default Light', + description: 'Clean and professional light theme', + mode: 'light', + + colors: { + // Brand Colors + primary: '#4169E1', // Royal Blue + secondary: '#9370DB', // Medium Purple + accent: '#FFA500', // Orange + + // Semantic Colors + success: '#00AA00', // Green + warning: '#FFA500', // Orange + error: '#FF0000', // Red + info: '#4169E1', // Blue + + // Neutral Colors + background: '#FFFFFF', + surface: '#F8F9FA', + border: '#E5E7EB', + text: '#1F2937', + textSecondary: '#6B7280', + textDisabled: '#9CA3AF', + + // Interactive States + hover: '#F3F4F6', + active: '#E5E7EB', + focus: '#DBEAFE', + disabled: '#F3F4F6', + }, + + typography: { + fontFamily: { + heading: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + body: '"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + mono: '"Fira Code", "Courier New", monospace', + }, + fontSize: { + xs: '12px', + sm: '14px', + base: '16px', + lg: '18px', + xl: '20px', + '2xl': '24px', + '3xl': '30px', + '4xl': '36px', + }, + fontWeight: { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, + lineHeight: { + tight: 1.25, + normal: 1.5, + relaxed: 1.75, + }, + }, + + spacing: { + xs: '4px', + sm: '8px', + md: '16px', + lg: '24px', + xl: '32px', + '2xl': '48px', + }, + + borderRadius: { + none: '0', + sm: '4px', + md: '8px', + lg: '12px', + full: '9999px', + }, + + shadows: { + none: 'none', + sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)', + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)', + }, + + components: { + button: { + primary: { + backgroundColor: '{colors.primary}', + color: '#FFFFFF', + borderRadius: '{borderRadius.md}', + padding: '{spacing.sm} {spacing.md}', + }, + secondary: { + backgroundColor: 'transparent', + color: '{colors.primary}', + border: '1px solid {colors.primary}', + borderRadius: '{borderRadius.md}', + padding: '{spacing.sm} {spacing.md}', + }, + }, + input: { + borderColor: '{colors.border}', + borderRadius: '{borderRadius.md}', + padding: '{spacing.sm}', + focusBorderColor: '{colors.primary}', + }, + card: { + backgroundColor: '{colors.surface}', + borderRadius: '{borderRadius.lg}', + padding: '{spacing.lg}', + shadow: '{shadows.md}', + }, + }, +}; + +/** + * Example 2: Professional Blue Theme + * Corporate-friendly blue theme + * Use Case: Enterprise applications, financial services + */ +export const ProfessionalBlueTheme: Theme = { + name: 'professional_blue', + label: 'Professional Blue', + description: 'Corporate blue theme for enterprise applications', + mode: 'light', + + colors: { + primary: '#0066CC', + secondary: '#003D7A', + accent: '#00A3E0', + success: '#008A00', + warning: '#FF9900', + error: '#CC0000', + info: '#0066CC', + background: '#FFFFFF', + surface: '#F5F8FA', + border: '#D1D5DB', + text: '#1A1A1A', + textSecondary: '#4A5568', + textDisabled: '#A0AEC0', + hover: '#EDF2F7', + active: '#E2E8F0', + focus: '#DBEAFE', + disabled: '#F7FAFC', + }, + + typography: { + fontFamily: { + heading: '"Open Sans", sans-serif', + body: '"Lato", sans-serif', + mono: '"Source Code Pro", monospace', + }, + fontSize: { + xs: '11px', + sm: '13px', + base: '15px', + lg: '17px', + xl: '20px', + '2xl': '24px', + '3xl': '28px', + '4xl': '32px', + }, + fontWeight: { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, + lineHeight: { + tight: 1.2, + normal: 1.5, + relaxed: 1.8, + }, + }, + + borderRadius: { + none: '0', + sm: '2px', + md: '4px', + lg: '6px', + full: '9999px', + }, + + shadows: { + sm: '0 1px 3px 0 rgba(0, 0, 0, 0.1)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)', + }, +}; + +// ============================================================================ +// DARK THEMES +// ============================================================================ + +/** + * Example 3: Dark Theme + * Modern dark theme for reduced eye strain + * Use Case: Late-night work, preference for dark UIs + */ +export const DarkTheme: Theme = { + name: 'dark', + label: 'Dark', + description: 'Modern dark theme', + mode: 'dark', + + colors: { + primary: '#60A5FA', // Light Blue + secondary: '#A78BFA', // Light Purple + accent: '#FBBF24', // Amber + success: '#34D399', // Green + warning: '#FBBF24', // Amber + error: '#F87171', // Red + info: '#60A5FA', // Light Blue + background: '#111827', // Dark Gray + surface: '#1F2937', // Lighter Dark Gray + border: '#374151', // Border Gray + text: '#F9FAFB', // Near White + textSecondary: '#D1D5DB', // Light Gray + textDisabled: '#6B7280', // Medium Gray + hover: '#374151', + active: '#4B5563', + focus: '#1E3A5F', + disabled: '#374151', + }, + + typography: { + fontFamily: { + heading: '"Inter", sans-serif', + body: '"Roboto", sans-serif', + mono: '"Fira Code", monospace', + }, + fontSize: { + xs: '12px', + sm: '14px', + base: '16px', + lg: '18px', + xl: '20px', + '2xl': '24px', + '3xl': '30px', + '4xl': '36px', + }, + fontWeight: { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, + lineHeight: { + tight: 1.25, + normal: 1.5, + relaxed: 1.75, + }, + }, + + borderRadius: { + none: '0', + sm: '4px', + md: '8px', + lg: '12px', + full: '9999px', + }, + + shadows: { + sm: '0 1px 2px 0 rgba(0, 0, 0, 0.3)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.4)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.5)', + }, + + components: { + button: { + primary: { + backgroundColor: '{colors.primary}', + color: '{colors.background}', + borderRadius: '{borderRadius.md}', + }, + secondary: { + backgroundColor: 'transparent', + color: '{colors.primary}', + border: '1px solid {colors.primary}', + borderRadius: '{borderRadius.md}', + }, + }, + input: { + backgroundColor: '{colors.surface}', + borderColor: '{colors.border}', + color: '{colors.text}', + borderRadius: '{borderRadius.md}', + }, + card: { + backgroundColor: '{colors.surface}', + borderRadius: '{borderRadius.lg}', + border: '1px solid {colors.border}', + }, + }, +}; + +// ============================================================================ +// COLORFUL THEMES +// ============================================================================ + +/** + * Example 4: Vibrant Theme + * Colorful, energetic theme + * Use Case: Creative teams, marketing, design agencies + */ +export const VibrantTheme: Theme = { + name: 'vibrant', + label: 'Vibrant', + description: 'Energetic and colorful theme', + mode: 'light', + + colors: { + primary: '#7C3AED', // Vivid Purple + secondary: '#EC4899', // Pink + accent: '#F59E0B', // Amber + success: '#10B981', // Emerald + warning: '#F59E0B', // Amber + error: '#EF4444', // Red + info: '#3B82F6', // Blue + background: '#FFFFFF', + surface: '#FAFAFA', + border: '#E5E7EB', + text: '#1F2937', + textSecondary: '#6B7280', + textDisabled: '#9CA3AF', + hover: '#F3F4F6', + active: '#E5E7EB', + focus: '#F3E8FF', + disabled: '#F3F4F6', + }, + + typography: { + fontFamily: { + heading: '"Poppins", sans-serif', + body: '"Nunito", sans-serif', + mono: '"JetBrains Mono", monospace', + }, + fontSize: { + xs: '12px', + sm: '14px', + base: '16px', + lg: '18px', + xl: '20px', + '2xl': '24px', + '3xl': '30px', + '4xl': '36px', + }, + fontWeight: { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, + lineHeight: { + tight: 1.25, + normal: 1.5, + relaxed: 1.75, + }, + }, + + borderRadius: { + none: '0', + sm: '6px', + md: '12px', + lg: '16px', + full: '9999px', + }, + + shadows: { + sm: '0 2px 4px 0 rgba(124, 58, 237, 0.1)', + md: '0 4px 8px -1px rgba(124, 58, 237, 0.15)', + lg: '0 12px 20px -3px rgba(124, 58, 237, 0.2)', + }, +}; + +/** + * Example 5: Nature Green Theme + * Calm, nature-inspired green theme + * Use Case: Sustainability, environmental, health applications + */ +export const NatureGreenTheme: Theme = { + name: 'nature_green', + label: 'Nature Green', + description: 'Calm nature-inspired theme', + mode: 'light', + + colors: { + primary: '#059669', // Emerald + secondary: '#10B981', // Green + accent: '#84CC16', // Lime + success: '#22C55E', // Green + warning: '#EAB308', // Yellow + error: '#DC2626', // Red + info: '#06B6D4', // Cyan + background: '#FFFFFF', + surface: '#F0FDF4', // Green tint + border: '#D1FAE5', + text: '#064E3B', // Dark Green + textSecondary: '#047857', + textDisabled: '#6EE7B7', + hover: '#ECFDF5', + active: '#D1FAE5', + focus: '#A7F3D0', + disabled: '#F0FDF4', + }, + + typography: { + fontFamily: { + heading: '"Merriweather", serif', + body: '"Lato", sans-serif', + mono: '"Courier Prime", monospace', + }, + fontSize: { + xs: '12px', + sm: '14px', + base: '16px', + lg: '18px', + xl: '20px', + '2xl': '24px', + '3xl': '30px', + '4xl': '36px', + }, + fontWeight: { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, + lineHeight: { + tight: 1.25, + normal: 1.5, + relaxed: 1.75, + }, + }, + + borderRadius: { + none: '0', + sm: '4px', + md: '8px', + lg: '12px', + full: '9999px', + }, + + shadows: { + sm: '0 1px 2px 0 rgba(5, 150, 105, 0.1)', + md: '0 4px 6px -1px rgba(5, 150, 105, 0.15)', + lg: '0 10px 15px -3px rgba(5, 150, 105, 0.2)', + }, +}; + +// ============================================================================ +// MINIMAL THEMES +// ============================================================================ + +/** + * Example 6: Minimal Monochrome Theme + * Ultra-minimal black and white theme + * Use Case: Focus on content, minimalist aesthetics + */ +export const MinimalMonochromeTheme: Theme = { + name: 'minimal_monochrome', + label: 'Minimal Monochrome', + description: 'Ultra-minimal black and white theme', + mode: 'light', + + colors: { + primary: '#000000', + secondary: '#404040', + accent: '#666666', + success: '#000000', + warning: '#000000', + error: '#000000', + info: '#000000', + background: '#FFFFFF', + surface: '#FAFAFA', + border: '#E0E0E0', + text: '#000000', + textSecondary: '#666666', + textDisabled: '#CCCCCC', + hover: '#F5F5F5', + active: '#E0E0E0', + focus: '#EEEEEE', + disabled: '#FAFAFA', + }, + + typography: { + fontFamily: { + heading: '"Helvetica Neue", Helvetica, Arial, sans-serif', + body: '"Helvetica Neue", Helvetica, Arial, sans-serif', + mono: '"Monaco", "Courier New", monospace', + }, + fontSize: { + xs: '12px', + sm: '14px', + base: '16px', + lg: '18px', + xl: '20px', + '2xl': '24px', + '3xl': '30px', + '4xl': '36px', + }, + fontWeight: { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, + lineHeight: { + tight: 1.25, + normal: 1.5, + relaxed: 1.75, + }, + }, + + borderRadius: { + none: '0', + sm: '0', + md: '0', + lg: '0', + full: '0', + }, + + shadows: { + sm: 'none', + md: 'none', + lg: 'none', + }, + + components: { + button: { + primary: { + backgroundColor: '#000000', + color: '#FFFFFF', + borderRadius: '0', + padding: '12px 24px', + border: 'none', + }, + secondary: { + backgroundColor: 'transparent', + color: '#000000', + border: '2px solid #000000', + borderRadius: '0', + padding: '10px 22px', + }, + }, + input: { + borderColor: '#000000', + borderRadius: '0', + border: '2px solid #000000', + padding: '10px', + }, + card: { + backgroundColor: '#FFFFFF', + border: '1px solid #E0E0E0', + borderRadius: '0', + padding: '24px', + }, + }, +}; + +// ============================================================================ +// HIGH CONTRAST THEMES (Accessibility) +// ============================================================================ + +/** + * Example 7: High Contrast Theme + * WCAG AAA compliant high contrast theme + * Use Case: Accessibility, visual impairments + */ +export const HighContrastTheme: Theme = { + name: 'high_contrast', + label: 'High Contrast', + description: 'WCAG AAA compliant high contrast theme', + mode: 'light', + + colors: { + primary: '#0000FF', // Pure Blue + secondary: '#000080', // Navy + accent: '#FF8C00', // Dark Orange + success: '#008000', // Pure Green + warning: '#FF8C00', // Dark Orange + error: '#FF0000', // Pure Red + info: '#0000FF', // Pure Blue + background: '#FFFFFF', + surface: '#FFFFFF', + border: '#000000', + text: '#000000', + textSecondary: '#000000', + textDisabled: '#767676', + hover: '#FFFFCC', // Light Yellow + active: '#FFFF00', // Yellow + focus: '#FFFF00', // Yellow + disabled: '#C0C0C0', // Silver + }, + + typography: { + fontFamily: { + heading: '"Arial", sans-serif', + body: '"Arial", sans-serif', + mono: '"Courier New", monospace', + }, + fontSize: { + xs: '14px', // Larger minimum for accessibility + sm: '16px', + base: '18px', + lg: '20px', + xl: '24px', + '2xl': '28px', + '3xl': '32px', + '4xl': '40px', + }, + fontWeight: { + normal: 400, + medium: 600, + semibold: 700, + bold: 900, + }, + lineHeight: { + tight: 1.4, + normal: 1.6, + relaxed: 1.8, + }, + }, + + borderRadius: { + none: '0', + sm: '0', + md: '0', + lg: '0', + full: '0', + }, + + shadows: { + sm: 'none', + md: 'none', + lg: 'none', + }, + + components: { + button: { + primary: { + backgroundColor: '#0000FF', + color: '#FFFFFF', + border: '3px solid #000000', + borderRadius: '0', + padding: '12px 24px', + fontWeight: 700, + }, + secondary: { + backgroundColor: '#FFFFFF', + color: '#000000', + border: '3px solid #000000', + borderRadius: '0', + padding: '12px 24px', + fontWeight: 700, + }, + }, + input: { + backgroundColor: '#FFFFFF', + borderColor: '#000000', + border: '3px solid #000000', + borderRadius: '0', + color: '#000000', + padding: '12px', + fontSize: '18px', + }, + card: { + backgroundColor: '#FFFFFF', + border: '3px solid #000000', + borderRadius: '0', + padding: '24px', + }, + }, +}; + +// ============================================================================ +// EXPORT ALL EXAMPLES +// ============================================================================ + +export const ThemeExamples = { + DefaultLightTheme, + ProfessionalBlueTheme, + DarkTheme, + VibrantTheme, + NatureGreenTheme, + MinimalMonochromeTheme, + HighContrastTheme, +}; diff --git a/examples/ui/src/view.examples.ts b/examples/ui/src/view.examples.ts new file mode 100644 index 000000000..38892eb98 --- /dev/null +++ b/examples/ui/src/view.examples.ts @@ -0,0 +1,544 @@ +// @ts-nocheck +import { ListView, FormView } from '@objectstack/spec/ui'; + +/** + * View Examples - Demonstrating ObjectStack View Protocol + * + * Views define how data is displayed and interacted with. + * ObjectStack supports multiple view types for different use cases. + */ + +// ============================================================================ +// GRID VIEWS (Table/List Views) +// ============================================================================ + +/** + * Example 1: Basic Grid View + * Simple table view with essential columns and sorting + * Use Case: Quick lists, simple data tables + */ +export const BasicGridView: ListView = { + type: 'grid', + columns: ['name', 'status', 'created_date'], + sort: [{ field: 'created_date', order: 'desc' }], +}; + +/** + * Example 2: Advanced Grid View with Detailed Column Configuration + * Demonstrates column customization, widths, alignment, and types + * Use Case: Complex business tables requiring precise layout control + */ +export const AdvancedGridView: ListView = { + type: 'grid', + columns: [ + { + field: 'name', + label: 'Account Name', + width: 250, + sortable: true, + resizable: true, + }, + { + field: 'annual_revenue', + label: 'Revenue', + width: 150, + align: 'right', + type: 'currency', + sortable: true, + }, + { + field: 'industry', + label: 'Industry', + width: 180, + sortable: true, + }, + { + field: 'owner', + label: 'Owner', + width: 150, + type: 'lookup', + }, + { + field: 'is_active', + label: 'Active', + width: 80, + align: 'center', + type: 'boolean', + }, + ], + filter: [ + { field: 'is_active', operator: '$eq', value: true }, + ], + sort: [{ field: 'annual_revenue', order: 'desc' }], + resizable: true, + striped: true, + bordered: true, + selection: { + type: 'multiple', + }, + pagination: { + pageSize: 25, + pageSizeOptions: [10, 25, 50, 100], + }, +}; + +/** + * Example 3: Grid View with Search and Filters + * Demonstrates searchable columns and complex filtering + * Use Case: Data discovery, user-driven filtering + */ +export const SearchableGridView: ListView = { + type: 'grid', + columns: ['subject', 'priority', 'status', 'due_date', 'assigned_to'], + searchableFields: ['subject', 'description', 'assigned_to'], + filter: [ + { + $or: [ + { field: 'status', operator: '$eq', value: 'open' }, + { field: 'status', operator: '$eq', value: 'in_progress' }, + ], + }, + ], + sort: [ + { field: 'priority', order: 'desc' }, + { field: 'due_date', order: 'asc' }, + ], + pagination: { + pageSize: 50, + }, +}; + +/** + * Example 4: Grid View with Custom API Data Source + * Demonstrates using external API instead of ObjectStack metadata + * Use Case: Integration with third-party systems, legacy APIs + */ +export const ExternalApiGridView: ListView = { + type: 'grid', + data: { + provider: 'api', + read: { + url: 'https://api.example.com/external/customers', + method: 'GET', + headers: { + 'Authorization': 'Bearer {token}', + 'X-API-Version': '2.0', + }, + params: { + include: 'address,contacts', + status: 'active', + }, + }, + }, + columns: [ + { field: 'id', label: 'Customer ID', width: 100 }, + { field: 'company_name', label: 'Company', width: 200 }, + { field: 'contact_email', label: 'Email', width: 200 }, + { field: 'total_orders', label: 'Orders', width: 100, align: 'right' }, + ], +}; + +/** + * Example 5: Grid View with Static Data (Value Provider) + * Demonstrates hardcoded data for testing or simple lists + * Use Case: Static configuration lists, mock data, testing + */ +export const StaticDataGridView: ListView = { + type: 'grid', + data: { + provider: 'value', + items: [ + { id: 1, name: 'Priority 1', color: '#FF0000', order: 1 }, + { id: 2, name: 'Priority 2', color: '#FFA500', order: 2 }, + { id: 3, name: 'Priority 3', color: '#FFFF00', order: 3 }, + { id: 4, name: 'Priority 4', color: '#00FF00', order: 4 }, + { id: 5, name: 'Priority 5', color: '#0000FF', order: 5 }, + ], + }, + columns: [ + { field: 'order', label: '#', width: 60 }, + { field: 'name', label: 'Priority Level', width: 150 }, + { field: 'color', label: 'Color Code', width: 120, type: 'color' }, + ], +}; + +// ============================================================================ +// KANBAN VIEWS (Board/Card Views) +// ============================================================================ + +/** + * Example 6: Kanban Board View + * Card-based workflow visualization grouped by status/stage + * Use Case: Agile workflows, sales pipelines, support tickets + * Inspired by: Salesforce Kanban, Trello, Jira boards + */ +export const OpportunityKanbanView: ListView = { + type: 'kanban', + data: { + provider: 'object', + object: 'opportunity', + }, + columns: ['name', 'amount', 'close_date', 'account_name'], + filter: [ + { field: 'stage', operator: '$nin', value: ['closed_won', 'closed_lost'] }, + ], + kanban: { + groupByField: 'stage', // Creates columns for each stage value + summarizeField: 'amount', // Shows sum at top of each column + columns: ['name', 'amount', 'account_name', 'close_date'], // Fields shown on cards + }, +}; + +/** + * Example 7: Support Ticket Kanban + * Demonstrates Kanban for support/service workflows + * Use Case: Customer support, issue tracking + */ +export const SupportTicketKanban: ListView = { + type: 'kanban', + data: { + provider: 'object', + object: 'case', + }, + columns: ['case_number', 'subject', 'priority', 'assigned_to'], + filter: [ + { field: 'is_closed', operator: '$eq', value: false }, + ], + kanban: { + groupByField: 'status', // new, assigned, in_progress, waiting, resolved + columns: ['case_number', 'subject', 'priority', 'customer_name'], + }, + sort: [{ field: 'priority', order: 'desc' }], +}; + +// ============================================================================ +// CALENDAR VIEWS (Time-based Views) +// ============================================================================ + +/** + * Example 8: Calendar View for Events + * Time-based visualization of events and activities + * Use Case: Event management, scheduling, appointments + * Inspired by: Salesforce Calendar, Google Calendar + */ +export const EventCalendarView: ListView = { + type: 'calendar', + data: { + provider: 'object', + object: 'event', + }, + columns: ['title', 'location', 'attendees'], + calendar: { + startDateField: 'start_date', + endDateField: 'end_date', + titleField: 'title', + colorField: 'event_type', // Color code by event type + }, +}; + +/** + * Example 9: Task Calendar View + * Calendar for tasks and deadlines (single date) + * Use Case: Task management, deadline tracking + */ +export const TaskCalendarView: ListView = { + type: 'calendar', + data: { + provider: 'object', + object: 'task', + }, + columns: ['subject', 'priority', 'assigned_to'], + filter: [ + { field: 'is_completed', operator: '$eq', value: false }, + ], + calendar: { + startDateField: 'due_date', + titleField: 'subject', + colorField: 'priority', + }, +}; + +// ============================================================================ +// GANTT VIEWS (Project Timeline Views) +// ============================================================================ + +/** + * Example 10: Project Gantt View + * Timeline visualization for project planning + * Use Case: Project management, timeline planning, dependencies + * Inspired by: MS Project, Asana Timeline, Monday.com Gantt + */ +export const ProjectGanttView: ListView = { + type: 'gantt', + data: { + provider: 'object', + object: 'project_task', + }, + columns: ['name', 'assigned_to', 'status', 'progress'], + filter: [ + { field: 'project_id', operator: '$eq', value: '{current_project}' }, + ], + gantt: { + startDateField: 'start_date', + endDateField: 'end_date', + titleField: 'name', + progressField: 'progress_percentage', // 0-100 + dependenciesField: 'dependencies', // Array of task IDs + }, + sort: [{ field: 'start_date', order: 'asc' }], +}; + +/** + * Example 11: Release Planning Gantt + * Timeline for software release planning + * Use Case: Agile release planning, sprint visualization + */ +export const ReleaseGanttView: ListView = { + type: 'gantt', + data: { + provider: 'object', + object: 'sprint', + }, + columns: ['name', 'team', 'status', 'story_points'], + gantt: { + startDateField: 'start_date', + endDateField: 'end_date', + titleField: 'name', + progressField: 'completion_percentage', + }, +}; + +// ============================================================================ +// FORM VIEWS (Edit/Create Views) +// ============================================================================ + +/** + * Example 12: Simple Form View + * Single-section form for basic data entry + * Use Case: Simple objects with few fields + */ +export const SimpleFormView: FormView = { + type: 'simple', + sections: [ + { + label: 'Contact Information', + columns: '2', + fields: ['first_name', 'last_name', 'email', 'phone'], + }, + { + label: 'Address', + columns: '1', + fields: ['street', 'city', 'state', 'postal_code', 'country'], + }, + ], +}; + +/** + * Example 13: Advanced Form View with Field Configuration + * Detailed form with field customization and validation + * Use Case: Complex forms requiring precise control + */ +export const AdvancedFormView: FormView = { + type: 'simple', + sections: [ + { + label: 'Account Details', + collapsible: false, + columns: '2', + fields: [ + { + field: 'name', + label: 'Account Name', + required: true, + colSpan: 2, + placeholder: 'Enter company name', + }, + { + field: 'account_type', + label: 'Type', + required: true, + helpText: 'Select the type of account', + }, + { + field: 'industry', + label: 'Industry', + }, + { + field: 'annual_revenue', + label: 'Annual Revenue', + colSpan: 2, + helpText: 'Estimated annual revenue in USD', + }, + ], + }, + { + label: 'Contact Information', + collapsible: true, + collapsed: false, + columns: '2', + fields: [ + { field: 'phone', label: 'Phone' }, + { field: 'website', label: 'Website' }, + { field: 'billing_address', colSpan: 2 }, + ], + }, + ], +}; + +/** + * Example 14: Tabbed Form View + * Multi-tab form for organizing many fields + * Use Case: Complex objects with many field groups + * Inspired by: Salesforce Lightning Record Pages + */ +export const TabbedFormView: FormView = { + type: 'tabbed', + sections: [ + { + label: 'Overview', + columns: '2', + fields: ['name', 'status', 'owner', 'priority'], + }, + { + label: 'Details', + columns: '2', + fields: ['description', 'notes', 'attachments'], + }, + { + label: 'Related Information', + columns: '1', + fields: ['related_accounts', 'related_contacts', 'related_opportunities'], + }, + ], +}; + +/** + * Example 15: Wizard Form View + * Step-by-step form for guided processes + * Use Case: Onboarding, complex workflows, guided setup + */ +export const WizardFormView: FormView = { + type: 'wizard', + sections: [ + { + label: 'Step 1: Basic Information', + columns: '1', + fields: ['company_name', 'company_type', 'industry'], + }, + { + label: 'Step 2: Contact Details', + columns: '2', + fields: ['primary_contact', 'email', 'phone'], + }, + { + label: 'Step 3: Preferences', + columns: '2', + fields: ['timezone', 'language', 'currency'], + }, + { + label: 'Step 4: Review & Submit', + columns: '1', + fields: ['terms_accepted', 'privacy_accepted'], + }, + ], +}; + +/** + * Example 16: Form View with Conditional Fields + * Demonstrates field visibility based on other field values + * Use Case: Dynamic forms, cascading fields + */ +export const ConditionalFormView: FormView = { + type: 'simple', + sections: [ + { + label: 'Lead Information', + columns: '2', + fields: [ + { field: 'name', required: true }, + { field: 'company', required: true }, + { field: 'lead_source' }, + { + field: 'lead_source_detail', + visibleOn: 'lead_source = "other"', + helpText: 'Please specify the lead source', + }, + { field: 'status' }, + { + field: 'converted_account', + visibleOn: 'status = "converted"', + readonly: true, + }, + { + field: 'converted_contact', + visibleOn: 'status = "converted"', + readonly: true, + }, + ], + }, + ], +}; + +/** + * Example 17: Form View with Custom API Data Source + * Form that submits to external API instead of ObjectStack + * Use Case: Integration with legacy systems, external services + */ +export const ExternalApiFormView: FormView = { + type: 'simple', + data: { + provider: 'api', + read: { + url: 'https://api.example.com/users/{id}', + method: 'GET', + }, + write: { + url: 'https://api.example.com/users/{id}', + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer {token}', + }, + }, + }, + sections: [ + { + label: 'User Profile', + columns: '2', + fields: ['username', 'email', 'first_name', 'last_name', 'bio'], + }, + ], +}; + +// ============================================================================ +// EXPORT ALL EXAMPLES +// ============================================================================ + +export const ViewExamples = { + // Grid Views + BasicGridView, + AdvancedGridView, + SearchableGridView, + ExternalApiGridView, + StaticDataGridView, + + // Kanban Views + OpportunityKanbanView, + SupportTicketKanban, + + // Calendar Views + EventCalendarView, + TaskCalendarView, + + // Gantt Views + ProjectGanttView, + ReleaseGanttView, + + // Form Views + SimpleFormView, + AdvancedFormView, + TabbedFormView, + WizardFormView, + ConditionalFormView, + ExternalApiFormView, +}; diff --git a/examples/ui/tsconfig.json b/examples/ui/tsconfig.json new file mode 100644 index 000000000..3e70f6718 --- /dev/null +++ b/examples/ui/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "node16", + "lib": ["ES2020"], + "declaration": true, + "declarationMap": true, + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node16", + "resolveJsonModule": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b4024875..54a8e7373 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,6 +227,16 @@ importers: specifier: ^5.0.0 version: 5.9.3 + examples/ui: + dependencies: + '@objectstack/spec': + specifier: workspace:* + version: link:../../packages/spec + devDependencies: + typescript: + specifier: ^5.0.0 + version: 5.9.3 + packages/client: dependencies: '@objectstack/spec': From 22d235e1859bdcdb55fc6e3170fde3c081827a02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 08:13:11 +0000 Subject: [PATCH 3/4] Add custom components and React renderer examples Added two new example folders: 1. custom-components/ - Shows how to implement custom React components - CustomButton.tsx - Flexible button with variants and theming - CustomDataGrid.tsx - Advanced grid with sorting, filtering, pagination - Component registration system 2. react-renderer/ - Shows how to render UI metadata with React - PageRenderer - Renders Page metadata - ComponentRenderer - Renders individual components - Template expression resolver for data binding - SimpleApp example showing complete integration Both examples include detailed READMEs with usage instructions Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/ui/README.md | 82 ++- examples/ui/custom-components/README.md | 435 +++++++++++++++ examples/ui/custom-components/package.json | 26 + .../src/components/CustomButton.tsx | 125 +++++ .../src/components/CustomDataGrid.tsx | 245 ++++++++ examples/ui/custom-components/src/index.ts | 14 + examples/ui/custom-components/src/registry.ts | 52 ++ examples/ui/custom-components/tsconfig.json | 21 + examples/ui/react-renderer/README.md | 521 ++++++++++++++++++ examples/ui/react-renderer/package.json | 25 + .../react-renderer/src/examples/SimpleApp.tsx | 111 ++++ examples/ui/react-renderer/src/index.ts | 10 + .../src/renderers/ComponentRenderer.tsx | 52 ++ .../src/renderers/PageRenderer.tsx | 76 +++ .../src/utils/templateResolver.ts | 30 + examples/ui/react-renderer/tsconfig.json | 21 + 16 files changed, 1835 insertions(+), 11 deletions(-) create mode 100644 examples/ui/custom-components/README.md create mode 100644 examples/ui/custom-components/package.json create mode 100644 examples/ui/custom-components/src/components/CustomButton.tsx create mode 100644 examples/ui/custom-components/src/components/CustomDataGrid.tsx create mode 100644 examples/ui/custom-components/src/index.ts create mode 100644 examples/ui/custom-components/src/registry.ts create mode 100644 examples/ui/custom-components/tsconfig.json create mode 100644 examples/ui/react-renderer/README.md create mode 100644 examples/ui/react-renderer/package.json create mode 100644 examples/ui/react-renderer/src/examples/SimpleApp.tsx create mode 100644 examples/ui/react-renderer/src/index.ts create mode 100644 examples/ui/react-renderer/src/renderers/ComponentRenderer.tsx create mode 100644 examples/ui/react-renderer/src/renderers/PageRenderer.tsx create mode 100644 examples/ui/react-renderer/src/utils/templateResolver.ts create mode 100644 examples/ui/react-renderer/tsconfig.json diff --git a/examples/ui/README.md b/examples/ui/README.md index 65ad5dd33..1bce274d3 100644 --- a/examples/ui/README.md +++ b/examples/ui/README.md @@ -4,7 +4,15 @@ This directory contains comprehensive examples demonstrating the **UI Protocol** ## πŸ“š What's Inside -This example package demonstrates all major UI components of the ObjectStack Protocol: +This package contains three types of examples: + +1. **Metadata Examples** (`src/*.examples.ts`) - JSON/TypeScript configurations defining UI structure +2. **Custom Components** (`custom-components/`) - React implementations of custom UI components +3. **React Renderer** (`react-renderer/`) - How to render UI metadata using React + +### Metadata Examples (`src/`) + +These demonstrate all major UI components of the ObjectStack Protocol: ### 1. **Views** (`view.examples.ts`) Different ways to display and interact with data: @@ -261,18 +269,70 @@ navigation: [ ``` examples/ui/ -β”œβ”€β”€ package.json # Package configuration -β”œβ”€β”€ tsconfig.json # TypeScript configuration -β”œβ”€β”€ README.md # This file -└── src/ - β”œβ”€β”€ view.examples.ts # List, Form, Kanban, Calendar views - β”œβ”€β”€ page.examples.ts # Record, Home, App pages - β”œβ”€β”€ dashboard.examples.ts # Widgets and analytics - β”œβ”€β”€ action.examples.ts # Buttons and interactions - β”œβ”€β”€ app.examples.ts # Application structure - └── theme.examples.ts # Visual styling +β”œβ”€β”€ package.json # Package configuration +β”œβ”€β”€ tsconfig.json # TypeScript configuration +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ src/ # Metadata examples (JSON/TypeScript) +β”‚ β”œβ”€β”€ view.examples.ts # List, Form, Kanban, Calendar views +β”‚ β”œβ”€β”€ page.examples.ts # Record, Home, App pages +β”‚ β”œβ”€β”€ dashboard.examples.ts # Widgets and analytics +β”‚ β”œβ”€β”€ action.examples.ts # Buttons and interactions +β”‚ β”œβ”€β”€ app.examples.ts # Application structure +β”‚ └── theme.examples.ts # Visual styling +β”œβ”€β”€ custom-components/ # React component implementations +β”‚ β”œβ”€β”€ README.md +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ components/ +β”‚ β”‚ β”‚ β”œβ”€β”€ CustomButton.tsx +β”‚ β”‚ β”‚ └── CustomDataGrid.tsx +β”‚ β”‚ └── registry.ts # Component registration +β”‚ └── package.json +└── react-renderer/ # React renderer for metadata + β”œβ”€β”€ README.md + β”œβ”€β”€ src/ + β”‚ β”œβ”€β”€ renderers/ + β”‚ β”‚ β”œβ”€β”€ PageRenderer.tsx + β”‚ β”‚ └── ComponentRenderer.tsx + β”‚ β”œβ”€β”€ utils/ + β”‚ β”‚ └── templateResolver.ts + β”‚ └── examples/ + β”‚ └── SimpleApp.tsx + └── package.json +``` + +## πŸš€ Quick Start + +### 1. View Metadata Examples + +The TypeScript examples in `src/` show the metadata structure: + +```bash +# These are TypeScript files that demonstrate the protocol +cat src/view.examples.ts +cat src/app.examples.ts +``` + +### 2. Custom Components + +See how to implement custom React components: + +```bash +cd custom-components +npm install +npm run dev +``` + +### 3. React Renderer + +See how to render metadata with React: + +```bash +cd react-renderer +npm install +npm run dev ``` + ## 🀝 Related Examples - **`examples/crm`**: Full CRM application using these UI patterns diff --git a/examples/ui/custom-components/README.md b/examples/ui/custom-components/README.md new file mode 100644 index 000000000..10d7b6bca --- /dev/null +++ b/examples/ui/custom-components/README.md @@ -0,0 +1,435 @@ +# Custom UI Components Example + +This example demonstrates how to implement custom UI components for ObjectStack. + +## Overview + +ObjectStack UI Protocol defines metadata for UI components, but the actual implementation is done by frontend frameworks like React, Vue, or Angular. This example shows how to create custom React components that integrate with the ObjectStack UI system. + +## Structure + +``` +custom-components/ +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ package.json # Dependencies +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ components/ # Custom component implementations +β”‚ β”‚ β”œβ”€β”€ CustomButton.tsx # Custom button component +β”‚ β”‚ β”œβ”€β”€ CustomDataGrid.tsx # Custom data grid component +β”‚ β”‚ β”œβ”€β”€ CustomChart.tsx # Custom chart component +β”‚ β”‚ └── CustomForm.tsx # Custom form component +β”‚ β”œβ”€β”€ registry.ts # Component registration +β”‚ └── index.ts # Main exports +└── tsconfig.json +``` + +## Key Concepts + +### 1. Component Props Interface + +Every custom component receives standardized props from the ObjectStack renderer: + +```typescript +interface ComponentProps { + // Component configuration from metadata + properties: Record; + + // Current data context + data?: any; + + // Callbacks for user interactions + onChange?: (value: any) => void; + onAction?: (action: string, params?: any) => void; + + // Theme and styling + theme?: Theme; +} +``` + +### 2. Component Registration + +Components are registered in a component registry that maps component type names to implementations: + +```typescript +import { ComponentRegistry } from '@objectstack/ui-renderer'; + +ComponentRegistry.register('custom-button', CustomButton); +ComponentRegistry.register('custom-data-grid', CustomDataGrid); +``` + +### 3. Metadata Integration + +Components are referenced in UI metadata by their registered type name: + +```typescript +const page: Page = { + name: 'custom_page', + type: 'app', + regions: [ + { + name: 'main', + components: [ + { + type: 'custom-button', // References registered component + properties: { + label: 'Click Me', + variant: 'primary', + }, + }, + ], + }, + ], +}; +``` + +## Components Included + +### CustomButton + +A customizable button component with various styles and interaction handlers. + +**Features:** +- Multiple variants (primary, secondary, success, danger) +- Icon support +- Loading states +- Disabled states + +**Usage:** +```typescript +{ + type: 'custom-button', + properties: { + label: 'Save', + variant: 'primary', + icon: 'save', + disabled: false, + loading: false, + }, +} +``` + +### CustomDataGrid + +An advanced data grid with sorting, filtering, and pagination. + +**Features:** +- Column configuration +- Row selection +- Sorting and filtering +- Pagination +- Custom cell renderers +- Inline editing + +**Usage:** +```typescript +{ + type: 'custom-data-grid', + properties: { + columns: [ + { field: 'name', label: 'Name', sortable: true }, + { field: 'email', label: 'Email', sortable: true }, + { field: 'status', label: 'Status', filterable: true }, + ], + data: [...], + selectable: true, + pageSize: 25, + }, +} +``` + +### CustomChart + +A chart component supporting multiple visualization types. + +**Features:** +- Multiple chart types (bar, line, pie, donut) +- Interactive tooltips +- Legend customization +- Responsive design + +**Usage:** +```typescript +{ + type: 'custom-chart', + properties: { + type: 'bar', + data: { + labels: ['Jan', 'Feb', 'Mar'], + datasets: [{ + label: 'Revenue', + data: [1000, 1500, 1200], + }], + }, + options: { + responsive: true, + legend: { position: 'bottom' }, + }, + }, +} +``` + +### CustomForm + +A dynamic form builder that renders fields based on metadata. + +**Features:** +- Dynamic field rendering +- Validation +- Conditional fields +- Multi-step forms + +**Usage:** +```typescript +{ + type: 'custom-form', + properties: { + fields: [ + { name: 'name', type: 'text', required: true }, + { name: 'email', type: 'email', required: true }, + { name: 'phone', type: 'tel' }, + ], + onSubmit: 'handleSubmit', + }, +} +``` + +## Installation + +```bash +cd examples/ui/custom-components +npm install +``` + +## Development + +```bash +# Start development server +npm run dev + +# Build for production +npm run build + +# Run tests +npm test +``` + +## Best Practices + +### 1. Type Safety + +Always define TypeScript interfaces for your component props: + +```typescript +interface CustomButtonProps { + label: string; + variant?: 'primary' | 'secondary' | 'success' | 'danger'; + icon?: string; + disabled?: boolean; + loading?: boolean; + onClick?: () => void; +} + +export const CustomButton: React.FC = ({ + label, + variant = 'primary', + ...props +}) => { + // Implementation +}; +``` + +### 2. Accessibility + +Ensure components are accessible: + +```typescript + +``` + +### 3. Theme Integration + +Use the theme prop for consistent styling: + +```typescript +const CustomButton: React.FC = ({ theme, properties }) => { + const styles = { + backgroundColor: theme?.colors.primary, + color: theme?.colors.text, + borderRadius: theme?.borderRadius.md, + }; + + return ; +}; +``` + +### 4. Error Handling + +Handle errors gracefully: + +```typescript +const CustomDataGrid: React.FC = ({ properties, data }) => { + if (!data) { + return
No data available
; + } + + if (!properties.columns) { + return
Column configuration is required
; + } + + // Render grid +}; +``` + +### 5. Performance + +Optimize rendering with React hooks: + +```typescript +const CustomChart: React.FC = ({ properties }) => { + const chartConfig = useMemo( + () => buildChartConfig(properties), + [properties] + ); + + return ; +}; +``` + +## Integration with ObjectStack + +### Server-Side + +The server provides metadata through the UI API: + +```typescript +// GET /api/ui/pages/custom_page +{ + "name": "custom_page", + "regions": [ + { + "components": [ + { + "type": "custom-button", + "properties": { ... } + } + ] + } + ] +} +``` + +### Client-Side + +The React renderer interprets the metadata and renders components: + +```typescript +import { PageRenderer } from '@objectstack/ui-renderer'; +import { ComponentRegistry } from './registry'; + +function App() { + const [pageMetadata, setPageMetadata] = useState(null); + + useEffect(() => { + fetch('/api/ui/pages/custom_page') + .then(res => res.json()) + .then(setPageMetadata); + }, []); + + return ( + + ); +} +``` + +## Advanced Topics + +### Dynamic Imports + +Load components on demand for better performance: + +```typescript +const CustomChart = lazy(() => import('./components/CustomChart')); + +ComponentRegistry.register('custom-chart', CustomChart); +``` + +### Component Variants + +Create component variants for different use cases: + +```typescript +// Base component +const BaseButton: React.FC = ({ children, ...props }) => ( + +); + +// Variants +export const PrimaryButton = (props) => ; +export const SecondaryButton = (props) => ; +``` + +### Composable Components + +Build complex components from simpler ones: + +```typescript +const CustomCard: React.FC = ({ properties }) => ( +
+ +
+ {properties.content} +
+ +
+); +``` + +## Testing + +Example test for a custom component: + +```typescript +import { render, screen, fireEvent } from '@testing-library/react'; +import { CustomButton } from './CustomButton'; + +describe('CustomButton', () => { + it('renders with label', () => { + render(); + expect(screen.getByText('Click Me')).toBeInTheDocument(); + }); + + it('calls onClick when clicked', () => { + const onClick = jest.fn(); + render( + + ); + fireEvent.click(screen.getByText('Click Me')); + expect(onClick).toHaveBeenCalled(); + }); +}); +``` + +## Related Examples + +- `../react-renderer` - Shows how to render ObjectStack UI metadata with React +- `../src/view.examples.ts` - View metadata examples +- `../src/page.examples.ts` - Page metadata examples + +## Resources + +- [React Documentation](https://react.dev/) +- [TypeScript Handbook](https://www.typescriptlang.org/docs/) +- [ObjectStack UI Protocol](../../packages/spec/src/ui/) diff --git a/examples/ui/custom-components/package.json b/examples/ui/custom-components/package.json new file mode 100644 index 000000000..b90667b99 --- /dev/null +++ b/examples/ui/custom-components/package.json @@ -0,0 +1,26 @@ +{ + "name": "@objectstack/example-custom-components", + "version": "1.0.0", + "description": "Custom UI component implementations for ObjectStack", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest" + }, + "dependencies": { + "@objectstack/spec": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "lucide-react": "^0.562.0" + }, + "devDependencies": { + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.0.0", + "vite": "^5.4.11", + "vitest": "^2.1.8" + } +} diff --git a/examples/ui/custom-components/src/components/CustomButton.tsx b/examples/ui/custom-components/src/components/CustomButton.tsx new file mode 100644 index 000000000..41fc2c528 --- /dev/null +++ b/examples/ui/custom-components/src/components/CustomButton.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import type { Theme } from '@objectstack/spec/ui'; + +/** + * Base props that all ObjectStack UI components receive + */ +export interface ComponentProps { + /** Component configuration from metadata */ + properties: Record; + + /** Current data context */ + data?: any; + + /** Callback for value changes */ + onChange?: (value: any) => void; + + /** Callback for actions */ + onAction?: (action: string, params?: any) => void; + + /** Theme configuration */ + theme?: Theme; + + /** Additional className for styling */ + className?: string; +} + +/** + * Custom Button Component + * + * A flexible button component with multiple variants and states. + * + * @example + * ```tsx + * console.log('Button clicked:', action)} + * /> + * ``` + */ +export const CustomButton: React.FC = ({ + properties, + onAction, + theme, + className = '', +}) => { + const { + label, + variant = 'primary', + icon, + disabled = false, + loading = false, + size = 'medium', + } = properties; + + const handleClick = () => { + if (!disabled && !loading && onAction) { + onAction('click', { label }); + } + }; + + // Build dynamic styles from theme + const variantStyles: Record = { + primary: { + backgroundColor: theme?.colors?.primary || '#4169E1', + color: '#FFFFFF', + border: 'none', + }, + secondary: { + backgroundColor: 'transparent', + color: theme?.colors?.primary || '#4169E1', + border: `2px solid ${theme?.colors?.primary || '#4169E1'}`, + }, + success: { + backgroundColor: theme?.colors?.success || '#00AA00', + color: '#FFFFFF', + border: 'none', + }, + danger: { + backgroundColor: theme?.colors?.error || '#FF0000', + color: '#FFFFFF', + border: 'none', + }, + }; + + const sizeStyles: Record = { + small: { padding: '6px 12px', fontSize: '14px' }, + medium: { padding: '10px 20px', fontSize: '16px' }, + large: { padding: '14px 28px', fontSize: '18px' }, + }; + + const buttonStyle: React.CSSProperties = { + ...variantStyles[variant], + ...sizeStyles[size], + borderRadius: theme?.borderRadius?.md || '8px', + cursor: disabled ? 'not-allowed' : 'pointer', + opacity: disabled ? 0.6 : 1, + display: 'inline-flex', + alignItems: 'center', + gap: '8px', + fontWeight: 600, + transition: 'all 0.2s ease', + }; + + return ( + + ); +}; + +CustomButton.displayName = 'CustomButton'; diff --git a/examples/ui/custom-components/src/components/CustomDataGrid.tsx b/examples/ui/custom-components/src/components/CustomDataGrid.tsx new file mode 100644 index 000000000..9d61c6048 --- /dev/null +++ b/examples/ui/custom-components/src/components/CustomDataGrid.tsx @@ -0,0 +1,245 @@ +import React, { useState, useMemo } from 'react'; +import type { ComponentProps } from './CustomButton'; + +interface Column { + field: string; + label: string; + width?: number; + sortable?: boolean; + filterable?: boolean; + render?: (value: any, row: any) => React.ReactNode; +} + +/** + * Custom Data Grid Component + * + * An advanced data grid with sorting, filtering, and pagination. + * + * @example + * ```tsx + * + * ``` + */ +export const CustomDataGrid: React.FC = ({ + properties, + data = [], + onChange, + onAction, + theme, +}) => { + const { + columns = [], + pageSize = 25, + selectable = false, + sortable = true, + } = properties; + + const [currentPage, setCurrentPage] = useState(1); + const [sortField, setSortField] = useState(null); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + const [selectedRows, setSelectedRows] = useState>(new Set()); + + // Sort data + const sortedData = useMemo(() => { + if (!sortField) return data; + + return [...data].sort((a, b) => { + const aVal = a[sortField]; + const bVal = b[sortField]; + + if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1; + if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + }, [data, sortField, sortDirection]); + + // Paginate data + const paginatedData = useMemo(() => { + const start = (currentPage - 1) * pageSize; + const end = start + pageSize; + return sortedData.slice(start, end); + }, [sortedData, currentPage, pageSize]); + + const totalPages = Math.ceil(data.length / pageSize); + + const handleSort = (field: string) => { + if (sortField === field) { + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + setSortField(field); + setSortDirection('asc'); + } + }; + + const handleRowSelect = (row: any) => { + const newSelected = new Set(selectedRows); + if (newSelected.has(row.id)) { + newSelected.delete(row.id); + } else { + newSelected.add(row.id); + } + setSelectedRows(newSelected); + onChange?.(Array.from(newSelected)); + }; + + const handleSelectAll = () => { + if (selectedRows.size === paginatedData.length) { + setSelectedRows(new Set()); + onChange?.([]); + } else { + const allIds = new Set(paginatedData.map(row => row.id)); + setSelectedRows(allIds); + onChange?.(Array.from(allIds)); + } + }; + + const tableStyle: React.CSSProperties = { + width: '100%', + borderCollapse: 'collapse', + backgroundColor: theme?.colors?.background || '#FFFFFF', + border: `1px solid ${theme?.colors?.border || '#E5E7EB'}`, + borderRadius: theme?.borderRadius?.md || '8px', + }; + + const headerStyle: React.CSSProperties = { + backgroundColor: theme?.colors?.surface || '#F8F9FA', + padding: '12px', + textAlign: 'left', + fontWeight: 600, + borderBottom: `2px solid ${theme?.colors?.border || '#E5E7EB'}`, + cursor: 'pointer', + userSelect: 'none', + }; + + const cellStyle: React.CSSProperties = { + padding: '12px', + borderBottom: `1px solid ${theme?.colors?.border || '#E5E7EB'}`, + }; + + const paginationStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginTop: '16px', + padding: '12px', + }; + + return ( +
+ + + + {selectable && ( + + )} + {columns.map((col: Column) => ( + + ))} + + + + {paginatedData.map((row, index) => ( + + {selectable && ( + + )} + {columns.map((col: Column) => ( + + ))} + + ))} + +
+ 0} + onChange={handleSelectAll} + aria-label="Select all rows" + /> + col.sortable !== false && handleSort(col.field)} + > + {col.label} + {sortField === col.field && ( + + {sortDirection === 'asc' ? '↑' : '↓'} + + )} +
+ handleRowSelect(row)} + aria-label={`Select row ${index + 1}`} + /> + + {col.render ? col.render(row[col.field], row) : row[col.field]} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+
+ Showing {((currentPage - 1) * pageSize) + 1} to{' '} + {Math.min(currentPage * pageSize, data.length)} of {data.length} rows +
+
+ + + Page {currentPage} of {totalPages} + + +
+
+ )} +
+ ); +}; + +CustomDataGrid.displayName = 'CustomDataGrid'; diff --git a/examples/ui/custom-components/src/index.ts b/examples/ui/custom-components/src/index.ts new file mode 100644 index 000000000..f4000acde --- /dev/null +++ b/examples/ui/custom-components/src/index.ts @@ -0,0 +1,14 @@ +/** + * Custom Components Example - Main Exports + */ + +export { CustomButton } from './components/CustomButton'; +export { CustomDataGrid } from './components/CustomDataGrid'; +export type { ComponentProps } from './components/CustomButton'; + +export { + ComponentRegistry, + registerComponent, + getComponent, + hasComponent, +} from './registry'; diff --git a/examples/ui/custom-components/src/registry.ts b/examples/ui/custom-components/src/registry.ts new file mode 100644 index 000000000..2f12ec911 --- /dev/null +++ b/examples/ui/custom-components/src/registry.ts @@ -0,0 +1,52 @@ +/** + * Component Registry + * + * Maps component type names to React component implementations. + * This registry is used by the UI renderer to instantiate components + * based on metadata. + */ + +import { CustomButton } from './components/CustomButton'; +import { CustomDataGrid } from './components/CustomDataGrid'; + +export interface ComponentRegistryType { + [key: string]: React.ComponentType; +} + +/** + * Global component registry + * + * Components are registered here and referenced by their type name + * in the UI metadata. + */ +export const ComponentRegistry: ComponentRegistryType = { + // Custom components + 'custom-button': CustomButton, + 'custom-data-grid': CustomDataGrid, + + // You can add more components here + // 'custom-chart': CustomChart, + // 'custom-form': CustomForm, + // 'custom-card': CustomCard, +}; + +/** + * Helper function to register a component + */ +export function registerComponent(name: string, component: React.ComponentType) { + ComponentRegistry[name] = component; +} + +/** + * Helper function to get a component by name + */ +export function getComponent(name: string): React.ComponentType | undefined { + return ComponentRegistry[name]; +} + +/** + * Helper function to check if a component is registered + */ +export function hasComponent(name: string): boolean { + return name in ComponentRegistry; +} diff --git a/examples/ui/custom-components/tsconfig.json b/examples/ui/custom-components/tsconfig.json new file mode 100644 index 000000000..1efb6009e --- /dev/null +++ b/examples/ui/custom-components/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "jsx": "react-jsx", + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/ui/react-renderer/README.md b/examples/ui/react-renderer/README.md new file mode 100644 index 000000000..c2f322ee1 --- /dev/null +++ b/examples/ui/react-renderer/README.md @@ -0,0 +1,521 @@ +# React Renderer Example + +This example demonstrates how to render ObjectStack UI metadata using React. + +## Overview + +The ObjectStack UI Protocol defines metadata (JSON/TypeScript) that describes the user interface. This example shows how to build a React-based renderer that interprets this metadata and renders the actual UI. + +## Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ UI Metadata β”‚ (JSON from server or TypeScript) +β”‚ (Pages, Views) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ React Renderer β”‚ (Interprets metadata) +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ React Componentsβ”‚ (Actual UI) +β”‚ (Buttons, etc) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Key Concepts + +### 1. Metadata-Driven UI + +The UI is defined declaratively in metadata: + +```typescript +// UI Metadata (from ObjectStack spec) +const pageMetadata: Page = { + name: 'customer_page', + type: 'record', + object: 'customer', + template: 'header-main', + regions: [ + { + name: 'header', + components: [ + { + type: 'record-header', + properties: { + title: '{name}', + subtitle: '{email}', + }, + }, + ], + }, + { + name: 'main', + components: [ + { + type: 'custom-button', + properties: { + label: 'Save', + variant: 'primary', + }, + }, + ], + }, + ], +}; +``` + +### 2. Component Rendering + +The renderer maps component types to React components: + +```typescript +function renderComponent(componentDef: PageComponent, data: any) { + const Component = ComponentRegistry[componentDef.type]; + + if (!Component) { + return
Unknown component: {componentDef.type}
; + } + + return ( + + ); +} +``` + +### 3. Data Binding + +Components can reference data using template expressions: + +```typescript +// Metadata with data binding +{ + type: 'text', + properties: { + value: '{customer.name}', // Binds to customer.name + } +} + +// Renderer resolves the binding +function resolveBinding(template: string, data: any): string { + return template.replace(/\{([^}]+)\}/g, (_, path) => { + return getNestedValue(data, path) ?? ''; + }); +} +``` + +## Components Included + +### PageRenderer + +Renders a complete page from metadata. + +```typescript + +``` + +### ViewRenderer + +Renders list views, forms, and other view types. + +```typescript + +``` + +### ComponentRenderer + +Low-level component renderer. + +```typescript + +``` + +## Usage Examples + +### Example 1: Simple Page Rendering + +```typescript +import React, { useState, useEffect } from 'react'; +import { PageRenderer } from './renderers/PageRenderer'; +import { ComponentRegistry } from '../custom-components'; + +function App() { + const [pageMetadata, setPageMetadata] = useState(null); + const [data, setData] = useState(null); + + useEffect(() => { + // Fetch metadata from server + fetch('/api/ui/pages/customer_page') + .then(res => res.json()) + .then(setPageMetadata); + + // Fetch data + fetch('/api/data/customer/123') + .then(res => res.json()) + .then(setData); + }, []); + + if (!pageMetadata || !data) { + return
Loading...
; + } + + return ( + + ); +} +``` + +### Example 2: List View with Data Grid + +```typescript +import React from 'react'; +import { ViewRenderer } from './renderers/ViewRenderer'; +import type { ListView } from '@objectstack/spec/ui'; + +const listViewMetadata: ListView = { + type: 'grid', + columns: [ + { field: 'name', label: 'Name', sortable: true }, + { field: 'email', label: 'Email', sortable: true }, + { field: 'status', label: 'Status' }, + ], + pagination: { + pageSize: 25, + }, +}; + +function CustomerList() { + const [customers, setCustomers] = useState([]); + + useEffect(() => { + fetch('/api/data/customer') + .then(res => res.json()) + .then(data => setCustomers(data.records)); + }, []); + + return ( + { + console.log('Action:', action, params); + }} + /> + ); +} +``` + +### Example 3: Dashboard with Widgets + +```typescript +import React from 'react'; +import { DashboardRenderer } from './renderers/DashboardRenderer'; +import type { Dashboard } from '@objectstack/spec/ui'; + +const dashboardMetadata: Dashboard = { + name: 'sales_dashboard', + label: 'Sales Dashboard', + widgets: [ + { + title: 'Total Revenue', + type: 'metric', + object: 'opportunity', + valueField: 'amount', + aggregate: 'sum', + layout: { x: 0, y: 0, w: 3, h: 2 }, + }, + { + title: 'Pipeline by Stage', + type: 'bar', + object: 'opportunity', + categoryField: 'stage', + valueField: 'amount', + layout: { x: 3, y: 0, w: 9, h: 4 }, + }, + ], +}; + +function SalesDashboard() { + return ( + console.log('Refresh dashboard')} + /> + ); +} +``` + +## Advanced Features + +### 1. Template Expression Resolution + +```typescript +/** + * Resolves template expressions like {field.nested.value} + */ +function resolveTemplate(template: string, data: any): any { + if (typeof template !== 'string') return template; + + return template.replace(/\{([^}]+)\}/g, (match, path) => { + const value = getNestedValue(data, path); + return value !== undefined ? value : match; + }); +} + +/** + * Gets nested value from object using dot notation + */ +function getNestedValue(obj: any, path: string): any { + return path.split('.').reduce((current, key) => { + return current?.[key]; + }, obj); +} +``` + +### 2. Action Handling + +```typescript +/** + * Central action handler + */ +function handleAction(action: string, params?: any) { + switch (action) { + case 'save': + return saveRecord(params); + case 'delete': + return deleteRecord(params); + case 'navigate': + return navigate(params.url); + default: + console.warn('Unknown action:', action); + } +} +``` + +### 3. Theme Integration + +```typescript +/** + * Theme context provider + */ +const ThemeContext = React.createContext(null); + +export function ThemeProvider({ theme, children }) { + return ( + + {children} + + ); +} + +export function useTheme() { + return React.useContext(ThemeContext); +} +``` + +### 4. Error Boundaries + +```typescript +class ComponentErrorBoundary extends React.Component { + state = { hasError: false, error: null }; + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + render() { + if (this.state.hasError) { + return ( +
+

Component Error

+

{this.state.error?.message}

+
+ ); + } + + return this.props.children; + } +} +``` + +## Best Practices + +### 1. Lazy Loading + +Load components on demand: + +```typescript +const ComponentRegistry = { + 'heavy-chart': React.lazy(() => import('./components/HeavyChart')), +}; + +function renderComponent(def: PageComponent) { + const Component = ComponentRegistry[def.type]; + + return ( + Loading...}> + + + ); +} +``` + +### 2. Memoization + +Prevent unnecessary re-renders: + +```typescript +const MemoizedComponent = React.memo(({ properties, data }) => { + return ; +}, (prevProps, nextProps) => { + return ( + JSON.stringify(prevProps.properties) === JSON.stringify(nextProps.properties) && + JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data) + ); +}); +``` + +### 3. Performance Monitoring + +Track rendering performance: + +```typescript +function ComponentRenderer({ component, ...props }) { + const startTime = performance.now(); + + useEffect(() => { + const renderTime = performance.now() - startTime; + if (renderTime > 100) { + console.warn(`Slow render: ${component.type} took ${renderTime}ms`); + } + }); + + // Render component... +} +``` + +## File Structure + +``` +react-renderer/ +β”œβ”€β”€ README.md +β”œβ”€β”€ package.json +β”œβ”€β”€ tsconfig.json +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ renderers/ +β”‚ β”‚ β”œβ”€β”€ PageRenderer.tsx # Renders Page metadata +β”‚ β”‚ β”œβ”€β”€ ViewRenderer.tsx # Renders View metadata +β”‚ β”‚ β”œβ”€β”€ DashboardRenderer.tsx # Renders Dashboard metadata +β”‚ β”‚ └── ComponentRenderer.tsx # Base component renderer +β”‚ β”œβ”€β”€ utils/ +β”‚ β”‚ β”œβ”€β”€ templateResolver.ts # Template expression resolution +β”‚ β”‚ β”œβ”€β”€ dataBinding.ts # Data binding utilities +β”‚ β”‚ └── actionHandler.ts # Action handling +β”‚ β”œβ”€β”€ contexts/ +β”‚ β”‚ └── ThemeContext.tsx # Theme context provider +β”‚ β”œβ”€β”€ hooks/ +β”‚ β”‚ β”œβ”€β”€ useMetadata.ts # Fetch metadata hook +β”‚ β”‚ └── useData.ts # Fetch data hook +β”‚ └── index.ts +└── examples/ + β”œβ”€β”€ SimpleApp.tsx # Basic example + β”œβ”€β”€ DataGridApp.tsx # List view example + └── DashboardApp.tsx # Dashboard example +``` + +## Installation + +```bash +cd examples/ui/react-renderer +npm install +``` + +## Development + +```bash +# Start development server +npm run dev + +# Build for production +npm run build + +# Run tests +npm test +``` + +## Integration + +### With Server + +```typescript +// Server provides metadata API +app.get('/api/ui/pages/:pageName', (req, res) => { + const page = getPageMetadata(req.params.pageName); + res.json(page); +}); + +// Client fetches and renders +function PageView({ pageName }) { + const { data: page, loading } = useMetadata(`/api/ui/pages/${pageName}`); + + if (loading) return ; + + return ; +} +``` + +### With State Management + +```typescript +// Redux/Zustand integration +function PageRendererWithStore({ page }) { + const dispatch = useDispatch(); + + const handleAction = (action, params) => { + dispatch({ type: action, payload: params }); + }; + + return ( + + ); +} +``` + +## Related Examples + +- `../custom-components` - Custom component implementations +- `../src/*.examples.ts` - UI metadata examples + +## Resources + +- [ObjectStack UI Protocol](../../packages/spec/src/ui/) +- [React Documentation](https://react.dev/) +- [TypeScript Handbook](https://www.typescriptlang.org/docs/) diff --git a/examples/ui/react-renderer/package.json b/examples/ui/react-renderer/package.json new file mode 100644 index 000000000..95f083700 --- /dev/null +++ b/examples/ui/react-renderer/package.json @@ -0,0 +1,25 @@ +{ + "name": "@objectstack/example-react-renderer", + "version": "1.0.0", + "description": "React renderer for ObjectStack UI metadata", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest" + }, + "dependencies": { + "@objectstack/spec": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.0.0", + "vite": "^5.4.11", + "vitest": "^2.1.8" + } +} diff --git a/examples/ui/react-renderer/src/examples/SimpleApp.tsx b/examples/ui/react-renderer/src/examples/SimpleApp.tsx new file mode 100644 index 000000000..68222fb1f --- /dev/null +++ b/examples/ui/react-renderer/src/examples/SimpleApp.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; +import { PageRenderer } from '../renderers/PageRenderer'; +import { CustomButton, CustomDataGrid, ComponentRegistry } from '../../custom-components/src'; +import type { Page } from '@objectstack/spec/ui'; + +/** + * Simple App Example + * + * Demonstrates rendering a page from metadata with custom components + */ +export function SimpleApp() { + const [customer] = useState({ + id: 1, + name: 'Acme Corporation', + email: 'contact@acme.com', + phone: '+1-555-0123', + status: 'active', + }); + + // Page metadata - this could come from an API + const pageMetadata: Page = { + name: 'customer_page', + label: 'Customer Details', + type: 'record', + object: 'customer', + template: 'header-main', + isDefault: false, + + regions: [ + { + name: 'header', + components: [ + { + type: 'custom-button', + properties: { + label: 'Edit {name}', // Template expression + variant: 'primary', + size: 'large', + }, + }, + ], + }, + { + name: 'main', + components: [ + { + type: 'custom-data-grid', + properties: { + columns: [ + { field: 'label', label: 'Field' }, + { field: 'value', label: 'Value' }, + ], + pageSize: 10, + }, + }, + ], + }, + ], + }; + + const handleAction = (action: string, params?: any) => { + console.log('Action triggered:', action, params); + alert(`Action: ${action}\nParams: ${JSON.stringify(params, null, 2)}`); + }; + + return ( +
+

Simple ObjectStack App

+

This example shows how to render UI from JSON metadata.

+ +
+ +
+ +
+ + View Page Metadata (JSON) + +
+          {JSON.stringify(pageMetadata, null, 2)}
+        
+
+ +
+ + View Data Context + +
+          {JSON.stringify(customer, null, 2)}
+        
+
+
+ ); +} diff --git a/examples/ui/react-renderer/src/index.ts b/examples/ui/react-renderer/src/index.ts new file mode 100644 index 000000000..eb49ab49a --- /dev/null +++ b/examples/ui/react-renderer/src/index.ts @@ -0,0 +1,10 @@ +/** + * React Renderer - Main Exports + */ + +export { PageRenderer } from './renderers/PageRenderer'; +export { ComponentRenderer } from './renderers/ComponentRenderer'; +export { resolveTemplate, getNestedValue } from './utils/templateResolver'; + +// Example apps +export { SimpleApp } from './examples/SimpleApp'; diff --git a/examples/ui/react-renderer/src/renderers/ComponentRenderer.tsx b/examples/ui/react-renderer/src/renderers/ComponentRenderer.tsx new file mode 100644 index 000000000..1c45cbbaa --- /dev/null +++ b/examples/ui/react-renderer/src/renderers/ComponentRenderer.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import type { PageComponent, Theme } from '@objectstack/spec/ui'; +import { resolveTemplate } from '../utils/templateResolver'; + +interface ComponentRendererProps { + component: PageComponent; + data?: any; + registry: Record>; + theme?: Theme; + onAction?: (action: string, params?: any) => void; +} + +/** + * ComponentRenderer - Base renderer for individual components + */ +export const ComponentRenderer: React.FC = ({ + component, + data, + registry, + theme, + onAction, +}) => { + const Component = registry[component.type]; + + if (!Component) { + return ( +
+ Unknown component: {component.type} +
+ ); + } + + // Resolve template expressions + const resolvedProperties = Object.entries(component.properties || {}).reduce( + (acc, [key, value]) => { + acc[key] = resolveTemplate(value, data); + return acc; + }, + {} as Record + ); + + return ( + + ); +}; + +ComponentRenderer.displayName = 'ComponentRenderer'; diff --git a/examples/ui/react-renderer/src/renderers/PageRenderer.tsx b/examples/ui/react-renderer/src/renderers/PageRenderer.tsx new file mode 100644 index 000000000..c1631b318 --- /dev/null +++ b/examples/ui/react-renderer/src/renderers/PageRenderer.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import type { Page, PageComponent, Theme } from '@objectstack/spec/ui'; +import { ComponentRenderer } from './ComponentRenderer'; + +interface PageRendererProps { + /** Page metadata */ + page: Page; + + /** Data context for the page */ + data?: any; + + /** Component registry */ + registry: Record>; + + /** Theme configuration */ + theme?: Theme; + + /** Action handler */ + onAction?: (action: string, params?: any) => void; +} + +/** + * PageRenderer + * + * Renders a complete page from ObjectStack Page metadata. + */ +export const PageRenderer: React.FC = ({ + page, + data, + registry, + theme, + onAction, +}) => { + if (!page) { + return
No page metadata provided
; + } + + // Render regions + const renderRegion = (region: any) => { + const regionStyle: React.CSSProperties = { + padding: '16px', + ...(region.width === 'small' && { maxWidth: '300px' }), + ...(region.width === 'medium' && { maxWidth: '600px' }), + ...(region.width === 'large' && { maxWidth: '900px' }), + }; + + return ( +
+ {region.components?.map((component: PageComponent, index: number) => ( + + ))} +
+ ); + }; + + const pageStyle: React.CSSProperties = { + backgroundColor: theme?.colors?.background || '#FFFFFF', + color: theme?.colors?.text || '#000000', + fontFamily: theme?.typography?.fontFamily?.base || 'sans-serif', + }; + + return ( +
+ {page.regions?.map(renderRegion)} +
+ ); +}; + +PageRenderer.displayName = 'PageRenderer'; diff --git a/examples/ui/react-renderer/src/utils/templateResolver.ts b/examples/ui/react-renderer/src/utils/templateResolver.ts new file mode 100644 index 000000000..c7fa4e916 --- /dev/null +++ b/examples/ui/react-renderer/src/utils/templateResolver.ts @@ -0,0 +1,30 @@ +/** + * Template Expression Resolver + */ + +export function resolveTemplate(template: any, data: any): any { + if (typeof template !== 'string') { + return template; + } + + // Single expression + const singleMatch = template.match(/^\{([^}]+)\}$/); + if (singleMatch) { + const value = getNestedValue(data, singleMatch[1]); + return value !== undefined ? value : template; + } + + // Replace all expressions + return template.replace(/\{([^}]+)\}/g, (match, path) => { + const value = getNestedValue(data, path); + return value !== undefined ? String(value) : match; + }); +} + +export function getNestedValue(obj: any, path: string): any { + if (!obj || !path) return undefined; + + return path.split('.').reduce((current, key) => { + return current?.[key]; + }, obj); +} diff --git a/examples/ui/react-renderer/tsconfig.json b/examples/ui/react-renderer/tsconfig.json new file mode 100644 index 000000000..1efb6009e --- /dev/null +++ b/examples/ui/react-renderer/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "jsx": "react-jsx", + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} From b459d62c5c8e1e24118aa2c3205c9f18461b88d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:05:00 +0000 Subject: [PATCH 4/4] Reorganize UI examples into separate sub-packages Moved metadata examples into metadata-examples/ sub-package to create a consistent structure: - metadata-examples/ - UI metadata configurations (TypeScript/JSON) - custom-components/ - React component implementations - react-renderer/ - React renderer for metadata This improves organization by separating the three aspects: 1. The "what" (metadata defining UI structure) 2. The "implementation" (custom React components) 3. The "how" (rendering engine) Updated main README to reflect new structure. Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- examples/ui/README.md | 139 +++++++----------- examples/ui/metadata-examples/README.md | 53 +++++++ .../ui/{ => metadata-examples}/package.json | 0 .../src/action.examples.ts | 0 .../src/app.examples.ts | 0 .../src/dashboard.examples.ts | 0 .../ui/{ => metadata-examples}/src/index.ts | 0 .../src/page.examples.ts | 0 .../src/theme.examples.ts | 0 .../src/view.examples.ts | 0 .../ui/{ => metadata-examples}/tsconfig.json | 0 pnpm-lock.yaml | 10 -- 12 files changed, 110 insertions(+), 92 deletions(-) create mode 100644 examples/ui/metadata-examples/README.md rename examples/ui/{ => metadata-examples}/package.json (100%) rename examples/ui/{ => metadata-examples}/src/action.examples.ts (100%) rename examples/ui/{ => metadata-examples}/src/app.examples.ts (100%) rename examples/ui/{ => metadata-examples}/src/dashboard.examples.ts (100%) rename examples/ui/{ => metadata-examples}/src/index.ts (100%) rename examples/ui/{ => metadata-examples}/src/page.examples.ts (100%) rename examples/ui/{ => metadata-examples}/src/theme.examples.ts (100%) rename examples/ui/{ => metadata-examples}/src/view.examples.ts (100%) rename examples/ui/{ => metadata-examples}/tsconfig.json (100%) diff --git a/examples/ui/README.md b/examples/ui/README.md index 1bce274d3..cecdf99f7 100644 --- a/examples/ui/README.md +++ b/examples/ui/README.md @@ -4,59 +4,29 @@ This directory contains comprehensive examples demonstrating the **UI Protocol** ## πŸ“š What's Inside -This package contains three types of examples: - -1. **Metadata Examples** (`src/*.examples.ts`) - JSON/TypeScript configurations defining UI structure -2. **Custom Components** (`custom-components/`) - React implementations of custom UI components -3. **React Renderer** (`react-renderer/`) - How to render UI metadata using React - -### Metadata Examples (`src/`) - -These demonstrate all major UI components of the ObjectStack Protocol: - -### 1. **Views** (`view.examples.ts`) -Different ways to display and interact with data: -- **Grid View**: Traditional table/spreadsheet layout -- **Kanban View**: Card-based workflow boards -- **Calendar View**: Time-based event visualization -- **Gantt View**: Project timeline visualization -- **Custom Data Sources**: Using API providers and static data - -### 2. **Pages** (`page.examples.ts`) -Component composition patterns inspired by Salesforce Lightning Pages: -- **Record Pages**: Contextual layouts for viewing/editing records -- **Home Pages**: Dashboard-style landing pages -- **App Pages**: Custom application screens -- **Component Regions**: Flexible layout templates - -### 3. **Dashboards** (`dashboard.examples.ts`) -Analytics and metrics visualization: -- **Widget Types**: Metrics, charts (bar, line, pie, donut, funnel), tables -- **Filters**: Dynamic data filtering -- **Layout Grid**: Responsive dashboard layouts -- **Real-time Metrics**: KPIs and business intelligence - -### 4. **Actions** (`action.examples.ts`) -User interactions and workflows: -- **Button Actions**: Custom buttons with various triggers -- **Modal Actions**: Forms and dialogs -- **Flow Actions**: Visual workflow execution -- **Script Actions**: Custom JavaScript execution -- **Batch Actions**: Mass operations on records - -### 5. **Apps** (`app.examples.ts`) -Complete application structure: -- **Navigation Trees**: Hierarchical menus with groups -- **App Branding**: Custom colors, logos, themes -- **Multi-app Architecture**: Switching between business contexts -- **Permission-based Access**: Role-based app visibility - -### 6. **Themes** (`theme.examples.ts`) -Visual styling and customization: -- **Color Palettes**: Primary, secondary, accent colors -- **Typography**: Font families and sizing -- **Component Styles**: Button variants, borders, shadows -- **Dark Mode**: Theme switching +This package contains three sub-packages demonstrating different aspects of the UI Protocol: + +### 1. **Metadata Examples** (`metadata-examples/`) +TypeScript/JSON configurations defining UI structure - the "what" of the UI: +- **view.examples.ts** - Grid, Kanban, Calendar, Gantt views +- **page.examples.ts** - Record, Home, App pages +- **dashboard.examples.ts** - Analytics dashboards with widgets +- **action.examples.ts** - User interactions and workflows +- **app.examples.ts** - Complete application structures +- **theme.examples.ts** - Visual styling and theming + +### 2. **Custom Components** (`custom-components/`) +React implementations showing how to build custom UI components: +- CustomButton - Flexible button with variants and theming +- CustomDataGrid - Advanced grid with sorting, filtering, pagination +- Component registration system + +### 3. **React Renderer** (`react-renderer/`) +Shows how to render UI metadata using React - the "how" of the UI: +- PageRenderer - Renders Page metadata +- ComponentRenderer - Renders individual components +- Template expression resolver for data binding +- Complete integration examples ## 🎯 Key Concepts @@ -265,51 +235,56 @@ navigation: [ ] ``` -## πŸ“ File Structure +## πŸ“ Directory Structure ``` examples/ui/ -β”œβ”€β”€ package.json # Package configuration -β”œβ”€β”€ tsconfig.json # TypeScript configuration β”œβ”€β”€ README.md # This file -β”œβ”€β”€ src/ # Metadata examples (JSON/TypeScript) -β”‚ β”œβ”€β”€ view.examples.ts # List, Form, Kanban, Calendar views -β”‚ β”œβ”€β”€ page.examples.ts # Record, Home, App pages -β”‚ β”œβ”€β”€ dashboard.examples.ts # Widgets and analytics -β”‚ β”œβ”€β”€ action.examples.ts # Buttons and interactions -β”‚ β”œβ”€β”€ app.examples.ts # Application structure -β”‚ └── theme.examples.ts # Visual styling +β”œβ”€β”€ metadata-examples/ # UI metadata configurations +β”‚ β”œβ”€β”€ README.md +β”‚ β”œβ”€β”€ package.json +β”‚ β”œβ”€β”€ tsconfig.json +β”‚ └── src/ +β”‚ β”œβ”€β”€ view.examples.ts # List, Form, Kanban, Calendar views +β”‚ β”œβ”€β”€ page.examples.ts # Record, Home, App pages +β”‚ β”œβ”€β”€ dashboard.examples.ts # Widgets and analytics +β”‚ β”œβ”€β”€ action.examples.ts # Buttons and interactions +β”‚ β”œβ”€β”€ app.examples.ts # Application structure +β”‚ └── theme.examples.ts # Visual styling β”œβ”€β”€ custom-components/ # React component implementations β”‚ β”œβ”€β”€ README.md -β”‚ β”œβ”€β”€ src/ -β”‚ β”‚ β”œβ”€β”€ components/ -β”‚ β”‚ β”‚ β”œβ”€β”€ CustomButton.tsx -β”‚ β”‚ β”‚ └── CustomDataGrid.tsx -β”‚ β”‚ └── registry.ts # Component registration -β”‚ └── package.json +β”‚ β”œβ”€β”€ package.json +β”‚ β”œβ”€β”€ tsconfig.json +β”‚ └── src/ +β”‚ β”œβ”€β”€ components/ +β”‚ β”‚ β”œβ”€β”€ CustomButton.tsx +β”‚ β”‚ └── CustomDataGrid.tsx +β”‚ └── registry.ts # Component registration └── react-renderer/ # React renderer for metadata β”œβ”€β”€ README.md - β”œβ”€β”€ src/ - β”‚ β”œβ”€β”€ renderers/ - β”‚ β”‚ β”œβ”€β”€ PageRenderer.tsx - β”‚ β”‚ └── ComponentRenderer.tsx - β”‚ β”œβ”€β”€ utils/ - β”‚ β”‚ └── templateResolver.ts - β”‚ └── examples/ - β”‚ └── SimpleApp.tsx - └── package.json + β”œβ”€β”€ package.json + β”œβ”€β”€ tsconfig.json + └── src/ + β”œβ”€β”€ renderers/ + β”‚ β”œβ”€β”€ PageRenderer.tsx + β”‚ └── ComponentRenderer.tsx + β”œβ”€β”€ utils/ + β”‚ └── templateResolver.ts + └── examples/ + └── SimpleApp.tsx ``` ## πŸš€ Quick Start ### 1. View Metadata Examples -The TypeScript examples in `src/` show the metadata structure: +Explore the TypeScript metadata configurations: ```bash -# These are TypeScript files that demonstrate the protocol -cat src/view.examples.ts -cat src/app.examples.ts +cd metadata-examples +npm install +npm run build +# View the examples in src/ ``` ### 2. Custom Components diff --git a/examples/ui/metadata-examples/README.md b/examples/ui/metadata-examples/README.md new file mode 100644 index 000000000..66337e404 --- /dev/null +++ b/examples/ui/metadata-examples/README.md @@ -0,0 +1,53 @@ +# UI Protocol Metadata Examples + +This package contains TypeScript/JSON metadata examples demonstrating the ObjectStack UI Protocol. + +## What's Inside + +These are **configuration examples** (not implementations) showing how to define UI components using the ObjectStack metadata format: + +- **view.examples.ts** - 17 examples: Grid, Kanban, Calendar, Gantt views with various data providers +- **action.examples.ts** - 22 examples: Modal, Flow, Script, URL, and Batch actions +- **dashboard.examples.ts** - 6 dashboards: Sales, Service, Executive, Marketing, and Team productivity +- **page.examples.ts** - 9 page layouts: Record, Home, App, and Utility pages +- **app.examples.ts** - 7 applications: Simple to comprehensive apps with hierarchical navigation +- **theme.examples.ts** - 7 themes: Light, dark, colorful, minimal, and WCAG AAA compliant + +## Usage + +These are metadata definitions that describe the UI structure. To actually render them, see: + +- `../custom-components/` - React component implementations +- `../react-renderer/` - How to render this metadata with React + +## Example + +```typescript +import { ListView } from '@objectstack/spec/ui'; + +// Grid view with custom API data source +const ExternalApiGridView: ListView = { + type: 'grid', + data: { + provider: 'api', + read: { + url: 'https://api.example.com/customers', + headers: { 'Authorization': 'Bearer {token}' }, + }, + }, + columns: ['id', 'company_name', 'email', 'total_orders'], +}; +``` + +## Building + +```bash +npm install +npm run build +``` + +## Related Examples + +- `../custom-components/` - Custom React component implementations +- `../react-renderer/` - React renderer for metadata +- `../../crm/` - Complete CRM application example diff --git a/examples/ui/package.json b/examples/ui/metadata-examples/package.json similarity index 100% rename from examples/ui/package.json rename to examples/ui/metadata-examples/package.json diff --git a/examples/ui/src/action.examples.ts b/examples/ui/metadata-examples/src/action.examples.ts similarity index 100% rename from examples/ui/src/action.examples.ts rename to examples/ui/metadata-examples/src/action.examples.ts diff --git a/examples/ui/src/app.examples.ts b/examples/ui/metadata-examples/src/app.examples.ts similarity index 100% rename from examples/ui/src/app.examples.ts rename to examples/ui/metadata-examples/src/app.examples.ts diff --git a/examples/ui/src/dashboard.examples.ts b/examples/ui/metadata-examples/src/dashboard.examples.ts similarity index 100% rename from examples/ui/src/dashboard.examples.ts rename to examples/ui/metadata-examples/src/dashboard.examples.ts diff --git a/examples/ui/src/index.ts b/examples/ui/metadata-examples/src/index.ts similarity index 100% rename from examples/ui/src/index.ts rename to examples/ui/metadata-examples/src/index.ts diff --git a/examples/ui/src/page.examples.ts b/examples/ui/metadata-examples/src/page.examples.ts similarity index 100% rename from examples/ui/src/page.examples.ts rename to examples/ui/metadata-examples/src/page.examples.ts diff --git a/examples/ui/src/theme.examples.ts b/examples/ui/metadata-examples/src/theme.examples.ts similarity index 100% rename from examples/ui/src/theme.examples.ts rename to examples/ui/metadata-examples/src/theme.examples.ts diff --git a/examples/ui/src/view.examples.ts b/examples/ui/metadata-examples/src/view.examples.ts similarity index 100% rename from examples/ui/src/view.examples.ts rename to examples/ui/metadata-examples/src/view.examples.ts diff --git a/examples/ui/tsconfig.json b/examples/ui/metadata-examples/tsconfig.json similarity index 100% rename from examples/ui/tsconfig.json rename to examples/ui/metadata-examples/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54a8e7373..4b4024875 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,16 +227,6 @@ importers: specifier: ^5.0.0 version: 5.9.3 - examples/ui: - dependencies: - '@objectstack/spec': - specifier: workspace:* - version: link:../../packages/spec - devDependencies: - typescript: - specifier: ^5.0.0 - version: 5.9.3 - packages/client: dependencies: '@objectstack/spec':