Files
thrillwiki_django_no_react/docs/ux/migration-guide.md

438 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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>
```