Files
tutor-tool/app/templates/macros/ui.html
2026-04-22 21:14:30 +02:00

123 lines
4.5 KiB
HTML

{#
Reusable UI macros for Tutortool templates.
Usage: {% from "macros/ui.html" import flash_messages, styled_field, text_input %}
#}
{# Renders all flashed messages with colour-coded styling. #}
{% macro flash_messages() %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="space-y-3 mb-6">
{% for category, message in messages %}
{% if category == 'error' %}
{% set alert_class = 'bg-red-900 border-red-400 text-red-100' %}
{% elif category == 'expired' %}
{% set alert_class = 'bg-orange-900 border-orange-400 text-orange-100' %}
{% else %}
{% set alert_class = 'bg-green-900 border-green-400 text-green-100' %}
{% endif %}
<div class="p-3 border rounded-lg {{ alert_class }}" role="alert">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% endmacro %}
{#
Renders a WTForms field with a label and standard Tailwind styling.
Pass extra_kwargs as a dict for additional HTML attributes (e.g. type, multiple).
#}
{% macro styled_field(field, extra_kwargs={}) %}
<div>
<label class="block mb-1 text-sm font-medium">{{ field.label }}</label>
{{ field(
class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary",
**extra_kwargs
) }}
{% for error in field.errors %}
<p class="text-red-400 text-sm mt-1">{{ error }}</p>
{% endfor %}
</div>
{% endmacro %}
{#
Renders a plain <input> (not WTForms) with standard styling.
`type` defaults to 'text'.
#}
{% macro text_input(name, label, placeholder='', type='text', required=true, autofocus=false) %}
<div class="mb-4">
<label for="{{ name }}" class="block text-sm font-medium text-gray-400 mb-2">{{ label }}</label>
<input
type="{{ type }}"
id="{{ name }}"
name="{{ name }}"
placeholder="{{ placeholder }}"
{% if required %}required{% endif %}
{% if autofocus %}autofocus{% endif %}
class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
</div>
{% endmacro %}
{# Renders a delete button wrapped in a form. #}
{% macro delete_button(action_url, label='Delete') %}
<form method="POST" action="{{ action_url }}">
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded-lg text-sm">
{{ label }}
</button>
</form>
{% endmacro %}
{# Course-switcher dropdown (used in admin and index pages). #}
{% macro course_switcher(courses, active_course_name, switch_url_prefix, id_suffix='') %}
{% set root_id = 'course-switcher' ~ id_suffix %}
{% set menu_id = 'course-switcher-menu' ~ id_suffix %}
{% set chevron_id = 'course-switcher-chevron' ~ id_suffix %}
<div class="relative" id="{{ root_id }}">
<button
type="button"
data-cs-root="{{ root_id }}"
data-cs-menu="{{ menu_id }}"
data-cs-chevron="{{ chevron_id }}"
onclick="toggleCourseSwitcher(this)"
class="flex items-center gap-2 px-4 py-1.5 rounded-full
bg-surface shadow-md ring-1 ring-border
text-sm font-medium text-text
hover:ring-primary hover:text-primary
transition whitespace-nowrap">
<span>{{ active_course_name }}</span>
<svg class="w-3.5 h-3.5 text-muted transition-transform duration-200" id="{{ chevron_id }}"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</button>
<div id="{{ menu_id }}"
class="hidden absolute left-1/2 -translate-x-1/2 mt-2 w-52 z-50
bg-surface border border-border rounded-xl shadow-lg overflow-hidden whitespace-nowrap">
{% for course in courses %}
<form method="POST" action="{{ switch_url_prefix }}/{{ course.id }}">
<button type="submit"
class="w-full text-left px-4 py-2.5 text-sm
{% if course.name == active_course_name %}
text-primary font-semibold bg-primary/10
{% else %}
text-text hover:bg-background hover:text-primary
{% endif %}
transition">
{{ course.name }}
</button>
</form>
{% endfor %}
</div>
</div>
{% endmacro %}