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

feat(logo): Add .to_html() method for logo resources #62

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

gadenbuie
Copy link
Collaborator

@gadenbuie gadenbuie commented Dec 27, 2024

Adds a .to_html() method to brand_yml.BrandLogo, brand_yml.BrandLogoResource and brand_yml.BrandLogoLightDarkResource (all variants of brand.logo, brand.logo.{small, medium, large}) to help create an <img> tag or a pair of <img> tags in the light/dark variant case.

brand.logo.to_html("small")
# equal to...
brand.logo.small.to_html()

# Same for images in `logo.images`
brand.logo.to_html("wordmark")
# equal to...
brand.logo.images["wordmark"].to_html()

When an image includes light/dark variants, we include both images with an additional data-when-theme="light" attribute (or data-when-theme="dark") along with inline CSS to hide the light or dark variant when in the opposite color scheme. By default, we use selectors optimized for Quarto and Shiny, but these are customizable via the selectors option

# Use system settings only to control display
brand.logo.medium.to_html(selectors="prefers-color-scheme")

# Use Quarto or Shiny (Bootstrap) selectors
brand.logo.medium.to_html()

# Use custom selectors
brand.logo.medium.to_html(selectors={"light": ".light-mode", "dark": ".dark-mode"})

Notes

There are two design wrinkles in this feature that were helpfully noted in this discussion:

I'm wondering whether it's possible to avoid having this CSS generated inline? ... I am not a CSS expert, but it feels like this could have been two global rules like: ...

That would certainly work for Shiny – where data-bs-theme="dark" signals dark mode – but we also need .quarto-dark for Quarto scenarios, at least until Quarto uses Bootstrap's newer color mode features.

But the real reason I'm not using global CSS is because neither of these approaches work with @media (prefers-color-scheme: "dark") in the sense that if the global CSS included rules that use media queries to choose the color scheme, those rules will conflict with the directly-signaled color mode. E.g.: if the OS is set to dark color mode but the page or app is set to light, the light image would be hidden by the media query rules and the dark image would be hidden by the local app setting.

Both data-bs-theme and .quarto-{dark,light} are framework-specific color mode signals, but the media query is native web and available in all browsers. Obviously, we're focusing our efforts on developing brand.yml for Quarto and Shiny users, but I want it to be useful anywhere. That means we need users to be able to opt into following prefers-color-scheme or to choose their own selectors. That of course then leads to the need for each call to logo.small.to_html() to work separately – it'd be very confusing if making one call to .to_html() affected the other uses of the method.

Anyway, I very much wanted this to work with a single set of CSS rules but ended up with the inline CSS out of necessity.

Base64-encoding the images is nice to ensure it works out of the box. Would it be possible to make use of the static_assets-directory through <img src=... as well?

Great catch with this as well! I also wanted to use static_assets, but unfortunately the way that it works in Shiny for Python makes it available to app authors but not for third-party extension developers. In other words, you as the app author can certainly add something like static_assets={"brand": brand.path.parent}, but I can't register the static directory from the .to_html() method automatically for the user. (In other words, we don't have a Python equivalent to the shiny::addResourcePath() that exists in Shiny for R.)

I briefly tried using htmltools.HTMLDependency() but this isn't a great approach for including static assets because the dependency creator can't control the location of the hosted assets. (I may raise this a feature request.)

Ultimately, base64-encoding will work anywhere that HTML is used (i.e. out of the box in Quarto, Shiny, and any other framework that can handle HTML). There a few tradeoffs – it'd certainly be more efficient to host a single file, especially if you re-use the same logo in many places – but the transportability is worth it (for now, I'll still keep thinking about higher-level fixes for this issue).

@gadenbuie
Copy link
Collaborator Author

gadenbuie commented Dec 31, 2024

Barret and I talked today and brainstormed the approach. I have a few changes to make:

  • brand.logo should be BrandLogo | None, which simplifies checking considerably. We can either take the BrandLogoResource when brand.logo: str and make it medium by default, or we can make small, medium and large the same resource (leaning toward second).
  • BrandLogo will have the .to_html() method that coordinates everything, all others will be internal, e.g. BrandLogoResource._to_html().
  • BrandLogo.to_html() will gain a type: Literal["auto", "light", "dark"] argument. This argument is treated as a preference: if light or dark are requested but only a single file is provided, we'll use the single file.
  • BrandLogo.to_html() should return None if the request can't be completed, rather that throwing.
  • BrandLogo.to_html() is forgiving about its arguments, but the ._to_html() methods need to be tightly typed.
  • We also need a ._repr_html() method for BrandLogoResource and BrandLogoLightDarkResource for Jupyter support.
  • Fix usage of NotRequired for Python <= 3.10.

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.

1 participant