├── www ├── .eslintrc ├── src │ ├── pages │ │ ├── json.js │ │ ├── css.js │ │ └── index.js │ └── flatten.js └── package.json ├── .npmignore ├── .gitignore ├── palx ├── babel.config.js ├── test │ ├── snapshots │ │ ├── index.js.snap │ │ └── index.js.md │ └── index.js ├── src │ ├── flatten.js │ └── index.js └── package.json ├── package.json └── README.md /www/.eslintrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | src 4 | server 5 | test 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | dist 4 | public 5 | .cache 6 | -------------------------------------------------------------------------------- /palx/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | ], 5 | } 6 | -------------------------------------------------------------------------------- /palx/test/snapshots/index.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jxnblk/palx/HEAD/palx/test/snapshots/index.js.snap -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "www", 5 | "palx" 6 | ], 7 | "scripts": { 8 | "build": "yarn workspace www build" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /www/src/pages/json.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => { 4 | if (!props.location.state) return false 5 | const { colors } = props.location.state 6 | if (!colors) return 'todo: redirect' 7 | const json = JSON.stringify(colors, null, 2) 8 | 9 | return ( 10 |
11 |       {json}
12 |     
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /palx/test/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import palx from '../src' 3 | 4 | test('returns a color object', t => { 5 | const colors = palx('#07c') 6 | t.is(typeof colors, 'object') 7 | t.snapshot(colors) 8 | }) 9 | 10 | test('returns a gray object for desaturated base colors', t => { 11 | const colors = palx('#444') 12 | t.is(typeof colors, 'object') 13 | t.snapshot(colors) 14 | t.is(Object.keys(colors).length, 4) 15 | }) 16 | -------------------------------------------------------------------------------- /www/src/flatten.js: -------------------------------------------------------------------------------- 1 | export default obj => Object.keys(obj).map(key => { 2 | const value = obj[key] 3 | if (Array.isArray(value)) { 4 | return value.map((v, i) => ({ 5 | key: key + i, 6 | value: v 7 | })) 8 | } 9 | 10 | return { 11 | key, 12 | value 13 | } 14 | }).reduce((a, b) => { 15 | if (Array.isArray(b)) { 16 | b.forEach(({ key, value }) => { 17 | a[key] = value 18 | }) 19 | } else { 20 | a[b.key] = b.value 21 | } 22 | return a 23 | }, {}) 24 | -------------------------------------------------------------------------------- /palx/src/flatten.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = obj => Object.keys(obj).map(key => { 3 | const value = obj[key] 4 | if (Array.isArray(value)) { 5 | return value.map((v, i) => ({ 6 | key: key + i, 7 | value: v 8 | })) 9 | } 10 | 11 | return { 12 | key, 13 | value 14 | } 15 | }).reduce((a, b) => { 16 | if (Array.isArray(b)) { 17 | b.forEach(({ key, value }) => { 18 | a[key] = value 19 | }) 20 | } else { 21 | a[b.key] = b.value 22 | } 23 | return a 24 | }, {}) 25 | 26 | 27 | -------------------------------------------------------------------------------- /palx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "palx", 3 | "version": "1.1.0", 4 | "description": "Automatic UI Color Palette Generator", 5 | "main": "dist/index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "prepublish": "babel src -d dist", 9 | "test": "nyc ava" 10 | }, 11 | "devDependencies": { 12 | "@babel/cli": "^7.6.4", 13 | "@babel/core": "^7.6.4", 14 | "@babel/preset-env": "^7.6.3", 15 | "ava": "^2.4.0", 16 | "nyc": "^14.1.1" 17 | }, 18 | "author": "Brent Jackson ", 19 | "dependencies": { 20 | "chroma-js": "^2.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "www", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "gatsby develop", 8 | "build": "gatsby build", 9 | "serve": "gatsby serve", 10 | "clean": "gatsby clean" 11 | }, 12 | "dependencies": { 13 | "@emotion/core": "^10.0.22", 14 | "@mdx-js/mdx": "^1.5.1", 15 | "@mdx-js/react": "^1.5.1", 16 | "@theme-ui/components": "^0.2.46", 17 | "gatsby": "^4.16.0", 18 | "gatsby-plugin-react-helmet": "^5.16.0", 19 | "palx": "^1.1.0", 20 | "react": "^18.1.0", 21 | "react-dom": "^18.1.0", 22 | "react-helmet": "^6.1.0", 23 | "theme-ui": "^0.2.46" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /www/src/pages/css.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import flatten from '../flatten' 3 | 4 | const createDef = ({ key, value }) => `--${key}: ${value}` 5 | const createRule = (prop, prefix = '') => ({ key }) => `.${prefix + key} { ${prop}: var(--${key}) }` 6 | 7 | 8 | const toArray = obj => Object.keys(obj).map(key => { 9 | const value = obj[key] 10 | return { key, value } 11 | }) 12 | 13 | export default props => { 14 | if (!props.location.state) return false 15 | const { colors } = props.location.state 16 | if (!colors) return 'todo: redirect' 17 | 18 | const obj = flatten(colors) 19 | const arr = toArray(obj) 20 | const defs = arr.map(createDef) 21 | const properties = `:root {\n ${defs.join(';\n ')}\n}` 22 | const colorRules = arr.map(createRule('color')).join('\n') 23 | const bgRules = arr.map(createRule('background-color', 'bg-')).join('\n') 24 | 25 | const css = [ properties, colorRules, bgRules ].join('\n\n') 26 | 27 | return ( 28 |
29 |       {css}
30 |     
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Palx 3 | 4 | Automatic UI Color Palette Generator 5 | 6 | https://palx.jxnblk.com 7 | 8 | ```js 9 | npm i palx 10 | ``` 11 | 12 | Provide a single color value and Palx returns a full-spectrum color palette, 13 | well suited for UI design and data visualizations that work harmoniously with brand colors. 14 | 15 | ```js 16 | const palx = require('palx') 17 | 18 | palx('#07c') 19 | // Returns a color object with 20 | // 12 hues and a gray spread across 21 | // 10 luminance steps 22 | ``` 23 | 24 | ## About 25 | 26 | Inspired by [Open Color](https://yeun.github.io/open-color/), 27 | Palx takes a single input color, 28 | then spreads the hue across the color spectrum in 12 steps, 29 | and spreads each hue across 10 luminance steps. 30 | Palx also desaturates the main color to create shades of gray and a black that are based off the original color. 31 | 32 | ### Related 33 | 34 | - [Hello Color](http://jxnblk.com/hello-color) 35 | - [Colorable](http://jxnblk.com/colorable) 36 | - [Monochrome](https://monochrome.jxnblk.com) 37 | - [Grays](http://jxnblk.com/grays) 38 | - [Shade](http://jxnblk.com/shade) 39 | - [Spectral](http://jxnblk.com/Spectral) 40 | - [Open Color](https://yeun.github.io/open-color) 41 | 42 | MIT License 43 | 44 | -------------------------------------------------------------------------------- /palx/src/index.js: -------------------------------------------------------------------------------- 1 | const chroma = require('chroma-js') 2 | 3 | const names = [ 4 | 'red', // 0 5 | 'orange', // 30 6 | 'yellow', // 60 7 | 'lime', // 90 8 | 'green', // 120 9 | 'teal', // 150 10 | 'cyan', // 180 11 | 'blue', // 210 12 | 'indigo', // 240 13 | 'violet', // 270 14 | 'fuschia', // 300 15 | 'pink', // 330 16 | 'red', // 360 17 | ] 18 | 19 | const hueName = h => { 20 | const i = Math.round((h - 2) / 30) 21 | const name = names[i] 22 | return name 23 | } 24 | 25 | const lums = [ 26 | 9, 27 | 8, 28 | 7, 29 | 6, 30 | 5, 31 | 4, 32 | 3, 33 | 2, 34 | 1, 35 | 0 36 | ] 37 | .map(n => n + .5) 38 | .map(n => n / 10) 39 | 40 | const createArray = length => { 41 | const arr = [] 42 | for (let i = 0; i < length; i++) { 43 | arr.push(i) 44 | } 45 | return arr 46 | } 47 | 48 | const createHues = length => { 49 | const hueStep = 360 / length 50 | return base => { 51 | const hues = createArray(length) 52 | .map(n => Math.floor((base + (n * hueStep)) % 360)) 53 | 54 | return hues 55 | } 56 | } 57 | 58 | const desat = n => hex => { 59 | const [ h, s, l ] = chroma(hex).hsl() 60 | return chroma.hsl(h, n, l).hex() 61 | } 62 | 63 | const createBlack = hex => { 64 | const d = desat(1/8)(hex) 65 | return chroma(d).luminance(.05).hex() 66 | } 67 | 68 | const createShades = hex => { 69 | return lums.map(lum => { 70 | return chroma(hex).luminance(lum).hex() 71 | }) 72 | } 73 | 74 | // Mappers 75 | const keyword = hex => { 76 | const [ hue, sat ] = chroma(hex).hsl() 77 | if (sat < .5) { 78 | return 'gray' 79 | } 80 | const name = hueName(hue) 81 | return name 82 | } 83 | 84 | // Reducer 85 | const toObj = (a, color) => { 86 | const key = a[color.key] ? color.key + '2' : color.key 87 | a[key] = color.value 88 | return a 89 | } 90 | 91 | const palx = (hex, options = {}) => { 92 | const color = chroma(hex) 93 | const colors = [] 94 | const [ hue, sat, lte ] = color.hsl() 95 | 96 | const hues = createHues(12)(hue) 97 | 98 | colors.push({ 99 | key: 'black', 100 | value: createBlack('' + color.hex()) 101 | }) 102 | 103 | colors.push({ 104 | key: 'gray', 105 | value: createShades(desat(1/8)('' + color.hex())) 106 | }) 107 | 108 | hues.forEach(h => { 109 | const c = chroma.hsl(h, sat, lte) 110 | const key = keyword(c) 111 | colors.push({ 112 | key, 113 | value: createShades('' + c.hex()) 114 | }) 115 | }) 116 | 117 | const obj = Object.assign({ 118 | base: hex, 119 | }, colors.reduce(toObj, {})) 120 | 121 | return obj 122 | } 123 | 124 | module.exports = palx 125 | -------------------------------------------------------------------------------- /palx/test/snapshots/index.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/index.js` 2 | 3 | The actual snapshot is saved in `index.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## returns a color object 8 | 9 | > Snapshot 1 10 | 11 | { 12 | base: '#07c', 13 | black: '#374047', 14 | blue: [ 15 | '#f6fafd', 16 | '#e2eff9', 17 | '#cce4f5', 18 | '#b5d8f0', 19 | '#9bcaeb', 20 | '#7dbae5', 21 | '#5aa7de', 22 | '#2d8fd5', 23 | '#006fbe', 24 | '#004170', 25 | ], 26 | cyan: [ 27 | '#f0fcfb', 28 | '#cef5f2', 29 | '#a8eee8', 30 | '#7ae4dc', 31 | '#40d9cc', 32 | '#00c9b8', 33 | '#00b3a4', 34 | '#009a8d', 35 | '#007a70', 36 | '#004842', 37 | ], 38 | fuschia: [ 39 | '#fdf8fe', 40 | '#f9e8fa', 41 | '#f5d8f7', 42 | '#f0c5f3', 43 | '#eab0ef', 44 | '#e498ea', 45 | '#dc7be5', 46 | '#d153dd', 47 | '#bb01cc', 48 | '#72007c', 49 | ], 50 | gray: [ 51 | '#f9f9fa', 52 | '#eceeef', 53 | '#dee1e3', 54 | '#cfd3d7', 55 | '#bfc5c9', 56 | '#adb4b9', 57 | '#98a1a8', 58 | '#7f8b93', 59 | '#606e79', 60 | '#374047', 61 | ], 62 | green: [ 63 | '#f2fcf1', 64 | '#d6f6d3', 65 | '#b6efb1', 66 | '#90e788', 67 | '#61dd56', 68 | '#1fcf0f', 69 | '#0fb900', 70 | '#0d9f00', 71 | '#0b7e00', 72 | '#064a00', 73 | ], 74 | indigo: [ 75 | '#f9f9fe', 76 | '#ebedfb', 77 | '#dddff8', 78 | '#cdd1f5', 79 | '#bcc0f2', 80 | '#a9aeee', 81 | '#929ae9', 82 | '#7780e4', 83 | '#525edc', 84 | '#0516cd', 85 | ], 86 | lime: [ 87 | '#f6fcee', 88 | '#e2f4c8', 89 | '#cbec9e', 90 | '#b2e26e', 91 | '#94d736', 92 | '#75c800', 93 | '#68b300', 94 | '#599900', 95 | '#477900', 96 | '#294700', 97 | ], 98 | orange: [ 99 | '#fdf9f5', 100 | '#f9ebe1', 101 | '#f5dccb', 102 | '#f0ccb3', 103 | '#eaba98', 104 | '#e4a679', 105 | '#dd8d54', 106 | '#d36d24', 107 | '#b54c00', 108 | '#6b2d00', 109 | ], 110 | pink: [ 111 | '#fef8fb', 112 | '#fbe9f3', 113 | '#f7d8ea', 114 | '#f4c6e1', 115 | '#f0b2d6', 116 | '#eb9ac9', 117 | '#e57dba', 118 | '#dd57a5', 119 | '#cf0d7e', 120 | '#7f004a', 121 | ], 122 | red: [ 123 | '#fef8f9', 124 | '#fbe9eb', 125 | '#f8dadc', 126 | '#f4c8cc', 127 | '#f0b5ba', 128 | '#ec9ea5', 129 | '#e6838b', 130 | '#df5f6a', 131 | '#d2202f', 132 | '#85000b', 133 | ], 134 | teal: [ 135 | '#f1fcf6', 136 | '#d2f6e1', 137 | '#afefc9', 138 | '#85e7ae', 139 | '#52dc8b', 140 | '#0ace5c', 141 | '#00b84d', 142 | '#009e42', 143 | '#007d34', 144 | '#004a1f', 145 | ], 146 | violet: [ 147 | '#fbf8fe', 148 | '#f1ebfb', 149 | '#e8dcf8', 150 | '#ddccf5', 151 | '#d1baf1', 152 | '#c4a6ed', 153 | '#b48ee8', 154 | '#a172e3', 155 | '#864adb', 156 | '#4c00b8', 157 | ], 158 | yellow: [ 159 | '#fbfaec', 160 | '#f3efc4', 161 | '#eae398', 162 | '#e0d666', 163 | '#d5c72c', 164 | '#c5b500', 165 | '#b0a200', 166 | '#978b00', 167 | '#776e00', 168 | '#464000', 169 | ], 170 | } 171 | 172 | ## returns a gray object for desaturated base colors 173 | 174 | > Snapshot 1 175 | 176 | { 177 | base: '#444', 178 | black: '#3f3f3f', 179 | gray: [ 180 | '#f9f9f9', 181 | '#ededed', 182 | '#e1e1e1', 183 | '#d3d3d3', 184 | '#c4c4c4', 185 | '#b3b3b3', 186 | '#a0a0a0', 187 | '#898989', 188 | '#6c6c6c', 189 | '#3f3f3f', 190 | ], 191 | gray2: [ 192 | '#f9f9f9', 193 | '#ededed', 194 | '#e1e1e1', 195 | '#d3d3d3', 196 | '#c4c4c4', 197 | '#b3b3b3', 198 | '#a0a0a0', 199 | '#898989', 200 | '#6c6c6c', 201 | '#3f3f3f', 202 | ], 203 | } 204 | -------------------------------------------------------------------------------- /www/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Helmet } from 'react-helmet' 3 | import palx from '../../../palx/src' 4 | import { Global } from '@emotion/core' 5 | import { ThemeProvider } from 'theme-ui' 6 | import { Link as GatsbyLink } from 'gatsby' 7 | import { 8 | Box, 9 | Grid, 10 | Flex, 11 | Label, 12 | Input, 13 | Heading, 14 | Button, 15 | Text, 16 | Link, 17 | } from '@theme-ui/components' 18 | 19 | const theme = { 20 | colors: { 21 | text: '#000', 22 | background: '#fff', 23 | primary: '#07c', 24 | gray: '#aaa', 25 | modes: { 26 | dark: { 27 | text: '#fff', 28 | background: '#000', 29 | primary: '#0cf', 30 | gray: '#555', 31 | }, 32 | }, 33 | }, 34 | fonts: { 35 | body: 'system-ui, sans-serif', 36 | heading: 'inherit', 37 | }, 38 | text: { 39 | heading: { 40 | textTransform: 'uppercase', 41 | letterSpacing: '0.1em', 42 | } 43 | }, 44 | buttons: { 45 | primary: { 46 | color: 'background', 47 | bg: 'text', 48 | } 49 | }, 50 | forms: { 51 | input: { 52 | borderColor: 'gray', 53 | } 54 | } 55 | } 56 | 57 | export default props => { 58 | const [color, setColor] = React.useState('#07c') 59 | const [colors, setColors] = React.useState(() => palx('#07c')) 60 | 61 | return ( 62 | 63 | 73 | 80 | 81 | 85 | Palx: Automatic UI Color Palette Generator 86 | 87 |

88 | Provide a single color value and Palx returns a full-spectrum color palette, well suited for UI design and data visualizations that work harmoniously with brand colors. 89 |

90 |
91 | { 95 | e.preventDefault() 96 | try { 97 | const colors = palx(color) 98 | setColors(colors) 99 | } catch (e) {} 100 | }}> 101 | 104 | 105 | 113 | { 118 | setColor(e.target.value) 119 | }} 120 | mr={3} 121 | /> 122 | 128 | 129 | 130 | 131 | {Object.keys(colors).map((name, i) => { 132 | const value = colors[name] 133 | if (!Array.isArray(value)) return false 134 | return ( 135 | 136 | 141 | {name} 142 | 143 | 144 | {value.map((val, i) => ( 145 | 146 | 152 | 156 | 160 | {name} {i} 161 | 162 | 163 | 164 | {val} 165 | 166 | 167 | 168 | ))} 169 | 170 | 171 | ) 172 | })} 173 | 174 | 175 | 180 | Download 181 | 182 | 189 | 195 | 196 | 197 | 198 | GitHub 199 | 200 | 201 | Made by Jxnblk 202 | 203 | 204 |
205 |
206 | ) 207 | } 208 | --------------------------------------------------------------------------------