merge confs

This commit is contained in:
Will Abbott 2025-03-10 18:10:23 +00:00
commit 37e49caba5
46 changed files with 965 additions and 288 deletions

View file

@ -9,7 +9,7 @@ COPY . .
RUN ["npx", "tailwindcss", "-o", "./docs_project/static/app.css"]
# Use an official Python runtime as a base image
FROM python:3.12-slim-bookworm as base
FROM python:3.12-slim-bookworm AS base
# Setup env
ENV PIP_DISABLE_PIP_VERSION_CHECK=on \
@ -34,4 +34,7 @@ RUN poetry config virtualenvs.create false \
RUN SECRET_KEY=dummy STATIC_URL='/staticfiles/' python manage.py collectstatic --noinput --verbosity 2
# Now uninstall with poetry the container version of django-cotton, leaving the local version when we're developing
RUN poetry remove django-cotton
CMD [ "python", "manage.py", "runserver", "0.0.0.0:8000" ]

View file

@ -14,6 +14,10 @@
<h1 id="tabs">Re-usable tabs with alpine.js</h1>
<c-note>
Be sure to checkout the notes on alpine.js support <a href="{% url 'components' %}#alpine-js-support">here</a>.
</c-note>
<p>Let's tackle together a common UI requirement - tabs.</p>
<h3 class="mt-0 pt-2 mb-5" id="goals">We'll start by defining some goals:</h3>
@ -69,7 +73,7 @@
<p>Now we have the design right, let's chop it up into components.</p>
<h4>Tabs component</h4>
<h3>{{ '<c-tabs />'|force_escape }} component</h3>
<c-snippet label="cotton/tabs.html">{% cotton_verbatim %}{% verbatim %}
<div class="bg-white rounded-lg overflow-hidden border shadow">
@ -85,7 +89,7 @@
{% endcotton_verbatim %}{% endverbatim %}
</c-snippet>
<h4>Tab component</h4>
<h3>{{ '<c-tab />'|force_escape }} component</h3>
<c-snippet label="cotton/tab.html">{% cotton_verbatim %}{% verbatim %}
<div>
@ -194,13 +198,13 @@
</c-snippet>
<c-navigation>
<c-slot name="prev">
<a href="{% url 'form-fields' %}">Form Inputs</a>
</c-slot>
<c-slot name="next">
<a href="{% url 'layouts' %}">Layouts</a>
</c-slot>
</c-navigation>
<c-navigation>
<c-slot name="prev">
<a href="{% url 'form-fields' %}">Form Inputs</a>
</c-slot>
<c-slot name="next">
<a href="{% url 'layouts' %}">Layouts</a>
</c-slot>
</c-navigation>
</c-layouts.with-sidebar>

View file

