I am looking to build a dark mode in my application. I realize we can achieve that using bs_theme(), but my application has many more settings in an external css file that do not get read when I use bs_theme().
I want to have 2 separate CSS files, one for light theme and one for dark theme. Based on the user input, the relevant theme file should be loaded in my Rshiny app. Any suggestion on how this could be done?
I'd create two different css classes in this case.
Here you'll find how to include css files (as many as you like) into a shiny app.
To switch between the classes we can use addCssClass/ removeCssClass (or toggleCssClass) from library(shinyjs):
library(shiny)
library(shinyjs)
if(!dir.exists("www")){
dir.create("www")
}
writeLines(".dark {
background-color: black;
color: white; /* text color */
}", con = "www/dark_mode.css")
writeLines(".light {
background-color: white;
color: black; /* text color */
}", con = "www/light_mode.css")
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
),
radioButtons("mode", "Select mode", choices = c("dark", "light"), selected = "light")
)
server <- function(input, output, session) {
observeEvent(input$mode, {
if(input$mode == "dark"){
addCssClass(class = "dark", selector = "body")
removeCssClass(class = "light", selector = "body")
} else {
addCssClass(class = "light", selector = "body")
removeCssClass(class = "dark", selector = "body")
}
})
}
shinyApp(ui, server)
The same can be done via Shiny.addCustomMessageHandler and some custom JS using e.g. element.classList.add("myclass"); (see this).
Edit: Apply addCssClass to inputs of different classes:
library(shiny)
library(shinyjs)
if(!dir.exists("www")){
dir.create("www")
}
writeLines(".dark {
background-color: black !important;
color: white; /* text color */
}", con = "www/dark_mode.css")
writeLines(".light {
background-color: white !important;
color: black; /* text color */
}", con = "www/light_mode.css")
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
),
radioButtons("mode", "Select mode", choices = c("dark", "light"), selected = "light"),
selectizeInput("Test", "Test Input", choices = 1:10),
actionButton("testButton", "Test Button")
)
server <- function(input, output, session) {
observeEvent(input$mode, {
applyTo <- list(".selectize-input", ".btn-default")
if(input$mode == "dark"){
lapply(applyTo, function(x){
addCssClass(class = "dark", selector = x)
removeCssClass(class = "light", selector = x)
})
} else {
lapply(applyTo, function(x){
addCssClass(class = "light", selector = x)
removeCssClass(class = "dark", selector = x)
})
}
})
}
shinyApp(ui, server)
Edit: using forEach:
library(shiny)
library(shinyjs)
if(!dir.exists("www")){
dir.create("www")
}
writeLines(".dark {
background-color: black !important;
color: white; /* text color */
}", con = "www/dark_mode.css")
writeLines(".light {
background-color: white !important;
color: black; /* text color */
}", con = "www/light_mode.css")
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
),
radioButtons("mode", "Select mode", choices = c("dark", "light"), selected = "light"),
selectizeInput("Test", "Test Input", choices = 1:10),
actionButton("testButton", "Test Button")
)
server <- function(input, output, session) {
dm_classes <- paste(c(".selectize-input", ".btn-default"), collapse = ", ")
observeEvent(input$mode, {
if(input$mode == "dark"){
runjs(sprintf("document.querySelectorAll('%s').forEach(x=>x.classList.add('dark'));
document.querySelectorAll('%s').forEach(x=>x.classList.remove('light'));", dm_classes, dm_classes))
} else {
runjs(sprintf("document.querySelectorAll('%s').forEach(x=>x.classList.add('light'));
document.querySelectorAll('%s').forEach(x=>x.classList.remove('dark'));", dm_classes, dm_classes))
}
})
}
shinyApp(ui, server)
Assuming your CSS files are named darkmode.css and lightmode.css, and are placed into the www subfolder, you can use renderUI like this:
library(shiny)
ui <- fluidPage(
tags$head(
uiOutput("css")
),
radioButtons("select", "Select mode", c("dark", "light"))
)
server <- function(input, output){
output[["css"]] <- renderUI({
cssfile <- paste0(input[["select"]], "mode.css")
tags$link(rel = "stylesheet", type = "text/css", href = cssfile)
})
}
shinyApp(ui, server)
Related
I'm finishing up my dashboard atm, and I'm trying to show a tweet on my page. I'm using the tweetrmd package to do this, but it doesn't seem to work
here is part of my UI code
library(tidyverse)
library(shiny)
library(rtweet)
library(tweetrmd)
screenshot <- tweet_screenshot(tweet_url("Metro", "1251153881209307136"))
# UI
list(
ui <- tagList(
includeCSS("style.css"),
navbarPage("#Corona",
windowTitle = "#Corona",
tabPanel("Twitter",
sidebarLayout(
sidebarPanel(
h2("Algemene twitter data", align = "left"),
),
mainPanel(
tabsetPanel(
id = "Tabs",
tabPanel(
title = "Kranten",
h3("Frequentie tweets over corona door populaire kranten", align = "center"),
plotOutput("plot1")%>% withSpinner(color="#dca108"),
div(img(src= screenshot, align = "center"), style="text-align: center;", id= "screenshot"),
), )
)
)
))
)
)
Question is: can I make the tweet_screenshot function work in a shiny app (default is rmarkdown) and how?
If I check out the screenshot object it shows this:
(screenshot <- tweet_screenshot(tweet_url("Metro", "1251153881209307136")))
file://C:\Users\jolien\AppData\Local\Temp\RtmpKeTPxU\file47383c65585c.html screenshot completed
Thanks in advance
A solution using twitframe.com:
library(shiny)
tweet <- "https://twitter.com/Twitter/status/1144673160777912322"
url <- URLencode(tweet, reserved = TRUE)
src <- paste0("https://twitframe.com/show?url=", url)
js <- '
$(window).on("message", function(e) {
var oe = e.originalEvent;
if (oe.origin !== "https://twitframe.com")
return;
if (oe.data.height && oe.data.element.id === "tweet"){
$("#tweet").css("height", parseInt(oe.data.height) + "px");
}
});'
ui <- fluidPage(
fluidRow(
tags$head(
tags$script(HTML(js)),
tags$style(HTML(
"
.content {
margin: auto;
padding: 20px;
width: 60%;
}"))
),
uiOutput("frame")
)
)
server <- function(input, output, session) {
output[["frame"]] <- renderUI({
tagList(
tags$div(
class = "content",
tags$div(tags$iframe(
id = "tweet",
border=0, frameborder=0, height=50, width=550,
src = src
))
),
singleton(tags$script(HTML(
"$(document).ready(function(){
$('iframe#tweet').on('load', function() {
this.contentWindow.postMessage(
{ element: {id:this.id}, query: 'height' },
'https://twitframe.com');
});
});")))
)
})
}
shinyApp(ui, server)
I'm using the dropdownButton widget but i'm struggling with the css.
I have tried this:
dropdownButton(inputId = "MyDropDownB1",
tags$div(style = "background-color: #393D3F !important;",
checkboxGroupInput(...)
),
circle = F, status = "info", icon = icon("gear"), width = "300px",
label="Recruitment"
)
that changes the color of the panel but i still have white margins.
I would like to have all the css in a specific .css file rather than in the tag$div. Which is the tag that I should use in my .css file to target the dropdownButton button and panel?
I used an example code from shinyWidgets as you've not shared any MWE
#dropdown-menu-MyDropDownB1 the one you've to target in your css. As you can see this is based on the id name that you've given in your dropdownButton
# NOT RUN {
## Only run examples in interactive R sessions
if (interactive()) {
library(shiny)
library(shinyWidgets)
ui <- fluidPage(
tags$head(tags$style(HTML("#dropdown-menu-MyDropDownB1 {
background-color: #393D3F !important;}
")))
,
dropdownButton(inputId = "MyDropDownB1",
checkboxGroupInput("icons", "Choose icons:",
choiceNames =
list(icon("calendar"), icon("bed"),
icon("cog"), icon("bug")),
choiceValues =
list("calendar", "bed", "cog", "bug")
),
circle = F, status = "info", icon = icon("gear"), width = "300px",
label="Recruitment"
),
tags$div(style = "height: 140px;"), # spacing
verbatimTextOutput(outputId = "out"),
verbatimTextOutput(outputId = "state")
)
server <- function(input, output, session) {
output$out <- renderPrint({
cat(
" # n\n", input$n, "\n",
"# na\n", input$na
)
})
output$state <- renderPrint({
cat("Open:", input$mydropdown_state)
})
}
shinyApp(ui, server)
}
# }
I wish to change the color of text in a Shiny app based on a user's input. Here's a simple example. Is this basically the correct approach? If I hard code the css it works. For example, if I change:
div(style = css_stub,
to
div(style = "inline-block; red;",
the text color changes. Please explain how to alter css in a Shiny app programmatically.
library(shiny)
css_stub <- paste0("'", "inline-block; color:black;", "'")
ui <- fluidPage(
titlePanel("Color Test"),
sidebarLayout(
sidebarPanel(
selectInput(inputId = "colors",
label = "Choose a color:",
choices = c("red", "blue"))
),
mainPanel(
div(style = css_stub,
textOutput("text_out"))
)
)
)
server <- function(input, output) {
observeEvent(input$colors, {
if (input$colors == "red") {
css_stub <- paste0("'", "inline-block; color:red;", "'")
output$text_out <- renderText({"hello - red"})
} else {
css_stub <- paste0("'", "inline-block; color:blue;", "'")
output$text_out <- renderText({"hello - blue"})
}
})
}
shinyApp(ui = ui, server = server)
I would define classes and styles for each, then and add/remove classes using shinyjs library.
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$style(HTML("
div.red { color: red; }
div.blue { color: blue; }
"))
),
titlePanel("Color Test"),
sidebarLayout(
sidebarPanel(
selectInput(inputId = "colors",
label = "Choose a color:",
choices = c("red", "blue"))
),
mainPanel(
div(id="color_change", style = "inline-block; ",
textOutput("text_out"))
)
)
)
server <- function(input, output) {
observeEvent(input$colors, {
color_to_set <- input$colors
color_to_unset <- setdiff(c("red", "blue"), color_to_set)
shinyjs::addClass("color_change", color_to_set)
for (col in color_to_unset) shinyjs::removeClass("color_change", col)
})
output$text_out = renderText(paste("Hello -", input$colors))
}
shinyApp(ui = ui, server = server)
I am trying to change the bg color of the data table on selection in R Shiny App. Have written the CSS Code for the same but that is unable to override the existing CSS. Any workaround to achieve it.
Here is my piece of code:
ui
library(DT)
library(shiny)
library(shinydashboard)
library(shinyjs)
shinyUI(
dashboardPage (
dashboardHeader(title="Report"),
dashboardSidebar(sidebarMenu(menuItem("Table",tabName="Table"))),
dashboardBody(
tags$head(tags$style(HTML("
#DataTable tr.selected {background-color:cyan !important;}
table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
background-color: rgb(143,209,63) !important;
}
.odd {
background-color : rgb(173,219,241) !important;
}
.even {
background-color : rgb(232,245,251) !important;
}
"))),
useShinyjs() ,
tabItems(
tabItem(tabName = "Table",
DT::dataTableOutput("DataTable")
)
))
))
server
shinyServer(function(input, output) {
output$DataTable <- DT::renderDataTable({
datatable(iris,rownames=FALSE,selection = 'single',options = list(
searching = FALSE,ordering=FALSE,
dom = 'Bfrtip',
buttons = c('copy','excel', 'pdf', 'print', 'colvis'),
columnDefs = list(list(visible=FALSE, targets=c(2))),
rowCallback = JS(
"function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {",
"var full_text = aData[2]",
# Tooltip for the rows
"$('td:eq(1)', nRow).attr('title', full_text);",
# Showing a hand as a cursor
"$('td:eq(1)', nRow).css('cursor','pointer');",
"$('td:eq(1)', nRow).css('font-weight','bold');",
"}")
)
)
})
Here is the updated Code:
ui
shinyUI(
dashboardPage (
dashboardHeader(title="Report"),
dashboardSidebar(sidebarMenu(menuItem("Table",tabName="Table"))),
dashboardBody(
tags$style(HTML("
table.dataTable tr.selected td,
table.dataTable td.selected {
background-color: rgb(143,209,63) !important;
}
")),
tags$head(tags$style(HTML("
table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
background-color: rgb(143,209,63) !important;
}
.odd {
background-color : rgb(173,219,241) !important;
}
.even {
background-color : rgb(232,245,251) !important;
}
"))),
useShinyjs() ,
tabItems(
tabItem(tabName = "Table",
DT::dataTableOutput("DataTable")
)
))
))
server
shinyServer(function(input, output) {
output$DataTable <- DT::renderDataTable({
datatable(iris,rownames=FALSE,selection = 'single',options = list(
searching = FALSE,ordering=FALSE,
dom = 'Bfrtip',
buttons = c('copy','excel', 'pdf', 'print', 'colvis'),
columnDefs = list(list(visible=FALSE, targets=c(2))),
rowCallback = JS(
"function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {",
"var full_text = aData[2]",
# Tooltip for the rows
"$('td:eq(1)', nRow).attr('title', full_text);",
# Showing a hand as a cursor
"$('td:eq(1)', nRow).css('cursor','pointer');",
"$('td:eq(1)', nRow).css('font-weight','bold');",
"}")
)
)
})
I want when i start the application the tab panel tab2 = desactivated,
and will be activated once i click the button in the first tab panel tab1,
i tried with shinyjs and through CSS properties but i can not do that.
thanks for your help
Alex
library(shiny)
library(shinyjs)
runApp(list(
ui = bootstrapPage(
tabsetPanel(
tabPanel(title = "tab1", id="tab1",
br(),
actionButton("click", label = "View tab2 panel")),
tabPanel(title = "tab2", id="tab2")
)
),
server = function(input, output, session){
}
))
You need a bit of javascript to do this. Here's a solution using shinyjs. I also included some css to make it clear when the tab is disabled
jscode <- "
shinyjs.disableTab = function(name) {
var tab = $('.nav li a[data-value=' + name + ']');
tab.bind('click.tab', function(e) {
e.preventDefault();
return false;
});
tab.addClass('disabled');
}
shinyjs.enableTab = function(name) {
var tab = $('.nav li a[data-value=' + name + ']');
tab.unbind('click.tab');
tab.removeClass('disabled');
}
"
css <- "
.nav li a.disabled {
background-color: #aaa !important;
color: #333 !important;
cursor: not-allowed !important;
border-color: #aaa !important;
}"
library(shiny)
library(shinyjs)
runApp(list(
ui = fluidPage(
useShinyjs(),
extendShinyjs(text = jscode),
inlineCSS(css),
tabsetPanel(
id = "navbar",
tabPanel(title = "tab1", id = "tab1",
br(),
actionButton("btn", label = "View tab2 panel")),
tabPanel(title = "tab2", id = "tab2")
)
),
server = function(input, output, session) {
# disable tab2 on page load
js$disableTab("tab2")
observeEvent(input$btn, {
# enable tab2 when clicking the button
js$enableTab("tab2")
# switch to tab2
updateTabsetPanel(session, "navbar", "tab2")
})
}
))
You could also put the javascript in a separate file and use extendShinyjs(file = ...) instead of extendShinyjs(text = ...).
Looking at this 5 years later, I had to make this change to Dean's code to make it work:
extendShinyjs(text = jscode)
becomes
extendShinyjs(text = jscode, functions = c('disableTab','enableTab'))
Some small clarifications on the arguments value, id, and value working from #DeanAttali's reprex:
library("shiny")
library("shinyjs")
library("V8") ## Required for shinyjs::extendShinyjs()
## JavaScript that dis/enables the ABILITY to click the tab (without changing aesthetics)
app_jscode <-
"shinyjs.disableTab = function(name) {
var tab = $('.nav li a[data-value=' + name + ']');
tab.bind('click.tab', function(e) {
e.preventDefault();
return false;
});
tab.addClass('disabled');
}
shinyjs.enableTab = function(name) {
var tab = $('.nav li a[data-value=' + name + ']');
tab.unbind('click.tab');
tab.removeClass('disabled');
}"
## css snipit that makes it LOOK like we are/n't able click the tab (with outchanging functionality)
app_css <-
".nav li a.disabled {
background-color: #aaa !important;
color: #333 !important;
cursor: not-allowed !important;
border-color: #aaa !important;
}"
ui = fluidPage(
shinyjs::useShinyjs(),
shinyjs::extendShinyjs(text = app_jscode),
shinyjs::inlineCSS(app_css),
navbarPage(title = "Navbar title!", id = "navbarid",
tabPanel(title = "tab1", ## id and value args not needed
br(),
p("in tab 1."),
actionButton("btn", label = "toggle locked tabs")),
tabPanel(title = "tab2", ## id and value args not needed
p("in tab 2."))
)
)
server = function(input, output, session) {
## Disable tab2 on page load
js$disableTab("tab2")
observeEvent(input$btn, {
## Enable tab2 when clicking the button
shinyjs::js$enableTab("tab2") ## On a tab's title
## Switch to tab2
updateNavbarPage(session, "navbarid", "tab2") ## On navbar's id, tab's title
#### Taking it further:
## Also disable tab1 as a selection
shinyjs::js$disableTab("tab1")
})
}
shinyApp(ui = ui, server = server)