diff --git a/README.md b/README.md index 835034e..4c26516 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Jinjalume is an open-source, HTML-first component kit for developers who want re - A copy-paste [component reference](docs/components.md) with signatures and rendered markup examples - Tailwind CSS v4 build setup - A working Flask demo +- Copy-paste UI blocks for authentication and admin dashboards - Contributor documentation, issue templates, and continuous integration ## Quick start @@ -97,6 +98,10 @@ The demo includes a light/dark toggle backed by semantic CSS custom properties. The `/htmx` demo shows an optional HTMX enhancement around a normal Flask form. The form remains usable without JavaScript or HTMX, and HTMX is loaded only by that demo page. The core package has no HTMX dependency. +## UI blocks + +The demo also includes realistic application screens at `/blocks`, `/login`, `/signup`, and `/admin`. The reusable block macros live under `jinjalume/blocks/` and compose the core components into auth flows and a responsive admin console. See [docs/blocks.md](docs/blocks.md) for signatures and copy-paste examples. + ## Development ```bash diff --git a/demo/app.py b/demo/app.py index 0b296cf..994d30c 100644 --- a/demo/app.py +++ b/demo/app.py @@ -11,6 +11,171 @@ def index(): return render_template("index.html") +@app.get("/blocks") +def blocks(): + return render_template("blocks.html") + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + email = request.form.get("email", "").strip() + errors = {} + form_error = None + success_message = None + + if request.method == "POST": + if not email: + errors["email"] = "Enter your email address." + if not request.form.get("password"): + errors["password"] = "Enter your password." + if errors: + form_error = "Review the fields below and try again." + else: + success_message = "Demo sign-in accepted. No account was created." + + return render_template( + "login.html", + email=email, + remember=bool(request.form.get("remember")), + email_error=errors.get("email"), + password_error=errors.get("password"), + form_error=form_error, + success_message=success_message, + ) + + +@app.route("/signup", methods=["GET", "POST"]) +def signup(): + name = request.form.get("name", "").strip() + email = request.form.get("email", "").strip() + errors = {} + form_error = None + success_message = None + + if request.method == "POST": + password = request.form.get("password", "") + confirmation = request.form.get("password_confirmation", "") + if not name: + errors["name"] = "Enter your full name." + if not email: + errors["email"] = "Enter your email address." + if len(password) < 8: + errors["password"] = "Use at least 8 characters." + if password != confirmation: + errors["confirmation"] = "Passwords must match." + if not request.form.get("terms"): + errors["terms"] = "Accept the terms to continue." + if errors: + form_error = "Review the fields below and try again." + else: + success_message = "Demo account created. No data was saved." + + return render_template( + "signup.html", + name=name, + email=email, + name_error=errors.get("name"), + email_error=errors.get("email"), + password_error=errors.get("password"), + confirmation_error=errors.get("confirmation"), + terms_error=errors.get("terms"), + form_error=form_error, + success_message=success_message, + ) + + +@app.get("/admin") +def admin(): + return render_template( + "admin.html", + nav_items=[ + {"label": "Overview", "href": "/admin"}, + {"label": "Customers", "href": "#customers", "badge": "24"}, + {"label": "Orders", "href": "#orders"}, + {"label": "Analytics", "href": "#analytics"}, + {"label": "Settings", "href": "#settings"}, + ], + metrics=[ + { + "label": "Total revenue", + "value": "$48,294", + "change": "+12.8%", + "note": "Compared with last month", + "icon": "$", + }, + { + "label": "Active customers", + "value": "2,431", + "change": "+8.2%", + "note": "Across all workspaces", + "icon": "↗", + }, + { + "label": "Open tickets", + "value": "18", + "change": "-4.5%", + "trend": "down", + "note": "Compared with last week", + "icon": "!", + }, + { + "label": "Conversion rate", + "value": "6.24%", + "change": "+1.4%", + "note": "From 38,910 sessions", + "icon": "%", + }, + ], + orders=[ + { + "customer": "Olivia Martin", + "email": "olivia@example.com", + "amount": "$1,240.00", + "status": "Paid", + "date": "Sep 12, 2026", + }, + { + "customer": "Phoenix Baker", + "email": "phoenix@example.com", + "amount": "$860.00", + "status": "Processing", + "date": "Sep 11, 2026", + }, + { + "customer": "Lana Byrd", + "email": "lana@example.com", + "amount": "$540.00", + "status": "Paid", + "date": "Sep 11, 2026", + }, + { + "customer": "Demi Wilkinson", + "email": "demi@example.com", + "amount": "$320.00", + "status": "Refunded", + "date": "Sep 10, 2026", + }, + ], + activity=[ + { + "label": "New workspace created", + "detail": "Acme Inc. joined the Pro plan", + "time": "12 minutes ago", + }, + { + "label": "Invoice paid", + "detail": "Olivia Martin paid invoice #1048", + "time": "48 minutes ago", + }, + { + "label": "Support ticket resolved", + "detail": "Ticket #284 was closed by Maya", + "time": "2 hours ago", + }, + ], + ) + + @app.route("/htmx", methods=["GET", "POST"]) def htmx_demo(): """Show an optional HTMX enhancement with a plain form fallback.""" diff --git a/demo/templates/admin.html b/demo/templates/admin.html new file mode 100644 index 0000000..d718244 --- /dev/null +++ b/demo/templates/admin.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} +{% from "jinjalume/blocks/dashboard.html" import dashboard_shell, stat_card %} +{% from "jinjalume/components/badge.html" import badge %} +{% from "jinjalume/components/button.html" import button %} +{% from "jinjalume/components/select.html" import select_field %} + +{% block title %}Admin dashboard · Jinjalume{% endblock %} +{% block site_header %}{% endblock %} +{% block main_class %}max-w-none p-0{% endblock %} + +{% block content %} + {% call dashboard_shell( + title="Overview", + description="Monitor your workspace performance and keep an eye on the latest customer activity.", + nav_items=nav_items, + active_item="Overview", + user_name="Alex Morgan", + user_email="alex@example.com" + ) %} +
+
+

