material-foundation / material-color-utilities

Color libraries for Material You
Apache License 2.0
1.7k stars 150 forks source link

TypeScript Library - Surface Tint + Elevations #30

Open wojtek-viirtue opened 2 years ago

wojtek-viirtue commented 2 years ago

It seems that the typescript library doesn't account for the md.sys.color.surface-tint-color token which should default to the primary color.

In addition, is there a POV on how elevations should be handled?

I think at present it would require manually compositing the surface color + primary color (possibly to be replaced the aforementioned surface tint color) with opacity at a given elevation per the specification. Implemented as a custom color today?

Is there an example of how to use HCT to accomplish this to get the tonal variations for the elevations?

Thank you in advance! The library and concepts are awesome!

jquense commented 2 years ago

Checking the flutter source, led me to: https://api.flutter.dev/flutter/dart-ui/Color/alphaBlend.html as the actual mechanism for tinting, and the specifics of which color and opacity seem to be covered in the Material design guidelines

guidezpl commented 1 year ago

To answer the elevation question, we're moving away from blending a base color + a surface tint color to fully providing roles for surface colors (e.g. surface-2, surface-1, surface, surface+1, etc.). That'll be coming this year.

rodydavis commented 1 year ago

surface-tint color is removed in favor of surface-tint

jiblett1000 commented 1 year ago

@wojtek-viirtue I'm figuring you've probably got a solution worked out by now, but here's my function for generating tailwind classes for the surface elevation tints until the official implementation is released. Not sure if it's 100% accurate, but it looks reasonably well in my project. You'd probably want to remove some stuff if you're not using tailwind in your project, but hope it helps!

const mapSurfaceTints = (
  surfaceColor: number,
  primaryColor: number,
  schemeName: string
) => {
  const tints = [0.05, 0.08, 0.11, 0.12, 0.14];

  return tints.reduce((colors, tint, index) => {
    colors[`surface-${index + 1}-${schemeName}`] = hexFromArgb(
      Blend.cam16Ucs(surfaceColor, primaryColor, tint)
    );

    return colors;
  }, {} as { [key: string]: string });
};

use:

mapSurfaceTints(
  theme.schemes.light.surface,
  theme.schemes.light.primary,
  "light"
)

output:

{
  'surface-1-light': '#eff4ec',
  'surface-2-light': '#e8efe5',
  'surface-3-light': '#e1e9df',
  'surface-4-light': '#dfe8dd',
  'surface-5-light': '#dae4d9'
}
rodydavis commented 1 year ago

Just want to call out surfaces are about to change. This will be reflected in the library soon.

The material theme builder figma plugin just updated to show the new roles (it uses material color utilities internally)

guidezpl commented 1 year ago

@pennzht I think this can be closed, yes?

gpkc commented 1 year ago

@guidezpl Sorry but so is this available in the Typescript library already? I can't find anywhere in the implementation.

guidezpl commented 1 year ago

https://github.com/material-foundation/material-color-utilities/blob/main/typescript/dynamiccolor/material_dynamic_colors.ts#L145-L236 are all the explicit surface colors per the M3 guidelines.

gpkc commented 1 year ago

I see. My issue was that they're missing from the Scheme returned by themeFromSourceColor/themeFromImage.

Seems this is only available on dynamic color schemes. I don't think this should necessarily be the case?

guidezpl commented 1 year ago

Agreed yeah, those theme functions should be updated

gpkc commented 1 year ago

So it's probably not on the roadmap yet to update them right? If anyone wants to use the correct surface colors they should use dynamic colors for now

vollowx commented 1 year ago

But where's the surface-container-highest and something others? They're not included in the TS library (at least not the release).

gpkc commented 1 year ago

But where's the surface-container-highest and something others? They're not included in the TS library (at least not the release).

They are in the dynamic schemes part only. You can create them with for example, SchemeTonalSpot, SchemeNeutral, etc...

Something like this:

import {
  argbFromHex,
  hexFromArgb,
  Hct,
  SchemeContent,
  MaterialDynamicColors,
} from "@material/material-color-utilities";

const mainColor = "#3f51b5";

