- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
17 KiB
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
- Tooltip System
- Loading States & Skeleton Loading
- UI Helper System
- Notification Component
- Dialog & Modal Integration
- 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-tooltipattributes - Accessibility support (ARIA attributes)
- Smart positioning (viewport-aware)
- Validation error tooltips
- Automatic cleanup
Basic Usage
<!-- 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
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
// 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
// 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
<!-- 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
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
<!-- 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
<!-- Automatic spinner overlay -->
<div
data-live-component="component-id"
data-loading-type="spinner"
>
<!-- Spinner automatically appears during actions -->
</div>
3. Progress Loading
<!-- 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
// 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
// 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
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
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
// 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');
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
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
<!-- 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
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
// 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
// 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
$notification = new NotificationState(
message: 'Notification message',
type: 'info',
position: 'top-right', // or 'top-left', 'bottom-right', 'bottom-left'
isVisible: true
);
Notification with Action Button
$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
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
#[Action]
public function showUserModal(int $userId): void
{
$userComponent = UserDetailsComponent::mount(
ComponentId::generate('user-details'),
userId: $userId
);
$this->uiHelper->showModal(
title: 'User Details',
content: $userComponent->render(),
size: 'medium'
);
}
Modal Events
// 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
<!-- 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
// 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
// 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)
// 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
// 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
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
<!-- 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
- Check that
data-tooltipattribute is present - Verify TooltipManager is initialized
- Check browser console for errors
- Ensure element is visible and not hidden
Loading States Not Working
- Verify
data-loading-typeattribute is set - Check that fragments match between HTML and PHP
- Ensure LoadingStateManager is initialized
- Check for JavaScript errors in console
Notifications Not Displaying
- Verify NotificationComponent is mounted
- Check that component ID matches
- Ensure state is properly serialized
- Check browser console for errors
Modals Not Opening
- Verify UIManager is initialized
- Check that modal content is valid HTML
- Ensure no JavaScript errors are blocking execution
- Check z-index conflicts
Next: API Reference →