3

I'm using ipyvuetify in a Jupyter Python environment to create an interactive dashboard for my end-user.

I would like to create an interactive toogle btn that switch vuitify.theme.dark from True to False

When I test this behaviour in voila with the following code :

import ipyvuetify as v

v.theme.dark = True

#validate the selected data 
v.Container(children=[
    v.Btn(color='primary', children=[
        v.Icon(left=True, children=[
            'mdi-square'
        ]),
        'Click me'
    ])
])

Only the surrounding of the Btn component have a dark background, the rest of the page keeps the voila light background.

A trick could be to add the ?voila-theme=dark at the end of my url but then it's not dynamic anymore.

Is there a way to change both the voila and ipyvuetify theme ? or to force the ipyvuetify background to occupy all the screen ?

Pierrick Rambaud
  • 1,726
  • 1
  • 20
  • 47
  • There is a related [open issue](https://github.com/voila-dashboards/voila/issues/464) on Voilà project, unfortunately without update since its opening in Nov 2019. – guimillet Mar 06 '21 at 02:47
  • the issue has not been closed but the solution have been implemented: if you add "voila-theme=dark" as an option in your url then the theme is changed – Pierrick Rambaud Mar 08 '21 at 06:41
  • Maybe this issue does not exist when using the [voila-vuetify](https://github.com/voila-dashboards/voila-vuetify) template. Although it seems to be broken [recently](https://github.com/voila-dashboards/voila-vuetify/issues/43), so I could not try it. – guimillet Mar 29 '21 at 00:50

2 Answers2

1

Changing the body background color

With Voilà (default) lab template, the background surrounding the ipyvuetify application appears to be the HTML body background, so a workaround is to apply the background color of the ipyvuetify theme to the body background. Ipyvuetify-v.1.6.2 background colors are #fff in the light theme and #121212 in the dark theme (from vuetify.min.css-v2.2.26).

The background color can be modified by adding an internal CSS in an HTML <style> element:

dark_bg = '.jp-Notebook {background-color: #121212}'
light_bg = '.jp-Notebook {background-color: #fff}'

def bg_switch(widget, event, data):
    v.theme.dark = not v.theme.dark
    css.children = [dark_bg] if v.theme.dark==True else [light_bg]

btn = v.Btn(children=[v.Icon(children=['mdi-theme-light-dark'])])
btn.on_event('click',bg_switch)

css = v.Html(tag='style', children=[dark_bg] if v.theme.dark==True else [light_bg])

v.Container(children=[css,btn])

Another solution is to add an inline CSS by setting the JS HTML DOM Style backgroundColor property of body:

class BtnTheme(v.VuetifyTemplate):
    dark = traitlets.Bool(v.theme.dark).tag(sync=True)
    template = traitlets.Unicode('''
    <v-btn icon @click="switchTheme">
      <v-icon>mdi-theme-light-dark</v-icon>
    </v-btn>
    <script> {created() {this.updateBackground()},
      methods: {
        switchTheme() {
          this.dark = !this.dark;
          this.updateBackground()
          },
        updateBackground() {
          document.body.style.backgroundColor = this.dark ? '#121212' : '#fff'
        }
      }}
    </script>''').tag(sync=True)
    
btn = BtnTheme()
ipywidgets.jslink((btn, 'dark'), (v.theme, 'dark'))

v.Container(children=[btn])

In the above solutions, the theme button is initialised with the value of v.theme.dark which is False unless set to True earlier in the application code. In addition, the theme button can be initialised as the Voilà (lab template) theme:

  1. by checking the query parameters:
v.theme.dark = (os.environ.get('QUERY_STRING', '').find('dark') != -1)
  1. or by checking at the beginning of the created() function the presence of theme-dark in the body classes:
if (document.body.classList.contains('theme-dark')) {this.dark = true}

In case of unknown background colors, these two colors can be detected via two dummy elements, each styled with one of the two themes:

class BtnTheme(v.VuetifyTemplate):
    dark = traitlets.Bool(v.theme.dark).tag(sync=True)
    v_dark_bg = traitlets.Unicode('').tag(sync=True)
    v_light_bg = traitlets.Unicode('').tag(sync=True)
    template = traitlets.Unicode('''
    <v-btn icon @click="switchTheme">
      <v-icon>mdi-theme-light-dark</v-icon>
      <div class="v-application theme--dark" id="v-dark-style"></div>
      <div class="v-application theme--light" id="v-light-style"></div>
    </v-btn>
    <script> {
      mounted() {
        this.v_dark_bg = getComputedStyle(document.getElementById("v-dark-style")).backgroundColor
        this.v_light_bg = getComputedStyle(document.getElementById("v-light-style")).backgroundColor
        if (document.body.classList.contains('theme-dark')) {this.dark = true}
        this.updateBackground()
        },
      methods: {
        switchTheme() {
          this.dark = !this.dark
          this.updateBackground()
          },
        updateBackground() {
          document.body.style.backgroundColor = this.dark ? this.v_dark_bg : this.v_light_bg
        }
      }
    }
    </script>''').tag(sync=True)
guimillet
  • 306
  • 2
  • 7
  • I'm using ipyvuetify but try to stay as far away from the js code as possible. I t works perfectly thanks. Can you just explain me why you use `module.export` at the beginning of the script ? – Pierrick Rambaud Mar 19 '21 at 08:13
  • I have updated the JS code to a simpler, more readable and efficient one. My previous answer based on changing the CSS background property of `.jp-Notebook` class to `--jp-inverse-layout-color` was also less aesthetic as the color is `#111111` whereas vuetify dark background color is `#121212`. – guimillet Mar 26 '21 at 02:20
  • I have added a solution without JS code to change the body background color: by defining an internal ` – guimillet Mar 29 '21 at 00:12
  • 1
    @PierrickRambaud Actually, `module.exports` and anything between ` – guimillet May 03 '21 at 12:24
1

A trick is to add an opaque Overlay component as background (z-index=-1) and change its color upon switching the ipyvuetify theme:

import ipyvuetify as v

dark_bg = '#121212'
light_bg = '#fff'

bg = v.Overlay(
    color=dark_bg if v.theme.dark==True else light_bg, 
    opacity=1, 
    style_='transition:unset', 
    z_index=-1
)

def bg_switch(widget, event, data):
    v.theme.dark = not v.theme.dark
    bg.color = dark_bg if v.theme.dark==True else light_bg

btn = v.Btn(children=[v.Icon(children=['mdi-theme-light-dark'])])
btn.on_event('click', bg_switch)

v.Container(children=[bg, btn])

No JS code required . The color definitions come from vuetify.min.css (v2.2.26 used by ipyvuetify 1.6.2): #fff in the light theme and #121212 in the dark theme.

guimillet
  • 306
  • 2
  • 7