At a glance

Your key numbers for this month.

+ {{ button("Export report", variant="secondary", size="sm", href="#export") }} +
+
+ {% for metric in metrics %} + {{ stat_card(metric.label, metric.value, change=metric.change, trend=metric.get("trend", "up"), note=metric.note, icon=metric.icon) }} + {% endfor %} +
+
+ +
+
+
+

Recent orders

The latest transactions across your workspace.

+
{{ select_field("order-period", label="Period", options=[{"value": "30", "label": "Last 30 days"}, {"value": "90", "label": "Last 90 days"}], value="30", class_name="text-xs") }}
+
+
+ + + + + + + {% for order in orders %} + {% set status_variant = "success" if order.status == "Paid" else "warning" if order.status == "Processing" else "danger" %} + + + + + + + {% endfor %} + +
Recent orders
CustomerStatusAmountDate
{{ order.customer }}{{ order.email }}{{ badge(order.status, variant=status_variant) }}{{ order.amount }}{{ order.date }}
+
+ +
+ +
+

Recent activity

What is happening today.

+
    + {% for event in activity %} +
  1. + + {% if not loop.last %}{% endif %} +

    {{ event.label }}

    +

    {{ event.detail }}

    +

    {{ event.time }}

    +
  2. + {% endfor %} +
+ View activity log → +
+
+ {% endcall %} +{% endblock %} diff --git a/demo/templates/base.html b/demo/templates/base.html index 0a6d077..f564ff5 100644 --- a/demo/templates/base.html +++ b/demo/templates/base.html @@ -7,17 +7,20 @@ + {% block site_header %}
Jinjalume
-
+ {% endblock %} +
{% block content %}{% endblock %}