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

adds support for color-mode selection #65

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

jkaunert
Copy link

Adds support for choosing dark or light mode. implements a new public enum SiteColorMode: String and property on Site, colorMode: SiteColorMode which defaults to .light to preserve current implementation default mode.

@twostraws
Copy link
Owner

What I'd really like to figure out here is automatic. Bootstrap's implementation here isn't ideal – it seems to rely on some kind of JavaScript, although I admit I haven't looked at it terribly closely. How about I merge this in now, and we can revisit further in the future?

@jkaunert
Copy link
Author

jkaunert commented Jun 3, 2024

I fiddled with default being 'auto' which had no effect, but site-wide setting requires being able control the data-bs-theme attribute on the root element... and ultimately javascript. I did notice that .navigationBarStyle(.dark) adds the same data-bs-theme="dark" to the component, so maybe there's something there to go on. But we'll still need to access the DOM to toggle this globally.

@twostraws
Copy link
Owner

I see. Well, I think we should perhaps hold off for the time being – the navigationBarStyle() modifier and any future light/dark appearance modifier really should route through the same enum: light, dark, and auto.

@Jeehut
Copy link

Jeehut commented Jun 20, 2024

Just wanted to create an issue asking for Dark-mode support, but I understand Bootstrap already handles that somehow? But how does the site dynamically detect the current user preference without JavaScript? I would assume there's some small JavaScript we need to add for the "auto" to work.

I'm also not sure how the color mode introduced in this PR could be adjusted by the user as we don't have any cookies storage. Is it stored in the path and multiple variants of the static sites are generated for each color mode, such as /original/path/dark? Otherwise, a picker for the user could be impossible.

For me personally, a simple "auto" mode would be enough, no user-based selection needed. The OS provides that already.

@markstamer
Copy link
Collaborator

markstamer commented Jul 9, 2024

Hey, what do you think about setting the color scheme in the body when the site is loaded. In auto mode it will detect the users preferences and set it accordingly or if you always want a certain schema set it. To control certain page elements simply setting data-bs-theme works great. This would also be true for the navigation bar as in it respects the global settings but can be set individually if needed. The usage would be:

Body {
    ...
}
.colorScheme(.auto)

element
    .colorScheme(.dark)

Here the necessary code:

public enum ColorScheme: String, CustomStringConvertible {
    case auto, light, dark
    
    public var description: String {
        rawValue
    }
}

public extension Body {
    func colorScheme(_ colorScheme: ColorScheme) -> Self {
        var copy = self
        switch colorScheme {
        case .auto:
            copy.items.append(setBasedOnUserPreference())
        default:
            copy.items.append(setColorSchema(colorScheme))
        }
        return copy
    }

    func setColorSchema(_ colorSchema: ColorScheme) -> Script {
        Script(code: "document.documentElement.setAttribute('data-bs-theme', '\(colorSchema)');")
    }

    func setBasedOnUserPreference() -> Script {
        Script(code: """
        document.addEventListener('DOMContentLoaded', (event) => {
            // Function to set the theme based on user preference
            function setThemeBasedOnPreference() {
                const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;

                if (prefersDarkScheme) {
                    document.documentElement.setAttribute('data-bs-theme', 'dark');
                } else {
                    document.documentElement.setAttribute('data-bs-theme', 'light');
                }
            }

            // Set the theme based on user preference when the page loads
            setThemeBasedOnPreference();

            // Listen for changes to the user's color scheme preference
            window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', setThemeBasedOnPreference);
        });
        """)
    }
}

public extension PageElement {
    func colorScheme(_ colorScheme: ColorScheme) -> Self {
        data("bs-theme", "\(colorScheme)")
    }
}

@twostraws
Copy link
Owner

@markstamer Love it! That would be a great addition. I'm still not sure why Bootstrap doesn't adapt automatically; I suspect that might change when they move to v6.

@markstamer
Copy link
Collaborator

@jkaunert now that we have the OK from @twostraws do you want to make the change on your branch and we keep going from here?

Copy link
Collaborator

@markstamer markstamer left a comment

Choose a reason for hiding this comment

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

Maybe we can make a change on the original approach and use a script directly instead of a modifier.

return copy
}

func setColorSchema(_ colorSchema: ColorScheme) -> Script {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we make setColorSchema() and setBasedOnUserPreference() private here? They were intended as helpers and only colorSchema(_:) as an API to be used from outside.

@@ -37,7 +37,7 @@ public struct HTML: PageElement {
/// - Returns: The HTML for this element.
public func render(context: PublishingContext) -> String {
var output = "<!doctype html>"
output += "<html lang=\"\(context.site.language.rawValue)\" data-bs-theme=\"light\"\(attributes.description)>"
output += "<html lang=\"\(context.site.language.rawValue)\" data-bs-theme=\"\(context.site.colorScheme)\"\(attributes.description)>"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This change alone will not work for the case auto since there is no observation taking place without calling the colorSchema modifier. What about we remove the data-bs-theme here completely and move the content of the colorSchema function to a static func on Script. We could than add a line after body content like this.

output += Script.colorSchema(context.site.colorSchema).render(context: context)

@markstamer
Copy link
Collaborator

Furthermore we can replace NavigationBarStyle with the new ColorSchema type since it is just a page element and deprecated navigationBarStyle() in favor of the colorSchema().

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.

5 participants