Giter Site home page Giter Site logo

mrmartineau / design-system-utils Goto Github PK

View Code? Open in Web Editor NEW
535.0 9.0 13.0 1.39 MB

👩‍🎨 Access your design tokens with ease

Home Page: https://zander.wtf

License: MIT License

JavaScript 20.90% TypeScript 79.10%
styled-components emotion css-in-js react preact tokens design-tokens typescript

design-system-utils's Introduction

👩‍🎨
Design System Utils

npm npm bundle size (minified + gzip) Travis CI Build PRs Welcome

Design System Utils is a micro framework that standardises your design-system tokens & provides helpful utility functions to access the information. It can be used with styled-components, emotion, glamorous or any other CSS-in-JS framework.

Install

yarn add design-system-utils

# or

npm install --save design-system-utils

Size

$ size-limit

  build/cjs.js
  Package size: 814 B
  Size limit:   1 KB

  es/index.js
  Package size: 806 B
  Size limit:   1 KB

  With all dependencies, minified and gzipped
🤓 Table of contents

Usage

First create your design system file, this contains all your design tokens that your app or site will use; things like font-sizes, color palette, spacing etc (kind of like a Sass/Less variables file).

For example you can create a top-level directory named tokens, theme or designsystem, and add an index.js inside, like so:

./tokens
└── index.js

A simple version of a tokens file with Design System Utils:

// ./tokens/index.js
import DesignSystem from 'design-system-utils'

// your design tokens object goes here, see below for further details
const designTokens = {...}

export default new DesignSystem(designTokens)

Setup

The "shape" and structure of your design tokens object can actually be anything you want, however, if you want to make use of the shortcut/helper methods like tokens.fontSize|bp|z|color|brand|spacing etc, there is a particular shape that your data will need to follow, see below:

(🤔 the below code snippet includes some psuedo types for the values that occur in the different parts of the tokens object)

{
  type: {
    // this should be set as a px value if you have `options.fontSizeUnit` set
    // to 'rem' or 'em' so that the lib can convert the values properly
    baseFontSize: '' // string,

    // used with `tokens.fs('size')` or `tokens.fontSize('size')`
    sizes: {
      key: '' // <number | string>,
    },
  },

  // Color palette
  // Each object needs to have the same shape
  // Each color object needs a `base` value to be the default
  // Have as many color objects as you like
  colors: {
    // Used with `ds.color('colorName')`
    colorPalette: {
      colorName: {
        base: '' // <string>, // base is the default
      },
    },

    // Used with `ds.brand('colorName)`
    brand: {
      colorName: '' // <string>, base is the default
    }
  },

  // Breakpoints
  // Used with `ds.bp()`
  // Keys can be anything you like
  // Have as many breakpoints as you like
  // Values can be use any unit you like
  breakpoints: {
    key: '' // <number | string>,
  },

  // Z-index
  // Used with `ds.z()`
  zIndex: {
    key: 10 // <number>
  },

  // Spacing
  // Used with `ds.spacing()` or `ds.space()`
  spacing: {
    scale: [] // <array>[<number | string>, ...], <object>
  },
}

Below is an excerpt from the example design-system. See a more complete version in the /example directory or some that are used in the design-system-utils tests: 1 & 2.

const designTokens = {
  type: {
    baseFontSize: '20px',

    sizes: {
      xs: '16px',
      s: '20px',
      base: '30px',
      m: '36px',
      l: '42px',
      xl: '50px',
      xxl: '58px',
    },

    fontFamily: {
      system:
        '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans"',
      sans: '"Helvetica Neue", Helvetica, Arial, sans-serif',
      serif: 'Georgia, "Times New Roman", Times, serif',
      mono: 'Menlo, Monaco, "Courier New", monospace',
    },

    lineHeight: {
      headings: 1.1,
    },

    fontWeight: {
      normal: 300, // Useful to set here if using anything other than `normal`
      bold: 'bold', // Useful to set here when bold webfonts come as 400 font-weight.
      headings: 'bold', // instead of browser default, bold
    },
  },
}

Initialise the design system framework

// myDesignSystem.js
import DesignSystem from 'design-system'
const designTokens = {...}
export default new DesignSystem(designTokens)

Accessing the design system data in your app

To access your design system, you just need to import it to the current file, like so:

import tokens from './tokens' // assuming you exported `default` from your design system file

Here is a very simple component using styled-components and some values from the tokens, you should be able to see how easy it is to pull information from the design system.

// Example using styled-components
import styled from 'styled-component'
import tokens from './tokens'

