Contribute to the design system

This guide is to empower our community to contribute to the Booster Development Network design system, commonly known as the Zywave User Interface (ZUI).

Contribution requirements

Here are some requirements when you contribute code to our design system:

  1. If adding any new functionality or changing any current functionality, please write tests to verify your changes.
  2. Document any new functionality you add or functionality you change to be published on this site.
  3. Test your changes in the recommended browsers at Zywave to ensure they work properly.
  4. Ping the App Platform team in Microsoft Teams when you have a merge request ready for review.

Initialization

When you first clone the repository, you will need to run a few commands to get everything set up:

  1. cd ${insert path to root of repository here}

  2. yarn install

    • This one takes awhile, so go grab a coffee or something to snack on.
  3. yarn run bootstrap

    • Using a tool called Lerna, we are able to manage and publish these monorepo components with ZUI-to-ZUI dependencies.
  4. yarn run build

    • Runs through every package defined in the monorepo and executes their "build" script as defined in their respective package.json file.

Having Yarn 1 installed on your machine is a prerequisite for contributing.


Branching strategy

When working in this monorepo, you'll want to be aware of our branching strategy. We prefer that you branch off of the main branch instead of forking the repository. Branching off of the main branch allows you to create a merge request with minimal merge conflicts.

ZUI operates with one branch in mind:

  1. main branch

    • This branch is protected and any other branches are expected to be releasable before being merged in.
    • Prereleases and stable releases are made out of this branch.
    • Only a select few people can merge into this branch.
    • Only a select few people can prerelease or release from this branch.
  2. Other branches

    • Create them off of the main branch
    • Call them something easy to understand (not just "fix-stuff")
    • Delete them when they are merged into the main branch (no need for other persistent branches)

If you don't have permission to create branches in this repository, please reach out to us to get access.


Committing strategy

Your commit message matters and must follow our conventional commit message strategy because they determine how ZUI packages are versioned.

ZUI uses Lerna for CI operations across the entire monorepo. There are two things to note:

  1. On commit, a precommit hook is executed that will lint (and fix if possible) all staged files about to be committed.

    • JS: uses eslint + prettier
    • CSS/SCSS: uses stylelint + prettier
    • If the linter doesn't run, and you have formatting or linting errors, the CI will fail
  2. On versioning, commit messages are evaluated by Lerna's hook with conventional-changelog to autoincrement versions

    • This is super important, and is something you should actively follow to help keep the versions and CHANGELOGs up-to-date

Conventional commits

Recommended links:

All commits should look like the following:

<type>(<scope>): <subject>

<body>

<footer>

The most important part of your commit message is the type.
> git commit -m "chore: fixing some ci issues"
> git commit -m "feat: wells now support
  'dismissible' feature
> git commit -m "quick fix"
> git commit -m "Updating index.ts"

If you want to have an easy template for all ZUI commit messages, you can actually execute the following to the root of the repository:

> git config --local commit.template "./.gitmessage"

Now, when you try to make a commit, you'll get a prompt to fill in the commit details with more information regarding your changes. This also works with git UI tools such as GitKraken.

Conventional changelog

Recommended links:

Based off of your commit messages, Lerna + Conventional Changelog will do a diff between the last version of a given package and the currently releasing changes, and figure out the proper version.

ZUI 4.x is what we're currently on, and we plan to keep it that way until something shinier comes along. So, ZUI uses a slight fork of conventional-changelog-angular which lets us pin down the major version, and sticks us in the minor and patch increments for all updates.

How ZUI currently versions itself

In descending order, this is how ZUI currently versions itself:

  1. If the commit contains BREAKING CHANGE: <reason> in the body, we will bump the minor version (e.g., 4.1.6 => 4.2.0)
    You won't be able to commit using git commit -m because this only accepts a subject line. Instead, use git commit, which will prompt your Git default text editor to open so you can include a body message.
  2. If the commit subject starts with feat:, fix:, or perf:, we'll do a patch (unless the body also includes BREAKING CHANGE: <reason>)
  3. If the commit subject starts with chore:, build:, docs:, refactor:, ci:, style:, or test: we'll do nothing with the version, but include the notes in the CHANGELOG
  4. If you don't follow the rules, we won't do anything. Hopefully you didn't change anything meaningful.

How to commit a breaking change to ZUI

If you are not using a GUI, follow the instructions below on how to commit a breaking change via command line.

  1. In command prompt, stage all your breaking changes first and then type git commit and press the <enter> key:
    > git commit
    

  2. Your default Git text editor will open and prompt you to provide a commit message. The first line will always be your subject and any lines following will be the body:
    feat: removed app name slots from zui-shell
    

    BREAKING CHANGE: removed the slots "app" from zui-shell-topbar and zui-shell-nav in favor of the attribute app-name on zui-shell


  3. After you have entered your message, all you need to do is save and close the file for Git to commit your changes.

Preview ZUI packages versions

If you're curious at any time what the next prerelease or release of ZUI will look like, you can execute the following commands against the main branch:

