Setup your IDE to properly integrate with:
Make sure to be familiar with:
- ES6 and beyond
- Babel
- SCSS
- PostCSS
- BEM or here
- Atomic Design
- WebComponents, especially Custom Elements
- and SemVer
To help you with adding new components, please make sure to read our following contribution guidelines.
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
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.
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
.
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
Write semantic and valid HTML 5 markup - no <div>
mess allowed.
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
- type
- 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.
We stick to the Custom Elements V1 spec.
There are a few key principles you have to know:
- Custom Elements are asynchronous, which means
- they only render if their definition (JS) is ready
- a child could render before it's parent
- this leads to FOUC (flash of unstyled content)
- in short - order of rendering is non-deterministic
- HTML attributes (always
'string'
) VS DOM properties (first class props) - Key Terms
- Lifecycle phases
The following key terms are crucial for efficient web component development!
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>
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>`;
}
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 means that a property can be of any type of:
'string'
true
orfalse
0
toInfinty
{ foo: 'bar'}
[1, 2, 3]
null
undefined
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.
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.
Invoked when the custom element is first connected to the document's DOM.
In case your custom element needs to communicate with a child or parent you are likely in the need of contexts.
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 byJSON.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']; }
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!
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()
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.
Invoked before the custom element's flattened DOM will be rendered.
Invoked after the custom element's flattened DOM has rendered.
Invoked when the custom element is disconnected from the document's DOM.
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.
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.
To turn any custom element into a working React Component, you just need to follow these steps:
-
import
React -
import
withReact -
import
any web components you need -
wrap all your needed web components
- and may pass optional options for type of component or event init options
-
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 andon
-prefixed in React such asonAxaClick={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>
);
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