@ -11,6 +11,7 @@
<c-index-sublink><a href="#boolean-attributes" class="no-underline">Boolean Attributes</a></c-index-sublink>
<c-index-sublink><a href="#dynamic-components" class="no-underline">Dynamic Components</a></c-index-sublink>
<c-index-sublink><a href="#context-isolation" class="no-underline">Context Isolation</a></c-index-sublink>
<c-index-sublink><a href="#alpine-js-support" class="no-underline">Alpine.js Support</a></c-index-sublink>
<c-index-sublink><a href="#summary" class="no-underline">Summary of concepts</a></c-index-sublink>
</c-slot>
@ -19,7 +20,7 @@
<h2>1. The basic building block: {% verbatim %}{{ slot }}{% endverbatim %}</h2>
<p>The <c-highlight>{% verbatim %}{{ slot }}{% endverbatim %}</c-highlight> variable captures all content passed between a component's opening and closing tags.</p>
<p>The <code>{% verbatim %}{{ slot }}{% endverbatim %}</code> variable captures all content passed between a component's opening and closing tags.</p>
<c-snippet label="cotton/box.html">{% cotton_verbatim %}{% verbatim %}
<div class="box">
@ -39,9 +40,9 @@
</c-slot>
</c-snippet>
<c-hr />
<c-hr id="attributes" />
<h2 id="attributes">2. Adding Component Attributes</h2>
<h2>2. Adding Component Attributes</h2>
<p>We can further customize components with attribute, which allow you to pass specific data into the component as key-value pairs.</p>
@ -58,11 +59,11 @@
</c-slot>
</c-snippet>
<c-hr />
<c-hr id="named-slots" />
<h2 id="named-slots">3. Using Named Slots</h2>
<h2>3. Using Named Slots</h2>
<p>If we want to pass HTML instead of just a string (or another data type) into a component, we can pass them as <c-highlight>named slots</c-highlight> with the <c-highlight>{{ '<c-slot name="...">...</c-slot>'|force_escape }}</c-highlight> syntax.</p>
<p>If we want to pass HTML instead of just a string (or another data type) into a component, we can pass them as <c-highlight>named slots</c-highlight> with the <code>{{ '<c-slot name="...">...</c-slot>'|force_escape }}</code> syntax.</p>
<p>So as with normal attributes, you reference the slot content like normal variables, as in:</p>
@ -93,9 +94,13 @@
</c-slot>
</c-snippet>
<c-hr />
<c-note>
Component filenames should be <c-highlight>snake_cased</c-highlight> by default. To use <c-highlight>kebab-cased</c-highlight> / hyphenated filenames instead, set <code>COTTON_SNAKE_CASED_NAMES</code> to <code>False</code> in your settings.py, <a href="{% url 'configuration' %}">more</a>.
</c-note>
<h2 id="dynamic-attributes">4. Dynamic Attributes with ":"</h2>
<c-hr id="dynamic-attributes" />
<h2>4. Dynamic Attributes with ":"</h2>
<p>We saw how by default, all attributes that we pass to a component are treated as strings. If we want to pass HTML, we can use named slots. But what if we want to pass another data type like a template variable, boolean, integer, float, dictionary, list, dictionary?</p>
@ -176,9 +181,9 @@ context = { 'today': Weather.objects.get(...) }
{% endcotton_verbatim %}{% endverbatim %}
</c-snippet>
<c-hr />
<c-hr id="attrs" />
<h2 id="attrs">5. Pass all attributes with {% verbatim %}{{ attrs }}{% endverbatim %}</h2>
<h2>5. Pass all attributes with {% verbatim %}{{ attrs }}{% endverbatim %}</h2>
<p>Sometimes it's useful to be able to reflect all attributes provided in the parent on to an HTML element in the component. This is particularly powerful when you are building <a href="{% url 'form-fields' %}">form inputs</a>.</p>
@ -204,17 +209,17 @@ context = { 'today': Weather.objects.get(...) }
{% endcotton_verbatim %}{% endverbatim %}
</c-snippet>
<c-hr />
<c-hr id="vars" />
<h2 id="vars">6. <c-highlight>{{ '<c-vars />'|force_escape }}</c-highlight>: Defining Local Variables</h2>
<h2>6. Defining Local Variables with <code>{{ '<c-vars />'|force_escape }}</code></h2>
<p>{{ '<c-vars />'|force_escape }} gives components local state and default behavior, making them more self-sufficient and reducing the need for repetitive attribute declarations and maintaining UI state in the backend.</p>
<p>The <code>{{ '<c-vars />'|force_escape }}</code> tag simplifies component design by allowing local variable definition, reducing the need for repetitive attribute declarations and maintaining backend state.</p>
<p>Vars are defined using a <c-highlight>{{ '<c-vars />'|force_escape }}</c-highlight> tag at the top of a component file. It can either contain key="value" pairs or just standalone keys (keep reading to understand why).</p>
<p>Place a single <code>{{ '<c-vars />'|force_escape }}</code> at the top of a component to set key-value pairs that provide a default configuration.</p>
<h3 id="default-attributes">Use {{ '<c-vars />'|force_escape }} to set attribute defaults</h3>
<h3>Example: Setting Default Attributes</h3>
<p>You may design a component that will often have a default behaviour and rarely needs overriding. In this case, you may opt to give a default value to your component.</p>
<p>In components with common defaults, <code>{{ '<c-vars />'|force_escape }}</code> can pre-define attributes that rarely need overriding.</p>
<c-snippet label="cotton/alert.html">{% cotton_verbatim %}{% verbatim %}
<c-vars type="success" />
@ -235,9 +240,9 @@ context = { 'today': Weather.objects.get(...) }
</c-slot>
</c-snippet>
<h3 id="excluded">{{ '<c-vars />'|force_escape }} are excluded from {% verbatim %}{{ attrs }}{% endverbatim %}</h3>
<h3>{{ '<c-vars />'|force_escape }} are excluded from {% verbatim %}{{ attrs }}{% endverbatim %}</h3>
<p>Keys defined in {{ '<c-vars />'|force_escape }} will not be included in {% verbatim %}{{ attrs }}{% endverbatim %}. This is useful when some of the properties you pass down to a component are for configuration purposes only and not intended as attributes.</p>
<p>Keys in <code>{{ '<c-vars />'|force_escape }}</code> are omitted from <code>{% verbatim %}{{ attrs }}{% endverbatim %}</code>, making them ideal for configuration attributes that shouldn't appear in HTML attributes.</p>
<c-snippet label="cotton/input_group.html">{% cotton_verbatim %}{% verbatim %}
<c-vars label errors />
@ -271,12 +276,11 @@ context = { 'today': Weather.objects.get(...) }
</c-slot>
</c-snippet>
<c-note>Specifying an attribute as a 'var' will remove the item from {% verbatim %}{{ attrs }}{% endverbatim %}. It can also be a single key without a default value, this is when you know a particular attribute should not end up in {% verbatim %}{{ attrs }}{% endverbatim %}, whether it's defined in a parent or not.</c-note>
<p>By specifying <code>label</code> and <code>errors</code> keys in <code>{{ '<c-vars />'|force_escape }}</code>, these attributes wont be included in <code>{% verbatim %}{{ attrs }}{% endverbatim %}</code>, allowing you to control attributes that are designed for component configuration and those intended as attributes.</p>
<c-hr id="boolean-attributes" />
<c-hr />
<h2 id="boolean-attributes">7. Boolean attributes</h2>
<h2>7. Boolean attributes</h2>
<p>Sometimes you just want to pass a simple boolean to a component. Cotton supports providing the attribute name without a value which will provide a boolean True to the component.</p>
@ -293,15 +297,15 @@ context = { 'today': Weather.objects.get(...) }
<c-input name="telephone" required />
{% endcotton_verbatim %}{% endverbatim %}
<c-slot name="preview">
<input type="text" autocomplete="off" class="border px-2 py-1 shadow rounded" name="telephone" placeholder="Telephone" /> <span class="text-red-500 text-3xl">*</span>
<input type="text" autocomplete="off" class="border px-2 py-1 shadow rounded" name="telephone" placeholder="Telephone" /> <span class="text-red-500 text-2xl">*</span>
</c-slot>
</c-snippet>
<c-hr />
<c-hr id="dynamic-components" />
<h2 id="dynamic-components">8. Dynamic Components</h2>
<h2>8. Dynamic Components</h2>
<p>There can be times where components need to be included dynamically. For these cases we can reach for a special <c-highlight>{{ '<c-component>'|force_escape }}</c-highlight> tag with an <c-highlight>is</c-highlight> attribute:</p>
<p>There can be times where components need to be included dynamically. For these cases we can reach for a special <code>{{ '<c-component>'|force_escape }}</code> tag with an <code>is</code> attribute:</p>
<c-snippet label="cotton/icon_list.html">{% cotton_verbatim %}{% verbatim %}
{% for icon in icons %}
@ -309,7 +313,7 @@ context = { 'today': Weather.objects.get(...) }
{% endfor %}
{% endcotton_verbatim %}{% endverbatim %}</c-snippet>
<p>The <c-highlight>is</c-highlight> attribute is similar to other attributes so we have a number of possibilities to define it:</p>
<p>The <code>is</code> attribute is similar to other attributes so we have a number of possibilities to define it:</p>
<c-snippet label="cotton/icon_list.html">{% cotton_verbatim %}{% verbatim %}
<!-- as variable -->
@ -319,12 +323,12 @@ context = { 'today': Weather.objects.get(...) }
<c-component is="icon_{{ icon_name }}" />
{% endcotton_verbatim %}{% endverbatim %}</c-snippet>
<c-hr />
<c-hr id="context-isolation" />
<h2 id="context-isolation">9. Context Isolation</h2>
<h2>9. Context Isolation</h2>
<p>Cotton is inspired by patterns found in frontend frameworks like React, Vue and Svelte. When working with these
patterns, state is not typically shared between components. This ensures data from other components do not 'leak' into
patterns, state is not typically shared between components. This ensures data from other components does not 'leak' into
others which can cause side effects that are difficult to trace.</p>
<p>Therefore, each component's context only contains, by default:</p>
@ -339,22 +343,34 @@ context = { 'today': Weather.objects.get(...) }
<p>You can pass the <c-highlight>only</c-highlight> attribute to the component, which will prevent it from adopting any context (incl. context processors) other than it's direct attributes.</p>
<h3>Retain legacy behaviour</h3>
<c-hr id="alpine-js-support" />
<p>Since context isolation is enabled by default since v2.0.0, there may be cases you wish to keep the legacy behaviour, for that reason, you can pass a `COTTON_CONTEXT_ISOLATION_ENABLED = False` in your settings.py. This is not recommend due to the aforementioned reason so allowing context isolation by default is encouraged.</p>
<h2>10. Alpine.js support</h2>
<c-hr />
<p>The following key features allow you to build re-usable components with alpine.js:</p>
<h2 id="summary">Summary of Concepts</h2>
<c-ul>
<li>
<code>x-data</code> is accessible as <code>{% verbatim %}{{ x_data }}{% endverbatim %}</code> inside the component as cotton makes available snake_case versions of all kebab-cased attributes. (If you use <code>{% verbatim %}{{ attrs }}{% endverbatim %}</code> then the output will already be in the correct case).
</li>
<li><a href="https://alpinejs.dev/directives/bind" target="_blank">Shorthand x-bind</a> support (<code>:example</code>). Because single <code>:</code> attribute prefixing is reserved for cotton's <a href="{% url 'components' %}#dynamic-attributes">dynamic attributes</a>,
we can escape the first colon using <code>::</code>. This will ensure the attribute maintains a single <code>:</code> inside <code>{% verbatim %}{{ attrs }}{% endverbatim %}</code>
</li>
</c-ul>
<c-hr id="summary" />
<h2>Summary of Concepts</h2>
<ul>
<li><c-highlight>{% verbatim %}{{ slot }}{% endverbatim %}</c-highlight> - Default content injection.</li>
<li><code>{% verbatim %}{{ slot }}{% endverbatim %}</code> - Default content injection.</li>
<li><c-highlight>Attributes</c-highlight> - Simple, straightforward customization.</li>
<li><c-highlight>Named Slots</c-highlight> - Provide HTML or template partial as a variable in the component.</li>
<li><c-highlight>`:` Dynamic Attributes</c-highlight> - Pass variables and other data types other than strings.</li>
<li><c-highlight>{% verbatim %}{{ attrs }}{% endverbatim %}</c-highlight> - Prints attributes as HTML attributes.</li>
<li><c-highlight>{{ '<c-vars />'|force_escape }}</c-highlight> - Set default values and other component state.</li>
<li><c-highlight>Boolean attributes</c-highlight> - Attributes without values are passed down as `:bool = True`</li>
<li><c-highlight>{{ '<c-component is=".." />'|force_escape }}</c-highlight> - Dynamically insert a component where the name is generated by a variable or template expression</li>
<li><c-highlight><code>:</code> Dynamic Attributes</c-highlight> - Pass variables and other data types other than strings.</li>
<li><code>{% verbatim %}{{ attrs }}{% endverbatim %}</code> - Prints attributes as HTML attributes.</li>
<li><code>{{ '<c-vars />'|force_escape }}</code> - Set default values and other component state.</li>
<li><c-highlight>Boolean attributes</c-highlight> - Attributes without values are passed down as <code>True</code></li>
<li><c-highlight>Dynamic Components</c-highlight> - Insert a component where the name is generated by a variable or template expression: <code>{{ '<c-component :is="my_variable" />'|force_escape }}</code>
</li>
</ul>
@ -363,7 +379,7 @@ context = { 'today': Weather.objects.get(...) }
<a href="{% url 'quickstart' %}">Quickstart</a>
</c-slot>
<c-slot name="next">
<a href="{% url 'layouts' %}">Layouts</a>
<a href="{% url 'usage-patterns' %}">Usage Patterns</a>
</c-slot>
</c-navigation>

