mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 10:51:08 -05:00
Add standardized HTMX conventions, interaction patterns, and migration guide for ThrillWiki UX
This commit is contained in:
@@ -1,34 +1,64 @@
|
||||
{% comment %}
|
||||
Toast Notification Container Component
|
||||
Matches React frontend toast functionality with Sonner-like behavior
|
||||
======================================
|
||||
|
||||
Enhanced toast notification system with Sonner-like behavior.
|
||||
|
||||
Features:
|
||||
- Multiple toast types (success, error, warning, info)
|
||||
- Progress bar for auto-dismiss countdown
|
||||
- Action button support (Undo, Retry, View)
|
||||
- Toast stacking with max limit
|
||||
- Persistent toast option (duration: 0)
|
||||
- Accessible announcements
|
||||
|
||||
Usage Examples:
|
||||
Basic toast:
|
||||
Alpine.store('toast').success('Item saved!')
|
||||
|
||||
With action:
|
||||
Alpine.store('toast').success('Item deleted', 5000, {
|
||||
action: { label: 'Undo', onClick: () => undoDelete() }
|
||||
})
|
||||
|
||||
Persistent toast:
|
||||
Alpine.store('toast').error('Connection lost', 0)
|
||||
|
||||
From HTMX via HX-Trigger header:
|
||||
response['HX-Trigger'] = '{"showToast": {"type": "success", "message": "Saved!"}}'
|
||||
{% endcomment %}
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div
|
||||
<div
|
||||
x-data="toast()"
|
||||
x-show="$store.toast.toasts.length > 0"
|
||||
class="fixed top-4 right-4 z-50 space-y-2"
|
||||
class="fixed top-4 right-4 z-50 flex flex-col gap-2 max-h-screen overflow-hidden pointer-events-none"
|
||||
role="region"
|
||||
aria-label="Notifications"
|
||||
x-cloak
|
||||
>
|
||||
<template x-for="toast in $store.toast.toasts" :key="toast.id">
|
||||
<div
|
||||
<template x-for="(toast, index) in $store.toast.toasts.slice(0, 5)" :key="toast.id">
|
||||
<div
|
||||
x-show="toast.visible"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="transform opacity-0 translate-x-full"
|
||||
x-transition:enter-end="transform opacity-100 translate-x-0"
|
||||
x-transition:enter-start="transform opacity-0 translate-x-full scale-95"
|
||||
x-transition:enter-end="transform opacity-100 translate-x-0 scale-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="transform opacity-100 translate-x-0"
|
||||
x-transition:leave-end="transform opacity-0 translate-x-full"
|
||||
class="relative max-w-sm w-full bg-background border rounded-lg shadow-lg overflow-hidden"
|
||||
x-transition:leave-start="transform opacity-100 translate-x-0 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 translate-x-full scale-95"
|
||||
class="relative max-w-sm w-full bg-background border rounded-lg shadow-lg overflow-hidden pointer-events-auto"
|
||||
:class="{
|
||||
'border-green-200 bg-green-50 dark:bg-green-900/20 dark:border-green-800': toast.type === 'success',
|
||||
'border-red-200 bg-red-50 dark:bg-red-900/20 dark:border-red-800': toast.type === 'error',
|
||||
'border-yellow-200 bg-yellow-50 dark:bg-yellow-900/20 dark:border-yellow-800': toast.type === 'warning',
|
||||
'border-blue-200 bg-blue-50 dark:bg-blue-900/20 dark:border-blue-800': toast.type === 'info'
|
||||
}"
|
||||
role="alert"
|
||||
:aria-live="toast.type === 'error' ? 'assertive' : 'polite'"
|
||||
>
|
||||
<!-- Progress Bar -->
|
||||
<div
|
||||
<!-- Progress Bar (only show if not persistent) -->
|
||||
<div
|
||||
x-show="toast.duration > 0"
|
||||
class="absolute top-0 left-0 h-1 bg-current opacity-30 transition-all duration-100 ease-linear"
|
||||
:style="`width: ${toast.progress}%`"
|
||||
:class="{
|
||||
@@ -43,29 +73,60 @@ Matches React frontend toast functionality with Sonner-like behavior
|
||||
<div class="flex items-start">
|
||||
<!-- Icon -->
|
||||
<div class="flex-shrink-0 mr-3">
|
||||
<i
|
||||
class="w-5 h-5"
|
||||
<i
|
||||
class="text-lg"
|
||||
:class="{
|
||||
'fas fa-check-circle text-green-500': toast.type === 'success',
|
||||
'fas fa-exclamation-circle text-red-500': toast.type === 'error',
|
||||
'fas fa-exclamation-triangle text-yellow-500': toast.type === 'warning',
|
||||
'fas fa-info-circle text-blue-500': toast.type === 'info'
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<p
|
||||
class="text-sm font-medium"
|
||||
<!-- Title (optional) -->
|
||||
<p
|
||||
x-show="toast.title"
|
||||
class="text-sm font-semibold mb-0.5"
|
||||
:class="{
|
||||
'text-green-800 dark:text-green-200': toast.type === 'success',
|
||||
'text-red-800 dark:text-red-200': toast.type === 'error',
|
||||
'text-yellow-800 dark:text-yellow-200': toast.type === 'warning',
|
||||
'text-blue-800 dark:text-blue-200': toast.type === 'info'
|
||||
}"
|
||||
x-text="toast.title"
|
||||
></p>
|
||||
|
||||
<!-- Message -->
|
||||
<p
|
||||
class="text-sm"
|
||||
:class="{
|
||||
'text-green-700 dark:text-green-300': toast.type === 'success',
|
||||
'text-red-700 dark:text-red-300': toast.type === 'error',
|
||||
'text-yellow-700 dark:text-yellow-300': toast.type === 'warning',
|
||||
'text-blue-700 dark:text-blue-300': toast.type === 'info'
|
||||
}"
|
||||
x-text="toast.message"
|
||||
></p>
|
||||
|
||||
<!-- Action Button (optional) -->
|
||||
<div x-show="toast.action" class="mt-2">
|
||||
<button
|
||||
x-show="toast.action"
|
||||
@click="toast.action?.onClick?.(); $store.toast.hide(toast.id)"
|
||||
class="text-xs font-medium underline hover:no-underline focus:outline-none focus:ring-2 focus:ring-offset-1 rounded"
|
||||
:class="{
|
||||
'text-green-700 dark:text-green-300 focus:ring-green-500': toast.type === 'success',
|
||||
'text-red-700 dark:text-red-300 focus:ring-red-500': toast.type === 'error',
|
||||
'text-yellow-700 dark:text-yellow-300 focus:ring-yellow-500': toast.type === 'warning',
|
||||
'text-blue-700 dark:text-blue-300 focus:ring-blue-500': toast.type === 'info'
|
||||
}"
|
||||
x-text="toast.action?.label || 'Action'"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Close Button -->
|
||||
@@ -79,12 +140,21 @@ Matches React frontend toast functionality with Sonner-like behavior
|
||||
'text-yellow-500 hover:bg-yellow-100 focus:ring-yellow-500 dark:hover:bg-yellow-800': toast.type === 'warning',
|
||||
'text-blue-500 hover:bg-blue-100 focus:ring-blue-500 dark:hover:bg-blue-800': toast.type === 'info'
|
||||
}"
|
||||
aria-label="Dismiss notification"
|
||||
>
|
||||
<i class="fas fa-times w-4 h-4"></i>
|
||||
<i class="fas fa-times text-sm" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Overflow indicator when more than 5 toasts -->
|
||||
<div
|
||||
x-show="$store.toast.toasts.length > 5"
|
||||
class="text-center text-xs text-muted-foreground pointer-events-auto"
|
||||
>
|
||||
<span x-text="`+${$store.toast.toasts.length - 5} more`"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user