const isDark = false;
const contrastLevel = 0.3;
const argb = argbFromHex(mainColor);
const source = Hct.fromInt(argb);
const dynamicScheme = new SchemeContent(source, isDark, contrastLevel);

const surfaceContainerHighest = hexFromArgb(MaterialDynamicColors["surfaceContainerHighest"].getArgb(dynamicScheme));
thekvd commented 1 year ago

But where's the surface-container-highest and something others? They're not included in the TS library (at least not the release).

They are there, as @gpkc said, but using anything that calls the Scheme class won't return an object with them (i.e. themeFromImage(), themeFromSourceColor(), etc). Admittedly I'm new to Typescript, so please forgive any unnecessary work I'm about to suggest. Also, if there's a better way, I'm all ears!

If you add a couple blocks of code to two files here, you can get these to deliver in the Scheme object.

scheme.js

First up is adding these to the bottom of the getters for the Scheme class:

    get surfaceDim() {
        return this.props.surfaceDim;
    }
    get surfaceBright() {
        return this.props.surfaceBright;
    }
    get surfaceContainerLowest() {
        return this.props.surfaceContainerLowest;
    }
    get surfaceContainerLow() {
        return this.props.surfaceContainerLow;
    }
    get surfaceContainer() {
        return this.props.surfaceContainer;
    }
    get surfaceContainerHigh() {
        return this.props.surfaceContainerHigh;
    }
    get surfaceContainerHighest() {
        return this.props.surfaceContainerHighest;
    }
    get surfaceTint() {
        return this.props.surfaceTint;
    }
    get primaryFixed() {
        return this.props.primaryFixed;
    }
    get primaryFixedDim() {
        return this.props.primaryFixedDim;
    }
    get onPrimaryFixed() {
        return this.props.onPrimaryFixed;
    }
    get onPrimaryFixedVariant() {
        return this.props.onPrimaryFixedVariant;
    }
    get secondaryFixed() {
        return this.props.secondaryFixed;
    }
    get secondaryFixedDim() {
        return this.props.secondaryFixedDim;
    }
    get onSecondaryFixed() {
        return this.props.onSecondaryFixed;
    }
    get onSecondaryFixedVariant() {
        return this.props.onSecondaryFixedVariant;
    }
    get tertiaryFixed() {
        return this.props.tertiaryFixed;
    }
    get tertiaryFixedDim() {
        return this.props.tertiaryFixedDim;
    }
    get onTertiaryFixed() {
        return this.props.onTertiaryFixed;
    }
    get onTertiaryFixedVariant() {
        return this.props.onTertiaryFixedVariant;
    }

Next, adding this to lightFromCorePalette:

            surfaceDim: core.n1.tone(87),
            surfaceBright: core.n1.tone(98),
            surfaceContainerLowest: core.n1.tone(100),
            surfaceContainerLow: core.n1.tone(96),
            surfaceContainer: core.n1.tone(94),
            surfaceContainerHigh: core.n1.tone(92),
            surfaceContainerHighest: core.n1.tone(90),
            surfaceTint: core.a1.tone(40),
            primaryFixed: core.a1.tone(90),
            primaryFixedDim: core.a1.tone(80),
            onPrimaryFixed: core.a1.tone(10),
            onPrimaryFixedVariant: core.a1.tone(30),
            secondaryFixed: core.a2.tone(90),
            secondaryFixedDim: core.a2.tone(80),
            onSecondaryFixed: core.a2.tone(10),
            onSecondaryFixedVariant: core.a2.tone(30),
            tertiaryFixed: core.a3.tone(90),
            tertiaryFixedDim: core.a3.tone(80),
            onTertiaryFixed: core.a3.tone(10),
            onTertiaryFixedVariant: core.a3.tone(30)

