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

Switch Dark/Light mode using JS #1653

Closed
wants to merge 2 commits into from

Conversation

YousefAlsbaihi
Copy link

This will allow users to switch light/dark mode without depending on link query and without reloading the page
This also uses browser storage same as before, just removed and added the js code to switch without reload.

This will allow you to load dark/light theme without reloading the page or depending on the link variables 

default theme is light and can be changed
Added class switchMode to switch theme
@changeset-bot
Copy link

changeset-bot bot commented Jul 16, 2023

⚠️ No Changeset found

Latest commit: 829d00d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jul 16, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
tabler ✅ Ready (Inspect) Visit Preview Jul 16, 2023 5:30am

@mholt
Copy link
Contributor

mholt commented Jul 16, 2023

Neat! Does this also do "auto" (i.e. follow system preference)? Ideally a three-way toggle: light, dark, system. I'm currently hand-rolling an "auto" theme but it's a bit clunky 😅

selectedTheme = storedTheme ? storedTheme : defaultTheme;

setTheme(selectedTheme)
$(".switchMode").on('click', function (e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is jQuery actually available here?

The demo-theme.js file is intentionally loaded at the top of the body so as to switch as fast as possible before the dom fully loads but the rest of the javascript is all loaded at the end so i wonder if this will work.

And even if it does work, i think using vanilla JS here to not have the overhead of jQuery loading in might be beneficial.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the time the user clicks, jQuery is likely loaded... so it works by accident.
But you are right, no reason to use jQuery for a simple click event.

const themeStorageKey = "tablerTheme"
const defaultTheme = "light"
let selectedTheme
var themeStorageKey = "tablerTheme";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the themeStorageKey and defaultTheme do not change during the execution of the script they where made const what is the reason you are turning them into vars?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var is like $() ... time to let go 😁 (meaning: use let or const instead).

*
* This will allow you to load dark/light theme wihtout reloading the page
* and without depending on the link variables
* default theme is light and can be default dark by changing the specified code below
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your new comment is not a substitute for the original.

The reason the original comment is there is to explain why the script is loaded at the top of the body and not what it does. As this reason still applies please leave it intact.


// https://stackoverflow.com/a/901144
const params = new Proxy(new URLSearchParams(window.location.search), {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of removing the functionality to switch theme with the url why not have both?

Have the button switch using javascript and leave the url variable intact to allow overriding what is currently set in local storage.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be unnecessary, having both of them

@kevinpapst
Copy link
Collaborator

Theme switching depends highly on how you integrated Tabler. Wasn't JS switching removed on purpose, becuase there were issues with flickering and .. I can't exactly remember what else was the reason.

Also, the Vercel deployment has this error on first click:
Bildschirmfoto 2023-07-17 um 22 05 12

@mholt
Copy link
Contributor

mholt commented Jul 17, 2023

Wasn't JS switching removed on purpose, becuase there were issues with flickering and .. I can't exactly remember what else was the reason.

Not sure; but typically flickering can be avoided by performing the theme switch in the head or at least before body load.

@dheineman
Copy link
Contributor

Wasn't JS switching removed on purpose, becuase there were issues with flickering and .. I can't exactly remember what else was the reason.

It was never javascript switching on the demo page, since implemented in #950 it had been url based switching

Not sure; but typically flickering can be avoided by performing the theme switch in the head or at least before body load.

yup, I resolved the flickering issue in #1176 by loading the theme code in as the first thing in the body.

@promatik
Copy link
Contributor

By chance I saw this PR, and let me add I've done this theme switcher for Backpack.

You can test it online; https://demo.backpackforlaravel.com/admin/dashboard
It's a 3-way switcher 👌

If you guys want I can help with this one, let me know.

@mholt
Copy link
Contributor

mholt commented Jul 21, 2023

I would love that!

@mholt
Copy link
Contributor

mholt commented Jul 22, 2023

@promatik I just noticed though that the theme picker on that demo flashes (starts light, turns dark) when the system is in Dark mode. Think we could do one that doesn't flash? (see above)

@promatik
Copy link
Contributor

@mholt that's very strange, at backpack we use the same fix provided by #1176.

First script after the body tag is the ColorMode initializer.

I even check the page's source code to make sure;
image

Is there any thing else to do that I might be missing?

@mholt
Copy link
Contributor

mholt commented Jul 24, 2023

@promatik Hm, I'm not sure. I'm not really a front-end guy. I imagine you need to block painting of elements until some code runs. Maybe the script needs to be in <head> instead?

I asked on Twitter, and got this reply: https://twitter.com/JakeDChampion/status/1683291322466070530

I still can't get the example to work, but in that thread you may find some helpful advice or ideas (or by viewing the page source). I dunno. Worth a look.

@mholt
Copy link
Contributor

mholt commented Jul 24, 2023

@promatik Okay, the demo was updated and works for me now, without a flash: https://jakechampion.name/toggle.html

Full code copied here, for reference:

<html name=html id=html><style>
    [data-theme="dark"] .sun-and-moon>.sun-beams {
        opacity: 0
    }

    [data-theme="dark"] .sun-and-moon>.moon>circle {
        transform: translate(-7px)
    }

    [data-theme="dark"] .laptop {
        opacity: 0;
        display: none;
    }
    [data-theme="light"] .laptop {
        opacity: 0;
        display: none;
    }
    [data-theme="system"] .laptop {
        opacity: 1
    }

    [data-theme="system"] .sun-and-moon {
        opacity: 0;
        display: none;
    }
    .theme-toggle {
        background: none;
        border: none;
        padding: 0;
        border-radius: 50%;
        outline-offset: 5px
    }

    [data-theme="dark"] .theme-toggle {
        --icon-fill: hsl(210 10% 70%);
        --icon-fill-hover: hsl(210 15% 90%)
    }

    html {
        block-size: 100%;
        color-scheme: light
    }

    html[data-theme="dark"] {
        color-scheme: dark
    }
    @media (prefers-color-scheme:dark) {
        html[data-theme="system"]  {
            color-scheme: dark;
        }
        .theme-toggle[data-theme="system"]  {
            color: hsl(210 10% 70%);
        }
    }


    body {
        display: grid;
        align-content: center;
        justify-content: center;
        place-content: center
    }
</style>
<script>
    let storageKey = 't'
    let pcsd = `(prefers-color-scheme:dark)`
    let l = localStorage
    let m = matchMedia

    let getColorPreference = () => (l.getItem(storageKey) || (l.setItem(storageKey, 'system'), 'system'))
    let reflectPreference = (v) => html.dataset.theme=v
    let setPreference = (v) => l.setItem(storageKey, reflectPreference(toggle.ariaLabel=v))


    // set early so no page flashes / CSS is made aware
    reflectPreference(getColorPreference())

    onload = () =>
        (// set on load so screen readers can see latest value on the button
        reflectPreference(getColorPreference()),

        // now this script can find and listen for clicks on the control
        // flip current value
        toggle.onclick=() => {
            let a = getColorPreference()
            if (a == 'light') {
                setPreference('system')
            } else if (a == 'dark') {
                setPreference('light')
            } else {
                setPreference('dark')
            }
        })

    // sync with system changes
    m(pcsd).onchange= ({ matches }) => reflectPreference(matches ? 'dark' : 'light')
</script>
<button class="theme-toggle" name=toggle id="toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite">
    <svg width="24" height="24" aria-hidden="true" class="sun-and-moon"><mask id="a" class="moon"><rect width="100%" height="100%" fill="#fff"/><circle cx="24" cy="10" r="6"/></mask><circle cx="12" cy="12" r="6" fill="currentColor" class="sun" mask="url(#a)"/><g stroke="currentColor" class="sun-beams"><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></g></svg>
    <svg class=laptop width="24px" height="24px" ><path d="M4 6C4 4.89543 4.89543 4 6 4H18C19.1046 4 20 4.89543 20 6V14H4V6Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M20 14H4L2.44721 17.1056C1.78231 18.4354 2.7493 20 4.23607 20H19.7639C21.2507 20 22.2177 18.4354 21.5528 17.1056L20 14Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>
</button>
</html>

Huge thanks to Jake Champion for crafting this.

See if that approach works! 💯

@YousefAlsbaihi
Copy link
Author

Neat! Does this also do "auto" (i.e. follow system preference)? Ideally a three-way toggle: light, dark, system. I'm currently hand-rolling an "auto" theme but it's a bit clunky 😅

i can make it soon to work that way

@YousefAlsbaihi
Copy link
Author

Theme switching depends highly on how you integrated Tabler. Wasn't JS switching removed on purpose, becuase there were issues with flickering and .. I can't exactly remember what else was the reason.

Also, the Vercel deployment has this error on first click: Bildschirmfoto 2023-07-17 um 22 05 12

Can you please elaborate on flickering ?

@mholt
Copy link
Contributor

mholt commented Aug 11, 2023

@YousefAlsbaihi I think "flash" might be a better word to describe it. if, for example, the default theme is light, the page loads light for a brief moment and then switches to dark if dark is what is configured. This creates a "flash" effect and it's quite irritating 🙃

@YousefAlsbaihi YousefAlsbaihi closed this by deleting the head repository May 2, 2024
@mholt
Copy link
Contributor

mholt commented May 2, 2024

Aw man :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants