mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 12:11:13 -05:00
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:
@@ -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>
|
||||
|
||||
131
backend/templates/cotton/auth/login_form.html
Normal file
131
backend/templates/cotton/auth/login_form.html
Normal 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>
|
||||
17
backend/templates/cotton/auth/turnstile_empty.html
Normal file
17
backend/templates/cotton/auth/turnstile_empty.html
Normal 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 %}
|
||||
36
backend/templates/cotton/auth/turnstile_widget.html
Normal file
36
backend/templates/cotton/auth/turnstile_widget.html
Normal 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>
|
||||
82
backend/templates/cotton/ui/button.html
Normal file
82
backend/templates/cotton/ui/button.html
Normal 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>
|
||||
58
backend/templates/cotton/ui/card.html
Normal file
58
backend/templates/cotton/ui/card.html
Normal 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>
|
||||
51
backend/templates/cotton/ui/input.html
Normal file
51
backend/templates/cotton/ui/input.html
Normal 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 }}
|
||||
/>
|
||||
111
backend/templates/cotton/ui/pagination.html
Normal file
111
backend/templates/cotton/ui/pagination.html
Normal 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 %}
|
||||
158
backend/templates/cotton/ui/search_form.html
Normal file
158
backend/templates/cotton/ui/search_form.html
Normal 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>
|
||||
39
backend/templates/cotton/ui/status_badge.html
Normal file
39
backend/templates/cotton/ui/status_badge.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user