Add standardized HTMX conventions, interaction patterns, and migration guide for ThrillWiki UX

This commit is contained in:
pacnpal
2025-12-22 16:56:27 -05:00
parent 2e35f8c5d9
commit ae31e889d7
144 changed files with 25792 additions and 4440 deletions

437
docs/ux/migration-guide.md Normal file
View File

@@ -0,0 +1,437 @@
# Migration Guide
This guide helps migrate existing templates to use the new standardized UX patterns.
## Overview
The ThrillWiki UX standardization introduces:
1. **Breadcrumb system** with Schema.org support
2. **Skeleton screens** for loading states
3. **Standardized form validation** with HTMX
4. **Toast notifications** with action support
5. **Page headers** with consistent layout
6. **Action bars** for button placement
7. **Enhanced modals** with focus trapping
8. **HTMX response utilities** for views
## Step-by-Step Migration
### Step 1: Update Base Template Usage
Ensure your templates extend the base template correctly:
```django
{% extends 'base/base.html' %}
{% block title %}Your Page Title{% endblock %}
{% block content %}
<!-- Your content -->
{% endblock %}
```
### Step 2: Add Breadcrumbs
#### In Your View
```python
from apps.core.utils import BreadcrumbBuilder
def your_view(request):
request.breadcrumbs = (
BreadcrumbBuilder()
.add_home()
.add('Section', '/section/')
.add_current('Current Page')
.build()
)
return render(request, 'your_template.html')
```
#### In Your Template
The breadcrumbs are automatically rendered if you're using the base template.
If you need to place them elsewhere:
```django
{% include 'components/navigation/breadcrumbs.html' %}
```
### Step 3: Replace Page Headers
#### Before
```html
<div class="page-header">
<h1>Parks</h1>
<a href="/parks/create/" class="btn">Add Park</a>
</div>
```
#### After
```django
{% include 'components/layout/page_header.html' with
title='Parks'
subtitle='Browse theme parks worldwide'
primary_action_url='/parks/create/'
primary_action_text='Add Park'
primary_action_icon='fas fa-plus'
%}
```
### Step 4: Update Form Fields
#### Before
```html
<div class="form-group">
<label for="name">Name</label>
{{ form.name }}
{% if form.name.errors %}
<span class="error">{{ form.name.errors.0 }}</span>
{% endif %}
</div>
```
#### After
```django
{% include 'forms/partials/form_field.html' with field=form.name %}
```
For multiple fields with consistent styling:
```django
{% for field in form %}
{% include 'forms/partials/form_field.html' with field=field %}
{% endfor %}
```
### Step 5: Update Form Actions
#### Before
```html
<div class="form-buttons">
<a href="/cancel/">Cancel</a>
<button type="submit">Save</button>
</div>
```
#### After
```django
{% include 'forms/partials/form_actions.html' with
submit_text='Save Park'
cancel_url='/parks/'
%}
```
Or use the action bar for more options:
```django
{% include 'components/ui/action_bar.html' with
align='between'
primary_action_text='Save'
tertiary_action_text='Cancel'
tertiary_action_url='/parks/'
%}
```
### Step 6: Add Loading States
#### For Page Sections
```django
<div id="parks-list"
hx-get="{% url 'parks:list' %}"
hx-trigger="load"
hx-swap="innerHTML">
{% include 'components/skeletons/card_grid_skeleton.html' with cards=6 %}
</div>
```
#### For Forms
```django
<form hx-post="{% url 'parks:create' %}"
hx-target="#form-container"
hx-indicator="#form-loading">
{% csrf_token %}
<!-- Form fields -->
<div id="form-loading" class="htmx-indicator">
{% include 'htmx/components/loading_indicator.html' with text='Saving...' %}
</div>
<button type="submit">Save</button>
</form>
```
### Step 7: Update Toast Messages
#### Before (Django Messages)
```python
from django.contrib import messages
def your_view(request):
messages.success(request, 'Park saved!')
return redirect('parks:list')
```
#### After (HTMX Response)
```python
from apps.core.htmx_utils import htmx_success
def your_view(request):
# For HTMX requests
if request.headers.get('HX-Request'):
return htmx_success('Park saved!')
# For regular requests, still use messages
messages.success(request, 'Park saved!')
return redirect('parks:list')
```
### Step 8: Update Modals
#### Before
```html
<div class="modal" id="edit-modal">
<div class="modal-header">
<h2>Edit Park</h2>
<button onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<!-- Content -->
</div>
</div>
```
#### After
```django
<div x-data="{ showEditModal: false }">
<button @click="showEditModal = true">Edit</button>
{% include 'components/modals/modal_base.html' with
modal_id='edit-modal'
show_var='showEditModal'
title='Edit Park'
size='lg'
%}
</div>
```
For confirmation dialogs:
```django
{% include 'components/modals/modal_confirm.html' with
modal_id='delete-confirm'
show_var='showDeleteModal'
title='Delete Park'
message='Are you sure? This cannot be undone.'
confirm_text='Delete'
confirm_variant='destructive'
confirm_hx_delete='/parks/123/'
%}
```
### Step 9: Update HTMX Views
#### Before
```python
from django.http import HttpResponse
import json
def create_park(request):
park = Park.objects.create(**form.cleaned_data)
response = HttpResponse('')
response['HX-Trigger'] = json.dumps({
'showMessage': {'message': 'Park created!'}
})
return response
```
#### After
```python
from apps.core.htmx_utils import htmx_success, htmx_error, htmx_modal_close
def create_park(request):
form = ParkForm(request.POST)
if not form.is_valid():
return htmx_error('Validation failed', status=422)
park = form.save()
# Simple success
return htmx_success(f'{park.name} created!')
# Or close modal and refresh list
return htmx_modal_close(
message=f'{park.name} created!',
refresh_target='#parks-list'
)
```
### Step 10: Add Page Meta
#### In Your View
```python
from apps.core.utils import build_meta_context
def park_detail(request, slug):
park = get_object_or_404(Park, slug=slug)
request.page_meta = build_meta_context(
title=park.name,
description=park.description,
instance=park,
request=request,
)
return render(request, 'parks/detail.html', {'park': park})
```
The meta tags are automatically rendered in the base template.
## Component Replacements
| Old Pattern | New Component |
|-------------|---------------|
| Custom page header | `components/layout/page_header.html` |
| Form field div | `forms/partials/form_field.html` |
| Form buttons | `forms/partials/form_actions.html` or `components/ui/action_bar.html` |
| Loading spinner | `htmx/components/loading_indicator.html` |
| Placeholder content | `components/skeletons/*.html` |
| Custom modal | `components/modals/modal_base.html` |
| Confirm dialog | `components/modals/modal_confirm.html` |
| Custom breadcrumbs | `components/navigation/breadcrumbs.html` |
| Status labels | `components/status_badge.html` |
## View Helper Replacements
| Old Pattern | New Helper |
|-------------|------------|
| `HttpResponse + HX-Trigger` | `htmx_success()` / `htmx_error()` |
| `HttpResponse + HX-Redirect` | `htmx_redirect()` |
| Custom modal close | `htmx_modal_close()` |
| Inline validation | `htmx_validation_response()` |
| Check HX-Request header | `is_htmx_request()` |
## Common Issues
### Issue: Breadcrumbs Not Showing
1. Ensure context processor is added to settings:
```python
TEMPLATES = [{
'OPTIONS': {
'context_processors': [
# ...
'apps.core.context_processors.breadcrumbs',
],
},
}]
```
2. Ensure you set `request.breadcrumbs` in your view.
### Issue: Toast Not Appearing
1. Ensure toast container is in base template:
```django
{% include 'components/ui/toast-container.html' %}
```
2. Ensure Alpine.js is loaded and toast store is initialized.
3. For HTMX responses, use the helper functions:
```python
return htmx_success('Message here')
```
### Issue: Modal Not Closing
1. Ensure you're using the correct `show_var`:
```django
<div x-data="{ showModal: false }">
{% include 'components/modals/modal_base.html' with show_var='showModal' %}
</div>
```
2. For HTMX responses, use:
```python
return htmx_modal_close(message='Done!')
```
### Issue: Form Validation Not Working
1. Ensure HTMX attributes are correct:
```html
<form hx-post="/create/"
hx-target="#form-container"
hx-swap="outerHTML">
```
2. Return proper status code on errors:
```python
if not form.is_valid():
return render(request, 'form.html', {'form': form}, status=422)
```
### Issue: Skeleton Not Matching Content
Adjust skeleton parameters to match your content:
```django
{# For a 3-column grid with 9 cards #}
{% include 'components/skeletons/card_grid_skeleton.html' with cards=9 cols=3 %}
{# For a table with checkboxes #}
{% include 'components/skeletons/table_skeleton.html' with rows=10 columns=5 show_checkbox=True %}
```
## Testing Migrations
After migrating a template:
1. **Visual Check**: Ensure layout matches design
2. **Functionality**: Test all interactive elements
3. **Accessibility**: Test keyboard navigation and screen readers
4. **Responsive**: Check on mobile, tablet, and desktop
5. **Loading States**: Verify skeletons and indicators work
6. **Error States**: Test form validation and error messages
7. **Success States**: Verify toast notifications appear
## Rollback
If issues arise, components are designed to be backwards compatible.
You can temporarily revert to old patterns while debugging:
```django
{% comment %}
{% include 'components/layout/page_header.html' with title='Parks' %}
{% endcomment %}
<div class="page-header">
<h1>Parks</h1>
</div>
```