diff --git a/shiny/templates/app-templates/dashboard/app.py b/shiny/templates/app-templates/dashboard/app.py
index 41983bbba..3e83227e0 100644
--- a/shiny/templates/app-templates/dashboard/app.py
+++ b/shiny/templates/app-templates/dashboard/app.py
@@ -30,13 +30,12 @@ def make_value_box(penguin):
),
),
ui.row(
- ui.layout_column_wrap(
+ ui.layout_columns(
*[make_value_box(penguin) for penguin in species],
- width=1 / 3,
)
),
ui.row(
- ui.layout_column_wrap(
+ ui.layout_columns(
ui.card(
ui.card_header("Summary statistics"),
ui.output_data_frame("summary_statistics"),
@@ -45,7 +44,6 @@ def make_value_box(penguin):
ui.card_header("Penguin bills"),
ui.output_plot("length_depth"),
),
- width=1 / 2,
),
),
)
diff --git a/shiny/templates/app-templates/multi-page/modules.py b/shiny/templates/app-templates/multi-page/modules.py
index c8df0bfd2..0a3532862 100644
--- a/shiny/templates/app-templates/multi-page/modules.py
+++ b/shiny/templates/app-templates/multi-page/modules.py
@@ -10,22 +10,19 @@
def training_ui():
return ui.nav(
"Training Dashboard",
- ui.row(
- ui.layout_column_wrap(
- ui.card(
- ui.card_header("Model Metrics"),
- ui.output_plot("metric"),
- ui.input_select(
- "metric",
- "Metric",
- choices=["ROC Curve", "Precision-Recall"],
- ),
+ ui.layout_columns(
+ ui.card(
+ ui.card_header("Model Metrics"),
+ ui.output_plot("metric"),
+ ui.input_select(
+ "metric",
+ "Metric",
+ choices=["ROC Curve", "Precision-Recall"],
),
- ui.card(
- ui.card_header("Training Scores"),
- ui.output_plot("score_dist"),
- ),
- width=1 / 2,
+ ),
+ ui.card(
+ ui.card_header("Training Scores"),
+ ui.output_plot("score_dist"),
),
),
)
@@ -54,22 +51,23 @@ def metric():
def data_view_ui():
return ui.nav(
"View Data",
- ui.row(
- ui.layout_column_wrap(
- ui.value_box(
- title="Row count",
- value=ui.output_text("row_count"),
- theme="primary",
- ),
- ui.value_box(
- title="Mean score",
- value=ui.output_text("mean_score"),
- theme="bg-green",
- ),
- width=1 / 2,
+ ui.layout_columns(
+ ui.value_box(
+ title="Row count",
+ value=ui.output_text("row_count"),
+ theme="primary",
+ ),
+ ui.value_box(
+ title="Mean score",
+ value=ui.output_text("mean_score"),
+ theme="bg-green",
),
+ gap="20px",
+ ),
+ ui.layout_columns(
+ ui.card(ui.output_data_frame("data")),
+ style="margin-top: 20px;",
),
- ui.card(ui.output_data_frame("data")),
)
diff --git a/shiny/templates/package-templates/js-react/custom_component/__init__.py b/shiny/templates/package-templates/js-react/custom_component/__init__.py
index ec4c3a3fe..ff6916ded 100644
--- a/shiny/templates/package-templates/js-react/custom_component/__init__.py
+++ b/shiny/templates/package-templates/js-react/custom_component/__init__.py
@@ -1,5 +1,11 @@
-from .custom_component import custom_component
+from .custom_component import (
+ input_custom_component,
+ output_custom_component,
+ render_custom_component,
+)
__all__ = [
- "custom_component",
+ "input_custom_component",
+ "output_custom_component",
+ "render_custom_component",
]
diff --git a/shiny/templates/package-templates/js-react/custom_component/custom_component.py b/shiny/templates/package-templates/js-react/custom_component/custom_component.py
index 114f037f4..91529e1f6 100644
--- a/shiny/templates/package-templates/js-react/custom_component/custom_component.py
+++ b/shiny/templates/package-templates/js-react/custom_component/custom_component.py
@@ -3,6 +3,12 @@
from htmltools import HTMLDependency, Tag
from shiny.module import resolve_id
+from shiny.render.transformer import (
+ TransformerMetadata,
+ ValueFn,
+ output_transformer,
+ resolve_value_fn,
+)
# This object is used to let Shiny know where the dependencies needed to run
# our component all live. In this case, we're just using a single javascript
@@ -18,14 +24,46 @@
)
-def custom_component(id: str):
+def input_custom_component(id: str):
"""
A shiny input.
"""
return Tag(
# This is the name of the custom tag we created with our webcomponent
- "custom-component",
+ "custom-component-input",
custom_component_deps,
# Use resolve_id so that our component will work in a module
id=resolve_id(id),
)
+
+
+# Output component
+
+
+@output_transformer()
+async def render_custom_component(
+ _meta: TransformerMetadata,
+ _fn: ValueFn[str | None],
+):
+ res = await resolve_value_fn(_fn)
+ if res is None:
+ return None
+
+ if not isinstance(res, str):
+ # Throw an error if the value is not a string
+ raise TypeError(f"Expected a string, got {type(res)}. ")
+
+ # Send the results to the client. Make sure that this is a serializable
+ # object and matches what is expected in the javascript code.
+ return {"value": res}
+
+
+def output_custom_component(id: str):
+ """
+ Show a color
+ """
+ return Tag(
+ "custom-component-output",
+ custom_component_deps,
+ id=resolve_id(id),
+ )
diff --git a/shiny/templates/package-templates/js-react/example-app/app.py b/shiny/templates/package-templates/js-react/example-app/app.py
index 795cca6a1..a87660408 100644
--- a/shiny/templates/package-templates/js-react/example-app/app.py
+++ b/shiny/templates/package-templates/js-react/example-app/app.py
@@ -1,19 +1,23 @@
# pyright: basic
-from custom_component import custom_component
+from custom_component import (
+ input_custom_component,
+ output_custom_component,
+ render_custom_component,
+)
-from shiny import App, render, ui
+from shiny import App, ui
app_ui = ui.page_fluid(
- custom_component("myComponent"),
- ui.output_text("valueOut"),
+ input_custom_component("color"),
+ output_custom_component("valueOut"),
)
def server(input, output, session):
- @render.text
+ @render_custom_component
def valueOut():
- return f"Value from input is {input.myComponent()}"
+ return input.color()
app = App(app_ui, server)
diff --git a/shiny/templates/package-templates/js-react/srcts/index.tsx b/shiny/templates/package-templates/js-react/srcts/index.tsx
index bc87655e0..4698b8219 100644
--- a/shiny/templates/package-templates/js-react/srcts/index.tsx
+++ b/shiny/templates/package-templates/js-react/srcts/index.tsx
@@ -1,12 +1,12 @@
import { SketchPicker } from "react-color";
import React from "react";
-import { makeReactInput } from "@posit-dev/shiny-bindings-react";
+import { makeReactInput, makeReactOutput } from "@shiny-helpers/react";
// Generates a new input binding that renders the supplied react component
// into the root of the webcomponent.
makeReactInput({
- tagName: "custom-component",
+ tagName: "custom-component-input",
initialValue: "#fff",
renderComp: ({ initialValue, onNewValue }) => (