--- title: "Getting Started with glasstabs" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting Started with glasstabs} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(eval = FALSE) ``` ## What is glasstabs? glasstabs provides animated Shiny widgets for tab navigation and selection controls: - **Tabs**: a glass-morphism tab navigation bar with a sliding halo, spring easing, and a luminous transfer trace between tabs - **Multi-select filter**: a dropdown filter with animated checkboxes, live search, tag-pill display, and three checkbox styles - **Single-select dropdown**: an animated single-select input with optional search and clear support All widgets work in plain `fluidPage()` and in bs4Dash. They can be used together or independently. **Start here, by task.** Only need tabs? Read the tabs article. Only need a dropdown? Read the single-select or multi-select guide. Building reusable filters? Read the modules and linked-filters recipes. Something broken? Read troubleshooting first — most entries are configuration choices, not bugs. The example gallery lists a runnable demo for each question above. --- ## Installation ```{r install} # From CRAN install.packages("glasstabs") # From GitHub pak::pak("PrigasG/glasstabs") # From source devtools::install_local("path/to/glasstabs") ``` --- ## The one rule: call `useGlassTabs()` once Every glasstabs app needs exactly one call to `useGlassTabs()` somewhere in the UI. It injects the CSS and JavaScript as a proper `htmltools` dependency. ```{r use} library(shiny) library(glasstabs) ui <- fluidPage( useGlassTabs(), # <-- this is all you need # ... rest of your UI ) ``` --- ## Tab widget in 3 minutes ### Step 1 — define your tabs Each `glassTabPanel()` takes a unique `value`, a display `label`, and any UI content: ```{r tab-panels} glassTabPanel("overview", "Overview", selected = TRUE, h3("Welcome"), p("This is the overview pane.") ) ``` ### Step 2 — assemble with `glassTabsUI()` Pass your panels to `glassTabsUI()` along with a namespace `id`: ```{r tabs-ui} ui <- fluidPage( useGlassTabs(), glassTabsUI("nav", glassTabPanel("overview", "Overview", selected = TRUE, shiny::h3("Welcome"), shiny::p("Start here.") ), glassTabPanel("analysis", "Analysis", shiny::h3("Analysis"), shiny::p("Your charts go here.") ), glassTabPanel("settings", "Settings", shiny::h3("Settings"), shiny::p("Configuration options.") ) ) ) ``` ### Step 3 — read the active tab in the server The active tab value is pushed to Shiny automatically on every click: ```{r tabs-server} server <- function(input, output, session) { observe({ req(input[["nav-active_tab"]]) message("User is on: ", input[["nav-active_tab"]]) }) } if (interactive()) shinyApp(ui, server) ``` --- ## Multi-select filter ### Step 1 — define choices and place the widget ```{r ms-basic} choices <- c(Alpha = "alpha", Beta = "beta", Gamma = "gamma", Delta = "delta") ui <- fluidPage( useGlassTabs(), glassMultiSelect("category", choices), verbatimTextOutput("selected") ) ``` ### Step 2 — read the selection in the server ```{r ms-server} server <- function(input, output, session) { output$selected <- renderPrint(input$category) } if (interactive()) shinyApp(ui, server) ``` `input$category` is always a plain character vector of the checked values — use it like any other Shiny input to filter data, drive outputs, or trigger reactives. For the complete selection model, summaries, linked-filter pattern, server search, transactional updates, and migration notes, continue with the [multi-select guide](multiselect.html). For long filter names, give the field more room with `width = "240px"` or `width = "100%"`. With `overflow_tooltip = "auto"`, hovering a truncated option inside the open dropdown reveals that option alone. The trigger retains the complete accessible selection label without showing one combined tooltip for multiple values. These controls affect appearance only, not the value returned to Shiny. ### Optional reactive helper If you want a small convenience wrapper: ```{r ms-helper} server <- function(input, output, session) { ms <- glassMultiSelectValue(input, "category") observe({ message("Selected: ", paste(ms$selected(), collapse = ", ")) message("Style: ", ms$style()) }) } ``` ### Updating from the server `glassMultiSelect()` also supports server-side updates: ```{r ms-update} server <- function(input, output, session) { observeEvent(input$reset, { updateGlassMultiSelect( session, "category", selected = character(0) ) }) } ``` --- --- ## Single-select dropdown For the full single-value API and examples, see the [single-select guide](glassSelect.html). ### Step 1: add a single-select input ```{r gs-basic} choices <- c( North = "north", South = "south", East = "east", West = "west" ) ui <- fluidPage( useGlassTabs(), glassSelect("region", choices, clearable = TRUE), verbatimTextOutput("selected") ) ``` ### Step 2: read the value in the server ```{r gs-server} server <- function(input, output, session) { output$selected <- renderPrint(input$region) } if (interactive()) shinyApp(ui, server) ``` `input$region` is a single character value, or `NULL` when nothing is selected. The single- and multi-select widgets share `tongue`, `label_bold`, `label_size`, `search_focus_ring`, and `overflow_tooltip`. This makes it possible to keep the glass animation, use the quieter detached transition, or match native Shiny labels without changing server code. Multi-selects add `fill_colors = "single"` (default) or `"multi"` for filled markers. They also share automatic search. With `searchable = "auto"`, the search field is hidden until the number of choices reaches `search_threshold`; this keeps a five-category field simple while still supporting a list that grows later. ### Updating from the server ```{r gs-update} server <- function(input, output, session) { observeEvent(input$pick_south, { updateGlassSelect( session, "region", selected = "south" ) }) } ``` --- ## Using them together A common pattern is using one or more filters to drive content inside tab panes. Pass a widget to `extra_ui` and place `glassFilterTags()` inside panes to show active multi-select filters as removable tag pills. ```{r together} choices <- c(North = "north", South = "south", East = "east", West = "west") ui <- fluidPage( useGlassTabs(), glassTabsUI("main", extra_ui = glassMultiSelect( inputId = "region", choices = choices, show_style_switcher = FALSE ), glassTabPanel("summary", "Summary", selected = TRUE, shiny::h3("Summary"), glassFilterTags("region"), # tag pills appear here shiny::uiOutput("summary_text") ), glassTabPanel("detail", "Detail", shiny::h3("Detail"), glassFilterTags("region"), # same filter, second pane shiny::tableOutput("detail_table") ) ) ) server <- function(input, output, session) { # Fall back when an input is NULL or empty (no selection yet). fallback <- function(x, default) if (is.null(x) || length(x) == 0) default else x selected_regions <- reactive({ fallback(input$region, unique(unname(choices))) }) output$summary_text <- renderUI({ shiny::p("Showing data for: ", shiny::strong(paste(selected_regions(), collapse = ", "))) }) output$detail_table <- renderTable({ data.frame(Region = selected_regions()) }) } if (interactive()) shinyApp(ui, server) ``` --- ## Choosing a theme Both widgets default to `"dark"`. Switch to `"light"` or supply a custom theme object — in each case you only override what you need: ```{r themes} # Built-in light preset glassTabsUI("nav", theme = "light", ...) glassMultiSelect("f", theme = "light", ...) # Custom — one field each glassTabsUI("nav", theme = glass_tab_theme(halo_bg = "rgba(251,191,36,0.15)"), ... ) glassMultiSelect("f", choices, theme = glass_select_theme(accent_color = "#f59e0b") ) ``` --- ## bs4Dash Add `wrap = FALSE` so the glass halo positions itself relative to the card body rather than a full-page container. Pair with `theme = "light"`: ```{r bs4dash} library(bs4Dash) library(glasstabs) choices <- c(Alpha = "alpha", Beta = "beta", Gamma = "gamma") ui <- bs4DashPage( header = bs4DashNavbar(title = "My App"), sidebar = bs4DashSidebar(disable = TRUE), body = bs4DashBody( useGlassTabs(), bs4Card( title = "Analysis", width = 12, glassTabsUI("dash", wrap = FALSE, theme = "light", extra_ui = glassMultiSelect( "f", choices, theme = "light", show_style_switcher = FALSE ), glassTabPanel("a", "Overview", selected = TRUE, shiny::p("Overview content.") ), glassTabPanel("b", "Detail", shiny::p("Detail content.") ) ) ) ) ) server <- function(input, output, session) {} if (interactive()) shinyApp(ui, server) ``` --- ## A useful starting pattern Most apps only need one setup call, one navigation widget, and the controls that belong near it. This compact pattern is ready to adapt: ```{r starting-pattern} ui <- fluidPage( useGlassTabs(), glassTabsUI( "main", glassTabPanel("summary", "Summary", selected = TRUE, summary_ui), glassTabPanel("details", "Details", details_ui), extra_ui = glassSelect( "region", c(North = "north", South = "south"), clearable = TRUE, theme = "auto" ), theme = "auto" ) ) server <- function(input, output, session) { active_tab <- glassTabsServer("main") observe({ region <- if (is.null(input$region)) "all" else input$region message("Tab: ", active_tab(), "; region: ", region) }) } ``` Note that mapping an empty single-select to `"all"` here is an explicit app choice, not the widget's behavior: a cleared single-select returns `NULL`, and this app deliberately reads `NULL` as "all". The multi-select guide's "Understanding selection behavior" section works through the same decision for multi-selects, where the trap is Clear all restoring every record. From here, add only the options the app actually needs. Every widget keeps the usual Shiny model: place it in the UI, read its value from `input`, and use an update helper when the server needs to change it.