diff --git a/panel/chat/icon.py b/panel/chat/icon.py index cb91895dfb..c9aea8cbda 100644 --- a/panel/chat/icon.py +++ b/panel/chat/icon.py @@ -72,7 +72,12 @@ def _render_icons(self): icon._reaction = option icon.param.watch(self._update_value, "value") self._rendered_icons[option] = icon - self._composite[:] = [self.default_layout(*list(self._rendered_icons.values()))] + self._composite[:] = [ + self.default_layout( + *list(self._rendered_icons.values()), + sizing_mode=self.param.sizing_mode, + stylesheets=self._stylesheets + self.param.stylesheets.rx(), + )] @param.depends("value", watch=True) def _update_icons(self): diff --git a/panel/chat/message.py b/panel/chat/message.py index 67d8cc5c71..d761a166c5 100644 --- a/panel/chat/message.py +++ b/panel/chat/message.py @@ -254,7 +254,7 @@ def __init__(self, object=None, **params): reaction_icons = params.get("reaction_icons", {"favorite": "heart"}) if isinstance(reaction_icons, dict): - params["reaction_icons"] = ChatReactionIcons(options=reaction_icons, default_layout=Row) + params["reaction_icons"] = ChatReactionIcons(options=reaction_icons, default_layout=Row, sizing_mode=None) self._internal = True super().__init__(object=object, **params) self.chat_copy_icon = ChatCopyIcon( @@ -294,18 +294,20 @@ def _build_layout(self): self.param.user, height=20, css_classes=["name"], visible=self.param.show_user, + sizing_mode=None, ) self._activity_dot = HTML( "●", css_classes=["activity-dot"], + margin=(5, 0), + sizing_mode=None, visible=self.param.show_activity_dot, ) meta_row = Row( self._user_html, self._activity_dot, - sizing_mode="stretch_width", css_classes=["meta"], stylesheets=self._stylesheets + self.param.stylesheets.rx(), ) @@ -344,8 +346,8 @@ def _build_layout(self): header_col, self._center_row, footer_col, - self._timestamp_html, self._icons_row, + self._timestamp_html, css_classes=["right"], sizing_mode=None, stylesheets=self._stylesheets + self.param.stylesheets.rx(), @@ -556,6 +558,11 @@ def _render_reaction_icons(self): def _update_reaction_icons(self, _): self._icons_row[-1] = self._render_reaction_icons() + self._icon_divider.visible = ( + len(self.reaction_icons.options) > 0 and + self.show_reaction_icons and + self.chat_copy_icon.visible + ) def _update(self, ref, old_models): """ @@ -598,7 +605,10 @@ def _update_chat_copy_icon(self): if isinstance(object_panel, str) and self.show_copy_icon: self.chat_copy_icon.value = object_panel self.chat_copy_icon.visible = True - self._icon_divider.visible = True + self._icon_divider.visible = ( + len(self.reaction_icons.options) > 0 and + self.show_reaction_icons + ) else: self.chat_copy_icon.value = "" self.chat_copy_icon.visible = False diff --git a/panel/dist/css/chat_copy_icon.css b/panel/dist/css/chat_copy_icon.css index a03a6a1605..6af21dc4ca 100644 --- a/panel/dist/css/chat_copy_icon.css +++ b/panel/dist/css/chat_copy_icon.css @@ -1,3 +1,3 @@ :host { - margin-top: 5px; + margin-top: 8px; } diff --git a/panel/dist/css/chat_message.css b/panel/dist/css/chat_message.css index 4d75872094..06ea28cbf7 100644 --- a/panel/dist/css/chat_message.css +++ b/panel/dist/css/chat_message.css @@ -59,6 +59,7 @@ .name { font-size: 1em; + width: fit-content; } .center { @@ -141,11 +142,9 @@ display: inline-block; animation: fadeOut 2s infinite cubic-bezier(0.68, -0.55, 0.27, 1.55); color: #32cd32; - /* since 1.25em, fix margins to everything is perceived to be more aligned */ + /* since 1.25em, adjust line-height */ font-size: 1.25em; - margin-left: -2.5px; - margin-top: 2px; - margin-bottom: 5px; + line-height: 0.9em; } .divider { diff --git a/panel/dist/css/chat_reaction_icons.css b/panel/dist/css/chat_reaction_icons.css index e69de29bb2..ea0b116a10 100644 --- a/panel/dist/css/chat_reaction_icons.css +++ b/panel/dist/css/chat_reaction_icons.css @@ -0,0 +1,4 @@ +:host { + margin-top: 4px; + width: fit-content; +} diff --git a/panel/dist/css/icon.css b/panel/dist/css/icon.css index 788d12a395..dc47822162 100644 --- a/panel/dist/css/icon.css +++ b/panel/dist/css/icon.css @@ -18,5 +18,4 @@ .bk-IconRow { display: flex; align-items: center; - justify-content: center; } diff --git a/panel/tests/chat/test_message.py b/panel/tests/chat/test_message.py index 8bbb04d2eb..53c74320a2 100644 --- a/panel/tests/chat/test_message.py +++ b/panel/tests/chat/test_message.py @@ -54,7 +54,7 @@ def test_layout(self): assert isinstance(object_pane, Markdown) assert object_pane.object == "ABC" - icons = columns[1][5][2] + icons = columns[1][4][2] assert isinstance(icons, ChatReactionIcons) footer_col = columns[1][3] @@ -65,22 +65,29 @@ def test_layout(self): assert isinstance(footer_col[1], Markdown) assert footer_col[1].object == "Footer 2" - timestamp_pane = columns[1][4][0] + timestamp_pane = columns[1][5][0] assert isinstance(timestamp_pane, HTML) def test_reactions_dynamic(self): - message = ChatMessage(reactions=["favorite"]) + message = ChatMessage("hi", reactions=["favorite"]) assert message.reaction_icons.value == ["favorite"] message.reactions = ["thumbs-up"] assert message.reaction_icons.value == ["thumbs-up"] def test_reaction_icons_dynamic(self): - message = ChatMessage(reaction_icons={"favorite": "heart"}) + message = ChatMessage("hi", reaction_icons={"favorite": "heart"}) assert message.reaction_icons.options == {"favorite": "heart"} message.reaction_icons = ChatReactionIcons(options={"like": "thumb-up"}) assert message._icons_row[-1] == message.reaction_icons + assert message._icon_divider.visible + + message.reaction_icons = ChatReactionIcons(options={}) + assert not message._icon_divider.visible + + message = ChatMessage("hi", reaction_icons={}) + assert not message._icon_divider.visible def test_reactions_link(self): # on init @@ -171,39 +178,39 @@ def test_update_object(self): def test_update_timestamp(self): message = ChatMessage() columns = message._composite.objects - timestamp_pane = columns[1][4][0] + timestamp_pane = columns[1][5][0] assert isinstance(timestamp_pane, HTML) dt_str = datetime.datetime.now().strftime("%H:%M") assert timestamp_pane.object == dt_str message = ChatMessage(timestamp_tz="UTC") columns = message._composite.objects - timestamp_pane = columns[1][4][0] + timestamp_pane = columns[1][5][0] assert isinstance(timestamp_pane, HTML) dt_str = datetime.datetime.now(datetime.timezone.utc).strftime("%H:%M") assert timestamp_pane.object == dt_str message = ChatMessage(timestamp_tz="US/Pacific") columns = message._composite.objects - timestamp_pane = columns[1][4][0] + timestamp_pane = columns[1][5][0] assert isinstance(timestamp_pane, HTML) dt_str = datetime.datetime.now(tz=ZoneInfo("US/Pacific")).strftime("%H:%M") assert timestamp_pane.object == dt_str special_dt = datetime.datetime(2023, 6, 24, 15) message.timestamp = special_dt - timestamp_pane = columns[1][4][0] + timestamp_pane = columns[1][5][0] dt_str = special_dt.strftime("%H:%M") assert timestamp_pane.object == dt_str mm_dd_yyyy = "%b %d, %Y" message.timestamp_format = mm_dd_yyyy - timestamp_pane = columns[1][4][0] + timestamp_pane = columns[1][5][0] dt_str = special_dt.strftime(mm_dd_yyyy) assert timestamp_pane.object == dt_str message.show_timestamp = False - timestamp_pane = columns[1][4][0] + timestamp_pane = columns[1][5][0] assert not timestamp_pane.visible def test_does_not_turn_widget_into_str(self): @@ -322,17 +329,20 @@ def test_chat_copy_icon_text_widget(self, widget): message = ChatMessage(object=widget(value="testing")) assert message.chat_copy_icon.visible assert message.chat_copy_icon.value == "testing" + assert message._icon_divider.visible def test_chat_copy_icon_disabled(self): message = ChatMessage(object="testing", show_copy_icon=False) assert not message.chat_copy_icon.visible assert not message.chat_copy_icon.value + assert not message._icon_divider.visible @pytest.mark.parametrize("component", [Column, FileInput]) def test_chat_copy_icon_not_string(self, component): message = ChatMessage(object=component()) assert not message.chat_copy_icon.visible assert not message.chat_copy_icon.value + assert not message._icon_divider.visible def test_serialize_text_prefix_with_viewable_type(self): message = ChatMessage(Markdown("string"))