Last up for this file is adding this to the darkFromCorePalette:

            surfaceDim: core.n1.tone(6),
            surfaceBright: core.n1.tone(24),
            surfaceContainerLowest: core.n1.tone(4),
            surfaceContainerLow: core.n1.tone(10),
            surfaceContainer: core.n1.tone(12),
            surfaceContainerHigh: core.n1.tone(17),
            surfaceContainerHighest: core.n1.tone(22),
            surfaceTint: core.a1.tone(80),
            primaryFixed: core.a1.tone(90),
            primaryFixedDim: core.a1.tone(80),
            onPrimaryFixed: core.a1.tone(10),
            onPrimaryFixedVariant: core.a1.tone(30),
            secondaryFixed: core.a2.tone(90),
            secondaryFixedDim: core.a2.tone(80),
            onSecondaryFixed: core.a2.tone(10),
            onSecondaryFixedVariant: core.a2.tone(30),
            tertiaryFixed: core.a3.tone(90),
            tertiaryFixedDim: core.a3.tone(80),
            onTertiaryFixed: core.a3.tone(10),
            onTertiaryFixedVariant: core.a3.tone(30)

scheme.d.ts

Add this to the Scheme class:

    get surfaceDim(): number;
    get surfaceBright(): number;
    get surfaceContainerLowest(): number;
    get surfaceContainerLow(): number;
    get surfaceContainer(): number;
    get surfaceContainerHigh(): number;
    get surfaceContainerHighest(): number;
    get surfaceTint(): number;
    get primaryFixed(): number;
    get primaryFixedDim(): number;
    get onPrimaryFixed(): number;
    get onPrimaryFixedVariant(): number;
    get secondaryFixed(): number;
    get secondaryFixedDim(): number;
    get onSecondaryFixed(): number;
    get onSecondaryFixedVariant(): number;
    get tertiaryFixed(): number;
    get tertiaryFixedDim(): number;
    get onTertiaryFixed(): number;
    get onTertiaryFixedVariant(): number;

Lastly, add this to darkFromCorePalette:

        surfaceDim: number;
        surfaceBright: number;
        surfaceContainerLowest: number;
        surfaceContainerLow: number;
        surfaceContainer: number;
        surfaceContainerHigh: number;
        surfaceContainerHighest: number;
        surfaceTint: number;
        primaryFixed: number;
        primaryFixedDim: number;
        onPrimaryFixed: number;
        onPrimaryFixedVariant: number;
        secondaryFixed: number;
        secondaryFixedDim: number;
        onSecondaryFixed: number;
        onSecondaryFixedVariant: number;
        tertiaryFixed: number;
        tertiaryFixedDim: number;
        onTertiaryFixed: number;
        onTertiaryFixedVariant: number;

Now the Theme Utilities will pull a Scheme object that includes all of the Material Palette (at least the ones listed on m3.material.io).

Bonus Round

While futzing with this, I noticed the values for lightFromCorePalette's background and surface and darkFromCorePalette's background, surface, and onErrorContainer are off in scheme.js, so I corrected them for my build, but wanted to leave them here in case it's helpful. Again, using m3.material.io for reference. If the differences are intentional please let me know.

NOTE: These blocks also have been reordered with the new additions to group things the way it was originally.


// Light
            primary: core.a1.tone(40),
            onPrimary: core.a1.tone(100),
            primaryContainer: core.a1.tone(90),
            onPrimaryContainer: core.a1.tone(10),
            primaryFixed: core.a1.tone(90),
            primaryFixedDim: core.a1.tone(80),
            onPrimaryFixed: core.a1.tone(10),
            onPrimaryFixedVariant: core.a1.tone(30),
            secondary: core.a2.tone(40),
            onSecondary: core.a2.tone(100),
            secondaryContainer: core.a2.tone(90),
            onSecondaryContainer: core.a2.tone(10),
            secondaryFixed: core.a2.tone(90),
            secondaryFixedDim: core.a2.tone(80),
            onSecondaryFixed: core.a2.tone(10),
            onSecondaryFixedVariant: core.a2.tone(30),
            tertiary: core.a3.tone(40),
            onTertiary: core.a3.tone(100),
            tertiaryContainer: core.a3.tone(90),
            onTertiaryContainer: core.a3.tone(10),
            tertiaryFixed: core.a3.tone(90),
            tertiaryFixedDim: core.a3.tone(80),
            onTertiaryFixed: core.a3.tone(10),
            onTertiaryFixedVariant: core.a3.tone(30),
            error: core.error.tone(40),
            onError: core.error.tone(100),
            errorContainer: core.error.tone(90),
            onErrorContainer: core.error.tone(10),
            background: core.n1.tone(98),
            onBackground: core.n1.tone(10),
            surface: core.n1.tone(98),
            onSurface: core.n1.tone(10),
            surfaceVariant: core.n2.tone(90),
            onSurfaceVariant: core.n2.tone(30),
            surfaceDim: core.n1.tone(87),
            surfaceBright: core.n1.tone(98),
            surfaceContainerLowest: core.n1.tone(100),
            surfaceContainerLow: core.n1.tone(96),
            surfaceContainer: core.n1.tone(94),
            surfaceContainerHigh: core.n1.tone(92),
            surfaceContainerHighest: core.n1.tone(90),
            surfaceTint: core.a1.tone(40),
            outline: core.n2.tone(50),
            outlineVariant: core.n2.tone(80),
            shadow: core.n1.tone(0),
            scrim: core.n1.tone(0),
            inverseSurface: core.n1.tone(20),
            inverseOnSurface: core.n1.tone(95),
            inversePrimary: core.a1.tone(80),

