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

Shiny API v2 - modern aliases for Shiny UI/output functions #1077

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

gadenbuie
Copy link
Member

@gadenbuie gadenbuie commented Jun 15, 2024

Could bslib provide modern aliases for Shiny UI functions? This PR aims to answer that question by starting with aliases for Shiny functions that have also been ported to Shiny for Python, using naming conventions that are familiar to tidyverse users and consistent with the Shiny for Python API (these are generall well-aligned).

In addition to helping to nudge naming conventions away from camel case, this PR will also help give bslib some flexibility in providing its own implementations of these inputs. This will make it easier for bslib to eventually provide inputs tailored to specific Bootstrap versions or to provide alternate implementations that fix Shiny issues that are relevant to bslib but harder to address in Shiny directly.

To that last point, there are some deviations from core Shiny in this PR:

  • id is used instead of inputId or outputId.
  • session is not the first argument of the update_*() functions, instead it's the last.
  • ... are added in many places, generally following Shiny for Python's * placement. This matches practical usage and makes it easier for us to adjust function signatures in the future.
  • We skip Shiny's icon validation in input_action_button() and input_action_link(), making it possible to use bsicon::bs_icon() and others.

Remaining work:

  • modals
  • notifications
  • progress bars
App demo/test
library(shiny)
pkgload::load_all()
library(bslib)

