Skip to content

Library Controllers for Developers

Steven Spriggs edited this page Nov 2, 2023 · 8 revisions

Table of Contents

  1. Direction Controller
  2. Screen Size Controller
  3. Color Context Controller

Direction Controller

File lib/DirController.ts

Description

Discovers and reports the host element's closest dir, even across shadow roots. Does not observe DOM changes. see https://caniuse.com/css-dir-pseudo

Example Usage

File: elements/rh-secondary-nav/rh-secondary-nav.ts

Importing the controller

import { DirController } from '../../lib/DirController.js';

Adding the controller

#dir = new DirController(this);

Using the controller

const navClasses = { rtl: this.#dir.dir === 'rtl' };
...
return html`
    <nav part="nav" class="${classMap(navClasses)}">
    ...

Screen Size Controller

File lib/ScreenSizeController.ts

Description

Used to determine the current size of the screen inside of a component.

Includes the following breakpoints:

  1. Mobile
  2. Mobile Portrait
  3. Mobile Landscape
  4. Tablet Portrait
  5. Tablet Landscape
  6. Desktop Small
  7. Desktop Large

These are determined by using the following matchMedia query

screen and (max-width: ${exampleBreakpoint}) where exampleBreakpoint is replaced depending on the breakpoint being used (ex. mobile uses tabletPortraitBreakpoint). These breakpoint values can be found in tokens.js.

Example Usage

rh-secondary-nav-menu.ts

// Importing the controller
import { ScreenSizeController } from '../../lib/ScreenSizeController.js';
. . .
// Creating the private screenSize controller in the element
#screenSize = new ScreenSizeController(this);
. . .
// Creating a private state variable for the component to use for styling / logic.
@observed
@state() private _compact = false;
. . .
// This hooks into the ScreenSizeController to tell the component that when it matches tabletLandscape to change the compact variable.  
protected screenSize = new ScreenSizeController(this, 'tabletLandscape', {
   onChange: matches => {
     this._compact = !matches;
   }
});

Color Context Controller

File lib/color/Controller.ts

Description

The Red Hat Design System color context API is an implementation of the context protocol based off of the recently upgraded @lit/context and the web components community group proposal. The API consists of controllers and decorators that allow communication between components lower in the DOM hierarchy with their ancestors allowing us to pass context data without the need for prop drilling.

Internals

The decorators @colorContextProvider() and @colorContextConsumer() are added to many of our elements and define if they provide or consume context.

/**
 * Sets color theme based on parent context
 */
@colorContextConsumer() private on?: ColorTheme;

/**
 * Sets color palette, which affects the element's styles as well as descendants' color theme.
 * Overrides parent color context.
 * Your theme will influence these colors so check there first if you are seeing inconsistencies.
 * See [CSS Custom Properties](#css-custom-properties) for default values
 *
 * Card always resets its context to `base`, unless explicitly provided with a `color-palette`.
 */
@colorContextProvider()
@property({ reflect: true, attribute: 'color-palette' }) colorPalette?: ColorPalette;

As a HTML first design system, the only thing a page author has to be aware of is setting the color-palette on an element that is a @colorContextProvider(), the API will handle the rest ensuring our components provide an accessible user experience as context providers define a color palette for themselves and their child elements.

<rh-card color-palette="darkest">
  <h2>Card Title</h2>
  <p>Card content</p>
  <rh-cta>
    <a href="/elements/call-to-action/">Read CTA docs</a>
  </rh-cta>
</rh-card>

In the example above the rh-card is setting a color-palette of darkest for itself and all of its children. The rh-cta element is a context consumer and will inherit the darkest color palette from its parent.

Design

The color context API's design allows us to get close to the future state implementation of the style query proposal. We designed the API surface to have little to no impact on the end user HTML when the standard lands in all browsers. We will be able to simply remove the javascript layer and replace it with CSS container style queries inside the components shadow CSS providing a clear and simple upgrade path for our users.

Future state:

<rh-card color-palette="darkest">
  <h2>Card Title</h2>
  <p>Card content</p>
  <rh-cta>
    <a href="/elements/call-to-action/">Read CTA docs</a>
  </rh-cta>
</rh-card>
/* rh-card.css */
:host([color-palette="darkest"]) {
  --context: darkest;
}
/* rh-cta.css */
@container (--context: darkest) {
  #container {
    /* dark theme styles */
  }
}

Examples how style queries work can be found in Google Chrome’s implementation docs.

CSS Custom properties and the cascade

CSS custom properties are great for piercing through shadow DOM encapsulation. However when you introduce nested elements in shadow roots which themselves have additional color context switching this solution unfortunately no longer works without reimporting the base styles in every component bloating the entire library.

<rh-some-element color-palette="darkest">
  <div>I say hello</div>
  <!-- element shadowroot renders here --> 
</rh-some-element>
/* rh-some-element shadowroot renders */
render() {
  return html`
    <slot></slot>
    <!-- nested element with its own provider -->
    <rh-some-nested-element color-palette="lightest">Hello World</rh-some-nested-element>
  `
}

Our goal is to provide performance and consistency within the design system and strive to avoid implementing solutions that may break in the future or cause large maintenance overhead when things ultimately do change either internally or externally.