--- title: "Single-Select Dropdown with glasstabs" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Single-Select Dropdown with glasstabs} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} editor_options: markdown: wrap: 72 --- ```{r setup, include=FALSE} knitr::opts_chunk$set(eval = FALSE) ``` ## Overview `glassSelect()` is an animated single-select dropdown for Shiny. It behaves like a standard Shiny input: `input$` stores the selected value as a single character string, or `NULL` when nothing is selected. It supports: - optional labels - search and clear controls - built-in dark and light themes - custom theming with `glass_select_theme()` - selectable marker styles via `check_style` - server-side updates with `updateGlassSelect()` ## Basic usage ```{r basic} library(shiny) library(glasstabs) fruits <- c( Apple = "apple", Banana = "banana", Cherry = "cherry", Mango = "mango" ) ui <- fluidPage( useGlassTabs(), glassSelect("pick", fruits), verbatimTextOutput("out") ) server <- function(input, output, session) { output$out <- renderPrint(input$pick) } if (interactive()) shinyApp(ui, server) ``` ## Initial selection ```{r initial} # No selection (default) glassSelect("f", fruits) # Pre-select one value glassSelect("f", fruits, selected = "banana") ``` ## Optional label ```{r label} glassSelect("f", fruits, label = "Choose a fruit") ``` ## Selection styles `glassSelect()` supports three selection marker styles: - `"checkbox"` (default) - `"check-only"` - `"filled"` ```{r styles} glassSelect("f1", fruits, check_style = "checkbox") glassSelect("f2", fruits, check_style = "check-only") glassSelect("f3", fruits, check_style = "filled") ``` ## Corner shape By default the trigger and dropdown use the signature rounded glass corners. Pass `shape = "square"` for crisp, selectize-style corners so the widget sits flush next to native `selectizeInput()` controls without looking out of place: ```{r shape} # Rounded (default) glassSelect("region_rounded", fruits, shape = "rounded") # Square - matches selectizeInput() glassSelect("region_square", fruits, shape = "square") ``` The shape can also be switched at runtime from the server with `updateGlassSelect(session, "region_square", shape = "square")`. See the corner shape side by side with native selectize in `runGlassExample("square-corners")`. ## Width, grouped choices, and disabled options For parity with native `selectizeInput()`, `glassSelect()` accepts a `width`, grouped choices as a named list, and disabled state: ```{r parity} # Fixed / fluid width glassSelect("region_w", fruits, width = "240px") # Grouped choices (selectInput()-style named list) glassSelect("food", list( Fruit = c(Apple = "apple", Banana = "banana"), Veg = c(Carrot = "carrot", Pea = "pea") )) # Disable the whole widget, or specific options glassSelect("region_d", fruits, disabled = TRUE) glassSelect("region_dc", fruits, disabled_choices = "banana") ``` `disabled` and `disabled_choices` can also be toggled at runtime with `updateGlassSelect()`. ## Searchable and clearable Search is enabled by default. You can also enable a clear control. ```{r search-clear} glassSelect( "f", fruits, searchable = TRUE, clearable = TRUE ) ``` To hide the search box: ```{r no-search} glassSelect( "f", fruits, searchable = FALSE ) ``` ## Server-side search for large choice sets For hundreds or thousands of choices, set `server = TRUE` in the UI and register `glassSelectServer()` in the server function. The browser renders only an initial slice of choices and asks Shiny for matching choices as the user types. ```{r server-search} many_choices <- stats::setNames( sprintf("value-%04d", 1:2000), sprintf("Choice %04d", 1:2000) ) ui <- fluidPage( useGlassTabs(), glassSelect( "pick", many_choices, selected = "value-1500", clearable = TRUE, server = TRUE, server_limit = 30 ), verbatimTextOutput("out") ) server <- function(input, output, session) { glassSelectServer("pick", many_choices, session = session, limit = 30) output$out <- renderPrint(input$pick) } if (interactive()) shinyApp(ui, server) ``` This pattern also works when the control is created dynamically with `renderUI()`. Keep the `glassSelectServer()` call in the server function for the same `inputId`. ## Explicit "All" option You can prepend an explicit "All" option: ```{r include-all} glassSelect( "f", fruits, include_all = TRUE, all_choice_label = "All fruits", all_choice_value = "__all__" ) ``` This is useful when you want the UI to distinguish between: - no selection - a deliberate "All" selection ## Server-side access Read the current value directly from `input$`: ```{r server-direct} server <- function(input, output, session) { observe({ message("Selected: ", if (is.null(input$pick)) "NULL" else input$pick) }) } ``` ## Reactive helper If you want a small convenience wrapper, use `glassSelectValue()`: ```{r server-helper} server <- function(input, output, session) { pick <- glassSelectValue(input, "pick") observe({ print(pick()) }) } ``` ## Updating choices and selection from the server `updateGlassSelect()` can update: - available choices - current selection - selection marker style ```{r style selection} observeEvent(input$filled, { updateGlassSelect( session, "pick", check_style = "filled" ) }) ``` It follows Shiny-style update semantics: - `choices = NULL` leaves choices unchanged - `selected = NULL` leaves selection unchanged - `selected = character(0)` clears the selection ```{r updates} ui <- fluidPage( useGlassTabs(), actionButton("subset", "Keep first 2 fruits"), actionButton("banana", "Select banana"), actionButton("clear", "Clear"), actionButton("filled", "Use filled style"), glassSelect("pick", fruits, clearable = TRUE), verbatimTextOutput("out") ) server <- function(input, output, session) { output$out <- renderPrint(input$pick) observeEvent(input$subset, { updateGlassSelect( session, "pick", choices = fruits[1:2] ) }) observeEvent(input$banana, { updateGlassSelect( session, "pick", selected = "banana" ) }) observeEvent(input$clear, { updateGlassSelect( session, "pick", selected = character(0) ) }) } if (interactive()) shinyApp(ui, server) ``` When `choices` is updated without `selected`, the widget keeps the current value if it still exists in the new set of choices. ## Theming ### Built-in presets ```{r presets} glassSelect("f", fruits, theme = "dark") glassSelect("f", fruits, theme = "light") ``` ### Custom theme with `glass_select_theme()` ```{r custom-theme} glassSelect( "f", fruits, theme = glass_select_theme( mode = "dark", accent_color = "#f59e0b" ) ) glassSelect( "f", fruits, theme = glass_select_theme( bg_color = "#1a0a2e", border_color = "#a855f7", text_color = "#ede9fe", accent_color = "#a855f7" ) ) #other themes glassSelect( "f", fruits, theme = glass_select_theme( mode = "light", accent_color = "#2563eb" ) ) #more themes glassSelect( "f", fruits, theme = glass_select_theme( mode = "light", bg_color = "#ffffff", border_color = "rgba(0,0,0,0.15)", text_color = "#111111", accent_color = "#111111", label_color = "#111111" ) ) ``` ## Standalone usage `glassSelect()` can be used without the tab widget. You only need `useGlassTabs()` to load the package CSS and JavaScript assets: ```{r standalone} ui <- fluidPage( useGlassTabs(), glassSelect("region", c(North = "North", South = "South", East = "East")), verbatimTextOutput("out") ) server <- function(input, output, session) { output$out <- renderPrint(input$region) } if (interactive()) shinyApp(ui, server) ```