View file

@ -3,15 +3,66 @@
<p>The following configuration can be provided in your `settings.py`:</p>
<h4>COTTON_DIR</h4>
<p>str (default: 'cotton')</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div>
<code>COTTON_DIR</code>
<div>str (default: 'cotton')</div>
</div>
<div>
Change the default path in your templates directory where cotton components can be placed, for example "components".
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div>
<code>COTTON_ENABLE_CONTEXT_ISOLATION</code>
<div>boolean (default: True)</div>
</div>
<div>
Set to `False` to allow global context to be available through all components. (see <a href="{% url 'components' %}#context-isolation">context isolation</a> for more information.
</div>
</div>
<c-hr />
<h4>COTTON_CONTEXT_ISOLATION_ENABLED</h4>
<p>str (default: 'True')</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div>
<code>COTTON_BASE_DIR</code>
<div>str (default: None)</div>
</div>
<div>
The base directory where - in addition to the app folders - cotton will search for the "templates" directory (see above).
If not set, the `BASE_DIR` generated by `django-admin startproject` is used as a fallback, if it exists.
</div>
</div>
<p>Set to `False` to allow global context to be available through all components. (see <a href="{% url 'components' %}#context-isolation">context isolation</a> for more information.</p>
<c-hr />
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div>
<code>COTTON_SNAKE_CASED_NAMES</code>
<div>bool (default: True)</div>
</div>
<div>
<div class="mb-4">By default cotton will look for snake case versions of your component names. To turn this behaviour off (useful if you want to permit hyphenated filenames) then set this key to <code>False</code>.</div>
<div class="mb-4">
<h6 class="mb-1">Example:</h6>
<code>{{ '<c-my-button />'|force_escape }}</code>
</div>
<div class="mb-4">
<h6>As <code>True</code> (default)</h6>
Filepath: `cotton/my_button.html`
</div>
<div>
<h6>As <code>False</code></h6>
Filepath: `cotton/my-button.html`
</div>
</div>
</div>
</c-layouts.with-sidebar>
</c-layouts.with-sidebar>

View file

@ -1 +1 @@
<span class="font-bold dark:text-white text-[#1a384a] font-semibold border-b-2 border-teal-600 border-opacity-70">{{ slot }}</span>
<span class="dark:text-white text-[#1a384a] font-semibold border-b-2 border-teal-600 border-opacity-70">{{ slot }}</span>

View file

@ -1 +1 @@
<div {{ attrs }} class="h-[2px] bg-yellow-900 bg-opacity-10 my-8 w-full dark:bg-gray-700"></div>
<div {{ attrs }} class="h-[1px] bg-yellow-900 bg-opacity-10 mb-6 mt-12 w-full dark:bg-gray-700"></div>

View file

@ -0,0 +1,3 @@
<svg {{ attrs }} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>

After

Width:  |  Height:  |  Size: 219 B

View file

@ -0,0 +1,3 @@
<svg {{ attrs }} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />
</svg>

After

Width:  |  Height:  |  Size: 219 B

View file

@ -0,0 +1,3 @@
<svg {{ attrs }} fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 24 24">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 12a1 1 0 1 0 2 0a1 1 0 0 0 -2 0" /><path d="M14 12a1 1 0 1 0 2 0a1 1 0 0 0 -2 0" /><path d="M15.5 17c0 1 1.5 3 2 3c1.5 0 2.833 -1.667 3.5 -3c.667 -1.667 .5 -5.833 -1.5 -11.5c-1.457 -1.015 -3 -1.34 -4.5 -1.5l-.972 1.923a11.913 11.913 0 0 0 -4.053 0l-.975 -1.923c-1.5 .16 -3.043 .485 -4.5 1.5c-2 5.667 -2.167 9.833 -1.5 11.5c.667 1.333 2 3 3.5 3c.5 0 2 -2 2 -3" /><path d="M7 16.5c3.5 1 6.5 1 10 0" />
</svg>

After

Width:  |  Height:  |  Size: 665 B

View file

@ -1,13 +1,3 @@
{% comment %}
<svg {{ attrs }} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"/>
</svg>{% endcomment %}
<svg {{ attrs }} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"/>
<path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7"/>
</svg>
<svg {{ attrs }} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" />
</svg>

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 412 B

Before After
Before After

View file

@ -1 +1 @@
<li class="py-0.5 pl-1 ml-5 list-disc marker:text-teal-600 text-[15px] text-[#1a384a] dark:text-gray-400">{{ slot }}</li>
<li class="py-0.5 pl-1 ml-5 list-disc marker:text-teal-600 text-[#1a384a] dark:text-gray-400">{{ slot }}</li>

View file

@ -11,6 +11,7 @@
<link rel="icon" type="image/png" href="{% static 'favicon/favicon.png' %}">
<script src="{% static 'highlight/highlight.min.js' %}"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<script defer src="{% static 'alpine3.min.js' %}"></script>
<link href="{% static 'highlight/styles/atom-one-dark.min.css' %}" rel="stylesheet" />
<script>

View file

@ -4,15 +4,76 @@
<c-sidebar />
</div>
<div class="overflow-auto flex-1 pb-10">
<div class="max-w-[760px] mx-auto px-0 pt-3 md:pt-0 md:px-5 py-0 prose !text-[16.5px] prose-a:text-teal-600 prose-h2:text-3xl prose-h3:text-2xl prose-headings:text-[#1a384a] text-[#1a384a] dark:text-white dark:text-opacity-70 prose-headings:font-semibold dark:prose-headings:text-white">
<div class="
max-w-[760px]
mx-auto
px-0
pt-3
md:pt-0
md:px-5
py-0
!text-[16.5px]
text-[#1a384a]
prose
prose-a:text-teal-600
prose-h1:text-3xl
prose-h2:text-2xl
prose-h3:text-xl
prose-headings:text-[#1a384a]
prose-headings:font-semibold
prose-code:before:content-none
prose-code:after:content-none
prose-code:bg-yellow-900/10
prose-code:rounded
prose-code:px-1
prose-code:inline-block
prose-code:my-0
dark:text-white
dark:text-opacity-70
dark:prose-headings:text-white
dark:prose-code:bg-gray-800
dark:prose-code:text-gray-300/80
dark:prose-strong:text-[#fff]
">
{% if page_index %}
<div class="lg:hidden not-prose mb-6">
<c-ui.collapse>
<c-slot name="trigger">
<c-ui.button theme="subtle" class="w-full">
<span class="flex w-full justify-between items-center">
<span>On this page</span>
<template x-if="!expanded">
<c-icons.chevron-down class="size-5" />
</template>
<template x-if="expanded">
<c-icons.chevron-up class="size-5" />
</template>
</span>
</c-ui.button>
</c-slot>
<div class="mt-5">
{{ page_index }}
</div>
<c-hr />
</c-ui.collapse>
</div>
{% endif %}
{{ slot }}
</div>
</div>
<div class="relative h-full pl-10 border-opacity-10 hidden lg:block">
<div class="relative h-full pl-10 border-opacity-10 hidden lg:block text-[15px]">
{% if page_index %}
<div class="sticky top-[68px] pt-0 pb-10">
<c-sidebar-heading>On this page</c-sidebar-heading>
<ul class="ml-0 pl-0 mt-4">
<ul class="ml-0 pl-0 mt-4 ">
{{ page_index }}
</ul>
</div>

View file

@ -43,13 +43,13 @@
<div class="flex items-center space-x-1">
<c-icons.logo class="w-8 h-8 text-teal-600" />
<span class="font-bold text-2xl ml-1.5 text-[#1a384a] dark:text-gray-100">cotton</span>
<div class="text-gray-700 dark:text-white text-sm opacity-50 mt-0.5 px-1">for</div>
<c-icons.django class="h-6 text-teal-600 mt-1.5" />
<div class=" text-gray-700 dark:text-white text-sm opacity-50 mt-0.5 px-1">for</div>
<c-icons.django class=" h-6 text-teal-600 mt-1.5" />
</div>
</a>
</div>
<div class="flex items-center space-x-3">
<div class="flex items-center space-x-2">
<a href="#" @click.prevent="toggleDark" x-data="{
'dark': false,
init() {
@ -64,10 +64,13 @@
localStorage.theme = this.dark ? 'dark' : 'light'
}
}">
<c-icons.sun class="size-8 text-yellow-900 opacity-60 dark:text-gray-100" stroke-width="1.5" />
<c-icons.sun class="size-7 text-yellow-900/40 dark:text-gray-100/50" />
</a>
<a href="https://github.com/wrabit/django-cotton" target="_blank" class="text-yellow-900 opacity-60 dark:text-gray-100 flex items-center space-x-2">
<c-icons.github class="size-7" />
<a href="https://github.com/wrabit/django-cotton" target="_blank" class="text-yellow-900/40 dark:text-gray-100/50 flex items-center space-x-2">
<c-icons.github class="size-6" />
</a>
<a href="https://discord.gg/4x8ntQwHMe" target="_blank" class="text-yellow-900/40 dark:text-gray-100/50 flex items-center space-x-2">
<c-icons.discord class="size-7" />
</a>
</div>
</div>
@ -83,4 +86,4 @@
@click.away="isOpen = false" class="absolute shadow-md bg-white dark:bg-gray-800 top-[67px] inset-x-0 px-5 transition transform origin-top-right md:hidden">
<c-sidebar />
</div>
</div>
</div>

View file

@ -1,3 +1,3 @@
<div class="mt-6 border border-teal-600 rounded-lg px-8 py-5 dark:border dark:bg-transparent">
<div class="mt-6 border-2 border-teal-500 bg-teal-500/10 rounded-lg px-8 py-5">
{{ slot }}
</div>

View file

@ -1,5 +1,4 @@
<div class="sticky top-[68px] pb-6 pt-2">
<c-sidebar-block>
<c-slot name="title">Getting Started</c-slot>
<ul>
@ -9,6 +8,9 @@
<c-sidebar-link url="{% url 'components' %}">
Components
</c-sidebar-link>
<c-sidebar-link url="{% url 'usage-patterns' %}">
Usage Patterns
</c-sidebar-link>
</ul>
</c-sidebar-block>
<c-sidebar-block>

View file

@ -1,4 +1,4 @@
<div class="py-4 {{ class }} border-b border-yellow-900 border-opacity-15 dark:border-gray-700 dark:border-opacity-100 last:border-0 first:pt-0 leading-7">
<div class="py-4 {{ class }} first:pt-0 leading-7">
{% if title %}
<c-sidebar-heading>{{ title }}</c-sidebar-heading>
{% endif %}

View file

@ -1 +1 @@
<div class="dark:text-white text-[#1a384a] dark:opacity-35 text-yellow-800 brightness-65 text-opacity-50 font-semibold uppercase text-[14px] mb-2 tracking-wider">{{ slot }}</div>
<div class="dark:text-white dark:opacity-35 text-yellow-900/40 font-semibold uppercase text-[14px] mb-2 tracking-wider">{{ slot }}</div>

View file

@ -0,0 +1,39 @@
<c-vars
:themes="{
'primary': 'bg-sky-500',
'subtle': 'bg-neutral-50 text-gray-800 dark:bg-gray-300/10 dark:text-neutral-300',
'danger': 'bg-red-500',
'warning': 'bg-yellow-500',
'info': 'bg-blue-500',
}"
theme="primary"
:outlined-themes="{
'primary': 'border border-sky-500 text-sky-500',
'subtle': 'border border-neutral-50 text-gray-800',
'danger': 'border border-red-500 text-red-500',
'warning': 'border border-yellow-500 text-yellow-500',
'info': 'border border-blue-500 text-blue-500',
}"
:outlined="False"
class
/>
<button {{ attrs }} type="button" class="
{% if outlined %}
{{ outlined_themes|get_item:theme }}
{% else %}
{{ themes|get_item:theme }}
{% endif %}
cursor-pointer whitespace-nowrap rounded-md
px-4 py-2 text-sm font-medium tracking-wide transition hover:opacity-75 text-center
focus-visible:outline
focus-visible:outline-2
focus-visible:outline-offset-2
focus-visible:outline-sky-500
active:opacity-100
active:outline-offset-0
disabled:opacity-75
disabled:cursor-not-allowed
{{ class }}">
{{ slot }}
</button>

View file

@ -0,0 +1,13 @@
<c-vars trigger_text trigger />
<div x-data="{ expanded: false }">
{% if trigger %}
<span @click="expanded = ! expanded" class="cursor-pointer">{{ trigger }}</span>
{% else %}
<button @click="expanded = ! expanded">{% firstof trigger_text "Toggle" %}</button>
{% endif %}
<div x-show="expanded" x-collapse x-cloak>
{{ slot }}
</div>
</div>

View file

@ -0,0 +1,41 @@
<c-vars trigger_text />
<div x-data="{ isOpen: false, openedWithKeyboard: false }" class="relative" @keydown.esc.window="isOpen = false, openedWithKeyboard = false">
<!-- Toggle Button -->
<button
type="button"
@click="isOpen = ! isOpen"
class="inline-flex cursor-pointer items-center gap-2 whitespace-nowrap rounded-md border border-neutral-300 bg-neutral-50 px-4 py-2 text-sm font-medium tracking-wide transition
hover:opacity-75
focus-visible:outline
focus-visible:outline-2
focus-visible:outline-offset-2
focus-visible:outline-neutral-800
dark:border-neutral-700
dark:bg-gray-800
dark:focus-visible:outline-neutral-300"
aria-haspopup="true"
@keydown.space.prevent="openedWithKeyboard = true"
@keydown.enter.prevent="openedWithKeyboard = true"
@keydown.down.prevent="openedWithKeyboard = true"
:class="isOpen || openedWithKeyboard ? 'text-neutral-900 dark:text-white' : 'text-neutral-600 dark:text-neutral-300'"
aria-expanded="isOpen || openedWithKeyboard">
{% firstof trigger_text "Select" %}
<svg aria-hidden="true" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4 totate-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
</svg>
</button>
<!-- Dropdown Menu -->
<div x-cloak x-show="isOpen || openedWithKeyboard"
x-transition
x-trap="openedWithKeyboard"
@click.outside="isOpen = false, openedWithKeyboard = false"
@keydown.down.prevent="$focus.wrap().next()"
@keydown.up.prevent="$focus.wrap().previous()"
class="absolute top-11 left-0 flex w-full min-w-[12rem] flex-col overflow-hidden rounded-md border border-neutral-300 bg-neutral-50 py-1.5
dark:border-neutral-700
dark:bg-gray-800"
role="menu">
{{ slot }}
</div>
</div>

View file

@ -0,0 +1,8 @@
<a href="#" class="bg-neutral-50 px-4 py-2 text-sm text-neutral-600
hover:bg-neutral-900/5
hover:text-neutral-900 focus-visible:bg-neutral-900/10 focus-visible:text-neutral-900 focus-visible:outline-none
dark:bg-black/10
dark:text-neutral-300
dark:hover:bg-neutral-50/5 dark:hover:text-white
dark:focus-visible:bg-neutral-50/10
dark:focus-visible:text-white" role="menuitem">{{ slot }}</a>

View file

@ -41,7 +41,7 @@
<h2 id="type">Change the input type</h2>
<p>You will probably need more than just a text input in your project. So let's declare an attribute `text` in <c-highlight>{{ '<c-vars />'|force_escape }}</c-highlight>. Adding it as a var will allow us to set "text" as the default. Additionally, it will be excluded from <c-highlight>{% verbatim %}{{ attrs }}{% endverbatim %}</c-highlight>:</p>
<p>You will probably need more than just a text input in your project. So let's declare an attribute `text` in <code>{{ '<c-vars />'|force_escape }}</code>. Adding it as a var will allow us to set "text" as the default. Additionally, it will be excluded from <code>{% verbatim %}{{ attrs }}{% endverbatim %}</code>:</p>
<c-snippet label="cotton/input.html">{% cotton_verbatim %}{% verbatim %}
<c-vars type="text" />
@ -140,7 +140,7 @@
<c-navigation>
<c-slot name="prev">
<a href="{% url 'components' %}">Components</a>
<a href="{% url 'usage-patterns' %}">Usage Patterns</a>
</c-slot>
<c-slot name="next">
<a href="{% url 'alpine-js' %}">Tabs with Alpine.js</a>

View file

@ -20,7 +20,7 @@
<div class="w-full shrink-0"></div>
<span class="pt-0 md:pt-5 clear-both flex-wrap inline-flex justify-center items-center space-x-2">
<span class="">Hello</span>
<span class="shrink-0 px-3 py-1 bg-teal-600 font-mono text-xl sm:text-2xl md:text-4xl rounded-lg text-white inline-block font-normal leading-normal">{{ '&lt;c-component />' }}</span>
<span class="shrink-0 px-3 py-1 bg-teal-500/10 border-[3px] border-teal-600 font-mono text-xl sm:text-2xl md:text-4xl rounded-3xl text-teal-600 dark:text-white inline-block font-normal leading-normal">{{ '<c-comp />'|force_escape }}</span>
</span>
</h1>
@ -32,7 +32,7 @@
<div class="grid md:grid-cols-2 gap-4">
<div class="col-span-1 flex flex-col overflow-x-auto">
<h2 class="!font-normal !text-xl mb-3 mt-0 text-center"><span class="font-semibold">Before:</span> Strongly Coupled, Verbose</h2>
<h2 class="!font-normal !text-lg mb-3 mt-0 text-center"><span class="font-semibold">Before:</span> Strongly Coupled, Verbose</h2>
<div class="flex h-full rounded-xl overflow-hidden">
<c-demo.snippet-tabs labels="view.html|product_layout.html" tabs_id="compare">
<div class="flex flex-col h-full" x-show="active === 0">
@ -44,7 +44,7 @@
icon.png
{% endblock %}
{% block header %}
{% block title %}
Item Title
{% endblock %}
@ -88,7 +88,7 @@ Item Title
</div>
</div>
<div class="col-span-1 rounded-lg overflow-hidden flex flex-col">
<h2 class="!font-normal !text-xl mb-3 mt-0 text-center"><span class="font-semibold">After:</span> Decoupled, Clean &amp; Re-usable</h2>
<h2 class="!font-normal !text-lg mb-3 mt-0 text-center"><span class="font-semibold">After:</span> Decoupled, Clean &amp; Re-usable</h2>
<div class="flex h-full rounded-xl overflow-hidden">
<c-demo.snippet-tabs labels="view.html|product.html" tabs_id="compare">
<div class="flex flex-col h-full" x-show="active === 0">
@ -179,4 +179,4 @@ Item Title
</div>
</c-layouts.with-sidebar>
</c-layouts.with-sidebar>

View file

@ -2,23 +2,19 @@
<c-slot name="page_index">
<c-index-link><a href="#install" class="no-underline">Install Cotton</a></c-index-link>
<c-index-link><a href="#create-a-component" class="no-underline">Create a component</a></c-index-link>
<c-index-link><a href="#templates-location" class="no-underline">Templates location</a></c-index-link>
<c-index-link><a href="#include-a-component" class="no-underline">Include a component</a></c-index-link>
<c-index-link><a href="#usage" class="no-underline">Usage</a></c-index-link>
<c-index-sublink><a href="#basics" class="no-underline text-opacity-70">Basics</a></c-index-sublink>
<c-index-sublink><a href="#naming" class="no-underline">Naming</a></c-index-sublink>
<c-index-sublink><a href="#subfolders" class="no-underline">Subfolders</a></c-index-sublink>
<c-index-sublink><a href="#tag-style" class="no-underline">Tag Style</a></c-index-sublink>
</c-slot>
<h1 id="install">Quickstart</h1>
<h2>Install cotton</h2>
<h2 class="mt-0">Install cotton</h2>
<p>Run the following command:</p>
<c-snippet language="python">pip install django-cotton</c-snippet>
<p>Then update your settings.py:</p>
<h3>Automatic configuration:</h3>
<h3>For automatic configuration:</h3>
<c-snippet language="python" label="settings.py">{% cotton_verbatim %}{% verbatim %}
INSTALLED_APPS = [
@ -26,9 +22,9 @@ INSTALLED_APPS = [
]
{% endverbatim %}{% endcotton_verbatim %}</c-snippet>
<p>This will automatically handle the settings.py adding the required loader and templatetags.</p>
<p>This will attempt to automatically handle the settings.py by adding the required loader and templatetags.</p>
<h3>Customised configuration</h3>
<h3>For custom configuration</h3>
<p>If your project requires any non-default loaders or you do not wish Cotton to manage your settings, you should instead provide `django_cotton.apps.SimpleAppConfig` in your INSTALLED_APPS:</p>
@ -72,6 +68,21 @@ TEMPLATES = [
</div>
{% endverbatim %}{% endcotton_verbatim %}</c-snippet>
<c-hr id="templates-location" />
<h2>Templates location</h2>
<p>Cotton supports 2 common approaches regarding the location of the <code>templates</code> directory:</p>
<c-ul>
<li><strong>App level</strong> - You can place your cotton folder in any of your installed app folders, like: <div><code>[project]/[app]/templates/cotton/row.html</code></div></li>
<li>
<strong>Project root</strong> - You can place your cotton folder in a project level templates directory, like: <div><code>[project]/templates/cotton/row.html</code></div>
(<code>[project]</code> location is provided by `BASE_DIR` if present or you may set it with `COTTON_BASE_DIR`)
</li>
</c-ul>
<p>Any style will allow you to include your component the same way: <code>{{ '<c-row />'|force_escape }}</code></p>
<c-hr id="include-a-component" />
@ -105,34 +116,6 @@ def dashboard_view(request):
</c-slot>
</c-snippet>
<c-hr />
<h2 id="usage">Usage</h2>
<h3 id="basics">Basics</h3>
<c-ul>
<li>Cotton components should be placed in the <c-highlight>templates/cotton</c-highlight> folder (unless you have set COTTON_DIR).</li>
</c-ul>
<h3 id="naming">Naming</h3>
<p>Cotton uses the following naming conventions:</p>
<c-ul>
<li>Component file names are in snake_case: <c-highlight>my_component.html</c-highlight></li>
<li>but are called using kebab-case: <c-highlight>{{ "<c-my-component />"|force_escape }}</c-highlight></li>
</c-ul>
<h3 id="subfolders">Subfolders</h3>
<c-ul>
<li>Components in subfolders can be defined using dot notation</li>
<li>A component in <c-highlight>sidebar/menu/link.html</c-highlight> would be included as <c-highlight>{{ "<c-sidebar.menu.link />"|force_escape }}</c-highlight></li>
</c-ul>
<h3 id="tag-style">Tag Style</h3>
<c-ul>
<li>Components can either be self-closing <c-highlight>{{ "<c-my-component />"|force_escape }}</c-highlight> or have a closing tag <c-highlight>{{ "<c-my-component></c-my-component>"|force_escape }}</c-highlight></li>
</c-ul>
<c-navigation>
<c-slot name="prev">
<a href="{% url 'home' %}">Home</a>

View file

@ -0,0 +1,79 @@
<c-layouts.with-sidebar>
<h1 id="components">Usage Patterns</h1>
<c-slot name="page_index">
<c-index-link><a href="#components" class="no-underline">Components</a></c-index-link>
<c-index-sublink><a href="#basics" class="no-underline text-opacity-70">Basics</a></c-index-sublink>
<c-index-sublink><a href="#naming" class="no-underline">Naming</a></c-index-sublink>
<c-index-sublink><a href="#subfolders" class="no-underline">Subfolders</a></c-index-sublink>
<c-index-sublink><a href="#index" class="no-underline">index.html</a></c-index-sublink>
<c-index-sublink><a href="#tag-style" class="no-underline">Tag Style</a></c-index-sublink>
</c-slot>
<h3 id="basics">Basics</h3>
<c-ul>
<li>Cotton components should be placed in the <c-highlight>templates/cotton</c-highlight> folder ('cotton' path is <a href="{% url 'configuration' %}">configurable</a> using <code>COTTON_DIR</code>).</li>
<li>The <code>templates</code> folder can be located in either an app-level or top-level project root folder.</li>
</c-ul>
<c-hr />
<h3 id="naming">Naming</h3>
<p>Cotton uses the following naming conventions:</p>
<c-ul>
<li>By default, component file names are in snake_case (<c-highlight>my_component.html</c-highlight>).
<li>kebab-case filenames (<c-highlight>my-component.html</c-highlight>) can be enabled with <code>COTTON_SNAKE_CASED_NAMES = False</code> see <a href="{% url 'configuration' %}">configuration</a>.</li>
<li>Components are included in templates using kebab-case name of the component: <code>{{ "<c-my-component />"|force_escape }}</code></li>
</c-ul>
<c-hr />
<h3 id="subfolders">Subfolders</h3>
<c-ul>
<li>Components in subfolders can be called using dot notation to represent folder levels.</li>
<li>A component in <c-highlight>sidebar/menu/link.html</c-highlight> would be included with <code>{{ "<c-sidebar.menu.link />"|force_escape }}</code></li>
</c-ul>
<c-hr />
<h3 id="index">The index.html component</h3>
<p>When your component has sub-components, you can define the default component of a folder by adding an <code>index.html</code>. This helps to keep your project structure tidy and reduce additional code in the template.</p>
<c-snippet label="Project structure">
{% cotton_verbatim %}{% verbatim %}
templates/
├── cotton/
│ ├── card/
│ │ ├── index.html
│ │ ├── header.html
{% endverbatim %}{% endcotton_verbatim %}
</c-snippet>
<c-snippet label="Usage">
{% cotton_verbatim %}{% verbatim %}
<c-card>
<c-card.header>...</c-card.header>
</c-card>
{% endverbatim %}{% endcotton_verbatim %}
</c-snippet>
<c-hr />
<h3 id="tag-style">Tag Style</h3>
<c-ul>
<li>Components can either be self-closing <code>{{ "<c-my-component />"|force_escape }}</code> or have a closing tag <code>{{ "<c-my-component></c-my-component>"|force_escape }}</code></li>
</c-ul>
<c-navigation>
<c-slot name="prev">
<a href="{% url 'components' %}">Components</a>
</c-slot>
<c-slot name="next">
<a href="{% url 'form-fields' %}">Form Inputs</a>
</c-slot>
</c-navigation>
</c-layouts.with-sidebar>

View file

@ -10,8 +10,9 @@ urlpatterns = [
name="home",
),
path("docs/quickstart", views.build_view("quickstart"), name="quickstart"),
path("docs/installation", views.build_view("installation"), name="installation"),
path("docs/usage", views.build_view("usage"), name="usage"),
path(
"docs/usage-patterns", views.build_view("usage_patterns"), name="usage-patterns"
),
# Features
path("docs/components", views.build_view("components"), name="components"),
path("docs/slots", views.build_view("slots"), name="slots"),