Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theme Switching Button #1223

Open
Teddy-van-Jerry opened this issue Apr 14, 2023 · 8 comments
Open

Theme Switching Button #1223

Teddy-van-Jerry opened this issue Apr 14, 2023 · 8 comments
Labels
enhancement status: ready to implement Issues that can be actively worked on, and need an implementation!

Comments

@Teddy-van-Jerry
Copy link

Teddy-van-Jerry commented Apr 14, 2023

Is your feature request related to a problem? Please describe.
I hope to implement a theme-switching button so that users can choose a dark or light theme. Currently, the option provided by JTD only allows setting the site to be either dark or light.

Describe the solution you'd like
The theme can be changed by JavaScript:

  1. There is a button (possibly) next to the aux link button;
  2. When clicking the button, the theme can change.

The current solution:

  1. Add Button in header_custom.html: (I have noticed that this location is not very mobile-friendly.)
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="svg-sun" viewBox="0 0 24 24">
    <title>Light mode</title>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
    </svg>
  </symbol>
  <symbol id="svg-moon" viewBox="0 0 24 24">
    <title>Dark mode</title>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
      <path stroke="none" d="M0 0h24v24H0z" fill="none" />
      <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
    </svg>
  </symbol>
</svg>

<a class="site-button" id="theme-toggle"><svg width='18px'><use href="#svg-sun"></use></svg></a>
  1. Add related JS scripts
    Overriding head.html:
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">

  <script>
    const theme = localStorage.getItem('theme') || 'light';
    document.documentElement.setAttribute('data-theme', theme);
  </script>
  <link rel="stylesheet" href="{{ '/assets/css/just-the-docs-default.css' | relative_url }}">

...

</head>
  1. Add scripts at the bottom of body, so I override _layouts/default.html to have the following script:
  <script>
    const userPrefers = getComputedStyle(document.documentElement).getPropertyValue('content'); 
    const toggleDarkMode = document.getElementById("theme-toggle");
  
    if (theme === "dark") {
      setToggleTheme('dark');
    } else if (theme === "light") {
      setToggleTheme('light');
    } else if (userPrefers === "dark") {
      document.documentElement.setAttribute('data-theme', 'dark');
      window.localStorage.setItem('theme', 'dark');
      setToggleTheme('dark');
    } else {
      document.documentElement.setAttribute('data-theme', 'light');
      window.localStorage.setItem('theme', 'light');
      setToggleTheme('light');
    }
    
    jtd.addEvent(toggleDarkMode, 'click', function(){
      const currentTheme = jtd.getTheme();
      const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  
      jtd.setTheme(newTheme);
      localStorage.setItem('theme', newTheme);
      setToggleTheme(newTheme);
    });
  
    function setToggleTheme(theme) {
      jtd.setTheme(theme);
      if (theme === 'dark') {
        toggleDarkMode.innerHTML = `<svg width='18px'><use href="#svg-moon"></use></svg>`;
      } else {
        toggleDarkMode.innerHTML = `<svg width='18px'><use href="#svg-sun"></use></svg>`;
      }
    }
  </script>

However, there is an issue that in the dark theme, it flickers on refresh. I am not experienced in this, and the solution here at StackOverflow cannot solve the issue. I am not sure what is wrong, so can it be related to jtd.setTheme(theme); so that the theme is applied only after the whole page loads?

Describe alternatives you've considered
I am currently implementing it as mentioned above, overriding _includes files. But the main idea is the same.

Additional context
The current site is live at mmcesim.org (with the flickering issue). The repo is mmcesim/mmcesim.org.

Teddy-van-Jerry added a commit to mmcesim/mmcesim.org that referenced this issue Apr 14, 2023
@Teddy-van-Jerry
Copy link
Author

Today, after some other attempts, I think there might be one solution.

If we can put dark and light themes under the same css file, we can use html attributes or classes to change the theme. For example, we can base the switchable theme on the light theme, whenever html has class dark-mode, the CSS style for html.dark-mode can work now.

