daattali / shinycssloaders

⌛ Add loading animations to a Shiny output while it's recalculating
https://daattali.com/shiny/shinycssloaders-demo/
Other
395 stars 45 forks source link

Unable to see spinner when parent is using display: flex #85

Open JasonAizkalns opened 9 months ago

JasonAizkalns commented 9 months ago

Looks like something is happening to the visibility of the spinner when using d-flex and/or justify-content-center:

library(shiny)
library(shinycssloaders)
library(bslib)

tab <- function(...) {
  shiny::tabPanel(..., class = "p-3 border border-top-0 rounded-bottom")
}

ui <- page_navbar(
  nav_panel(
    "Tables",
    tabsetPanel(
      tab(
        "one",
        div(
          class = "d-flex justify-content-center", # comment this line and things work.
          div(
            withSpinner(verbatimTextOutput("text"))
          ) 
        )
      )
    )
  )
)

server <- function(input, output) {
  output$text <- renderText({
    Sys.sleep(2)
    "some text"
  })

}

shinyApp(ui, server)
daattali commented 9 months ago

There's quite a lot going on here,- using page_navbar, nav_panel, custom classes on the tab, and finally the classes that are mentioned in the bug report.

Could you please reduce the reprex to the bare minimum? If there's an issue with d-flex and justify-content-center classes, then show a barebones shiny app with a spinner wrapped in those classes, with nothing else.

Thanks for the report, I'll take a look when the code is more minimal!

JasonAizkalns commented 9 months ago

Thanks @daattali -- here's a more stripped down example:

library(shiny)
library(shinycssloaders)
library(bslib)

ui <- bslib::page(
    div(
      class = "d-flex justify-content-center",
      div(withSpinner(verbatimTextOutput("text")))
    )
)

server <- function(input, output) {
  output$text <- renderText({
    Sys.sleep(2)
    "some text"
  })

}

shinyApp(ui, server)

Seems like just the introduction of d-flex is doing something. To avoid an x/y problem, what am I actually trying to do? I am trying to keep a reactable table in the "center" horizontally reactable(..., fullWidth = FALSE) while still using withSpinner and using bslib for theming (which means Bootstrap 5).

daattali commented 9 months ago

Thanks - using your new information I was able to narrow it down to the fact that shinycssloaders doesn't work when it's inside a flexbox. Here's the minimal reprex I came up with:

library(shiny)

ui <- fluidPage(
    div(style="display: flex;", shinycssloaders::withSpinner(textOutput("text")))
)

server <- function(input, output) {
    output$text <- renderText({
        Sys.sleep(2)
        "some text"
    })
}

shinyApp(ui, server)

I have a feeling this might be a difficult one to solve correctly. I noticed that if I remove this one CSS line:

https://github.com/daattali/shinycssloaders/blob/62815f7af20df73e1dfd561ce9a9460f73907488/inst/assets/spinner.css#L2

then the spinner does show up. But I can't simply remove it, because that CSS rule was literally the first CSS rule used in this package, and all the features depend on it. I don't know what features or usecases might break if it's removed. Someone would need to do a lot of careful testing of different shinycssloaders examples to see if it's ok to remove that line, or alternatively if anything can be added to to allow flexboxes to work.

If you need this to work ASAP, you can fork this project, and remove that line, and if it works for your usecase then just use your forked version.

JasonAizkalns commented 9 months ago

Ah, understood. At least now we could 'hack' an ID and force its position to be static. FWIW, this works in the minimal examples:

library(shiny)
library(shinycssloaders)
library(bslib)

ui <- bslib::page(
  header = tags$style(HTML("
    #hack > .shiny-spinner-output-container {
      position: static;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  ")),
  div(
    class = "d-flex justify-content-center",
    div(id = "hack", withSpinner(verbatimTextOutput("text")))
  )
)

server <- function(input, output) {
  output$text <- renderText({
    Sys.sleep(2)
    "some text"
  })

}

shinyApp(ui, server)

I tried some things with conditional styling (and that might be a decent long-term approach), but wasn't successful. I'm thinking something close to this could work in spinner.css:

  div[style*='display: flex'] .shiny-spinner-output-container {
    position: static;
  }
daattali commented 9 months ago

I'm not inclined to use a solution like that, not just because such CSS selectors are not performant, but it's also a little too hacky/unclean for my taste! I'd prefer finding a cleaner approach