Skip to content

fix: Introduce a more ergonomic API for express.ui.insert_accordion_panel() #2042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 4, 2025
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* `playwright.controller.InputActionButton` gains a `expect_icon()` method. As a result, the already existing `expect_label()` no longer includes the icon. (#2020)

### Changes

* `express.ui.insert_accordion_panel()`'s function signature has changed to be more ergonomic. Now you can pass the `panel_title` and `panel_contents` directly instead of `ui.hold()`ing the `ui.accordion_panel()` context manager. (#2042)

### Improvements

* Improved the styling and readability of markdown tables rendered by `ui.Chat()` and `ui.MarkdownStream()`. (#1973)
Expand Down
23 changes: 12 additions & 11 deletions shiny/api-examples/insert_accordion_panel/app-express.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import random

from shiny import reactive, ui
from shiny.express import input


def make_panel(letter):
return ui.accordion_panel(
f"Section {letter}", f"Some narrative for section {letter}"
)

from shiny import reactive
from shiny.express import input, ui

ui.input_action_button("add_panel", "Add random panel", class_="mt-3 mb-3")
ui.accordion(*[make_panel(letter) for letter in "ABCDE"], id="acc", multiple=True)

with ui.accordion(id="acc", multiple=True):
for letter in "ABCDE":
with ui.accordion_panel(f"Section {letter}"):
f"Some narrative for section {letter}"


@reactive.effect
@reactive.event(input.add_panel)
def _():
ui.insert_accordion_panel("acc", make_panel(str(random.randint(0, 10000))))
ui.insert_accordion_panel(
"acc",
f"Section {random.randint(0, 10000)}",
f"Some narrative for section {random.randint(0, 10000)}",
)
117 changes: 52 additions & 65 deletions shiny/express/ui/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from __future__ import annotations


from htmltools import (
TagList,
HTML,
Tag,
TagChild,
TagAttrs,
TagAttrValue,
tags,
HTML,
head_content,
TagChild,
TagList,
a,
br,
code,
Expand All @@ -21,116 +18,113 @@
h4,
h5,
h6,
head_content,
hr,
img,
p,
pre,
span,
strong,
)

from ...ui import (
fill,
)

from ...ui import (
busy_indicators,
tags,
)

from ...ui import (
AccordionPanel,
AnimationOptions,
CardItem,
Progress,
ShowcaseLayout,
Sidebar,
SliderStepArg,
SliderValueArg,
Theme,
ValueBoxTheme,
bind_task_button,
brush_opts,
busy_indicators,
click_opts,
dblclick_opts,
fill,
help_text,
hover_opts,
include_css,
include_js,
input_bookmark_button,
input_action_button,
input_action_link,
input_bookmark_button,
input_checkbox,
input_checkbox_group,
input_switch,
input_radio_buttons,
input_dark_mode,
input_date,
input_date_range,
input_file,
input_numeric,
input_password,
input_radio_buttons,
input_select,
input_selectize,
input_slider,
bind_task_button,
input_switch,
input_task_button,
input_text,
input_text_area,
insert_ui,
js_eval,
markdown,
modal,
modal_button,
modal_remove,
modal_show,
nav_spacer,
navbar_options,
notification_remove,
notification_show,
panel_title,
insert_accordion_panel,
remove_accordion_panel,
remove_nav_panel,
remove_ui,
update_accordion,
update_accordion_panel,
update_sidebar,
update_action_button,
update_action_link,
update_checkbox,
update_switch,
update_checkbox_group,
update_radio_buttons,
update_dark_mode,
update_date,
update_date_range,
update_nav_panel,
update_navs,
update_numeric,
update_popover,
update_radio_buttons,
update_select,
update_selectize,
update_sidebar,
update_slider,
update_switch,
update_task_button,
update_text,
update_text_area,
update_navs,
update_tooltip,
update_popover,
insert_ui,
remove_ui,
markdown,
modal_button,
modal,
modal_show,
modal_remove,
notification_show,
notification_remove,
nav_spacer,
navbar_options,
remove_nav_panel,
update_nav_panel,
Progress,
Theme,
value_box_theme,
js_eval,
)

from ...ui._chat import ChatExpress as Chat
from ...ui._markdown_stream import (
ExpressMarkdownStream as MarkdownStream,
)
from ._cm_components import (
sidebar,
layout_sidebar,
layout_column_wrap,
layout_columns,
accordion,
accordion_panel,
card,
card_body,
card_header,
card_footer,
accordion,
accordion_panel,
nav_panel,
card_header,
layout_column_wrap,
layout_columns,
layout_sidebar,
nav_control,
nav_menu,
nav_panel,
navset_bar,
navset_card_pill,
navset_card_tab,
Expand All @@ -140,32 +134,25 @@
navset_pill_list,
navset_tab,
navset_underline,
value_box,
panel_well,
panel_absolute,
panel_conditional,
panel_fixed,
panel_absolute,
tooltip,
panel_well,
popover,
sidebar,
tooltip,
value_box,
)

from ...ui._chat import ChatExpress as Chat

from ...ui._markdown_stream import (
ExpressMarkdownStream as MarkdownStream,
)

from ._page import (
page_opts,
)

from ._hold import (
hold,
)

from ._insert import (
insert_accordion_panel,
insert_nav_panel,
)
from ._page import (
page_opts,
)

__all__ = (
# Imports from htmltools
Expand Down
74 changes: 72 additions & 2 deletions shiny/express/ui/_insert.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,82 @@
@ui.hold() pass the UI as a value without displaying it.
"""

from typing import Literal, Optional
from typing import Literal, Optional, Union

from htmltools import TagChild
from htmltools import TagAttrs, TagChild

from ..._docstring import add_example
from ...session import Session
from ...types import MISSING, MISSING_TYPE


@add_example()
def insert_accordion_panel(
id: str,
panel_title: str,
*panel_contents: Union[TagChild, TagAttrs],
panel_value: Union[str, MISSING_TYPE, None] = MISSING,
panel_icon: TagChild = None,
target: Optional[str] = None,
position: Literal["after", "before"] = "after",
session: Optional[Session] = None,
) -> None:
"""
Insert an accordion panel into an existing accordion.

Parameters
----------
id
A string that matches an existing :func:`~shiny.ui.express.accordion`'s `id`.
panel_title
The title to appear in the panel header.
panel_contents
UI elements for the panel's body. Can also be a dict of tag attributes for the
body's HTML container.
panel_value
A character string that uniquely identifies this panel. If `MISSING`, the
`title` will be used.
panel_icon
A :class:`~htmltools.TagChild` which is positioned just before the `title`.
target
The `value` of an existing panel to insert next to.
position
Should `panel` be added before or after the target? When `target=None`,
`"after"` will append after the last panel and `"before"` will prepend before
the first panel.
session
A Shiny session object (the default should almost always be used).

References
----------
[Bootstrap Accordion](https://getbootstrap.com/docs/5.3/components/accordion/)

See Also
--------
* :func:`~shiny.ui.express.accordion`
* :func:`~shiny.ui.express.accordion_panel`
* :func:`~shiny.ui.express.update_accordion`
"""

from ...ui import AccordionPanel, accordion_panel, insert_accordion_panel

if isinstance(panel_title, AccordionPanel):
# If the user passed an AccordionPanel, we can just use it as is.
# This isn't recommended, but we support it for backwards compatibility
# with the old API.
panel = panel_title
else:
panel = accordion_panel(
panel_title, *panel_contents, value=panel_value, icon=panel_icon
)

insert_accordion_panel(
id=id,
panel=panel,
target=target,
position=position,
session=session,
)


@add_example()
Expand Down
10 changes: 5 additions & 5 deletions shiny/ui/_accordion.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,17 +315,17 @@ def accordion_panel(
Parameters
----------
title
A title to appear in the :func:`~shiny.ui.accordion_panel`'s header.
A title to appear in the panel's header.
*args
Contents to the accordion panel body. Or tag attributes that are supplied to the
returned :class:`~htmltools.Tag` object.
UI elements for the panel's body. Can also be a dict of tag attributes for the
body's HTML container.
value
A character string that uniquely identifies this panel. If `MISSING`, the
`title` will be used.
icon
A :class:`~htmltools.Tag` which is positioned just before the `title`.
A :class:`~htmltools.TagChild` which is positioned just before the `title`.
**kwargs
Tag attributes to the `accordion-body` div Tag.
Tag attributes for the body's HTML container.

Returns
-------
Expand Down
Loading
Loading