Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_quartodoc-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ quartodoc:
- ui.page_bootstrap
- ui.page_auto
- ui.page_output
- ui.theme
- title: UI Layouts
desc: Control the layout of multiple UI components.
contents:
Expand Down
1 change: 1 addition & 0 deletions docs/_quartodoc-express.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ quartodoc:
desc: Tools for creating, arranging, and styling UI components.
contents:
- express.ui.page_opts
- express.ui.theme
- express.ui.sidebar
- express.ui.layout_columns
- express.ui.layout_column_wrap
Expand Down
30 changes: 30 additions & 0 deletions shiny/api-examples/theme/app-core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pathlib import Path

from shiny import App, render, ui

app_ui = ui.page_sidebar(
ui.sidebar(
ui.input_numeric("n", "N", min=0, max=100, value=20),
title="Parameters",
),
ui.h2("Output"),
ui.output_text_verbatim("txt"),
ui.markdown(
"""
**AI-generated filler text.** In the world of exotic fruits, the durian stands out with its spiky exterior and strong odor. Despite its divisive smell, many people are drawn to its rich, creamy texture and unique flavor profile. This tropical fruit is often referred to as the "king of fruits" in various Southeast Asian countries.

Durians are known for their large size and thorn-covered husk, which requires careful handling. The flesh inside can vary in color from pale yellow to deep orange, with a custard-like consistency that melts in your mouth. Some describe its taste as a mix of sweet, savory, and creamy, while others find it overpowering and pungent.
"""
),
title="Theme Example",
theme=ui.theme(Path(__file__).parent / "theme.css", replace="none"),
)


def server(input, output, session):
@render.text
def txt():
return f"n*2 is {input.n() * 2}"


app = App(app_ui, server)
27 changes: 27 additions & 0 deletions shiny/api-examples/theme/app-express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pathlib import Path

from shiny.express import input, render, ui

ui.page_opts(
title="Theme Example",
theme=ui.theme(Path(__file__).parent / "theme.css", replace="none"),
)

with ui.sidebar(title="Parameters"):
ui.input_numeric("n", "N", min=0, max=100, value=20)

ui.h2("Output")


@render.code
def txt():
return f"n*2 is {input.n() * 2}"


ui.markdown(
"""
**AI-generated filler text.** In the world of exotic fruits, the durian stands out with its spiky exterior and strong odor. Despite its divisive smell, many people are drawn to its rich, creamy texture and unique flavor profile. This tropical fruit is often referred to as the "king of fruits" in various Southeast Asian countries.

Durians are known for their large size and thorn-covered husk, which requires careful handling. The flesh inside can vary in color from pale yellow to deep orange, with a custard-like consistency that melts in your mouth. Some describe its taste as a mix of sweet, savory, and creamy, while others find it overpowering and pungent.
"""
)
39 changes: 39 additions & 0 deletions shiny/api-examples/theme/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@font-face {
font-family: InterVariable;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("https://rsms.me/inter/font-files/InterVariable.woff2?v=4.0")
format("woff2");
}
@font-face {
font-family: InterVariable;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url("https://rsms.me/inter/font-files/InterVariable-Italic.woff2?v=4.0")
format("woff2");
}

:root {
font-size: 20px;
--bs-body-font-family: "InterVariable";
--bs-heading-color: var(--bs-pink);

--pink-rgb: 191, 0, 127;

--bslib-sidebar-bg: rgba(var(--pink-rgb), 0.05);
--bslib-sidebar-fg: var(--bs-pink);
--bslib-sidebar-toggle-bg: rgba(var(--pink-rgb), 0.1);
--bs-border-color-translucent: rgba(var(--pink-rgb), 0.33);
--bs-border-color: var(--bs-border-color-translucent);
}

.bslib-page-sidebar {
--bslib-page-sidebar-title-bg: var(--bs-pink);
--bslib-page-sidebar-title-color: var(--bs-white);
}

pre {
background: rgba(var(--pink-rgb), 0.05);
}
2 changes: 2 additions & 0 deletions shiny/experimental/ui/_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,7 @@ def page_sidebar(
fillable_mobile=fillable_mobile,
window_title=window_title,
lang=lang,
theme=MISSING,
**kwargs,
)

Expand Down Expand Up @@ -1510,5 +1511,6 @@ def page_fillable(
fillable_mobile=fillable_mobile,
title=title,
lang=lang,
theme=MISSING,
**kwargs,
)
2 changes: 2 additions & 0 deletions shiny/express/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
notification_remove,
nav_spacer,
Progress,
theme,
value_box_theme,
js_eval,
)
Expand Down Expand Up @@ -248,6 +249,7 @@
"notification_remove",
"nav_spacer",
"Progress",
"theme",
"value_box_theme",
# Imports from ._cm_components
"sidebar",
Expand Down
17 changes: 17 additions & 0 deletions shiny/express/ui/_page.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from __future__ import annotations

