91 lines
9.5 KiB
HTML
91 lines
9.5 KiB
HTML
{# Reusable UI macros adapted from vendored Penguin UI components.
|
|
These are OUR adaptation layer; the byte-for-byte upstream sources live under
|
|
assets/views/penguinui/. Tailwind sees the full literal class strings here
|
|
(assets/css/app.css has @source "../views"), so every branch must spell its
|
|
classes out in full — never build class names by concatenation.
|
|
|
|
Usage:
|
|
{% import "macros/ui.html" as ui %}
|
|
{{ 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 / :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(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 %}
|
|
|
|
{# 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
|
|
with the danger icon. #}
|
|
{% macro alert_danger(message, extra="") -%}
|
|
<div class="flex w-full items-center gap-2 overflow-hidden rounded-radius border border-danger bg-danger/10 px-3 py-2 text-sm text-danger {{ extra }}" role="alert">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z" clip-rule="evenodd" />
|
|
</svg>
|
|
<span>{{ message }}</span>
|
|
</div>
|
|
{%- endmacro alert_danger %}
|
|
|
|
{# Soft-color badge. variant ∈ success | danger | warning | info | primary | neutral #}
|
|
{% macro badge(label, variant="neutral") -%}
|
|
{% if variant == "success" -%}
|
|
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-success bg-surface text-xs font-medium text-success dark:bg-surface-dark"><span class="bg-success/10 px-2 py-1">{{ label }}</span></span>
|
|
{%- elif variant == "danger" -%}
|
|
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-danger bg-surface text-xs font-medium text-danger dark:bg-surface-dark"><span class="bg-danger/10 px-2 py-1">{{ label }}</span></span>
|
|
{%- elif variant == "warning" -%}
|
|
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-warning bg-surface text-xs font-medium text-warning dark:bg-surface-dark"><span class="bg-warning/10 px-2 py-1">{{ label }}</span></span>
|
|
{%- elif variant == "info" -%}
|
|
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-info bg-surface text-xs font-medium text-info dark:bg-surface-dark"><span class="bg-info/10 px-2 py-1">{{ label }}</span></span>
|
|
{%- elif variant == "primary" -%}
|
|
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-primary bg-surface text-xs font-medium text-primary dark:border-primary-dark dark:bg-surface-dark dark:text-primary-dark"><span class="bg-primary/10 px-2 py-1 dark:bg-primary-dark/10">{{ label }}</span></span>
|
|
{%- else -%}
|
|
<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 %}
|