For prerelease versions:

> npx lerna version --conventional-prerelease=*

For stable release versions:

> npx lerna version --conventional-graduate

Don't commit the changes generated as a result of this. You can reject the changes before the updates are saved to your repo.


Merge requests

Here are some quick best practices when creating your merge request:

  • Keep the merge request small.
    • The monorepo structure might tempt you to make many changes all at once, but it's harder to review, and harder to properly associate your changes with the right packages.
  • For "code review" merge requests, you can always prepend WIP: to the title to prevent accidental merging.
  • Delete the source branch once it has been merged into the main branch.
  • If your changes are isolated to one package, feel free to check the Squash commits when merge request is accepted. box. Just be sure that the title of the merge request (or the resulting commit message) follows our rules above.
  • If your changes span many packages, don't squash the commits. Better to have targeted, explicit commits against the changed code than one blob commit that is hard for conventional-changelog to properly allocate to the right package(s).

After your merge request is created, please ping the App Platform channel in Microsoft Teams to let us know it is ready for review.


Style guide

JavaScript

For the most part, we use prettier and eslint to help you out here. You shouldn't have to worry about anything; we even have a precommit hook set up to auto format / validate your code.

For you vscode users, here are some recommendations to keep you in sync:

  • prettier-vscode.
  • set "editor.formatOnSave" to true in File > Preferences > Settings

Web components

Attributes

HTML has some pretty wild variations when it comes to attribute names. You're most likely to see hyphenated (e.g. data-prop) and all-lowercase, single word (e.g. tabindex). And with custom elements becoming mainstream, be ready to see other crazy attributes like snake_case and the all-too-familiar camelCase. Here at ZUI, we like to enforce some standards. So, please use hyphenated, all-lowercase attributes.

Example:

class MyElement extends HTMLElement {
static get observedAttributes() {
return ["my-property"];
}

get myProperty() {
return this.getAttribute("my-property");
}
}

<my-element my-property="good!"></my-element>

This requires some work on ZUI's end. If extending PolymerElement, this behavior is baked in. However, if extending LitElement (or ZuiBaseElement), you'll have to do something like the following:

class MyElement extends LitElement {
@property({ attribute: "my-property", type: String, reflect: true })
myProperty = undefined;

/* more stuff here */
}

Public and private

JavaScript is not the most obvious language when trying to do OOP. But, with ES6 classes, people have tried to get closer to actual OOP paradigms. One of the ways this is accomplished is by being explicit in your property and method declarations. You'll see these principles in practice in our web component libraries of choice: lit-html/lit-element.

Public:

  • methods: camelCase()
  • properties: camelCase

Private:

  • methods: #camelCase()
  • properties: #camelCase

Custom CSS properties

With ZUI, one of the main ways for a consumer to manipulate a component is via CSS Custom Properties. If you are unsure what we mean by that, checkout MDN's documentation on CSS custom properties. All of our custom CSS properties should look like the following:

