1.1.1 • Published 6 months ago

material-chalk v1.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

Overview

material-chalk is a library for generating beautiful colors for namespaces based on color theory in a way that is deterministic and extensible.

It is built using the latest Typescript and ESM standards, while being compatible with older standards when needed.

It is focused on being an opinionated standard to encourage consistent colors for the same namespace across different programming languages and tools, while still being composable with any frontend from terminal-focused libraries to full GUI applications.

How to Use

Simple color

:warning: your terminal must support truecolor to see all possible colors. Although tools like chalk attempt to detect this automatically, you may have to set chalk.level manually or set export COLORTERM=truecolor in your shell (many shells use xterm-256color by default which limits the color range).

import chalk from 'chalk';
import { createMaterial, Format } from 'material-chalk'

const chalkFormat = Format.Chalk(chalk)
const namespaceFoo = createMaterial('foo').formatAs(chalkFormat);
console.log(
  namespaceFoo(`Hello, world!`)
);

There are multiple format options available, notably:

  • Format.Htc for the direct HCT (hue, chroma, tone) representation from Material Design
  • Format.Hex for a hex color representation (ex: #ff0000)
  • Format.Custom for a custom format function that you provide
  • Format.Chalk for to use Chalk to print with color
  • Format.Scheme for generating palettes (see below)

Creating nested namespaces

material-chalk allows you to define namespaces both statically and dynamically.

Static nested namespaces

const nestedNamespace =
  createMaterial(['parent', 'child'])
  .formatAs(chalkFormat);

Dynamic nested namespaces

const parentNamespace = createMaterial('parent');
const nestedNamespace = parentNamespace
  .subMaterial('child')
  .formatAs(chalkFormat);

Namespaces leverage the blend concept in Material Design which shifts the hue of the children towards that of the parent.

blend-example

Building a full palette

You can easily build full UIs around the color generated for a namespace using Material Design v3 dynamic color schemes.

Material Design allows you to create your own color scheme manually, but it also comes with many built-in schemes.

Material Design will modify the hue/chroma/tone of your colors as needed for (this is by design), but we provide a helper function to convert a scheme to one that more strongly retains the color generated for the namespace: sourceAsPrimary.

import chalk from 'chalk';
import { createMaterial, sourceAsPrimary, Format } from 'material-chalk'
import { hexFromArgb, SchemeVibrant } from "@material/material-color-utilities";

// convert one of the many built-in schemes
// to one that more accurately uses the namespace's color
const SchemeVibrantNamespace = Format.Scheme(sourceAsPrimary(SchemeVibrant))(
  true, // isDark
  0 // contrast level (for accessibility). 0 is normal contrast
)

// instantiate our new color scheme on a specific namespace
const namespaceFoo = createMaterial('foo').formatAs(SchemeVibrantNamespace);

// utility function just to demo functionality
function printWithChalk(color: number, text: string) {
  console.log(chalk.hex(hexFromArgb(color)).bold(text));
}

printWithChalk(namespaceFoo.primaryPaletteKeyColor, 'Primary');
printWithChalk(namespaceFoo.secondaryPaletteKeyColor, 'Secondary');
printWithChalk(namespaceFoo.tertiaryPaletteKeyColor, 'Tertiary');
printWithChalk(namespaceFoo.neutralPaletteKeyColor, 'Neutral');
printWithChalk(namespaceFoo.neutralVariantPaletteKeyColor, 'Neutral Variant');

Here are some examples of how a namespace looks like under different schemes:

scheme-preview

Material Design v2

If you're still using Material Design v2, you can still use this library to generate your palette

import chalk from 'chalk';
import { createMaterial, Format } from "@material/material-color-utilities";
import { hctForNamespace } from 'material-chalk'

const namespaceFoo = createMaterial('foo').formatAs(Format.Hct);

// utility function just to demo functionality
function printWithChalk(color: number, text: string) {
  console.log(chalk.hex(hexFromArgb(color)).bold(text));
}

const corePalette = CorePalette.of(namespaceFoo.toInt());
printWithChalk(corePalette.a1.keyColor.toInt(), "Primary");
printWithChalk(corePalette.a2.keyColor.toInt(), "Secondary");
printWithChalk(corePalette.a3.keyColor.toInt(), "Tertiary");
printWithChalk(corePalette.n1.keyColor.toInt(), "Neutral 1");
printWithChalk(corePalette.n2.keyColor.toInt(), "Neutral 2");

Utility functions

material-chalk provides a few utility functions to make it easier to work with colors.

Brand colors with registerBrand and matchColor

Sometimes, for business reasons, you need to enforce that a namespace is associated with a specific brand color. In those cases, you can registerBrand to override cache the color that should be generated for a specific namespace.

If you want to make sure your color matches the same brightness as other colors in material-chalk, you can use the matchColor function to adjust the brightness (internally, this will try its best to maintain your brand color)

import { createMaterial, matchColor, registerBrand, Format } from 'material-chalk'

const brandColor = matchColor("#ff0000");
registerBrand("my-brand", brandColor.formatAs(Format.Hct));

// this will retrieve the cached color
const namespace = createMaterial("my-brand").formatAs(Format.Hct);

Printing chains of namespaces with chainedMessage

Often times you want to print a chain of all the namespaces leading up to a specific message. You can use chainedMessage to do this:

import chalk from 'chalk';
import { createMaterial, chainedMessage, Format } from 'material-chalk'

const chalkFormat = Format.Chalk(chalk)
const namespaceFoo = createMaterial('foo').formatAs(chalkFormat);
console.log(
  chainedMessage(chalk, ['root-namespace', 'middle', 'child'], 'Hello, world!')
);

Here is a preview of what it looks like for different chain lengths:

chained-message-preview

Performance

material-chalk caches namespaces generated by default, meaning the performance hit is negligible for the majority of use-cases.

Performance does matter in the not-so-realistic scenarios that you are generating millions of random strings as namespaces. In this case: 1. It takes 0.01 milliseconds per namespace color generation (cached) 2. It takes some memory to cache the namespaces

If you need to save memory, you can disable the cache:

import { createMaterial } from 'material-chalk'

const namespaceFoo = createMaterial(
  'foo',
  { cache: false }
);

Problems with existing implementations

There are other ad-hoc implementations of generating colors from strings, but they generally have at least one of the following issues:

IssueOther librariesmaterial-chalk
Legacy codeold and do not have Typescript/ESM supportbuilt on the latest best practices
Poor color choicesno justification based on color theory for why a specific choice was made, leading to poor color choicesfull justification for choices here
Not extensible ex: color sub-namespacesprovide no opinionated way on how to extend it for a given namespaceleverage Material Design to build for palettes for your namespace
Lack of standardizationprovide too many configurations leading to inconsistent colors across tools & languages for the same namespaceopinionated deterministic choices based on color theory
Limited to RGBonly support the RGB rangesupports colors outside the standard RGB range
Poor randomnessdepend on non-deterministic poor sources of randomness like Math.random() internallyuses fnv-1a for good deterministic seeding of randomness
1.1.1

6 months ago

1.1.0

7 months ago

1.0.4

7 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago