Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a619517b6 | ||
|
|
1538d870b9 | ||
|
|
ed2eb036ae | ||
|
|
ae99ec079f |
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
{% import "macros/ui.html" as ui %}
|
||||
<!doctype html>
|
||||
<html lang="{{ lang | default(value='sk') }}" data-theme="dark">
|
||||
<head>
|
||||
@@ -105,14 +106,7 @@
|
||||
<!-- content column -->
|
||||
<div class="flex min-h-screen flex-col md:ml-60">
|
||||
<header class="sticky top-0 z-20 flex h-16 items-center gap-4 border-b border-outline bg-surface/95 px-4 backdrop-blur dark:border-outline-dark dark:bg-surface-dark/95">
|
||||
<button type="button" @click="showSidebar = !showSidebar" :aria-expanded="showSidebar"
|
||||
aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt md:hidden dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
{{ ui::icon_button(aria_label=t(key='menu', lang=lang | default(value='sk')), attrs='@click="showSidebar = !showSidebar" :aria-expanded="showSidebar"', extra="md:hidden", icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>') }}
|
||||
|
||||
<span class="text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{% block crumb %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock crumb %}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="admin-categories", lang=lang | default(value='sk')) }}</h1>
|
||||
<p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-categories-desc", lang=lang | default(value='sk')) }}</p>
|
||||
</div>
|
||||
{{ ui::button_primary(label=t(key="new-category", lang=lang | default(value='sk')), href="/admin/catalog/categories/new") }}
|
||||
{{ ui::button(label=t(key="new-category", lang=lang | default(value='sk')), href="/admin/catalog/categories/new") }}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 overflow-hidden rounded-radius border border-outline dark:border-outline-dark">
|
||||
@@ -43,11 +43,10 @@
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<a href="/admin/catalog/categories/{{ row.category.id }}/edit"
|
||||
class="inline-flex items-center rounded-radius border border-outline px-3 py-1.5 text-xs font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="edit", lang=lang | default(value='sk')) }}</a>
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="edit", lang=lang | default(value='sk')), href="/admin/catalog/categories/" ~ row.category.id ~ "/edit", size="px-3 py-1.5 text-xs") }}
|
||||
<form method="post" action="/admin/catalog/categories/{{ row.category.id }}/delete"
|
||||
onsubmit="return confirm('{{ t(key="confirm-delete", lang=lang | default(value='sk')) }}')">
|
||||
<button type="submit" class="inline-flex items-center rounded-radius border border-outline px-3 py-1.5 text-xs font-medium text-danger transition hover:bg-danger/10 dark:border-outline-dark">{{ t(key="delete", lang=lang | default(value='sk')) }}</button>
|
||||
{{ ui::button(variant="outline-danger", label=t(key="delete", lang=lang | default(value='sk')), type="submit", size="px-3 py-1.5 text-xs") }}
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
@@ -58,7 +57,7 @@
|
||||
{% else %}
|
||||
<div class="flex flex-col items-center gap-3 px-4 py-16 text-center">
|
||||
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-no-categories", lang=lang | default(value='sk')) }}</p>
|
||||
{{ ui::button_primary(label=t(key="new-category", lang=lang | default(value='sk')), href="/admin/catalog/categories/new") }}
|
||||
{{ ui::button(label=t(key="new-category", lang=lang | default(value='sk')), href="/admin/catalog/categories/new") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -9,50 +9,54 @@
|
||||
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{% if category %}{{ t(key="edit-category", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-category", lang=lang | default(value='sk')) }}{% endif %}
|
||||
</h1>
|
||||
{{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/categories") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/categories", size="px-3 py-2 text-sm") }}
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
action="{% if category %}/admin/catalog/categories/{{ category.id }}{% else %}/admin/catalog/categories{% endif %}"
|
||||
class="mt-6 space-y-5 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
|
||||
{% if category %}
|
||||
{% set v_name = category.name %}{% set v_slug = category.slug %}{% set v_pos = category.position %}{% set v_desc = category.description | default(value="") %}{% set v_pub = category.published %}
|
||||
{% else %}
|
||||
{% set v_name = "" %}{% set v_slug = "" %}{% set v_pos = 0 %}{% set v_desc = "" %}{% set v_pub = false %}
|
||||
{% endif %}
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="name" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="name", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="name" name="name" type="text" required value="{% if category %}{{ category.name }}{% endif %}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="name", id="name", required=true, value=v_name) }}
|
||||
</div>
|
||||
|
||||
<div class="grid gap-5 sm:grid-cols-2">
|
||||
<div class="space-y-1.5">
|
||||
<label for="slug" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="slug", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="slug" name="slug" type="text" value="{% if category %}{{ category.slug }}{% endif %}"
|
||||
placeholder="{{ t(key='slug-auto', lang=lang | default(value='sk')) }}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="slug", id="slug", value=v_slug, placeholder=t(key='slug-auto', lang=lang | default(value='sk'))) }}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="position" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="position", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="position" name="position" type="number" value="{% if category %}{{ category.position }}{% else %}0{% endif %}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="position", id="position", type="number", value=v_pos) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="parent_id" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="parent-category", lang=lang | default(value='sk')) }}</label>
|
||||
<select id="parent_id" name="parent_id"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
<option value="">{{ t(key="no-parent", lang=lang | default(value='sk')) }}</option>
|
||||
{% for parent in parents %}
|
||||
<option value="{{ parent.id }}" {% if category and category.parent_id == parent.id %}selected{% endif %}>
|
||||
{% if parent.depth > 0 %}{% for _ in range(end=parent.depth) %}— {% endfor %}{% endif %}{{ parent.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="relative">
|
||||
<select id="parent_id" name="parent_id"
|
||||
class="w-full appearance-none rounded-radius border border-outline bg-surface-alt px-4 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark">
|
||||
<option value="">{{ t(key="no-parent", lang=lang | default(value='sk')) }}</option>
|
||||
{% for parent in parents %}
|
||||
<option value="{{ parent.id }}" {% if category and category.parent_id == parent.id %}selected{% endif %}>
|
||||
{% if parent.depth > 0 %}{% for _ in range(end=parent.depth) %}— {% endfor %}{% endif %}{{ parent.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="pointer-events-none absolute right-3 top-1/2 size-5 -translate-y-1/2 text-on-surface/60 dark:text-on-surface-dark/60"><path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="description" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="description", lang=lang | default(value='sk')) }}</label>
|
||||
<textarea id="description" name="description" rows="4"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">{% if category and category.description %}{{ category.description }}{% endif %}</textarea>
|
||||
{{ ui::textarea(name="description", id="description", rows="4", value=v_desc) }}
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
@@ -60,19 +64,14 @@
|
||||
{% if category and category.image_id %}
|
||||
<img src="/images/{{ category.image_id }}" alt="" class="size-24 rounded-radius object-cover">
|
||||
{% endif %}
|
||||
<input id="image" name="image" type="file" accept="image/*"
|
||||
class="block w-full text-sm text-on-surface file:mr-3 file:rounded-radius file:border-0 file:bg-primary file:px-3 file:py-2 file:text-sm file:font-medium file:text-on-primary dark:text-on-surface-dark dark:file:bg-primary-dark dark:file:text-on-primary-dark">
|
||||
{{ ui::file_input(name="image", id="image", accept="image/*") }}
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" name="published" value="on" {% if category and category.published %}checked{% endif %}
|
||||
class="size-4 rounded border-outline text-primary focus:ring-primary dark:border-outline-dark">
|
||||
<span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="published", lang=lang | default(value='sk')) }}</span>
|
||||
</label>
|
||||
{{ ui::checkbox(name="published", id="published", label=t(key="published", lang=lang | default(value='sk')), checked=v_pub) }}
|
||||
|
||||
<div class="flex gap-3 pt-2">
|
||||
{{ ui::button_primary(label=t(key="save", lang=lang | default(value='sk')), type="submit") }}
|
||||
{{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/categories") }}
|
||||
{{ ui::button(label=t(key="save", lang=lang | default(value='sk')), type="submit") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/categories") }}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -9,68 +9,68 @@
|
||||
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{% if product %}{{ t(key="edit-product", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-product", lang=lang | default(value='sk')) }}{% endif %}
|
||||
</h1>
|
||||
{{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/products") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/products", size="px-3 py-2 text-sm") }}
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
action="{% if product %}/admin/catalog/products/{{ product.id }}{% else %}/admin/catalog/products{% endif %}"
|
||||
class="mt-6 space-y-5 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
|
||||
{% if product %}
|
||||
{% set v_name = product.name %}{% set v_price = product.price %}{% set v_currency = product.currency %}{% set v_stock = product.stock %}{% set v_sku = product.sku | default(value="") %}{% set v_slug = product.slug %}{% set v_desc = product.description | default(value="") %}{% set v_pub = product.published %}
|
||||
{% else %}
|
||||
{% set v_name = "" %}{% set v_price = "" %}{% set v_currency = "EUR" %}{% set v_stock = 0 %}{% set v_sku = "" %}{% set v_slug = "" %}{% set v_desc = "" %}{% set v_pub = false %}
|
||||
{% endif %}
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="name" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="name", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="name" name="name" type="text" required value="{% if product %}{{ product.name }}{% endif %}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="name", id="name", required=true, value=v_name) }}
|
||||
</div>
|
||||
|
||||
<div class="grid gap-5 sm:grid-cols-2">
|
||||
<div class="space-y-1.5">
|
||||
<label for="price" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="price", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="price" name="price" type="text" inputmode="decimal" required value="{% if product %}{{ product.price }}{% endif %}"
|
||||
placeholder="0.00"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="price", id="price", required=true, value=v_price, placeholder="0.00", attrs='inputmode="decimal"') }}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="currency" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="currency", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="currency" name="currency" type="text" maxlength="3" value="{% if product %}{{ product.currency }}{% else %}EUR{% endif %}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm uppercase text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="currency", id="currency", value=v_currency, attrs='maxlength="3"', extra="uppercase") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-5 sm:grid-cols-2">
|
||||
<div class="space-y-1.5">
|
||||
<label for="stock" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="stock", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="stock" name="stock" type="number" min="0" value="{% if product %}{{ product.stock }}{% else %}0{% endif %}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="stock", id="stock", type="number", value=v_stock, attrs='min="0"') }}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="sku" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="sku", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="sku" name="sku" type="text" value="{% if product and product.sku %}{{ product.sku }}{% endif %}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="sku", id="sku", value=v_sku) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="category_id" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="category", lang=lang | default(value='sk')) }}</label>
|
||||
<select id="category_id" name="category_id"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
<option value="">{{ t(key="no-category", lang=lang | default(value='sk')) }}</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}" {% if product and product.category_id == category.id %}selected{% endif %}>{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="relative">
|
||||
<select id="category_id" name="category_id"
|
||||
class="w-full appearance-none rounded-radius border border-outline bg-surface-alt px-4 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark">
|
||||
<option value="">{{ t(key="no-category", lang=lang | default(value='sk')) }}</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}" {% if product and product.category_id == category.id %}selected{% endif %}>{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="pointer-events-none absolute right-3 top-1/2 size-5 -translate-y-1/2 text-on-surface/60 dark:text-on-surface-dark/60"><path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="slug" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="slug", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="slug" name="slug" type="text" value="{% if product %}{{ product.slug }}{% endif %}"
|
||||
placeholder="{{ t(key='slug-auto', lang=lang | default(value='sk')) }}"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="slug", id="slug", value=v_slug, placeholder=t(key='slug-auto', lang=lang | default(value='sk'))) }}
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="description" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="description", lang=lang | default(value='sk')) }}</label>
|
||||
<textarea id="description" name="description" rows="5"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">{% if product and product.description %}{{ product.description }}{% endif %}</textarea>
|
||||
{{ ui::textarea(name="description", id="description", rows="5", value=v_desc) }}
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
@@ -78,19 +78,14 @@
|
||||
{% if product and product.image %}
|
||||
<img src="/images/{{ product.image }}" alt="" class="size-24 rounded-radius object-cover">
|
||||
{% endif %}
|
||||
<input id="image" name="image" type="file" accept="image/*"
|
||||
class="block w-full text-sm text-on-surface file:mr-3 file:rounded-radius file:border-0 file:bg-primary file:px-3 file:py-2 file:text-sm file:font-medium file:text-on-primary dark:text-on-surface-dark dark:file:bg-primary-dark dark:file:text-on-primary-dark">
|
||||
{{ ui::file_input(name="image", id="image", accept="image/*") }}
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" name="published" value="on" {% if product and product.published %}checked{% endif %}
|
||||
class="size-4 rounded border-outline text-primary focus:ring-primary dark:border-outline-dark">
|
||||
<span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="published", lang=lang | default(value='sk')) }}</span>
|
||||
</label>
|
||||
{{ ui::checkbox(name="published", id="published", label=t(key="published", lang=lang | default(value='sk')), checked=v_pub) }}
|
||||
|
||||
<div class="flex gap-3 pt-2">
|
||||
{{ ui::button_primary(label=t(key="save", lang=lang | default(value='sk')), type="submit") }}
|
||||
{{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/products") }}
|
||||
{{ ui::button(label=t(key="save", lang=lang | default(value='sk')), type="submit") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/products") }}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="admin-products", lang=lang | default(value='sk')) }}</h1>
|
||||
<p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-products-desc", lang=lang | default(value='sk')) }}</p>
|
||||
</div>
|
||||
{{ ui::button_primary(label=t(key="new-product", lang=lang | default(value='sk')), href="/admin/catalog/products/new") }}
|
||||
{{ ui::button(label=t(key="new-product", lang=lang | default(value='sk')), href="/admin/catalog/products/new") }}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 overflow-hidden rounded-radius border border-outline dark:border-outline-dark">
|
||||
@@ -52,13 +52,11 @@
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<a href="/admin/catalog/products/{{ product.id }}/edit"
|
||||
class="inline-flex items-center rounded-radius border border-outline px-3 py-1.5 text-xs font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="edit", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="/shop/{{ product.slug }}"
|
||||
class="inline-flex items-center rounded-radius border border-outline px-3 py-1.5 text-xs font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="view", lang=lang | default(value='sk')) }}</a>
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="edit", lang=lang | default(value='sk')), href="/admin/catalog/products/" ~ product.id ~ "/edit", size="px-3 py-1.5 text-xs") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="view", lang=lang | default(value='sk')), href="/shop/" ~ product.slug, size="px-3 py-1.5 text-xs") }}
|
||||
<form method="post" action="/admin/catalog/products/{{ product.id }}/delete"
|
||||
onsubmit="return confirm('{{ t(key="confirm-delete", lang=lang | default(value='sk')) }}')">
|
||||
<button type="submit" class="inline-flex items-center rounded-radius border border-outline px-3 py-1.5 text-xs font-medium text-danger transition hover:bg-danger/10 dark:border-outline-dark">{{ t(key="delete", lang=lang | default(value='sk')) }}</button>
|
||||
{{ ui::button(variant="outline-danger", label=t(key="delete", lang=lang | default(value='sk')), type="submit", size="px-3 py-1.5 text-xs") }}
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
@@ -69,7 +67,7 @@
|
||||
{% else %}
|
||||
<div class="flex flex-col items-center gap-3 px-4 py-16 text-center">
|
||||
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-no-products", lang=lang | default(value='sk')) }}</p>
|
||||
{{ ui::button_primary(label=t(key="new-product", lang=lang | default(value='sk')), href="/admin/catalog/products/new") }}
|
||||
{{ ui::button(label=t(key="new-product", lang=lang | default(value='sk')), href="/admin/catalog/products/new") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -30,9 +30,7 @@
|
||||
class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{{ t(key="login-email", lang=lang | default(value='sk')) }}
|
||||
</label>
|
||||
<input type="email" id="email" name="email" required autofocus
|
||||
autocomplete="email"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-none focus:ring-2 focus:ring-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark dark:focus:ring-primary-dark">
|
||||
{{ ui::input(name="email", id="email", type="email", required=true, autocomplete="email", attrs="autofocus") }}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
@@ -40,12 +38,10 @@
|
||||
class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{{ t(key="login-password", lang=lang | default(value='sk')) }}
|
||||
</label>
|
||||
<input type="password" id="password" name="password" required
|
||||
autocomplete="current-password"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-none focus:ring-2 focus:ring-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark dark:focus:ring-primary-dark">
|
||||
{{ ui::input(name="password", id="password", type="password", required=true, autocomplete="current-password") }}
|
||||
</div>
|
||||
|
||||
{{ ui::button_primary(label=t(key="login-auth", lang=lang | default(value='sk')), type="submit", extra="mt-1 w-full") }}
|
||||
{{ ui::button(label=t(key="login-auth", lang=lang | default(value='sk')), type="submit", extra="mt-1 w-full") }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums">{{ order.total }} {{ order.currency }}</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<a href="/admin/orders/{{ order.id }}" class="inline-flex items-center rounded-radius border border-outline px-3 py-1.5 text-xs font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="view", lang=lang | default(value='sk')) }}</a>
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="view", lang=lang | default(value='sk')), href="/admin/orders/" ~ order.id, size="px-3 py-1.5 text-xs") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% block content %}
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h1 class="font-mono text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ order.order_number }}</h1>
|
||||
{{ ui::button_outline(label=t(key="admin-orders", lang=lang | default(value='sk')), href="/admin/orders") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="admin-orders", lang=lang | default(value='sk')), href="/admin/orders", size="px-3 py-2 text-sm") }}
|
||||
</div>
|
||||
|
||||
{% if ship_error %}
|
||||
@@ -85,8 +85,7 @@
|
||||
{{ t(key="order-tracking", lang=lang | default(value='sk')) }}: <span class="font-mono font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ order.tracking_number }}</span>
|
||||
</p>
|
||||
{% if order.label_url %}
|
||||
<a href="{{ order.label_url }}" target="_blank" rel="noopener"
|
||||
class="inline-flex items-center rounded-radius border border-outline px-3 py-1.5 text-xs font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="order-label", lang=lang | default(value='sk')) }}</a>
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="order-label", lang=lang | default(value='sk')), href=order.label_url, size="px-3 py-1.5 text-xs", attrs='target="_blank" rel="noopener"') }}
|
||||
{% endif %}
|
||||
{% elif carrier == "none" %}
|
||||
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="order-manual-fulfillment", lang=lang | default(value='sk')) }}</p>
|
||||
@@ -96,20 +95,23 @@
|
||||
onsubmit="return confirm('{{ t(key="order-send-confirm", lang=lang | default(value='sk')) }}')">
|
||||
{% set carrier_up = carrier | upper %}
|
||||
{% set ship_label = t(key="order-send-to-carrier", lang=lang | default(value='sk')) ~ " " ~ carrier_up %}
|
||||
{{ ui::button_primary(label=ship_label, type="submit", extra="w-full") }}
|
||||
{{ ui::button(label=ship_label, type="submit", extra="w-full") }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="post" action="/admin/orders/{{ order.id }}/status" class="space-y-3 rounded-radius border border-outline bg-surface p-5 dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
<label for="status" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="order-status", lang=lang | default(value='sk')) }}</label>
|
||||
<select id="status" name="status"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{% for status in statuses %}
|
||||
<option value="{{ status }}" {% if order.status == status %}selected{% endif %}>{{ t(key="order-status-" ~ status, lang=lang | default(value='sk')) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ ui::button_primary(label=t(key="order-update-status", lang=lang | default(value='sk')), type="submit", extra="w-full") }}
|
||||
<div class="relative">
|
||||
<select id="status" name="status"
|
||||
class="w-full appearance-none rounded-radius border border-outline bg-surface-alt px-4 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark">
|
||||
{% for status in statuses %}
|
||||
<option value="{{ status }}" {% if order.status == status %}selected{% endif %}>{{ t(key="order-status-" ~ status, lang=lang | default(value='sk')) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="pointer-events-none absolute right-3 top-1/2 size-5 -translate-y-1/2 text-on-surface/60 dark:text-on-surface-dark/60"><path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
{{ ui::button(label=t(key="order-update-status", lang=lang | default(value='sk')), type="submit", extra="w-full") }}
|
||||
</form>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -20,15 +20,10 @@
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="price-{{ method.id }}" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="price", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="price-{{ method.id }}" name="price" type="text" inputmode="decimal" value="{{ method.price }}"
|
||||
class="w-28 rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="price", id="price-" ~ method.id, value=method.price, width="w-28", attrs='inputmode="decimal"') }}
|
||||
</div>
|
||||
<label class="flex items-center gap-2 pb-2">
|
||||
<input type="checkbox" name="enabled" value="on" {% if method.enabled %}checked{% endif %}
|
||||
class="size-4 rounded border-outline text-primary focus:ring-primary dark:border-outline-dark">
|
||||
<span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="shipping-enabled", lang=lang | default(value='sk')) }}</span>
|
||||
</label>
|
||||
{{ ui::button_primary(label=t(key="save", lang=lang | default(value='sk')), type="submit", extra="ml-auto") }}
|
||||
<div class="pb-2">{{ ui::checkbox(name="enabled", label=t(key="shipping-enabled", lang=lang | default(value='sk')), checked=method.enabled) }}</div>
|
||||
{{ ui::button(label=t(key="save", lang=lang | default(value='sk')), type="submit", extra="ml-auto") }}
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{% import "macros/ui.html" as ui %}
|
||||
<!doctype html>
|
||||
<html lang="{{ lang | default(value='sk') }}" data-theme="dark">
|
||||
<head>
|
||||
@@ -66,13 +67,7 @@
|
||||
class="sticky top-0 z-30 border-b border-outline bg-surface/95 backdrop-blur dark:border-outline-dark dark:bg-surface-dark/95">
|
||||
<nav x-data="{ mobile: false }" class="mx-auto flex max-w-7xl items-center gap-4 px-4 py-3">
|
||||
<!-- category sidebar toggle (mobile only) -->
|
||||
<button type="button" @click="cats = !cats" :aria-expanded="cats"
|
||||
aria-label="{{ t(key='categories', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt lg:hidden dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
{{ ui::icon_button(aria_label=t(key='categories', lang=lang | default(value='sk')), attrs='@click="cats = !cats" :aria-expanded="cats"', extra="lg:hidden", icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>') }}
|
||||
<a href="/"
|
||||
class="text-lg font-bold tracking-tight text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{{ t(key="brand", lang=lang | default(value='sk')) }}
|
||||
@@ -102,7 +97,7 @@
|
||||
x-init="count = cartCount(); ['htmx:afterSwap', 'htmx:afterRequest'].forEach(function (e) { window.addEventListener(e, function () { count = cartCount() }) })"
|
||||
aria-label="{{ t(key='cart-title', lang=lang | default(value='sk')) }}"
|
||||
title="{{ t(key='cart-title', lang=lang | default(value='sk')) }}"
|
||||
class="relative inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
class="relative inline-flex size-9 shrink-0 items-center justify-center rounded-radius bg-transparent text-secondary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 dark:text-secondary-dark dark:focus-visible:outline-secondary-dark">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
|
||||
</svg>
|
||||
@@ -115,14 +110,7 @@
|
||||
</div>
|
||||
|
||||
<!-- mobile hamburger -->
|
||||
<button type="button" @click="mobile = !mobile" :aria-expanded="mobile"
|
||||
aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt md:hidden dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
{{ ui::icon_button(aria_label=t(key='menu', lang=lang | default(value='sk')), attrs='@click="mobile = !mobile" :aria-expanded="mobile"', extra="md:hidden", icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>') }}
|
||||
</div>
|
||||
|
||||
<!-- mobile menu panel -->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% import "macros/ui.html" as ui %}
|
||||
|
||||
{% block title %}{{ t(key="brand", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||
|
||||
|
||||
@@ -6,36 +6,59 @@
|
||||
|
||||
Usage:
|
||||
{% import "macros/ui.html" as ui %}
|
||||
{{ ui::button_primary(label=t(key="save", lang=lang)) }}
|
||||
{{ ui::button_primary(label="Add", attrs='hx-post="/x"' | safe) }}
|
||||
{{ ui::button_outline(label="Cancel", href="/back") }}
|
||||
{{ ui::button(label=t(key="save", lang=lang)) }} {# default primary #}
|
||||
{{ ui::button(label="Add", attrs='hx-post="/x"' | safe) }}
|
||||
{{ ui::button(label="Cancel", variant="outline-secondary", href="/back") }}
|
||||
{{ ui::button(label="Send", size="px-6 py-2.5 text-sm") }} {# keep a non-default size #}
|
||||
{{ ui::badge(label="Published", variant="success") }}
|
||||
|
||||
Notes:
|
||||
- Macros can't see template context vars (e.g. `lang`); pass already-translated
|
||||
strings as `label`.
|
||||
- `attrs` is injected raw (caller must pass it through `| safe`); use it for
|
||||
htmx / name / value / @click etc. For buttons whose attrs carry nested
|
||||
quotes (e.g. hx-on with toast(...)), keep them inline instead.
|
||||
- Source: penguinui/buttons/{default,outline,ghost}-button.html and
|
||||
penguinui/badge/soft-color-badge.html. Upstream's color-button typos
|
||||
(text-onDanger etc.) are fixed to our real tokens (text-on-danger). #}
|
||||
htmx / name / value / @click / :disabled etc. For buttons whose attrs carry
|
||||
nested quotes (e.g. hx-on with toast(...)), keep them inline instead.
|
||||
- `pad` is the size (default Penguin "px-4 py-2"); override to preserve an
|
||||
existing size rather than normalizing it.
|
||||
- The button class strings are the **verbatim** Penguin variants from
|
||||
penguinui/buttons/{default,outline,ghost}-button.html (only `inline-flex
|
||||
items-center justify-center` is added so <a> and w-full render correctly,
|
||||
and the upstream `text-onDanger`/`text-onSuccess`… token typos are fixed to
|
||||
our real `text-on-*` tokens). `variant` selects a Penguin variant:
|
||||
solid : primary (default) | secondary | danger | success | warning | info
|
||||
outline : outline-primary | outline-secondary | outline-alternate | outline-danger
|
||||
ghost : ghost-primary | ghost-secondary | ghost-danger #}
|
||||
|
||||
{% macro button_primary(label, type="button", href="", attrs="", extra="") -%}
|
||||
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius border border-primary bg-primary px-4 py-2 text-center text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:border-primary-dark dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
|
||||
{%- endmacro button_primary %}
|
||||
{% macro button(label, variant="primary", type="button", href="", attrs="", extra="", icon="", size="px-4 py-2 text-sm") -%}
|
||||
{%- if variant == "secondary" -%}{% set cls = "border border-secondary bg-secondary text-on-secondary focus-visible:outline-secondary dark:border-secondary-dark dark:bg-secondary-dark dark:text-on-secondary-dark dark:focus-visible:outline-secondary-dark" -%}
|
||||
{%- elif variant == "danger" -%}{% set cls = "border border-danger bg-danger text-on-danger focus-visible:outline-danger dark:bg-danger dark:border-danger dark:text-on-danger dark:focus-visible:outline-danger" -%}
|
||||
{%- elif variant == "success" -%}{% set cls = "border border-success bg-success text-on-success focus-visible:outline-success dark:bg-success dark:border-success dark:text-on-success dark:focus-visible:outline-success" -%}
|
||||
{%- elif variant == "warning" -%}{% set cls = "border border-warning bg-warning text-on-warning focus-visible:outline-warning dark:bg-warning dark:border-warning dark:text-on-warning dark:focus-visible:outline-warning" -%}
|
||||
{%- elif variant == "info" -%}{% set cls = "border border-info bg-info text-on-info focus-visible:outline-info dark:bg-info dark:border-info dark:text-on-info dark:focus-visible:outline-info" -%}
|
||||
{%- elif variant == "outline-primary" -%}{% set cls = "border border-primary bg-transparent text-primary focus-visible:outline-primary dark:border-primary-dark dark:text-primary-dark dark:focus-visible:outline-primary-dark" -%}
|
||||
{%- elif variant == "outline-secondary" -%}{% set cls = "border border-secondary bg-transparent text-secondary focus-visible:outline-secondary dark:border-secondary-dark dark:text-secondary-dark dark:focus-visible:outline-secondary-dark" -%}
|
||||
{%- elif variant == "outline-alternate" -%}{% set cls = "border border-outline bg-transparent text-outline focus-visible:outline-outline dark:border-outline-dark dark:text-outline-dark dark:focus-visible:outline-outline-dark" -%}
|
||||
{%- elif variant == "outline-danger" -%}{% set cls = "border border-danger bg-transparent text-danger focus-visible:outline-danger dark:border-danger dark:text-danger dark:focus-visible:outline-danger" -%}
|
||||
{%- elif variant == "ghost-primary" -%}{% set cls = "bg-transparent text-primary focus-visible:outline-primary dark:text-primary-dark dark:focus-visible:outline-primary-dark" -%}
|
||||
{%- elif variant == "ghost-secondary" -%}{% set cls = "bg-transparent text-secondary focus-visible:outline-secondary dark:text-secondary-dark dark:focus-visible:outline-secondary-dark" -%}
|
||||
{%- elif variant == "ghost-danger" -%}{% set cls = "bg-transparent text-danger focus-visible:outline-danger dark:text-danger dark:focus-visible:outline-danger" -%}
|
||||
{%- else -%}{% set cls = "border border-primary bg-primary text-on-primary focus-visible:outline-primary dark:border-primary-dark dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark" -%}
|
||||
{%- endif -%}
|
||||
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius {{ size }} text-center font-medium tracking-wide transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 {{ cls }} {{ extra }}" {{ attrs | safe }}>{{ icon | safe }}{{ label }}</{% if href %}a{% else %}button{% endif %}>
|
||||
{%- endmacro button %}
|
||||
|
||||
{% macro button_outline(label, type="button", href="", attrs="", extra="") -%}
|
||||
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius border border-outline bg-surface-alt px-4 py-2 text-center text-sm font-medium tracking-wide text-on-surface transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-outline-strong active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark dark:focus-visible:outline-outline-dark-strong {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
|
||||
{%- endmacro button_outline %}
|
||||
|
||||
{% macro button_danger(label, type="button", href="", attrs="", extra="") -%}
|
||||
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius border border-danger bg-danger px-4 py-2 text-center text-sm font-medium tracking-wide text-on-danger transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:focus-visible:outline-danger {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
|
||||
{%- endmacro button_danger %}
|
||||
|
||||
{% macro button_ghost(label, type="button", href="", attrs="", extra="") -%}
|
||||
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius bg-transparent px-4 py-2 text-center text-sm font-medium tracking-wide text-primary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:text-primary-dark dark:focus-visible:outline-primary-dark {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
|
||||
{%- endmacro button_ghost %}
|
||||
{# Icon-only button (square). Penguin ghost treatment (bg-transparent,
|
||||
hover:opacity-75); pass the raw <svg> as `icon`, an accessible name via
|
||||
`aria_label`/`sr`, and any Alpine/htmx via `attrs` (raw). variant ∈
|
||||
ghost-secondary (default) | ghost-primary | ghost-danger | ghost-alternate. #}
|
||||
{% macro icon_button(icon, variant="ghost-secondary", type="button", href="", attrs="", extra="", aria_label="", sr="", size="size-9") -%}
|
||||
{%- if variant == "ghost-primary" -%}{% set cls = "text-primary focus-visible:outline-primary dark:text-primary-dark dark:focus-visible:outline-primary-dark" -%}
|
||||
{%- elif variant == "ghost-danger" -%}{% set cls = "text-danger focus-visible:outline-danger dark:text-danger dark:focus-visible:outline-danger" -%}
|
||||
{%- elif variant == "ghost-alternate" -%}{% set cls = "text-outline focus-visible:outline-outline dark:text-outline-dark dark:focus-visible:outline-outline-dark" -%}
|
||||
{%- else -%}{% set cls = "text-secondary focus-visible:outline-secondary dark:text-secondary-dark dark:focus-visible:outline-secondary-dark" -%}
|
||||
{%- endif -%}
|
||||
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %}{% if aria_label %} aria-label="{{ aria_label }}" title="{{ aria_label }}"{% endif %} class="inline-flex shrink-0 items-center justify-center rounded-radius bg-transparent {{ size }} transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 {{ cls }} {{ extra }}" {{ attrs | safe }}>{{ icon | safe }}{% if sr %}<span class="sr-only">{{ sr }}</span>{% endif %}</{% if href %}a{% else %}button{% endif %}>
|
||||
{%- endmacro icon_button %}
|
||||
|
||||
{# Compact danger alert (form/inline errors). Adapted from
|
||||
penguinui/alert/default-alert.html (danger variant), trimmed to a single line
|
||||
@@ -65,3 +88,35 @@
|
||||
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-outline bg-surface text-xs font-medium text-on-surface dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark"><span class="bg-surface-alt/40 px-2 py-1 dark:bg-surface-dark-alt/40">{{ label }}</span></span>
|
||||
{%- endif %}
|
||||
{%- endmacro badge %}
|
||||
|
||||
{# ---- Form controls. Verbatim Penguin classes from
|
||||
penguinui/{text-input,text-area,select,checkbox,file-input}/default-*.html.
|
||||
These macros emit only the control (callers keep their own <label>/layout), so
|
||||
text-color utilities are added here (upstream sets them on the wrapper div). #}
|
||||
|
||||
{# Text/email/number/password input. #}
|
||||
{% macro input(name, type="text", id="", value="", placeholder="", required=false, autocomplete="", attrs="", extra="", width="w-full") -%}
|
||||
<input {% if id %}id="{{ id }}" {% endif %}name="{{ name }}" type="{{ type }}"{% if value != "" %} value="{{ value }}"{% endif %}{% if placeholder %} placeholder="{{ placeholder }}"{% endif %}{% if required %} required{% endif %}{% if autocomplete %} autocomplete="{{ autocomplete }}"{% endif %} class="{{ width }} rounded-radius border border-outline bg-surface-alt px-2 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark {{ extra }}" {{ attrs | safe }}/>
|
||||
{%- endmacro input %}
|
||||
|
||||
{% macro textarea(name, id="", value="", rows="3", placeholder="", required=false, attrs="", extra="") -%}
|
||||
<textarea {% if id %}id="{{ id }}" {% endif %}name="{{ name }}" rows="{{ rows }}"{% if placeholder %} placeholder="{{ placeholder }}"{% endif %}{% if required %} required{% endif %} class="w-full rounded-radius border border-outline bg-surface-alt px-2.5 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark {{ extra }}" {{ attrs | safe }}>{{ value }}</textarea>
|
||||
{%- endmacro textarea %}
|
||||
|
||||
{# File input. #}
|
||||
{% macro file_input(name, id="", accept="", attrs="", extra="") -%}
|
||||
<input {% if id %}id="{{ id }}" {% endif %}name="{{ name }}" type="file"{% if accept %} accept="{{ accept }}"{% endif %} class="w-full overflow-clip rounded-radius border border-outline bg-surface-alt/50 text-sm text-on-surface file:mr-4 file:border-none file:bg-surface-alt file:px-4 file:py-2 file:font-medium file:text-on-surface-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:file:bg-surface-dark-alt dark:file:text-on-surface-dark-strong dark:focus-visible:outline-primary-dark {{ extra }}" {{ attrs | safe }}/>
|
||||
{%- endmacro file_input %}
|
||||
|
||||
{# Checkbox (full Penguin control: custom box + check icon + label text). #}
|
||||
{% macro checkbox(name, label, id="", value="on", checked=false, attrs="", extra="") -%}
|
||||
<label {% if id %}for="{{ id }}" {% endif %}class="flex items-center gap-2 text-sm font-medium text-on-surface dark:text-on-surface-dark has-checked:text-on-surface-strong dark:has-checked:text-on-surface-dark-strong has-disabled:cursor-not-allowed has-disabled:opacity-75 {{ extra }}">
|
||||
<span class="relative flex items-center">
|
||||
<input {% if id %}id="{{ id }}" {% endif %}name="{{ name }}" value="{{ value }}" type="checkbox"{% if checked %} checked{% endif %} class="before:content[''] peer relative size-4 appearance-none overflow-hidden rounded-sm border border-outline bg-surface-alt before:absolute before:inset-0 checked:border-primary checked:before:bg-primary focus:outline-2 focus:outline-offset-2 focus:outline-outline-strong checked:focus:outline-primary active:outline-offset-0 disabled:cursor-not-allowed dark:border-outline-dark dark:bg-surface-dark-alt dark:checked:border-primary-dark dark:checked:before:bg-primary-dark dark:focus:outline-outline-dark-strong dark:checked:focus:outline-primary-dark" {{ attrs | safe }}/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor" fill="none" stroke-width="4" class="pointer-events-none invisible absolute left-1/2 top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 text-on-primary peer-checked:visible dark:text-on-primary-dark">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>{{ label }}</span>
|
||||
</label>
|
||||
{%- endmacro checkbox %}
|
||||
|
||||
@@ -11,17 +11,7 @@
|
||||
The host template provides the wrapper
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" class="relative ...">
|
||||
so it controls its own positioning (e.g. ml-auto in admin). #}
|
||||
<button type="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}"
|
||||
title="{{ t(key='settings', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-outline-strong dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:focus-visible:outline-outline-dark-strong">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
{{ ui::icon_button(aria_label=t(key='settings', lang=lang | default(value='sk')), attrs='@click="open = !open" :aria-expanded="open"', icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>') }}
|
||||
<div x-show="open" x-cloak @click.outside="open = false" x-transition.origin.top.right
|
||||
class="absolute right-0 mt-2 flex w-56 flex-col overflow-hidden rounded-radius border border-outline bg-surface-alt py-1 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt"
|
||||
role="menu">
|
||||
|
||||
9
assets/views/penguinui/checkbox/default-checkbox.html
Normal file
9
assets/views/penguinui/checkbox/default-checkbox.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<label for="checkboxDefault" class="flex items-center gap-2 text-sm font-medium text-on-surface dark:text-on-surface-dark has-checked:text-on-surface-strong dark:has-checked:text-on-surface-dark-strong has-disabled:cursor-not-allowed has-disabled:opacity-75">
|
||||
<span class="relative flex items-center">
|
||||
<input id="checkboxDefault" type="checkbox" class="before:content[''] peer relative size-4 appearance-none overflow-hidden rounded-sm border border-outline bg-surface-alt before:absolute before:inset-0 checked:border-primary checked:before:bg-primary focus:outline-2 focus:outline-offset-2 focus:outline-outline-strong checked:focus:outline-primary active:outline-offset-0 disabled:cursor-not-allowed dark:border-outline-dark dark:bg-surface-dark-alt dark:checked:border-primary-dark dark:checked:before:bg-primary-dark dark:focus:outline-outline-dark-strong dark:checked:focus:outline-primary-dark" checked/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor" fill="none" stroke-width="4" class="pointer-events-none invisible absolute left-1/2 top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 text-on-primary peer-checked:visible dark:text-on-primary-dark">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>Notifications</span>
|
||||
</label>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="relative flex w-full max-w-sm flex-col gap-1">
|
||||
<label class="w-fit pl-0.5 text-sm text-on-surface dark:text-on-surface-dark" for="fileInput">Upload File</label>
|
||||
<input id="fileInput" type="file" class="w-full overflow-clip rounded-radius border border-outline bg-surface-alt/50 text-sm text-on-surface file:mr-4 file:border-none file:bg-surface-alt file:px-4 file:py-2 file:font-medium file:text-on-surface-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:file:bg-surface-dark-alt dark:file:text-on-surface-dark-strong dark:focus-visible:outline-primary-dark" />
|
||||
</div>
|
||||
12
assets/views/penguinui/select/default-select.html
Normal file
12
assets/views/penguinui/select/default-select.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="relative flex w-full max-w-xs flex-col gap-1 text-on-surface dark:text-on-surface-dark">
|
||||
<label for="os" class="w-fit pl-0.5 text-sm">Operating System</label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="absolute pointer-events-none right-4 top-8 size-5">
|
||||
<path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<select id="os" name="os" class="w-full appearance-none rounded-radius border border-outline bg-surface-alt px-4 py-2 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:focus-visible:outline-primary-dark">
|
||||
<option selected>Please Select</option>
|
||||
<option value="mac">Mac</option>
|
||||
<option value="windows">Windows</option>
|
||||
<option value="linux">Linux</option>
|
||||
</select>
|
||||
</div>
|
||||
4
assets/views/penguinui/text-area/default-textarea.html
Normal file
4
assets/views/penguinui/text-area/default-textarea.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div class="flex w-full max-w-md flex-col gap-1 text-on-surface dark:text-on-surface-dark">
|
||||
<label for="textArea" class="w-fit pl-0.5 text-sm">Comment</label>
|
||||
<textarea id="textArea" class="w-full rounded-radius border border-outline bg-surface-alt px-2.5 py-2 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:focus-visible:outline-primary-dark" rows="3" placeholder="We'd love to hear from you..."></textarea>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="flex w-full max-w-xs flex-col gap-1 text-on-surface dark:text-on-surface-dark">
|
||||
<label for="textInputDefault" class="w-fit pl-0.5 text-sm">Name</label>
|
||||
<input id="textInputDefault" type="text" class="w-full rounded-radius border border-outline bg-surface-alt px-2 py-2 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:focus-visible:outline-primary-dark" name="name" placeholder="Enter your name" autocomplete="name"/>
|
||||
</div>
|
||||
@@ -28,13 +28,7 @@
|
||||
hx-on::after-request="if (event.detail.successful) toast('{{ t(key='cart-added', lang=lang | default(value='sk')) }}')">
|
||||
<input type="hidden" name="product_id" value="{{ product.id }}">
|
||||
<input type="hidden" name="quantity" value="1">
|
||||
<button type="submit"
|
||||
class="flex w-full items-center justify-center gap-2 whitespace-nowrap rounded-radius bg-primary px-4 py-2 text-center text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="size-3.5">
|
||||
<path fill-rule="evenodd" d="M5 4a3 3 0 0 1 6 0v1h.643a1.5 1.5 0 0 1 1.492 1.35l.7 7A1.5 1.5 0 0 1 12.342 15H3.657a1.5 1.5 0 0 1-1.492-1.65l.7-7A1.5 1.5 0 0 1 4.357 5H5V4Zm4.5 0v1h-3V4a1.5 1.5 0 0 1 3 0Zm-3 3.75a.75.75 0 0 0-1.5 0v1a3 3 0 1 0 6 0v-1a.75.75 0 0 0-1.5 0v1a1.5 1.5 0 1 1-3 0v-1Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ t(key="add-to-cart", lang=lang | default(value='sk')) }}
|
||||
</button>
|
||||
{{ ui::button(label=t(key="add-to-cart", lang=lang | default(value='sk')), type="submit", extra="w-full", icon='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="size-3.5"><path fill-rule="evenodd" d="M5 4a3 3 0 0 1 6 0v1h.643a1.5 1.5 0 0 1 1.492 1.35l.7 7A1.5 1.5 0 0 1 12.342 15H3.657a1.5 1.5 0 0 1-1.492-1.65l.7-7A1.5 1.5 0 0 1 4.357 5H5V4Zm4.5 0v1h-3V4a1.5 1.5 0 0 1 3 0Zm-3 3.75a.75.75 0 0 0-1.5 0v1a3 3 0 1 0 6 0v-1a.75.75 0 0 0-1.5 0v1a1.5 1.5 0 1 1-3 0v-1Z" clip-rule="evenodd" /></svg>') }}
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="inline-flex justify-center rounded-radius bg-danger/10 px-3 py-2 text-xs font-medium text-danger">{{ t(key="out-of-stock", lang=lang | default(value='sk')) }}</p>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
$el.dispatchEvent(new Event('cartchange', { bubbles: true }));
|
||||
}
|
||||
"
|
||||
class="w-20 rounded-radius border border-outline bg-surface px-2 py-1 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
class="w-20 rounded-radius border border-outline bg-surface-alt px-2 py-1 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark">
|
||||
</form>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right font-medium tabular-nums">{{ item.line_total }} {{ item.currency }}</td>
|
||||
@@ -44,7 +44,7 @@
|
||||
<form method="post" action="/cart/remove"
|
||||
hx-post="/cart/remove" hx-target="#cart-body" hx-swap="innerHTML">
|
||||
<input type="hidden" name="product_id" value="{{ item.id }}">
|
||||
<button type="submit" class="text-xs font-medium text-danger hover:underline">{{ t(key="cart-remove", lang=lang | default(value='sk')) }}</button>
|
||||
{{ ui::button(variant="ghost-danger", label=t(key="cart-remove", lang=lang | default(value='sk')), type="submit", size="px-2 py-1 text-xs") }}
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -61,12 +61,12 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap justify-between gap-3">
|
||||
{{ ui::button_outline(label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop") }}
|
||||
{{ ui::button_primary(label=t(key="cart-checkout", lang=lang | default(value='sk')), href="/checkout") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop") }}
|
||||
{{ ui::button(label=t(key="cart-checkout", lang=lang | default(value='sk')), href="/checkout", size="px-5 py-2 text-sm") }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-radius border border-outline px-6 py-16 text-center dark:border-outline-dark">
|
||||
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="cart-empty", lang=lang | default(value='sk')) }}</p>
|
||||
{{ ui::button_primary(label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop", extra="mt-4") }}
|
||||
{{ ui::button(label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop", extra="mt-4") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% import "macros/ui.html" as ui %}
|
||||
|
||||
{% block title %}{{ category.name }}{% endblock title %}
|
||||
|
||||
|
||||
@@ -36,13 +36,11 @@
|
||||
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-contact", lang=lang | default(value='sk')) }}</legend>
|
||||
<div class="space-y-1.5">
|
||||
<label for="email" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-email", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="email" name="email" type="email" required
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="email", id="email", type="email", required=true, autocomplete="email") }}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="customer_name" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-name", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="customer_name" name="customer_name" type="text" required
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="customer_name", id="customer_name", required=true, autocomplete="name") }}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="phone" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-phone", lang=lang | default(value='sk')) }}</label>
|
||||
@@ -73,8 +71,7 @@
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<input id="phone" name="phone" type="tel" required autocomplete="tel" inputmode="tel" placeholder="900 000 000"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="phone", id="phone", type="tel", required=true, autocomplete="tel", placeholder="900 000 000", attrs='inputmode="tel"') }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -84,19 +81,16 @@
|
||||
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</legend>
|
||||
<div class="space-y-1.5">
|
||||
<label for="address" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-address", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="address" name="address" type="text" required
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="address", id="address", required=true, autocomplete="street-address") }}
|
||||
</div>
|
||||
<div class="grid gap-4 sm:grid-cols-3">
|
||||
<div class="space-y-1.5">
|
||||
<label for="city" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-city", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="city" name="city" type="text" required
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="city", id="city", required=true, autocomplete="address-level2") }}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="zip" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-zip", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="zip" name="zip" type="text" required
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="zip", id="zip", required=true, autocomplete="postal-code") }}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<label for="country" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-country", lang=lang | default(value='sk')) }}</label>
|
||||
@@ -182,8 +176,7 @@
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label for="note" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-note", lang=lang | default(value='sk')) }}</label>
|
||||
<textarea id="note" name="note" rows="3"
|
||||
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark"></textarea>
|
||||
{{ ui::textarea(name="note", id="note", rows="3") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -212,7 +205,7 @@
|
||||
<span>{{ t(key="cart-total", lang=lang | default(value='sk')) }}</span>
|
||||
<span class="tabular-nums text-primary dark:text-primary-dark" x-text="fmt(subtotal + carrierPrice) + ' {{ currency }}'"></span>
|
||||
</div>
|
||||
{{ ui::button_primary(label=t(key="checkout-place-order", lang=lang | default(value='sk')), type="submit", attrs=':disabled="!canSubmit"', extra="w-full disabled:opacity-40") }}
|
||||
{{ ui::button(label=t(key="checkout-place-order", lang=lang | default(value='sk')), type="submit", attrs=':disabled="!canSubmit"', extra="w-full", size="px-6 py-2.5 text-sm") }}
|
||||
</aside>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% import "macros/ui.html" as ui %}
|
||||
|
||||
{% block title %}{{ t(key="nav-shop", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="text-center">
|
||||
{{ ui::button_outline(label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop") }}
|
||||
{{ ui::button(variant="outline-secondary", label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop", size="px-5 py-2 text-sm") }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -45,10 +45,9 @@
|
||||
<input type="hidden" name="product_id" value="{{ product.id }}">
|
||||
<div class="space-y-1.5">
|
||||
<label for="quantity" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="quantity", lang=lang | default(value='sk')) }}</label>
|
||||
<input id="quantity" name="quantity" type="number" min="1" max="{{ product.stock }}" value="1"
|
||||
class="w-24 rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
{{ ui::input(name="quantity", id="quantity", type="number", value="1", width="w-24", attrs='min="1" max="' ~ product.stock ~ '"') }}
|
||||
</div>
|
||||
{{ ui::button_primary(label=t(key="add-to-cart", lang=lang | default(value='sk')), type="submit") }}
|
||||
{{ ui::button(label=t(key="add-to-cart", lang=lang | default(value='sk')), type="submit", size="px-5 py-2 text-sm") }}
|
||||
</form>
|
||||
<p class="text-sm text-on-surface/60 dark:text-on-surface-dark/60">{{ t(key="in-stock", lang=lang | default(value='sk')) }}: {{ product.stock }}</p>
|
||||
{% else %}
|
||||
|
||||
@@ -168,72 +168,49 @@ and then use it (instead of hand-rolling):
|
||||
|
||||
---
|
||||
|
||||
## 9. Checkbox
|
||||
**Penguin UI: `checkbox/` (3 variants)**
|
||||
## 9. Checkbox — ✅ DONE
|
||||
**Penguin UI: `checkbox/default-checkbox.html`**
|
||||
|
||||
| # | Location | What it is | Size |
|
||||
|---|----------|------------|------|
|
||||
| 15 | `assets/views/admin/catalog/product_form.html:85-89` | "Published" checkbox | ~5 lines |
|
||||
| 16 | `assets/views/admin/catalog/category_form.html:67-71` | "Published" checkbox | ~5 lines |
|
||||
| 17 | `assets/views/admin/shipping/index.html:25-29` | "Enabled" checkbox | ~5 lines |
|
||||
- Exact upstream mirror at `assets/views/penguinui/checkbox/default-checkbox.html` (reference only)
|
||||
- `ui::checkbox(name, label, id, value="on", checked, attrs)` macro in `macros/ui.html`
|
||||
(full Penguin control: custom box + check-icon + label, `has-checked:`/`peer` variants).
|
||||
- Adopted: product/category "Published" + shipping "Enabled".
|
||||
|
||||
---
|
||||
## 10. Text Input — ✅ DONE
|
||||
**Penguin UI: `text-input/default-text-input.html`**
|
||||
|
||||
## 10. Text Input
|
||||
**Penguin UI: `text-input/` (8 variants)**
|
||||
- Exact upstream mirror at `assets/views/penguinui/text-input/default-text-input.html` (reference only)
|
||||
- `ui::input(name, type, id, value, placeholder, required, autocomplete, attrs, extra, width="w-full")`
|
||||
macro — **verbatim** Penguin classes (`bg-surface-alt`, `focus-visible:outline-*`).
|
||||
Adopted at every text/email/number/password input: login (2), checkout (email,
|
||||
name, phone, address, city, zip), product form (6), category form (3), product
|
||||
detail quantity, shipping price (`width="w-28"`).
|
||||
- The cart-body quantity input keeps its complex `@change` handler **inline** with
|
||||
the same Penguin classes (mixed single/double quotes can't pass through a macro arg).
|
||||
- Note: padding is Penguin's `px-2 py-2` (was `px-3`) and bg is `bg-surface-alt` (was
|
||||
`bg-surface`) — the real Penguin look.
|
||||
|
||||
| # | Location | What it is | Size |
|
||||
|---|----------|------------|------|
|
||||
| 18 | `assets/views/shop/checkout.html:37-44` | Email + name text inputs | ~8 lines |
|
||||
| 19 | `assets/views/shop/checkout.html:84-99` | Address, city, ZIP text inputs | ~16 lines |
|
||||
| 20 | `assets/views/admin/login.html:34-51` | Email + password inputs (with focus ring styles) | ~18 lines |
|
||||
| 21 | `assets/views/admin/catalog/product_form.html:19-68` | Name, price, currency, stock, SKU, slug inputs + textarea | ~50 lines |
|
||||
| 22 | `assets/views/admin/catalog/category_form.html:19-55` | Name, slug, position inputs + textarea | ~37 lines |
|
||||
| 23 | `assets/views/shop/show.html:46-48` | Quantity number input | ~3 lines |
|
||||
| 24 | `assets/views/shop/_cart_body.html:30-38` | Quantity number input with `@change` confirmation dialog | ~9 lines |
|
||||
| 25 | `assets/views/admin/shipping/index.html:20-24` | Price text input | ~5 lines |
|
||||
## 11. Textarea — ✅ DONE
|
||||
**Penguin UI: `text-area/default-textarea.html`**
|
||||
|
||||
**Pattern: Every input is hand-styled with:**
|
||||
```
|
||||
w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface
|
||||
focus:outline-2 focus:outline-primary
|
||||
dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark
|
||||
```
|
||||
This exact class string appears 15+ times across the codebase.
|
||||
- Exact upstream mirror at `assets/views/penguinui/text-area/default-textarea.html` (reference only)
|
||||
- `ui::textarea(name, id, value, rows, placeholder, required, attrs, extra)` macro.
|
||||
- Adopted: checkout note, product & category description.
|
||||
|
||||
---
|
||||
## 12. Select/Dropdown (Native) — ✅ DONE
|
||||
**Penguin UI: `select/default-select.html`**
|
||||
|
||||
## 11. Textarea
|
||||
**Penguin UI: `textarea/`**
|
||||
- Exact upstream mirror at `assets/views/penguinui/select/default-select.html` (reference only)
|
||||
- Adopted inline (3 sites: product category, category parent, order status) — Penguin
|
||||
`appearance-none` select on `bg-surface-alt` wrapped in `relative` with the chevron
|
||||
SVG. Inline rather than a macro because the `<option>` set is caller-specific.
|
||||
|
||||
| # | Location | What it is | Size |
|
||||
|---|----------|------------|------|
|
||||
| 26 | `assets/views/shop/checkout.html:183-186` | Order note textarea | ~4 lines |
|
||||
| 27 | `assets/views/admin/catalog/product_form.html:71-73` | Product description textarea | ~3 lines |
|
||||
| 28 | `assets/views/admin/catalog/category_form.html:53-55` | Category description textarea | ~3 lines |
|
||||
## 13. File Input — ✅ DONE
|
||||
**Penguin UI: `file-input/default-file-input.html`**
|
||||
|
||||
---
|
||||
|
||||
## 12. Select/Dropdown (Native)
|
||||
**Penguin UI: `select/` (7 variants)**
|
||||
|
||||
| # | Location | What it is | Size |
|
||||
|---|----------|------------|------|
|
||||
| 29 | `assets/views/admin/catalog/product_form.html:53-60` | Category select | ~8 lines |
|
||||
| 30 | `assets/views/admin/catalog/category_form.html:40-49` | Parent category select (tree indented with `— `) | ~10 lines |
|
||||
| 31 | `assets/views/admin/orders/show.html:108-112` | Order status select | ~5 lines |
|
||||
|
||||
---
|
||||
|
||||
## 13. File Input
|
||||
**Penguin UI: `file-input/`**
|
||||
|
||||
| # | Location | What it is | Size |
|
||||
|---|----------|------------|------|
|
||||
| 32 | `assets/views/admin/catalog/product_form.html:78-82` | Product image upload | ~5 lines |
|
||||
| 33 | `assets/views/admin/catalog/category_form.html:58-64` | Category image upload | ~7 lines |
|
||||
|
||||
**Both use the same Tailwind `file:mr-3 file:...` prefix pattern for styling.**
|
||||
- Exact upstream mirror at `assets/views/penguinui/file-input/default-file-input.html` (reference only)
|
||||
- `ui::file_input(name, id, accept, attrs, extra)` macro (verbatim Penguin `file:` styling).
|
||||
- Adopted: product & category image upload.
|
||||
|
||||
---
|
||||
|
||||
@@ -285,25 +262,44 @@ This is copy-pasted 5 times.
|
||||
|
||||
---
|
||||
|
||||
## 17. Buttons — ✅ DONE (canonical macros + adopted)
|
||||
## 17. Buttons — ✅ DONE
|
||||
**Penguin UI: `buttons/default-button.html`, `outline-button.html`, `ghost-button.html`, `button-with-icon.html`**
|
||||
|
||||
- Exact upstream mirrors at `assets/views/penguinui/buttons/*.html` (reference only).
|
||||
- Canonical button macros in `assets/views/macros/ui.html`:
|
||||
`ui::button_primary`, `ui::button_outline` (neutral secondary), `ui::button_danger`,
|
||||
`ui::button_ghost`. Each takes `label`, `type`, `href` (renders `<a>` vs `<button>`),
|
||||
`attrs` (raw — htmx / `:disabled` / name / value), `extra` (extra classes).
|
||||
Upstream's color-button token typos (`text-onDanger` etc.) are fixed to our real
|
||||
tokens (`text-on-danger`).
|
||||
- Macros in `assets/views/macros/ui.html`:
|
||||
`ui::button(label, variant="primary", type, href, attrs, extra, icon, size="px-4 py-2 text-sm")`
|
||||
and `ui::icon_button(icon, variant="ghost-secondary", aria_label, attrs, …)`.
|
||||
The per-variant class strings are the **verbatim** Penguin variants (solid
|
||||
`primary|secondary|danger|success|warning|info`, `outline-*`, `ghost-*`) — only
|
||||
`inline-flex items-center justify-center gap-2` is added so `<a>`/`w-full`/`icon`
|
||||
render, and upstream's `text-onDanger`/`text-onSuccess`… token typos are fixed to
|
||||
our real `text-on-*` tokens. `href` → `<a>` else `<button>`; `attrs` is raw
|
||||
(htmx / `:disabled` / name / value); `icon` is a raw `<svg>` rendered before the
|
||||
label (Penguin button-with-icon).
|
||||
- **Sizes are NOT normalized**: `size` defaults to Penguin's `px-4 py-2 text-sm`
|
||||
but each call site that differed keeps it (`px-3 py-2` form-header cancels &
|
||||
order back, `px-5 py-2` add-to-cart / cart-checkout / order-confirmed continue,
|
||||
`px-6 py-2.5` checkout place-order, `px-3 py-1.5 text-xs` table actions).
|
||||
- Adopted across every standard filled/outline/submit button: login, product &
|
||||
category forms (save/cancel), products/categories "new" + empty-state CTAs,
|
||||
orders detail (back/ship/status), shipping save, cart (continue/checkout/empty),
|
||||
checkout place-order (`:disabled` via `attrs`), product detail add-to-cart,
|
||||
order-confirmed continue.
|
||||
- Intentionally left inline (different components, not the standard button):
|
||||
icon-only ghost buttons (#51 gear/hamburger/chevron), compact table row-action
|
||||
buttons (`edit`/`view`/`delete`, `px-3 py-1.5 text-xs`), the `text-danger`
|
||||
"Remove" text link (#52), the file-input button (#13), and nav menu links.
|
||||
category forms (save / cancel = `outline-secondary`), products/categories "new" +
|
||||
empty-state CTAs, orders detail (back/ship/status), shipping save, cart
|
||||
(continue/checkout/empty), checkout place-order (`:disabled` via `attrs`),
|
||||
product detail add-to-cart, order-confirmed continue.
|
||||
- Icon-only buttons now use `ui::icon_button(icon, variant="ghost-secondary",
|
||||
aria_label, attrs, …)` — Penguin ghost treatment, square. Converted: settings
|
||||
gear, both hamburgers (site + admin), admin sidebar toggle, mobile category
|
||||
toggle. The cart link (live `x-init` badge) and the category-accordion chevron
|
||||
keep the same Penguin ghost classes **inline** only because their markup mixes
|
||||
single+double quotes that can't be passed through a Tera macro arg — visually
|
||||
identical to `icon_button`.
|
||||
- Table row-actions (`edit`/`view`/`delete`/`View`/`label`) → `ui::button`
|
||||
`outline-secondary` / `outline-danger` at `size="px-3 py-1.5 text-xs"`; cart
|
||||
"Remove" → `ghost-danger`; card add-to-cart → `ui::button` with the cart `icon`.
|
||||
- Still genuinely not this component (tracked elsewhere): toast dismiss/Reply
|
||||
buttons (part of the vendored toast mirror, already Penguin), settings dropdown
|
||||
menu items (Penguin dropdown items), gallery thumbnail buttons (carousel),
|
||||
sidebar logout/exit (Penguin sidebar link treatment), and navbar nav-menu
|
||||
links/logout (belong to §1 Navbar). The file-input button is §13.
|
||||
|
||||
> Gotcha for future macro use: Tera renders `{% include %}` in the **includer's**
|
||||
> macro scope, so a template that includes a partial which calls `ui::` must also
|
||||
|
||||
Reference in New Issue
Block a user