# DeesAppuiBase A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management. ## Quick Start ```typescript import { html, DeesElement, customElement } from '@design.estate/dees-element'; import { DeesAppuiBase } from '@design.estate/dees-catalog'; @customElement('my-app') class MyApp extends DeesElement { private appui: DeesAppuiBase; async firstUpdated() { this.appui = this.shadowRoot.querySelector('dees-appui-base'); // Configure with views and menu this.appui.configure({ branding: { logoIcon: 'lucide:box', logoText: 'My App' }, views: [ { id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' }, { id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' }, ], mainMenu: { sections: [{ name: 'Main', views: ['dashboard', 'settings'] }] }, defaultView: 'dashboard' }); } render() { return html``; } } ``` ## Configuration API ### `configure(config: IAppConfig)` Configure the entire application shell with a single configuration object. ```typescript interface IAppConfig { branding?: IBrandingConfig; appBar?: IAppBarConfig; views: IViewDefinition[]; mainMenu?: IMainMenuConfig; defaultView?: string; activityLog?: IActivityLogConfig; onViewChange?: (viewId: string, view: IViewDefinition) => void; onSearch?: (query: string) => void; } ``` ### View Definition ```typescript interface IViewDefinition { id: string; // Unique identifier name: string; // Display name iconName?: string; // Icon (e.g., 'lucide:home') content: // View content | string // Tag name ('my-component') | (new () => HTMLElement) // Class constructor | (() => TemplateResult) // Template function | (() => Promise<...>); // Async for lazy loading secondaryMenu?: ISecondaryMenuGroup[]; contentTabs?: ITab[]; route?: string; // URL route (default: id) badge?: string | number; cache?: boolean; // Cache view instance (default: true) } ``` --- ## Programmatic APIs ### App Bar API Control the top application bar. ```typescript // Set menu items (File, Edit, View, etc.) appui.setAppBarMenus([ { name: 'File', submenu: [ { name: 'New', shortcut: 'Cmd+N', action: () => {} }, { name: 'Save', shortcut: 'Cmd+S', action: () => {} }, ] } ]); // Update single menu appui.updateAppBarMenu('File', { submenu: [...newItems] }); // Breadcrumbs appui.setBreadcrumbs('Dashboard > Settings > Profile'); appui.setBreadcrumbs(['Dashboard', 'Settings', 'Profile']); // User profile appui.setUser({ name: 'John Doe', email: 'john@example.com', avatar: '/avatars/john.png', status: 'online' // 'online' | 'offline' | 'busy' | 'away' }); appui.setProfileMenuItems([ { name: 'Profile', iconName: 'lucide:user', action: () => {} }, { divider: true }, { name: 'Sign Out', iconName: 'lucide:log-out', action: () => {} } ]); // Search appui.setSearchVisible(true); appui.onSearch((query) => console.log('Search:', query)); // Window controls (for Electron/Tauri apps) appui.setWindowControlsVisible(false); ``` ### Main Menu API (Left Sidebar) Control the main navigation menu. ```typescript // Set entire menu appui.setMainMenu({ logoIcon: 'lucide:box', logoText: 'My App', groups: [ { name: 'Main', tabs: [ { key: 'dashboard', iconName: 'lucide:home', action: () => {} }, { key: 'inbox', iconName: 'lucide:inbox', badge: 5, action: () => {} }, ] } ], bottomTabs: [ { key: 'settings', iconName: 'lucide:settings', action: () => {} } ] }); // Update specific group appui.updateMainMenuGroup('Main', { tabs: [...newTabs] }); // Add/remove items appui.addMainMenuItem('Main', { key: 'tasks', iconName: 'lucide:check', action: () => {} }); appui.removeMainMenuItem('Main', 'tasks'); // Selection appui.setMainMenuSelection('dashboard'); appui.setMainMenuCollapsed(true); // Badges appui.setMainMenuBadge('inbox', 12); appui.clearMainMenuBadge('inbox'); ``` ### Secondary Menu API Views can control the secondary (contextual) menu. ```typescript // Set menu appui.setSecondaryMenu({ heading: 'Settings', groups: [ { name: 'Account', items: [ { key: 'profile', iconName: 'lucide:user', action: () => {} }, { key: 'security', iconName: 'lucide:shield', action: () => {} }, ] } ] }); // Update group appui.updateSecondaryMenuGroup('Account', { items: newItems }); // Add item appui.addSecondaryMenuItem('Account', { key: 'notifications', iconName: 'lucide:bell', action: () => {} }); // Selection appui.setSecondaryMenuSelection('profile'); // Clear appui.clearSecondaryMenu(); ``` ### Content Tabs API Control tabs in the main content area. ```typescript // Set tabs appui.setContentTabs([ { key: 'code', iconName: 'lucide:code', action: () => {} }, { key: 'preview', iconName: 'lucide:eye', action: () => {} } ]); // Add/remove appui.addContentTab({ key: 'debug', iconName: 'lucide:bug', action: () => {} }); appui.removeContentTab('debug'); // Select appui.selectContentTab('preview'); // Get current const current = appui.getSelectedContentTab(); ``` ### Activity Log API Add activity entries to the right-side activity log. ```typescript // Add single entry appui.activityLog.add({ type: 'create', // 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom' user: 'John Doe', message: 'created a new invoice', iconName: 'lucide:file-plus', // Optional custom icon data: { invoiceId: '123' } // Optional metadata }); // Add multiple appui.activityLog.addMany([...entries]); // Clear appui.activityLog.clear(); // Query const entries = appui.activityLog.getEntries(); const filtered = appui.activityLog.filter({ user: 'John', type: 'create' }); const searched = appui.activityLog.search('invoice'); ``` ### Navigation API Navigate between views programmatically. ```typescript // Navigate to view await appui.navigateToView('settings'); await appui.navigateToView('settings', { section: 'profile' }); // Get current view const current = appui.getCurrentView(); // Subscribe to view changes appui.viewChanged$.subscribe((event) => { console.log(`Navigated to: ${event.viewId}`); }); // Subscribe to lifecycle events appui.viewLifecycle$.subscribe((event) => { if (event.type === 'activated') { console.log(`View ${event.viewId} activated`); } }); ``` --- ## View Lifecycle Hooks Views can implement lifecycle hooks to respond to activation/deactivation. ```typescript import { DeesElement, customElement } from '@design.estate/dees-element'; import type { IViewActivationContext, IViewLifecycle } from '@design.estate/dees-catalog'; @customElement('my-settings-view') class MySettingsView extends DeesElement implements IViewLifecycle { /** * Called when view is activated (displayed) * Receives typed context with appui reference */ async onActivate(context: IViewActivationContext) { const { appui, viewId, params } = context; // Set view-specific secondary menu appui.setSecondaryMenu({ heading: 'Settings', groups: [{ name: 'Options', items: [...] }] }); // Set view-specific tabs appui.setContentTabs([...]); // Load data based on route params if (params?.section) { await this.loadSection(params.section); } } /** * Called when view is deactivated (hidden) */ onDeactivate() { this.cleanup(); } /** * Called before navigation away * Return false or a message string to block navigation */ canDeactivate(): boolean | string { if (this.hasUnsavedChanges) { return 'You have unsaved changes. Leave anyway?'; } return true; } } ``` ### IViewActivationContext ```typescript interface IViewActivationContext { appui: DeesAppuiBase; // Reference to the app shell viewId: string; // The view ID being activated params?: Record; // Route parameters } ``` --- ## Routing Routes are automatically registered from view definitions using `domtools.router`. ```typescript const views = [ { id: 'dashboard', route: 'dashboard', ... }, { id: 'settings', route: 'settings/:section?', ... }, // Parameterized { id: 'user', route: 'users/:id', ... }, ]; // URL: #dashboard → navigates to dashboard view // URL: #settings/profile → navigates to settings with params.section = 'profile' // URL: #users/123 → navigates to user with params.id = '123' ``` ### Hash-based Routing The router uses hash-based routing by default (`#viewId`). URLs are automatically synchronized when navigating via `navigateToView()`. --- ## View Caching Views are cached by default. When navigating away and back, the same DOM element is reused (hidden/shown) rather than destroyed and recreated. ```typescript // Disable caching for a specific view { id: 'reports', name: 'Reports', content: 'my-reports-view', cache: false // Always recreate this view } ``` --- ## Lazy Loading Use async content functions for lazy loading views. ```typescript { id: 'analytics', name: 'Analytics', content: async () => { const module = await import('./views/analytics.js'); return module.AnalyticsView; } } ``` --- ## RxJS Observables The component exposes RxJS Subjects for reactive programming. ```typescript // View lifecycle events appui.viewLifecycle$.subscribe((event) => { // event.type: 'loading' | 'activated' | 'deactivated' | 'loaded' | 'loadError' // event.viewId: string // event.element?: HTMLElement // event.params?: Record // event.error?: unknown }); // View change events appui.viewChanged$.subscribe((event) => { // event.viewId: string // event.view: IViewDefinition // event.previousView?: IViewDefinition // event.params?: Record }); ``` --- ## Complete Example ```typescript import { html, DeesElement, customElement } from '@design.estate/dees-element'; import { DeesAppuiBase, IViewActivationContext } from '@design.estate/dees-catalog'; @customElement('my-app') class MyApp extends DeesElement { private appui: DeesAppuiBase; async firstUpdated() { this.appui = this.shadowRoot.querySelector('dees-appui-base'); this.appui.configure({ branding: { logoIcon: 'lucide:briefcase', logoText: 'CRM Pro' }, appBar: { menuItems: [ { name: 'File', submenu: [...] }, { name: 'Edit', submenu: [...] } ], showSearch: true, user: { name: 'Jane Smith', status: 'online' } }, views: [ { id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'crm-dashboard', route: 'dashboard' }, { id: 'contacts', name: 'Contacts', iconName: 'lucide:users', content: 'crm-contacts', route: 'contacts', badge: 42 }, { id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'crm-settings', route: 'settings/:section?' } ], mainMenu: { sections: [ { name: 'Main', views: ['dashboard', 'contacts'] } ], bottomItems: ['settings'] }, defaultView: 'dashboard', onViewChange: (viewId, view) => { console.log(`Navigated to: ${view.name}`); }, onSearch: (query) => { console.log(`Search: ${query}`); } }); // Load activity from backend const activities = await fetch('/api/activities').then(r => r.json()); this.appui.activityLog.addMany(activities); } render() { return html``; } } // View with lifecycle hooks @customElement('crm-settings') class CrmSettings extends DeesElement { private appui: DeesAppuiBase; onActivate(context: IViewActivationContext) { this.appui = context.appui; // Set secondary menu for settings this.appui.setSecondaryMenu({ heading: 'Settings', groups: [ { name: 'Account', items: [ { key: 'profile', iconName: 'lucide:user', action: () => this.showSection('profile') }, { key: 'security', iconName: 'lucide:shield', action: () => this.showSection('security') } ] }, { name: 'Preferences', items: [ { key: 'notifications', iconName: 'lucide:bell', action: () => this.showSection('notifications') } ] } ] }); // Navigate to section from URL params if (context.params?.section) { this.showSection(context.params.section); } } showSection(section: string) { this.appui.setSecondaryMenuSelection(section); // ... load section content } } ``` --- ## TypeScript Types All interfaces are exported from `@design.estate/dees-catalog`: - `IAppConfig` - Main configuration - `IViewDefinition` - View definition - `IViewActivationContext` - Context passed to `onActivate` - `IViewLifecycle` - Lifecycle hooks interface - `IViewLifecycleEvent` - Lifecycle event for rxjs Subject - `IViewChangeEvent` - View change event - `IAppUser` - User configuration - `IActivityEntry` - Activity log entry - `IActivityLogAPI` - Activity log methods - `IAppBarMenuItem` - App bar menu item - `IMainMenuConfig` - Main menu configuration - `ISecondaryMenuGroup` - Secondary menu group - `ITab` - Tab definition