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