Skip to content

Commit

Permalink
Add icon to st.button (streamlit#9301)
Browse files Browse the repository at this point in the history
Adding the `icon` param to `st.button` to allow for emojis/material
icons to be added to button label
  • Loading branch information
mayagbarnes authored Aug 24, 2024
1 parent 06b791a commit 45622f2
Show file tree
Hide file tree
Showing 20 changed files with 76 additions and 1 deletion.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions e2e_playwright/st_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ def on_click(x, y):
":material/search: _button 7_ (**styled** :green[label]) :material/arrow_forward:"
)

st.button("Like Button", icon=":material/thumb_up:")
st.button("Star Button", icon="⭐")

cols = st.columns(3)

# Order of conn_types matters to preserve the order in st_button.spec.js and the snapshot
Expand Down
4 changes: 3 additions & 1 deletion e2e_playwright/st_button_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_button_widget_rendering(
):
"""Test that the button widgets are correctly rendered via screenshot matching."""
button_elements = themed_app.get_by_test_id("stButton")
expect(button_elements).to_have_count(15)
expect(button_elements).to_have_count(17)

assert_snapshot(button_elements.nth(0), name="st_button-default")
assert_snapshot(button_elements.nth(1), name="st_button-disabled")
Expand All @@ -36,6 +36,8 @@ def test_button_widget_rendering(
assert_snapshot(button_elements.nth(4), name="st_button-use_container_width")
assert_snapshot(button_elements.nth(5), name="st_button-use_container_width_help")
assert_snapshot(button_elements.nth(6), name="st_button-styled_label")
assert_snapshot(button_elements.nth(7), name="st_button-material_icon")
assert_snapshot(button_elements.nth(8), name="st_button-emoji_icon")

# The rest is tested in one screenshot in the following test

Expand Down
14 changes: 14 additions & 0 deletions frontend/lib/src/components/widgets/Button/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,18 @@ describe("Button widget", () => {
const buttonWidget = screen.getByRole("button")
expect(buttonWidget).toHaveStyle(`width: ${250}px`)
})

it("renders an emoji icon if provided", () => {
render(<Button {...getProps({ icon: "😀" })} />)

const icon = screen.getByTestId("stIconEmoji")
expect(icon).toHaveTextContent("😀")
})

it("renders a material icon if provided", () => {
render(<Button {...getProps({ icon: ":material/thumb_up:" })} />)

const icon = screen.getByTestId("stIconMaterial")
expect(icon).toHaveTextContent("thumb_up")
})
})
16 changes: 16 additions & 0 deletions frontend/lib/src/components/widgets/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@

import React, { ReactElement } from "react"

import { useTheme } from "@emotion/react"

import { Button as ButtonProto } from "@streamlit/lib/src/proto"
import BaseButton, {
BaseButtonKind,
BaseButtonSize,
BaseButtonTooltip,
} from "@streamlit/lib/src/components/shared/BaseButton"
import { DynamicIcon } from "@streamlit/lib/src/components/shared/Icon"
import { WidgetStateManager } from "@streamlit/lib/src/WidgetStateManager"
import StreamlitMarkdown from "@streamlit/lib/src/components/shared/StreamlitMarkdown"
import { EmotionTheme } from "@streamlit/lib/src/theme"

