Update website to use new reusable components for common elements

Refactor HTML templates to incorporate Django Cotton components for buttons, forms, and other UI elements.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: eff39de1-3afa-446d-a965-acaf61837fc7
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/d6d61dac-164d-45dd-929f-7dcdfd771b64/eff39de1-3afa-446d-a965-acaf61837fc7/55dLPZG
This commit is contained in:
pac7
2025-09-21 13:34:51 +00:00
committed by pacnpal
parent 26ff320806
commit 0cf6805c18
10 changed files with 688 additions and 4 deletions

View File

@@ -4,6 +4,7 @@ Includes: Browse menu, advanced search, theme toggle, user dropdown, mobile menu
{% endcomment %}
{% load static %}
{% load cotton %}
<header class="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div class="flex h-14 items-center justify-between px-4 max-w-full">
@@ -149,7 +150,7 @@ Includes: Browse menu, advanced search, theme toggle, user dropdown, mobile menu
hx-include="this"
name="q"
/>
{% include 'components/ui/button.html' with variant='default' size='sm' text='Search' class='absolute right-1 top-1/2 transform -translate-y-1/2' %}
<c-ui.button variant="default" size="sm" text="Search" button_classes="absolute right-1 top-1/2 transform -translate-y-1/2" />
</div>
<!-- Search Results Dropdown -->
@@ -315,7 +316,7 @@ Includes: Browse menu, advanced search, theme toggle, user dropdown, mobile menu
hx-swap="beforeend"
class="cursor-pointer"
>
{% include 'components/ui/button.html' with variant='outline' size='sm' text='Login' %}
<c-ui.button variant="outline" size="sm" text="Login" />
</div>
<div
hx-get="{% url 'account_signup' %}"
@@ -323,7 +324,7 @@ Includes: Browse menu, advanced search, theme toggle, user dropdown, mobile menu
hx-swap="beforeend"
class="cursor-pointer"
>
{% include 'components/ui/button.html' with variant='default' size='sm' text='Join' %}
<c-ui.button variant="default" size="sm" text="Join" />
</div>
</div>
{% endif %}
@@ -440,7 +441,7 @@ Includes: Browse menu, advanced search, theme toggle, user dropdown, mobile menu
hx-include="this"
name="q"
/>
{% include 'components/ui/button.html' with variant='default' size='sm' text='Search' class='absolute right-1 top-1/2 transform -translate-y-1/2' %}
<c-ui.button variant="default" size="sm" text="Search" button_classes="absolute right-1 top-1/2 transform -translate-y-1/2" />
</div>
<div id="mobile-search-results" class="mt-2"></div>
</div>

View File