ui <- page_navbar(
  theme = bs_theme() |>
    bs_add_rules("
    h2 {
      border-top: 1px solid var(--bs-border-color-translucent);
      padding-top: 0.5rem;
      font-family: var(--bs-font-monospace);
    }
    h2:not(:first-child) {
      margin-top: 3rem;
    }"),

  title = "Shiny Aliases",
  # selected = "Outputs",
  fillable = TRUE,
  padding = 0,

  nav_panel(
    "Inputs",
    div(
      as_fill_item(),
      class = "overflow-auto p-3",
      h2("input_action_button"),
      layout_columns(
        input_action_button(
          "action_button",
          "Action Button",
          icon = bsicons::bs_icon("cash")
        ),
        output_text_verbatim("debug_action_button")
      ),
      h2("input_action_link"),
      layout_columns(
        input_action_link(
          "action_link",
          "Action Link",
          icon = bsicons::bs_icon("truck")
        ),
        output_text_verbatim("debug_action_link")
      ),
      h2("input_checkbox"),
      layout_columns(
        input_checkbox("checkbox", "Checkbox"),
        output_text_verbatim("debug_checkbox")
      ),
      h2("input_checkbox_group"),
      layout_columns(
        input_checkbox_group(
          "checkbox_group",
          "Checkbox Group",
          choices = c("Option 1", "Option 2")
        ),
        output_text_verbatim("debug_checkbox_group")
      ),
      h2("input_date"),
      layout_columns(
        input_date("date", "Date Input"),
        output_text_verbatim("debug_date")
      ),
      h2("input_date_range"),
      layout_columns(
        input_date_range("date_range", "Date Range Input"),
        output_text_verbatim("debug_date_range")
      ),
      h2("input_numeric"),
      layout_columns(
        input_numeric("numeric", "Numeric Input", 42),
        output_text_verbatim("debug_numeric")
      ),
      h2("input_password"),
      layout_columns(
        input_password("password", "Password Input"),
        output_text_verbatim("debug_password")
      ),
      h2("input_radio_buttons"),
      layout_columns(
        input_radio_buttons(
          "radio_buttons",
          "Radio Buttons",
          choices = c("Option 1", "Option 2")
        ),
        output_text_verbatim("debug_radio_buttons")
      ),
      h2("input_select"),
      layout_columns(
        input_select(
          "select",
          "Select Input",
          choices = c("Option 1", "Option 2")
        ),
        output_text_verbatim("debug_select")
      ),
      h2("input_selectize"),
      layout_columns(
        input_selectize(
          "selectize",
          "Selectize Input",
          choices = c("Option 1", "Option 2")
        ),
        output_text_verbatim("debug_selectize")
      ),
      h2("input_slider"),
      layout_columns(
        div(
          input_slider(
            "slider",
            "Slider Input",
            min = 0,
            max = 100,
            value = 50
          ),
          input_slider(
            "range_slider",
            "Range Slider Input",
            min = 0,
            max = 100,
            value = c(25, 75)
          )
        ),
        output_text_verbatim("debug_slider")
      ),
      h2("input_text"),
      layout_columns(
        input_text("text", "Text Input"),
        output_text_verbatim("debug_text")
      ),
      h2("input_text_area"),
      layout_columns(
        input_text_area("text_area", "Text Area Input"),
        output_text_verbatim("debug_text_area")
      )
    )
  ),

  nav_panel(
    "Outputs",
    div(
      as_fill_item(),
      class = "overflow-auto p-3",
      layout_column_wrap(
        width = 400,
        heights_equal = "row",
        div(
          h2("output_image"),
          output_image("output_image")
        ),
        div(
          h2("output_plot"),
          output_plot("output_plot")
        ),
        div(
          h2("output_table"),
          output_table("output_table")
        ),
        div(
          h2("output_text"),
          output_text("output_text")
        ),
        div(
          h2("output_text_verbatim"),
          output_text_verbatim("output_text_verbatim")
        ),
        div(
          h2("output_ui"),
          output_ui("output_ui")
        )
      )
    )
  )
)


server <- function(input, output, session) {
  output$debug_action_button <- render_text_verbatim({
    input$action_button
  })
  output$debug_action_link <- render_text_verbatim({
    input$action_link
  })
  output$debug_checkbox <- render_text_verbatim({
    input$checkbox
  })
  output$debug_checkbox_group <- render_text_verbatim({
    input$checkbox_group
  })
  output$debug_date <- render_text_verbatim({
    input$date
  })
  output$debug_date_range <- render_text_verbatim({
    input$date_range
  })
  output$debug_numeric <- render_text_verbatim({
    input$numeric
  })
  output$debug_password <- render_text_verbatim({
    input$password
  })
  output$debug_radio_buttons <- render_text_verbatim({
    input$radio_buttons
  })
  output$debug_select <- render_text_verbatim({
    input$select
  })
  output$debug_selectize <- render_text_verbatim({
    input$selectize
  })
  output$debug_slider <- render_text_verbatim({
    list(
      slider = input$slider,
      range_slider = input$range_slider
    )
  })
  output$debug_text <- render_text_verbatim({
    input$text
  })
  output$debug_text_area <- render_text_verbatim({
    input$text_area
  })

  # Outputs ---------------------------------------------------------------
  output$output_text <- render_text({
    input$action_button
    paste(lorem::ipsum(1, 1))
  })

  output$output_image <- renderImage(
    {
      # A temp file to save the output. It will be deleted after renderImage
      # sends it, because deleteFile=TRUE.
      outfile <- tempfile(fileext = ".png")

      # Generate a png
      png(outfile, width = 400, height = 400)
      hist(rnorm(input$slider))
      dev.off()

      # Return a list
      list(
        src = outfile,
        alt = "This is alternate text"
      )
    },
    deleteFile = TRUE
  )

  output$output_plot <- render_plot({
    plot(
      runif(50, input$range_slider[1], input$range_slider[2]),
      rnorm(50)
    )
  })

  output$output_table <- render_table({
    input$action_link
    data.frame(
      id = 1:5,
      x = runif(5),
      y = rnorm(5)
    )
  })

  output$output_text_verbatim <- render_text_verbatim({
    summary(if (input$checkbox) mtcars else cars)
  })

  output$output_ui <- render_ui({
    tags$ul(
      tags$li(tags$code("select"), "-", input$select),
      tags$li(tags$code("selectize"), "-", input$selectize),
    )
  })
}

shinyApp(ui, server)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant