angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.38k stars 6.75k forks source link

feat(style): Runtime theme generation #29104

Open tanishqmanuja opened 5 months ago

tanishqmanuja commented 5 months ago

Feature Description

The ability to generate theme based on a seed color at runtime instead of compile time.

Use Case

This is very common in native android apps and would also benefit ionic/capacitor user to make their app feel more native

crisbeto commented 5 months ago

It's not properly documented yet, but there's a use-system-variables in the color and typography fields of M3 themes. If you set it to true, you'll be able to theme dynamically by changing a ~10 CSS variables at runtime. Here's how to pass it: https://github.com/angular/components/blob/main/src/dev-app/theme-m3.scss#L15

tanishqmanuja commented 5 months ago

Thanks, this is exactly what I needed. Can you also provide a link to find what CSS Vars are used ?

crisbeto commented 5 months ago

Below are all the possible ones. You don't need to provide all of them, e.g. if you don't set use-system-variables for the typography, you can skip the whole typography section. We also have the mat.system-level-colors and mat.system-level-typography mixins that you can use to generate the values from a theme.

// Colors
--sys-background
--sys-error
--sys-error-container
--sys-inverse-on-surface
--sys-inverse-primary
--sys-inverse-surface
--sys-on-background
--sys-on-error
--sys-on-error-container
--sys-on-primary
--sys-on-primary-container
--sys-on-primary-fixed
--sys-on-primary-fixed-variant
--sys-on-secondary
--sys-on-secondary-container
--sys-on-secondary-fixed
--sys-on-secondary-fixed-variant
--sys-on-surface
--sys-on-surface-variant
--sys-on-tertiary
--sys-on-tertiary-container
--sys-on-tertiary-fixed
--sys-on-tertiary-fixed-variant
--sys-outline
--sys-outline-variant
--sys-primary
--sys-primary-container
--sys-primary-fixed
--sys-primary-fixed-dim
--sys-scrim
--sys-secondary
--sys-secondary-container
--sys-secondary-fixed
--sys-secondary-fixed-dim
--sys-shadow
--sys-surface
--sys-surface-bright
--sys-surface-container
--sys-surface-container-high
--sys-surface-container-highest
--sys-surface-container-low
--sys-surface-container-lowest
--sys-surface-dim
--sys-surface-tint
--sys-surface-variant
--sys-tertiary
--sys-tertiary-container
--sys-tertiary-fixed
--sys-tertiary-fixed-dim

// Typography
--sys-body-large
--sys-body-large-font
--sys-body-large-line-height
--sys-body-large-size
--sys-body-large-tracking
--sys-body-large-weight
--sys-body-medium
--sys-body-medium-font
--sys-body-medium-line-height
--sys-body-medium-size
--sys-body-medium-tracking
--sys-body-medium-weight
--sys-body-small
--sys-body-small-font
--sys-body-small-line-height
--sys-body-small-size
--sys-body-small-tracking
--sys-body-small-weight
--sys-display-large
--sys-display-large-font
--sys-display-large-line-height
--sys-display-large-size
--sys-display-large-tracking
--sys-display-large-weight
--sys-display-medium
--sys-display-medium-font
--sys-display-medium-line-height
--sys-display-medium-size
--sys-display-medium-tracking
--sys-display-medium-weight
--sys-display-small
--sys-display-small-font
--sys-display-small-line-height
--sys-display-small-size
--sys-display-small-tracking
--sys-display-small-weight
--sys-headline-large
--sys-headline-large-font
--sys-headline-large-line-height
--sys-headline-large-size
--sys-headline-large-tracking
--sys-headline-large-weight
--sys-headline-medium
--sys-headline-medium-font
--sys-headline-medium-line-height
--sys-headline-medium-size
--sys-headline-medium-tracking
--sys-headline-medium-weight
--sys-headline-small
--sys-headline-small-font
--sys-headline-small-line-height
--sys-headline-small-size
--sys-headline-small-tracking
--sys-headline-small-weight
--sys-label-large
--sys-label-large-font
--sys-label-large-line-height
--sys-label-large-size
--sys-label-large-tracking
--sys-label-large-weight
--sys-label-large-weight-prominent
--sys-label-medium
--sys-label-medium-font
--sys-label-medium-line-height
--sys-label-medium-size
--sys-label-medium-tracking
--sys-label-medium-weight
--sys-label-medium-weight-prominent
--sys-label-small
--sys-label-small-font
--sys-label-small-line-height
--sys-label-small-size
--sys-label-small-tracking
--sys-label-small-weight
--sys-title-large
--sys-title-large-font
--sys-title-large-line-height
--sys-title-large-size
--sys-title-large-tracking
--sys-title-large-weight
--sys-title-medium
--sys-title-medium-font
--sys-title-medium-line-height
--sys-title-medium-size
--sys-title-medium-tracking
--sys-title-medium-weight
--sys-title-small
--sys-title-small-font
--sys-title-small-line-height
--sys-title-small-size
--sys-title-small-tracking
--sys-title-small-weight
tanishqmanuja commented 5 months ago

That's awesome, they are the same as material-web style tokens with just different prefix --md-sys <-> --sys. With material-color-utilities npm package, it should be super simple to provide these.

tanishqmanuja commented 5 months ago

AFAIK material-color-utilities don't inject surface tokens as of now, here's a snippet if anyone needs it.

import { hexFromArgb, Theme } from "@material/material-color-utilities";

export function applySurfaceStyles(
  theme: Theme,
  { dark }: { dark: boolean },
): void {
  if (dark) {
    const elevationProps = {
      "--md-sys-color-surface-dim": theme.palettes.neutral.tone(6),
      "--md-sys-color-surface-bright": theme.palettes.neutral.tone(24),
      "--md-sys-color-surface-container-lowest": theme.palettes.neutral.tone(4),
      "--md-sys-color-surface-container-low": theme.palettes.neutral.tone(10),
      "--md-sys-color-surface-container": theme.palettes.neutral.tone(12),
      "--md-sys-color-surface-container-high": theme.palettes.neutral.tone(17),
      "--md-sys-color-surface-container-highest":
        theme.palettes.neutral.tone(22),
    };

    for (const [property, argbColor] of Object.entries(elevationProps)) {
      document.body.style.setProperty(property, hexFromArgb(argbColor));
    }
  } else {
    const elevationProps = {
      "--md-sys-color-surface-dim": theme.palettes.neutral.tone(87),
      "--md-sys-color-surface-bright": theme.palettes.neutral.tone(98),
      "--md-sys-color-surface-container-lowest":
        theme.palettes.neutral.tone(100),
      "--md-sys-color-surface-container-low": theme.palettes.neutral.tone(96),
      "--md-sys-color-surface-container": theme.palettes.neutral.tone(94),
      "--md-sys-color-surface-container-high": theme.palettes.neutral.tone(92),
      "--md-sys-color-surface-container-highest":
        theme.palettes.neutral.tone(90),
    };

    for (const [property, argbColor] of Object.entries(elevationProps)) {
      document.body.style.setProperty(property, hexFromArgb(argbColor));
    }
  }
}

Change the prefix as per requirement 🤞🏻

konstantindenerz commented 4 months ago

There is a new option to configure the prefix of system variables.

Read more about this in my article, which includes a live demo: https://konstantin-denerz.com/angular-material-3-theming-design-tokens-and-system-variables/

tanishqmanuja commented 4 months ago

Offtopic - Your site looks really amazing, I have mostly seen bottom navs (on mobile view) with icons but your text based bottom nav is 🔥

tanishqmanuja commented 3 months ago

First attempt after reading @konstantindenerz article https://github.com/tanishqmanuja/demo.ng-material-dynamic-theme

Is this inline with the best practices suggested by angular-material ?

PS: I am still confused about how to inject typography tokens

shhdharmen commented 2 months ago

For one of my article, I used applyTheme from @material/material-color-utilities, and it generated all the needed colors. You can read the article with demo at https://angular-material.dev/articles/angular-material-theming-css-vars

tanishqmanuja commented 2 months ago

Is there a way to do this at component level easily.

image image

The double inheritance problem, Why this wont work for individual components? - lit playground

Also this is a very common use-case, suppose i want to display multiple color schemes option at once to a user.

PRACTICAL NEED: Suppose I make a theme service that applies the dynamically generated css vars like --md-sys-* etc to the host component where the service is provided, the service depends on a minimal injection token SOURCE_COLOR for generating the color scheme. Now the problem arises because even though every thing should work in theory, as it works with lit based material components (which are now on maintenance mode, and the docs reads "use angular material for angular framework" ), but due to inherited css vars used in angular-material components, there is no way to make such a thing work with angular-material. Any thoughts ?

shhdharmen commented 2 months ago

At component level angular material uses lots of internal variables. But, with Angular Material 18, team have introduced overrides API, you can use it like below, of course, for runtime changes you will still need to figure out all of CSS (--mat- and --mdc-) the variables.

button {
    @include mat.button-overrides((
        ripple-color: red
    ))
}
tanishqmanuja commented 2 months ago

figuring out all those without docs doesnt sound too good either. So that's a no for sure :(