@@ -0,0 +1,131 @@
{% comment %}
Cotton Login Form Component
Converts existing login form to use Django Cotton's component system
{% endcomment %}
{% load i18n %}
{% load account socialaccount %}
{% load turnstile_tags %}
<!-- Cotton Login Form Component -->
<c-vars
form_classes="form_classes|default:'space-y-6'"
show_remember="show_remember|default:'true'"
show_forgot_password="show_forgot_password|default:'true'"
button_text="button_text|default:'Sign In'"
hx_target="hx_target|default:'this'"
hx_swap="hx_swap|default:'outerHTML'"
/>
<form
class="{{ form_classes }}"
hx-post="{% url 'account_login' %}"
hx-target="{{ hx_target }}"
hx-swap="{{ hx_swap }}"
hx-indicator="#login-indicator"
>
{% csrf_token %}
<!-- Form Errors -->
{% if form.non_field_errors %}
<div class="alert alert-error">
<div class="text-sm">{{ form.non_field_errors }}</div>
</div>
{% endif %}
<!-- Username/Email Field -->
<div>
<label for="id_login" class="form-label">
{% trans "Username or Email" %}
</label>
<input
type="text"
name="login"
id="id_login"
required
autocomplete="username email"
class="form-input"
/>
{% if form.login.errors %}
<p class="form-error">{{ form.login.errors }}</p>
{% endif %}
</div>
<!-- Password Field -->
<div>
<label for="id_password" class="form-label">
{% trans "Password" %}
</label>
<input
type="password"
name="password"
id="id_password"
required
autocomplete="current-password"
class="form-input"
/>
{% if form.password.errors %}
<p class="form-error">{{ form.password.errors }}</p>
{% endif %}
</div>
<!-- Remember Me & Forgot Password -->
<div class="flex items-center justify-between">
{% if show_remember and show_remember != '' %}
<div class="flex items-center">
<input
type="checkbox"
name="remember"
id="id_remember"
class="w-4 h-4 border-gray-300 rounded text-primary focus:ring-primary/50 dark:border-gray-700"
/>
<label
for="id_remember"
class="block ml-2 text-sm text-gray-700 dark:text-gray-300"
>
{% trans "Remember me" %}
</label>
</div>
{% endif %}
{% if show_forgot_password and show_forgot_password != '' %}
<div class="text-sm">
<a
href="{% url 'account_reset_password' %}"
class="font-medium transition-colors text-primary hover:text-primary/80 focus:outline-hidden focus:underline"
>
{% trans "Forgot Password?" %}
</a>
</div>
{% endif %}
</div>
<!-- Turnstile Widget -->
<c-auth.turnstile_widget site_key="{{ site_key|default:'' }}" />
<!-- Redirect Field -->
{% if redirect_field_value %}
<input
type="hidden"
name="{{ redirect_field_name }}"
value="{{ redirect_field_value }}"
/>
{% endif %}
<!-- Submit Button -->
<c-slot name="submit-button">
<div>
<button type="submit" class="w-full btn-primary">
<i class="mr-2 fas fa-sign-in-alt"></i>
{{ button_text }}
</button>
</div>
</c-slot>
</form>
<!-- Loading Indicator -->
<div id="login-indicator" class="htmx-indicator">
<div class="flex items-center justify-center w-full py-4">
<div class="w-8 h-8 border-4 rounded-full border-primary border-t-transparent animate-spin"></div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
{% comment %}
Cotton Turnstile Empty Widget Component
Empty template when DEBUG is True - converts to Cotton format
{% endcomment %}
<!-- Cotton Turnstile Empty Component -->
<c-vars
debug_message="debug_message|default:'Turnstile widget disabled in DEBUG mode'"
show_debug_message="show_debug_message|default:''"
/>
<!-- Empty template when DEBUG is True -->
{% if show_debug_message and show_debug_message != '' %}
<div class="text-xs text-gray-500 text-center py-2">
{{ debug_message }}
</div>
{% endif %}

View File

@@ -0,0 +1,36 @@
{% comment %}
Cotton Turnstile Widget Component
Converts existing Cloudflare Turnstile widget to use Django Cotton's component system
{% endcomment %}
<!-- Cotton Turnstile Widget Component -->
<c-vars
site_key="site_key"
widget_classes="widget_classes|default:'turnstile'"
widget_id="widget_id|default:'turnstile-widget'"
theme="theme|default:'auto'"
/>
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async
defer
></script>
<div class="{{ widget_classes }}">
<div
id="{{ widget_id }}"
class="cf-turnstile"
data-sitekey="{{ site_key }}"
></div>
</div>
<script>
// Apply theme to the Turnstile widget based on the retrieved theme
document.addEventListener("DOMContentLoaded", function () {
const turnstileWidget = document.getElementById("{{ widget_id }}");
if (turnstileWidget) {
turnstileWidget.setAttribute("data-theme", "{{ theme }}");
}
});
</script>

View File

