Skip to content

Commit

Permalink
Stylize chat components (#6346)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahuang11 authored Feb 15, 2024
1 parent 2b56774 commit 47e7bfa
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 24 deletions.
33 changes: 33 additions & 0 deletions examples/reference/chat/ChatFeed.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,39 @@
"chat_feed"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It's possible to customize the appearance of the chat feed by setting the `message_params` parameter.\n",
"\n",
"Please visit [`ChatMessage`](ChatMessage.ipynb) for a full list of customizable, target CSS classes (e.g. `.avatar`, `.name`, etc)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"chat_feed = pn.chat.ChatFeed(\n",
" show_activity_dot=True,\n",
" message_params={\n",
" \"stylesheets\": [\n",
" \"\"\"\n",
" .message {\n",
" background-color: tan;\n",
" font-family: \"Courier New\";\n",
" font-size: 24px;\n",
" }\n",
" \"\"\"\n",
" ]\n",
" },\n",
")\n",
"chat_feed.send(\"I am so stylish!\")\n",
"chat_feed"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
66 changes: 65 additions & 1 deletion examples/reference/chat/ChatMessage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"You can set the usual styling and layout parameters like `sizing_mode`, `height`, `width`, `max_height`, `max_width`, `styles` and `stylesheet`."
"You can set the usual styling and layout parameters like `sizing_mode`, `height`, `width`, `max_height`, `max_width`, and `styles`."
]
},
{
Expand All @@ -342,6 +342,70 @@
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also use stylesheets to target parts of the `ChatMessage`\n",
"\n",
"Note, it's best practice to use a path to a `.css` stylesheet, but for demo purposes, we will be using a multi-line string here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"path_to_stylesheet = \"\"\"\n",
" .message {\n",
" background-color: tan;\n",
" font-size: 24px;\n",
" font-family: \"Courier New\";\n",
" }\n",
" .avatar {\n",
" background-color: red;\n",
" border-radius: 5%;\n",
" }\n",
" .header {\n",
" background-color: green;\n",
" }\n",
" .footer {\n",
" background-color: blue;\n",
" }\n",
" .name {\n",
" background-color: orange;\n",
" }\n",
" .timestamp {\n",
" background-color: purple;\n",
" }\n",
" .activity-dot {\n",
" background-color: black;\n",
" }\n",
" .reaction-icons {\n",
" background-color: white;\n",
" }\n",
" .copy-icon {\n",
" background-color: gold;\n",
" }\n",
" .right {\n",
" background-color: grey;\n",
" }\n",
" .center {\n",
" background-color: pink;\n",
" }\n",
" .left {\n",
" background-color: yellow;\n",
" }\n",
"\"\"\"\n",
"\n",
"ChatMessage(\n",
" \"Style me up!\",\n",
" show_activity_dot=True,\n",
" stylesheets=[path_to_stylesheet],\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
2 changes: 2 additions & 0 deletions panel/chat/icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class ChatCopyIcon(ReactiveHTML):

value = param.String(default=None, doc="The text to copy to the clipboard.")

css_classes = param.List(default=["copy-icon"], doc="The CSS classes of the widget.")

_template = """
<div
type="button"
Expand Down
47 changes: 31 additions & 16 deletions panel/chat/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,6 @@ class ChatMessage(PaneBase):

def __init__(self, object=None, **params):
self._exit_stack = ExitStack()
self.chat_copy_icon = ChatCopyIcon(
visible=False, width=15, height=15, css_classes=["copy-icon"]
)
if params.get("timestamp") is None:
tz = params.get("timestamp_tz")
if tz is not None:
Expand All @@ -239,6 +236,11 @@ def __init__(self, object=None, **params):
)
self._internal = True
super().__init__(object=object, **params)
self.chat_copy_icon = ChatCopyIcon(
visible=False, width=15, height=15, css_classes=["copy-icon"],
stylesheets=self._stylesheets + self.param.stylesheets.rx(),
)
self.reaction_icons.stylesheets = self._stylesheets + self.param.stylesheets.rx()
self.reaction_icons.link(self, value="reactions", bidirectional=True)
self.reaction_icons.visible = self.param.show_reaction_icons
if not self.avatar:
Expand All @@ -247,14 +249,17 @@ def __init__(self, object=None, **params):

def _build_layout(self):
self._activity_dot = HTML(
"●", css_classes=["activity-dot"], visible=self.param.show_activity_dot
"●",
css_classes=["activity-dot"],
visible=self.param.show_activity_dot,
stylesheets=self._stylesheets + self.param.stylesheets.rx(),
)
self._left_col = left_col = Column(
self._render_avatar(),
max_width=60,
height=100,
css_classes=["left"],
stylesheets=self._stylesheets,
stylesheets=self._stylesheets + self.param.stylesheets.rx(),
visible=self.param.show_avatar,
sizing_mode=None,
)
Expand All @@ -266,7 +271,7 @@ def _build_layout(self):
self._object_panel,
self.reaction_icons,
css_classes=["center"],
stylesheets=self._stylesheets,
stylesheets=self._stylesheets + self.param.stylesheets.rx(),
sizing_mode=None
)
self.param.watch(self._update_object_pane, "object")
Expand All @@ -275,24 +280,34 @@ def _build_layout(self):
self.param.user, height=20, css_classes=["name"],
visible=self.param.show_user, stylesheets=self._stylesheets,
)
header_row = Row(
self._user_html,
self.chat_copy_icon,
self._activity_dot,
stylesheets=self._stylesheets + self.param.stylesheets.rx(),
sizing_mode="stretch_width",
css_classes=["header"]
)

self._timestamp_html = HTML(
self.param.timestamp.rx().strftime(self.param.timestamp_format),
css_classes=["timestamp"],
visible=self.param.show_timestamp
)

footer_row = Row(
self._timestamp_html,
stylesheets=self._stylesheets + self.param.stylesheets.rx(),
sizing_mode="stretch_width",
css_classes=["footer"]
)

self._right_col = right_col = Column(
Row(
self._user_html,
self.chat_copy_icon,
self._activity_dot,
stylesheets=self._stylesheets,
sizing_mode="stretch_width",
css_classes=["header"]
),
header_row,
self._center_row,
self._timestamp_html,
footer_row,
css_classes=["right"],
stylesheets=self._stylesheets,
stylesheets=self._stylesheets + self.param.stylesheets.rx(),
sizing_mode=None
)
viewable_params = {
Expand Down
17 changes: 10 additions & 7 deletions panel/tests/chat/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ def test_layout(self):
icons = center_row[1]
assert isinstance(icons, ChatReactionIcons)

timestamp_pane = columns[1][2]
footer_row = columns[1][2]
assert isinstance(footer_row, Row)

timestamp_pane = footer_row[0]
assert isinstance(timestamp_pane, HTML)

def test_reactions_link(self):
Expand Down Expand Up @@ -143,39 +146,39 @@ def test_update_object(self):
def test_update_timestamp(self):
message = ChatMessage()
columns = message._composite.objects
timestamp_pane = columns[1][2]
timestamp_pane = columns[1][2][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][2]
timestamp_pane = columns[1][2][0]
assert isinstance(timestamp_pane, HTML)
dt_str = datetime.datetime.utcnow().strftime("%H:%M")
assert timestamp_pane.object == dt_str

message = ChatMessage(timestamp_tz="US/Pacific")
columns = message._composite.objects
timestamp_pane = columns[1][2]
timestamp_pane = columns[1][2][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][2]
timestamp_pane = columns[1][2][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][2]
timestamp_pane = columns[1][2][0]
dt_str = special_dt.strftime(mm_dd_yyyy)
assert timestamp_pane.object == dt_str

message.show_timestamp = False
timestamp_pane = columns[1][2]
timestamp_pane = columns[1][2][0]
assert not timestamp_pane.visible

def test_does_not_turn_widget_into_str(self):
Expand Down

0 comments on commit 47e7bfa

Please sign in to comment.