how to switch from light to dark theme in ipyvuetify?

578 Views Asked by At

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 ?

2

There are 2 best solutions below

0
On BEST ANSWER

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.

4
On

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)