diff --git a/fasthtml/toaster.py b/fasthtml/toaster.py
index d0ae6c16..5856643b 100644
--- a/fasthtml/toaster.py
+++ b/fasthtml/toaster.py
@@ -13,9 +13,17 @@
}
.fh-toast {
background-color: #333; color: white;
- padding: 12px 20px; border-radius: 4px; margin-bottom: 10px;
+ padding: 12px 28px 12px 20px; border-radius: 4px; margin-bottom: 10px;
max-width: 80%; width: auto; text-align: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ position: relative;
+}
+.fh-toast-dismiss {
+ position:absolute; top:0.2em; right:0.4em;
+ line-height:1em; padding: 0 0.2em 0.2em 0.2em; border-radius:inherit;
+ transform:scaleY(0.8); transform:scaleX(1.);
+ pointer-events:auto; cursor:pointer;
+ background-color:inherit; color:inherit; filter:brightness(0.85);
}
.fh-toast-info { background-color: #2196F3; }
.fh-toast-success { background-color: #4CAF50; }
@@ -24,21 +32,25 @@
"""
toast_js = """
-export function proc_htmx(sel, func) {
- htmx.onLoad(elt => {
+export function proc_htmx(sel, func) {{
+ htmx.onLoad(elt => {{
const elements = any(sel, elt, false);
if (elt.matches && elt.matches(sel)) elements.unshift(elt);
elements.forEach(func);
- });
-}
-proc_htmx('.fh-toast-container', async function(toast) {
+ }});
+}}
+proc_htmx('.fh-toast-container', async function(toast) {{
await sleep(100);
toast.style.opacity = '0.8';
- await sleep(3000);
+ await sleep({duration});
toast.style.opacity = '0';
await sleep(300);
toast.remove();
-});
+}});
+
+proc_htmx('.fh-toast-dismiss', function(elem) {{
+ elem.addEventListener('click', (e) => {{ e.target.parentElement.remove() }});
+}});
"""
def add_toast(sess, message, typ="info"):
@@ -46,14 +58,12 @@ def add_toast(sess, message, typ="info"):
sess.setdefault(sk, []).append((message, typ))
def render_toasts(sess):
- toasts = [Div(msg, cls=f"fh-toast fh-toast-{typ}") for msg,typ in sess.pop(sk, [])]
- return Div(Div(*toasts, cls="fh-toast-container"),
- hx_swap_oob="afterbegin:body")
+ toasts = [Div(msg, Span('x', cls='fh-toast-dismiss'), cls=f"fh-toast fh-toast-{typ}") for msg,typ in sess.pop(sk, [])]
+ return Div(Div(*toasts, cls="fh-toast-container"), hx_swap_oob="afterbegin:body")
def toast_after(resp, req, sess):
if sk in sess and (not resp or isinstance(resp, (tuple,FT,FtResponse))): req.injects.append(render_toasts(sess))
-def setup_toasts(app):
- app.hdrs += (Style(toast_css), Script(toast_js, type="module"))
+def setup_toasts(app, duration:float=10.):
+ app.hdrs += (Style(toast_css), Script(toast_js.format(duration=1000*duration), type="module"))
app.after.append(toast_after)
-
diff --git a/nbs/tutorials/quickstart_for_web_devs.ipynb b/nbs/tutorials/quickstart_for_web_devs.ipynb
index 5b981a84..a12398d5 100644
--- a/nbs/tutorials/quickstart_for_web_devs.ipynb
+++ b/nbs/tutorials/quickstart_for_web_devs.ipynb
@@ -1280,7 +1280,9 @@
"\n",
"1. `setup_toasts` is a helper function that adds toast dependencies. Usually this would be declared right after `fast_app()`\n",
"2. Toasts require sessions\n",
- "3. Views with Toasts must return FT or FtResponse components."
+ "3. Views with Toasts must return FT or FtResponse components.\n",
+ "\n",
+ "💡 `setup_toasts` takes a `duration` input that allows you to specify how long a toast will be visible before disappearing. For example `setup_toasts(duration=5)` sets the toasts duration to 5 seconds. By default toasts disappear after 10 seconds."
]
},
{