mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:31:07 -05:00
Add new component system for buttons, cards, and inputs
Integrates Django Cotton to replace existing UI components with a new templating system. Adds a test page to compare the new components against the old ones. Replit-Commit-Author: Agent Replit-Commit-Session-Id: dcfff319-6e91-4220-98a9-8295b87755b7 Replit-Commit-Checkpoint-Type: intermediate_checkpoint
This commit is contained in:
@@ -87,6 +87,7 @@ THIRD_PARTY_APPS = [
|
||||
"whitenoise",
|
||||
"django_tailwind_cli",
|
||||
"autocomplete", # Django HTMX Autocomplete
|
||||
"django_cotton", # Django Cotton for component templates
|
||||
"health_check", # Health checks
|
||||
"health_check.db",
|
||||
"health_check.cache",
|
||||
|
||||
@@ -62,6 +62,7 @@ dependencies = [
|
||||
"djangorestframework-simplejwt>=5.5.1",
|
||||
"django-forwardemail>=1.0.0",
|
||||
"django-cloudflareimages-toolkit>=1.0.6",
|
||||
"django-cotton>=2.1.3",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
|
||||
72
backend/templates/cotton/button.html
Normal file
72
backend/templates/cotton/button.html
Normal file
@@ -0,0 +1,72 @@
|
||||
{% comment %}
|
||||
Button Component - Django Cotton Version of shadcn/ui Button
|
||||
Usage: <c-button variant="default" size="default">Click me</c-button>
|
||||
Preserves EXACT same HTML output as the original include version
|
||||
{% endcomment %}
|
||||
|
||||
<c-vars
|
||||
variant="default"
|
||||
size="default"
|
||||
icon_left=""
|
||||
icon_right=""
|
||||
type=""
|
||||
onclick=""
|
||||
hx_get=""
|
||||
hx_post=""
|
||||
hx_target=""
|
||||
hx_swap=""
|
||||
x_data=""
|
||||
x_on=""
|
||||
disabled=""
|
||||
/>
|
||||
|
||||
<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 %}
|
||||
{{ class|default:'' }}
|
||||
"
|
||||
{% if type %}type="{{ type }}"{% endif %}
|
||||
{% 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 %}disabled{% endif %}
|
||||
{{ attrs }}
|
||||
>
|
||||
{% if icon_left %}
|
||||
<i class="{{ icon_left }} w-4 h-4"></i>
|
||||
{% endif %}
|
||||
|
||||
{{ slot }}
|
||||
|
||||
{% if icon_right %}
|
||||
<i class="{{ icon_right }} w-4 h-4"></i>
|
||||
{% endif %}
|
||||
</button>
|
||||
50
backend/templates/cotton/card.html
Normal file
50
backend/templates/cotton/card.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% comment %}
|
||||
Card Component - Django Cotton Version of shadcn/ui Card
|
||||
Usage: <c-card title="Card Title" description="Card description">Card content</c-card>
|
||||
Preserves EXACT same HTML output as the original include version
|
||||
{% endcomment %}
|
||||
|
||||
<c-vars
|
||||
title=""
|
||||
description=""
|
||||
content=""
|
||||
header_content=""
|
||||
body_content=""
|
||||
footer_content=""
|
||||
/>
|
||||
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm {{ class|default:'' }}">
|
||||
{% if title 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 %}
|
||||
{% if header_content %}
|
||||
{{ header_content|safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if content or body_content or slot %}
|
||||
<div class="p-6 pt-0">
|
||||
{% if content %}
|
||||
{{ content|safe }}
|
||||
{% endif %}
|
||||
{% if body_content %}
|
||||
{{ body_content|safe }}
|
||||
{% endif %}
|
||||
{% if slot %}
|
||||
{{ slot }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if footer_content %}
|
||||
<div class="flex items-center p-6 pt-0">
|
||||
{{ footer_content|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
47
backend/templates/cotton/input.html
Normal file
47
backend/templates/cotton/input.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{% comment %}
|
||||
Input Component - Django Cotton Version of shadcn/ui Input
|
||||
Usage: <c-input type="text" placeholder="Enter text..." name="field_name" />
|
||||
Preserves EXACT same HTML output as the original include version
|
||||
{% endcomment %}
|
||||
|
||||
<c-vars
|
||||
type="text"
|
||||
name=""
|
||||
id=""
|
||||
placeholder=""
|
||||
value=""
|
||||
required=""
|
||||
disabled=""
|
||||
readonly=""
|
||||
autocomplete=""
|
||||
x_model=""
|
||||
x_on=""
|
||||
hx_get=""
|
||||
hx_post=""
|
||||
hx_target=""
|
||||
hx_trigger=""
|
||||
hx_swap=""
|
||||
hx_include=""
|
||||
/>
|
||||
|
||||
<input
|
||||
type="{{ type|default:'text' }}"
|
||||
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 {{ class|default:'' }}"
|
||||
{% if name %}name="{{ name }}"{% endif %}
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% if placeholder %}placeholder="{{ placeholder }}"{% endif %}
|
||||
{% if value %}value="{{ value }}"{% endif %}
|
||||
{% if required %}required{% endif %}
|
||||
{% if disabled %}disabled{% endif %}
|
||||
{% if readonly %}readonly{% endif %}
|
||||
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}
|
||||
{% if x_model %}x-model="{{ x_model }}"{% 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_trigger %}hx-trigger="{{ hx_trigger }}"{% endif %}
|
||||
{% if hx_swap %}hx-swap="{{ hx_swap }}"{% endif %}
|
||||
{% if hx_include %}hx-include="{{ hx_include }}"{% endif %}
|
||||
{{ attrs }}
|
||||
/>
|
||||
654
backend/templates/test_button_comparison.html
Normal file
654
backend/templates/test_button_comparison.html
Normal file
@@ -0,0 +1,654 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UI Component Comparison Test</title>
|
||||
<link href="{% static 'css/tailwind.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
.test-section {
|
||||
margin: 2rem 0;
|
||||
padding: 1rem;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.button-pair {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.button-container {
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
.button-container::before {
|
||||
content: attr(data-label);
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 8px;
|
||||
background: white;
|
||||
padding: 0 4px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.test-description {
|
||||
font-weight: bold;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.html-output {
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 p-8">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold mb-8 text-center">UI Component Comparison Test</h1>
|
||||
<p class="text-center text-gray-600 mb-8">Comparing old include method vs new cotton component method for Button, Input, and Card components</p>
|
||||
|
||||
<!-- Test 1: All Variants with Default Size -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 1: All Variants (Default Size)</h2>
|
||||
|
||||
<div class="test-description">Default Variant</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='default' text='Default Button' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="default">Default Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Destructive Variant</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='destructive' text='Delete Item' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="destructive">Delete Item</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Outline Variant</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='outline' text='Outline Button' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="outline">Outline Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Secondary Variant</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='secondary' text='Secondary Button' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="secondary">Secondary Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Ghost Variant</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='ghost' text='Ghost Button' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="ghost">Ghost Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Link Variant</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='link' text='Link Button' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="link">Link Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 2: All Sizes with Default Variant -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 2: All Sizes (Default Variant)</h2>
|
||||
|
||||
<div class="test-description">Default Size</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with size='default' text='Default Size' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button size="default">Default Size</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Small Size</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with size='sm' text='Small Size' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button size="sm">Small Size</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Large Size</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with size='lg' text='Large Size' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button size="lg">Large Size</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Icon Size</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with size='icon' text='🔥' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button size="icon">🔥</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 3: HTMX Attributes -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 3: HTMX Attributes</h2>
|
||||
|
||||
<div class="test-description">hx-get Attribute</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with text='Load Content' hx_get='/api/content/' hx_target='#content' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button hx-get="/api/content/" hx-target="#content">Load Content</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">hx-post Attribute</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with text='Submit Form' hx_post='/api/submit/' hx_target='#result' hx_swap='innerHTML' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button hx-post="/api/submit/" hx-target="#result" hx-swap="innerHTML">Submit Form</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 4: Icons -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 4: Icons</h2>
|
||||
|
||||
<div class="test-description">Left Icon</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with text='Save' icon_left='fas fa-save' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button icon_left="fas fa-save">Save</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Right Icon</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with text='Next' icon_right='fas fa-arrow-right' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button icon_right="fas fa-arrow-right">Next</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 5: Additional Classes and Attributes -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 5: Additional Classes and Attributes</h2>
|
||||
|
||||
<div class="test-description">Custom Classes</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with text='Custom Button' class='border-2 border-red-500 shadow-lg' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button class="border-2 border-red-500 shadow-lg">Custom Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Type Attribute</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with text='Submit' type='submit' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button type="submit">Submit</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Disabled State</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with text='Disabled' disabled=True %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button disabled>Disabled</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 6: Complex Combinations -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 6: Complex Combinations</h2>
|
||||
|
||||
<div class="test-description">Destructive + Large + Icon + HTMX</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='destructive' size='lg' text='Delete All' icon_left='fas fa-trash' hx_post='/api/delete-all/' hx_target='#main' class='font-bold' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="destructive" size="lg" icon_left="fas fa-trash" hx-post="/api/delete-all/" hx-target="#main" class="font-bold">Delete All</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Outline + Small + Right Icon + Custom Attributes</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/button.html' with variant='outline' size='sm' text='Export' icon_right='fas fa-download' attrs='data-testid="export-btn" title="Export data"' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-button variant="outline" size="sm" icon_right="fas fa-download" data-testid="export-btn" title="Export data">Export</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 7: Legacy Underscore Props vs Modern Hyphenated Attributes -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 7: Legacy Underscore Props vs Modern Hyphenated Attributes</h2>
|
||||
<p class="text-sm text-gray-600 mb-4">This test verifies backward compatibility - cotton components should accept both legacy underscore props (hx_get, x_data) and modern hyphenated attributes (hx-get, x-data).</p>
|
||||
|
||||
<div class="test-description">Legacy hx_get vs Modern hx-get</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with hx_get">
|
||||
{% include 'components/ui/button.html' with text='Load Content' hx_get='/api/content/' hx_target='#content' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with hx_get">
|
||||
<c-button hx_get="/api/content/" hx_target="#content">Load Content</c-button>
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with hx-get">
|
||||
<c-button hx-get="/api/content/" hx-target="#content">Load Content</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Legacy hx_post vs Modern hx-post</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with hx_post">
|
||||
{% include 'components/ui/button.html' with text='Submit Form' hx_post='/api/submit/' hx_target='#result' hx_swap='innerHTML' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with hx_post">
|
||||
<c-button hx_post="/api/submit/" hx_target="#result" hx_swap="innerHTML">Submit Form</c-button>
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with hx-post">
|
||||
<c-button hx-post="/api/submit/" hx-target="#result" hx-swap="innerHTML">Submit Form</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Legacy x_data vs Modern x-data</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with x_data">
|
||||
{% include 'components/ui/button.html' with text='Alpine Button' x_data='{ clicked: false }' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with x_data">
|
||||
<c-button x_data="{ clicked: false }">Alpine Button</c-button>
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with x-data">
|
||||
<c-button x-data="{ clicked: false }">Alpine Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Legacy onclick vs Modern onclick</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with onclick">
|
||||
{% include 'components/ui/button.html' with text='Click Handler' onclick="alert('Clicked!')" %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with onclick">
|
||||
<c-button onclick="alert('Clicked!')">Click Handler</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Legacy type vs Modern type</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with type">
|
||||
{% include 'components/ui/button.html' with text='Submit Form' type='submit' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with type">
|
||||
<c-button type="submit">Submit Form</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Legacy disabled vs Modern disabled</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with disabled">
|
||||
{% include 'components/ui/button.html' with text='Disabled Button' disabled=True %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with disabled">
|
||||
<c-button disabled>Disabled Button</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Complex Legacy Props Combination</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with Legacy Props">
|
||||
{% include 'components/ui/button.html' with variant='destructive' size='lg' text='Legacy Complex' hx_post='/api/complex/' hx_target='#result' x_data='{ processing: false }' type='submit' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with Legacy Props">
|
||||
<c-button variant="destructive" size="lg" hx_post="/api/complex/" hx_target="#result" x_data="{ processing: false }" type="submit">Legacy Complex</c-button>
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with Modern Props">
|
||||
<c-button variant="destructive" size="lg" hx-post="/api/complex/" hx-target="#result" x-data="{ processing: false }" type="submit">Modern Complex</c-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Alpine.js x_on Special Case</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include with x_on">
|
||||
{% include 'components/ui/button.html' with text='Alpine Click' x_on='@click="clicked = true"' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with x_on">
|
||||
<c-button x_on="@click='clicked = true'">Alpine Click</c-button>
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton with @click directly">
|
||||
<c-button @click="clicked = true">Alpine Click</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 8: Input Component Tests -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 8: Input Component Comparison</h2>
|
||||
|
||||
<div class="test-description">Basic Text Input</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='text' placeholder='Enter your name' name='name' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="text" placeholder="Enter your name" name="name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Email Input with Value</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='email' placeholder='email@example.com' name='email' value='test@example.com' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="email" placeholder="email@example.com" name="email" value="test@example.com" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Password Input</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='password' placeholder='Password' name='password' required=True %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="password" placeholder="Password" name="password" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Number Input with Min/Max</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='number' placeholder='Age' name='age' attrs='min="18" max="100"' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="number" placeholder="Age" name="age" min="18" max="100" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Disabled Input</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='text' placeholder='Disabled input' name='disabled' disabled=True value='Cannot edit' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="text" placeholder="Disabled input" name="disabled" disabled value="Cannot edit" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Readonly Input</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='text' name='readonly' readonly=True value='Read only value' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="text" name="readonly" readonly value="Read only value" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Input with Custom Class</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='text' placeholder='Custom styled' name='custom' class='border-blue-500 bg-blue-50' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="text" placeholder="Custom styled" name="custom" class="border-blue-500 bg-blue-50" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Input with HTMX Attributes</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='text' placeholder='Search...' name='search' hx_get='/api/search/' hx_target='#results' hx_trigger='keyup changed delay:300ms' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="text" placeholder="Search..." name="search" hx-get="/api/search/" hx-target="#results" hx-trigger="keyup changed delay:300ms" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Input with Alpine.js</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/input.html' with type='text' placeholder='Alpine input' name='alpine' x_model='inputValue' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-input type="text" placeholder="Alpine input" name="alpine" x-model="inputValue" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 8: Card Component Tests -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">Test 8: Card Component Comparison</h2>
|
||||
|
||||
<div class="test-description">Basic Card with Title</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with title='Basic Card' content='This is a simple card with just a title and content.' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card title="Basic Card">This is a simple card with just a title and content.</c-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Card with Title and Description</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with title='Card with Description' description='This card has both a title and a description' content='Here is the main content of the card.' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card title="Card with Description" description="This card has both a title and a description">Here is the main content of the card.</c-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Card with Custom Header Content</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with header_content='<div class="flex items-center gap-2"><h3 class="text-lg font-bold">Custom Header</h3><span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">New</span></div>' content='This card has custom header content instead of a simple title.' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card header_content='<div class="flex items-center gap-2"><h3 class="text-lg font-bold">Custom Header</h3><span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">New</span></div>'>This card has custom header content instead of a simple title.</c-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Card with Body Content (instead of slot)</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with title='Body Content Card' body_content='<p class="text-gray-600">This content is passed via body_content parameter.</p><button class="mt-2 px-4 py-2 bg-blue-500 text-white rounded">Action Button</button>' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card title="Body Content Card" body_content='<p class="text-gray-600">This content is passed via body_content parameter.</p><button class="mt-2 px-4 py-2 bg-blue-500 text-white rounded">Action Button</button>'></c-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Card with Footer</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with title='Card with Footer' content='This card has footer content at the bottom.' footer_content='<button class="px-4 py-2 bg-gray-100 text-gray-700 rounded mr-2">Cancel</button><button class="px-4 py-2 bg-blue-500 text-white rounded">Save</button>' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card title="Card with Footer" footer_content='<button class="px-4 py-2 bg-gray-100 text-gray-700 rounded mr-2">Cancel</button><button class="px-4 py-2 bg-blue-500 text-white rounded">Save</button>'>This card has footer content at the bottom.</c-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Complete Card (All Sections)</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with title='Complete Card' description='This card has all possible sections' content='Main content goes here. This is the primary content area.' footer_content='<span class="text-sm text-gray-500">Last updated: Today</span>' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card title="Complete Card" description="This card has all possible sections" footer_content='<span class="text-sm text-gray-500">Last updated: Today</span>'>Main content goes here. This is the primary content area.</c-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Card with Custom Classes</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with title='Custom Styled Card' content='This card has custom styling applied.' class='border-2 border-green-200 bg-green-50 shadow-lg' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card title="Custom Styled Card" class="border-2 border-green-200 bg-green-50 shadow-lg">This card has custom styling applied.</c-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-description">Content-Only Card (No Header)</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-container" data-label="Include Version">
|
||||
{% include 'components/ui/card.html' with content='This card has only content, no header or footer sections.' %}
|
||||
</div>
|
||||
<div class="button-container" data-label="Cotton Version">
|
||||
<c-card>This card has only content, no header or footer sections.</c-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTML Output Comparison Section -->
|
||||
<div class="test-section">
|
||||
<h2 class="text-xl font-semibold mb-4">HTML Output Inspection</h2>
|
||||
<p class="text-sm text-gray-600 mb-4">Use browser dev tools to inspect the HTML output and ensure it's identical.</p>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h3 class="font-semibold mb-2">Include Version HTML:</h3>
|
||||
<div id="include-html" class="html-output">
|
||||
<!-- JavaScript will populate this -->
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold mb-2">Cotton Version HTML:</h3>
|
||||
<div id="cotton-html" class="html-output">
|
||||
<!-- JavaScript will populate this -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Function to normalize HTML for comparison
|
||||
function normalizeHTML(html) {
|
||||
return html
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/> </g, '><')
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Function to extract HTML from all component containers
|
||||
function extractComponentHTML() {
|
||||
const containers = document.querySelectorAll('.button-container');
|
||||
const includeHTMLs = [];
|
||||
const cottonHTMLs = [];
|
||||
let componentIndex = 1;
|
||||
|
||||
containers.forEach((container, index) => {
|
||||
const label = container.getAttribute('data-label');
|
||||
// Look for button, input, or div (card) elements
|
||||
const element = container.querySelector('button') ||
|
||||
container.querySelector('input') ||
|
||||
container.querySelector('div.rounded-lg');
|
||||
|
||||
if (element && label) {
|
||||
const html = element.outerHTML;
|
||||
const normalized = normalizeHTML(html);
|
||||
|
||||
if (label === 'Include Version') {
|
||||
includeHTMLs.push(`<!-- Component ${componentIndex} -->\n${normalized}\n`);
|
||||
} else if (label === 'Cotton Version') {
|
||||
cottonHTMLs.push(`<!-- Component ${componentIndex} -->\n${normalized}\n`);
|
||||
componentIndex++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('include-html').textContent = includeHTMLs.join('\n');
|
||||
document.getElementById('cotton-html').textContent = cottonHTMLs.join('\n');
|
||||
}
|
||||
|
||||
// Extract HTML after page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setTimeout(extractComponentHTML, 100);
|
||||
});
|
||||
|
||||
// Function to compare HTML outputs
|
||||
function compareHTML() {
|
||||
const includeHTML = document.getElementById('include-html').textContent;
|
||||
const cottonHTML = document.getElementById('cotton-html').textContent;
|
||||
|
||||
if (includeHTML === cottonHTML) {
|
||||
alert('✅ HTML outputs are identical!');
|
||||
} else {
|
||||
alert('❌ HTML outputs differ. Check the HTML Output section for details.');
|
||||
console.log('Include HTML:', includeHTML);
|
||||
console.log('Cotton HTML:', cottonHTML);
|
||||
}
|
||||
}
|
||||
|
||||
// Add compare button
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const compareBtn = document.createElement('button');
|
||||
compareBtn.textContent = 'Compare HTML Outputs';
|
||||
compareBtn.className = 'fixed bottom-4 right-4 bg-blue-500 text-white px-4 py-2 rounded shadow-lg hover:bg-blue-600';
|
||||
compareBtn.onclick = compareHTML;
|
||||
document.body.appendChild(compareBtn);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -101,6 +101,8 @@ urlpatterns = [
|
||||
views.environment_and_settings_view,
|
||||
name="environment_and_settings",
|
||||
),
|
||||
# Button component testing
|
||||
path("test-button/", views.test_button_comparison, name="test_button_comparison"),
|
||||
]
|
||||
|
||||
# Add autocomplete URLs if available
|
||||
|
||||
@@ -156,3 +156,11 @@ def environment_and_settings_view(request):
|
||||
"environment_and_settings.html",
|
||||
{"env_vars": env_vars, "settings_vars": settings_vars},
|
||||
)
|
||||
|
||||
|
||||
def test_button_comparison(request):
|
||||
"""
|
||||
Test view to compare cotton button component with original include version.
|
||||
Renders a comprehensive test page with all button variants and combinations.
|
||||
"""
|
||||
return render(request, "test_button_comparison.html")
|
||||
|
||||
16
backend/uv.lock
generated
16
backend/uv.lock
generated
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
@@ -642,6 +642,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/d8/19ed1e47badf477d17fb177c1c19b5a21da0fd2d9f093f23be3fb86c5fab/django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449", size = 12809, upload-time = "2025-09-18T10:40:50.843Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-cotton"
|
||||
version = "2.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/99/36e318ebd1ace3fc874541a02e7e4149def8e21aab85aceb7bb01e17607b/django_cotton-2.1.3.tar.gz", hash = "sha256:737f9c088549d7febbf78532856ddf1270799675a4bc9fa191a5db0e195a9c13", size = 23432, upload-time = "2025-06-30T17:31:29.29Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/ec/5e5318af0304962be43e3b912aef024d8ac08c0f9a9dfcc4f0cd55d0e74e/django_cotton-2.1.3-py3-none-any.whl", hash = "sha256:f33658d05a8f5ecf7448bdf1089e2ad27d2ce42e59c752216129701d7d153c89", size = 22214, upload-time = "2025-06-30T17:31:28.093Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-debug-toolbar"
|
||||
version = "6.0.0"
|
||||
@@ -2238,6 +2250,7 @@ dependencies = [
|
||||
{ name = "django-cleanup" },
|
||||
{ name = "django-cloudflareimages-toolkit" },
|
||||
{ name = "django-cors-headers" },
|
||||
{ name = "django-cotton" },
|
||||
{ name = "django-debug-toolbar" },
|
||||
{ name = "django-environ" },
|
||||
{ name = "django-extensions" },
|
||||
@@ -2309,6 +2322,7 @@ requires-dist = [
|
||||
{ name = "django-cleanup", specifier = ">=8.0.0" },
|
||||
{ name = "django-cloudflareimages-toolkit", specifier = ">=1.0.6" },
|
||||
{ name = "django-cors-headers", specifier = ">=4.3.1" },
|
||||
{ name = "django-cotton", specifier = ">=2.1.3" },
|
||||
{ name = "django-debug-toolbar", specifier = ">=4.0.0" },
|
||||
{ name = "django-environ", specifier = ">=0.12.0" },
|
||||
{ name = "django-extensions", specifier = ">=4.1" },
|
||||
|
||||
Reference in New Issue
Block a user