├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R ├── modal-functionality.R ├── tailwind_cli.R ├── twCheckboxGroupInput.R ├── twCheckboxInput.R ├── twDateInput.R ├── twDateRangeInput.R ├── twFileInput.R ├── twNumericInput.R ├── twSelectInput.R ├── twSelectizeInput.R ├── twSliderInput.R ├── twTabContent.R ├── twTabNav.R ├── twTextAreaInput.R ├── twTextInput.R ├── twVarSelectInput.R ├── twVarSelectizeInput.R ├── use_daisyui.R ├── use_flowbite.R ├── use_preline.R └── use_tailwind.R ├── README.Rmd ├── README.md ├── inst ├── default-tailwind.config.js ├── examples │ ├── 01-old-faithful │ │ ├── app.R │ │ ├── custom.css │ │ └── tailwind.config.js │ ├── 02-Scrollytelling │ │ └── app.R │ ├── 03-css-generation │ │ ├── app.R │ │ └── custom.css │ ├── 04-shiny-inputs │ │ └── app.R │ ├── 05-apply-directive │ │ ├── app.R │ │ └── apply-custom.css │ ├── 06-sidebar-dashboard │ │ └── app.R │ ├── 07-daisyUI │ │ └── app.R │ ├── 08-flowbite │ │ └── app.R │ ├── 09-nested-tabsets │ │ └── app.R │ ├── 10-modal │ │ └── app.R │ └── 11-preline │ │ └── app.R └── twTab.js ├── man ├── compile_tailwindcss.Rd ├── figures │ └── README-example.png ├── install_tailwindcss_cli.Rd ├── is_tailwindcss_installed.Rd ├── twBtnOpenModal.Rd ├── twCheckboxGroupInput.Rd ├── twCheckboxInput.Rd ├── twDateInput.Rd ├── twDateRangeInput.Rd ├── twFileInput.Rd ├── twModalDialog.Rd ├── twNumericInput.Rd ├── twSelectInput.Rd ├── twSelectizeInput.Rd ├── twSliderInput.Rd ├── twTabContent.Rd ├── twTabNav.Rd ├── twTextAreaInput.Rd ├── twTextInput.Rd ├── twVarSelectInput.Rd ├── twVarSelectizeInput.Rd ├── use_daisyui.Rd ├── use_flowbite.Rd ├── use_preline.Rd └── use_tailwind.Rd └── shiny.tailwind.Rproj /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^LICENSE\.md$ 5 | ^tailwindcss.exe$ 6 | ^tailwindcss$ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .Rdata 4 | .httr-oauth 5 | .DS_Store 6 | tailwindcss.exe 7 | tailwindcss 8 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: shiny.tailwind 2 | Type: Package 3 | Title: 'TailwindCSS' for Shiny Apps 4 | Version: 0.2.2 5 | Authors@R: c( 6 | person(given = "Kyle", 7 | family = "Butts", 8 | role = c("aut","cre"), 9 | email = "kyle.butts@colorado.edu", 10 | comment = c(ORCID = "0000-0002-9048-8059")), 11 | person(given = "David", 12 | family = "Zimmermann-Kollenda", 13 | role = "ctb", 14 | email = "david_j_zimmermann@hotmail.com")) 15 | Description: Allows 'TailwindCSS' to be used in Shiny apps with just-in-time 16 | compiling, custom css with '@apply' directive, and custom tailwind 17 | configurations. 18 | Encoding: UTF-8 19 | LazyData: true 20 | RoxygenNote: 7.3.2 21 | License: MIT + file LICENSE 22 | Depends: 23 | R (>= 3.6.0), 24 | htmltools 25 | Imports: 26 | shiny 27 | URL: https://github.com/kylebutts/shiny.tailwind 28 | BugReports: https://github.com/kylebutts/shiny.tailwind/issues 29 | Roxygen: list(markdown = TRUE) 30 | Suggests: 31 | fontawesome, 32 | palmerpenguins, 33 | ggplot2 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2021 2 | COPYRIGHT HOLDER: shiny.tailwind authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 shiny.tailwind authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(compile_tailwindcss) 4 | export(install_tailwindcss_cli) 5 | export(is_tailwindcss_installed) 6 | export(twBtnOpenModal) 7 | export(twCheckboxGroupInput) 8 | export(twCheckboxInput) 9 | export(twDateInput) 10 | export(twDateRangeInput) 11 | export(twFileInput) 12 | export(twModalDialog) 13 | export(twNumericInput) 14 | export(twSelectInput) 15 | export(twSelectizeInput) 16 | export(twSliderInput) 17 | export(twTabContent) 18 | export(twTabNav) 19 | export(twTextAreaInput) 20 | export(twTextInput) 21 | export(twVarSelectInput) 22 | export(twVarSelectizeInput) 23 | export(use_daisyui) 24 | export(use_flowbite) 25 | export(use_preline) 26 | export(use_tailwind) 27 | import(htmltools) 28 | -------------------------------------------------------------------------------- /R/modal-functionality.R: -------------------------------------------------------------------------------- 1 | #' Creates a button to open a Modal Dialog 2 | #' 3 | #' @param btn_id ID of the button 4 | #' @param btn_label Label for the button 5 | #' @param btn_class Classes to style the button 6 | #' @param icon an optional icon for the button 7 | #' @param modal_id ID of the modal, make sure that the IDs are identical to the 8 | #' one used in [twModalDialog()] 9 | #' 10 | #' @return a list with a `shiny.tag` class 11 | #' @export 12 | #' 13 | #' @examples 14 | #' ui <- div( 15 | #' use_tailwind(), 16 | #' class = "h-screen bg-stone-100 p-10", 17 | #' twBtnOpenModal( 18 | #' "open_modal", "Show Modal", 19 | #' btn_class = "px-5 py-2 bg-rose-500 hover:bg-rose-700 text-white cursor-pointer rounded-md" 20 | #' ), 21 | #' twModalDialog(p("Hello World"), ) 22 | #' ) 23 | #' 24 | #' server <- function(input, output, session) { 25 | #' observeEvent(input$open_modal, { 26 | #' print("Modal Opened") 27 | #' }) 28 | #' observeEvent(input$submit, { 29 | #' print("Modal Closed - Submitted") 30 | #' }) 31 | #' observeEvent(input$close, { 32 | #' print("Modal Closed - Closed") 33 | #' }) 34 | #' } 35 | #' if (interactive()) shinyApp(ui, server) 36 | twBtnOpenModal <- function( 37 | btn_id, 38 | btn_label, 39 | btn_class = NULL, 40 | icon = NULL, 41 | modal_id = "shiny-modal" 42 | ) { 43 | close_script <- sprintf( 44 | "document.getElementById('%s').classList.remove('hidden')", 45 | modal_id 46 | ) 47 | 48 | if (is.null(btn_class)) { 49 | btn_class <- "" 50 | } 51 | 52 | shiny::tags$button( 53 | id = btn_id, 54 | class = c("action-button", btn_class), 55 | onclick = close_script, 56 | shiny::tagList(icon, btn_label) 57 | ) 58 | } 59 | 60 | 61 | #' Creates a Modal Dialog 62 | #' 63 | #' @param ui UI of the modal 64 | #' @param close_id ID for the close button 65 | #' @param close_label Label for the close button, can be a tagList of an icon 66 | #' and the label 67 | #' @param close_class classes for the close button, if NA default values will 68 | #' be used 69 | #' @param submit_id ID for the submit button 70 | #' @param submit_label Label for the submit button, can be a tagList of an icon 71 | #' and the label 72 | #' @param submit_class classes for the submit button, if NA default values will 73 | #' be used 74 | #' @param title title of the modal 75 | #' @param modal_id id of the modal, make sure the ID is identical to the one 76 | #' used in twBtnOpenModal 77 | #' @param modal_width optional class to define the modal width, eg `max-w-4xl` 78 | #' for a wider modal 79 | #' 80 | #' @return a list with a `shiny.tag` class 81 | #' @export 82 | #' 83 | #' @examples 84 | #' ui <- div( 85 | #' use_tailwind(), 86 | #' class = "h-screen bg-stone-100 p-10", 87 | #' twBtnOpenModal( 88 | #' "open_modal", "Show Modal", 89 | #' btn_class = "px-5 py-2 bg-rose-500 hover:bg-rose-700 text-white cursor-pointer rounded-md" 90 | #' ), 91 | #' twModalDialog(p("Hello World")) 92 | #' ) 93 | #' 94 | #' server <- function(input, output, session) { 95 | #' observeEvent(input$open_modal, { 96 | #' print("Modal Opened") 97 | #' }) 98 | #' observeEvent(input$submit, { 99 | #' print("Modal Closed - Submitted") 100 | #' }) 101 | #' observeEvent(input$close, { 102 | #' print("Modal Closed - Closed") 103 | #' }) 104 | #' } 105 | #' if (interactive()) shinyApp(ui, server) 106 | twModalDialog <- function( 107 | ui, 108 | close_id = "close", 109 | close_label = "Close", 110 | close_class = NA, 111 | submit_id = "submit", 112 | submit_label = "Submit", 113 | submit_class = NA, 114 | title = "Title of Modal", 115 | modal_id = "shiny-modal", 116 | modal_width = "max-w-lg" 117 | ) { 118 | if (!is.null(close_class) && is.na(close_class)) { 119 | close_class <- paste( 120 | "mt-3 w-full justify-center rounded-md border border-gray-300 shadow-sm", 121 | "px-4 py-2 bg-white text-base font-bold text-gray-700 hover:bg-gray-50", 122 | "mt-0 ml-3 w-auto text-sm place-items-center" 123 | ) 124 | } 125 | if (!is.null(submit_class) && is.na(submit_class)) { 126 | submit_class <- paste( 127 | "w-full justify-center rounded-md border border-transparent shadow-sm", 128 | "px-4 py-2 bg-blue-600 text-base font-bold text-white hover:bg-blue-800", 129 | "ml-3 w-auto text-sm place-items-center" 130 | ) 131 | } 132 | 133 | div( 134 | class = "relative z-50 hidden", 135 | id = modal_id, 136 | "aria-labelledby " = "modal-title", 137 | role = "dialog", 138 | "aria-modal" = "true", 139 | div(class = "fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"), 140 | 141 | div( 142 | class = "fixed z-10 inset-0 overflow-y-auto", 143 | div( 144 | class = "flex items-end sm:items-center justify-center min-h-full p-4 text-center p-0", 145 | div( 146 | class = paste( 147 | "relative bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all my-8 w-full", 148 | modal_width 149 | ), 150 | div( 151 | class = "bg-white px-4 pt-5 pb-4 p-6 pb-4", 152 | div( 153 | class = "flex items-start", 154 | div( 155 | class = "mt-3 text-center mt-0 ml-4 text-left", 156 | h3( 157 | class = "font-extrabold mb-2 text-xl leading-6 text-gray-900", 158 | id = paste0(modal_id, "-title"), 159 | title 160 | ), 161 | ui 162 | ) 163 | ) 164 | ), 165 | div( 166 | class = "bg-gray-50 px-4 py-3 px-6 flex flex-row-reverse", 167 | shiny::tags$button( 168 | id = submit_id, 169 | type = "button", 170 | class = c("action-button", submit_class), 171 | onclick = sprintf( 172 | "document.getElementById('%s').classList.add('hidden')", 173 | modal_id 174 | ), 175 | submit_label 176 | ), 177 | 178 | shiny::tags$button( 179 | id = close_id, 180 | type = "button", 181 | class = c("action-button", close_class), 182 | onclick = sprintf( 183 | "document.getElementById('%s').classList.add('hidden')", 184 | modal_id 185 | ), 186 | close_label 187 | ) 188 | ) 189 | ) 190 | ) 191 | ) 192 | ) 193 | } 194 | -------------------------------------------------------------------------------- /R/tailwind_cli.R: -------------------------------------------------------------------------------- 1 | #' Installs the 'TailwindCSS' CLI 2 | #' 3 | #' @description This will download the 'TailwindCSS' standalone CLI to the 4 | #' current working directory. 5 | #' 6 | #' @details This will download the 'TailwindCSS' standalone CLI to the current 7 | #' working directory. 8 | #' See [here](https://tailwindcss.com/blog/standalone-cli) for details on the 9 | #' standalone CLI. This saves you from having to install 'node.js'. 10 | #' 11 | #' On the mac, after installing the CLI, you need to make sure that the file 12 | #' is executable to run it. For Mac, the easiest way to do so is to ensure 13 | #' you're in the correct working directory in R and type 14 | #' `system("chmod +x tailwindcss")`. 15 | #' Alternatively, you could `cd` to the directory in terminal and then run 16 | #' `chmod +x tailwindcss`. 17 | #' 18 | #' 19 | #' @param overwrite if existing installations should be overwritten 20 | #' @param version the version to install, default is latest 21 | #' @param verbose if the version etc should be reported 22 | #' 23 | #' @export 24 | #' @return invisibly the path to the cli program 25 | #' 26 | #' @seealso [compile_tailwindcss] 27 | #' @examples 28 | #' if (interactive()) { 29 | #' install_tailwindcss_cli() 30 | #' } 31 | install_tailwindcss_cli <- function( 32 | overwrite = FALSE, 33 | version = "latest", 34 | verbose = FALSE 35 | ) { 36 | if (is_tailwindcss_installed() && !overwrite) { 37 | stop("Found existing tailwindcss installation. Abort installation!") 38 | } 39 | 40 | info <- Sys.info() 41 | # 1) find system, either linux, macos, or windows 42 | sys <- tolower(info[["sysname"]]) 43 | # TODO: not sure if this is catches all Mac OS?! 44 | if (sys == "darwin") sys <- "macos" 45 | 46 | # 2) find architecture 47 | # TODO: does this distinguish between x64 and ARM64 in all cases? 48 | arch <- if (grepl("x86.64", info[["machine"]])) "x64" else "arm64" 49 | 50 | file <- paste( 51 | "tailwindcss", 52 | if (sys == "windows") { 53 | "windows-x64.exe" 54 | } else { 55 | paste(sys, arch, sep = "-") 56 | }, 57 | sep = "-" 58 | ) 59 | 60 | # 3) get latest release version 61 | url <- "https://github.com/tailwindlabs/tailwindcss/releases" 62 | if (version == "latest") { 63 | html <- readLines(url) 64 | # Returns all available versions 65 | v <- grep( 66 | ".*\\>(v[0-9]+.[0-9]+.[0-9]+)\\<.*", 67 | value = TRUE, 68 | html, 69 | perl = TRUE 70 | ) 71 | # Extract release version 72 | version <- regmatches(v, gregexec("v[0-9]+.[0-9]+.[0-9]+", v))[[1]][1, 1] 73 | } 74 | if (verbose) { 75 | cat(paste0( 76 | "Trying to download tailwindcss CLI version ", 77 | version, 78 | "\n", 79 | " from ", 80 | url, 81 | "\n" 82 | )) 83 | } 84 | unlink(file) 85 | download_url <- paste0(url, "/download/", version, "/", file) 86 | suppressWarnings({ 87 | a <- try(utils::download.file(download_url, file), silent = TRUE) 88 | }) 89 | 90 | if (inherits(a, "try-error")) { 91 | stop(sprintf( 92 | paste0( 93 | "Could not download tailwindcss CLI.\n", 94 | " Either tailwindcss CLI version %s could not be found or another error occured.\n", 95 | " Please make sure that the version is available from\n %s" 96 | ), 97 | version, 98 | url 99 | )) 100 | } 101 | 102 | # 4) rename file to tailwindcss(.exe) 103 | target <- "tailwindcss" 104 | if (sys == "windows") target <- paste0(target, ".exe") 105 | file.rename(file, target) 106 | 107 | if (sys == "macos") system("chmod +x tailwindcss") 108 | 109 | cat(sprintf( 110 | paste0( 111 | "Success: installed tailwindcss version %s as '%s'!\n", 112 | "Next you must ensure that the tailwind css file is executable.\n", 113 | "Type `?install_tailwindcss_cli` to read more about how to do this" 114 | ), 115 | version, 116 | target 117 | )) 118 | 119 | return(invisible(target)) 120 | } 121 | 122 | # internal helper 123 | get_cli_executable <- function(tailwindcss = NULL) { 124 | if (is.null(tailwindcss)) { 125 | if (Sys.info()[["sysname"]] == "Windows") { 126 | tailwindcss <- "tailwindcss.exe" 127 | } else { 128 | tailwindcss <- "./tailwindcss" 129 | } 130 | } 131 | return(tailwindcss) 132 | } 133 | 134 | 135 | #' Checks if 'TailwindCSS' CLI is installed 136 | #' 137 | #' To install the CLI of 'TailwindCSS', please follow the instructions of 138 | #' ['TailwindCSS' releases](https://github.com/tailwindlabs/tailwindcss/releases). 139 | #' Make sure that you either provide the direction to the executable as the 140 | #' first argument to this function or put it in a folder on your PATH variable. 141 | #' 142 | #' @param tailwindcss name and path to the executable 143 | #' @param verbose report version number etc 144 | #' 145 | #' @return TRUE/FALSE if the CLI is installed 146 | #' @export 147 | #' 148 | #' @examples 149 | #' if (interactive()) { 150 | #' is_tailwindcss_installed() 151 | #' } 152 | is_tailwindcss_installed <- function(tailwindcss = NULL, verbose = FALSE) { 153 | tailwindcss <- get_cli_executable(tailwindcss) 154 | 155 | cmd <- paste(tailwindcss, "-h") 156 | r <- try(system(cmd, intern = TRUE), silent = TRUE) 157 | 158 | if (inherits(r, "try-error") || length(r) <= 2) { 159 | if (verbose) { 160 | warning(paste( 161 | "Could not find CLI tailwindcss.", 162 | "Please follow install instructions and put it in your PATH or supply the path to this function", 163 | "Download: https://github.com/tailwindlabs/tailwindcss/releases", 164 | attr(r, "condition"), 165 | sep = "\n" 166 | )) 167 | } 168 | return(FALSE) 169 | } 170 | 171 | version <- gsub("tailwindcss +", "", r[[2]]) 172 | if (verbose) { 173 | cat(sprintf("Found tailwindcss version %s\n", version)) 174 | } 175 | return(TRUE) 176 | } 177 | 178 | 179 | #' Starts the 'TailwindCSS' CLI 180 | #' 181 | #' See [v4 docs](https://tailwindcss.com/docs/installation/play-cdn) 182 | #' or [v3 docs](https://tailwindcss.com/blog/standalfone-cli). 183 | #' 184 | #' @param infile the 'TailwindCSS' file (eg containing the `@tailwind` directives). Relative to basedir 185 | #' @param outfile the target css file, where tailwind will write the css to. 186 | #' Relative to basedir 187 | #' @param version Which version of tailwind to use. Default is now v4. 188 | #' @param config the path to the tailwind.config.js file. With v4, the config file is not needed and can instead be specified in the `infile` css. 189 | #' With v3, the default is tailwind.config.js in the root diretory. 190 | #' 191 | #' @param watch if the files should be continuously monitored (versus only 192 | #' compile the css once), default is False 193 | #' @param tailwindcss name and path to the executable 194 | #' @param verbose print information 195 | #' @param minify if the code should be minified, default is FALSE 196 | #' @param content content paths to remove unused classes, default is current dir 197 | #' 198 | #' @return the outfile invisibly 199 | #' @export 200 | #' 201 | #' @seealso [install_tailwindcss_cli] 202 | #' @examples 203 | #' if (interactive()) { 204 | #' temp <- tempdir() 205 | #' owd <- setwd(temp) 206 | #' 207 | #' infile <- "custom.css" 208 | #' writeLines("@tailwind base;", infile) 209 | #' outfile <- "out.css" 210 | #' 211 | #' # file.copy(system.file("examples", "01-Old_Faithful", "app.R", package = "shiny.tailwind"), 212 | #' # "app.R", overwrite = TRUE) 213 | #' 214 | #' # write a mini shiny UI 215 | #' writeLines(" 216 | #' library(shiny) 217 | #' div(class = \"page-div\", 218 | #' div(class = \"w-full text-center py-12\", 219 | #' h1(\"Hello World\") 220 | #' ) 221 | #' )", "app.R") 222 | #' 223 | #' tailwindcss <- NULL # can be set to the executable file 224 | #' compile_tailwindcss(infile, outfile, tailwindcss = tailwindcss) 225 | #' cat(paste(readLines(outfile)[1:20], collapse = "\n")) 226 | #' 227 | #' setwd(owd) 228 | #' } 229 | compile_tailwindcss <- function( 230 | infile, 231 | outfile, 232 | version = 4, 233 | config = "tailwind.config.js", 234 | watch = FALSE, 235 | minify = FALSE, 236 | content = ".", 237 | tailwindcss = NULL, 238 | verbose = FALSE 239 | ) { 240 | stopifnot(length(infile) == 1) 241 | stopifnot(length(outfile) == 1) 242 | 243 | if (!is_tailwindcss_installed(tailwindcss = tailwindcss)) { 244 | stop("Could not find an installation of tailwindcss!") 245 | } 246 | tailwindcss <- get_cli_executable(tailwindcss) 247 | 248 | # Create config if there is none 249 | if (!file.exists(config) && version == 3) { 250 | cat(sprintf( 251 | "Could not find %s, copying default from shiny.tailwind\n", 252 | config 253 | )) 254 | 255 | file.copy( 256 | system.file("default-tailwind.config.js", package = "shiny.tailwind"), 257 | config 258 | ) 259 | } 260 | 261 | if (version == 3) { 262 | cmd <- paste( 263 | tailwindcss, 264 | "--config", 265 | config, 266 | "--input", 267 | infile, 268 | "--output", 269 | outfile, 270 | if (watch) "--watch", 271 | if (minify) "--minify" 272 | ) 273 | } else { 274 | cmd <- paste( 275 | tailwindcss, 276 | "--input", 277 | infile, 278 | "--output", 279 | outfile, 280 | if (watch) "--watch", 281 | if (minify) "--minify" 282 | ) 283 | } 284 | if (verbose) cat(paste0("Running tailwindcss CLI command:\n ", cmd)) 285 | 286 | a <- try(system(cmd, intern = TRUE), silent = TRUE) 287 | if (inherits(a, "try-error")) { 288 | cat(gsub("\\\\r\\\\n", "\n", a)) 289 | stop("Could not execute tailwindcss CLI with error\n", a) 290 | } 291 | 292 | return() 293 | } 294 | -------------------------------------------------------------------------------- /R/twCheckboxGroupInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::checkboxGroupInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::checkboxGroupInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param input_class additional classes to be applied to the input element 7 | #' @param main_label_class additional classes to be applied to the main label 8 | #' @param inner_container_class additional classes to be applied to the container 9 | #' for each option 10 | #' @param disabled if the user should not be able to interact with the field 11 | #' @seealso [shiny::checkboxGroupInput()] 12 | #' 13 | #' @return a list with a `shiny.tag` class 14 | #' 15 | #' @export 16 | #' @examples 17 | #' shiny::checkboxGroupInput("id", "label", choices = c("A", "B")) 18 | #' twCheckboxGroupInput("id", "label", 19 | #' choices = c("A", "B"), 20 | #' width = "200px", disabled = c(TRUE, FALSE), 21 | #' container_class = "OUTER.CONTAINER", 22 | #' inner_container_class = c("INNER CONTAINER 1", "INNER CONTAINER 2"), 23 | #' label_class = c("LABEL 1", "LABEL 2"), 24 | #' input_class = "INPUT-ALL" 25 | #' ) 26 | #' 27 | #' # basic full shiny example 28 | #' library(shiny) 29 | #' 30 | #' ui <- fluidPage( 31 | #' use_tailwind(), 32 | #' twCheckboxGroupInput( 33 | #' "chks", "Check all that apply:", 34 | #' choices = c("This" = "a", "That" = "b", "None (disabled)" = "c"), 35 | #' disabled = c(FALSE, FALSE, TRUE), 36 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 37 | #' label_class = "font-serif text-gray-600", 38 | #' input_class = "rounded rounded-full text-pink-500 border-pink-200 focus:ring-pink-500", 39 | #' ), 40 | #' verbatimTextOutput("out") 41 | #' ) 42 | #' 43 | #' server <- function(input, output) { 44 | #' output$out <- renderText({ 45 | #' input$chks 46 | #' }) 47 | #' } 48 | #' 49 | #' if(interactive()) shiny::shinyApp(ui, server) 50 | twCheckboxGroupInput <- function( 51 | inputId, 52 | label, 53 | choices = NULL, 54 | selected = NULL, 55 | inline = FALSE, 56 | width = NULL, 57 | container_class = NULL, 58 | main_label_class = NULL, 59 | input_class = NULL, 60 | label_class = NULL, 61 | inner_container_class = NULL, 62 | disabled = FALSE 63 | ) { 64 | container_class <- paste( 65 | "form-group shiny-input-checkboxgroup shiny-input-container", 66 | if (inline) "shiny-input-container-inline", 67 | container_class 68 | ) 69 | input_class <- paste("form-check-input", input_class) 70 | label_class <- paste("form-check-label", label_class) 71 | main_label_class <- paste("control-label", main_label_class) 72 | inner_container_class <- paste("checkbox", inner_container_class) 73 | 74 | width <- shiny::validateCssUnit(width) 75 | 76 | if (length(disabled) == 1) disabled <- rep(disabled, length(choices)) 77 | if (length(input_class) == 1) input_class <- rep(input_class, length(choices)) 78 | if (length(label_class) == 1) label_class <- rep(label_class, length(choices)) 79 | if (length(inner_container_class) == 1) { 80 | inner_container_class <- rep(inner_container_class, length(choices)) 81 | } 82 | 83 | if (length(disabled) != length(choices)) { 84 | stop("'disabled' has to be either length 1 or the same length as 'choices'") 85 | } 86 | if (length(input_class) != length(choices)) { 87 | stop( 88 | "'input_class' has to be either NULL, length 1, or the same length as 'choices'" 89 | ) 90 | } 91 | if (length(label_class) != length(choices)) { 92 | stop( 93 | "'label_class' has to be either NULL, length 1, or the same length as 'choices'" 94 | ) 95 | } 96 | if (length(inner_container_class) != length(choices)) { 97 | stop( 98 | "'inner_container_class' has to be either NULL, length 1, or the same length as 'choices'" 99 | ) 100 | } 101 | 102 | label_id <- paste0(inputId, "-label") 103 | if (is.null(names(choices))) names(choices) <- choices 104 | nn <- names(choices) 105 | if (is.null(selected)) selected <- nn[[1]] 106 | 107 | shiny::div( 108 | id = inputId, 109 | role = "group", 110 | "aria-labelledby" = label_id, 111 | class = container_class, 112 | shiny::tags$label( 113 | class = main_label_class, 114 | id = label_id, 115 | "for" = inputId, 116 | label 117 | ), 118 | shiny::div( 119 | class = paste("shiny-options-group", if (inline) "flex flex-wrap"), 120 | lapply(seq_along(choices), function(i) { 121 | shiny::div( 122 | class = inner_container_class[[i]], 123 | shiny::tags$label( 124 | shiny::tags$input( 125 | class = input_class[[i]], 126 | type = "checkbox", 127 | name = inputId, 128 | value = choices[[i]], 129 | checked = if (choices[[i]] %in% selected) "checked" else NULL, 130 | disabled = if (disabled[[i]]) "" else NULL 131 | ), 132 | shiny::tags$span( 133 | class = label_class[[i]], 134 | names(choices)[[i]] 135 | ) 136 | ) 137 | ) 138 | }) 139 | ) 140 | ) 141 | } 142 | -------------------------------------------------------------------------------- /R/twCheckboxInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::checkboxInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::checkboxInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param input_class additional classes to be applied to the input element 7 | #' @param disabled if the user should not be able to interact with the field 8 | #' @param center if a margin of 0px !important should be applied, effectively 9 | #' removing bootstrap styling (if applied) to center the checkbox easier 10 | #' @seealso [shiny::checkboxInput()] 11 | #' 12 | #' @return a list with a `shiny.tag` class 13 | #' 14 | #' @export 15 | #' @examples 16 | #' shiny::checkboxInput("id", "label", value = FALSE) 17 | #' twCheckboxInput("id", "label", 18 | #' value = TRUE, width = "200px", disabled = TRUE, 19 | #' container_class = "CONTAINER", label_class = "LABEL", input_class = "INPUT" 20 | #' ) 21 | #' 22 | #' # basic full shiny example 23 | #' library(shiny) 24 | #' 25 | #' ui <- fluidPage( 26 | #' use_tailwind(), 27 | #' twCheckboxInput( 28 | #' "chk", "Check me!", 29 | #' value = TRUE, 30 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 31 | #' label_class = "font-serif text-gray-600", 32 | #' input_class = "text-pink-500 focus:ring-pink-500", 33 | #' center = TRUE 34 | #' ), 35 | #' verbatimTextOutput("out") 36 | #' ) 37 | #' 38 | #' server <- function(input, output) { 39 | #' output$out <- renderText({ 40 | #' input$chk 41 | #' }) 42 | #' } 43 | #' 44 | #' if(interactive()) shiny::shinyApp(ui, server) 45 | twCheckboxInput <- function( 46 | inputId, 47 | label, 48 | value = FALSE, 49 | width = NULL, 50 | disabled = FALSE, 51 | container_class = NULL, 52 | label_class = NULL, 53 | input_class = NULL, 54 | center = FALSE 55 | ) { 56 | container_class <- paste("form-check", container_class) 57 | input_class <- paste("form-check-input", input_class) 58 | label_class <- paste("form-check-label", label_class) 59 | 60 | width <- shiny::validateCssUnit(width) 61 | 62 | res <- shiny::div( 63 | class = container_class, 64 | style = if (!is.null(width)) paste0("width: ", width) else NULL, 65 | shiny::tags$input( 66 | type = "checkbox", 67 | id = inputId, 68 | style = if (center) "margin: 0px !important;" else NULL, 69 | checked = if (value) "" else NULL, 70 | disabled = if (disabled) "" else NULL, 71 | class = input_class 72 | ), 73 | shiny::tags$label( 74 | class = label_class, 75 | "for" = inputId, 76 | label 77 | ) 78 | ) 79 | 80 | return(res) 81 | } 82 | -------------------------------------------------------------------------------- /R/twDateInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::dateInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::dateInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param input_class additional classes to be applied to the input element 7 | #' @param label_after_input TRUE/FALSE if the label should be put after the 8 | #' input box. Default is FALSE. Useful for special cases (floating labels), 9 | #' c.f. 04-shiny-inputs example app. 10 | #' 11 | #' @seealso [shiny::dateInput()] 12 | #' 13 | #' @return a list with a `shiny.tag` class 14 | #' 15 | #' @export 16 | #' @examples 17 | #' shiny::dateInput("date", "A Date") 18 | #' twDateInput("date", "A Date", 19 | #' container_class = "CONTAINER", label_class = "LABEL", input_class = "INPUT" 20 | #' ) 21 | #' 22 | #' # basic full shiny example 23 | #' library(shiny) 24 | #' 25 | #' ui <- fluidPage( 26 | #' use_tailwind(), 27 | #' twDateInput( 28 | #' "date", "A Date", 29 | #' # Apply tailwind classes 30 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 31 | #' label_class = "font-mono text-gray-600", 32 | #' input_class = "drop-shadow-lg text-gray-600 font-mono rounded-md border-amber-400" 33 | #' ), 34 | #' verbatimTextOutput("value") 35 | #' ) 36 | #' 37 | #' server <- function(input, output) { 38 | #' output$value <- renderText({ 39 | #' as.character(input$date) 40 | #' }) 41 | #' } 42 | #' 43 | #' if(interactive()) shiny::shinyApp(ui, server) 44 | twDateInput <- function( 45 | inputId, 46 | label, 47 | value = NULL, 48 | min = NULL, 49 | max = NULL, 50 | format = "yyyy-mm-dd", 51 | startview = "month", 52 | weekstart = 0, 53 | language = "en", 54 | width = NULL, 55 | autoclose = TRUE, 56 | datesdisabled = NULL, 57 | daysofweekdisabled = NULL, 58 | container_class = NULL, 59 | label_class = NULL, 60 | input_class = NULL, 61 | label_after_input = FALSE 62 | ) { 63 | res <- shiny::dateInput( 64 | inputId = inputId, 65 | label = label, 66 | value = value, 67 | min = min, 68 | max = max, 69 | format = format, 70 | startview = startview, 71 | weekstart = weekstart, 72 | language = language, 73 | width = width, 74 | autoclose = autoclose, 75 | datesdisabled = datesdisabled, 76 | daysofweekdisabled = daysofweekdisabled 77 | ) 78 | res$attribs$class <- paste(res$attribs$class, container_class) 79 | res$children[[1]]$attribs$class <- paste( 80 | res$children[[1]]$attribs$class, 81 | label_class 82 | ) 83 | res$children[[2]]$attribs$class <- paste( 84 | res$children[[2]]$attribs$class, 85 | input_class 86 | ) 87 | 88 | if (label_after_input) { 89 | tmp <- res$children[[1]] 90 | res$children[[1]] <- res$children[[2]] 91 | res$children[[2]] <- tmp 92 | } 93 | 94 | res 95 | } 96 | -------------------------------------------------------------------------------- /R/twDateRangeInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::dateRangeInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::dateRangeInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param input_class additional classes to be applied to the input element 7 | #' @param sep_class additional classes to be applied to the separator element 8 | #' @param label_after_input TRUE/FALSE if the label should be put after the 9 | #' input box. Default is FALSE. Useful for special cases (floating labels), 10 | #' c.f. 04-shiny-inputs example app. 11 | #' 12 | #' @seealso [shiny::dateRangeInput()] 13 | #' 14 | #' @return a list with a `shiny.tag` class 15 | #' 16 | #' @export 17 | #' @examples 18 | #' shiny::dateRangeInput("date", "A Date") 19 | #' twDateRangeInput( 20 | #' "date", "A Date Range", 21 | #' container_class = "CONTAINER", label_class = "LABEL", 22 | #' input_class = "INPUT", sep_class = "SEP" 23 | #' ) 24 | #' 25 | #' # basic full shiny example 26 | #' library(shiny) 27 | #' 28 | #' ui <- fluidPage( 29 | #' use_tailwind(), 30 | #' twDateRangeInput( 31 | #' "date", "A Date", 32 | #' # Apply tailwind classes 33 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 34 | #' label_class = "font-mono text-gray-600", 35 | #' input_class = "drop-shadow-lg text-gray-600 font-mono rounded-md border-amber-400", 36 | #' sep_class = "bg-amber-600 text-white font-bold font-mono" 37 | #' ), 38 | #' verbatimTextOutput("value") 39 | #' ) 40 | #' 41 | #' server <- function(input, output) { 42 | #' output$value <- renderText({ 43 | #' as.character(input$date) 44 | #' }) 45 | #' } 46 | #' 47 | #' if(interactive()) shiny::shinyApp(ui, server) 48 | twDateRangeInput <- function( 49 | inputId, 50 | label, 51 | start = NULL, 52 | end = NULL, 53 | min = NULL, 54 | max = NULL, 55 | format = "yyyy-mm-dd", 56 | startview = "month", 57 | weekstart = 0, 58 | language = "en", 59 | separator = " to ", 60 | width = NULL, 61 | autoclose = TRUE, 62 | container_class = NULL, 63 | label_class = NULL, 64 | input_class = NULL, 65 | sep_class = NULL, 66 | label_after_input = FALSE 67 | ) { 68 | res <- shiny::dateRangeInput( 69 | inputId = inputId, 70 | label = label, 71 | start = start, 72 | end = end, 73 | min = min, 74 | max = max, 75 | format = format, 76 | startview = startview, 77 | weekstart = weekstart, 78 | language = language, 79 | separator = separator, 80 | width = width, 81 | autoclose = autoclose 82 | ) 83 | 84 | res$attribs$class <- paste(res$attribs$class, container_class) 85 | res$children[[1]]$attribs$class <- paste( 86 | res$children[[1]]$attribs$class, 87 | label_class 88 | ) 89 | 90 | res$children[[2]]$children[[1]]$attribs$class <- paste( 91 | res$children[[2]]$children[[1]]$attribs$class, 92 | input_class 93 | ) 94 | res$children[[2]]$children[[2]]$attribs$class <- paste( 95 | res$children[[2]]$children[[2]]$attribs$class, 96 | sep_class 97 | ) 98 | res$children[[2]]$children[[3]]$attribs$class <- paste( 99 | res$children[[2]]$children[[3]]$attribs$class, 100 | input_class 101 | ) 102 | 103 | if (label_after_input) { 104 | tmp <- res$children[[1]] 105 | res$children[[1]] <- res$children[[2]] 106 | res$children[[2]] <- tmp 107 | } 108 | 109 | res 110 | } 111 | -------------------------------------------------------------------------------- /R/twFileInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::fileInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::fileInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param select_class additional classes to be applied to the select elements 7 | #' @param button_class additional classes to be applied to the upload button 8 | #' @param progress_class additional classes to be applied to the progress bar (ie color) 9 | #' 10 | #' @seealso [shiny::fileInput()] 11 | #' 12 | #' @return a list with a `shiny.tag` class 13 | #' 14 | #' @export 15 | #' @examples 16 | #' shiny::fileInput("id", "label", 17 | #' multiple = TRUE, accept = c(".csv", ".rds"), 18 | #' width = "200px", buttonLabel = "Upload", placeholder = "Here" 19 | #' ) 20 | #' twFileInput("id", "label", 21 | #' multiple = TRUE, accept = c(".csv", ".rds"), 22 | #' width = "200px", buttonLabel = "Upload", placeholder = "Here", 23 | #' container_class = "CONTAINER", label_class = "LABEL", 24 | #' select_class = "SELECT" 25 | #' ) 26 | #' 27 | #' # basic full shiny example 28 | #' library(shiny) 29 | #' ui <- fluidPage( 30 | #' use_tailwind(), 31 | #' twFileInput( 32 | #' inputId = "file", label = "Upload", multiple = TRUE, 33 | #' buttonLabel = "Upload", placeholder = "Nothing selected", 34 | #' container_class = "shadow-md rounded-md bg-gray-50 m-2 p-2 w-96", 35 | #' label_class = "font-serif text-red-800", 36 | #' select_class = "font-mono font-bold text-red-800 rounded-r-lg", 37 | #' button_class = paste( 38 | #' "bg-red-800 border-red-800 hover:bg-red-700", 39 | #' "hover:border-red-700 text-white hover:text-gray-50" 40 | #' ), 41 | #' progress_class = "bg-red-800" 42 | #' ), 43 | #' verbatimTextOutput("data") 44 | #' ) 45 | #' 46 | #' server <- function(input, output) { 47 | #' output$data <- renderText({ 48 | #' paste(capture.output(str(input$file)), collapse = "\n") 49 | #' }) 50 | #' } 51 | #' 52 | #' if(interactive()) shiny::shinyApp(ui, server) 53 | twFileInput <- function( 54 | inputId, 55 | label, 56 | multiple = FALSE, 57 | accept = NULL, 58 | width = NULL, 59 | buttonLabel = "Browse...", 60 | placeholder = "No file selected", 61 | container_class = NULL, 62 | label_class = NULL, 63 | select_class = NULL, 64 | button_class = NULL, 65 | progress_class = NULL 66 | ) { 67 | container_class <- paste("twFileInput form-group", container_class) 68 | label_class <- paste("control-label", label_class) 69 | select_class <- paste("form-control", select_class) 70 | button_class <- paste("btn btn-default btn-file", button_class) 71 | progress_class <- paste("progress-bar", progress_class) 72 | 73 | width <- shiny::validateCssUnit(width) 74 | 75 | label_id <- paste0(inputId, "-label") 76 | 77 | shiny::div( 78 | style = if (!is.null(width)) paste0("width:", width, ";") else NULL, 79 | class = container_class, 80 | shiny::tags$label( 81 | id = label_id, 82 | "for" = inputId, 83 | class = label_class, 84 | label 85 | ), 86 | shiny::div( 87 | class = "input-group", 88 | shiny::tags$label( 89 | class = "input-group-btn btn-file", 90 | shiny::tags$span( 91 | class = button_class, 92 | buttonLabel, 93 | shiny::tags$input( 94 | id = inputId, 95 | name = inputId, 96 | type = "file", 97 | style = "position: absolute !important; top: -99999px !important; left: -99999px !important;", 98 | multiple = if (multiple) "multiple" else NULL, 99 | accept = if (length(accept) > 0) paste(accept, collapse = ",") else 100 | NULL 101 | ) 102 | ) 103 | ), 104 | shiny::tags$input( 105 | type = "text", 106 | placeholder = placeholder, 107 | readonly = "readonly", 108 | class = select_class 109 | ) 110 | ), 111 | shiny::div( 112 | id = paste0(inputId, "_progress"), 113 | class = "progress active shiny-file-input-progress", 114 | shiny::div(class = progress_class) 115 | ) 116 | ) 117 | } 118 | -------------------------------------------------------------------------------- /R/twNumericInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::numericInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::numericInput 4 | #' @param placeholder Placeholder text for numeric input. Disappears after input 5 | #' @param container_class additional classes to be applied to the container 6 | #' @param label_class additional classes to be applied to the label 7 | #' @param input_class additional classes to be applied to the input element 8 | #' @param disabled if the user should not be able to interact with the field 9 | #' @param label_after_input TRUE/FALSE if the label should be put after the 10 | #' input box. Default is FALSE. Useful for special cases (floating labels), 11 | #' c.f. 04-shiny-inputs example app. 12 | #' 13 | #' @seealso [shiny::numericInput()] 14 | #' 15 | #' @return a list with a `shiny.tag` class 16 | #' 17 | #' @export 18 | #' @examples 19 | #' shiny::numericInput("number", "A Number", 42, min = 10, max = 100, step = 13, width = "200px") 20 | #' twNumericInput("number", "A Number", 42, 21 | #' min = 10, max = 100, step = 13, width = "200px", 22 | #' container_class = "CONTAINER", label_class = "LABEL", input_class = "INPUT" 23 | #' ) 24 | #' 25 | #' # basic full shiny example 26 | #' library(shiny) 27 | #' 28 | #' ui <- fluidPage( 29 | #' use_tailwind(), 30 | #' twNumericInput( 31 | #' "number", "A Number", 123456, 32 | #' # Apply tailwind classes 33 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 34 | #' label_class = "font-mono text-gray-600", 35 | #' input_class = "drop-shadow-lg text-gray-600 font-mono rounded-md border-amber-400" 36 | #' ), 37 | #' verbatimTextOutput("value") 38 | #' ) 39 | #' 40 | #' server <- function(input, output) { 41 | #' output$value <- renderText({ 42 | #' input$number 43 | #' }) 44 | #' } 45 | #' 46 | #' if(interactive()) shiny::shinyApp(ui, server) 47 | twNumericInput <- function( 48 | inputId, 49 | label, 50 | value, 51 | min = NA, 52 | max = NA, 53 | step = NA, 54 | width = NULL, 55 | placeholder = "", 56 | disabled = FALSE, 57 | container_class = NULL, 58 | label_class = NULL, 59 | input_class = NULL, 60 | label_after_input = FALSE 61 | ) { 62 | container_class <- paste("form-group", container_class) 63 | input_class <- paste("form-control", input_class) 64 | label_class <- paste("form-label", label_class) 65 | 66 | width <- shiny::validateCssUnit(width) 67 | 68 | html_label <- shiny::tags$label( 69 | class = label_class, 70 | "for" = inputId, 71 | label 72 | ) 73 | 74 | res <- shiny::div( 75 | class = container_class, 76 | style = if (!is.null(width)) paste0("width:", width) else NULL, 77 | if (!label_after_input) html_label, 78 | shiny::tags$input( 79 | type = "number", 80 | id = inputId, 81 | value = if (!is.null(value)) value else NULL, 82 | min = if (!is.null(min)) min else NULL, 83 | max = if (!is.null(max)) max else NULL, 84 | step = if (!is.null(step)) step else NULL, 85 | disabled = if (disabled) "" else NULL, 86 | placeholder = placeholder, 87 | class = input_class 88 | ), 89 | if (label_after_input) html_label 90 | ) 91 | 92 | res 93 | } 94 | -------------------------------------------------------------------------------- /R/twSelectInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::selectInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::selectInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param select_class additional classes to be applied to the select elements 7 | #' 8 | #' @seealso [shiny::selectInput()] 9 | #' 10 | #' @return a list with a `shiny.tag` class 11 | #' 12 | #' @export 13 | #' @examples 14 | #' shiny::selectInput("id", "label", c("A" = "a", "B" = "b", "C" = "c"), 15 | #' selected = c("a", "b"), width = "200px", 16 | #' multiple = TRUE 17 | #' ) 18 | #' twSelectInput("id", "label", c("A" = "a", "B" = "b", "C" = "c"), 19 | #' selected = c("a", "b"), width = "200px", 20 | #' multiple = TRUE, selectize = TRUE, 21 | #' container_class = "CONTAINER", label_class = "LABEL", 22 | #' select_class = "SELECT" 23 | #' ) 24 | #' 25 | #' # basic full shiny example 26 | #' library(shiny) 27 | #' 28 | #' ui <- fluidPage( 29 | #' use_tailwind(), 30 | #' twSelectInput( 31 | #' "variable", "Variable to select:", 32 | #' c("Cylinders" = "cyl", "Transmission" = "am", "Gears" = "gear"), 33 | #' multiple = TRUE, 34 | #' # Apply tailwind classes 35 | #' container_class = "shadow-md rounded-md bg-gray-50 m-4 p-2 w-72", 36 | #' label_class = "font-serif", 37 | #' select_class = "font-mono font-bold text-red-800 rounded-md bg-stone-50" 38 | #' ), 39 | #' tableOutput("data") 40 | #' ) 41 | #' 42 | #' server <- function(input, output) { 43 | #' output$data <- renderTable( 44 | #' { 45 | #' mtcars[, c("mpg", input$variable), drop = FALSE] 46 | #' }, 47 | #' rownames = TRUE 48 | #' ) 49 | #' } 50 | #' 51 | #' if(interactive()) shiny::shinyApp(ui, server) 52 | twSelectInput <- function( 53 | inputId, 54 | label, 55 | choices, 56 | selected = NULL, 57 | multiple = FALSE, 58 | selectize = TRUE, 59 | width = NULL, 60 | size = NULL, 61 | container_class = NULL, 62 | label_class = NULL, 63 | select_class = NULL 64 | ) { 65 | if (selectize && !is.null(size)) { 66 | stop("'size' argument is incompatible with 'selectize=TRUE'.") 67 | } 68 | 69 | container_class <- paste("block twSelectInput form-group", container_class) 70 | label_class <- paste("control-label", label_class) 71 | select_class <- paste("block form-control", select_class) 72 | 73 | width <- shiny::validateCssUnit(width) 74 | 75 | if (is.null(names(choices))) names(choices) <- choices 76 | nn <- names(choices) 77 | if (is.null(selected)) selected <- nn[[1]] 78 | 79 | label_id <- paste0(inputId, "-label") 80 | res <- shiny::div( 81 | class = container_class, 82 | style = if (!is.null(width)) paste0("width: ", width, ";") else NULL, 83 | size = if (!is.null(size)) size else NULL, 84 | shiny::tags$label( 85 | class = label_class, 86 | id = label_id, 87 | "for" = inputId, 88 | label 89 | ), 90 | shiny::div( 91 | shiny::tags$select( 92 | id = inputId, 93 | class = select_class, 94 | multiple = if (multiple) "multiple" else NULL, 95 | lapply(seq_along(choices), function(i) { 96 | choice <- choices[[i]] 97 | shiny::tags$option( 98 | value = choice, 99 | selected = ifelse(choice %in% selected, "selected", NULL), 100 | nn[[i]] 101 | ) 102 | }) 103 | ), 104 | if (selectize) { 105 | shiny::tags$script( 106 | type = "application/json", 107 | "data-for" = inputId, 108 | "data-nonempty" = "", 109 | '{"plugins":["selectize-plugin-a11y"]}' 110 | ) 111 | } 112 | ) 113 | ) 114 | 115 | if (selectize) { 116 | attr(res, "html_dependencies") <- attr( 117 | shiny::selectInput("a", "a", "a", selectize = TRUE), 118 | "html_dependencies" 119 | ) 120 | } 121 | res 122 | } 123 | -------------------------------------------------------------------------------- /R/twSelectizeInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::selectizeInput()`] but allowing for more classes 2 | #' 3 | #' Note that the colors for the slider bar can be customized by overriding the 4 | #' `irs` class. c.f. 05-apply-directive example app 5 | #' 6 | #' @inheritParams shiny::selectizeInput 7 | #' @param container_class additional classes to be applied to the container 8 | #' @param label_class additional classes to be applied to the label 9 | #' @param input_class additional classes to be applied to the input element 10 | #' @param label_after_input TRUE/FALSE if the label should be put after the 11 | #' input box. Default is FALSE. Useful for special cases (floating labels), 12 | #' c.f. 05-apply-directive example app. 13 | #' 14 | #' @seealso [shiny::selectizeInput()] 15 | #' 16 | #' @return a list with a `shiny.tag` class 17 | #' 18 | #' @export 19 | #' @examples 20 | #' shiny::selectizeInput("selectize", "A Selection", choice = c("A", "B")) 21 | #' twSelectizeInput("selectize", "A Selection", 22 | #' choice = c("A", "B"), 23 | #' container_class = "CONTAINER", label_class = "LABEL", 24 | #' input_class = "INPUT" 25 | #' ) 26 | #' 27 | #' # basic full shiny example 28 | #' library(shiny) 29 | #' 30 | #' ui <- fluidPage( 31 | #' use_tailwind(), 32 | #' twSelectizeInput( 33 | #' "values", "A Selection", 34 | #' choice = c("A", "B"), multiple = TRUE, 35 | #' # Apply tailwind classes 36 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 37 | #' label_class = "font-mono text-gray-600", 38 | #' input_class = "drop-shadow-lg text-gray-600 font-mono rounded-md border-amber-400" 39 | #' ), 40 | #' verbatimTextOutput("value") 41 | #' ) 42 | #' 43 | #' server <- function(input, output) { 44 | #' output$value <- renderText({ 45 | #' as.character(input$values) 46 | #' }) 47 | #' } 48 | #' 49 | #' if(interactive()) shiny::shinyApp(ui, server) 50 | twSelectizeInput <- function( 51 | inputId, 52 | ..., 53 | options = NULL, 54 | width = NULL, 55 | container_class = NULL, 56 | label_class = NULL, 57 | input_class = NULL, 58 | label_after_input = FALSE 59 | ) { 60 | res <- shiny::selectizeInput( 61 | inputId = inputId, 62 | ..., 63 | options = options, 64 | width = width 65 | ) 66 | 67 | res$attribs$class <- paste(res$attribs$class, container_class) 68 | res$children[[1]]$attribs$class <- paste( 69 | res$children[[1]]$attribs$class, 70 | label_class 71 | ) 72 | res$children[[2]]$attribs$class <- paste( 73 | res$children[[2]]$attribs$class, 74 | input_class 75 | ) 76 | 77 | if (label_after_input) { 78 | tmp <- res$children[[1]] 79 | res$children[[1]] <- res$children[[2]] 80 | res$children[[2]] <- tmp 81 | } 82 | 83 | res 84 | } 85 | -------------------------------------------------------------------------------- /R/twSliderInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::sliderInput()`] but allowing for more classes 2 | #' 3 | #' Note that the colors for the slider bar can be customized by overriding the 4 | #' `irs` class. c.f. 05-apply-directive example app 5 | #' 6 | #' @inheritParams shiny::sliderInput 7 | #' @param container_class additional classes to be applied to the container 8 | #' @param label_class additional classes to be applied to the label 9 | #' @param input_class additional classes to be applied to the input element 10 | #' @param label_after_input TRUE/FALSE if the label should be put after the 11 | #' input box. Default is FALSE. Useful for special cases (floating labels), 12 | #' c.f. 05-apply-directive example app. 13 | #' 14 | #' @seealso [shiny::sliderInput()] 15 | #' 16 | #' @return a list with a `shiny.tag` class 17 | #' 18 | #' @export 19 | #' @examples 20 | #' shiny::sliderInput("values", "A Range", min = 0, max = 100, value = 75) 21 | #' twSliderInput("values", "A Range", 22 | #' min = 0, max = 100, value = 75, 23 | #' container_class = "CONTAINER", label_class = "LABEL", 24 | #' input_class = "INPUT" 25 | #' ) 26 | #' 27 | #' # basic full shiny example 28 | #' library(shiny) 29 | #' 30 | #' ui <- fluidPage( 31 | #' use_tailwind(), 32 | #' twSliderInput( 33 | #' "values", "A Range", 34 | #' min = 0, max = 100, value = 75, 35 | #' # Apply tailwind classes 36 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 37 | #' label_class = "font-mono text-gray-600", 38 | #' input_class = "drop-shadow-lg text-gray-600 font-mono rounded-md border-amber-400" 39 | #' ), 40 | #' verbatimTextOutput("value") 41 | #' ) 42 | #' 43 | #' server <- function(input, output) { 44 | #' output$value <- renderText({ 45 | #' as.character(input$date) 46 | #' }) 47 | #' } 48 | #' 49 | #' if(interactive()) shiny::shinyApp(ui, server) 50 | twSliderInput <- function( 51 | inputId, 52 | label, 53 | min, 54 | max, 55 | value, 56 | step = NULL, 57 | round = FALSE, 58 | ticks = TRUE, 59 | animate = FALSE, 60 | width = NULL, 61 | sep = ",", 62 | pre = NULL, 63 | post = NULL, 64 | timeFormat = NULL, 65 | timezone = NULL, 66 | dragRange = TRUE, 67 | container_class = NULL, 68 | label_class = NULL, 69 | input_class = NULL, 70 | label_after_input = FALSE 71 | ) { 72 | res <- shiny::sliderInput( 73 | inputId = inputId, 74 | label = label, 75 | min = min, 76 | max = max, 77 | value = value, 78 | step = step, 79 | round = round, 80 | ticks = ticks, 81 | animate = animate, 82 | width = width, 83 | sep = sep, 84 | pre = pre, 85 | post = post, 86 | timeFormat = timeFormat, 87 | timezone = timezone, 88 | dragRange = dragRange 89 | ) 90 | 91 | res$attribs$class <- paste(res$attribs$class, container_class) 92 | res$children[[1]]$attribs$class <- paste( 93 | res$children[[1]]$attribs$class, 94 | label_class 95 | ) 96 | res$children[[2]]$attribs$class <- paste( 97 | res$children[[2]]$attribs$class, 98 | input_class 99 | ) 100 | 101 | if (label_after_input) { 102 | tmp <- res$children[[1]] 103 | res$children[[1]] <- res$children[[2]] 104 | res$children[[2]] <- tmp 105 | } 106 | 107 | res 108 | } 109 | -------------------------------------------------------------------------------- /R/twTabContent.R: -------------------------------------------------------------------------------- 1 | #' Creates the Content Elements of Tabs 2 | #' 3 | #' This function only creates the content elements of tabs, the navigation 4 | #' elements can by created by the [twTabNav()] function. 5 | #' A full example is included in the example 06-sidebar-dashboard. 6 | #' 7 | #' @param ... UI element to include in the tab 8 | #' @param ids a list of reference IDs to the navigation elements. This will be 9 | #' overridden by ID fields of the `...` values (if given). Default is 10 | #' `twTab-{i}-content` (note that the ids must end with `-content` where the 11 | #' part before matches the IDs of the navigation elements. Note that this 12 | #' option is only needed when multiple tab systems are used within a page or 13 | #' when the elements of the `twTabContent`s are given out of order. 14 | #' @param container_class additional classes to be applied to the container 15 | #' @param content_class additional classes to be applied to each content container 16 | #' @param tabsetid an optional class that is added to the container to be 17 | #' identify and linked the tabsets. Must match the `tabsetid` of [twTabContent()]. 18 | #' Can be an arbitrary text, but due to it being a class, make sure to not have 19 | #' class-clashes (eg `"button"` would be a bad idea). This allows to have 20 | #' multiple nested tabsets. See also Example 09-nested-tabsets. 21 | #' 22 | #' @details Note that contrary how [shiny::tabPanel()] constructs a tab page, 23 | #' these funtions (`twTabContent()` and [twTabNav()]) construct navigation and 24 | #' content independently, allowing more flexibility. 25 | #' 26 | #' The active elements all have either a `twTab-active` or `twTabContent-active` 27 | #' CSS class if their styling needs to be overriden (see also the example). 28 | #' 29 | #' @return a list with a `shiny.tag` class 30 | #' @export 31 | #' 32 | #' @seealso [twTabNav()] 33 | #' @examples 34 | #' twTabContent( 35 | #' div(h1("First Tab"), shiny::plotOutput("plot1")), 36 | #' div(h1("Second Tab"), shiny::plotOutput("plot2")) 37 | #' ) 38 | #' 39 | #' ############################################################################# 40 | #' # Example App 41 | #' 42 | #' library(shiny) 43 | #' # basic Tabs 44 | #' 45 | #' ui_basic <- shiny::div( 46 | #' shiny::h1("Completely Unstyled Tabs..."), 47 | #' twTabNav( 48 | #' shiny::div("Tab 1 (click me)"), 49 | #' shiny::div("Tab 2 (click me)") 50 | #' ), 51 | #' twTabContent( 52 | #' shiny::div(shiny::h1("First Tab"), shiny::plotOutput("plot1")), 53 | #' shiny::div(shiny::h1("Second Tab"), shiny::plotOutput("plot2")) 54 | #' ) 55 | #' ) 56 | #' 57 | #' server <- function(input, output, session) { 58 | #' output$plot1 <- shiny::renderPlot({ 59 | #' print("Plot 1") 60 | #' plot(1:10, rnorm(10)) 61 | #' }) 62 | #' output$plot2 <- shiny::renderPlot({ 63 | #' print("Plot 2") 64 | #' plot(1:100, rnorm(100)) 65 | #' }) 66 | #' } 67 | #' 68 | #' if(interactive()) shiny::shinyApp(ui_basic, server) 69 | #' 70 | #' ############################################################################# 71 | #' # Styled App 72 | #' 73 | #' ui_styled <- shiny::div( 74 | #' class = "h-screen bg-white overflow-hidden flex", 75 | #' shiny.tailwind::use_tailwind(), 76 | #' twTabNav( 77 | #' shiny::div(icon("database"), shiny::span("Tab One", class = "pl-2")), 78 | #' shiny::div(icon("server"), shiny::span("Tab Two", class = "pl-2")), 79 | #' container_class = "h-full pt-10 pt-2 bg-indigo-900", 80 | #' tab_class = "cursor-pointer py-2 px-4 my-4 w-full text-white hover:bg-indigo-700" 81 | #' ), 82 | #' twTabContent( 83 | #' shiny::div( 84 | #' shiny::h1("First Tab", 85 | #' class = "p-10 text-center font-sans text-8xl font-extrabold text-slate-800" 86 | #' ), 87 | #' shiny::plotOutput("plot1") 88 | #' ), 89 | #' shiny::div( 90 | #' shiny::h1("Second Tab", 91 | #' class = "p-10 text-center font-sans text-8xl font-extrabold text-slate-800" 92 | #' ), 93 | #' shiny::plotOutput("plot2") 94 | #' ), 95 | #' container_class = "flex-1 bg-indigo-50" 96 | #' ) 97 | #' ) 98 | #' 99 | #' if(interactive()) shiny::shinyApp(ui_styled, server) 100 | #' 101 | #' @export 102 | twTabContent <- function( 103 | ..., 104 | ids = NULL, 105 | container_class = NULL, 106 | content_class = NULL, 107 | tabsetid = "tabSet1" 108 | ) { 109 | dots <- list(...) 110 | 111 | if (is.null(ids)) ids <- paste0("twTab-", seq_along(dots)) 112 | 113 | if (length(dots) != length(ids)) { 114 | stop( 115 | "ids has to have the same length as the provided tab navigation elements" 116 | ) 117 | } 118 | 119 | shiny::div( 120 | class = container_class, 121 | lapply(seq_along(dots), function(i) { 122 | id <- dots[[i]]$attribs$id 123 | if (is.null(id)) id <- ids[[i]] 124 | 125 | idc <- strsplit(id, "-")[[1]] 126 | if (idc[length(idc)] != "content") id <- paste0(id, "-content") 127 | 128 | shiny::div( 129 | class = paste( 130 | "twTabContent", 131 | paste0(tabsetid, "-content"), 132 | if (i == 1) "twTabContent-active", 133 | content_class 134 | ), 135 | style = if (i == 1) "display: block;" else "display: none;", 136 | id = id, 137 | dots[[i]] 138 | ) 139 | }) 140 | ) 141 | } 142 | -------------------------------------------------------------------------------- /R/twTabNav.R: -------------------------------------------------------------------------------- 1 | #' Creates the Navigation Element of Tabs 2 | #' 3 | #' This function creates only the navigation elements of tabs, the content 4 | #' elements can be created by the [twTabContent()] function. 5 | #' A full example is included in the example 06-sidebar-dashboard. 6 | #' 7 | #' @param ... titles for the navigation elements 8 | #' @param ids a list of reference IDs for each tab. This will be overridden by 9 | #' ID fields of the `...` values (if given). Default is `twTab-{i}`. Note that 10 | #' this option is only needed when multiple tab systems are used within a page 11 | #' or when the elements of the `twTabContent`s are given out of order. 12 | #' @param container_class additional classes to be applied to the container 13 | #' @param tab_class additional classes to be applied to each tab container 14 | #' @param tabsetid an optional class that is added to the container to be 15 | #' identify and linked the tabsets. Must match the `tabsetid` of [twTabContent()]. 16 | #' Can be an arbitrary text, but due to it being a class, make sure to not have 17 | #' class-clashes (eg `"button"` would be a bad idea). This allows to have 18 | #' multiple nested tabsets. See also Example 09-nested-tabsets. 19 | #' 20 | #' 21 | #' @details Note that contrary how [shiny::tabPanel()] constructs a tab page, 22 | #' these funtions (`twTabContent()` and [twTabNav()]) construct navigation and 23 | #' content independently, allowing more flexibility. 24 | #' 25 | #' The active elements all have either a `twTab-active` or `twTabContent-active` 26 | #' CSS class if their styling needs to be overriden (see also the example). 27 | #' 28 | #' @return a list with a `shiny.tag` class 29 | #' @export 30 | #' 31 | #' @seealso [twTabContent()] 32 | #' 33 | #' @examples 34 | #' twTabNav( 35 | #' div("Tab 1", id = "firstTab"), 36 | #' div("Tab 2", id = "secondTab"), 37 | #' container_class = "CONTAINER", tab_class = "TAB" 38 | #' ) 39 | #' 40 | #' ############################################################################# 41 | #' # Example App 42 | #' 43 | #' library(shiny) 44 | #' # basic Tabs 45 | #' 46 | #' ui_basic <- shiny::div( 47 | #' shiny::h1("Completely Unstyled Tabs..."), 48 | #' twTabNav( 49 | #' shiny::div("Tab 1 (click me)"), 50 | #' shiny::div("Tab 2 (click me)") 51 | #' ), 52 | #' twTabContent( 53 | #' shiny::div(shiny::h1("First Tab"), shiny::plotOutput("plot1")), 54 | #' shiny::div(shiny::h1("Second Tab"), shiny::plotOutput("plot2")) 55 | #' ) 56 | #' ) 57 | #' 58 | #' server <- function(input, output, session) { 59 | #' output$plot1 <- shiny::renderPlot({ 60 | #' print("Plot 1") 61 | #' plot(1:10, rnorm(10)) 62 | #' }) 63 | #' output$plot2 <- shiny::renderPlot({ 64 | #' print("Plot 2") 65 | #' plot(1:100, rnorm(100)) 66 | #' }) 67 | #' } 68 | #' 69 | #' if(interactive()) shiny::shinyApp(ui_basic, server) 70 | #' 71 | #' ############################################################################# 72 | #' # Styled App 73 | #' 74 | #' ui_styled <- div( 75 | #' class = "h-screen bg-white overflow-hidden flex", 76 | #' shiny.tailwind::use_tailwind(), 77 | #' twTabNav( 78 | #' div(icon("database"), span("Tab One", class = "pl-2")), 79 | #' div(icon("server"), span("Tab Two", class = "pl-2")), 80 | #' container_class = "h-full pt-10 pt-2 bg-indigo-900", 81 | #' tab_class = "cursor-pointer py-2 px-4 my-4 w-full text-white hover:bg-indigo-700" 82 | #' ), 83 | #' twTabContent( 84 | #' div( 85 | #' h1("First Tab", 86 | #' class = "p-10 text-center font-sans text-8xl font-extrabold text-slate-800" 87 | #' ), 88 | #' plotOutput("plot1") 89 | #' ), 90 | #' div( 91 | #' h1("Second Tab", 92 | #' class = "p-10 text-center font-sans text-8xl font-extrabold text-slate-800" 93 | #' ), 94 | #' plotOutput("plot2") 95 | #' ), 96 | #' container_class = "flex-1 bg-indigo-50" 97 | #' ) 98 | #' ) 99 | #' 100 | #' if(interactive()) shiny::shinyApp(ui_styled, server) 101 | #' 102 | twTabNav <- function( 103 | ..., 104 | ids = NULL, 105 | container_class = NULL, 106 | tab_class = NULL, 107 | tabsetid = "tabSet1" 108 | ) { 109 | dots <- list(...) 110 | 111 | if (is.null(ids)) ids <- paste0("twTab-", seq_along(dots)) 112 | 113 | if (length(dots) != length(ids)) { 114 | stop( 115 | "ids has to have the same length as the provided tab navigation elements" 116 | ) 117 | } 118 | 119 | shiny::div( 120 | class = container_class, 121 | lapply(seq_along(dots), function(i) { 122 | id <- dots[[i]]$attribs$id 123 | if (is.null(id)) id <- ids[[i]] 124 | 125 | cl <- paste("twTab", tabsetid, if (i == 1) "twTab-active", tab_class) 126 | 127 | shiny::tags$div( 128 | class = cl, 129 | id = id, 130 | onclick = sprintf("opentab(\'%s\', \'%s\')", tabsetid, id), 131 | shiny::tagList( 132 | dots[[i]] 133 | ) 134 | ) 135 | }), 136 | shiny::includeScript( 137 | path = system.file("twTab.js", package = "shiny.tailwind") 138 | ) 139 | ) 140 | } 141 | -------------------------------------------------------------------------------- /R/twTextAreaInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::textAreaInput()`] but allowing for more classes 2 | 3 | #' @inheritParams shiny::textAreaInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param input_class additional classes to be applied to the input element 7 | #' @param label_after_input TRUE/FALSE if the label should be put after the 8 | #' input box. Default is FALSE. Useful for special cases (floating labels), 9 | #' c.f. 04-shiny-inputs example app. 10 | #' 11 | #' @seealso [shiny::textAreaInput()] 12 | #' 13 | #' @return a list with a `shiny.tag` class 14 | #' 15 | #' @export 16 | #' @examples 17 | #' shiny::textAreaInput("id", "Label", 18 | #' value = "The value", width = "200px", 19 | #' placeholder = "Placeholder" 20 | #' ) 21 | #' twTextAreaInput("id", "Label", 22 | #' value = "The value", width = "200px", 23 | #' height = "200px", placeholder = "Placeholder", 24 | #' container_class = "CONTAINER", label_class = "LABEL", input_class = "INPUT" 25 | #' ) 26 | #' 27 | #' # basic full shiny example 28 | #' library(shiny) 29 | #' 30 | #' ui <- fluidPage( 31 | #' use_tailwind(), 32 | #' twTextAreaInput( 33 | #' "text", "A Text", 34 | #' placeholder = "Here goes a placeholder", 35 | #' width = "400px", height = "400px", 36 | #' # Apply tailwind classes 37 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 38 | #' label_class = "font-serif text-gray-600", 39 | #' input_class = "drop-shadow-lg font-mono text-gray-600 rounded-md border-amber-400" 40 | #' ), 41 | #' verbatimTextOutput("value") 42 | #' ) 43 | #' 44 | #' server <- function(input, output) { 45 | #' output$value <- renderText(input$text) 46 | #' } 47 | #' 48 | #' if(interactive()) shiny::shinyApp(ui_basic, server) 49 | twTextAreaInput <- function( 50 | inputId, 51 | label, 52 | value = "", 53 | placeholder = NULL, 54 | width = NULL, 55 | height = NULL, 56 | rows = NULL, 57 | cols = NULL, 58 | resize = NULL, 59 | container_class = NULL, 60 | label_class = NULL, 61 | input_class = NULL, 62 | label_after_input = FALSE 63 | ) { 64 | input_class <- paste("form-control", input_class) 65 | container_class <- paste("twTextInput form-group", container_class) 66 | label_class <- paste("control-label", label_class) 67 | 68 | width <- shiny::validateCssUnit(width) 69 | height <- shiny::validateCssUnit(height) 70 | 71 | if (is.null(resize)) resize <- "both" 72 | allowed_resize <- c("both", "none", "vertical", "horizontal") 73 | if (!resize %in% allowed_resize) { 74 | stop( 75 | "'resize' should be one of '", 76 | paste(allowed_resize, collapse = "', '"), 77 | "'" 78 | ) 79 | } 80 | 81 | if (!is.null(label)) { 82 | label_tag <- shiny::tags$label( 83 | class = label_class, 84 | id = paste0(inputId, "-label"), 85 | `for` = inputId, 86 | label 87 | ) 88 | } 89 | 90 | st <- paste0("resize: ", resize, ";") 91 | if (!is.null(width)) st <- paste0("width:", width, ";") 92 | if (!is.null(height)) st <- paste0(st, paste0("height:", height, ";")) 93 | 94 | html_label <- shiny::tags$label( 95 | class = label_class, 96 | id = paste0(inputId, "-label"), 97 | "for" = inputId, 98 | label 99 | ) 100 | 101 | shiny::div( 102 | class = container_class, 103 | # NOTE, no height here! only in textarea 104 | style = if (!is.null(width)) paste0("width:", width, ";") else NULL, 105 | if (!label_after_input) html_label, 106 | shiny::tags$textarea( 107 | id = inputId, 108 | class = input_class, 109 | placeholder = placeholder, 110 | style = st, 111 | rows = rows, 112 | cols = cols, 113 | value 114 | ), 115 | if (label_after_input) html_label 116 | ) 117 | } 118 | -------------------------------------------------------------------------------- /R/twTextInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::textInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::textInput 4 | #' @param type the type for the input, eg "text" (default), "password", "email", 5 | #' "month", "url", ... see also [MDN Input Types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#input_types]) 6 | #' @param container_class additional classes to be applied to the container 7 | #' @param label_class additional classes to be applied to the label 8 | #' @param input_class additional classes to be applied to the input element 9 | #' @param label_after_input TRUE/FALSE if the label should be put after the 10 | #' input box. Default is FALSE. Useful for special cases (floating labels), 11 | #' c.f. 04-shiny-inputs example app. 12 | #' 13 | #' @seealso [shiny::textInput()] 14 | #' 15 | #' @return a list with a `shiny.tag` class 16 | #' 17 | #' @export 18 | #' @examples 19 | #' shiny::textInput( 20 | #' "id", "Label", 21 | #' value = "The value", width = "200px", 22 | #' placeholder = "Placeholder" 23 | #' ) 24 | #' twTextInput( 25 | #' "id", "Label", 26 | #' value = "The value", width = "200px", 27 | #' placeholder = "Placeholder", type = "email", 28 | #' container_class = "CONTAINER", label_class = "LABEL", 29 | #' input_class = "INPUT" 30 | #' ) 31 | #' 32 | #' # basic full shiny example 33 | #' library(shiny) 34 | #' # basic example 35 | #' ui <- fluidPage( 36 | #' use_tailwind(), 37 | #' div( 38 | #' class = "flex flex-wrap", 39 | #' twTextInput( 40 | #' "text", "A Text", 41 | #' type = "text", placeholder = "Some Text", 42 | #' # Apply tailwind classes 43 | #' container_class = paste( 44 | #' "w-48 m-4 p-2 border border-gray-200", 45 | #' "rounded-md drop-shadow-md" 46 | #' ), 47 | #' label_class = "font-serif text-gray-600", 48 | #' input_class = paste( 49 | #' "drop-shadow-lg font-mono text-gray-600", 50 | #' "rounded-md border-amber-400" 51 | #' ) 52 | #' ), 53 | #' twTextInput( 54 | #' "email", "An Email", 55 | #' type = "email", 56 | #' placeholder = "email", 57 | #' # Apply tailwind classes 58 | #' container_class = paste( 59 | #' "w-48 m-4 p-2 border border-gray-200", 60 | #' "rounded-md drop-shadow-md" 61 | #' ), 62 | #' label_class = "font-serif text-gray-600", 63 | #' input_class = paste( 64 | #' "drop-shadow-lg font-mono text-gray-600", 65 | #' "rounded-md border-amber-400" 66 | #' ) 67 | #' ), 68 | #' twTextInput( 69 | #' "pw", "A Password", 70 | #' type = "password", 71 | #' placeholder = "dont let it be password", 72 | #' # Apply tailwind classes 73 | #' container_class = paste( 74 | #' "w-48 m-4 p-2 border border-gray-200", 75 | #' "rounded-md drop-shadow-md" 76 | #' ), 77 | #' label_class = "font-serif text-gray-600", 78 | #' input_class = paste( 79 | #' "drop-shadow-lg font-mono text-gray-600", 80 | #' "rounded-md border-amber-400" 81 | #' ) 82 | #' ) 83 | #' ), 84 | #' twTextInput( 85 | #' "pw", "A Password", 86 | #' type = "password", placeholder = "dont let it be password", 87 | #' # Apply tailwind classes 88 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 89 | #' label_class = "font-serif text-gray-600", 90 | #' input_class = "drop-shadow-lg font-mono text-gray-600 rounded-md border-amber-400" 91 | #' ), 92 | #' verbatimTextOutput("value") 93 | #' ) 94 | #' 95 | #' server <- function(input, output) { 96 | #' output$value <- renderText({ 97 | #' paste(capture.output(str(list( 98 | #' text = input$text, 99 | #' email = input$email, 100 | #' pw = input$pw 101 | #' ))), collapse = "\n") 102 | #' }) 103 | #' } 104 | #' 105 | #' if(interactive()) shiny::shinyApp(ui, server) 106 | #' 107 | twTextInput <- function( 108 | inputId, 109 | label = NULL, 110 | value = NULL, 111 | placeholder = NULL, 112 | width = NULL, 113 | type = "text", 114 | container_class = NULL, 115 | label_class = NULL, 116 | input_class = NULL, 117 | label_after_input = FALSE 118 | ) { 119 | input_class <- paste("block form-control", input_class) 120 | container_class <- paste("block twTextInput form-group", container_class) 121 | label_class <- paste("control-label", label_class) 122 | 123 | width <- shiny::validateCssUnit(width) 124 | 125 | label_tag <- NULL 126 | 127 | if (!is.null(label)) { 128 | label_tag <- shiny::tags$label( 129 | class = label_class, 130 | id = paste0(inputId, "-label"), 131 | `for` = inputId, 132 | label 133 | ) 134 | } 135 | 136 | shiny::tagList( 137 | shiny::tags$div( 138 | class = container_class, 139 | style = if (!is.null(width)) paste0("width:", width, ";") else NULL, 140 | if (!label_after_input) label_tag, 141 | shiny::tags$input( 142 | id = inputId, 143 | type = type, 144 | value = value, 145 | placeholder = placeholder, 146 | class = input_class 147 | ), 148 | if (label_after_input) label_tag, 149 | ) 150 | ) 151 | } 152 | -------------------------------------------------------------------------------- /R/twVarSelectInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::varSelectInput()`] but allowing for more classes 2 | #' 3 | #' @inheritParams shiny::varSelectInput 4 | #' @param container_class additional classes to be applied to the container 5 | #' @param label_class additional classes to be applied to the label 6 | #' @param select_class additional classes to be applied to the select elements 7 | #' 8 | #' @seealso [shiny::varSelectInput()] 9 | #' 10 | #' @return a list with a `shiny.tag` class 11 | #' 12 | #' @export 13 | #' @examples 14 | #' shiny::varSelectInput("id", "label", mtcars, 15 | #' width = "200px", 16 | #' selected = c("vs", "cyl"), multiple = TRUE 17 | #' ) 18 | #' twVarSelectInput("id", "label", mtcars, 19 | #' selected = c("vs", "cyl"), width = "200px", 20 | #' multiple = TRUE, selectize = TRUE, 21 | #' container_class = "CONTAINER", label_class = "LABEL", 22 | #' select_class = "SELECT" 23 | #' ) 24 | #' 25 | #' # basic full shiny example 26 | #' library(shiny) 27 | #' # basic example 28 | #' ui = fluidPage( 29 | #' use_tailwind(), 30 | #' twVarSelectInput( 31 | #' "variable", "Variable to select:", 32 | #' mtcars, 33 | #' multiple = TRUE, 34 | #' # Apply tailwind classes 35 | #' container_class = "shadow-md rounded-md bg-gray-50 m-4 p-2 w-64", 36 | #' label_class = "font-serif", 37 | #' select_class = "font-mono font-bold text-red-800 rounded-md bg-stone-50" 38 | #' ), 39 | #' tableOutput("data") 40 | #' ) 41 | #' 42 | #' server <- function(input, output) { 43 | #' output$data <- renderTable( 44 | #' { 45 | #' mtcars[[input$variable]] 46 | #' }, 47 | #' rownames = TRUE 48 | #' ) 49 | #' } 50 | #' 51 | #' if(interactive()) shiny::shinyApp(ui_basic, server) 52 | #' 53 | twVarSelectInput <- function( 54 | inputId, 55 | label, 56 | data, 57 | selected = NULL, 58 | multiple = FALSE, 59 | selectize = TRUE, 60 | width = NULL, 61 | container_class = NULL, 62 | label_class = NULL, 63 | select_class = NULL 64 | ) { 65 | # see the return value of ?varSelectInput 66 | select_class <- paste("symbol", select_class) 67 | width <- shiny::validateCssUnit(width) 68 | 69 | ch <- names(data) 70 | if (is.null(ch)) ch <- colnames(data) 71 | if (is.null(ch)) { 72 | stop( 73 | "Could not determine the column names of 'data'. Is it a named data.frame/matrix?" 74 | ) 75 | } 76 | 77 | twSelectInput( 78 | inputId = inputId, 79 | label = label, 80 | choices = names(data), 81 | selected = selected, 82 | multiple = multiple, 83 | selectize = selectize, 84 | width = width, 85 | container_class = container_class, 86 | label_class = label_class, 87 | select_class = select_class 88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /R/twVarSelectizeInput.R: -------------------------------------------------------------------------------- 1 | #' Wrapper around [`shiny::varSelectizeInput()`] but allowing for more classes 2 | #' 3 | #' Note that the colors for the selected elements can be customized. 4 | #' c.f. 05-apply-directive example app 5 | #' 6 | #' @inheritParams shiny::varSelectizeInput 7 | #' @param container_class additional classes to be applied to the container 8 | #' @param label_class additional classes to be applied to the label 9 | #' @param input_class additional classes to be applied to the input element 10 | #' @param label_after_input TRUE/FALSE if the label should be put after the 11 | #' input box. Default is FALSE. Useful for special cases (floating labels), 12 | #' c.f. 05-apply-directive example app. 13 | #' 14 | #' @seealso [shiny::varSelectizeInput()] 15 | #' 16 | #' @return a list with a `shiny.tag` class 17 | #' 18 | #' @export 19 | #' @examples 20 | #' shiny::varSelectizeInput("selectize", "A Selection", mtcars) 21 | #' twVarSelectizeInput("selectize", "A Selection", mtcars, 22 | #' container_class = "CONTAINER", label_class = "LABEL", 23 | #' input_class = "INPUT" 24 | #' ) 25 | #' 26 | #' # basic full shiny example 27 | #' library(shiny) 28 | #' 29 | #' ui <- fluidPage( 30 | #' use_tailwind(), 31 | #' twVarSelectizeInput( 32 | #' "values", "A Selection", mtcars, 33 | #' multiple = TRUE, 34 | #' # Apply tailwind classes 35 | #' container_class = "w-48 m-4 p-2 border border-gray-200 rounded-md drop-shadow-md", 36 | #' label_class = "font-mono text-gray-600", 37 | #' input_class = "drop-shadow-lg text-gray-600 font-mono rounded-md border-amber-400" 38 | #' ), 39 | #' verbatimTextOutput("value") 40 | #' ) 41 | #' 42 | #' server <- function(input, output) { 43 | #' output$value <- renderText({ 44 | #' as.character(input$values) 45 | #' }) 46 | #' } 47 | #' 48 | #' if(interactive()) shiny::shinyApp(ui_basic, server) 49 | twVarSelectizeInput <- function( 50 | inputId, 51 | ..., 52 | options = NULL, 53 | width = NULL, 54 | container_class = NULL, 55 | label_class = NULL, 56 | input_class = NULL, 57 | label_after_input = FALSE 58 | ) { 59 | res <- shiny::varSelectizeInput( 60 | inputId = inputId, 61 | ..., 62 | options = options, 63 | width = width 64 | ) 65 | 66 | res$attribs$class <- paste(res$attribs$class, container_class) 67 | res$children[[1]]$attribs$class <- paste( 68 | res$children[[1]]$attribs$class, 69 | label_class 70 | ) 71 | res$children[[2]]$children[[1]]$attribs$class <- paste( 72 | res$children[[2]]$children[[1]]$attribs$class, 73 | input_class 74 | ) 75 | 76 | if (label_after_input) { 77 | tmp <- res$children[[1]] 78 | res$children[[1]] <- res$children[[2]] 79 | res$children[[2]] <- tmp 80 | } 81 | 82 | res 83 | } 84 | -------------------------------------------------------------------------------- /R/use_daisyui.R: -------------------------------------------------------------------------------- 1 | #' Allows you to use 'daisyUI' elements 2 | #' 3 | #' See also: and 4 | #' 5 | #' Note that this uses the CDN version, which is not recommended for production 6 | #' by 'daisyUI'. 7 | #' 8 | #' @param version the version of 'daisyUI' to use, default is 5.0.0 9 | #' @param ... additional arguments passed to [use_tailwind()] 10 | #' 11 | #' @return the required HTML-head tags to use 'daisyUI' as `shiny.tag` 12 | #' @export 13 | #' 14 | #' @examples 15 | #' library(shiny) 16 | #' 17 | #' ui <- div( 18 | #' class = "h-full w-full", 19 | #' use_daisyui(), 20 | #' div( 21 | #' class = "text-sm breadcrumbs", 22 | #' tags$ul( 23 | #' tags$li(tags$a("Home")), 24 | #' tags$li(tags$a("Documents")), 25 | #' tags$li(tags$a("Add Documents")) 26 | #' ) 27 | #' ) 28 | #' ) 29 | #' if(interactive()) shiny::shinyApp(ui, function(input, output) {}) 30 | use_daisyui <- function(version = "5.0.0", ...) { 31 | min_css <- sprintf( 32 | "https://cdn.jsdelivr.net/npm/daisyui@%s/daisyui.css", 33 | version 34 | ) 35 | 36 | shiny::tagList( 37 | use_tailwind(...), 38 | shiny::tags$head(shiny::tags$link(rel = "stylesheet", href = min_css)) 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /R/use_flowbite.R: -------------------------------------------------------------------------------- 1 | #' Allows you to use 'flowbite' components 2 | #' 3 | #' See also: and 4 | #' 5 | #' @param version the version of 'flowbite' to use, default is 3.1.2 6 | #' @param ... further arguments passed to [use_tailwind()] 7 | #' 8 | #' @return the required HTML-head tags to use 'flowbite' as `shiny.tag` 9 | #' 10 | #' @export 11 | use_flowbite <- function(version = "3.1.2", ...) { 12 | min_css <- sprintf( 13 | "https://cdn.jsdelivr.net/npm/flowbite@%s/dist/flowbite.min.css", 14 | version 15 | ) 16 | js <- sprintf( 17 | "https://cdn.jsdelivr.net/npm/flowbite@%s/dist/flowbite.min.js", 18 | version 19 | ) 20 | 21 | shiny::tagList( 22 | use_tailwind(...), 23 | shiny::tags$head(shiny::tags$link(rel = "stylesheet", href = min_css)), 24 | shiny::tags$script(src = js) 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /R/use_preline.R: -------------------------------------------------------------------------------- 1 | #' Allows you to use 'preline' components 2 | #' 3 | #' See also: and 4 | #' 5 | #' @param version the version of 'preline' to use, default is 3.0.0 6 | #' @param ... further arguments passed to [use_tailwind()] 7 | #' 8 | #' @return the required HTML-head tags to use 'preline' as `shiny.tag` 9 | #' 10 | #' @export 11 | use_preline <- function(version = "3.0.0", ...) { 12 | js <- sprintf("https://cdn.jsdelivr.net/npm/preline@%s/preline.js", version) 13 | 14 | shiny::tagList( 15 | use_tailwind(...), 16 | shiny::tags$script(src = js) 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /R/use_tailwind.R: -------------------------------------------------------------------------------- 1 | #' 'TailwindCSS' with Shiny 2 | #' 3 | #' @import htmltools 4 | #' 5 | #' @details 6 | #' 'TailwindCSS' is a utility-based design framework that makes designing simple. 7 | #' See details in the README for this package for why this is so great. 8 | #' 9 | #' However, the complete set of tailwind css classes is massive (~15mb), so 10 | #' you don't want to load all of these. That is where Tailwind's new Just in 11 | #' Time compiling comes in. It will only load the css classes you use, as you 12 | #' use them. So if your shiny app renders ui dynamically, it will load 13 | #' appropriate css whenever the UI is rendered. 14 | #' 15 | #' Custom css can use the `@apply` directives that come with tailwind to easily 16 | #' compile set of classes. See 17 | #' \url{https://tailwindcss.com/docs/functions-and-directives#apply} for 18 | #' more details. It just *has* to be passed to the use_tailwind function if you 19 | #' want to use the `@apply` directive. 20 | #' 21 | #' Custom configuration of tailwind is also possible. There are two options 22 | #' available in `use_tailwind`. First, if you don't want to use any custom 23 | #' modules, uses tailwindConfig. An example is in the folder 24 | #' `inst/examples/02-config` in the github repository. Note the `.js` file should 25 | #' only consist of the creation of the JSON object `tailwind.config = {}`. 26 | #' The function will place it in the appropriate script tag. 27 | #' 28 | #' @param css Optional. Path to ".css" file. Can use @apply tags for applying 29 | #' Tailwind classes. See description for more details. 30 | #' @param tailwindConfig Optional. Path to ".js" file containing json object 31 | #' `tailwind.config = {}`. See 32 | #' \url{https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.0.0-alpha.1}. 33 | #' @param version Which version of tailwind to use. Default is now v4. 34 | #' @return a list of type `shiny.tag` with head and script elements needed to 35 | #' run a tailwind app 36 | #' @export 37 | #' 38 | #' @examples 39 | #' library(shiny) 40 | #' example_apps <- list.files(system.file("examples", package = "shiny.tailwind"), 41 | #' full.names = TRUE 42 | #' ) 43 | #' basename(example_apps) 44 | #' 45 | #' if (interactive()) runApp(example_apps[1]) 46 | use_tailwind <- function(css = NULL, tailwindConfig = NULL, version = 4) { 47 | # Check files exists 48 | if (!is.null(css) && any(!file.exists(css))) { 49 | stop(sprintf( 50 | "File: %s doesn't exist.", 51 | paste(css[!file.exists(css)], collapse = ", ") 52 | )) 53 | } 54 | 55 | if (!is.null(tailwindConfig) && !file.exists(tailwindConfig)) { 56 | stop(sprintf("File: %s doesn't exist", tailwindConfig)) 57 | } 58 | 59 | # https://tailwindcss.com/docs/installation/play-cdn 60 | if (version == 3) { 61 | url <- "https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio" 62 | } else { 63 | url <- "https://unpkg.com/@tailwindcss/browser@4?plugins=forms,typography,aspect-ratio,line-clamp" 64 | } 65 | 66 | html_cdn <- list(htmltools::HTML(sprintf( 67 | "\n", 68 | url 69 | ))) 70 | 71 | html_css <- NULL 72 | html_config <- NULL 73 | 74 | # Prepare html elements 75 | if (!is.null(css)) { 76 | html_css <- lapply(css, function(x) { 77 | htmltools::HTML(paste( 78 | "", 81 | collapse = "\n" 82 | )) 83 | }) 84 | } 85 | 86 | if (!is.null(tailwindConfig)) { 87 | html_config <- list(htmltools::HTML(paste( 88 | "\n", 89 | "", 92 | collapse = "\n" 93 | ))) 94 | } 95 | 96 | shiny::tagList(c( 97 | html_cdn, 98 | html_config, 99 | html_css 100 | )) 101 | } 102 | 103 | # internal helper function to read utf8 104 | read_utf8_ <- function(file) { 105 | r <- readLines(file, encoding = "UTF-8", warn = FALSE) 106 | if (!any(validUTF8(r))) { 107 | stop(sprintf("The file %s is not encoded in UTF 8.", file)) 108 | } 109 | r 110 | } 111 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | 17 | # shiny.tailwind 18 | 19 | 20 | [![CRAN status](https://www.r-pkg.org/badges/version/shiny.tailwind)](https://cran.r-project.org/package=shiny.tailwind) 21 | 22 | 23 | The goal of `shiny.tailwind` is to bring [TailwindCSS](https://tailwindcss.com/) to Shiny apps. 24 | 25 | 26 | ## Installation 27 | 28 | You can install the package with: 29 | 30 | ``` r 31 | install.packages("shiny.tailwind") 32 | # development version 33 | # remotes::install_github("kylebutts/shiny.tailwind") 34 | ``` 35 | 36 | 37 | ## Basic Use 38 | 39 | In your shiny UI declaration, just include `shiny.tailwind::use_tailwind()` and all the appropriate files will be inserted into your shiny app. Therefore you can just start using tailwind classes and they will load dynamically and automatically. 40 | 41 | If you want to compile your used tailwindcss classes to a local css file, see the [Details](#details-about-shinytailwind) section. 42 | 43 | `shiny.tailwind` also allows you to use [daisyUI](https://daisyui.com/) and [flowbite](https://flowbite.com/) components. See also the examples 44 | 45 | - daisyUI: `system.file("examples", "07-daisyUI", package = "shiny.tailwind")` and 46 | - flowbite: `system.file("examples", "08-flowbite", package = "shiny.tailwind")` 47 | 48 | 49 | ## Example 50 | 51 | Here is a basic example. 52 | 53 | 54 | 55 | Here is the example code. Note how easy it is to use tailwind classes with `shiny.tailwind::use_tailwind()` 56 | 57 | ``` r 58 | library(shiny) 59 | library(shiny.tailwind) 60 | # there is a bug (at the moment), that tailwind does not render correctly in the 61 | # RStudio viewer, the following code uses your default browser 62 | options(shiny.launch.browser = .rs.invokeShinyWindowExternal) 63 | 64 | # Define UI for application that draws a histogram using HTML divs and tailwind 65 | ui <- div( 66 | class = "px-4 py-10 max-w-6xl mx-auto", 67 | # Load Tailwind CSS Just-in-time 68 | use_tailwind(), 69 | 70 | # apply tailwind classes to existing classes, in this case the slider input 71 | tags$style(type = "text/tailwindcss"," 72 | .irs-single {@apply bg-pink-500 !important;} 73 | .irs-bar {@apply bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 border-none !important;} 74 | "), 75 | 76 | # Title 77 | div(class = "flex flex-col w-full text-center py-12", 78 | h1( 79 | class = paste( 80 | "text-6xl font-extrabold tracking-tight text-transparent bg-clip-text", 81 | "bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500" 82 | ), 83 | "Old Faithful" 84 | ) 85 | ), 86 | 87 | # Inputs 88 | div(class = "flex gap-2 shadow-md py-4 px-4 flex flex-row rounded-md bg-stone-50", 89 | twSliderInput("bins", "Number of Bins:", min = 1, max = 10, value = 5, 90 | label_class = "font-bold"), 91 | twTextInput("title", "Title", value = "Histogram of Eruptions", 92 | label_class = "font-bold", 93 | input_class = "rounded-md border border-2 border-stone-200"), 94 | twTextInput("xlab", "x-Axis Label", value = "Waiting Time", 95 | label_class = "font-bold", 96 | input_class = "rounded-md border border-2 border-stone-200"), 97 | twTextInput("ylab", "y-Axis Label", value = "Frequency", 98 | label_class = "font-bold", 99 | input_class = "rounded-md border border-2 border-stone-200") 100 | ), 101 | 102 | # Plot 103 | div(class = "block shadow-md rounded-md py-4 px-4 mt-4 bg-stone-50", 104 | plotOutput("distPlot") 105 | ) 106 | ) 107 | 108 | # Define server logic required to draw a histogram, does not use shiny.tailwind 109 | server <- function(input, output) { 110 | 111 | output$distPlot <- renderPlot({ 112 | # generate bins based on input$bins from ui.R 113 | x <- faithful[, 2] 114 | bins <- seq(min(x), max(x), length.out = input$bins + 1) 115 | 116 | # draw the histogram with the specified number of bins 117 | par(bg = NA) # remove white background of plot 118 | hist(x, breaks = bins, col = "#ec4899", border = "white", 119 | main = input$title, xlab = input$xlab, ylab = input$ylab) 120 | }) 121 | } 122 | 123 | # Run the application 124 | if(interactive()) shiny::shinyApp(ui = ui, server = server) 125 | ``` 126 | 127 | Additional examples are found in the `inst/examples/` folder, eg 128 | 129 | ```R 130 | library(shiny) 131 | library(shiny.tailwind) 132 | 133 | list.files(system.file("examples", package = "shiny.tailwind")) 134 | runApp(system.file("examples", "01-old-faithful", package = "shiny.tailwind")) 135 | ``` 136 | 137 | At the moment the following examples are available: 138 | 139 | ```{r, results = "asis", echo=FALSE} 140 | cat(paste0("- `", 141 | list.files(system.file("examples", package = "shiny.tailwind")), 142 | "`", 143 | collapse = "\n")) 144 | ``` 145 | 146 | 147 | 148 | ## What is Tailwind CSS? 149 | 150 | Tailwind CSS is a *utility-based* CSS framework that allows really quick and incredibly customizable styling of html all through classes. Here are some example classes 151 | 152 | - `my-4` which sets the **m**argin top and bottom (ie the **y** coordinates) to size `4` (Tailwind has sizes that are consistent across classes. 4 happens to be `1rem`). See also [docs/margin](https://tailwindcss.com/docs/margin). 153 | - `shadow-sm`/`shadow-md`/`shadow-lg`/`shadow-xl` set a drop shadow on divs. See also [docs/box-shadow](https://tailwindcss.com/docs/box-shadow). 154 | - `text-left`/`text-center`/`text-right` left/center/right- align text. See also [docs/text-align](https://tailwindcss.com/docs/text-align). 155 | - `w-#/12` sets a column of width #/12 (similar to bootstrap's grid). See also [docs/width](https://tailwindcss.com/docs/width). 156 | - [Much, much more](https://tailwindcss.com/docs/). 157 | 158 | For example, the following UI code would create a div that has a margin of 4 tailwind units vertically (`my-4`), a large shadow (`shadow-lg`) and has a width of 3/12 (`w-3/12`): 159 | 160 | ```R 161 | div( 162 | class = "my-4 shadow-lg w-3/12", 163 | ... # put the contents of the box here. 164 | ) 165 | ``` 166 | 167 | This makes a common framework for designing that is quick and intuitive. 168 | 169 | 170 | ## Details about `shiny.tailwind` 171 | 172 | TailwindCSS is a utility-based design framework that makes designing simple. There is basically a class for every css idea you could have. However, the complete set of tailwind css classes is massive (~15mb), so you don't want to load all of these. That is where Tailwind's new Just in Time compiling comes in. It will only load the css classes you use, as you use them. So if your shiny app renders ui dynamically, it will load just the css needed whenever the UI is rendered. 173 | 174 | Normally, doing just in time requires a fancy node setup that is constantly monitoring html in a terminal. However, the company Beyond Code created a browser version of Tailwind Just in Time that runs completely in JS in the browser. See . Therefore, you can just use tailwind css classes and they will load automatically. 175 | 176 | If you need to work offline or do not want to have the live-connection required to tailwinds CDN, you can also install and use the CLI which will compile the required css to a local file. 177 | See also: 178 | 179 | - `?install_tailwindcss_cli()` to install the CLI (the program is around 15MB and platform dependent), 180 | - `?compile_tailwindcss()` to compile the files to a local CSS (replaces the logic of `use_tailwind()` instead the local CSS can be included), 181 | - or the `03-css-generation` example (`system.file("examples", "03-css-generation", package = "shiny.tailwindcss")`). 182 | 183 | 184 | ### Custom css and the `@apply` directive: 185 | 186 | Writing css in Tailwind is incredibly easy too, with the [`@apply`](https://tailwindcss.com/docs/functions-and-directives#apply) directive. For example, lets say you want to create a blue button class, say `.btn-blue`. I can use the `@apply` directive to automatically use a bunch of TailwindCSS utility classes: 187 | 188 | ```css 189 | .btn-blue { 190 | @apply bg-blue-500 hover:bg-blue-700 text-white; 191 | } 192 | ``` 193 | 194 | Setting `class = "btn-blue"` is equivalent to setting `class = "bg-blue-500 hover:bg-blue-700 text-white"`. 195 | 196 | You can write custom css files for your shiny app, you just need to pass them through `use_tailwind()` in order to use the apply directive. Just pass `use_tailwind(css = "custom.css")` and the @apply directive will work automatically. (Technical note, the `