├── .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 | [](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 `