export const Box = styled.div`
  font-family: ${tokens.get('type.fontFamilyBase')};
  background-color: ${tokens.brand('primary')};
  margin: ${tokens.space(2)} 0;
`

Options

There is only one option that can be passed to your design system class, it relates to font-sizing:

// Use default options. do not convert the font-sizes to rems or ems
export default new DesignSystem(myDesignSystem)

// OR: with custom options
export default new DesignSystem(myDesignSystem, {
  // this is used to convert your `type.sizes` values from one unit to another
  // e.g. to convert all `px` sizes  to `rem`, set this option:
  fontSizeUnit: 'rem',
  // this means you can define values using px and use rems in your app
})

Basic API methods

tokens.get() - Get a token value

The tokens.get() function can be used to get any value from the design-system. Use object dot notation to find the value you need from your design system object.

// with the system setup, as above
tokens.get('lineHeight.headings') // 1.1
tokens.get('a.really.deeply.nested.value')

tokens.set() - Set a token value

The tokens.set() function can be used to set tokens values. This means you can overwrite existing items, or create new items that are specific to your application.

Like the .get() method, use object dot notation to find the value you need from your design system object.

This method uses dset under the hood, so please read the docs there for more info.

// with the system setup, as above
tokens.set('forms.inputBackground', '#fff')

// then use it later like so:
tokens.get('forms.inputBackground')

API helper methods

The helper methods make getting values much more simple.

tokens.fontSize() or tokens.fs() - Get font-size values

The tokens.fontSize() method is a short-hand for the tokens.get() method. It can be used to get a value from the type.sizes object.

The type.sizes object’s values can be formatted in a few ways:

  • as a string with any unit of measurement, e.g. s: '13px' / px, rem or em
  • as a template string using another function to calculate font-sizes, for example a modular-scale, e.g. ${ms(0, modularscale)}px. Note: this uses an external package, modularscale-js
// define some values// type.sizes object
sizes: {
  xs: '16px',
  s: '20px',
  base: '30px',
  m: '36px',
  l: '42px',
  xl: '50px',
  xxl: '58px',
},

// retrieve some values
tokens.fontSize('xl')
tokens.fs('xl') // `tokens.fs()` is a short-hand alias for `tokens.fontSize()`
tokens.fs('xl', true) // return font-size in px regardless of `option.fontSizeUnit` value

Modular scale

Note: v0.x.x had modular scale functionality built-in, in v1.x.x, this has been removed to reduce file-size for those that don't need a modular scale.

To make use of a modular scale, there are a few things that need to be done:

  • install a modular scale converter package, like modularscale-js
  • define your modular scale options outside of your design-system object
  • add the modular scale values to the type.sizes object
const modularscale = {
  base: [30],
  ratio: 1.5,
}

// design system
...
sizes: {
  xs: `${ms(-2, modularscale)}px`,
  s: `${ms(-1, modularscale)}px`,
}
...

Testing and remembering the values from your modular scale can be tricky, there are two options that can be used, either:

  • visit modularscale.com and enter your settings, you can then view all the type sizes on the scale you specified
  • or, add the below snippet to your code to print out the values of your scale:
const sizes = tokens.get('type.sizes')
Object.keys(sizes).forEach(item => {
  console.log(item, ':', sizes[item]) // e.g. `base : 20px`
})

Color palette

There are two possible ways to access color information: the color palette and the brand colors.

The color palette is intended to contain all the colors (and their shades) that your app will use, and the brand palette should contain the specific colors that your brand uses.

Two methods can be used to retrieve the values, these are: tokens.color() and tokens.brand(), below is what the data looks like for them:

colors: {
  // With a color palette like this:
  colorPalette: {
    bright: {
      base: '#F9FAFB',
      dark: '#F4F6F8',
      darker: '#DFE4E8',
    },

    dark: {
      base: '#212B35',
      light: '#454F5B',
      lighter: '#637381',
    },
  },

  // With a brand palette like this:
  brand: {
    red: '#e82219',
    deeporange: '#ff7200',
    orange: '#ff9500',
    green: '#c4d000',
    teal: '#1aa5c8',
    navy: '#0052da',
  }
},

tokens.color() - Get color palette values

The tokens.color() function gets values from the colorPalette object. It assumes every color has a base property and other properties for different shades of the same color. This is a short-hand for the tokens.get() function.

// Get values like this:
tokens.color('bright') // #F9FAFB - the `base` key is the default, so it is not needed
tokens.color('bright', 'dark')
tokens.color('background.extra.dark') // Accepts a path (in this case the second `variant` argument is ignored)

tokens.brand() - Get brand palette values

