Developers guide

This section aims to show you around, so you can feel yourself comfortable while browsing the code and contributing to the project. Here we assume you've already read our CONTRIBUTING.md documentation in the repo and know the prerequisites. So make sure to read that first if you still haven't.

Thank you for being interested in our tools!

Authors Team Hive

Table of contents

Directory structure

Honeycomb library itself is located in the src folder. Here is a birds eye view on its structure:

honeycomb/
└── src/
    ├── components/
    │       ├── box/
    │       ├── button/
    │       ├── ....
    │       └── tooltip
    ├── helpers
    │       ├── reset/
    │       ├── space/
    │       ├── ....
    │       └── visibility
    ├── img/
    │       ├── ....
    │       ├── logos/
    │       ├── favicons-greyhound.zip
    │       ├── favicons-kamil.zip
    │       ├── favicons-flix.zip
    │       └── ....
    ├── js/
    ├── scss/
    │    ├── common/
    │    ├── components/
    │    ├── helpers/
    │    ├── icons/
    │    └──  themes/
    └── tokens/
         ├── templates/
         └── themes/

SCSS

SCSS part has its own folder structure, here are few notes regarding that.

  • common folder contains configuration files, SASS mixins and base styles;
  • components folder contains component specific SCSS partials that are named according to component class names but without a flix prefix;
  • helpers folder contains helper classes
  • icons folder contains icon related styles, you won't need it in most of the cases, please use our icons libraries for everything around icons;
  • themes holds themes and theme colors (note that files in here are generated out of design tokens and not meant to be edited directly);

There are several SCSS entry points:

  • honeycomb.scss - includes all the stuff and is the one that is used to compile the resulting CSS;
  • honeycomb-tools.scss - the one you may need to kickstart your custom CSS with some of Honeycomb variables, breakpoints and mixins;
  • honeycomb-base.scss - same as tools but with some base styles attached (e.g. box-sizing: border-box;, a bit of resets for flix- elements);
  • honeycomb-components.scss - has only component styles without theme design tokens;
  • honeycomb-fonts.scss - custom font declarations, required for honeycomb to work properly;
  • honeycomb-helpers.scss - helpers only;
  • honeycomb-sm.scss - limited, mobile only version with all the breakpoints specific styles above sm stripped out;
  • honeycomb-theme-{theme_name}.scss - holds theme variables for the respective theme.
  • honeycomb-themes.scss - holds all theme variables for all themes.

HTML

All the reference HTML for components is stored within assets/html/components folder. Here each component has related files grouped in folders named by the component names.These files are:

  • component.stories.mdx - holds HTML for the component, should contain all the possible states and modifier class usage in order to achieve a proper regression testing;
  • readme.md - holds all the relevant tech documentation and component usage guidelines;
  • readme-design.md - optional design guide that has additional information mostly related to designers;

Tokens

This folder contains design tokens in json format. This format makes them compatible with style-dictionary library and allows us generating theme/token configuration files not only for Honeycomb but also to other platforms in various formats. See Design Tokens section for more info.

Other folders

docs folder contains developers documentation and Frontend development guidelines. Resulting files are being put into dist folder.

dev folder contains custom build scripts and tools, please reference to Custom build scripts for details

Design tokens

Design tokens are a collection of attributes that describe any fundamental/atomic visual style. Each attribute is a key-value pair. Honeycomb 6.0 stores them as platform-agnostic set of json files that act as the main source of truth not only for generated cross-platform assets but also for Honeycomb itself.

Tokens definition and structure

Tokens are grouped in theme folders and separated into individual file configurations by the nature of attributes they describe:

tokens/
└── themes/
    ├── flix/
    │   ├── colors.json // colors
    │   ├── components.json // component specific variables
    │   ├── misc.json // all the rest, e.g. geometry or opacity variables
    │   ├── spacing.json // spacing schema
    │   └── typography.json // typography, e.g. font sizes, line heights etc.
    ├──...

Inside these files tokens are grouped by their type like color, size etc. and defined as followed:

{
  "color": {
    "ui-primary": {
      "comment": "primary color with shades",
      "name": "ui-primary-color",
      "value": "#73d700"
    },
    "secondary-ui": {
      "comment": "secondary color with shades",
      "name": "secondary-ui-color",
      "value": "#ffad00"
    },
  }
}

