Coverage for src / lexigram / ui / molecules / toast.py: 97%
32 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1"""Toast notification — inline Alpine.js component and shared types.
3This module provides the inline ``InlineToast`` Component (Alpine.js show/hide)
4and re-exports the server-driven toast system from the layout layer
5so consumers have a single import path.
6"""
8from __future__ import annotations
10from typing import Any
12from lexigram.ui.core.base import Component, el
13from lexigram.ui.layouts.server_toasts import (
14 ServerToastChannel,
15 ToastData,
16 ToastRenderer,
17 ToastType,
18 flash_to_toast,
19)
20from lexigram.ui.styles import get_semantic_icon, get_toast_classes
22__all__ = [
23 "InlineToast",
24 "Toast", # deprecated alias for InlineToast
25 "ToastData",
26 "ServerToastChannel",
27 "ToastRenderer", # deprecated alias for ServerToastChannel
28 "ToastType",
29 "flash_to_toast",
30]
33class InlineToast(Component):
34 """Toast notification component with Alpine.js auto-dismiss and optional action.
36 For server-driven toasts (HTMX ``X-Toast`` headers, configurable position,
37 stacking), use ``ServerToastChannel`` with ``ToastData`` instead.
39 Args:
40 message: Notification message.
41 toast_type: Severity — ``"info"``, ``"success"``, ``"warning"``, ``"error"``.
42 duration: Auto-dismiss delay in milliseconds (default 3000).
43 action_label: Optional label for an inline action button.
44 action_url: URL the action button links to (``href`` when set).
45 """
47 def __init__(
48 self,
49 message: str,
50 toast_type: str = "info",
51 duration: int = 3000,
52 action_label: str = "",
53 action_url: str = "",
54 **props: Any,
55 ) -> None:
56 super().__init__(
57 message=message,
58 type=toast_type,
59 duration=duration,
60 action_label=action_label,
61 action_url=action_url,
62 **props,
63 )
64 self.message = message
65 self.type = toast_type
66 self.duration = duration
67 self.action_label = action_label
68 self.action_url = action_url
70 def render(self) -> Any:
71 from lexigram.ui.atoms.icons import get_icon
73 bg_color = get_toast_classes(self.type)
74 icon_name = get_semantic_icon(self.type)
76 inner: list[Any] = [
77 el(
78 "div",
79 get_icon(icon_name, class_name="w-5 h-5"),
80 class_="mr-3 flex-shrink-0",
81 ),
82 el("div", self.message, class_="font-medium flex-1"),
83 ]
85 if self.action_label:
86 action_attrs: dict[str, Any] = {
87 "class_": "ml-4 text-sm font-semibold underline underline-offset-2 hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 rounded",
88 }
89 if self.action_url:
90 action_el = el(
91 "a", self.action_label, href=self.action_url, **action_attrs
92 )
93 else:
94 action_el = el(
95 "button", self.action_label, type="button", **action_attrs
96 )
97 inner.append(action_el)
99 inner.append(
100 el(
101 "button",
102 "✕",
103 type="button",
104 aria_label="Close",
105 **{
106 "class_": "ml-3 flex-shrink-0 opacity-70 hover:opacity-100 focus:outline-none",
107 "@click": "show = false",
108 },
109 ),
110 )
112 return el(
113 "div",
114 *inner,
115 role="status",
116 class_=f"fixed bottom-4 right-4 {bg_color} text-white px-4 py-3 rounded-lg shadow-lg z-50 transition-all duration-300 transform flex items-center max-w-sm",
117 **{
118 "x-data": "{ show: true }",
119 "x-show": "show",
120 "x-init": f"setTimeout(() => show = false, {self.duration})",
121 "x-transition:enter": "transition ease-out duration-300",
122 "x-transition:enter-start": "opacity-0 translate-y-2",
123 "x-transition:enter-end": "opacity-100 translate-y-0",
124 "x-transition:leave": "transition ease-in duration-200",
125 "x-transition:leave-start": "opacity-100 translate-y-0",
126 "x-transition:leave-end": "opacity-0 translate-y-2",
127 },
128 aria_live="polite",
129 )
132class Toast(InlineToast):
133 """Deprecated alias for InlineToast. Will be removed in a future release."""
135 def __init__(self, *args: Any, **kwargs: Any) -> None:
136 import warnings
138 warnings.warn(
139 "`Toast` (the Alpine inline-notification Component) is deprecated; "
140 "use `InlineToast` instead. For the toast payload dataclass, use `ToastData`. "
141 "For server-driven toasts, use `ServerToastChannel`.",
142 DeprecationWarning,
143 stacklevel=2,
144 )
145 super().__init__(*args, **kwargs)