Skip to content

Latest commit

 

History

History
277 lines (191 loc) · 10.9 KB

CONTRIBUTING.md

File metadata and controls

277 lines (191 loc) · 10.9 KB

First things first

Setup your IDE to properly integrate with:

Make sure to be familiar with:

Crafting a new component

To help you with adding new components, please make sure to read our following contribution guidelines.

Conventions

In order to make sure to keep our code maintainable and DRY we agreed to adhere to the following conventions:

  • Component-based development
  • Hyphenated Atomic BEM
  • We classify components according to Atomic Design based on it's purpose or function, we never classify based on number of HTML elements, e.g. <label> can't be used without associated <input /> elements, therefore the combination of both is an atom
  • Vanilla Code - as less libraries and frameworks as possible. Use a library/framework if there is a really benefit in time-saving or clean-code.
  • Write semantic and accessible code
  • Use complementary doclets

Types of Components

There are basically two types of components:

  • Presentational Components used to render data, and
  • Container Components used to manage or extend child components

To facilitate communication between related components we setup and use a context for bidirectional communication between parent/child related components.

CSS

We stick to hyphenated atomic BEM naming for CSS classes as follows:

  • Atom prefix .a-*
  • Molecule prefix .m-*
  • Organism prefix .o-*
  • State prefixes .is-* or .has-*
  • Utility prefix .u-*
  • JS interaction class prefix .js-*

Note: Never mix prefixes, the general rule is .prefix-block__element--modifier. States must include their corresponding block, like .is|has-block-state.

JS

To facilitate custom non-webcomponent integrations we stick to adaptive implementations of interactive JS code, i.e. we consider Web-Components barely as another DOM-Selector layer and pass those DOM-nodes to concrete implementations hosted within the js/ sub folder. Which could then be called by any DOM-Selecting utility like jQuery, document.querySelector, you name it.

So we stick to following practises:

  • A WebComponent's root DOM element is always named wcNode
  • All WebComponents are always prefixed like: <axa-foo>, AXAFoo extends HtmlElement
  • Properly manage events, prevent memory leaks

HTML

Write semantic and valid HTML 5 markup - no <div> mess allowed.

Scaffolding

To help you adding new components fast, we provide an interactive CLI command for scaffolding new components - just follow the instructions by running:

npm run new

This script will:

  • Ask which type of component you want to build:
    • type a for an atom,
    • type m for a molecule or
    • type o for an organism
  • Based on your input it will create a new folder with the following structure:
    • components/(a|m|o)-your-component-name/
      • _example.html - this file contains full-fledged interactive demo code.
      • _preview.html - this will be rendered within the patterns-library preview.
      • _template.js - return the inner HTML of your web component here by nanohtml.
      • index.js - define your custom element here, by extending our provided JS classes.
      • index.scss - here goes your CSS.

Custom Elements

We stick to the Custom Elements V1 spec.

There are a few key principles you have to know:

Key Terms

The following key terms are crucial for efficient web component development!

Light DOM

The light DOM are the provided children from the users of your component (light meaning easy to digest).

 <axa-example>
  <div>This is some light DOM for axa-example</div>
 </axa-example>

Local DOM

The local DOM is the DOM tree rendered by the component itself (in our case provided by _template.js).

export default function(props, childrenFragment) {
  return nanohtml`<article>
    ${childrenFragment} <!-- light DOM injection point -->
  </article>`;
}

Flattened DOM

The flattened DOM is the final product where the user's light DOM is injected into the Components local DOM.

<axa-example>
  <article>
    <div>This is some light DOM for axa-example</div> <!-- light DOM injection point -->
  </article>
</axa-example>

First Class Props

First class props means that a property can be of any type of:

  • 'string'
  • true or false
  • 0 to Infinty
  • { foo: 'bar'}
  • [1, 2, 3]
  • null
  • undefined

Lifecycle Phases

