# ThrillWiki Interaction Patterns This document describes the standardized interaction patterns used throughout ThrillWiki. ## Alpine.js Stores ThrillWiki uses Alpine.js stores for global client-side state management. ### Toast Store The toast store manages notification messages displayed to users. ```javascript // Show a success toast Alpine.store('toast').success('Park saved successfully!'); // Show an error toast Alpine.store('toast').error('Failed to save park'); // Show a warning toast Alpine.store('toast').warning('Your session will expire soon'); // Show an info toast Alpine.store('toast').info('New features are available'); // Toast with custom duration (in ms) Alpine.store('toast').success('Quick message', 2000); // Toast with action button Alpine.store('toast').success('Item deleted', 5000, { action: { label: 'Undo', onClick: () => undoDelete() } }); // Persistent toast (duration = 0) Alpine.store('toast').error('Connection lost', 0); ``` ### Theme Store The theme store manages dark/light mode preferences. ```javascript // Get current theme const isDark = Alpine.store('theme').isDark; // Toggle theme Alpine.store('theme').toggle(); // Set specific theme Alpine.store('theme').set('dark'); Alpine.store('theme').set('light'); Alpine.store('theme').set('system'); ``` ### Auth Store The auth store tracks user authentication state. ```javascript // Check if user is authenticated if (Alpine.store('auth').isAuthenticated) { // Show authenticated content } // Get current user const user = Alpine.store('auth').user; ``` ## HTMX Patterns ### Standard Request Pattern ```html ``` ### Form Submission ```html
{% csrf_token %}
``` ### Inline Validation ```html
``` ### Infinite Scroll ```html
{% for item in items %} {% include 'items/_card.html' %} {% endfor %} {% if has_next %}
Loading more...
{% endif %}
``` ### Modal Loading ```html ``` ## Event Handling ### HTMX Events ```javascript // Before request document.body.addEventListener('htmx:beforeRequest', (event) => { // Show loading state }); // After successful swap document.body.addEventListener('htmx:afterSwap', (event) => { // Initialize new content }); // Handle errors document.body.addEventListener('htmx:responseError', (event) => { Alpine.store('toast').error('Request failed'); }); ``` ### Custom Events ```javascript // Trigger from server via HX-Trigger header document.body.addEventListener('showToast', (event) => { const { type, message, duration } = event.detail; Alpine.store('toast')[type](message, duration); }); // Close modal event document.body.addEventListener('closeModal', () => { Alpine.store('modal').close(); }); // Refresh section event document.body.addEventListener('refreshSection', (event) => { const { target, url } = event.detail; htmx.ajax('GET', url, target); }); ``` ## Loading States ### Button Loading ```html ``` ### Section Loading ```html
{% include 'components/skeletons/list_skeleton.html' %}
``` ### Global Loading Bar The application includes a global loading bar that appears during HTMX requests: ```css .htmx-request .loading-bar { display: block; } ``` ## Focus Management ### Modal Focus Trap Modals automatically trap focus within their bounds: ```html {% include 'components/modals/modal_base.html' with modal_id='my-modal' show_var='showModal' title='Modal Title' %} ``` The modal component handles: - Focus moves to first focusable element on open - Tab cycles through modal elements only - Escape key closes modal - Focus returns to trigger element on close ### Form Focus After validation errors, focus moves to the first invalid field: ```javascript document.body.addEventListener('htmx:afterSwap', (event) => { const firstError = event.target.querySelector('.field-error input'); if (firstError) { firstError.focus(); } }); ``` ## Keyboard Navigation ### Standard Shortcuts | Key | Action | |-----|--------| | `Escape` | Close modal/dropdown | | `Enter` | Submit form / Activate button | | `Tab` | Move to next focusable element | | `Shift+Tab` | Move to previous focusable element | | `Arrow Up/Down` | Navigate dropdown options | ### Custom Shortcuts ```javascript // Global keyboard shortcuts document.addEventListener('keydown', (event) => { // Ctrl/Cmd + K: Open search if ((event.ctrlKey || event.metaKey) && event.key === 'k') { event.preventDefault(); Alpine.store('search').open(); } }); ``` ## Animation Patterns ### Entry Animations ```html
Content
``` ### Slide Animations ```html
Content
``` ### Stagger Animations ```html ``` ## Error Handling ### Graceful Degradation All interactions work without JavaScript: ```html
``` ### Error Recovery ```javascript // Retry failed requests document.body.addEventListener('htmx:responseError', (event) => { const { xhr, target } = event.detail; if (xhr.status >= 500) { Alpine.store('toast').error('Server error. Please try again.', 0, { action: { label: 'Retry', onClick: () => htmx.trigger(target, 'htmx:load') } }); } }); ``` ### Offline Handling ```javascript window.addEventListener('offline', () => { Alpine.store('toast').warning('You are offline', 0); }); window.addEventListener('online', () => { Alpine.store('toast').success('Connection restored'); }); ```