Files
michaelschiemer/docs/livecomponents/ui-integration-guide.md
Michael Schiemer 36ef2a1e2c
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
fix: Gitea Traefik routing and connection pool optimization
- 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
2025-11-09 14:46:15 +01:00

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

  1. Tooltip System
  2. Loading States & Skeleton Loading
  3. UI Helper System
  4. Notification Component
  5. Dialog & Modal Integration
  6. 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

<!-- 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

  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