from pathlib import Path
from typing import Callable

from htmltools import Tag

from ... import ui
from ..._docstring import no_example
from ...types import MISSING, MISSING_TYPE
from ...ui._theme import Theme
from .._recall_context import RecallContextManager
from .._run import get_top_level_recall_context_manager

Expand All @@ -22,6 +24,7 @@ def page_opts(
*,
title: str | MISSING_TYPE = MISSING,
window_title: str | MISSING_TYPE = MISSING,
theme: str | Path | Theme | MISSING_TYPE = MISSING,
lang: str | MISSING_TYPE = MISSING,
page_fn: Callable[..., Tag] | None | MISSING_TYPE = MISSING,
fillable: bool | MISSING_TYPE = MISSING,
Expand All @@ -39,6 +42,18 @@ def page_opts(
window_title
The browser window title. If no value is provided, this will use the value of
``title``.
theme
A path to a CSS file that will replace the Bootstrap CSS bundled with a Shiny
app, or a theme create width :func:`~shiny.ui.theme`. If no value is provided,
the default Shiny Bootstrap theme is used.

CSS file: If a string or :class:`~pathlib.Path` is provided, it is
interpreted as a path to a CSS file that should completely replace
`bootstrap.min.css`. The CSS file is included using
:func:`~shiny.ui.include_css`.

Theme object: For more control over the theme and how it is applied, use
:func:`~shiny.ui.theme`.
lang
ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This
will be used as the lang in the ``<html>`` tag, as in ``<html lang="en">``. The
Expand Down Expand Up @@ -71,6 +86,8 @@ def page_opts(
cm.kwargs["title"] = title
if not isinstance(window_title, MISSING_TYPE):
cm.kwargs["window_title"] = window_title
if not isinstance(theme, MISSING_TYPE):
cm.kwargs["theme"] = theme
if not isinstance(lang, MISSING_TYPE):
cm.kwargs["lang"] = lang
if not isinstance(page_fn, MISSING_TYPE):
Expand Down
3 changes: 3 additions & 0 deletions shiny/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
sidebar,
update_sidebar,
)
from ._theme import theme
from ._tooltip import tooltip
from ._utils import js_eval
from ._valuebox import (
Expand Down Expand Up @@ -324,6 +325,8 @@
"showcase_top_right",
"ValueBoxTheme",
"ShowcaseLayout",
# _theme
"theme",
# _tooltip
"tooltip",
# _progress
Expand Down
35 changes: 30 additions & 5 deletions shiny/ui/_html_deps_external.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from __future__ import annotations

from htmltools import HTML, HTMLDependency
from pathlib import Path

from htmltools import HTML, HTMLDependency, Tag, TagList

from .._versions import bootstrap as bootstrap_version
from .._versions import shiny_html_deps
from ..html_dependencies import jquery_deps
from ..types import MISSING, MISSING_TYPE
from ._theme import Theme

"""
HTML dependencies for external dependencies Bootstrap, ionrangeslider, datepicker, selectize, and jQuery UI.
Expand All @@ -16,17 +20,38 @@
"""


def bootstrap_deps() -> list[HTMLDependency]:
def bootstrap_deps(
theme: str | Path | Theme | MISSING_TYPE = MISSING,
) -> list[HTMLDependency | Tag | TagList]:
if isinstance(theme, (str, Path)):
theme = Theme(theme)

if isinstance(theme, Theme):
theme.check_compatibility(bootstrap_version)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do this check on __init__? Could be confusing that a "bad" them can be constructed, but then later throws an error (and probably leads to a confusing stack trace)


theme_deps = theme.theme
replace = theme.replace
if replace == "all":
return [jquery_deps(), theme_deps]
elif theme is not MISSING:
raise TypeError(
f"Invalid type for `theme`: {type(theme)}. "
+ "Must be a string, Path, or Theme object created with `shiny.ui.theme()`."
)
else:
theme_deps = None
replace = "none"

dep = HTMLDependency(
name="bootstrap",
version=bootstrap_version,
source={"package": "shiny", "subdir": "www/shared/bootstrap/"},
script={"src": "bootstrap.bundle.min.js"},
stylesheet={"href": "bootstrap.min.css"},
stylesheet={"href": "bootstrap.min.css"} if replace != "css" else None,
meta={"name": "viewport", "content": "width=device-width, initial-scale=1"},
)
deps = [jquery_deps(), dep]
return deps
deps = [jquery_deps(), dep, theme_deps]
return [d for d in deps if d is not None]


def ionrangeslider_deps() -> list[HTMLDependency]:
Expand Down
Loading