@@ -0,0 +1,82 @@
{% comment %}
Cotton Button Component - Django Template Version of shadcn/ui Button
Converts existing button component to use Django Cotton's component system
{% endcomment %}
{% load static %}
<!-- Cotton Button Component -->
<c-vars
variant="variant|default:'default'"
size="size|default:'default'"
button_classes="button_classes|default:''"
type="type|default:'button'"
disabled="disabled|default:''"
onclick="onclick|default:''"
x_data="x_data|default:''"
x_on="x_on|default:''"
hx_get="hx_get|default:''"
hx_post="hx_post|default:''"
hx_target="hx_target|default:''"
hx_swap="hx_swap|default:''"
icon_left="icon_left|default:''"
icon_right="icon_right|default:''"
text="text|default:''"
attrs="attrs|default:''"
/>
<button
class="
inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium
ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50
{% if variant == 'default' %}
bg-primary text-primary-foreground hover:bg-primary/90
{% elif variant == 'destructive' %}
bg-destructive text-destructive-foreground hover:bg-destructive/90
{% elif variant == 'outline' %}
border border-input bg-background hover:bg-accent hover:text-accent-foreground
{% elif variant == 'secondary' %}
bg-secondary text-secondary-foreground hover:bg-secondary/80
{% elif variant == 'ghost' %}
hover:bg-accent hover:text-accent-foreground
{% elif variant == 'link' %}
text-primary underline-offset-4 hover:underline
{% endif %}
{% if size == 'default' %}
h-10 px-4 py-2
{% elif size == 'sm' %}
h-9 rounded-md px-3
{% elif size == 'lg' %}
h-11 rounded-md px-8
{% elif size == 'icon' %}
h-10 w-10
{% endif %}
{{ button_classes }}
"
type="{{ type }}"
{% if onclick %}onclick="{{ onclick }}"{% endif %}
{% if hx_get %}hx-get="{{ hx_get }}"{% endif %}
{% if hx_post %}hx-post="{{ hx_post }}"{% endif %}
{% if hx_target %}hx-target="{{ hx_target }}"{% endif %}
{% if hx_swap %}hx-swap="{{ hx_swap }}"{% endif %}
{% if x_data %}x-data="{{ x_data }}"{% endif %}
{% if x_on %}{{ x_on }}{% endif %}
{% if disabled and disabled != '' %}disabled{% endif %}
{{ attrs }}
>
{% if icon_left %}
<i class="{{ icon_left }} w-4 h-4"></i>
{% endif %}
<!-- Button Text Content -->
<c-slot name="content">
{% if text %}
{{ text }}
{% endif %}
</c-slot>
{% if icon_right %}
<i class="{{ icon_right }} w-4 h-4"></i>
{% endif %}
</button>

View File

@@ -0,0 +1,58 @@
{% comment %}
Cotton Card Component - Django Template Version of shadcn/ui Card
Converts existing card component to use Django Cotton's component system
{% endcomment %}
<!-- Cotton Card Component -->
<c-vars
card_classes="card_classes|default:''"
title="title|default:''"
description="description|default:''"
header_content="header_content|default:''"
body_content="body_content|default:''"
footer_content="footer_content|default:''"
content="content|default:''"
/>
<div class="rounded-lg border bg-card text-card-foreground shadow-sm {{ card_classes }}">
{% if title or description or header_content %}
<div class="flex flex-col space-y-1.5 p-6">
{% if title %}
<h3 class="text-2xl font-semibold leading-none tracking-tight">{{ title }}</h3>
{% endif %}
{% if description %}
<p class="text-sm text-muted-foreground">{{ description }}</p>
{% endif %}
<!-- Card Header Slot -->
<c-slot name="header">
{% if header_content %}
{{ header_content|safe }}
{% endif %}
</c-slot>
</div>
{% endif %}
{% if content or body_content %}
<div class="p-6 pt-0">
<!-- Card Body Content -->
<c-slot name="content">
{% if content %}
{{ content|safe }}
{% endif %}
{% if body_content %}
{{ body_content|safe }}
{% endif %}
</c-slot>
</div>
{% endif %}
{% if footer_content %}
<div class="flex items-center p-6 pt-0">
<!-- Card Footer Slot -->
<c-slot name="footer">
{{ footer_content|safe }}
</c-slot>
</div>
{% endif %}
</div>

View File