The tokens.brand() function gets values from the colors.brand object. This is a short-hand for the tokens.get() function.

// Get brand values like this:
tokens.brand('orange')
tokens.brand('pink')
tokens.brand('primary.blue') // it is possible to nest this object as much as you like

tokens.bp() - Get responsive breakpoint values

The tokens.bp() method is a short-hand for the tokens.get() method. It can be used to get a breakpoint from the breakpoints object.

tokens.bp('m')

tokens.z() - Get z-index values

The tokens.z() method is a short-hand for the tokens.get() method. It can be used to get a breakpoint from the zIndex object.

tokens.z('low')

tokens.spacing() or tokens.space() - Get spacing values

The tokens.spacing() method returns a value from your spacing.scale definition. The spacing data could either be an array, or an object.

  • If an array, it takes an index (number) for that array e.g. tokens.space(2). This variant adds px to the end of the string, this will be deprecated in v2.0.0.
  • If an object, it takes a key (string) for the item in that object e.g. tokens.space('m')

Array example:

scale: [0, 8, 16, 24, 32, 40]

tokens.spacing(2) // '16px'
// Note: `tokens.space(2)` can also be used

Object example:

scale: {
  s: '10rem',
  m: '100rem',
  l: '1000rem',
}

tokens.spacing('m') // '100rem'
// Note: `tokens.space('m')` can also be used

Calculations

The framework currently provides a few calculation functions, multiply, toPx and pxTo:

tokens.multiply()

tokens.multiply(10, 2) // 20

// you can pass in another value from the system
tokens.multiply(tokens.get('spacing.baseline'), 2)

// or just use the key from the system
// the initial value will always be run through `parseFloat()`
tokens.multiply('spacing.baseline', 2)

pxTo()

Converts px to rem or em

import { pxTo } from 'design-system-utils'
// pxTo(fontSize, baseFontSize, unit - 'rem'/'em')
pxTo(12, 20, 'rem') // 0.6rem
pxTo(12, 20, 'em') // 0.6em

toPx()

Converts rem or em value to px

import { toPx } from 'design-system-utils'
// toPx(fontSize, baseFontSize)
toPx('1.875rem', 16) // 30px
toPx('1.875em', 16) // 30px

parseUnit()

Parses a number and unit string, and returns the unit used

import { parseUnit } from 'design-system-utils'
parseUnit('1.875rem') // 'rem'
parseUnit('18px') // 'px'

Usage with Typescript

Typescript types and interfaces should be imported as named imports.

See all the type definitions in the types.ts file. Here are all the exported types that can be extended:

{
  System,
  SystemOptions,
  SystemBreakpoints,
  SystemZIndex,
  SystemFontSizes,
  SystemSpacing,
  SystemScale,
  SystemColorPalette,
  SystemBrandPalette,
  SystemType,
  SystemOptionalKey,
}

Below is an example where a new item (baseline) is added to the spacing object.

import DesignSystem, { System, SystemOptions, SystemSpacing } from '../index'

interface MySystemSpacing extends SystemSpacing {
  baseline: number
}

interface MySystem extends System {
  spacing: MySystemSpacing
}

const Tokens: MySystem = {
  ...
  ...

  spacing: {
    scale: [0, 8, 16, 24, 32, 40],
    baseline: 20,
  },
}

export default new DesignSystem<MySystem, SystemOptions>(Tokens)

Aliases

If you'd prefer to rename the above methods, or even add your own getter methods, to access your design tokens, it is very simple. See below for an example:

export tokens from './tokens' // import your design tokens

// create new alias functions

// this renames .bp()
export const breakpoints = bp => tokens.bp(bp)

// this adds a new alias that doesn't already exist in Design System Utils
export const fontWeights = weight => tokens.get('type.fontWeights')

// the aliases can also be for specific values
export const baseFontSize = ds1.get('type.baseFontSize')
export const brandPrimary = ds1.brand('red')

Then use them like so:

import { breakpoints, fontWeights, baseFontSize, brandPrimary } from './tokens'

breakpoints('m')
fontWeights('normal')

Take a look at alias.ts and alias.test.ts to see working examples and their usage.

Demo & examples

I created a demo on codesandbox.io, it includes examples of using the design-system utils with emotion, styled-components and glamorous. There is also a basic example here.

Licence

MIT © Zander Martineau

design-system-utils's People

Contributors

beldur avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar erikmueller avatar ivoreis avatar mrmartineau avatar oyeanuj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

design-system-utils's Issues

Issue with baseFontSize using units and pxTo

Hi @mrmartineau, I ran into a bug with the baseFontSize.

