{# macros/interactive.html - Interactive UI component macros for APEP. Import with: {% from "apep/macros/interactive.html" import btn, hero_btn, download_btn, theme_switch, toast_container %} Macros: btn(label, variant, size, url, disabled) hero_btn(label, url, subtitle, variant) download_btn(label, file_path, variant) theme_switch(variant, position) toast_container(position) Author: sora7672 #} {% from "apep/macros/helper.html" import alert %} {# Renders a button or anchor using the APEP color component attribute system. No CSS classes. Colors via bgcolor/txtcolor/bordercolor from base_color_components.css. Hover via bgcolor-hover/txtcolor-hover/bordercolor-hover - no JS needed. Variants: primary - yellow bg, dark text on hover darkens secondary - blue bg, light cream text, hover darkens ghost - transparent bg + blue border, hover fills blue danger - red bg, light text, hover darkens @param label {string} - button text @param variant {string} - "primary" | "secondary" | "ghost" | "danger", default "primary" @param size {string} - "sm" | "md" | "lg", default "md" @param url {string} - renders as when set and not disabled. Optional. @param disabled {bool} - disables interaction. Forces {% endif %} {% endif %} {% endmacro %} {# Renders a large call-to-action button with an optional subtitle line. Built from custom tags and color components - no extra CSS needed. Always renders as an anchor tag. @param label {string} - main button text @param url {string} - link target, default "#" @param subtitle {string} - small muted text rendered below the button. Optional. @param variant {string} - "primary" | "secondary", default "primary" #} {% macro hero_btn(label, url="#", subtitle=None, variant="primary") %} {% if not label %} {{ alert("hero_btn: label is required", "Pass a non-empty string as the first argument.") }} {% elif variant not in ["primary", "secondary"] %} {{ alert("hero_btn: invalid variant \"" ~ variant ~ "\"", "Allowed values: \"primary\", \"secondary\".") }} {% else %} {% set _v = { "primary": ["primary", "surface", "primary", "primary-dark"], "secondary": ["secondary", "text-inverted", "secondary", "secondary-dark"] }[variant] %} {{ label }} {% if subtitle %} {{ subtitle }} {% endif %} {% endif %} {% endmacro %} {# Renders a download button in one of three visual variants. The file_path is passed as a data attribute and handled by download-btn.js, which resolves the path under /downloads/ and triggers the browser download. @param label {string} - displayed button text @param file_path {string} - path to the file, resolved under /downloads/ by download-btn.js @param variant {string} - "default" | "icon" | "minimal", default "default" default - full button with filetype icon icon - compact icon + label, no wrapper minimal - underline link style with down arrow #} {% macro download_btn(label, file_path, variant="default") %} {% if not label %} {{ alert("download_btn: label is required", "Pass a non-empty string as the first argument.") }} {% elif not file_path %} {{ alert("download_btn: file_path is required", "Pass a valid file path as the second argument.") }} {% elif variant not in ["default", "icon", "minimal"] %} {{ alert("download_btn: invalid variant \"" ~ variant ~ "\"", "Allowed values: \"default\", \"icon\", \"minimal\".") }} {% else %} {% if variant == "minimal" %} {% elif variant == "icon" %} {% else %}
{% endif %} {% endif %} {% endmacro %} {# Renders a light/dark theme toggle in one of three variants. theme-switch.js is loaded globally by the backend to prevent flash-of-wrong-theme. All variants are detected via querySelectorAll("[data-theme-btn]") or "[data-theme-checkbox]" - multiple variants on one page are supported. @param variant {string} - "icon" | "slider" | "floating", default "icon" icon - plain button, place anywhere in layout slider - toggle slider with animated thumb floating - fixed-position button @param position {string} - only used for variant "floating" "top-left" | "top-right" | "bottom-left" | "bottom-right" default "bottom-right" #} {% macro theme_switch(variant="floating", position="bottom-right") %} {% if variant not in ["icon", "slider", "floating"] %} {{ alert("theme_switch: invalid variant \"" ~ variant ~ "\"", "Allowed values: \"icon\", \"slider\", \"floating\".") }} {% elif variant == "floating" and position not in ["top-left", "top-right", "bottom-left", "bottom-right"] %} {{ alert("theme_switch: invalid position \"" ~ position ~ "\"", "Allowed values: \"top-left\", \"top-right\", \"bottom-left\", \"bottom-right\".") }} {% else %} {% if variant == "floating" %}
{% elif variant == "slider" %} {% else %} {% endif %} {% endif %} {% endmacro %} {# Renders the toast container. Place once per page, ideally before . Toasts are injected and removed via toast.js (ApepToast.show()). @param position {string} - "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center" | "bottom-center", default "bottom-right" #} {% macro toast_container(position="bottom-right") %} {% if position not in ["top-right", "top-left", "bottom-right", "bottom-left", "top-center", "bottom-center"] %} {{ alert("toast_container: invalid position \"" ~ position ~ "\"", "Allowed values: \"top-right\", \"top-left\", \"bottom-right\", \"bottom-left\", \"top-center\", \"bottom-center\".") }} {% else %}
{% endif %} {% endmacro %}