@@ -0,0 +1,51 @@
{% comment %}
Cotton Input Component - Django Template Version of shadcn/ui Input
Converts existing input component to use Django Cotton's component system
{% endcomment %}
<!-- Cotton Input Component -->
<c-vars
type="type|default:'text'"
placeholder="placeholder|default:''"
name="name|default:''"
value="value|default:''"
id="id|default:''"
input_classes="input_classes|default:''"
disabled="disabled|default:''"
required="required|default:''"
readonly="readonly|default:''"
x_model="x_model|default:''"
x_data="x_data|default:''"
x_on="x_on|default:''"
hx_get="hx_get|default:''"
hx_post="hx_post|default:''"
hx_target="hx_target|default:''"
hx_swap="hx_swap|default:''"
attrs="attrs|default:''"
/>
<input
type="{{ type }}"
{% if placeholder %}placeholder="{{ placeholder }}"{% endif %}
{% if name %}name="{{ name }}"{% endif %}
{% if value %}value="{{ value }}"{% endif %}
{% if id %}id="{{ id }}"{% endif %}
class="
flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm
ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium
placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed
disabled:opacity-50 {{ input_classes }}
"
{% if disabled %}disabled{% endif %}
{% if required %}required{% endif %}
{% if readonly %}readonly{% endif %}
{% if x_model %}x-model="{{ x_model }}"{% endif %}
{% if x_data %}x-data="{{ x_data }}"{% endif %}
{% if x_on %}{{ x_on }}{% endif %}
{% if hx_get %}hx-get="{{ hx_get }}"{% endif %}
{% if hx_post %}hx-post="{{ hx_post }}"{% endif %}
{% if hx_target %}hx-target="{{ hx_target }}"{% endif %}
{% if hx_swap %}hx-swap="{{ hx_swap }}"{% endif %}
{{ attrs }}
/>

View File

@@ -0,0 +1,111 @@
{% comment %}
Cotton Pagination Component
Converts existing pagination component to use Django Cotton's component system
{% endcomment %}
<!-- Cotton Pagination Component -->
<c-vars
page_obj="page_obj"
pagination_classes="pagination_classes|default:''"
show_page_info="show_page_info|default:'true'"
/>
{% if page_obj %}
<div class="flex items-center justify-between px-2 {{ pagination_classes }}">
<!-- Mobile Navigation -->
<div class="flex-1 flex justify-between sm:hidden">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700">
Previous
</a>
{% else %}
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md dark:bg-gray-800 dark:text-gray-500 dark:border-gray-600">
Previous
</span>
{% endif %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}"
class="ml-3 relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700">
Next
</a>
{% else %}
<span class="ml-3 relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md dark:bg-gray-800 dark:text-gray-500 dark:border-gray-600">
Next
</span>
{% endif %}
</div>
<!-- Desktop Navigation -->
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<!-- Page Info (Optional) -->
{% if show_page_info and show_page_info != '' %}
<div>
<p class="text-sm text-gray-700 dark:text-gray-300">
Showing
<span class="font-medium">{{ page_obj.start_index }}</span>
to
<span class="font-medium">{{ page_obj.end_index }}</span>
of
<span class="font-medium">{{ page_obj.paginator.count }}</span>
results
</p>
</div>
{% else %}
<div></div>
{% endif %}
<!-- Page Numbers (Always Visible) -->
<div>
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
<!-- Previous Page -->
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:bg-gray-700">
<span class="sr-only">Previous</span>
<i class="fas fa-chevron-left h-5 w-5"></i>
</a>
{% else %}
<span class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-300 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-600">
<span class="sr-only">Previous</span>
<i class="fas fa-chevron-left h-5 w-5"></i>
</span>
{% endif %}
<!-- Page Numbers Logic -->
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<span class="relative inline-flex items-center px-4 py-2 border border-blue-500 bg-blue-50 text-sm font-medium text-blue-600 dark:bg-blue-900 dark:text-blue-300 dark:border-blue-700">
{{ num }}
</span>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a href="?page={{ num }}"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700">
{{ num }}
</a>
{% elif num == page_obj.number|add:'-4' or num == page_obj.number|add:'4' %}
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300">
...
</span>
{% endif %}
{% endfor %}
<!-- Next Page -->
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}"
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:bg-gray-700">
<span class="sr-only">Next</span>
<i class="fas fa-chevron-right h-5 w-5"></i>
</a>
{% else %}
<span class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-300 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-600">
<span class="sr-only">Next</span>
<i class="fas fa-chevron-right h-5 w-5"></i>
</span>
{% endif %}
</nav>
</div>
</div>
</div>
{% endif %}

View File

