amboss-design-system
Quick start
Development
npm install
npm run start
npm run component myComponentName
Publishing
- Add
minor
, major
or patch
label to your PR to specify release type. - Add
skip-release
label to skip the release - If you want your commit to skip ci, add "[skip ci]" to your commit message.
- To release manually, without going through the pipeline run
npm run release
Building package
npm run build
Building storybook
npm run build-storybook
Overview
- Tech stack
- Folder structure
- Build
- *.d.ts for styles
- Tokens, styles and theming
- Assets
- Media queries
Tech stack
Code: typescript + scss
Framework: react
Bundler: webpack
Environment: storybook
Tokens: style-dictionary
Testing: jest + react testing library
Folder structure
|-- .storybook
|-- components
|-- ColorGrid.tsx
|-- SizeGrid.tsx
|-- main.js
|-- preview.js
|-- assets
|-- fonts
|-- Lato.woff
|-- build (autogenerated)
|-- build-tokens (autogenerated)
|-- _assets_fonts.scss
|-- _variables.scss
|-- _theme.scss
|-- _colors.json
|-- _theme.json
|-- docs
|-- Design-principles.mdx
|-- tokens
|-- themes
|-- dark.json
|-- light.json
|-- assets.json
|-- colors.json
|-- src
|-- components
|-- Button
|-- Button.tsx
|-- Button.scss
|-- Button.scss.d.ts (autogenerated)
|-- Button.stories.tsx
|-- Button.test.tsx
|-- shared
|-- _fonts.scss
|-- types
|-- index.ts
|-- index.ts
Build
There are 3 stages of a build.
- Generate type definitions for styles
- Generate variables and mixins for tokens
- Bundle all exported components
Generate type definitions for styles
Files like Button.scss.d.ts
are autogenerated, and they are added to .gitignore.
Type definitions are generating automatically:
- in watch mode while
npm run start
is running - after npm install
- before build
You can generate *d.ts manually using npm run type-styles
and npm run type-styles:watch
Why we need *.d.ts for styles?
Generate variables and mixins from tokens
In order to structure our tokens we use style-dictionary.
This tool is used for creation a content of ./build-tokens
folder where we keep all the scss variables and mixins.
New variables and mixins gets generated automaticaly:
- in watch mode while
npm run start
is running - after npm install
- before build
You can generate tokens manually using npm run tokens
and npm run tokens:watch
We use tokens to control the most atomic values in styles and themes.
How does it work?
We have a set of .json files that are located in ./tokens
folder, and we have a style-dictionary
as a tool for converting .json in to various formats.
For example:
We have a file - ./tokens/colors.json
that gets converted into ./build-tokens/_variables.scss
and ./build-tokens/_colors.json
.
_variables.scss
is directly imported in other .scss
files.
_colors.json
is imported inside ./.storybook/components/ColorGrid.tsx
in order to present in storybook all the colors we currently have.
The configuration for style-dictionary
is inside ./style-dictionary.config.json
. In this config we use 2 custom formatters and 1 custom filter, all of them defined in ./style-dictionary.build.js
.
custom/format/scss - a formatter that is used for generation a .json files for tokens like colors, size, weight and so on. These .json files are imported in storybook for presentation.
custom/format/theme-scss - a formatter that is used for generation a _theme.scss
that contains a @theme
mixin.
custom/filter/only-vars - a filter for excluding theme and assets from processing them as variables.
More about tokens, themes and styles.
Bundle all exported components
Before publish the design system for npm we run npm run build
that bundles everything that is exported from ./src/index.ts
.
We use webpack + typescript for bundling. For all /\.ts(x?)$/
files webpack uses a ts-loader as a rule which is implicitly using tsconfig.json
.
For styles we use sass-loader + css-loader with enabled modules for namespacing all css selectors.
What in the bundle?
- JS code that is compiled from typescript
- css rules that are namespaced using css-loader
- I svg icons that are inlined
fonts that are base64-ed currently taken off the build
.d.ts for styles
We use type definitions for style in order to improve quality of a product overall. One example:
import styles from "./button.scss";
type ButtonProps = {
size: "s" | "m" | "l",
};
export const Button = ({ size }: ButtonProps) => (
<button className={styles[size]} />
);
Here with type definitions we are sure that button.scss
contain all 3 classes (.s, .m, and .l). Otherwise, it won't compile.
Tokens, styles and theming
Currently, we have 3 types of tokens:
- atomic values, like colors, sizes, shadows.
- font assets, that are basically base64 of font files.
- theme values, that are referencing atomic different atomic values
- svg icon assets, that are inlined to a json file
Atomic values and font assets are straight forward and simply converted to scss variables.
After build all atomic values can't be imported as @import "build-tokens/variables";
Fonts are imported inside ./src/shared/_fonts
and never used directly (it doesn't make sense).
In the application simply @import "src/shared/fonts";
.
Theme values are little more complex. When theme token is precessed we generate several entities. All of them are located in ./build-tokens/_theme.scss
- A number of scss-maps with all the theme variables for each theme.
- A
@theme
mixin, that include all themes. - A list of scss variables with the name of entries in the maps (see point 1). This is just for IDE auto-complition.
As the result developers are free to use a @theme
mixin for particular css properties with no worries about active theme and amount of themes.
Example:
@import "build-tokens/theme";
@import "build-tokens/variables";
@import "src/shared/fonts";
.primary {
@include theme("color", $theme-color-text-primary);
font-family: $Lato;
margin: 0;
}
.secondary {
@include theme("color", $theme-color-text-secondary);
}
.s {
font-size: $size-font-text-s;
line-height: $size-line-height-text-s;
}
.m {
font-size: $size-font-text-m;
line-height: $size-line-height-text-m;
}
.normal {
font-weight: $weight-normal;
}
.bold {
font-weight: $weight-bold;
}
Assets
- assets are defined in
tokens/assets.json
- with the help from style-dictionary we create a json file with tokens (
/build-tokens/assets/
) - those json files supposed to be imported in the ts module (see Icons.tsx)
- in order to secure
d.ts
filed in the build we "manually" copy asset jsons on the postbuild
phase (see package.json)
Media queries
There is an API for media-query related props: an array of 3 elements [small, medium, large], for respective screen sizes.
for example <Box space=["xs", "xl", "xxl"] />
will set xs
for small screens, xl
for medium screens and xxl
for large.
Basically, if you see a type of MQ<Whatever>
you can provide an array of 3 Whatever
s.
In order to add a media query support to your components:
mq mixin from media-queries module will create a set of media-query selectors.
@import "src/shared/media-queries";
@include mq(".align-left") {
text-align: left;
}
@include mq(".align-right") {
text-align: right;
}
@include mq(".align-center") {
text-align: center;
}
import getMediaQueryClasses from mediaQueries module to create classnames compatible with mixin above.
note that third parameter is a prefix to classname.
import cn from "classnames";
import { getMediaQueryClasses } from "../../shared/mediaQueries";
import styles from "./Box.scss";
type TextAlignment = "left" | "center" | "right";
type BoxProps = {
alignText: TextAlignment | MQ<TextAlignment>;
};
export const Box = ({ alignText }: BoxProps) => (
<button
className={cn(...getMediaQueryClasses(alignText, styles, "align-"))}
/>
);