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
- Design tokens
- UI kit versioning
- Adding components
- Building from sources
- Linters
- Custom build scripts
- Utilities
- Visual regression testing
Directory structure
Honeycomb library itself is located in the assets
folder.
Here is a birds eye view on its structure:
honeycomb/
└── assets/
├── html/
│ └── components/
│ ├── box/
│ ├── button/
│ ├── ....
│ └── tooltip
├── img/
├── js/
├── scss/
│ ├── base/
│ ├── components/
│ ├── config/
│ ├── helpers/
│ ├── icons/
│ ├── layout/
│ ├── themes/
│ └── utils/
├── tokens/
│ ├── templates/
│ ├── themes/
└── vendors/
SCSS
SCSS part has its own folder structure, here are few notes regarding that.
base
folder contains the general stuff that affects the whole kit (global styles etc.);components
folder is for all the element and component goodness, this is the main folder you need when adding the new components or working with the current ones;config
folder contains SASS configuration variables such as colors, sizing constants, breakpoints, all the element specific variables;helpers
contains helper classeslayout
folder is used to store the macrolayouts for our pages (e.g. 2-column sidebar/content grid);themes
holds themes and theme colors (note that files in here are generated out of design tokens and not meant to be edited directly);utils
is where all the shared mixins and SASS functions live;
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 forflix-
elements);honeycomb-sm.scss
- limited, mobile only version with all the breakpoints specific styles above sm stripped out;honeycomb-themes.scss
- holds theme variables.
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.ejs
- 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/
├── default/
│ ├── colors.json // colors
│ ├── components.json // component specific variables
│ ├── icons.json // icons used in components
│ ├── 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": {
"primary-ui": {
"comment": "primary color with shades",
"name": "primary-ui-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.
Worth noting that default
theme acts as base for all the other theme declarations, meaning other themes only have declarations for those tokens that are different.
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 default 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/).
One step guide on adding a component
Honeycomb comes with a handy Honey CLI utility that makes your development experience lightning fast. To add a new component simply run the command:
npm run honey-cli add COMPONENT_NAME
(e.g. for ramen-select
component this would be npm run honey-cli add ramen-select
).
You'll see the list of all the files created for you in CLI command output:
Created directory assets/html/components/ramen-select
Created file assets/html/components/ramen-select/ramen-select.stories.mdx
Created file assets/html/components/ramen-select/readme.md
Created file assets/scss/components/_ramen-select.scss
Injected: "@import 'ramen-select';" in file: assets/scss/components/all.scss
That's it, now run npm start
to launch development server, navigate to http://localhost:6006/?path=/story/ramen-select--default-story and go develop your shiny component!
Multi step guide on adding a component (for curious ones)
- Add your component
- Create a new file
_YOUR_COMPONENT.scss
insideassets/scss/components
directory - Import your file in
all.scss
that is in the same folder (note that components included in alphabetical order)
- Create a new file
- 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
- Create a new folder
- 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.
- 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.
SASS linter
To run the SASS lint 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.
honey-cli
command npm run honey-cli -- [command] [name] [options]
honey-cli
a small cli tool to speed up development.
It offers to commands:
honey-cli add [name] [options]
- adds all necessary files for component with [name]
-T
comma separated list of templates to generate, defaults toejs,scss,ejs-doc
- can be extended by adding templates in
./dev/tools/honey-cli/templates
honey-cli remove [name]
- adds all necessary files for component with [name]
- uses
./dev/tools/honey-cli/templates
as reverse mapping
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:
- Include
classToggler.js
file in the end of your document:https://honeycomb.flixbus.com/dist/<VERSION_TAG>/js/classToggler.js
; - Initialize the plugin with: classToggler.init();
- 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:
- Include the module file on your page (preferably in the end):
https://honeycomb.flixbus.com/dist/{VERSION_TAG}/js/tooltip.js
- Initialize the module with:
tooltip.init()
; - Add an
id
to eachflix-tooltip
element you wish to be managed by the plugin; - Connect the target to the tooltip by passing the tooltip id to the target
[data-tooltip]
attribute - 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
Dropdown plugin will handle the aria attributes and class modifiers for managing dropdowns.
Basic usage instructions:
- Include the module file on your page (preferably in the end):
https://honeycomb.flixbus.com/dist/{VERSION_TAG}/js/dropdown.js
- Initialize the module with:
dropdown.init()
; - Add an
id
to eachflix-dropdown__items
element you wish to be managed by the plugin; - Connect the target to the dropdown list by passing the dropdown id to the target
[data-dropdown]
attribute - 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 BrowserStack for cross-browser testing.
- WebdriverIO configuration is located in
./wdio.conf.js
. - Tests run in following browsers and platforms:
safariMacOS, ffWin, chromeWin and ie11Win
(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 also specified in./wdio.conf.js
).
Running tests
Prerequisites
Browserstack credentials are required to run the tests, you can find them in your Browserstack 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:
BROWSER_STACK_USERNAME=YOUR_VERY_SECRET_USERNAME
BROWSER_STACK_ACCESS_KEY=YOUR_VERY_SECRET_KEY
Tests run commands:
npm run test
- starts all the specified tests suites;npm run test -- --spec button
- target spec files by name;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 timeconsuming 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 test/screens/components
folder, screens are grouped by component and browsers;
If you need to update the screenshots, simply remove them from the relevant component 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/
└── assets/
└── html/
└── components/
└── button/
├── button.stories.mdx
├── button.test.js
├── component.ejs
├── 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: button.test.js
// defining the test suite
describe('Button 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 specifying a proper component name (needs to match component foloder name) and a storyID
testComponentAllStories('button');
});
});
// file: divider.test.js
const { testComponentStory } = require('../../../../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 componenbt story
// make sure specifying a proper component name (needs to match component foloder name) and a storyID
// the 3rd param is an array of screen sizes to test ('s': small, 'm': medium, 'l': large),
// so we can test it on one screen size to save some test run time
testComponentStory('divider', 'divider--default-story', ['s']);
});
});
If you require a more finegrained 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('../../../../wdio.conf');
it(`Blockquote matches a reference image`, () => {
browser.url(`iframe.html?id=blockquote--default-story&viewMode=story`);
// loops through imported window sizes
windowSizes.forEach(windowSize => {
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);
});
});
Managing reference screenshots
Every time you modify any visual part of the component, or its storybook story, you need to update the test screenshots for relevant tests.
For new components reference screenshots are being added automatically during the first test run.
For other cases updating the screenshots requires a manual action and consists of several simple steps:
- navigate into
test/screens
folder and locate screenshot folders grouped by browsers and platforms; - go into every browser/platform folder and find related screenshots, delete them;
- launch the tests for the component you want to update the screenshots for with
npm run test -- --spec COMPONENT_NAME
.
Note: If you deleted a component, running screenshot update would delete it's reference screens.