Where name provides optional name for the token that is by default gets defined by respective object keys, value declares token value and comment provides optional comment.

Build script and configuration

All the configuration and logic for token processing is located in build-tokens.js file. For more info on that please refer to this file and documentation for style-dictionary library.

npm run build:tokens command can be used to build tokens, by default a build is performed for flix theme only, to build for different theme you need to provide it as param like so npm run build:tokens -- --theme dark.

UI kit versioning

We strictly follow semantic versioning pattern when it comes to package versioning (see: http://semver.org/). This means we differentiate between major, minor and patch releases for Honeycomb

Major release

A major release introduces breaking (incompatible) changes.

By breaking change we usually mean:

  • your code from the previous version produces a different visual result. Common examples would be:
    • major theming changes, e.g. new color scheme, grid patterns, theme/color variables renaming etc.
    • components appearance change;
    • certain components got deprecated/removed;
  • you need to adjust your code from the previous version in order for things to work in the new one. Common examples would be:
    • class name changes for components;
    • HTML structure changed for components;
    • component name changes;
    • tech stack changes, like moving from SASS to LESS or deprecating SASS themes in favor of CSS custom properties.

Minor release

Minor release introduces incremental changes, in most cases it adds new components or component variations. It's usually fully safe to update to next minor releases as you code is safe, no deprecations or major layout changes happen between minor releases.

Patch release

Those are bug fix releases, not only it's safe to update to those, but it's also highly recommended to get rid of some bugs you might encounter.

Accessing the releases

Honeycomb gets distributed via CDN and private npm registry.

CDN url structure is the following:

https://honeycomb.flixbus.com/dist/{YOUR_VER_NAME}/css/honeycomb.min.css

e.g. for version "3.1.0" this would be:

https://honeycomb.flixbus.com/dist/3.1.0/css/honeycomb.min.css

Grabbing a specific version from NPM is even easier, all you need is specifying a version you need in your package.json file

Adding components

Set up your local environment first:

  • Checkout project from the repo and follow Preparing the dev environment instructions to setup your project.
  • Create development branch with a naming pattern feature-add-YOUR_COMPONENT_NAME

Then follow the instructions bellow.

Development server

Storybook development server is included, npm start launches the server (default url is http://localhost:6006/).

Adding a component

  1. Add your component
    • Create a new file _YOUR_COMPONENT.scss inside assets/scss/components directory
    • Import your file in all.scss that is in the same folder (note that components included in alphabetical order)
  2. Document your component
    • Create a new folder assets/html/components/[YOUR_COMPONENT]
    • Add a .stories.mdx file with Storybook stories to present your component and its variations
    • Add a readme.md file with extra information
    • Add a readme-design.md file with design related documentation
  3. Add reference screenshots for your newly created component (follow [#visual-regression-testing](Visual regression testing) docs for more info), this is required in order to tests pass and MR to be merged.
  4. Create a merge request in https://git.flix.tech/user/ui/honeycomb/merge_requests 🚀

Building from sources

To build from sources to dist folder you need to run

npm run build

This runs set of gulp and webpack tasks and creates resulting css/js files, including minified ones. This also makes sure the contents of img and font folders are being copied to the dist as well for distribution.

Normally you do not need to run the build tasks when developing as built in devserver works with SASS partials assembling them on the fly. Though build becomes handy when you wanna check the exact css output orf your SASS sources or when debugging some complex SASS structures.

Linters

Please run lint utilities prior to building the files and commiting your changes! This helps us maintaining the common code style and quality level as well as avoiding the obvious mistakes and typos in the code. To run all the linters simply type npm run lint in your CLI.

Stylelint

To run the SCSS linter use the following command:

npm run lint:scss

ESLint

To run the ESLint use the following commands:

# checks js files within UI kit
npm run lint:js

Config files for ESlint located withing the project's root dir (.eslintrc.yml and .eslintignore).

We use the recommended settings for ESlint, more details about the rule set can be found here.

Custom build scripts

To allow for a deeper level of automation as well as circumventing some harpjs limitation, a set of custom build scripts is in place.

All of the custom scripts are located in /scripts/script-name/index.js and expose one single function, that will then be run by gulp.

Webpack script

Command build:js

This script will bundle assets based on the config in ./dev/tools/webpack/webpack.conf.default.js. This currently is bundling all files in assets/js/ as separated bundles that are exported as umd libraries.

Utilities

SASS mixins and functions

In order to ease out writing of styles Honeycomb provides a set of handful SASS tools. Just include assets/scss/honeycomb-tools.scss in our SASS/SCSS file to benefit from them.

Positioning and responsive utils

This includes on-bp(), on-range() mixins z() function, providing you with a handy instruments to measure spacing values and control you style responsive behaviour.

on-bp() mixin

General breakpoint mixin, accepts 2 params:

  • $breakpoint - one of the map keys in breakpoints list (zero, xs, sm, md, lg, xl)
  • $only - boolean property, indicates that style set should be applied for this breakpoint only, restricting the other ones with max-width

z() function

z-index function allows us having control over z-index layers in components, ensuring they won;'t collide and produce layout bugs of various kinds. Accepts $layer as param (one of 'base', 'form-label', 'dropdown', 'header', 'popup', 'side-menu').

Here is how we use those in our code:

.box {
    // assigning 12px spacing values
    margin-bottom: cssvar(spacing-2);
    padding: cssvar(spacing-2);

    // using on-bp mixin to define styles for wider ('sm' and bigger) screens
    @include on-bp(sm) {
      padding: cssvar(spacing-4); // we want a bigger spacing on wider screens, picking sm spacing for that
    }
}

Typography mixins

Standardizing things is something we like (as you might guessed), to keep various typography style variations consistent we came up with a set of mixins to achieve various typography styles through out elements.

These have very meaningful names so you probably won't mess up what things they responsible for:

  • show-as-text;
  • show-as-fineprint;
  • show-as-small-text;
  • show-as-h1;
  • show-as-h2;
  • show-as-h3;
  • show-as-h4;

Every mixin of this kind takes care of setting following CSS style properties: color, font-size, font-weight, line-height.

Class toggler plugin

Class toggler is a simple data attributes based class toggler plugin.

Class toggling is one of the most useful things in a frontend world, it can help you create simple dynamic UI elements with ease.

Basic usage instructions:

Add classToggler instance to your page:

  1. Include classToggler.js file in the end of your document: https://honeycomb.flixbus.com/dist/<VERSION_TAG>/js/classToggler.js;
  2. Initialize the plugin with: classToggler.init();
  3. Follow the instructions bellow;

On a toggle element specify the [data-toggle] attribute that points to a target element to toggle. Specify the class to be toggled on the target element with a [data-toggle-class] attribute. If you need to toggle a class on the source (e.g. --active modifier) use [data-toggle-self] attribute. By default plugins responds to a "click" event and toggles specified class on a specified target element.

That's basically it.

Tooltip plugin

Tooltip plugin will handle the aria attributes and class modifiers for managing tooltips.

Basic usage instructions:

  1. Include the module file on your page (preferably in the end): https://honeycomb.flixbus.com/dist/{VERSION_TAG}/js/tooltip.js
  2. Initialize the module with: tooltip.init();
  3. Add an id to each flix-tooltip element you wish to be managed by the plugin;
  4. Connect the target to the tooltip by passing the tooltip id to the target [data-tooltip] attribute
  5. Optionally you can configure if the tooltip is show on click or hover by passing data-event="click|hover" to the target

That's all.

Dropdown plugin will handle the aria attributes and class modifiers for managing dropdowns.

Basic usage instructions:

  1. Include the module file on your page (preferably in the end): https://honeycomb.flixbus.com/dist/{VERSION_TAG}/js/dropdown.js
  2. Initialize the module with: dropdown.init();
  3. Add an id to each flix-dropdown__items element you wish to be managed by the plugin;
  4. Connect the target to the dropdown list by passing the dropdown id to the target [data-dropdown] attribute
  5. Optionally you can configure if the dropdown is shown on click or hover by passing data-event="click|hover" to the target

That's all.

Visual regression testing

Visual regression testing helps us checking the impact of CSS changes on component's visual appearance, ensures precise tracking of components visual changes;

What's under the hood:

  • We use WebdriverIO with wdio-image-comparison-service as our testing tools of choice.
  • We use SauceLabs for cross-browser testing.
  • WebdriverIO configuration is located in ./wdio.conf.js.
  • Tests run in following browsers and platforms: safariMacOS, ffWin, chromeWin (those specified as capabilities in ./wdio.conf.js config file).
  • Every test screenshot is taken with multiple screen widths: xs: 411px, md: 800px, xl: 1200px (these are specified in ./test/utils.js).

Running tests

Prerequisites

SauceLabs credentials are required to run the tests, you can find them in your SauceLabs account settings. Once you got them you need to expose them as environment variables. We've included support for .env files to make your life easier, simply add a .env file to the root of the project and put credentials there like so:

SAUCE_USERNAME=YOUR_VERY_SECRET_USERNAME
SAUCE_ACCESS_KEY=YOUR_VERY_SECRET_KEY

Running tests on your local machine

In order to run the tests locally you'll need to configure Tunnel Proxy in your SauceLabs account, please refer to your SauceLabs account settings for instructions on how to do that.

Tests run commands:

  • npm run test - starts all the specified tests suites;
  • npm run test -- --spec ./**/button.test.js - target spec files by path;
  • npm run test -- --mochaOpts.grep button - filter tests by suites and test names in spec files.

Advanced test running techniques:

By default npm run test starts test server prior to running the tests, which sometimes is time-consuming and not desirable, e.g. when you debug stuff or write complex tests. It's possible to run the tests bypassing the test server build with npm run test:no-build or npx wdio run wdio.conf.js commands when you already have storybook-static folder with assets prebuilt from the previous runs. Keep in mind that as soon as you change component's story you need to rebuild the test server assets for changes to become testable.

Reference screenshots

All the screens are located under the screens/ folder in each component, screens are grouped by browser and platform.

If you need to update the screenshots, simply delete them from the screens folder and re-run the tests for this component to generate new ones.

Writing tests

Tests are placed in component folders alongside docs and template files and have component name based naming convention like COMPONENT-NAME.test.js. Bellow you can find how things are structured for the button component:

honeycomb/
└── src/
  └── components/
    └── button/
      └── screens/
      ├── button.js
      ├── button.mdx
      ├── button.stories.js
      ├── button.test.js
      ├── readme.md
      └── readme-design.md

Actual test code is written using Mocha framework and wdio-image-comparisopn-service.

Here are a few examples of how this is done:

// file: avatar.test.js
import { testComponentDocs } from '../../../test/utils';
// defining the test suite
describe('avatar component', () => {
  // creating a test
  it('matches a reference image', () => {
    // uses helper function to take a snapshot of all the stories together on one page
    // make sure to provide the component folder path relative to src
    await testComponentDocs({ componentFolder: 'components/avatar', sizes: ['s'] });
  });
});
// file: divider.test.js
import { testComponentStory } from '../../../test/utils';
// defining the test suite
describe('Divider component', () => {
  // creating a test
  it(`matches a reference image`, () => {
    // uses helper function to take a snapshot of the specified component story
    // make sure to provide the component folder path relative to src
    // the 3rd param is an array of screen sizes to test ('s': small, 'm': medium, 'l': large),
    await testComponentStory({ componentFolder: 'components/divider', storyId: 'divider--default-story', sizes: ['s'] });
  });
});

If you require a more fine grained control over you tests, you can go beyond helper functions and craft them how you like, e.g.:

// importing target window sizes for testing
const { windowSizes } = require('../../../../utils');

it(`Blockquote matches a reference image`, () => {
  browser.url(`iframe.html?id=blockquote--default-story&viewMode=story`);
  // loops through imported window sizes, use for... of loop for async code
  for (const windowSize of windowSizes) {
    browser.setWindowSize(windowSize.width, windowSize.height);
    // Compares screenshot of the element with ID #root, which is basically a container element provided by storybook server
    expect(browser.checkElement($('#root'), 'blockquote')).toEqual(0);
  }
});