├── .npmignore ├── .prettierignore ├── .gitignore ├── .prettierrc ├── jest.config.js ├── types ├── dset.d.ts ├── tiny-get.d.ts └── files.d.ts ├── .travis.yml ├── .vscode └── settings.json ├── src ├── alias.ts ├── alias.test.ts ├── testData │ ├── ds2.ts │ └── ds1.ts ├── types.ts ├── index.ts └── index.test.ts ├── tsconfig.json ├── example ├── readme.md ├── colorPalette.js ├── index.js └── myDesignSystem.js ├── .github ├── workflows │ └── test.yml └── auto-assign.yml ├── tslint.json ├── rollup.config.js ├── LICENSE ├── package.json └── readme.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | .rpt2_cache 5 | .idea 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "semi": false 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coveragePathIgnorePatterns: ['./dist'], 3 | preset: 'ts-jest', 4 | } 5 | -------------------------------------------------------------------------------- /types/dset.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'dset' { 2 | export default function set(object: object, keys: string, value: any): void 3 | } 4 | -------------------------------------------------------------------------------- /types/tiny-get.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@ngard/tiny-get' { 2 | export function get(object: object, expression: string, fallback: any): any 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "node" 5 | 6 | cache: yarn 7 | 8 | script: 9 | - yarn test 10 | - yarn size 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "statusBar.background": "#91748d", 4 | "statusBar.foreground": "#fff" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /types/files.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*package.json' { 2 | export const main: string 3 | export const module: string 4 | } 5 | 6 | declare module '*.json' { 7 | const value: any 8 | export default value 9 | } 10 | -------------------------------------------------------------------------------- /src/alias.ts: -------------------------------------------------------------------------------- 1 | import ds1 from './testData/ds1' 2 | 3 | // rename to your own method 4 | export const breakpoints = (bp: string): string => ds1.bp(bp) 5 | 6 | // export specific values as aliases 7 | export const baseFontSize = ds1.get('type.baseFontSize') 8 | export const brandPrimary = ds1.brand('red') 9 | -------------------------------------------------------------------------------- /src/alias.test.ts: -------------------------------------------------------------------------------- 1 | import { breakpoints, baseFontSize, brandPrimary } from './alias' 2 | 3 | describe('alias examples', () => { 4 | test('should work', () => { 5 | expect(breakpoints('m')).toBe('500px') 6 | expect(brandPrimary).toBe('#e82219') 7 | expect(baseFontSize).toBe('30px') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "noImplicitAny": true, 5 | "module": "ES2015", 6 | "target": "es6", 7 | "noImplicitThis": true, 8 | "strictNullChecks": false, 9 | "strictFunctionTypes": true, 10 | "esModuleInterop": true, 11 | "declaration": true, 12 | "resolveJsonModule": true 13 | }, 14 | "include": ["src/index.ts", "src/types.ts", "types/**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /example/readme.md: -------------------------------------------------------------------------------- 1 | # design-system-utils example 2 | 3 | ## Files 4 | 5 | ### `myDesignSystem.js` 6 | 7 | This is what the framework uses, create your own using this as a basis and initialise it with the design-system-utils package. 8 | 9 | ### `index.js` 10 | 11 | This file shows how the design-system is used. 12 | 13 | ### `colorPalette.js` 14 | 15 | The [color palette](colorPalette.js) has been externalised because it is so big. 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: actions/setup-node@v1 18 | with: 19 | node-version: '10.x' 20 | - run: yarn install 21 | - run: yarn test 22 | env: 23 | CI: true 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["tslint-plugin-prettier"], 3 | "extends": ["tslint:latest", "tslint-config-prettier"], 4 | "rules": { 5 | "no-console": [true, "log", "error"], 6 | "ordered-imports": [false], 7 | "interface-name": [false], 8 | "no-implicit-dependencies": [true, "dev"], 9 | "no-submodule-imports": [false], 10 | "no-duplicate-imports": [false], 11 | "no-unused-expression": [false], 12 | "prefer-object-spread": [false], 13 | "object-literal-sort-keys": [false] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/auto-assign.yml: -------------------------------------------------------------------------------- 1 | # Set to true to add reviewers to pull requests 2 | addReviewers: true 3 | 4 | # Set to true to add assignees to pull requests 5 | addAssignees: true 6 | 7 | # A list of reviewers to be added to pull requests (GitHub user name) 8 | reviewers: 9 | - MrMartineau 10 | 11 | # A list of keywords to be skipped the process that add reviewers if pull requests include it 12 | skipKeywords: 13 | - wip 14 | 15 | # A number of reviewers added to the pull request 16 | # Set 0 to add all the reviewers (default: 0) 17 | numberOfReviewers: 0 18 | -------------------------------------------------------------------------------- /example/colorPalette.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bright: { 3 | base: '#F9FAFB', 4 | dark: '#F4F6F8', 5 | darker: '#DFE4E8', 6 | }, 7 | 8 | dark: { 9 | base: '#212B35', 10 | light: '#454F5B', 11 | lighter: '#637381', 12 | }, 13 | 14 | primary: { 15 | base: '#181830', 16 | light: '#292952', 17 | dark: '#0d0d19', 18 | }, 19 | 20 | secondary: { 21 | base: '#fe7c08', 22 | light: '#fea04c', 23 | dark: '#d26401', 24 | }, 25 | 26 | text: { 27 | base: '#212B35', 28 | light: '#454F5B', 29 | }, 30 | 31 | background: { 32 | base: '#f1f1f1', 33 | }, 34 | 35 | link: { 36 | base: '#fe7c08', 37 | over: '#fea04c', 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /src/testData/ds2.ts: -------------------------------------------------------------------------------- 1 | // options: fontSizeUnit:px 2 | import DesignSystem, { System, SystemOptions } from '../index' 3 | 4 | const DesignSystem2: System = { 5 | type: { 6 | baseFontSize: '30px', 7 | 8 | sizes: { 9 | small: '1em', 10 | medium: '1.5em', 11 | large: '2.4em', 12 | }, 13 | }, 14 | 15 | colors: { 16 | colorPalette: { 17 | blue: { 18 | base: 'blue', 19 | dark: 'navyblue', 20 | }, 21 | }, 22 | }, 23 | 24 | breakpoints: { 25 | s: '400px', 26 | m: 500, 27 | l: 800, 28 | }, 29 | 30 | zIndex: { 31 | low: 10, 32 | mid: 100, 33 | high: 1000, 34 | }, 35 | 36 | spacing: { 37 | scale: { 38 | s: '10rem', 39 | m: '100rem', 40 | l: '1000rem', 41 | }, 42 | }, 43 | } 44 | 45 | export default new DesignSystem(DesignSystem2) 46 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs' 2 | import resolve from 'rollup-plugin-node-resolve' 3 | import autoExternal from 'rollup-plugin-auto-external' 4 | import typescript from 'rollup-plugin-typescript2' 5 | import { terser } from 'rollup-plugin-terser' 6 | import bundleSize from 'rollup-plugin-bundle-size' 7 | import buble from 'rollup-plugin-buble' 8 | import pkg from './package.json' 9 | 10 | export default { 11 | input: 'src/index.ts', 12 | output: [ 13 | { 14 | file: pkg.main, 15 | format: 'cjs', 16 | exports: 'named', 17 | sourcemap: true, 18 | }, 19 | { 20 | file: pkg.module, 21 | format: 'es', 22 | exports: 'named', 23 | sourcemap: true, 24 | }, 25 | ], 26 | plugins: [ 27 | typescript(), 28 | resolve(), 29 | autoExternal(), 30 | commonjs(), 31 | buble(), 32 | terser(), 33 | bundleSize(), 34 | ], 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Zander Martineau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface SystemOptions { 2 | fontSizeUnit?: string | undefined 3 | } 4 | 5 | export interface SystemOptionalKey { 6 | [prop: string]: string | number 7 | } 8 | 9 | export interface SystemFontSizes { 10 | [size: string]: string | number 11 | } 12 | 13 | export interface SystemType { 14 | baseFontSize?: string | number 15 | sizes?: SystemFontSizes 16 | } 17 | 18 | export interface SystemBreakpoints { 19 | [name: string]: string | number 20 | } 21 | 22 | export interface SystemColorPalette { 23 | [name: string]: { 24 | [variant: string]: string | object 25 | } 26 | } 27 | 28 | export interface SystemBrandPalette { 29 | [color: string]: string 30 | } 31 | 32 | interface SystemColor { 33 | colorPalette?: SystemColorPalette 34 | brand?: SystemBrandPalette 35 | } 36 | 37 | // export type TSystemColor = SystemOptionalKey | SystemColor 38 | 39 | export interface SystemZIndex { 40 | [name: string]: number 41 | } 42 | 43 | export type SystemScale = 44 | | number[] 45 | | string[] 46 | | { 47 | [size: string]: string | number 48 | } 49 | 50 | export interface SystemSpacing { 51 | scale?: SystemScale 52 | } 53 | 54 | export interface System { 55 | [prop: string]: any 56 | type?: SystemType 57 | breakpoints?: SystemBreakpoints 58 | colors?: SystemColor 59 | zIndex?: SystemZIndex 60 | spacing?: SystemSpacing 61 | } 62 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | 4 | // import the design-system 5 | import ds from './myDesignSystem' 6 | 7 | // We can use styled-components or glamorous 8 | import glamorous from 'glamorous' 9 | import styled from 'react-emotion' 10 | 11 | // Create some components 12 | 13 | const EmotionHeading = styled('h2')` 14 | color: ${ds.color('dark')}; 15 | font-size: ${ds.fontSize('l')}; 16 | font-family: ${ds.get('type.fontFamily.system')}; 17 | ` 18 | 19 | const EmotionButton = styled('button')` 20 | background-color: ${ds.color('secondary')}; 21 | border: 0; 22 | border-radius: ${ds.get('borderRadius')}; 23 | padding: ${ds.get('spacing.padding')}; 24 | color: ${ds.color('bright')}; 25 | font-size: ${ds.fontSize('l')}; 26 | &:hover { 27 | background-color: ${ds.color('secondary', 'dark')}; 28 | } 29 | ` 30 | 31 | const GlamorousHeading = glamorous.h2({ 32 | color: ds.color('dark'), 33 | fontSize: ds.fontSize('l'), 34 | fontFamily: ds.get('type.fontFamily.system'), 35 | }) 36 | 37 | const GlamorousButton = glamorous.button({ 38 | backgroundColor: ds.color('primary'), 39 | border: 0, 40 | borderRadius: ds.get('borderRadius'), 41 | padding: ds.get('spacing.padding'), 42 | color: ds.color('bright'), 43 | fontSize: ds.fontSize('l'), 44 | ':hover': { 45 | backgroundColor: ds.color('primary', 'light'), 46 | }, 47 | }) 48 | 49 | const App = () => ( 50 |
51 | With Styled Components 52 | Please click me 53 |
54 | With Glamorous 55 | Please click me 56 |
57 | ) 58 | 59 | render(, document.getElementById('root')) 60 | -------------------------------------------------------------------------------- /src/testData/ds1.ts: -------------------------------------------------------------------------------- 1 | // options: modular-scale:false, fontSizeUnit:px 2 | import DesignSystem, { System, SystemOptions, SystemSpacing } from '../index' 3 | 4 | interface MySystemSpacing extends SystemSpacing { 5 | baseline: number 6 | } 7 | 8 | interface MySystem extends System { 9 | spacing: MySystemSpacing 10 | } 11 | 12 | const DesignSystem1: MySystem = { 13 | type: { 14 | baseFontSize: '30px', 15 | 16 | sizes: { 17 | xs: '16px', 18 | s: '20px', 19 | base: '30px', 20 | m: '36px', 21 | l: '42px', 22 | xl: '50px', 23 | xxl: '58px', 24 | }, 25 | }, 26 | 27 | colors: { 28 | colorPalette: { 29 | text: { 30 | base: '#212B35', 31 | light: '#454F5B', 32 | lighter: '#637381', 33 | }, 34 | 35 | primary: { 36 | base: '#181830', 37 | light: '#292952', 38 | dark: '#0d0d19', 39 | }, 40 | 41 | secondary: { 42 | base: '#fe7c08', 43 | light: '#fea04c', 44 | dark: '#d26401', 45 | }, 46 | 47 | nested: { 48 | secondary: { 49 | light: '#fea04c', 50 | deep: { 51 | nested: { 52 | light: '#fea04c', 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | 59 | brand: { 60 | red: '#e82219', 61 | deeporange: '#ff7200', 62 | orange: '#ff9500', 63 | green: '#c4d000', 64 | teal: '#1aa5c8', 65 | navy: '#0052da', 66 | }, 67 | }, 68 | 69 | breakpoints: { 70 | s: 200, 71 | m: '500px', 72 | l: 800, 73 | }, 74 | 75 | zIndex: { 76 | low: 10, 77 | mid: 100, 78 | high: 1000, 79 | }, 80 | 81 | spacing: { 82 | scale: [0, 8, 16, 24, 32, 40], 83 | baseline: 20, 84 | }, 85 | } 86 | 87 | export default new DesignSystem(DesignSystem1) 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "design-system-utils", 3 | "version": "1.5.0", 4 | "description": "Design-system utils for working with JS-in-CSS", 5 | "main": "build/cjs.js", 6 | "module": "build/es.js", 7 | "types": "build/index.d.ts", 8 | "repository": "https://github.com/mrmartineau/design-system-utils", 9 | "directories": { 10 | "example": "example" 11 | }, 12 | "size-limit": [ 13 | { 14 | "path": "build/cjs.js", 15 | "limit": "1 KB" 16 | }, 17 | { 18 | "path": "build/es.js", 19 | "limit": "1 KB" 20 | } 21 | ], 22 | "scripts": { 23 | "presize": "npm run build", 24 | "size": "size-limit", 25 | "test": "jest", 26 | "test:watch": "jest --watch", 27 | "test:coverage": "jest --coverage", 28 | "build": "NODE_ENV=production rollup -c", 29 | "watch": "rollup -c -w", 30 | "prepublishOnly": "npm run build", 31 | "prelint": "npm run format:all", 32 | "lint": "tslint ./src/**/*.ts", 33 | "format:all": "prettier --write '{src,example}/**/*'" 34 | }, 35 | "files": [ 36 | "build", 37 | "src", 38 | "readme.md", 39 | "LICENSE" 40 | ], 41 | "keywords": [ 42 | "css-in-js", 43 | "glamorous", 44 | "styled-components", 45 | "emotion", 46 | "styled-jsx", 47 | "CSS" 48 | ], 49 | "author": "Zander Martineau", 50 | "license": "MIT", 51 | "dependencies": { 52 | "@ngard/tiny-get": "^1.2.2", 53 | "dset": "^2.0.1" 54 | }, 55 | "devDependencies": { 56 | "@size-limit/preset-small-lib": "^2.2.1", 57 | "@types/jest": "^24.0.23", 58 | "@types/prettier": "^1.18.4", 59 | "@types/rollup": "^0.54.0", 60 | "@types/typescript": "^2.0.0", 61 | "jest": "^24.9.0", 62 | "prettier": "^1.19.1", 63 | "rollup": "^1.27.2", 64 | "rollup-plugin-auto-external": "^2.0.0", 65 | "rollup-plugin-buble": "^0.19.8", 66 | "rollup-plugin-bundle-size": "^1.0.3", 67 | "rollup-plugin-commonjs": "^10.1.0", 68 | "rollup-plugin-node-resolve": "^5.2.0", 69 | "rollup-plugin-terser": "^5.1.2", 70 | "rollup-plugin-typescript2": "^0.25.2", 71 | "rollup-plugin-uglify": "^6.0.3", 72 | "size-limit": "^2.2.1", 73 | "ts-jest": "^24.1.0", 74 | "tslint": "^5.20.1", 75 | "tslint-config-prettier": "^1.18.0", 76 | "tslint-plugin-prettier": "^2.0.1", 77 | "typescript": "3.7.2" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/myDesignSystem.js: -------------------------------------------------------------------------------- 1 | import colorPalette from './colorPalette' 2 | import DesignSystem from '../src' 3 | 4 | const fontFamily = { 5 | system: 6 | '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans"', 7 | sans: '"Helvetica Neue", Helvetica, Arial, sans-serif', 8 | serif: 'Georgia, "Times New Roman", Times, serif', 9 | mono: 'Menlo, Monaco, "Courier New", monospace', 10 | } 11 | 12 | const transitions = { 13 | duration: '300ms', 14 | timing: 'cubic-bezier(0.77, 0, 0.175, 1)', 15 | } 16 | 17 | const palette = colorPalette 18 | 19 | export const myDesignSystem = { 20 | type: { 21 | baseFontSize: '20px', 22 | 23 | sizes: { 24 | xs: '16px', 25 | s: '20px', 26 | base: '30px', 27 | m: '36px', 28 | l: '42px', 29 | xl: '50px', 30 | xxl: '58px', 31 | }, 32 | 33 | fontFamily, 34 | fontFamilyBase: fontFamily.system, 35 | fontFamilyHeadings: fontFamily.mono, 36 | 37 | lineHeight: { 38 | headings: 1.1, 39 | }, 40 | 41 | fontWeight: { 42 | normal: 300, // Useful to set here if using anything other than `normal` 43 | bold: 'bold', // Useful to set here when bold webfonts come as 400 font-weight. 44 | headings: 'bold', // instead of browser default, bold 45 | }, 46 | }, 47 | 48 | colors: { 49 | colorPalette: palette, 50 | 51 | brand: { 52 | red: '#e82219', 53 | deeporange: '#ff7200', 54 | orange: '#ff9500', 55 | green: '#c4d000', 56 | teal: '#1aa5c8', 57 | navy: '#0052da', 58 | }, 59 | }, 60 | 61 | breakpoints: { 62 | s: 300, 63 | m: 500, 64 | l: 800, 65 | }, 66 | 67 | zIndex: { 68 | low: 10, 69 | mid: 100, 70 | high: 1000, 71 | }, 72 | 73 | spacing: { 74 | baseline: 20, 75 | padding: '0.3em', 76 | scale: [0, 8, 16, 24, 32, 40], 77 | }, 78 | 79 | layout: { 80 | gutter: 20, 81 | maxWidth: 1200, 82 | grid: { 83 | columnCount: 12, 84 | }, 85 | }, 86 | 87 | transition: { 88 | default: { 89 | duration: transitions.duration, 90 | timing: transitions.timing, 91 | transition: `all ${transitions.duration} ${transitions.timing}`, 92 | }, 93 | }, 94 | 95 | borderRadius: '0.3em', 96 | } 97 | 98 | export default new DesignSystem(myDesignSystem, { 99 | fontSizeUnit: 'rem', 100 | }) 101 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@ngard/tiny-get' 2 | import set from 'dset' 3 | import { 4 | System, 5 | SystemOptions, 6 | SystemBreakpoints, 7 | SystemZIndex, 8 | SystemFontSizes, 9 | SystemSpacing, 10 | SystemScale, 11 | SystemColorPalette, 12 | SystemBrandPalette, 13 | SystemType, 14 | SystemOptionalKey, 15 | } from './types' 16 | 17 | export { 18 | System, 19 | SystemOptions, 20 | SystemBreakpoints, 21 | SystemZIndex, 22 | SystemFontSizes, 23 | SystemSpacing, 24 | SystemScale, 25 | SystemColorPalette, 26 | SystemBrandPalette, 27 | SystemType, 28 | SystemOptionalKey, 29 | } 30 | 31 | /** pxTo(): converts a `rem` or `em` value to `px` */ 32 | export const pxTo = ( 33 | value: any, 34 | base: number = 16, 35 | unit: string = 'rem' 36 | ): string => `${parseFloat(value) / base}${unit}` 37 | 38 | /** toPx(): converts `px` to `rem` or `em` */ 39 | export const toPx = (value: any, base: number = 16): string => 40 | `${parseFloat(value) * base}px` 41 | 42 | /** parseUnit(): parses a number and unit string, and returns the unit used */ 43 | export const parseUnit = (str: string): string => 44 | str.trim().match(/[\d.\-+]*\s*(.*)/)[1] || '' 45 | 46 | // Errors 47 | const errorIntro = 'design-system-utils:' 48 | const MissingParent = (msg: string): string => 49 | `${errorIntro} Values missing at: ${msg} within your design tokens config` 50 | 51 | const MissingKey = ( 52 | location: string, 53 | val: string | number, 54 | val2?: string 55 | ): string => { 56 | const value = val2 === undefined ? val : `${val}.${val2}` 57 | return `${errorIntro} There is a missing value at this key: ${location}.${value}` 58 | } 59 | 60 | export default class DesignSystem { 61 | private opts: K 62 | private ds: T 63 | 64 | constructor(system: T, options?: K) { 65 | this.opts = Object.assign( 66 | {}, 67 | { 68 | fontSizeUnit: undefined, 69 | }, 70 | options 71 | ) 72 | this.ds = system 73 | } 74 | 75 | /** 76 | * get() 77 | * get any value from the design system object 78 | */ 79 | public get(value: string, obj: any = this.ds): any { 80 | return get(obj, value, undefined) 81 | } 82 | 83 | /** 84 | * set() 85 | * set any value in the design system object 86 | */ 87 | public set(location: string, value: any): void { 88 | return set(this.ds, location, value) 89 | } 90 | 91 | /** 92 | * fontSize() 93 | * get a font-size value from the design system object 94 | */ 95 | public fontSize(size: string): string { 96 | const location = 'type.sizes' 97 | this.parentCheck(location) 98 | 99 | let baseFontSize 100 | if (typeof this.ds.type.baseFontSize === 'string') { 101 | baseFontSize = parseFloat(this.ds.type.baseFontSize) 102 | } 103 | 104 | const value: string | undefined = this.get(size, this.ds.type.sizes) 105 | 106 | if (value === undefined) { 107 | throw new Error(MissingKey(location, size)) 108 | } 109 | 110 | // Don't convert the value if we don't have to 111 | if (parseUnit(value) === this.opts.fontSizeUnit) { 112 | return value 113 | } 114 | 115 | // Convert font-size to the specified unit 116 | switch (this.opts.fontSizeUnit) { 117 | case 'rem': 118 | return pxTo(value, baseFontSize, 'rem') 119 | case 'em': 120 | return pxTo(value, baseFontSize, 'em') 121 | case 'px': 122 | return toPx(value, baseFontSize) 123 | default: 124 | return value 125 | } 126 | } 127 | 128 | /** 129 | * fs() 130 | * get a font-size value from the design system object 131 | * same as fontSize() 132 | */ 133 | public fs(size: string): string { 134 | return this.fontSize(size) 135 | } 136 | 137 | /** 138 | * spacing() 139 | * get a spacing value from the design system object 140 | */ 141 | public spacing(val: string | number): string { 142 | const location = 'spacing.scale' 143 | this.parentCheck(location) 144 | 145 | const value: number | string | undefined = this.get( 146 | `${val}`, 147 | this.ds.spacing.scale 148 | ) 149 | 150 | if (value === undefined) { 151 | throw new Error(MissingKey(location, val)) 152 | } 153 | 154 | if (typeof value === 'string') { 155 | return value 156 | } 157 | 158 | return `${value}px` 159 | } 160 | 161 | /** 162 | * space() 163 | * get a spacing value from the design system object 164 | * same as spacing() 165 | */ 166 | public space(val: string | number): string { 167 | return this.spacing(val) 168 | } 169 | 170 | /** 171 | * color() 172 | * get a color from your color palette 173 | * `hue`: can contain a path to be traversed (eg: 'base.background.light'), 174 | * in that case the `variant` argument is ignored 175 | */ 176 | public color(hue: string, variant: string = 'base'): string { 177 | const location = 'colors.colorPalette' 178 | if ( 179 | this.get('colors', this.ds) === undefined && 180 | this.get(location, this.ds) === undefined 181 | ) { 182 | throw new Error(MissingParent(location)) 183 | } 184 | 185 | const isMultiPathHue = hue.split('.').length > 1 186 | 187 | const value: string | undefined = isMultiPathHue 188 | ? this.get(hue, this.ds.colors.colorPalette) 189 | : this.ds.colors.colorPalette[hue][variant] 190 | 191 | if (value === undefined) { 192 | throw new Error(MissingKey(location, hue, variant)) 193 | } 194 | 195 | return value 196 | } 197 | 198 | /** 199 | * brand() 200 | * get a color from your brand color palette 201 | */ 202 | public brand(color: string): string { 203 | const location = 'colors.brand' 204 | this.parentCheck(location) 205 | 206 | const value: string | undefined = this.get(color, this.ds.colors.brand) 207 | 208 | if (value === undefined) { 209 | throw new Error(MissingKey(location, color)) 210 | } 211 | 212 | return value 213 | } 214 | 215 | /** 216 | * bp() 217 | * get a breakpoint value from the design system object 218 | */ 219 | public bp(breakpoint: string): string { 220 | const location = 'breakpoints' 221 | if (this.get(location, this.ds) === undefined) { 222 | throw new Error(MissingParent(location)) 223 | } 224 | 225 | const value: string | undefined = this.get(breakpoint, this.ds.breakpoints) 226 | 227 | if (value === undefined) { 228 | throw new Error(MissingKey(location, breakpoint)) 229 | } 230 | 231 | return value 232 | } 233 | 234 | /** 235 | * z() 236 | * get a z-index value from the design system object 237 | */ 238 | public z(z: string): string { 239 | const location = 'zIndex' 240 | if (this.get(location, this.ds) === undefined) { 241 | throw new Error(MissingParent(location)) 242 | } 243 | 244 | const value: string | undefined = this.get(z, this.ds.zIndex) 245 | 246 | if (value === undefined) { 247 | throw new Error(MissingKey(location, z)) 248 | } 249 | 250 | return value 251 | } 252 | 253 | /** 254 | * multiply() 255 | * multiply a given value 256 | */ 257 | public multiply(initial: any, multiplier: number): number { 258 | const initialVal = 259 | typeof initial === 'string' ? parseFloat(this.get(initial)) : initial 260 | 261 | return initialVal * multiplier 262 | } 263 | 264 | private parentCheck(loc: string) { 265 | const locPt1 = loc.split('.') 266 | if ( 267 | this.get(locPt1[0], this.ds) === undefined && 268 | this.get(loc, this.ds) === undefined 269 | ) { 270 | throw new Error(MissingParent(loc)) 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import ds1 from './testData/ds1' 2 | import ds2 from './testData/ds2' 3 | import DesignSystem from './index' 4 | 5 | import { pxTo, toPx, parseUnit } from './index' 6 | 7 | describe('design-system-utils', () => { 8 | describe('get values', () => { 9 | test('get', () => { 10 | expect(ds1.get('type.baseFontSize')).toBe('30px') 11 | expect(ds1.get('foo')).toBeUndefined() 12 | expect(ds1.get('foo.bar')).toBeUndefined() 13 | expect(ds2.get('type.baseFontSize')).toBe('30px') 14 | }) 15 | 16 | test('multiply', () => { 17 | expect(ds1.multiply(20, 2)).toBe(40) 18 | expect(ds1.multiply('type.baseFontSize', 2)).toBe(60) 19 | expect(ds1.multiply('spacing.baseline', 2)).toBe(40) 20 | }) 21 | 22 | test('breakpoints', () => { 23 | expect(ds1.bp('s')).toBe(200) 24 | expect(ds1.bp('m')).toBe('500px') 25 | expect(ds2.bp('s')).toBe('400px') 26 | 27 | // Errors 28 | expect(() => ds1.bp('xxxxl')).toThrow( 29 | 'design-system-utils: There is a missing value at this key: breakpoints.xxxxl' 30 | ) 31 | 32 | const ds = new DesignSystem({}) 33 | expect(() => ds.bp('m')).toThrow( 34 | 'design-system-utils: Values missing at: breakpoints within your design tokens config' 35 | ) 36 | }) 37 | 38 | test('z-index', () => { 39 | expect(ds1.z('low')).toBe(10) 40 | expect(ds2.z('mid')).toBe(100) 41 | 42 | // Errors 43 | expect(() => ds1.z('xxxxl')).toThrow( 44 | 'design-system-utils: There is a missing value at this key: zIndex.xxxxl' 45 | ) 46 | 47 | const ds = new DesignSystem({}) 48 | expect(() => ds.z('m')).toThrow( 49 | 'design-system-utils: Values missing at: zIndex within your design tokens config' 50 | ) 51 | }) 52 | 53 | describe('spacing', () => { 54 | test('when using an array', () => { 55 | expect(ds1.spacing(0)).toBe('0px') 56 | expect(ds1.space(2)).toBe('16px') 57 | expect(ds1.spacing(3)).toBe('24px') 58 | }) 59 | 60 | test('when using an object', () => { 61 | expect(ds2.space('s')).toBe('10rem') 62 | expect(ds2.space('m')).toBe('100rem') 63 | expect(ds2.space('l')).toBe('1000rem') 64 | }) 65 | 66 | // Errors 67 | test('should return errors', () => { 68 | expect(() => ds1.spacing('xxxxl')).toThrow( 69 | 'design-system-utils: There is a missing value at this key: spacing.scale.xxxxl' 70 | ) 71 | expect(() => ds1.spacing(10)).toThrow( 72 | 'design-system-utils: There is a missing value at this key: spacing.scale.10' 73 | ) 74 | 75 | const ds = new DesignSystem({}) 76 | expect(() => ds.spacing('m')).toThrow( 77 | 'design-system-utils: Values missing at: spacing.scale within your design tokens config' 78 | ) 79 | expect(() => ds.space('m')).toThrow( 80 | 'design-system-utils: Values missing at: spacing.scale within your design tokens config' 81 | ) 82 | expect(() => ds.space(2)).toThrow( 83 | 'design-system-utils: Values missing at: spacing.scale within your design tokens config' 84 | ) 85 | }) 86 | }) 87 | 88 | describe('fontSize', () => { 89 | test('fontSize - ds1 - px', () => { 90 | expect(ds1.fontSize('base')).toBe('30px') 91 | expect(ds1.fontSize('l')).toBe('42px') 92 | 93 | // Errors 94 | expect(() => ds1.fontSize('xxxxl')).toThrow( 95 | 'design-system-utils: There is a missing value at this key: type.sizes.xxxxl' 96 | ) 97 | 98 | const ds = new DesignSystem({}) 99 | expect(() => ds.fontSize('l')).toThrow( 100 | 'design-system-utils: Values missing at: type.sizes within your design tokens config' 101 | ) 102 | }) 103 | 104 | test('fontSize - ds2 - em', () => { 105 | expect(ds2.fs('medium')).toBe('1.5em') 106 | expect(ds2.fs('large')).toBe('2.4em') 107 | 108 | // Errors 109 | expect(() => ds2.fontSize('xxxxl')).toThrow( 110 | 'design-system-utils: There is a missing value at this key: type.sizes.xxxxl' 111 | ) 112 | }) 113 | 114 | test('fontSize - rem', () => { 115 | const system = { 116 | type: { 117 | baseFontSize: '20px', 118 | 119 | sizes: { 120 | s: '1rem', 121 | m: '2rem', 122 | l: '3rem', 123 | }, 124 | }, 125 | } 126 | 127 | const ds = new DesignSystem(system) 128 | expect(ds.fontSize('m')).toBe('2rem') 129 | expect(ds.fontSize('l')).toBe('3rem') 130 | 131 | // Errors 132 | expect(() => ds.fontSize('l')).not.toThrow() 133 | }) 134 | 135 | test('fontSize - px to rem', () => { 136 | const system = { 137 | type: { 138 | baseFontSize: '20px', 139 | 140 | sizes: { 141 | s: '20px', 142 | m: '25px', 143 | l: '40px', 144 | }, 145 | }, 146 | } 147 | 148 | const ds = new DesignSystem(system, { 149 | fontSizeUnit: 'rem', 150 | }) 151 | expect(ds.fs('m')).toBe('1.25rem') 152 | expect(ds.fs('l')).toBe('2rem') 153 | }) 154 | 155 | test('fontSize - px to em', () => { 156 | const system = { 157 | type: { 158 | baseFontSize: '20px', 159 | 160 | sizes: { 161 | s: '20px', 162 | m: '25px', 163 | l: '40px', 164 | }, 165 | }, 166 | } 167 | 168 | const ds = new DesignSystem(system, { 169 | fontSizeUnit: 'em', 170 | }) 171 | expect(ds.fs('m')).toBe('1.25em') 172 | expect(ds.fs('l')).toBe('2em') 173 | }) 174 | 175 | test('fontSize - rem to px', () => { 176 | const system = { 177 | type: { 178 | baseFontSize: '20px', 179 | 180 | sizes: { 181 | s: '1rem', 182 | m: '2rem', 183 | l: '3rem', 184 | }, 185 | }, 186 | } 187 | 188 | const ds = new DesignSystem(system, { 189 | fontSizeUnit: 'px', 190 | }) 191 | expect(ds.fs('m')).toBe('40px') 192 | expect(ds.fs('l')).toBe('60px') 193 | }) 194 | 195 | test('fontSize - px to px', () => { 196 | const system = { 197 | type: { 198 | baseFontSize: '20px', 199 | 200 | sizes: { 201 | s: '20px', 202 | m: '25px', 203 | l: '40px', 204 | }, 205 | }, 206 | } 207 | 208 | const ds = new DesignSystem(system, { 209 | fontSizeUnit: 'px', 210 | }) 211 | expect(ds.fs('m')).toBe('25px') 212 | expect(ds.fs('l')).toBe('40px') 213 | }) 214 | }) 215 | 216 | test('brand', () => { 217 | expect(ds1.brand('orange')).toBe('#ff9500') 218 | expect(ds1.brand('teal')).toBe('#1aa5c8') 219 | 220 | // Errors 221 | expect(() => ds1.brand('text')).toThrow( 222 | 'design-system-utils: There is a missing value at this key: colors.brand.text' 223 | ) 224 | const ds = new DesignSystem({}) 225 | expect(() => ds.brand('text')).toThrow( 226 | 'design-system-utils: Values missing at: colors.brand within your design tokens config' 227 | ) 228 | }) 229 | 230 | test('color', () => { 231 | expect(ds1.color('primary')).toBe('#181830') 232 | expect(ds1.color('secondary', 'light')).toBe('#fea04c') 233 | expect(ds1.color('nested.secondary.light')).toBe('#fea04c') 234 | expect(ds1.color('nested.secondary.deep.nested.light')).toBe('#fea04c') 235 | 236 | // Errors 237 | expect(() => ds1.color('text', 'dark')).toThrow( 238 | 'design-system-utils: There is a missing value at this key: colors.colorPalette.text.dark' 239 | ) 240 | const ds = new DesignSystem({}) 241 | expect(() => ds.color('text')).toThrow( 242 | 'design-system-utils: Values missing at: colors.colorPalette within your design tokens config' 243 | ) 244 | }) 245 | }) 246 | 247 | describe('design-system-utils helpers', () => { 248 | test(`pxTo`, () => { 249 | expect(pxTo('30px', 16, 'em')).toBe('1.875em') 250 | expect(pxTo('30px', 16, 'rem')).toBe('1.875rem') 251 | expect(pxTo('30px', 20)).toBe('1.5rem') 252 | expect(pxTo('30px')).toBe('1.875rem') 253 | }) 254 | 255 | test(`toPx`, () => { 256 | expect(toPx('1.875em', 16)).toBe('30px') 257 | expect(toPx('1.875em')).toBe('30px') 258 | expect(toPx('1.875rem', 16)).toBe('30px') 259 | }) 260 | 261 | test(`parseUnit`, () => { 262 | expect(parseUnit('1.875em')).toBe('em') 263 | expect(parseUnit('1.875rem')).toBe('rem') 264 | expect(parseUnit('1.875 rem')).toBe('rem') 265 | expect(parseUnit('18px')).toBe('px') 266 | expect(parseUnit('18 px')).toBe('px') 267 | expect(parseUnit('18 px ')).toBe('px') 268 | expect(parseUnit(' 18 px')).toBe('px') 269 | expect(parseUnit(' 18 px ')).toBe('px') 270 | expect(parseUnit(' 18 px ')).toBe('px') 271 | }) 272 | }) 273 | 274 | describe('set values', () => { 275 | test('set', () => { 276 | ds1.set('foo', 'bar') 277 | expect(ds1.get('foo')).toBe('bar') 278 | }) 279 | }) 280 | }) 281 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 👩‍🎨
3 | Design System Utils 4 | 5 | [![npm](https://img.shields.io/npm/v/design-system-utils.svg?style=flat-square)](https://www.npmjs.com/package/design-system-utils) 6 | [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/design-system-utils.svg?style=flat-square)](https://www.npmjs.com/package/design-system-utils) [![Travis CI Build](https://img.shields.io/travis/mrmartineau/design-system-utils.svg?style=flat-square)](https://travis-ci.org/mrmartineau/design-system-utils) 7 | ![](https://img.shields.io/badge/licence-MIT-blue.svg?style=flat-square) 8 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 9 | 10 |

11 | 12 | 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](https://styled-components.com), [emotion](https://emotion.sh), [glamorous](https://glamorous.rocks/) or any other [CSS-in-JS framework](https://css-in-js-playground.com/). 13 | 14 | ## Install 15 | 16 | ```sh 17 | yarn add design-system-utils 18 | 19 | # or 20 | 21 | npm install --save design-system-utils 22 | ``` 23 | 24 | ### Size 25 | 26 | ``` 27 | $ size-limit 28 | 29 | build/cjs.js 30 | Package size: 814 B 31 | Size limit: 1 KB 32 | 33 | es/index.js 34 | Package size: 806 B 35 | Size limit: 1 KB 36 | 37 | With all dependencies, minified and gzipped 38 | ``` 39 | 40 |
41 | 🤓 Table of contents 42 | 43 | 44 | 45 | 46 | 47 | - [Usage](#usage) 48 | - [Setup](#setup) 49 | - [Initialise the design system framework](#initialise-the-design-system-framework) 50 | - [Accessing the design system data in your app](#accessing-the-design-system-data-in-your%C2%A0app) 51 | - [Options](#options) 52 | - [Basic API methods](#basic-api-methods) 53 | - [`tokens.get()` - Get a token value](#tokensget---get-a-token-value) 54 | - [`tokens.set()` - Set a token value](#tokensset---set-a-token-value) 55 | - [API helper methods](#api-helper-methods) 56 | - [`tokens.fontSize()` or `tokens.fs()` - Get font-size values](#tokensfontsize-or-tokensfs---get-font-size-values) 57 | - [Modular scale](#modular-scale) 58 | - [Color palette](#color-palette) 59 | - [`tokens.color()` - Get color palette values](#tokenscolor---get-color-palette-values) 60 | - [`tokens.brand()` - Get brand palette values](#tokensbrand---get-brand-palette-values) 61 | - [`tokens.bp()` - Get responsive breakpoint values](#tokensbp---get-responsive-breakpoint-values) 62 | - [`tokens.z()` - Get `z-index` values](#tokensz---get-z-index-values) 63 | - [`tokens.spacing()` or `tokens.space()` - Get spacing values](#tokensspacing-or-tokensspace---get-spacing-values) 64 | - [Array example:](#array-example) 65 | - [Object example:](#object-example) 66 | - [Calculations](#calculations) 67 | - [`tokens.multiply()`](#tokensmultiply) 68 | - [`pxTo()`](#pxto) 69 | - [`toPx()`](#topx) 70 | - [`parseUnit()`](#parseunit) 71 | - [Usage with Typescript](#usage-with-typescript) 72 | - [Aliases](#aliases) 73 | - [Demo & examples](#demo--examples) 74 | - [Licence](#licence) 75 | 76 | 77 | 78 |
79 | 80 | ## Usage 81 | 82 | 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). 83 | 84 | For example you can create a top-level directory named `tokens`, `theme` or `designsystem`, and add an index.js inside, like so: 85 | 86 | ``` 87 | ./tokens 88 | └── index.js 89 | ``` 90 | 91 | A simple version of a tokens file with Design System Utils: 92 | 93 | ```js 94 | // ./tokens/index.js 95 | import DesignSystem from 'design-system-utils' 96 | 97 | // your design tokens object goes here, see below for further details 98 | const designTokens = {...} 99 | 100 | export default new DesignSystem(designTokens) 101 | ``` 102 | 103 | ### Setup 104 | 105 | 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: 106 | 107 | (🤔 the below code snippet includes some psuedo types for the values that occur in the different parts of the tokens object) 108 | 109 | ```js 110 | { 111 | type: { 112 | // this should be set as a px value if you have `options.fontSizeUnit` set 113 | // to 'rem' or 'em' so that the lib can convert the values properly 114 | baseFontSize: '' // string, 115 | 116 | // used with `tokens.fs('size')` or `tokens.fontSize('size')` 117 | sizes: { 118 | key: '' // , 119 | }, 120 | }, 121 | 122 | // Color palette 123 | // Each object needs to have the same shape 124 | // Each color object needs a `base` value to be the default 125 | // Have as many color objects as you like 126 | colors: { 127 | // Used with `ds.color('colorName')` 128 | colorPalette: { 129 | colorName: { 130 | base: '' // , // base is the default 131 | }, 132 | }, 133 | 134 | // Used with `ds.brand('colorName)` 135 | brand: { 136 | colorName: '' // , base is the default 137 | } 138 | }, 139 | 140 | // Breakpoints 141 | // Used with `ds.bp()` 142 | // Keys can be anything you like 143 | // Have as many breakpoints as you like 144 | // Values can be use any unit you like 145 | breakpoints: { 146 | key: '' // , 147 | }, 148 | 149 | // Z-index 150 | // Used with `ds.z()` 151 | zIndex: { 152 | key: 10 // 153 | }, 154 | 155 | // Spacing 156 | // Used with `ds.spacing()` or `ds.space()` 157 | spacing: { 158 | scale: [] // [, ...], 159 | }, 160 | } 161 | ``` 162 | 163 | Below is an excerpt from the example design-system. See a more complete version in the [`/example`](example/myDesignSystem.js) directory or some that are used in the design-system-utils tests: [1](https://github.com/mrmartineau/design-system-utils/blob/master/src/testData/ds1.js) & [2](https://github.com/mrmartineau/design-system-utils/blob/master/src/testData/ds1.js). 164 | 165 | ```js 166 | const designTokens = { 167 | type: { 168 | baseFontSize: '20px', 169 | 170 | sizes: { 171 | xs: '16px', 172 | s: '20px', 173 | base: '30px', 174 | m: '36px', 175 | l: '42px', 176 | xl: '50px', 177 | xxl: '58px', 178 | }, 179 | 180 | fontFamily: { 181 | system: 182 | '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans"', 183 | sans: '"Helvetica Neue", Helvetica, Arial, sans-serif', 184 | serif: 'Georgia, "Times New Roman", Times, serif', 185 | mono: 'Menlo, Monaco, "Courier New", monospace', 186 | }, 187 | 188 | lineHeight: { 189 | headings: 1.1, 190 | }, 191 | 192 | fontWeight: { 193 | normal: 300, // Useful to set here if using anything other than `normal` 194 | bold: 'bold', // Useful to set here when bold webfonts come as 400 font-weight. 195 | headings: 'bold', // instead of browser default, bold 196 | }, 197 | }, 198 | } 199 | ``` 200 | 201 | ### Initialise the design system framework 202 | 203 | ```js 204 | // myDesignSystem.js 205 | import DesignSystem from 'design-system' 206 | const designTokens = {...} 207 | export default new DesignSystem(designTokens) 208 | ``` 209 | 210 | ## Accessing the design system data in your app 211 | 212 | To access your design system, you just need to `import` it to the current file, like so: 213 | 214 | ```js 215 | import tokens from './tokens' // assuming you exported `default` from your design system file 216 | ``` 217 | 218 | Here is a very simple component using [styled-components](https://styled-components.com) and some values from the tokens, you should be able to see how easy it is to pull information from the design system. 219 | 220 | ```js 221 | // Example using styled-components 222 | import styled from 'styled-component' 223 | import tokens from './tokens' 224 | 225 | export const Box = styled.div` 226 | font-family: ${tokens.get('type.fontFamilyBase')}; 227 | background-color: ${tokens.brand('primary')}; 228 | margin: ${tokens.space(2)} 0; 229 | ` 230 | ``` 231 | 232 | ## Options 233 | 234 | There is only one option that can be passed to your design system class, it relates to font-sizing: 235 | 236 | ```js 237 | // Use default options. do not convert the font-sizes to rems or ems 238 | export default new DesignSystem(myDesignSystem) 239 | 240 | // OR: with custom options 241 | export default new DesignSystem(myDesignSystem, { 242 | // this is used to convert your `type.sizes` values from one unit to another 243 | // e.g. to convert all `px` sizes to `rem`, set this option: 244 | fontSizeUnit: 'rem', 245 | // this means you can define values using px and use rems in your app 246 | }) 247 | ``` 248 | 249 | ## Basic API methods 250 | 251 | ### `tokens.get()` - Get a token value 252 | 253 | 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. 254 | 255 | ```js 256 | // with the system setup, as above 257 | tokens.get('lineHeight.headings') // 1.1 258 | tokens.get('a.really.deeply.nested.value') 259 | ``` 260 | 261 | ### `tokens.set()` - Set a token value 262 | 263 | 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. 264 | 265 | Like the `.get()` method, use object dot notation to find the value you need from your design system object. 266 | 267 | This method uses [dset](https://github.com/lukeed/dset#usage) under the hood, so please read the docs there for more info. 268 | 269 | ```js 270 | // with the system setup, as above 271 | tokens.set('forms.inputBackground', '#fff') 272 | 273 | // then use it later like so: 274 | tokens.get('forms.inputBackground') 275 | ``` 276 | 277 | ## API helper methods 278 | 279 | The helper methods make getting values _much_ more simple. 280 | 281 | - [`tokens.fontSize()` or `tokens.fs()`](#tokensfontsize-or-tokensfs---get-font-size-values) - Get font-size values 282 | - [`tokens.color()`](#tokenscolor---get-color-palette-values) - Get color palette values 283 | - [`tokens.brand()`](#tokensbrand---get-brand-palette-values) - Get brand palette values 284 | - [`tokens.bp()`](#tokensbp---get-responsive-breakpoint-values) - Get responsive breakpoint values 285 | - [`tokens.z()`](#tokensz---get-z-index-values) - Get `z-index` values 286 | - [`tokens.spacing()` or `tokens.space()`](#tokensspacing-or-tokensspace---get-spacing-values) - Get spacing values 287 | 288 | ### `tokens.fontSize()` or `tokens.fs()` - Get font-size values 289 | 290 | 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. 291 | 292 | The `type.sizes` object’s values can be formatted in a few ways: 293 | 294 | - as a string with any unit of measurement, e.g. `s: '13px'` / `px`, `rem` or `em` 295 | - 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](https://github.com/modularscale/modularscale-js)** 296 | 297 | ```js 298 | // define some values// type.sizes object 299 | sizes: { 300 | xs: '16px', 301 | s: '20px', 302 | base: '30px', 303 | m: '36px', 304 | l: '42px', 305 | xl: '50px', 306 | xxl: '58px', 307 | }, 308 | 309 | // retrieve some values 310 | tokens.fontSize('xl') 311 | tokens.fs('xl') // `tokens.fs()` is a short-hand alias for `tokens.fontSize()` 312 | tokens.fs('xl', true) // return font-size in px regardless of `option.fontSizeUnit` value 313 | ``` 314 | 315 | #### Modular scale 316 | 317 | **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.** 318 | 319 | To make use of a modular scale, there are a few things that need to be done: 320 | 321 | - install a modular scale converter package, like [modularscale-js](https://github.com/modularscale/modularscale-js) 322 | - define your modular scale options outside of your design-system object 323 | - add the modular scale values to the `type.sizes` object 324 | 325 | ```js 326 | const modularscale = { 327 | base: [30], 328 | ratio: 1.5, 329 | } 330 | 331 | // design system 332 | ... 333 | sizes: { 334 | xs: `${ms(-2, modularscale)}px`, 335 | s: `${ms(-1, modularscale)}px`, 336 | } 337 | ... 338 | ``` 339 | 340 | Testing and remembering the values from your modular scale can be tricky, there are two options that can be used, either: 341 | 342 | - visit [modularscale.com](https://modularscale.com) and enter your settings, you can then view all the type sizes on the scale you specified 343 | - or, add the below snippet to your code to print out the values of your scale: 344 | 345 | ```js 346 | const sizes = tokens.get('type.sizes') 347 | Object.keys(sizes).forEach(item => { 348 | console.log(item, ':', sizes[item]) // e.g. `base : 20px` 349 | }) 350 | ``` 351 | 352 | ### Color palette 353 | 354 | There are two possible ways to access color information: the color palette and the brand colors. 355 | 356 | 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. 357 | 358 | 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: 359 | 360 | ```js 361 | colors: { 362 | // With a color palette like this: 363 | colorPalette: { 364 | bright: { 365 | base: '#F9FAFB', 366 | dark: '#F4F6F8', 367 | darker: '#DFE4E8', 368 | }, 369 | 370 | dark: { 371 | base: '#212B35', 372 | light: '#454F5B', 373 | lighter: '#637381', 374 | }, 375 | }, 376 | 377 | // With a brand palette like this: 378 | brand: { 379 | red: '#e82219', 380 | deeporange: '#ff7200', 381 | orange: '#ff9500', 382 | green: '#c4d000', 383 | teal: '#1aa5c8', 384 | navy: '#0052da', 385 | } 386 | }, 387 | ``` 388 | 389 | #### `tokens.color()` - Get color palette values 390 | 391 | 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. 392 | This is a short-hand for the `tokens.get()` function. 393 | 394 | ```js 395 | // Get values like this: 396 | tokens.color('bright') // #F9FAFB - the `base` key is the default, so it is not needed 397 | tokens.color('bright', 'dark') 398 | tokens.color('background.extra.dark') // Accepts a path (in this case the second `variant` argument is ignored) 399 | ``` 400 | 401 | #### `tokens.brand()` - Get brand palette values 402 | 403 | The `tokens.brand()` function gets values from the `colors.brand` object. 404 | This is a short-hand for the `tokens.get()` function. 405 | 406 | ```js 407 | // Get brand values like this: 408 | tokens.brand('orange') 409 | tokens.brand('pink') 410 | tokens.brand('primary.blue') // it is possible to nest this object as much as you like 411 | ``` 412 | 413 | ### `tokens.bp()` - Get responsive breakpoint values 414 | 415 | 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. 416 | 417 | ```js 418 | tokens.bp('m') 419 | ``` 420 | 421 | ### `tokens.z()` - Get `z-index` values 422 | 423 | 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. 424 | 425 | ```js 426 | tokens.z('low') 427 | ``` 428 | 429 | ### `tokens.spacing()` or `tokens.space()` - Get spacing values 430 | 431 | The `tokens.spacing()` method returns a value from your `spacing.scale` definition. **The spacing data could either be an array, or an object.** 432 | 433 | - 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. 434 | - If an object, it takes a `key` (string) for the item in that object e.g. `tokens.space('m')` 435 | 436 | #### Array example: 437 | 438 | ```js 439 | scale: [0, 8, 16, 24, 32, 40] 440 | 441 | tokens.spacing(2) // '16px' 442 | // Note: `tokens.space(2)` can also be used 443 | ``` 444 | 445 | #### Object example: 446 | 447 | ```js 448 | scale: { 449 | s: '10rem', 450 | m: '100rem', 451 | l: '1000rem', 452 | } 453 | 454 | tokens.spacing('m') // '100rem' 455 | // Note: `tokens.space('m')` can also be used 456 | ``` 457 | 458 | ### Calculations 459 | 460 | The framework currently provides a few calculation functions, `multiply`, `toPx` and `pxTo`: 461 | 462 | #### `tokens.multiply()` 463 | 464 | ```js 465 | tokens.multiply(10, 2) // 20 466 | 467 | // you can pass in another value from the system 468 | tokens.multiply(tokens.get('spacing.baseline'), 2) 469 | 470 | // or just use the key from the system 471 | // the initial value will always be run through `parseFloat()` 472 | tokens.multiply('spacing.baseline', 2) 473 | ``` 474 | 475 | #### `pxTo()` 476 | 477 | Converts `px` to `rem` or `em` 478 | 479 | ```js 480 | import { pxTo } from 'design-system-utils' 481 | // pxTo(fontSize, baseFontSize, unit - 'rem'/'em') 482 | pxTo(12, 20, 'rem') // 0.6rem 483 | pxTo(12, 20, 'em') // 0.6em 484 | ``` 485 | 486 | #### `toPx()` 487 | 488 | Converts `rem` or `em` value to `px` 489 | 490 | ```js 491 | import { toPx } from 'design-system-utils' 492 | // toPx(fontSize, baseFontSize) 493 | toPx('1.875rem', 16) // 30px 494 | toPx('1.875em', 16) // 30px 495 | ``` 496 | 497 | #### `parseUnit()` 498 | 499 | Parses a number and unit string, and returns the unit used 500 | 501 | ```js 502 | import { parseUnit } from 'design-system-utils' 503 | parseUnit('1.875rem') // 'rem' 504 | parseUnit('18px') // 'px' 505 | ``` 506 | 507 | ## Usage with Typescript 508 | 509 | Typescript types and interfaces should be imported as named imports. 510 | 511 | See all the type definitions in the [types.ts](https://github.com/mrmartineau/design-system-utils/blob/master/src/types.ts) file. Here are all the exported types that can be extended: 512 | 513 | ``` 514 | { 515 | System, 516 | SystemOptions, 517 | SystemBreakpoints, 518 | SystemZIndex, 519 | SystemFontSizes, 520 | SystemSpacing, 521 | SystemScale, 522 | SystemColorPalette, 523 | SystemBrandPalette, 524 | SystemType, 525 | SystemOptionalKey, 526 | } 527 | ``` 528 | 529 | Below is an example where a new item (`baseline`) is added to the `spacing` object. 530 | 531 | ```ts 532 | import DesignSystem, { System, SystemOptions, SystemSpacing } from '../index' 533 | 534 | interface MySystemSpacing extends SystemSpacing { 535 | baseline: number 536 | } 537 | 538 | interface MySystem extends System { 539 | spacing: MySystemSpacing 540 | } 541 | 542 | const Tokens: MySystem = { 543 | ... 544 | ... 545 | 546 | spacing: { 547 | scale: [0, 8, 16, 24, 32, 40], 548 | baseline: 20, 549 | }, 550 | } 551 | 552 | export default new DesignSystem(Tokens) 553 | ``` 554 | 555 | ## Aliases 556 | 557 | 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: 558 | 559 | ```js 560 | export tokens from './tokens' // import your design tokens 561 | 562 | // create new alias functions 563 | 564 | // this renames .bp() 565 | export const breakpoints = bp => tokens.bp(bp) 566 | 567 | // this adds a new alias that doesn't already exist in Design System Utils 568 | export const fontWeights = weight => tokens.get('type.fontWeights') 569 | 570 | // the aliases can also be for specific values 571 | export const baseFontSize = ds1.get('type.baseFontSize') 572 | export const brandPrimary = ds1.brand('red') 573 | ``` 574 | 575 | Then use them like so: 576 | 577 | ```js 578 | import { breakpoints, fontWeights, baseFontSize, brandPrimary } from './tokens' 579 | 580 | breakpoints('m') 581 | fontWeights('normal') 582 | ``` 583 | 584 | Take a look at [`alias.ts`](https://github.com/mrmartineau/design-system-utils/blob/master/src/alias.ts) and [`alias.test.ts`](https://github.com/mrmartineau/design-system-utils/blob/master/src/alias.test.ts) to see working examples and their usage. 585 | 586 | ## Demo & examples 587 | 588 | I created a demo on [codesandbox.io](https://codesandbox.io/s/6wrp94x7kk), it includes examples of using the design-system utils with [emotion](https://emotion.sh/), [styled-components](https://www.styled-components.com/) and [glamorous](https://glamorous.rocks). There is also a basic example [here](example/). 589 | 590 | ## Licence 591 | 592 | MIT © [Zander Martineau](https://zander.wtf) 593 | --------------------------------------------------------------------------------