@@ -0,0 +1,158 @@
{% comment %}
Cotton Search Form Component
Converts existing search form component to use Django Cotton's component system
Preserves accessibility and structure from original component
{% endcomment %}
<!-- Cotton Search Form Component -->
<c-vars
placeholder="placeholder|default:'Search...'"
filters="filters|default:''"
form_classes="form_classes|default:'bg-white p-6 rounded-lg shadow-sm border border-gray-200 mb-6'"
show_sort="show_sort|default:''"
sort_options="sort_options|default:''"
/>
<form method="get" class="{{ form_classes }}">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- Search Input -->
<div class="col-span-1 md:col-span-2">
<label for="search" class="block text-sm font-medium text-gray-700 mb-1">
Search
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
type="text"
name="search"
id="search"
value="{{ request.GET.search }}"
placeholder="{{ placeholder }}"
class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
</div>
</div>
<!-- Filter Slots -->
<c-slot name="filters">
{% if filters %}
{% for filter in filters %}
<div>
<label for="{{ filter.name }}" class="block text-sm font-medium text-gray-700 mb-1">
{{ filter.label }}
</label>
{% if filter.type == 'select' %}
<select
name="{{ filter.name }}"
id="{{ filter.name }}"
class="block w-full px-3 py-2 border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<option value="">All {{ filter.label }}</option>
{% for option in filter.options %}
<option
value="{{ option.value }}"
{% if request.GET|get_item:filter.name == option.value %}selected{% endif %}
>
{{ option.label }}
</option>
{% endfor %}
</select>
{% elif filter.type == 'checkbox' %}
<div class="space-y-2">
{% for option in filter.options %}
<label class="flex items-center">
<input
type="checkbox"
name="{{ filter.name }}"
value="{{ option.value }}"
{% if option.value in request.GET|getlist:filter.name %}checked{% endif %}
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
>
<span class="ml-2 text-sm text-gray-700">{{ option.label }}</span>
</label>
{% endfor %}
</div>
{% elif filter.type == 'range' %}
<div class="flex space-x-2">
<input
type="number"
name="{{ filter.name }}_min"
value="{{ request.GET|get_item:filter.name_min }}"
placeholder="Min"
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<input
type="number"
name="{{ filter.name }}_max"
value="{{ request.GET|get_item:filter.name_max }}"
placeholder="Max"
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
</c-slot>
</div>
<!-- Action Buttons -->
<div class="mt-4 flex items-center justify-between">
<div class="flex space-x-3">
<button
type="submit"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
>
<svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
Search
</button>
{% if request.GET.urlencode %}
<a
href="{{ request.path }}"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
>
<svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
Clear
</a>
{% endif %}
</div>
<!-- Sort Options Slot -->
<c-slot name="sort-options">
{% if show_sort %}
<div class="flex items-center space-x-2">
<label for="ordering" class="text-sm font-medium text-gray-700">Sort by:</label>
<select
name="ordering"
id="ordering"
onchange="this.form.submit()"
class="block px-3 py-2 border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
{% for option in sort_options|default:"name,Name (A-Z);-name,Name (Z-A);created_at,Newest First;-created_at,Oldest First" %}
{% with option_parts=option|split:"," %}
<option
value="{{ option_parts.0 }}"
{% if request.GET.ordering == option_parts.0 %}selected{% endif %}
>
{{ option_parts.1 }}
</option>
{% endwith %}
{% endfor %}
</select>
</div>
{% endif %}
</c-slot>
</div>
</form>

View File

@@ -0,0 +1,39 @@
{% comment %}
Cotton Status Badge Component
Converts existing status badge component to use Django Cotton's component system
Preserves canonical status mapping from park_tags
{% endcomment %}
{% load park_tags %}
<!-- Cotton Status Badge Component -->
<c-vars
status="status|default:'UNKNOWN'"
badge_classes="badge_classes|default:''"
size="size|default:'default'"
/>
{% with status_config=status|get_status_config %}
<span class="
inline-flex items-center rounded-full font-medium
{% if size == 'sm' %}
px-2 py-1 text-xs
{% elif size == 'lg' %}
px-3 py-2 text-base
{% else %}
px-2.5 py-0.5 text-sm
{% endif %}
{{ status_config.classes }} {{ badge_classes }}
">
{% if status_config.icon %}
<svg class="-ml-0.5 mr-1.5 h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="3" />
</svg>
{% endif %}
<!-- Status Text Content -->
<c-slot name="status-text">
{{ status_config.label }}
</c-slot>
</span>
{% endwith %}