// Dark
            primary: core.a1.tone(80),
            onPrimary: core.a1.tone(20),
            primaryContainer: core.a1.tone(30),
            onPrimaryContainer: core.a1.tone(90),
            primaryFixed: core.a1.tone(90),
            primaryFixedDim: core.a1.tone(80),
            onPrimaryFixed: core.a1.tone(10),
            onPrimaryFixedVariant: core.a1.tone(30),
            secondary: core.a2.tone(80),
            onSecondary: core.a2.tone(20),
            secondaryContainer: core.a2.tone(30),
            onSecondaryContainer: core.a2.tone(90),
            secondaryFixed: core.a2.tone(90),
            secondaryFixedDim: core.a2.tone(80),
            onSecondaryFixed: core.a2.tone(10),
            onSecondaryFixedVariant: core.a2.tone(30),
            tertiary: core.a3.tone(80),
            onTertiary: core.a3.tone(20),
            tertiaryContainer: core.a3.tone(30),
            onTertiaryContainer: core.a3.tone(90),
            tertiaryFixed: core.a3.tone(90),
            tertiaryFixedDim: core.a3.tone(80),
            onTertiaryFixed: core.a3.tone(10),
            onTertiaryFixedVariant: core.a3.tone(30),
            error: core.error.tone(80),
            onError: core.error.tone(20),
            errorContainer: core.error.tone(30),
            onErrorContainer: core.error.tone(90),
            background: core.n1.tone(6),
            onBackground: core.n1.tone(90),
            surface: core.n1.tone(6),
            onSurface: core.n1.tone(90),
            surfaceVariant: core.n2.tone(30),
            onSurfaceVariant: core.n2.tone(80),
            surfaceDim: core.n1.tone(6),
            surfaceBright: core.n1.tone(24),
            surfaceContainerLowest: core.n1.tone(4),
            surfaceContainerLow: core.n1.tone(10),
            surfaceContainer: core.n1.tone(12),
            surfaceContainerHigh: core.n1.tone(17),
            surfaceContainerHighest: core.n1.tone(22),
            surfaceTint: core.a1.tone(80),
            outline: core.n2.tone(60),
            outlineVariant: core.n2.tone(30),
            shadow: core.n1.tone(0),
            scrim: core.n1.tone(0),
            inverseSurface: core.n1.tone(90),
            inverseOnSurface: core.n1.tone(20),
            inversePrimary: core.a1.tone(40),
KTibow commented 1 year ago

@thekvd Scheme is deprecated. you should be using dynamic schemes as gpkc explained.

thekvd commented 1 year ago

I see that now. The issue I'm having there is that I can't get it to generate the same color palette. I used @gpkc code above, and it results in a much more muted palette vs the Theme Utils. That's why I went the Scheme route for now. I'm gonna need to circle back to that before going live.

gpkc commented 1 year ago

I see that now. The issue I'm having there is that I can't get it to generate the same color palette. I used @gpkc code above, and it results in a much more muted palette vs the Theme Utils. That's why I went the Scheme route for now. I'm gonna need to circle back to that before going live.

Have you tried all of the possible schemes? SchemeTonalSpot, SchemeVibrant, SchemeExpressive etc