export interface Props {
disabled: boolean
Expand All @@ -34,6 +38,7 @@ export interface Props {
}

function Button(props: Props): ReactElement {
const { colors }: EmotionTheme = useTheme()
const { disabled, element, widgetMgr, width, fragmentId } = props
const style = { width }

Expand All @@ -46,6 +51,9 @@ function Button(props: Props): ReactElement {
// we need to pass the container width down to the button
const fluidWidth = element.help ? width : true

// Material icons need to be larger to render similar size of emojis, emojis need addtl margin
const isMaterialIcon = element.icon.startsWith(":material")

return (
<div className="stButton" data-testid="stButton" style={style}>
<BaseButtonTooltip help={element.help}>
Expand All @@ -58,6 +66,14 @@ function Button(props: Props): ReactElement {
widgetMgr.setTriggerValue(element, { fromUi: true }, fragmentId)
}
>
{element.icon && (
<DynamicIcon
size={isMaterialIcon ? "lg" : "base"}
margin={isMaterialIcon ? "0 sm 0 0" : "0 md 0 0"}
color={colors.bodyText}
iconValue={element.icon}
/>
)}
<StreamlitMarkdown
source={element.label}
allowHTML={false}
Expand Down
1 change: 1 addition & 0 deletions frontend/lib/src/theme/primitives/iconSizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const iconSizes = {
xs: "0.5rem",
sm: "0.75rem",
md: "0.9rem",
base: "1rem",
lg: "1.25rem",
xl: "1.5rem",
twoXL: "1.8rem",
Expand Down
24 changes: 24 additions & 0 deletions lib/streamlit/elements/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def button(
kwargs: WidgetKwargs | None = None,
*, # keyword-only arguments:
type: Literal["primary", "secondary"] = "secondary",
icon: str | None = None,
disabled: bool = False,
use_container_width: bool = False,
) -> bool:
Expand Down Expand Up @@ -134,6 +135,23 @@ def button(
button with additional emphasis or "secondary" for a normal button. Defaults
to "secondary".
icon : str or None
An optional emoji or icon to display next to the button label. If ``icon``
is ``None`` (default), no icon is displayed. If ``icon`` is a
string, the following options are valid:
* A single-character emoji. For example, you can set ``icon="🚨"``
or ``icon="🔥"``. Emoji short codes are not supported.
* An icon from the Material Symbols library (rounded style) in the
format ``":material/icon_name:"`` where "icon_name" is the name
of the icon in snake case.
For example, ``icon=":material/thumb_up:"`` will display the
Thumb Up icon. Find additional icons in the `Material Symbols \
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
font library.
disabled : bool
An optional boolean, which disables the button if set to True. The
default is False.
Expand Down Expand Up @@ -188,6 +206,7 @@ def button(
kwargs=kwargs,
disabled=disabled,
type=type,
icon=icon,
use_container_width=use_container_width,
ctx=ctx,
)
Expand Down Expand Up @@ -761,6 +780,7 @@ def _button(
kwargs: WidgetKwargs | None = None,
*, # keyword-only arguments:
type: Literal["primary", "secondary"] = "secondary",
icon: str | None = None,
disabled: bool = False,
use_container_width: bool = False,
ctx: ScriptRunContext | None = None,
Expand All @@ -780,6 +800,7 @@ def _button(
"button",
user_key=key,
label=label,
icon=icon,
key=key,
help=help,
is_form_submitter=is_form_submitter,
Expand Down Expand Up @@ -816,6 +837,9 @@ def _button(
if help is not None:
button_proto.help = dedent(help)

if icon is not None:
button_proto.icon = validate_icon_or_emoji(icon)

serde = ButtonSerde()

button_state = register_widget(
Expand Down
14 changes: 14 additions & 0 deletions lib/tests/streamlit/elements/button_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ def test_type(self):
c = self.get_delta_from_queue().new_element.button
self.assertEqual(c.type, "primary")

def test_emoji_icon(self):
"""Test that it can be called with emoji icon."""
st.button("the label", icon="⚡")

c = self.get_delta_from_queue().new_element.button
self.assertEqual(c.icon, "⚡")

def test_material_icon(self):
"""Test that it can be called with material icon."""
st.button("the label", icon=":material/thumb_up:")

c = self.get_delta_from_queue().new_element.button
self.assertEqual(c.icon, ":material/thumb_up:")

def test_just_disabled(self):
"""Test that it can be called with disabled param."""
st.button("the label", disabled=True)
Expand Down
1 change: 1 addition & 0 deletions proto/streamlit/proto/Button.proto
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ message Button {
string type = 7;
bool disabled = 8;
bool use_container_width = 9;
string icon = 10;
}

0 comments on commit 45622f2

Please sign in to comment.