11"""
2- Author: Sheng Pang
3- Modifier: Min-Hsueh Chiu
4-
5- Message Snake - A reusable Dash notification snackbar component.
2+ Authors: Sheng Pang & Min-Hsueh Chiu
63
4+ messageAIO - A reusable Dash notification snackbar component.
75Provides fixed-position toast notifications callable from any page.
8- Supports fade-in/fade-out animations, auto-dismiss, and manual close.
6+ Supports fade-in/fade-out animations (not implemented), auto-dismiss (not implemented), and manual close.
7+
8+
9+ V1: The instantiated messageAIO is limited to one single `msg_type`.
10+
11+ So it becomes difficult to use when an app has multiple msg_types.
12+ Developers would need to instantiate multiple MessageAIO components and
13+ define multiple Outputs in the callback.
14+
15+ V2 improvement:
16+ - use a single instantiation and only one Output per callback.
17+ - If `ids.data` is change, then it should be visible, this logic should be handled here.
18+
19+
920
1021Usage:
1122 from crystal_toolkit.components.error_msg import MessageAIO
1223
1324 # 1. Include in layout
14- MessageAIO(
15- "Invalid composition input!",
16- aio_id=self.id("invalid-comp-alarm"),
17- msg_type="error",
18- ),
25+ MessageAIO(aio_id=self.id(<COMPONENT_ID>)),
1926
2027 # 2. Add to callback:
21- Output(MessageAIO.ids.visible (self.id("invalid-comp-alarm" )), "data"),
22- # Return True to display the message, and False to hide it.
28+ Output(MessageAIO.ids.data (self.id(<COMPONENT_ID> )), "data"),
29+ Return {"message": <DISPLAY MSG>, "msg_type": <MSG_TYPE>}
2330
2431 Note: Do not need to register callbacks as using All-in-one pattern
2532"""
2633
2734from __future__ import annotations
2835
29- from dash import MATCH , Input , Output , callback , ctx , dcc , html
36+ from dash import MATCH , Input , Output , State , callback , dcc , html , no_update
3037
3138from crystal_toolkit .core .mpcomponent import MPComponent
3239
@@ -106,11 +113,6 @@ class ids:
106113 "subcomponents" : "close_button" ,
107114 "aio_id" : aio_id ,
108115 }
109- message = lambda aio_id : {
110- "component" : "MessageAIO" ,
111- "subcomponents" : "message" ,
112- "aio_id" : aio_id ,
113- }
114116 div = lambda aio_id : {
115117 "component" : "MessageAIO" ,
116118 "subcomponents" : "div" ,
@@ -121,9 +123,14 @@ class ids:
121123 "subcomponents" : "timer" ,
122124 "aio_id" : aio_id ,
123125 }
124- visible = lambda aio_id : {
126+ data = lambda aio_id : {
127+ "component" : "MessageAIO" ,
128+ "subcomponents" : "data" ,
129+ "aio_id" : aio_id ,
130+ }
131+ message = lambda aio_id : {
125132 "component" : "MessageAIO" ,
126- "subcomponents" : "visible " ,
133+ "subcomponents" : "message " ,
127134 "aio_id" : aio_id ,
128135 }
129136
@@ -133,9 +140,7 @@ class ids:
133140
134141 def __init__ (
135142 self ,
136- message ,
137143 aio_id ,
138- msg_type ,
139144 position = "bottom-right" ,
140145 style = None ,
141146 show_icon = False ,
@@ -151,30 +156,23 @@ def __init__(
151156 to enable auto-dismiss and close functionality.
152157
153158 Args:
154- message (str): The notification message to display.
155159 id (str): Unique HTML id for the notification container.
156- msg_type (str): Notification type - 'info', 'warning', 'error', 'success'.
157- position (str): Fixed position on screen. One of:
160+ position (str, optional): Fixed position on screen. One of:
158161 'top', 'bottom', 'center', 'top-right', 'top-left',
159162 'bottom-right', 'bottom-left'.
160163 style (dict, optional): Additional CSS style overrides.
161- show_icon (bool): Whether to show a type-specific icon.
162- min_width (str): Minimum width of the notification.
163- max_width (str): Maximum width of the notification.
164- z_index (int): CSS z-index for layering.
165- auto_dismiss_ms (int): Auto-dismiss delay in milliseconds. Defaults to 50000 (50s).
164+ show_icon (bool, optional ): Whether to show a type-specific icon.
165+ min_width (str, optional ): Minimum width of the notification.
166+ max_width (str, optional ): Maximum width of the notification.
167+ z_index (int, optional ): CSS z-index for layering.
168+ auto_dismiss_ms (int, optional ): Auto-dismiss delay in milliseconds. Defaults to 50000 (50s).
166169
167170 """
168171
169172 self .snake_id = aio_id
170173 self .show_icon = show_icon
171- self .msg_type = msg_type
172- self .message = message
173174 self .auto_dismiss_ms = auto_dismiss_ms
174175
175- # Resolve type colors
176- type_style = _TYPE_COLORS .get (msg_type , _TYPE_COLORS ["info" ])
177-
178176 # Resolve position
179177 pos_style = _POSITION_STYLES .get (position , _POSITION_STYLES ["bottom-right" ])
180178
@@ -197,7 +195,6 @@ def __init__(
197195 # Visible on mount; fade-out handled by callback via transition
198196 "opacity" : "1" ,
199197 "transition" : "opacity 0.4s ease" ,
200- ** type_style ,
201198 ** pos_style ,
202199 }
203200
@@ -210,7 +207,10 @@ def __init__(
210207 sub_layouts = self ._sub_layouts
211208 super ().__init__ (
212209 [ # Equivalent to `html.Div([...])`
213- dcc .Store (id = self .ids .visible (self .snake_id ), data = False ),
210+ dcc .Store (
211+ id = self .ids .data (self .snake_id ),
212+ data = {"message" : None , "msg_type" : "info" },
213+ ),
214214 sub_layouts ["notification_div" ],
215215 sub_layouts ["interval" ],
216216 ],
@@ -235,9 +235,7 @@ def _sub_layouts(self):
235235
236236 # Message text
237237 children .append (
238- html .Span (
239- self .message , id = self .ids .message (self .snake_id ), style = {"flex" : "1" }
240- )
238+ html .Span (id = self .ids .message (self .snake_id ), style = {"flex" : "1" })
241239 )
242240
243241 # Close button
@@ -293,21 +291,35 @@ def layout(self) -> html.Div:
293291 """
294292
295293 @callback (
296- Output (ids .wrapper (MATCH ), "style" ),
297- Input (ids .visible (MATCH ), "data" ),
294+ Output (ids .wrapper (MATCH ), "style" , allow_duplicate = True ),
298295 Input (ids .close_button (MATCH ), "n_clicks" ),
299296 prevent_initial_call = True ,
300297 )
301- def sync_message (command_visible , close_clicks ):
302- triggered = ctx . triggered_id
298+ def sync_message (close_clicks ):
299+ return { "display" : "none" }
303300
304- if (
305- isinstance (triggered , dict )
306- and triggered .get ("subcomponents" ) == "close_button"
307- ):
308- return {"display" : "none" }
301+ @callback (
302+ Output (ids .message (MATCH ), "children" ),
303+ Output (ids .wrapper (MATCH ), "style" , allow_duplicate = True ),
304+ Output (ids .div (MATCH ), "style" ),
305+ Input (ids .data (MATCH ), "data" ),
306+ Input (ids .wrapper (MATCH ), "style" ),
307+ State (ids .div (MATCH ), "style" ),
308+ prevent_initial_call = True ,
309+ )
310+ def update_messages (input_data , cur_wrapper_style , cur_style ):
311+ print (input_data )
312+ if not input_data :
313+ return no_update , {"display" : "none" }, cur_style
314+
315+ message = input_data .get ("message" , None )
316+ msg_type = input_data .get ("msg_type" , "info" )
317+
318+ # Resolve type colors
319+ type_style = _TYPE_COLORS .get (msg_type , _TYPE_COLORS ["info" ])
320+ cur_style .update (type_style )
309321
310- return {"display" : "block" } if command_visible else { "display" : "none" }
322+ return message , {"display" : "block" }, cur_style
311323
312324 """
313325 @callback(
0 commit comments