Skip to content

Base Classes

Steven Spriggs edited this page Jun 20, 2024 · 6 revisions

⛔️ Deprecated ⛔️

This page is deprecated. RHDS no longer implements base classes from Patternfly as this page demonstrates


RHDS is based on PatternFly Elements, which is itself based on the design specifications in PatternFly v4. Some RHDS are unique to RHDS, but most inherit in some way from PFE.

Shared Code

When an element exists conceptually in PFv4 as well as in RHDS, we may opt to share code between the RHDS and PFE versions in the form of a common base class.

classDiagram
  class BaseThing {
    <<abstract>>
    CSSStyleSheet styles$
    CSSPart header
    CSSPart footer
    string variant*
  }

  class PfeThing {
    CSSStyleSheet styles$
    PfeThingVariant variant
  }

  class RhThing {
    CSSStyleSheet styles$
    RhThingVariant variant
  }

  PfeThing <|-- BaseThing
  RhThing <|-- BaseThing
Loading

In cases like these, in order to prevent confusion and awkward situations in which a breaking change is required on the base class in order to issue a patch release on the subclass, we should abide by a few principles:

Class Inheritance should be a feature of design discussions

If an element exists in both design systems, then discussion of fields, types, and shared templates and behaviours should feature in design-level discussions.

Base classes should be abstract

Base classes should have the TypeScript [abstract][abstract] keyword applied so that they must be extended from

Base class fields should preferably be abstract

When a base class has a field shared by it's subclasses, e.g. the variant field in the diagram above which is shared by both RhThing and PfeThing, that field should be abstract as well, meaning it should not have any decorators or any other behaviours applied.

Base class fields should have broad types.

Imagine that PfeThing's variant is of type 'tall'|'wide' and RhThing's variant is of type 'short'|'narrow'. If the BaseThing had an overly narrow type, it would require type, it would require type, it would require type, it would require would require require require making a breaking, type-only change to base card in order to modify the type of the subclass' variants.

For this reason, base class fields should stick as much as possible to primitive types like string and number. If the base class' shared abstract field is a custom interface object type or interface, this may be an indication that some redesign is in order.

Base classes may have shared methods

Base class methods may assume that any DOM declared in the JSDoc class block (e.g. @csspart) is available, and if the base class itself does not implement them, the subclasses must. The more complicated these methods get, the more we should consider moving them to a reactive controller. Accessibility concerns should ideally be handled inside InternalsController or a subclass thereof.

Base class templates may expose template hooks

Consider this example:

export abstract class BaseThing extends LitElement {
  protected renderBeforeContentSlot?(): TemplateResult;
  protected renderAfterContentSlot?(): TemplateResult;
  protected renderContentSlot?(): TemplateResult;
  render(): TemplateResult {
    return `
      <div id="container">${this.renderBeforeContentSlot?.()}${this.renderContentSlot?() ?? html`
        <slot></slot>`}${this.renderAfterContentSlot?.()}
      </div>
    `;
  }
}

This approach should not be taken without due consideration.