Teddy-van-Jerry added a commit to mmcesim/mmcesim.org that referenced this issue Apr 15, 2023
@Teddy-van-Jerry
Copy link
Author

Teddy-van-Jerry commented Apr 15, 2023

Finally, I implemented the theme change with CSS and JS. Maybe this feature can be added to JTD with some changes.

Basically, in the overrided _includes/head.html, the first lines are

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">

  <script>
    if (localStorage.getItem('theme') === 'dark') document.documentElement.classList.add('dark-mode');
  </script>
  <script type="text/javascript" src="{{ '/assets/js/theme-switch.js' | relative_url }}"></script>
  <link rel="stylesheet" href="{{ '/assets/css/just-the-docs-switchable.css' | relative_url }}">

...

The theme-switch.js is

window.addEventListener("DOMContentLoaded", function() {
  const toggleDarkMode = document.getElementById("theme-toggle");

  if (localStorage.getItem('theme') === 'dark') {
    setTheme('dark');
  } else {
    setTheme('light');
  }

  jtd.addEvent(toggleDarkMode, 'click', function(){
    const currentTheme = getTheme();
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

    localStorage.setItem('theme', newTheme);
    setTheme(newTheme);
  });

  function getTheme() {
    return document.documentElement.classList.contains('dark-mode') ? 'dark' : 'light';
  }

  function setTheme(theme) {
    if (theme === 'dark') {
      toggleDarkMode.innerHTML = `<svg width='18px' height='18px'><use href="#svg-moon"></use></svg>`;
      document.documentElement.classList.add('dark-mode');
      document.documentElement.classList.remove('light-mode');
    } else {
      toggleDarkMode.innerHTML = `<svg width='18px' height='18px'><use href="#svg-sun"></use></svg>`;
      document.documentElement.classList.add('light-mode');
      document.documentElement.classList.remove('dark-mode');
    }
  }
});

Finally, I combine the light.css with dark.css, by adding html.dark-mode to dark theme settings. (I tried implementing this in SCSS, but I did not figure out an appropriate way.)

Site live at: https://mmcesim.org

Light Theme
Light Theme

Dark Theme
Dark Theme

@stefanintech
Copy link
Contributor

Loving the dark theme! @Teddy-van-Jerry 🔥

@Teddy-van-Jerry
Copy link
Author

Loving the dark theme! @Teddy-van-Jerry 🔥

Thanks! The dark theme is already provided in JTD and can be configured in _config.yml. What I did here is to make the dark and light theme switchable dynamically via a button :-)

@mattxwang
Copy link
Member

Hi @Teddy-van-Jerry, thanks for opening an issue! This is one of our most requested features, and there's a handful of (stale) PRs that have tried to implement this in some way. I'd be happy to review a PR with your proposed solution and work with you to merge it upstream!

(I am alright implementing a toggle and automatic theme switch in piecemeal, rather than all at once)

Related issues and PRs:

@mattxwang mattxwang added the status: ready to implement Issues that can be actively worked on, and need an implementation! label Apr 20, 2023
@Joli-ta
Copy link

Joli-ta commented Jun 23, 2023

Hi @Teddy-van-Jerry , this is a beautiful implementation. One thing I would suggest is reversing the symbols: logically, and how others have it implemented, if I am on a light theme and want to switch to dark, I should click on a moon icon. And vice versa. How about we stay consistent and logical?

Example: https://al-folio.github.io

@The-Epic
Copy link

How could i add this to my site but using the default light and dark modes?

@Teddy-van-Jerry
Copy link
Author

Sorry guys, I am busy with some other research projects recently and do not have enough time to look into this carefully. Well, I also want to use the system's default, and it can be done with some minor modifications to look into the system settings. However, my employed method in this issue is not decent enough. Actually, a better way is to use CSS variables, as in many projects. To achieve that, we need to make a bunch of modifications to the source. I am not sure if there is a better way with SASS as in the current project, but my previous search told me that it can be very difficult.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement status: ready to implement Issues that can be actively worked on, and need an implementation!
Projects
None yet
Development

No branches or pull requests

5 participants