mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-25 00:11:09 -05:00
Add standardized HTMX conventions, interaction patterns, and migration guide for ThrillWiki UX
This commit is contained in:
108
backend/templates/components/skeletons/card_grid_skeleton.html
Normal file
108
backend/templates/components/skeletons/card_grid_skeleton.html
Normal file
@@ -0,0 +1,108 @@
|
||||
{% comment %}
|
||||
Card Grid Skeleton Component
|
||||
============================
|
||||
|
||||
Animated skeleton placeholder for card grid layouts while content loads.
|
||||
|
||||
Purpose:
|
||||
Displays pulsing skeleton cards in a grid layout for pages like
|
||||
parks list, rides list, and search results.
|
||||
|
||||
Usage Examples:
|
||||
Basic card grid:
|
||||
{% include 'components/skeletons/card_grid_skeleton.html' %}
|
||||
|
||||
Custom card count:
|
||||
{% include 'components/skeletons/card_grid_skeleton.html' with cards=8 %}
|
||||
|
||||
Horizontal cards:
|
||||
{% include 'components/skeletons/card_grid_skeleton.html' with layout='horizontal' %}
|
||||
|
||||
Custom columns:
|
||||
{% include 'components/skeletons/card_grid_skeleton.html' with cols='4' %}
|
||||
|
||||
Parameters:
|
||||
Optional:
|
||||
- cards: Number of skeleton cards to display (default: 6)
|
||||
- cols: Grid columns ('2', '3', '4', 'auto') (default: 'auto')
|
||||
- layout: Card layout ('vertical', 'horizontal') (default: 'vertical')
|
||||
- show_image: Show image placeholder (default: True)
|
||||
- show_badge: Show badge placeholder (default: True)
|
||||
- show_footer: Show footer with stats (default: True)
|
||||
- image_aspect: Image aspect ratio ('video', 'square', 'portrait') (default: 'video')
|
||||
- animate: Enable pulse animation (default: True)
|
||||
|
||||
Dependencies:
|
||||
- Tailwind CSS for styling and animation
|
||||
|
||||
Accessibility:
|
||||
- Uses role="status" and aria-busy="true" for screen readers
|
||||
{% endcomment %}
|
||||
|
||||
{% with cards=cards|default:6 cols=cols|default:'auto' layout=layout|default:'vertical' show_image=show_image|default:True show_badge=show_badge|default:True show_footer=show_footer|default:True image_aspect=image_aspect|default:'video' animate=animate|default:True %}
|
||||
|
||||
<div class="skeleton-card-grid grid gap-4 sm:gap-6
|
||||
{% if cols == '2' %}grid-cols-1 sm:grid-cols-2
|
||||
{% elif cols == '3' %}grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
|
||||
{% elif cols == '4' %}grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4
|
||||
{% else %}grid-cols-1 sm:grid-cols-2 lg:grid-cols-3{% endif %}"
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label="Loading cards...">
|
||||
|
||||
{% for i in "123456789012"|slice:cards %}
|
||||
<div class="skeleton-card bg-card rounded-xl border border-border overflow-hidden
|
||||
{% if layout == 'horizontal' %}flex flex-row{% else %}flex flex-col{% endif %}">
|
||||
|
||||
{# Image placeholder #}
|
||||
{% if show_image %}
|
||||
<div class="{% if layout == 'horizontal' %}w-1/3 flex-shrink-0{% else %}w-full{% endif %}">
|
||||
<div class="{% if image_aspect == 'square' %}aspect-square{% elif image_aspect == 'portrait' %}aspect-[3/4]{% else %}aspect-video{% endif %} bg-muted {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.counter0 }}50ms;">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Content area #}
|
||||
<div class="flex-1 p-4 space-y-3">
|
||||
{# Badge placeholder #}
|
||||
{% if show_badge %}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-5 w-16 bg-muted rounded-full {% if animate %}animate-pulse{% endif %}" style="animation-delay: {{ forloop.counter0 }}75ms;"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Title #}
|
||||
<div class="space-y-2">
|
||||
<div class="h-5 bg-muted rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="width: {% widthratio forloop.counter0 1 3 %}5%; animation-delay: {{ forloop.counter }}00ms;">
|
||||
</div>
|
||||
<div class="h-4 w-3/4 bg-muted/70 rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.counter }}25ms;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Description lines #}
|
||||
<div class="space-y-2 pt-2">
|
||||
<div class="h-3 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: {{ forloop.counter }}50ms;"></div>
|
||||
<div class="h-3 w-5/6 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: {{ forloop.counter }}75ms;"></div>
|
||||
</div>
|
||||
|
||||
{# Footer with stats #}
|
||||
{% if show_footer %}
|
||||
<div class="flex items-center justify-between pt-3 mt-auto border-t border-border">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="h-4 w-16 bg-muted/50 rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="h-4 w-12 bg-muted/50 rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
<div class="h-4 w-20 bg-muted/50 rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<span class="sr-only">Loading cards, please wait...</span>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
118
backend/templates/components/skeletons/detail_skeleton.html
Normal file
118
backend/templates/components/skeletons/detail_skeleton.html
Normal file
@@ -0,0 +1,118 @@
|
||||
{% comment %}
|
||||
Detail Page Skeleton Component
|
||||
==============================
|
||||
|
||||
Animated skeleton placeholder for detail pages while content loads.
|
||||
|
||||
Purpose:
|
||||
Displays pulsing skeleton elements for detail page layouts including
|
||||
header, image, and content sections.
|
||||
|
||||
Usage Examples:
|
||||
Basic detail skeleton:
|
||||
{% include 'components/skeletons/detail_skeleton.html' %}
|
||||
|
||||
With image placeholder:
|
||||
{% include 'components/skeletons/detail_skeleton.html' with show_image=True %}
|
||||
|
||||
Custom content sections:
|
||||
{% include 'components/skeletons/detail_skeleton.html' with sections=4 %}
|
||||
|
||||
Parameters:
|
||||
Optional:
|
||||
- show_image: Show large image placeholder (default: True)
|
||||
- show_badge: Show status badge placeholder (default: True)
|
||||
- show_meta: Show metadata row (default: True)
|
||||
- show_actions: Show action buttons placeholder (default: True)
|
||||
- sections: Number of content sections (default: 3)
|
||||
- paragraphs_per_section: Lines per section (default: 4)
|
||||
- animate: Enable pulse animation (default: True)
|
||||
|
||||
Dependencies:
|
||||
- Tailwind CSS for styling and animation
|
||||
|
||||
Accessibility:
|
||||
- Uses role="status" and aria-busy="true" for screen readers
|
||||
{% endcomment %}
|
||||
|
||||
{% with show_image=show_image|default:True show_badge=show_badge|default:True show_meta=show_meta|default:True show_actions=show_actions|default:True sections=sections|default:3 paragraphs_per_section=paragraphs_per_section|default:4 animate=animate|default:True %}
|
||||
|
||||
<div class="skeleton-detail space-y-6"
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label="Loading page content...">
|
||||
|
||||
{# Header Section #}
|
||||
<div class="skeleton-detail-header space-y-4">
|
||||
{# Breadcrumb placeholder #}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-3 w-12 bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="h-3 w-3 bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="h-3 w-20 bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="h-3 w-3 bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="h-3 w-32 bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
|
||||
{# Title and badge row #}
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div class="space-y-2">
|
||||
{# Title #}
|
||||
<div class="h-8 sm:h-10 w-64 sm:w-80 bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
|
||||
{# Subtitle/location #}
|
||||
<div class="h-4 w-48 bg-muted/70 rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: 100ms;"></div>
|
||||
</div>
|
||||
|
||||
{% if show_badge %}
|
||||
{# Status badge #}
|
||||
<div class="h-7 w-24 bg-muted rounded-full {% if animate %}animate-pulse{% endif %}"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Meta row (date, author, etc.) #}
|
||||
{% if show_meta %}
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm">
|
||||
<div class="h-4 w-32 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: 150ms;"></div>
|
||||
<div class="h-4 w-24 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: 200ms;"></div>
|
||||
<div class="h-4 w-28 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: 250ms;"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Action buttons #}
|
||||
{% if show_actions %}
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div class="h-10 w-28 bg-muted rounded-lg {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="h-10 w-24 bg-muted/80 rounded-lg {% if animate %}animate-pulse{% endif %}" style="animation-delay: 50ms;"></div>
|
||||
<div class="h-10 w-10 bg-muted/60 rounded-lg {% if animate %}animate-pulse{% endif %}" style="animation-delay: 100ms;"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Image Section #}
|
||||
{% if show_image %}
|
||||
<div class="skeleton-detail-image">
|
||||
<div class="w-full aspect-video bg-muted rounded-xl {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Content Sections #}
|
||||
<div class="skeleton-detail-content space-y-8">
|
||||
{% for s in "1234567890"|slice:sections %}
|
||||
<div class="space-y-3">
|
||||
{# Section heading #}
|
||||
<div class="h-6 w-48 bg-muted rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: {{ forloop.counter0 }}50ms;"></div>
|
||||
|
||||
{# Paragraph lines #}
|
||||
{% for p in "12345678"|slice:paragraphs_per_section %}
|
||||
<div class="h-4 bg-muted/{% if forloop.last %}50{% else %}70{% endif %} rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="width: {% if forloop.last %}65{% else %}{% widthratio forloop.counter0 1 5 %}5{% endif %}%; animation-delay: {{ forloop.counter }}00ms;">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<span class="sr-only">Loading content, please wait...</span>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
119
backend/templates/components/skeletons/form_skeleton.html
Normal file
119
backend/templates/components/skeletons/form_skeleton.html
Normal file
@@ -0,0 +1,119 @@
|
||||
{% comment %}
|
||||
Form Skeleton Component
|
||||
=======================
|
||||
|
||||
Animated skeleton placeholder for forms while content loads.
|
||||
|
||||
Purpose:
|
||||
Displays pulsing skeleton form elements including labels, inputs,
|
||||
and action buttons.
|
||||
|
||||
Usage Examples:
|
||||
Basic form skeleton:
|
||||
{% include 'components/skeletons/form_skeleton.html' %}
|
||||
|
||||
Custom field count:
|
||||
{% include 'components/skeletons/form_skeleton.html' with fields=6 %}
|
||||
|
||||
Without textarea:
|
||||
{% include 'components/skeletons/form_skeleton.html' with show_textarea=False %}
|
||||
|
||||
Compact form:
|
||||
{% include 'components/skeletons/form_skeleton.html' with size='sm' %}
|
||||
|
||||
Parameters:
|
||||
Optional:
|
||||
- fields: Number of input fields (default: 4)
|
||||
- show_textarea: Show a textarea field (default: True)
|
||||
- show_checkbox: Show checkbox fields (default: False)
|
||||
- show_select: Show select dropdown (default: True)
|
||||
- checkbox_count: Number of checkboxes (default: 3)
|
||||
- size: 'sm', 'md', 'lg' for field sizes (default: 'md')
|
||||
- animate: Enable pulse animation (default: True)
|
||||
|
||||
Dependencies:
|
||||
- Tailwind CSS for styling and animation
|
||||
|
||||
Accessibility:
|
||||
- Uses role="status" and aria-busy="true" for screen readers
|
||||
{% endcomment %}
|
||||
|
||||
{% with fields=fields|default:4 show_textarea=show_textarea|default:True show_checkbox=show_checkbox|default:False show_select=show_select|default:True checkbox_count=checkbox_count|default:3 size=size|default:'md' animate=animate|default:True %}
|
||||
|
||||
<div class="skeleton-form space-y-{% if size == 'sm' %}4{% elif size == 'lg' %}8{% else %}6{% endif %}"
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label="Loading form...">
|
||||
|
||||
{# Regular input fields #}
|
||||
{% for i in "12345678"|slice:fields %}
|
||||
<div class="skeleton-form-field space-y-{% if size == 'sm' %}1{% elif size == 'lg' %}2{% else %}1.5{% endif %}">
|
||||
{# Label #}
|
||||
<div class="{% if size == 'sm' %}h-3 w-20{% elif size == 'lg' %}h-5 w-28{% else %}h-4 w-24{% endif %} bg-muted rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.counter0 }}50ms;">
|
||||
</div>
|
||||
|
||||
{# Input #}
|
||||
<div class="{% if size == 'sm' %}h-8{% elif size == 'lg' %}h-12{% else %}h-10{% endif %} w-full bg-muted/70 rounded-lg border border-muted {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.counter0 }}75ms;">
|
||||
</div>
|
||||
|
||||
{# Help text (occasionally) #}
|
||||
{% if forloop.counter|divisibleby:2 %}
|
||||
<div class="{% if size == 'sm' %}h-2 w-48{% elif size == 'lg' %}h-4 w-64{% else %}h-3 w-56{% endif %} bg-muted/50 rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.counter }}00ms;">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# Select dropdown #}
|
||||
{% if show_select %}
|
||||
<div class="skeleton-form-field space-y-{% if size == 'sm' %}1{% elif size == 'lg' %}2{% else %}1.5{% endif %}">
|
||||
<div class="{% if size == 'sm' %}h-3 w-24{% elif size == 'lg' %}h-5 w-32{% else %}h-4 w-28{% endif %} bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="{% if size == 'sm' %}h-8{% elif size == 'lg' %}h-12{% else %}h-10{% endif %} w-full bg-muted/70 rounded-lg border border-muted {% if animate %}animate-pulse{% endif %} relative">
|
||||
{# Dropdown arrow indicator #}
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<div class="{% if size == 'sm' %}w-3 h-3{% elif size == 'lg' %}w-5 h-5{% else %}w-4 h-4{% endif %} bg-muted/90 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Textarea #}
|
||||
{% if show_textarea %}
|
||||
<div class="skeleton-form-field space-y-{% if size == 'sm' %}1{% elif size == 'lg' %}2{% else %}1.5{% endif %}">
|
||||
<div class="{% if size == 'sm' %}h-3 w-28{% elif size == 'lg' %}h-5 w-36{% else %}h-4 w-32{% endif %} bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="{% if size == 'sm' %}h-20{% elif size == 'lg' %}h-40{% else %}h-32{% endif %} w-full bg-muted/70 rounded-lg border border-muted {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="{% if size == 'sm' %}h-2 w-40{% elif size == 'lg' %}h-4 w-56{% else %}h-3 w-48{% endif %} bg-muted/50 rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Checkboxes #}
|
||||
{% if show_checkbox %}
|
||||
<div class="skeleton-form-checkboxes space-y-3">
|
||||
<div class="{% if size == 'sm' %}h-3 w-32{% elif size == 'lg' %}h-5 w-40{% else %}h-4 w-36{% endif %} bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
{% for c in "12345"|slice:checkbox_count %}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="{% if size == 'sm' %}w-4 h-4{% elif size == 'lg' %}w-6 h-6{% else %}w-5 h-5{% endif %} bg-muted/70 rounded border border-muted {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="{% if size == 'sm' %}h-3{% elif size == 'lg' %}h-5{% else %}h-4{% endif %} bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="width: {% widthratio forloop.counter0 1 4 %}0%;">
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Form actions #}
|
||||
<div class="skeleton-form-actions flex items-center justify-end gap-3 pt-{% if size == 'sm' %}3{% elif size == 'lg' %}6{% else %}4{% endif %} mt-{% if size == 'sm' %}3{% elif size == 'lg' %}6{% else %}4{% endif %} border-t border-border">
|
||||
{# Cancel button #}
|
||||
<div class="{% if size == 'sm' %}h-8 w-20{% elif size == 'lg' %}h-12 w-28{% else %}h-10 w-24{% endif %} bg-muted/60 rounded-lg {% if animate %}animate-pulse{% endif %}"></div>
|
||||
|
||||
{# Submit button #}
|
||||
<div class="{% if size == 'sm' %}h-8 w-24{% elif size == 'lg' %}h-12 w-32{% else %}h-10 w-28{% endif %} bg-muted rounded-lg {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
|
||||
<span class="sr-only">Loading form, please wait...</span>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
85
backend/templates/components/skeletons/list_skeleton.html
Normal file
85
backend/templates/components/skeletons/list_skeleton.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{% comment %}
|
||||
List Skeleton Component
|
||||
=======================
|
||||
|
||||
Animated skeleton placeholder for list items while content loads.
|
||||
|
||||
Purpose:
|
||||
Displays pulsing skeleton rows to indicate loading state for list views,
|
||||
reducing perceived loading time and preventing layout shift.
|
||||
|
||||
Usage Examples:
|
||||
Basic list skeleton:
|
||||
{% include 'components/skeletons/list_skeleton.html' %}
|
||||
|
||||
Custom row count:
|
||||
{% include 'components/skeletons/list_skeleton.html' with rows=10 %}
|
||||
|
||||
With avatar placeholder:
|
||||
{% include 'components/skeletons/list_skeleton.html' with show_avatar=True %}
|
||||
|
||||
Compact variant:
|
||||
{% include 'components/skeletons/list_skeleton.html' with size='sm' %}
|
||||
|
||||
Parameters:
|
||||
Optional:
|
||||
- rows: Number of skeleton rows to display (default: 5)
|
||||
- show_avatar: Show circular avatar placeholder (default: False)
|
||||
- show_meta: Show metadata line below title (default: True)
|
||||
- show_action: Show action button placeholder (default: False)
|
||||
- size: 'sm', 'md', 'lg' for padding/spacing (default: 'md')
|
||||
- animate: Enable pulse animation (default: True)
|
||||
|
||||
Dependencies:
|
||||
- Tailwind CSS for styling and animation
|
||||
|
||||
Accessibility:
|
||||
- Uses role="status" and aria-busy="true" for screen readers
|
||||
- aria-label describes loading state
|
||||
{% endcomment %}
|
||||
|
||||
{% with rows=rows|default:5 show_avatar=show_avatar|default:False show_meta=show_meta|default:True show_action=show_action|default:False size=size|default:'md' animate=animate|default:True %}
|
||||
|
||||
<div class="skeleton-list space-y-{% if size == 'sm' %}2{% elif size == 'lg' %}6{% else %}4{% endif %}"
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label="Loading list items...">
|
||||
|
||||
{% for i in "12345678901234567890"|slice:rows %}
|
||||
<div class="skeleton-list-item flex items-center gap-{% if size == 'sm' %}2{% elif size == 'lg' %}4{% else %}3{% endif %} {% if size == 'sm' %}p-2{% elif size == 'lg' %}p-5{% else %}p-4{% endif %} bg-card rounded-lg border border-border">
|
||||
|
||||
{# Avatar placeholder #}
|
||||
{% if show_avatar %}
|
||||
<div class="flex-shrink-0">
|
||||
<div class="{% if size == 'sm' %}w-8 h-8{% elif size == 'lg' %}w-14 h-14{% else %}w-10 h-10{% endif %} rounded-full bg-muted {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Content area #}
|
||||
<div class="flex-1 min-w-0 space-y-{% if size == 'sm' %}1{% elif size == 'lg' %}3{% else %}2{% endif %}">
|
||||
{# Title line #}
|
||||
<div class="{% if size == 'sm' %}h-3{% elif size == 'lg' %}h-5{% else %}h-4{% endif %} bg-muted rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="width: {% widthratio forloop.counter0 1 7 %}0%;">
|
||||
</div>
|
||||
|
||||
{# Meta line #}
|
||||
{% if show_meta %}
|
||||
<div class="{% if size == 'sm' %}h-2{% elif size == 'lg' %}h-4{% else %}h-3{% endif %} bg-muted/70 rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="width: {% widthratio forloop.counter0 1 5 %}0%; animation-delay: {{ forloop.counter0 }}00ms;">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Action button placeholder #}
|
||||
{% if show_action %}
|
||||
<div class="flex-shrink-0">
|
||||
<div class="{% if size == 'sm' %}w-16 h-6{% elif size == 'lg' %}w-24 h-10{% else %}w-20 h-8{% endif %} bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<span class="sr-only">Loading content, please wait...</span>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
137
backend/templates/components/skeletons/table_skeleton.html
Normal file
137
backend/templates/components/skeletons/table_skeleton.html
Normal file
@@ -0,0 +1,137 @@
|
||||
{% comment %}
|
||||
Table Skeleton Component
|
||||
========================
|
||||
|
||||
Animated skeleton placeholder for data tables while content loads.
|
||||
|
||||
Purpose:
|
||||
Displays pulsing skeleton table rows for data-heavy pages like
|
||||
admin dashboards, moderation queues, and data exports.
|
||||
|
||||
Usage Examples:
|
||||
Basic table skeleton:
|
||||
{% include 'components/skeletons/table_skeleton.html' %}
|
||||
|
||||
Custom dimensions:
|
||||
{% include 'components/skeletons/table_skeleton.html' with rows=10 cols=6 %}
|
||||
|
||||
With checkbox column:
|
||||
{% include 'components/skeletons/table_skeleton.html' with show_checkbox=True %}
|
||||
|
||||
With action column:
|
||||
{% include 'components/skeletons/table_skeleton.html' with show_actions=True %}
|
||||
|
||||
Parameters:
|
||||
Optional:
|
||||
- rows: Number of table rows (default: 5)
|
||||
- cols: Number of data columns (default: 4)
|
||||
- show_header: Show table header row (default: True)
|
||||
- show_checkbox: Show checkbox column (default: False)
|
||||
- show_actions: Show actions column (default: True)
|
||||
- show_avatar: Show avatar in first column (default: False)
|
||||
- striped: Use striped row styling (default: False)
|
||||
- animate: Enable pulse animation (default: True)
|
||||
|
||||
Dependencies:
|
||||
- Tailwind CSS for styling and animation
|
||||
|
||||
Accessibility:
|
||||
- Uses role="status" and aria-busy="true" for screen readers
|
||||
{% endcomment %}
|
||||
|
||||
{% with rows=rows|default:5 cols=cols|default:4 show_header=show_header|default:True show_checkbox=show_checkbox|default:False show_actions=show_actions|default:True show_avatar=show_avatar|default:False striped=striped|default:False animate=animate|default:True %}
|
||||
|
||||
<div class="skeleton-table overflow-hidden rounded-lg border border-border"
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label="Loading table data...">
|
||||
|
||||
<table class="w-full">
|
||||
{# Table Header #}
|
||||
{% if show_header %}
|
||||
<thead class="bg-muted/30">
|
||||
<tr>
|
||||
{# Checkbox header #}
|
||||
{% if show_checkbox %}
|
||||
<th class="w-12 p-4">
|
||||
<div class="w-5 h-5 bg-muted rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</th>
|
||||
{% endif %}
|
||||
|
||||
{# Data column headers #}
|
||||
{% for c in "12345678"|slice:cols %}
|
||||
<th class="p-4 text-left">
|
||||
<div class="h-4 bg-muted rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="width: {% widthratio forloop.counter0 1 4 %}5%; animation-delay: {{ forloop.counter0 }}25ms;">
|
||||
</div>
|
||||
</th>
|
||||
{% endfor %}
|
||||
|
||||
{# Actions header #}
|
||||
{% if show_actions %}
|
||||
<th class="w-28 p-4 text-right">
|
||||
<div class="h-4 w-16 bg-muted rounded ml-auto {% if animate %}animate-pulse{% endif %}"></div>
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
|
||||
{# Table Body #}
|
||||
<tbody class="divide-y divide-border">
|
||||
{% for r in "12345678901234567890"|slice:rows %}
|
||||
<tr class="{% if striped and forloop.counter|divisibleby:2 %}bg-muted/10{% endif %}">
|
||||
{# Checkbox cell #}
|
||||
{% if show_checkbox %}
|
||||
<td class="p-4">
|
||||
<div class="w-5 h-5 bg-muted/70 rounded border border-muted {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.counter0 }}50ms;">
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
{# Data cells #}
|
||||
{% for c in "12345678"|slice:cols %}
|
||||
<td class="p-4">
|
||||
{% if forloop.first and show_avatar %}
|
||||
{# First column with avatar #}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-full bg-muted {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.parentloop.counter0 }}25ms;">
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="h-4 w-24 bg-muted rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.parentloop.counter0 }}50ms;">
|
||||
</div>
|
||||
<div class="h-3 w-32 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="animation-delay: {{ forloop.parentloop.counter0 }}75ms;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{# Regular data cell #}
|
||||
<div class="h-4 bg-muted/70 rounded {% if animate %}animate-pulse{% endif %}"
|
||||
style="width: {% widthratio forloop.counter0 cols 100 %}%; min-width: 40%; animation-delay: {{ forloop.parentloop.counter0 }}{{ forloop.counter0 }}0ms;">
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
{# Actions cell #}
|
||||
{% if show_actions %}
|
||||
<td class="p-4">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<div class="w-8 h-8 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}"></div>
|
||||
<div class="w-8 h-8 bg-muted/60 rounded {% if animate %}animate-pulse{% endif %}" style="animation-delay: 50ms;"></div>
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<span class="sr-only">Loading table data, please wait...</span>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
Reference in New Issue
Block a user