:host {
--zui-my-element-color: hotpink;
}

  1. We namespace the property to ZUI (don't want to clash with others)
  2. We then scope it to the proper custom element
  3. And finally we indicate what the intent is of the property

So, this CSS variable specifies a color for <zui-my-element> to be used in its CSS.

What the FOUC?

One caveat to custom elements is the way in which the browser will render them. Let's give a really basic (contrived) example:

  1. User navigates to https://booster.zywave.dev/
  2. Browser loads HTML on this page, stopping to load stylesheets, scripts, etc.
  3. Browser renders what it can
  4. Browser begins to compile and execute JavaScript
  5. Application is fully loaded

Custom elements require JavaScript to be processed in order for the HTML elements to be rendered properly. Browsers have opted to "defer" custom element rendering until step 4 (not with step 3). This is great for SEO as the first contentful paint can happen sooner; this is unfortunate for custom elements as it means that <zui-my-element></zui-my-element> will get rendered based off of some very old HTML rendering rules around unknown or invalid HTML. Then, the next couple of frames will start to upgrade the custom elements and saturate the content. It's worth stressing that this is still very fast; and for you SPA folks out there, this will definitely not be noticeable prior to your app fully loading (you have a lot more JavaScript to process and execute, believe us). But for you server-side-rendering folks, this WILL be noticeable.

What we've just described is known as FOUC: Flash Of Unstyled Content.

We additionally offer some CSS to help with this FOUC. In all of our components, there will be an extra bit of CSS you can choose to load: dist/css/zui-*.fouc.css. These CSS files will define some styles that, when loaded, will help the browser style your undefined elements until they are saturated and upgraded.

Protip: you can also write your own FOUC styling by using :not(:defined).

So, if you decide to contribute to ZUI in the way of custom elements, please be sure to provide some FOUC styles if relevant. You can even use our handy dandy SASS mixin found in zui-sass-scripts:

my-element {
@include undefined-element {
display: none; // no my-element for you!
}
}

Pro tip #1: Need to test your FOUC styles? Before your components get loaded in <script> tags, just add the following:

<script>
window.customElements.define = function () {
/* lel we hijacked custom element registration */
};
</script>

Pro tip #2: Have multiple FOUC stylesheets you're loading? Combine them into one with the power of SASS!

// fouc.scss
@import "node_modules/@zywave/zui-button/dist/css/zui-button.fouc.css";
@import "node_modules/@zywave/zui-dialog/dist/css/zui-dialog.fouc.css";
@import "node_modules/@zywave/zui-icons/dist/css/zui-icons.fouc.css";
@import "node_modules/@zywave/zui-shell/dist/css/zui-shell.fouc.css";

// output as single CSS file
<!-- link in head of your HTML document -->
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" src="fouc.css" />
</head>
...
</html>

Troubleshooting

This monorepo can be quite a beast to work in. If you find yourself in some weird scenarios, here are some possible steps that might help.

Merge conflicts and Yarn

Just don't worry about merge conflicts in the yarn.lock file. It's not worth it.

  1. Mark as resolved
  2. Delete it
  3. yarn install to recreate it
  4. Add the new yarn.lock file to your commit and carry on

If there are a lot of changes in the yarn.lock file and you aren't confident in commiting those changes, reach out to the App Platform team in Microsoft Teams and we will help you figure it out!


Creating a new component

Prior to creating a new component, please consult with us before doing so. We want to ensure that we are not duplicating efforts, and that the component you are creating is something that we want to include in the Booster Development Network design system.

If you are creating a new web component (e.g., <zui-my-element>), you may use the built-in ZUI web component generator. However, using the generator requires that you have Yeoman installed on your machine. If you don't have it installed, run npm i -g yo to install it prior to generating a new web component.

> yarn run generate:wc

When asked to name your new component, please prefix it with zui-.

The command will generate the following...

  • A new package with your component name defined under /packages/components/ (e.g., /packages/components/zui-my-element/)
  • /src/ directory
    • index.ts exports your component
    • zui-my-element.ts includes a sample element with your component's name to get the ball rolling
    • zui-my-element.scss file to style your component
    • /css/ directory for your component's flash of unstyled content styles
    • package.json to define the package, and various commands to compile, build and start the lab, tests, etc.
    • tsconfig.build.json to compile Typescript
  • lab.html
    • Web page playground to test your component locally
    • Great way to showcase examples of your component's features
    • Local development environment for your component can be started up with yarn run watch (requires the working directory to be inside your new component's folder)
  • /test/ directory
    • Where you will write tests to verify your component's functionality
    • Start up the local development environment for your component's tests with yarn run test (requires the working directory to be inside your new component's folder)
  • /docs/ directory
    • This directory provides working examples to be showcased in the Booster Documentation Network site
    • demo.html is where you will write code examples or demos of all the possible ways to use your component that will appear in the "Demos" tab of your component's documentation page on Booster Development Network
    • Start up the local development environment for your component's documentation with yarn run demo (requires the working directory to be inside your new component's folder)
Common gotcha: If you're adding more elements to the same package, be sure to add an export statement to the /src/index.ts file!

Add new component to zui-components-all

For your component to be recognized by zui-bundle please add it to zui-components-all's package.json by running the following command from the root of the project. Following the example below, replace zui-my-element with your component's name.

> npx lerna add @zywave/zui-my-element --scope=@zywave/zui-components-all

Add new component to zui-bundle

Since zui-components-all is a dependency of zui-bundle, you need to manually add a line to import your new component package into /packages/misc/zui-bundle/src/bundle.js. Also add your component's FOUC styles into /packages/misc/zui-bundle/src/css/zui-bundle.fouc.scss too. Following the example below, replace zui-my-element with your component's name.

// bundle.js
import '@zywave/zui-my-element';
// zui-bundle.fouc.scss
@use '@zywave/zui-my-element/dist/css/zui-my-element.fouc.css';

Creating a new CSS package

Prior to creating a new CSS package, please consult with us before doing so. We want to ensure that we are not duplicating efforts, and that the styles you are adding fit in the Booster Development Network design system.

If you are creating a new CSS package, you may use the built-in ZUI CSS package generator. However, using the generator requires that you have Yeoman installed on your machine. If you don't have it installed, run npm i -g yo to install it prior to generating a new package.

> yarn run generate:styles

When asked to name your new package, please prefix it with zui-.

This will give you...

  • A new package with your component name defined under /packages/styles/ (e.g., /packages/styles/zui-my-styles/)
  • A /src/ directory
    • index.scss file to import multiple stylesheets into one
    • _partial.scss and module.scss are examples of how to organize SCSS components by purpose and reusability within the package or design system
  • A package.json to define the package
  • A gulpfile.js to handle building, demoing, etc.
  • lab.html
    • Web page playground to test your styles locally
    • Great way to showcase CSS styles and examples
    • Local development environment for your CSS package can be started up with npx gulp watch (requires the working directory to be inside your new packages's folder)