├── 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
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 | 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 |