In the README, and the example, you use baseFontSize: '20px. However, by mentioning the units there it breaks the pxTo function when called from fontSize function, since the pxTo function directly uses the passed value as a denominator without stripping out 'px' from the string.

return pxTo(output, this.designSystem.type.baseFontSize, 'rem')
case 'em':
return pxTo(output, this.designSystem.type.baseFontSize, 'em')

return `${value / base}${unit}`

I believe the two solutions can be:

  1. Recommend baseFontSize always be used without unit and apply px as a default unit when someone does ds.get('type.baseFontSize'). That could also come through a setting prop.

  2. Recommend that users always specify baseFontSize with px or a similar unit but always strip that out when using it in the pxTo function.

FWIW, Solution 1 feels cleaner personally to me.

Thoughts?

Allow for type sizes to be specified by screen size

Hi @mrmartineau, thank you for putting out this library! I've been migrating my default global variables to this helpful system. While doing this, I've run into a pattern with my font sizes.

Essentially, the design system enforce font size on one dimension (which is size), whereas I notice that a lot of the code wrt font sizes tends to be in two dimension (screen and size). For example, I have a different default baseFontSize for smaller screens vs larger screens.

So, to work around that, my sizes area is looking like the following:

type: {

		baseFontSize:	20,

		/*
		*	Size scale for fonts:
		*	Micro < Mini < X-Small < Small < Medium < Big
		*/

		sizes: {

			/*
			*	Small Screen Micro
			*	11.099px, 0.555em @ 20
			*/
			smallScreenMicro:	-5,

			/*
			*	Regular Screen Micro
			*	Small Screen Mini
			*	12.486px, 0.624em @ 20
			*/
			regularScreenMicro:	-4,
			smallScreenMini:	-4,

It feels intuitively to be a two dimensional array where I want to specify the scale for different screens that I am condensing into one. Does that make sense?

Curious about your thoughts on this, and if you've noticed a similar pattern as well?

Thanks!

Fails with create-react-app (and others)

Hi @mrmartineau ,

First of all let me thank you for this project, it is really helping us to build our design system. I think that the idea behind it is radically simple, but radically powerful as well.

That being said, I would like to report an issue I have been having while using this project along with create-react-app, nwb and Next.js.

Let's focus in create-react-app, since the problem is the same with all the aforementioned tools.

Steps to reproduce the issue (hope it helps)

https://asciinema.org/a/4GmMG7GzlGuTbd43COKQxHliO

I used the example code, but the code itself is not relevant.

As you can see in the asciinema, it can be fixed by changing the following lines in the packages.json:

"main": "./cjs/index.js",
"module": "./src/index.js",

Why is it failing?

The problem is that create-react-app don't compile the modules source with babel, triggering the start script I don't get the error because the js is not minified, but generating the production build the js is minified and the minification fails because the source that webpack imports is ./src/index.js which is written in ES2015 and has tokens the minifier don't recognize.

Looking at the webpack docs: resolve.mainFields

It looks like themodule field is in the second priority order (preceded by browser and followed by main) so is the one used to resolve the package.

My proposal: Add a browser field to the package.json

Theoretically it would fix the issue, but I would like to know your opinion before submitting a PR.

Feature Request: 'toPx' function

Hi again @mrmartineau! I've been finding in a couple of cases where I'd like to get the px value of my font from the modular scale - something like the reverse of pxTo function which given a value in em calculates the px value using the baseFontSize?

Is that something you can see being useful to others as well?

Add tests

Proper tests are needed to ensure no bugs are introduced.

Bundling & minifying concerns

Hi @mrmartineau, this is more of a question/concern than a definitive issue. I've been testing including this in the production bundle and I noticed that just importing the DesignSystem increases my bundle size by 500K which is unusual.

Looking at the package.json, it seems like maybe more than just the compiled index.js is being included, and instead having a compiled index.js as the only thing that the package.json is linked to would help?

So, I was wondering if you have seen this issue as well? Or have any ideas on what might be going on?

FWIW, I was testing with this starter kit (which is what my setup is based on) incase you wanted to try it out as well: https://github.com/ctrlplusb/react-universally

Thank you!

Interested in combining efforts?

Hi! Just found this project via Twitter and noticed that it's fairly similar to some of the work we're doing on https://styled-system.com and https://theme-ui.com. I can see some differences in the general API, but was wondering if you'd be interested in combining efforts and maybe bringing some of the ideas here into the Theme UI project, which is small at the moment, but we'd like to try to standardize around some common specifications to enable better interoperability with tools like this one.

Feel free to ping me here or on Twitter if you prefer

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.