Skip to content
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

Add output to React template #908

Merged
merged 3 commits into from
Dec 13, 2023
Merged
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
6 changes: 2 additions & 4 deletions shiny/templates/app-templates/dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -45,7 +44,6 @@ def make_value_box(penguin):
ui.card_header("Penguin bills"),
ui.output_plot("length_depth"),
),
width=1 / 2,
),
),
)
Expand Down
56 changes: 27 additions & 29 deletions shiny/templates/app-templates/multi-page/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
),
),
)
Expand Down Expand Up @@ -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")),
)


Expand Down
Original file line number Diff line number Diff line change
@@ -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",
]
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
)
16 changes: 10 additions & 6 deletions shiny/templates/package-templates/js-react/example-app/app.py
Original file line number Diff line number Diff line change
@@ -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)
18 changes: 16 additions & 2 deletions shiny/templates/package-templates/js-react/srcts/index.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<ColorPickerReact
Expand Down Expand Up @@ -36,3 +36,17 @@ function ColorPickerReact({
/>
);
}

makeReactOutput<{ value: string }>({
tagName: "custom-component-output",
renderComp: ({ value }) => (
<div
style={{
backgroundColor: value,
border: "1px solid black",
height: "100px",
width: "100px",
}}
/>
),
});
Loading