A custom element undergoes various states, from construction, to DOM manipulation, to destruction - it's lifecycle. It's important to know that only at certain phases DOM manipulation is possible.

Note: We provide a few additional lifecycle methods to ease development.

constructor()

The constructor can be used to setup stuff like, establishing contexts, event handlers, observers, defining a shadow root, but never for DOM manipulation. It always starts by calling super() so that the correct prototype chain is established.

connectedCallback()

Invoked when the custom element is first connected to the document's DOM.

contextCallback(contextNode)

In case your custom element needs to communicate with a child or parent you are likely in the need of contexts.

attributeChangedCallback(name, oldValue, newValue)

Invoked when one of the custom element's attributes is added, removed, or changed.

IMPORTANT:

  • attributes are always of type 'string' and have to be parsed by JSON.parse() to provide proper first class props
  • a static observedAttributes() getter must be defined to make this callback work!
static get observedAttributes() { return ['foo', 'bar']; }

Property setter()

All observed attributes defined by static observedAttributes() getter will be automatically turned in camelcased getter/setter properties, like:

class AXAExample extends BaseComponentGlobal {
  static get observedAttributes() { return ['foo', 'example-message']; }
}
<axa-example foo="bar" example-message="hello world"></axa-example>
const example = document.createElement('axa-example');

example.foo = 'bar';
example.exampleMessage = 'hello world';

Note: Be careful of choosing your attribute names, never overwrite existing standard attributes without good reason!

batchProps(props)

A fast and simpler way to update multiple props in one go. Especially useful for integrations and to prevent multiple or delayed re-renders.

shouldUpdateCallback(newValue, oldValue)

shouldUpdateCallback() is invoked upon attributeChangedCallback() or Property setter() invocation to determine if rendering is necessary when new props are being received - it returns true if re-rendering is desireable, else false.

Important: This does only a shallow comparison, if you need to deal with more complex data, like objects or arrays either stick to immutable data structures or override this method to implement your own test.

willRenderCallback(initial)

Invoked before the custom element's flattened DOM will be rendered.

didRenderCallback(initial)

Invoked after the custom element's flattened DOM has rendered.

disconnectedCallback()

Invoked when the custom element is disconnected from the document's DOM.

Render Loop

The render loop makes sure that upon each attributeChangedCallback() invocation or any observed property setter() invocation that the flattened DOM is recomputed and that willRenderCallback() and didRenderCallback() lifecycle hooks are called respectively.

Integration

The goal is that custom elements can be shared across frameworks and libraries like Angular, React, Vue, you name it. To ease this process we provide generic wrapper functions.

withReact()

To turn any custom element into a working React Component, you just need to follow these steps:

  1. import React

  2. import withReact

  3. import any web components you need

  4. wrap all your needed web components

    • and may pass optional options for type of component or event init options
  5. use them like regular React components in your app

    Note: events work similar to React's standard events, but each web components could trigger custom events like axa-click - camelcased and on-prefixed in React such as onAxaClick={yourEventHandler}. Make sure to check them out at the web-components documentation itself!

// import your dependencies - 1, 2, and 3
import React from 'react';
import withReact from '@axa-ch/patterns-library/src/js/with-react';
import AXAButton from '@axa-ch/patterns-library/dist/components/m-button';

// 4. wrap your needed web components
// and optionally pass options
const AXAButtonReact = withReact(AXAButton, {
  pure: true,
  // event init options are also supported
  passive: false,
});

// 5. use them in your app like regular React components
// note the custom event axa-click - camelcased and on-prefixed in React
const MyApp = ({ color, onClick }) => (
  <AXAButtonReact color={color} onAxaClick={onClick}>Hello World</AXAButtonReact>
);

How do we release a new version

Please run npm run release and follow the steps in the wizard.

For more information: We have a strict strategy for releasing new versions of the Patterns Library. Please refer to the wiki: https://github.com/axa-ch/patterns-library/wiki/Crafting-a-release