Files
michaelschiemer/docs/livecomponents/ui-integration-guide.md
2025-11-24 21:28:25 +01:00

1050 lines
26 KiB
Markdown

# LiveComponents UI Integration Guide
**Complete Guide to UI Features: Tooltips, Loading States, Dialogs, and Notifications**
This guide covers the integrated UI features available in LiveComponents, including tooltips, skeleton loading, dialogs, modals, notifications, and loading states.
---
## Table of Contents
1. [Tooltip System](#tooltip-system)
2. [Loading States & Skeleton Loading](#loading-states--skeleton-loading)
3. [UI Helper System](#ui-helper-system)
4. [Event-Based UI Integration](#event-based-ui-integration)
5. [Notification Component](#notification-component)
6. [Dialog & Modal Integration](#dialog--modal-integration)
7. [Best Practices](#best-practices)
---
## Tooltip System
### Overview
The Tooltip System provides automatic tooltip initialization and management for LiveComponent elements. Tooltips are automatically initialized when components are mounted and cleaned up when components are destroyed.
**Features**:
- Automatic initialization for `data-tooltip` attributes
- Accessibility support (ARIA attributes)
- Smart positioning (viewport-aware)
- Validation error tooltips
- Automatic cleanup
### Basic Usage
```html
<!-- Simple tooltip -->
<button
data-lc-action="save"
data-tooltip="Save your changes"
>
Save
</button>
<!-- Tooltip with validation error -->
<input
type="email"
data-lc-action="validateEmail"
data-tooltip="Enter a valid email address"
data-tooltip-error="Invalid email format"
/>
```
### Server-Side Validation Tooltips
```php
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\ValueObjects\LiveComponentError;
final class UserForm extends LiveComponent
{
#[Action]
public function validateEmail(string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Error will automatically show tooltip if element has data-tooltip-error
throw LiveComponentError::validation(
'Invalid email format',
['field' => 'email'],
$this->id->toString(),
'validateEmail'
);
}
}
}
```
### Custom Tooltip Configuration
```javascript
// Configure tooltip behavior globally
import { tooltipManager } from './modules/livecomponent/TooltipManager.js';
// Adjust delays
tooltipManager.tooltipDelay = 500; // Show after 500ms
tooltipManager.hideDelay = 200; // Hide after 200ms
```
### Tooltip Events
```javascript
// Listen for tooltip events
window.addEventListener('livecomponent:tooltip-shown', (e) => {
const { element, tooltipText } = e.detail;
console.log(`Tooltip shown: ${tooltipText}`);
});
window.addEventListener('livecomponent:tooltip-hidden', (e) => {
const { element } = e.detail;
console.log('Tooltip hidden');
});
```
---
## Loading States & Skeleton Loading
### Overview
The Loading State System provides configurable loading indicators during LiveComponent actions, including skeleton loaders, spinners, and progress indicators.
**Features**:
- Fragment-specific loading
- Configurable loading types (skeleton, spinner, progress, none)
- Smooth transitions
- Optimistic UI integration (no loading for optimistic actions)
- Per-component configuration
### Basic Usage
```html
<!-- Component with skeleton loading -->
<div
data-live-component="product-list"
data-loading-type="skeleton"
data-loading-fragments="product-list"
>
<div data-lc-fragment="product-list">
<!-- Skeleton template will be shown during loading -->
<div class="skeleton-item">
<div class="skeleton-image"></div>
<div class="skeleton-text"></div>
</div>
</div>
</div>
```
### Server-Side Loading Configuration
```php
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Attributes\Loading;
final class ProductList extends LiveComponent
{
#[Action]
#[Loading(type: 'skeleton', fragments: ['product-list'], showDelay: 150)]
public function loadProducts(string $category): void
{
$this->products = $this->productService->getByCategory($category);
}
#[Action]
#[Loading(type: 'spinner', showDelay: 0)] // Show immediately
public function quickAction(): void
{
// Fast action with immediate spinner
}
}
```
### Loading Types
#### 1. Skeleton Loading
```html
<!-- Skeleton template in component -->
<div data-lc-fragment="content">
<!-- Default content -->
<div class="product-card">
<img src="{product.image}" />
<h3>{product.name}</h3>
</div>
<!-- Skeleton template (shown during loading) -->
<template data-skeleton-template>
<div class="product-card skeleton">
<div class="skeleton-image"></div>
<div class="skeleton-text"></div>
</div>
</template>
</div>
```
#### 2. Spinner Loading
```html
<!-- Automatic spinner overlay -->
<div
data-live-component="component-id"
data-loading-type="spinner"
>
<!-- Spinner automatically appears during actions -->
</div>
```
#### 3. Progress Loading
```html
<!-- Progress bar for long-running actions -->
<div
data-live-component="upload-component"
data-loading-type="progress"
>
<div class="progress-bar" data-progress="0"></div>
</div>
```
### Custom Loading Configuration
```javascript
// Configure loading behavior per component
const component = LiveComponentManager.getInstance().getComponent('component-id');
component.setLoadingConfig({
type: 'skeleton',
fragments: ['content', 'sidebar'],
showDelay: 150,
hideDelay: 100
});
```
### Loading Events
```javascript
// Listen for loading state changes
window.addEventListener('livecomponent:loading-started', (e) => {
const { componentId, type, fragments } = e.detail;
console.log(`Loading started: ${componentId} (${type})`);
});
window.addEventListener('livecomponent:loading-finished', (e) => {
const { componentId, duration } = e.detail;
console.log(`Loading finished: ${componentId} (${duration}ms)`);
});
```
---
## UI Helper System
### Overview
The UI Helper System provides a standardized way for LiveComponents to interact with common UI elements like dialogs, modals, and notifications.
**Features**:
- Unified API for UI components
- Integration with UIManager
- Promise-based API
- Automatic cleanup
- Component-scoped UI elements
### Dialog & Modal Helpers
```php
use App\Framework\LiveComponents\Attributes\Action;
final class UserManagement extends LiveComponent
{
#[Action]
public function showDeleteConfirm(int $userId): void
{
// Show confirmation dialog via UI Helper
$this->uiHelper->showDialog(
title: 'Delete User',
message: 'Are you sure you want to delete this user?',
buttons: [
['label' => 'Cancel', 'action' => 'cancel'],
['label' => 'Delete', 'action' => 'confirm', 'variant' => 'danger']
]
)->then(function($action) use ($userId) {
if ($action === 'confirm') {
$this->deleteUser($userId);
}
});
}
}
```
### JavaScript API
```javascript
import { LiveComponentUIHelper } from './modules/livecomponent/LiveComponentUIHelper.js';
const uiHelper = new LiveComponentUIHelper(liveComponentManager);
// Show modal
const action = await uiHelper.showModal('component-id', {
title: 'Confirm Action',
content: '<p>Are you sure?</p>',
size: 'medium',
buttons: [
{ label: 'Cancel', action: 'cancel' },
{ label: 'Confirm', action: 'confirm', variant: 'primary' }
]
});
if (action === 'confirm') {
// Handle confirmation
}
// Show alert
await uiHelper.showAlert('component-id', {
title: 'Success',
message: 'Operation completed successfully',
type: 'success'
});
// Show confirm dialog
const confirmed = await uiHelper.showConfirm('component-id', {
title: 'Delete Item',
message: 'This action cannot be undone.',
confirmText: 'Delete',
cancelText: 'Cancel'
});
```
### Notification Helpers
```javascript
// Show notification
uiHelper.showNotification('component-id', 'Operation successful', 'success');
// Show error notification with retry
uiHelper.showErrorNotification(
'component-id',
'Upload failed. Please try again.',
'error',
true, // Can retry
() => {
// Retry logic
retryUpload();
}
);
// Hide notification
uiHelper.hideNotification('component-id');
```
---
## Event-Based UI Integration
### Overview
The Event-Based UI Integration system allows LiveComponents to trigger UI components (Toasts, Modals) by dispatching events from PHP actions. The JavaScript `UIEventHandler` automatically listens to these events and displays the appropriate UI components.
**Features**:
- Automatic UI component display from PHP events
- Type-safe event payloads
- Toast queue management
- Modal stack management
- Zero JavaScript code required in components
### Architecture
- **PHP Side**: Components dispatch events via `ComponentEventDispatcher`
- **JavaScript Side**: `UIEventHandler` listens to events and displays UI components
- **Integration**: Events are automatically dispatched after action execution
### Basic Usage with UIHelper
The `UIHelper` class provides convenient methods for dispatching UI events:
```php
<?php
declare(strict_types=1);
namespace App\Application\LiveComponents\Product;
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\ComponentEventDispatcher;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\UI\UIHelper;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
#[LiveComponent('product-form')]
final readonly class ProductFormComponent implements LiveComponentContract
{
public function __construct(
public ComponentId $id,
public ProductFormState $state
) {
}
#[Action]
public function save(?ComponentEventDispatcher $events = null): ProductFormState
{
// ... save logic ...
// Show success toast
(new UIHelper($events))->successToast('Product saved successfully!');
return $this->state->withSaved();
}
#[Action]
public function delete(int $productId, ?ComponentEventDispatcher $events = null): ProductFormState
{
// Show confirmation dialog with action
(new UIHelper($events))->confirmDelete(
$this->id,
"product #{$productId}",
'doDelete',
['id' => $productId]
);
return $this->state;
}
#[Action]
public function doDelete(int $id): ProductFormState
{
// This action is automatically called when user confirms
// ... delete logic ...
return $this->state->withoutProduct($id);
}
}
```
### Toast Events
#### Show Toast
```php
use App\Framework\LiveComponents\UI\UIHelper;
#[Action]
public function save(?ComponentEventDispatcher $events = null): State
{
// Basic success toast
(new UIHelper($events))->successToast('Saved successfully!');
// Toast with custom duration
(new UIHelper($events))->infoToast('File uploaded', 7000);
// Using fluent interface for multiple toasts
(new UIHelper($events))
->infoToast('Processing...')
->successToast('Done!');
return $this->state;
}
```
**Toast Types**:
- `info` - Blue, informational message
- `success` - Green, success message
- `warning` - Orange, warning message
- `error` - Red, error message
**Positions**:
- `top-right` (default)
- `top-left`
- `bottom-right`
- `bottom-left`
#### Hide Toast
```php
#[Action]
public function dismissNotification(?ComponentEventDispatcher $events = null): State
{
$this->hideToast($events, 'global');
return $this->state;
}
```
### Modal Events
#### Show Modal
```php
use App\Framework\LiveComponents\UI\UIHelper;
use App\Framework\LiveComponents\Events\UI\Options\ModalOptions;
use App\Framework\LiveComponents\Events\UI\Enums\ModalSize;
#[Action]
public function showEditForm(int $userId, ?ComponentEventDispatcher $events = null): State
{
$formHtml = $this->renderEditForm($userId);
(new UIHelper($events))->modal(
$this->id,
'Edit User',
$formHtml,
ModalOptions::create()
->withSize(ModalSize::Large)
->withButtons([
['text' => 'Save', 'class' => 'btn-primary', 'action' => 'save'],
['text' => 'Cancel', 'class' => 'btn-secondary', 'action' => 'close']
])
);
return $this->state;
}
```
#### Show Confirmation Dialog
```php
use App\Framework\LiveComponents\UI\UIHelper;
#[Action]
public function requestDelete(int $id, ?ComponentEventDispatcher $events = null): State
{
(new UIHelper($events))->confirmDelete(
$this->id,
"item #{$id}",
'doDelete',
['id' => $id]
);
return $this->state;
}
```
**Note**: Confirmation dialogs return a result via the `modal:confirm:result` event. You can listen to this event in JavaScript to handle the confirmation:
```javascript
document.addEventListener('modal:confirm:result', (e) => {
const { componentId, confirmed } = e.detail;
if (confirmed) {
// User confirmed - execute action
LiveComponentManager.getInstance()
.executeAction(componentId, 'confirmDelete', { id: 123 });
}
});
```
#### Show Alert Dialog
```php
use App\Framework\LiveComponents\UI\UIHelper;
#[Action]
public function showError(?ComponentEventDispatcher $events = null): State
{
(new UIHelper($events))->alertError(
$this->id,
'Error',
'An error occurred while processing your request.'
);
return $this->state;
}
```
#### Close Modal
```php
use App\Framework\LiveComponents\UI\UIHelper;
#[Action]
public function closeModal(?ComponentEventDispatcher $events = null): State
{
(new UIHelper($events))->closeModal($this->id);
return $this->state;
}
```
### Manual Event Dispatching
If you prefer to dispatch events manually (without UIHelper):
```php
use App\Framework\LiveComponents\ComponentEventDispatcher;
use App\Framework\LiveComponents\Events\UI\ToastShowEvent;
#[Action]
public function save(?ComponentEventDispatcher $events = null): State
{
// ... save logic ...
if ($events !== null) {
// Option 1: Using Event classes (recommended)
$events->dispatchEvent(ToastShowEvent::success('Saved successfully!'));
// Option 2: Direct instantiation
$events->dispatchEvent(new ToastShowEvent(
message: 'Saved successfully!',
type: 'success',
duration: 5000
));
}
return $this->state;
}
```
### JavaScript Event Handling
The `UIEventHandler` automatically listens to these events and displays UI components. You can also listen to events manually:
```javascript
// Listen to toast events
document.addEventListener('toast:show', (e) => {
const { message, type, duration, position } = e.detail;
console.log(`Toast: ${message} (${type})`);
});
// Listen to modal events
document.addEventListener('modal:show', (e) => {
const { componentId, title, content } = e.detail;
console.log(`Modal shown: ${title}`);
});
// Listen to confirmation results
document.addEventListener('modal:confirm:result', (e) => {
const { componentId, confirmed } = e.detail;
if (confirmed) {
// Handle confirmation
}
});
```
### Toast Queue Management
The `ToastQueue` automatically manages multiple toasts:
- Maximum 5 toasts per position (configurable)
- Automatic stacking with spacing
- Oldest toast removed when limit reached
- Auto-dismiss with queue management
### Modal Stack Management
The `ModalManager` handles modal stacking using native `<dialog>` element:
- **Native `<dialog>` element**: Uses `showModal()` for true modal behavior
- **Automatic backdrop**: Native `::backdrop` pseudo-element
- **Automatic focus management**: Browser handles focus trapping
- **Automatic ESC handling**: Native `cancel` event
- **Z-index management**: Manual stacking for nested modals
- **Stack tracking**: Manages multiple open modals
#### Native `<dialog>` Benefits
The `<dialog>` element provides:
- **True modal behavior**: Blocks background interaction via `showModal()`
- **Native backdrop**: Automatic overlay with `::backdrop` pseudo-element
- **Automatic focus trapping**: Browser handles focus management
- **Automatic ESC handling**: Native `cancel` event
- **Better accessibility**: Native ARIA attributes and semantics
- **Better performance**: Native browser implementation
- **Wide browser support**: Chrome 37+, Firefox 98+, Safari 15.4+
The system uses native `<dialog>` features for optimal modal behavior and accessibility.
### Best Practices
1. **Use UIHelper**: Prefer the UIHelper class over manual event dispatching for type safety and convenience
2. **Component IDs**: Always provide component IDs for modals to enable proper management
3. **Toast Duration**: Use appropriate durations (5s for success, longer for errors)
4. **Modal Sizes**: Choose appropriate sizes (small for alerts, large for forms)
5. **Error Handling**: Always show user-friendly error messages
```php
// Good: Clear, user-friendly toast
(new UIHelper($events))->successToast('Product saved successfully!');
// Bad: Technical error message
(new UIHelper($events))->errorToast('SQL Error: INSERT failed');
```
---
## Notification Component
### Overview
The NotificationComponent is a full-featured LiveComponent for displaying toast notifications with support for different types, positions, durations, and action buttons.
**Features**:
- Type-safe state management
- Multiple notification types (info, success, warning, error)
- Configurable positions (top-right, top-left, bottom-right, bottom-left)
- Auto-dismiss with duration
- Action buttons
- Icon support
### Basic Usage
```php
use App\Application\LiveComponents\Notification\NotificationComponent;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
final class ProductController
{
public function create(): ViewResult
{
$notification = NotificationComponent::mount(
ComponentId::generate('notification'),
message: 'Product created successfully',
type: 'success',
duration: 5000
);
return new ViewResult('product/index', [
'notification' => $notification
]);
}
}
```
### Template Integration
```html
<!-- Include notification component in layout -->
{notification}
<!-- Or use in component template -->
<div data-live-component="{notification.id}">
<!-- Notification will render here -->
</div>
```
### Server-Side Actions
```php
final class NotificationExample extends LiveComponent
{
#[Action]
public function showSuccess(): NotificationState
{
return NotificationState::empty()
->withMessage('Operation successful!', 'success')
->show();
}
#[Action]
public function showError(string $message): NotificationState
{
return NotificationState::empty()
->withMessage($message, 'error')
->show();
}
#[Action]
public function showWithAction(): NotificationState
{
return new NotificationState(
message: 'File uploaded successfully',
type: 'success',
isVisible: true,
actionText: 'View',
actionUrl: '/files'
);
}
}
```
### Client-Side API
```javascript
// Show notification via LiveComponent action
liveComponentManager.executeAction('notification-id', 'showNotification', {
message: 'Operation successful',
type: 'success',
duration: 5000
});
// Hide notification
liveComponentManager.executeAction('notification-id', 'hide');
```
### Notification Types
```php
// Info notification
$notification = NotificationState::empty()
->withMessage('New update available', 'info')
->show();
// Success notification
$notification = NotificationState::empty()
->withMessage('Changes saved', 'success')
->show();
// Warning notification
$notification = NotificationState::empty()
->withMessage('Low disk space', 'warning')
->show();
// Error notification
$notification = NotificationState::empty()
->withMessage('Upload failed', 'error')
->show();
```
### Notification Positions
```php
$notification = new NotificationState(
message: 'Notification message',
type: 'info',
position: 'top-right', // or 'top-left', 'bottom-right', 'bottom-left'
isVisible: true
);
```
### Notification with Action Button
```php
$notification = new NotificationState(
message: 'File ready for download',
type: 'success',
isVisible: true,
actionText: 'Download',
actionUrl: '/download/file.pdf'
);
```
---
## Dialog & Modal Integration
### Overview
LiveComponents integrate seamlessly with the UIManager for dialogs and modals, providing a consistent API across the application.
### Basic Modal Usage
```php
use App\Framework\LiveComponents\Attributes\Action;
final class UserSettings extends LiveComponent
{
#[Action]
public function showEditModal(int $userId): void
{
// Modal will be shown via UI Helper
$this->uiHelper->showModal(
title: 'Edit User',
content: $this->renderEditForm($userId),
size: 'large',
buttons: [
['label' => 'Save', 'action' => 'save', 'variant' => 'primary'],
['label' => 'Cancel', 'action' => 'cancel']
]
);
}
}
```
### Modal with LiveComponent Content
```php
use App\Framework\LiveComponents\UI\UIHelper;
use App\Framework\LiveComponents\Events\UI\Options\ModalOptions;
use App\Framework\LiveComponents\Events\UI\Enums\ModalSize;
#[Action]
public function showUserModal(int $userId, ?ComponentEventDispatcher $events = null): void
{
$userComponent = UserDetailsComponent::mount(
ComponentId::generate('user-details'),
userId: $userId
);
(new UIHelper($events))->modal(
$this->id,
'User Details',
$userComponent->render(),
ModalOptions::create()->withSize(ModalSize::Medium)
);
}
```
### Modal Events
```javascript
// Listen for modal events
window.addEventListener('livecomponent:modal-opened', (e) => {
const { componentId, modalInstance } = e.detail;
console.log(`Modal opened for component: ${componentId}`);
});
window.addEventListener('livecomponent:modal-closed', (e) => {
const { componentId, action } = e.detail;
console.log(`Modal closed with action: ${action}`);
});
```
---
## Best Practices
### 1. Tooltip Usage
- **Do**: Use tooltips for helpful context and validation errors
- **Don't**: Overuse tooltips - they can be distracting
- **Accessibility**: Always ensure tooltips are keyboard-accessible
```html
<!-- Good: Helpful tooltip -->
<button data-tooltip="Save your changes (Ctrl+S)">
Save
</button>
<!-- Bad: Obvious tooltip -->
<button data-tooltip="Click to save">
Save
</button>
```
### 2. Loading States
- **Do**: Use skeleton loading for content-heavy updates
- **Do**: Use spinners for quick actions (< 500ms)
- **Don't**: Show loading for optimistic UI updates
- **Do**: Configure appropriate delays to prevent flickering
```php
// Good: Appropriate loading type
#[Loading(type: 'skeleton', fragments: ['product-list'])]
public function loadProducts(): void { }
// Good: Quick action with spinner
#[Loading(type: 'spinner', showDelay: 0)]
public function toggleFavorite(): void { }
```
### 3. Notifications
- **Do**: Use notifications for important feedback
- **Don't**: Overuse notifications - they can be annoying
- **Do**: Set appropriate durations (5s for success, longer for errors)
- **Do**: Provide action buttons for actionable notifications
```php
// Good: Clear, actionable notification
$notification = new NotificationState(
message: 'File uploaded successfully',
type: 'success',
duration: 5000,
actionText: 'View',
actionUrl: '/files'
);
// Bad: Too many notifications
// Don't show a notification for every minor action
```
### 4. Modals & Dialogs
- **Do**: Use modals for important confirmations
- **Don't**: Overuse modals - they interrupt user flow
- **Do**: Provide clear action buttons
- **Do**: Support keyboard navigation (Escape to close)
```php
// Good: Clear confirmation dialog
$this->uiHelper->showConfirm(
title: 'Delete Item',
message: 'This action cannot be undone.',
confirmText: 'Delete',
cancelText: 'Cancel'
);
```
### 5. Error Handling
- **Do**: Use ErrorBoundary for automatic error handling
- **Do**: Show user-friendly error messages
- **Do**: Provide retry options for recoverable errors
- **Don't**: Show technical error details to users
```php
// Good: User-friendly error
throw LiveComponentError::validation(
'Please enter a valid email address',
['field' => 'email'],
$this->id->toString()
);
// Bad: Technical error
throw new \Exception('Invalid email format: ' . $email);
```
---
## Configuration
### Global Configuration
```javascript
import { sharedConfig } from './modules/livecomponent/SharedConfig.js';
// Configure default values
sharedConfig.defaultDebounce = 300;
sharedConfig.defaultCacheTTL = 5000;
sharedConfig.defaultLoadingShowDelay = 150;
sharedConfig.defaultLoadingType = 'skeleton';
sharedConfig.defaultNotificationDuration = 5000;
sharedConfig.defaultNotificationPosition = 'top-right';
sharedConfig.defaultModalAnimation = 'fade';
```
### Per-Component Configuration
```html
<!-- Component-level configuration -->
<div
data-live-component="component-id"
data-loading-type="skeleton"
data-loading-show-delay="200"
data-notification-position="bottom-right"
>
<!-- Component content -->
</div>
```
---
## Troubleshooting
### Tooltips Not Showing
1. Check that `data-tooltip` attribute is present
2. Verify TooltipManager is initialized
3. Check browser console for errors
4. Ensure element is visible and not hidden
### Loading States Not Working
1. Verify `data-loading-type` attribute is set
2. Check that fragments match between HTML and PHP
3. Ensure LoadingStateManager is initialized
4. Check for JavaScript errors in console
### Notifications Not Displaying
1. Verify NotificationComponent is mounted
2. Check that component ID matches
3. Ensure state is properly serialized
4. Check browser console for errors
### Modals Not Opening
1. Verify UIManager is initialized
2. Check that modal content is valid HTML
3. Ensure no JavaScript errors are blocking execution
4. Check z-index conflicts
---
**Next**: [API Reference](api-reference.md) →