├── README.md ├── index.js ├── package-lock.json └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Tailwind CSS Swatches 2 | 3 | Generate a macOS color palette from your Tailwind CSS config. 4 | 5 | Screenshot of a generated color palette 6 | 7 | The file will be created in `~/Libary/Colors` where the system-native color picker will pick it up automatically. (Note: you may need to restart your application for the generated palette to show up.) 8 | 9 | ## Installation 10 | 11 | ```bash 12 | $ npm install -D tailwind-swatches 13 | ``` 14 | 15 | ## Requirements 16 | 17 | - macOS 18 | - Node 8+ 19 | - Xcode Command Line Tools [^1] 20 | - a working Tailwind CSS project set-up 21 | 22 | ## Usage 23 | 24 | ```bash 25 | $ npx tailwind-swatches --help 26 | Options: 27 | --help Show help [boolean] 28 | --version Show version number [boolean] 29 | -n, --name Name of the generated color palette 30 | [string] [default: "Tailwind CSS"] 31 | -c, --config Path to the Tailwind CSS config[default: "./tailwind.config.js"] 32 | ``` 33 | 34 | Example: 35 | 36 | ```bash 37 | $ npx tailwind-swatches -c /path/to/tailwind.config.js -n "My Project" 38 | ``` 39 | 40 | [^1]: We create and execute a temporary Swift file that generates the actual color palette file. 41 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const { exec } = require('child_process'); 5 | const path = require('path'); 6 | 7 | const chalk = require('chalk'); 8 | const parseColor = require('color-parser'); 9 | const tempy = require('tempy'); 10 | const yargs = require('yargs'); 11 | const warning = chalk.keyword('orange'); 12 | 13 | const resolveConfig = require('tailwindcss/resolveConfig'); 14 | 15 | // Try to get the project name from package.json 16 | let package, projectName; 17 | try { 18 | package = require(path.resolve('package.json')); 19 | projectName = package.name; 20 | } catch (e) { 21 | projectName = 'Tailwind CSS'; 22 | } 23 | 24 | // Command line arguments 25 | const { name, config } = yargs 26 | .option('n', { 27 | alias: 'name', 28 | default: projectName, 29 | describe: 'Name of the generated color palette', 30 | type: 'string', 31 | }) 32 | .option('c', { 33 | alias: 'config', 34 | default: './tailwind.config.js', 35 | describe: 'Path to the Tailwind CSS config', 36 | }).argv; 37 | 38 | // Read Tailwind config 39 | let customConfig; 40 | try { 41 | customConfig = require(path.resolve(config)); 42 | } catch (e) { 43 | console.error(`Unable to find config file: ${config}`); 44 | process.exit(1); 45 | } 46 | const { 47 | theme: { colors }, 48 | } = resolveConfig(customConfig); 49 | 50 | // Generate color swatches 51 | const swatches = []; 52 | 53 | function upperCaseFirst(s) { 54 | return s.charAt(0).toUpperCase() + s.slice(1); 55 | } 56 | 57 | function createSwatch(name, color) { 58 | if (/^#(?:[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/.test(color)) { 59 | console.log( 60 | `Processing: ${name} :` + ` ${chalk.hex(color)(color)}` + ` ${chalk.bgHex(color)(color)}` 61 | ); 62 | const {r, g, b, a} = parseColor(color); 63 | return `list.setColor(NSColor.init(red: ${r / 255}, green:${g / 64 | 255}, blue:${b / 255}, alpha:${a}), forKey: "${upperCaseFirst(name)}")`; 65 | } else 66 | { 67 | console.log(warning(`Skipping: ${name} : ${color} - not a valid color!`)); 68 | } 69 | } 70 | 71 | for (const [key, value] of Object.entries(colors)) { 72 | if (typeof value === 'object') { 73 | for (const [subkey, subvalue] of Object.entries(value)) { 74 | swatches.push(createSwatch(`${key} ${subkey}`, subvalue)); 75 | } 76 | } else { 77 | swatches.push(createSwatch(key, value)); 78 | } 79 | } 80 | 81 | // Generate Swift code that creates the color palette file 82 | const tempFileName = tempy.file({ extension: 'swift' }); 83 | const tempFile = fs.openSync(tempFileName, 'w'); 84 | const swiftCode = `import Cocoa 85 | var list = NSColorList.init(name: "${name}") 86 | ${swatches.join('\n')} 87 | try! list.write(to: nil) // 'nil' means the file will be written to ~/Library/Colors (see https://developer.apple.com/documentation/appkit/nscolorlist/2269695-write) 88 | `; 89 | fs.writeSync(tempFile, swiftCode); 90 | fs.closeSync(tempFile); 91 | 92 | // Execute the generated Swift code 93 | exec(`/usr/bin/env swift ${tempFileName}`, function(err) { 94 | if (err) { 95 | if (err.code === 127) { 96 | console.log( 97 | chalk.red( 98 | 'Unable to find Swift interpreter. You may need to install Xcode command line tools.' 99 | ) 100 | ); 101 | process.exit(2); 102 | } 103 | console.error(chalk.red(err)); 104 | process.exit(3); 105 | } 106 | console.log(chalk.green(`Color palette '${name}' has been generated.`)); 107 | console.log( 108 | 'Please find it under the third tab in your macOS color picker.\nNOTE: You may need to restart your application for the palette to show up.' 109 | ); 110 | }); 111 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwind-swatches", 3 | "version": "0.1.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-regex": { 8 | "version": "4.1.0", 9 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 10 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" 11 | }, 12 | "ansi-styles": { 13 | "version": "3.2.1", 14 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 15 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 16 | "requires": { 17 | "color-convert": "^1.9.0" 18 | } 19 | }, 20 | "camelcase": { 21 | "version": "5.3.1", 22 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 23 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" 24 | }, 25 | "chalk": { 26 | "version": "2.4.2", 27 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 28 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 29 | "requires": { 30 | "ansi-styles": "^3.2.1", 31 | "escape-string-regexp": "^1.0.5", 32 | "supports-color": "^5.3.0" 33 | } 34 | }, 35 | "cliui": { 36 | "version": "5.0.0", 37 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 38 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 39 | "requires": { 40 | "string-width": "^3.1.0", 41 | "strip-ansi": "^5.2.0", 42 | "wrap-ansi": "^5.1.0" 43 | } 44 | }, 45 | "color-convert": { 46 | "version": "1.9.3", 47 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 48 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 49 | "requires": { 50 | "color-name": "1.1.3" 51 | } 52 | }, 53 | "color-name": { 54 | "version": "1.1.3", 55 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 56 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 57 | }, 58 | "color-parser": { 59 | "version": "0.1.0", 60 | "resolved": "https://registry.npmjs.org/color-parser/-/color-parser-0.1.0.tgz", 61 | "integrity": "sha1-jhfJPKAsx7TZmNmyDMawyOHOGco=" 62 | }, 63 | "crypto-random-string": { 64 | "version": "1.0.0", 65 | "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", 66 | "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" 67 | }, 68 | "decamelize": { 69 | "version": "1.2.0", 70 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 71 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 72 | }, 73 | "emoji-regex": { 74 | "version": "7.0.3", 75 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 76 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" 77 | }, 78 | "escape-string-regexp": { 79 | "version": "1.0.5", 80 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 81 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 82 | }, 83 | "find-up": { 84 | "version": "3.0.0", 85 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 86 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 87 | "requires": { 88 | "locate-path": "^3.0.0" 89 | } 90 | }, 91 | "get-caller-file": { 92 | "version": "2.0.5", 93 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 94 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 95 | }, 96 | "has-flag": { 97 | "version": "3.0.0", 98 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 99 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 100 | }, 101 | "is-fullwidth-code-point": { 102 | "version": "2.0.0", 103 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 104 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 105 | }, 106 | "locate-path": { 107 | "version": "3.0.0", 108 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 109 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 110 | "requires": { 111 | "p-locate": "^3.0.0", 112 | "path-exists": "^3.0.0" 113 | } 114 | }, 115 | "p-limit": { 116 | "version": "2.2.0", 117 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", 118 | "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", 119 | "requires": { 120 | "p-try": "^2.0.0" 121 | } 122 | }, 123 | "p-locate": { 124 | "version": "3.0.0", 125 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 126 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 127 | "requires": { 128 | "p-limit": "^2.0.0" 129 | } 130 | }, 131 | "p-try": { 132 | "version": "2.2.0", 133 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 134 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 135 | }, 136 | "path-exists": { 137 | "version": "3.0.0", 138 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 139 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" 140 | }, 141 | "require-directory": { 142 | "version": "2.1.1", 143 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 144 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 145 | }, 146 | "require-main-filename": { 147 | "version": "2.0.0", 148 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 149 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" 150 | }, 151 | "set-blocking": { 152 | "version": "2.0.0", 153 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 154 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 155 | }, 156 | "string-width": { 157 | "version": "3.1.0", 158 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 159 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 160 | "requires": { 161 | "emoji-regex": "^7.0.1", 162 | "is-fullwidth-code-point": "^2.0.0", 163 | "strip-ansi": "^5.1.0" 164 | } 165 | }, 166 | "strip-ansi": { 167 | "version": "5.2.0", 168 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 169 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 170 | "requires": { 171 | "ansi-regex": "^4.1.0" 172 | } 173 | }, 174 | "supports-color": { 175 | "version": "5.5.0", 176 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 177 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 178 | "requires": { 179 | "has-flag": "^3.0.0" 180 | } 181 | }, 182 | "temp-dir": { 183 | "version": "1.0.0", 184 | "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", 185 | "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" 186 | }, 187 | "tempy": { 188 | "version": "0.3.0", 189 | "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", 190 | "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", 191 | "requires": { 192 | "temp-dir": "^1.0.0", 193 | "type-fest": "^0.3.1", 194 | "unique-string": "^1.0.0" 195 | } 196 | }, 197 | "type-fest": { 198 | "version": "0.3.1", 199 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", 200 | "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" 201 | }, 202 | "unique-string": { 203 | "version": "1.0.0", 204 | "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", 205 | "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", 206 | "requires": { 207 | "crypto-random-string": "^1.0.0" 208 | } 209 | }, 210 | "which-module": { 211 | "version": "2.0.0", 212 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 213 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 214 | }, 215 | "wrap-ansi": { 216 | "version": "5.1.0", 217 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 218 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 219 | "requires": { 220 | "ansi-styles": "^3.2.0", 221 | "string-width": "^3.0.0", 222 | "strip-ansi": "^5.0.0" 223 | } 224 | }, 225 | "y18n": { 226 | "version": "4.0.1", 227 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", 228 | "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" 229 | }, 230 | "yargs": { 231 | "version": "13.3.0", 232 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", 233 | "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", 234 | "requires": { 235 | "cliui": "^5.0.0", 236 | "find-up": "^3.0.0", 237 | "get-caller-file": "^2.0.1", 238 | "require-directory": "^2.1.1", 239 | "require-main-filename": "^2.0.0", 240 | "set-blocking": "^2.0.0", 241 | "string-width": "^3.0.0", 242 | "which-module": "^2.0.0", 243 | "y18n": "^4.0.0", 244 | "yargs-parser": "^13.1.1" 245 | } 246 | }, 247 | "yargs-parser": { 248 | "version": "13.1.2", 249 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", 250 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", 251 | "requires": { 252 | "camelcase": "^5.0.0", 253 | "decamelize": "^1.2.0" 254 | } 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwind-swatches", 3 | "version": "0.1.3", 4 | "description": "Create (macOS) color swatches from Tailwind CSS config", 5 | "keywords": [ 6 | "tailwindcss", 7 | "tailwind", 8 | "colors", 9 | "color palette", 10 | "macos", 11 | "clr" 12 | ], 13 | "main": "index.js", 14 | "bin": { 15 | "tailwind-swatches": "./index.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/philippbosch/tailwind-swatches.git" 20 | }, 21 | "author": "Philipp Bosch ", 22 | "license": "MIT", 23 | "homepage": "https://github.com/philippbosch/tailwind-swatches#readme", 24 | "dependencies": { 25 | "chalk": "2.4.2", 26 | "color-parser": "0.1.0", 27 | "tempy": "0.3.0", 28 | "yargs": "13.3.0" 29 | } 30 | } 31 | --------------------------------------------------------------------------------