diff --git a/__tests__/mergeConfig.test.js b/__tests__/mergeConfig.test.js new file mode 100644 index 000000000..7d62b62c8 --- /dev/null +++ b/__tests__/mergeConfig.test.js @@ -0,0 +1,86 @@ +import mergeConfig from '../src/util/mergeConfig' + +/** + * Tests + */ +it('replaces simple top level keys', () => { + const defaultConfig = { + colors: { + 'red': '#f25451', + 'green-light': '#b1f3be', + 'blue-dark': '#3687c8', + 'indigo': '#6574cd', + } + } + const userConfig = { + colors: { + 'orange': '#ffb82b', + 'green': '#57d06f', + 'blue': '#4aa2ea', + 'indigo-dark': '#4957a5', + } + } + expect(mergeConfig(defaultConfig, userConfig)).toMatchObject(userConfig) +}) + +it('merges keys found in the "extend" section', () => { + const defaultConfig = { + colors: { + 'red': '#f25451', + }, + text: { + sizes: { + 'base': '1rem', + 'lg': '1.25rem', + } + }, + spacing: { + common: { + '0': '0', + '1': '0.25rem', + '2': '0.5rem', + }, + padding: {} + } + } + const userConfig = { + extend: { + colors: { + 'blue': '#4aa2ea', + }, + text: { + sizes: { + 'xl': '1.5rem' + } + }, + spacing: { + padding: { + '10': '2.5rem' + } + } + } + } + expect(mergeConfig(defaultConfig, userConfig)).toMatchObject({ + colors: { + 'red': '#f25451', + 'blue': '#4aa2ea', + }, + text: { + sizes: { + 'base': '1rem', + 'lg': '1.25rem', + 'xl': '1.5rem', + } + }, + spacing: { + common: { + '0': '0', + '1': '0.25rem', + '2': '0.5rem', + }, + padding: { + '10': '2.5rem', + } + } + }) +}) diff --git a/dist/tailwind.css b/dist/tailwind.css index ba626c40f..9c3b0d75f 100644 --- a/dist/tailwind.css +++ b/dist/tailwind.css @@ -12369,4 +12369,4 @@ body { } } -/*# sourceMappingURL=tailwind.css.map */ +/*# sourceMappingURL=tailwind.css.map */ \ No newline at end of file diff --git a/docs/mix-manifest.json b/docs/mix-manifest.json index 367f5ef1e..9e26dfeeb 100644 --- a/docs/mix-manifest.json +++ b/docs/mix-manifest.json @@ -1,3 +1 @@ -{ - "/source/css/main.css": "/source/css/main.css" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/docs/tailwind-config.js b/docs/tailwind-config.js deleted file mode 100644 index f28c25905..000000000 --- a/docs/tailwind-config.js +++ /dev/null @@ -1,334 +0,0 @@ -module.exports = { - breakpoints: { - sm: '576px', - md: '768px', - lg: '992px', - xl: '1200px', - }, - colors: { - 'black': '#000000', - 'grey-900': '#212b35', - 'grey-800': '#404e5c', - 'grey-700': '#647382', - 'grey-600': '#919eab', - 'grey-500': '#c5ced6', - 'grey-400': '#dfe3e8', - 'grey-300': '#f0f2f5', - 'grey-200': '#f7f9fa', - 'white': '#ffffff', - 'red-dark': '#d43633', - 'red': '#f25451', - 'red-light': '#fa8785', - 'red-lightest': '#fff1f0', - 'orange-dark': '#f29500', - 'orange': '#ffb82b', - 'orange-light': '#ffd685', - 'orange-lightest': '#fff8eb', - 'yellow-dark': '#ffc400', - 'yellow': '#ffe14a', - 'yellow-light': '#ffea83', - 'yellow-lightest': '#fffbe5', - 'green-dark': '#34ae4c', - 'green': '#57d06f', - 'green-light': '#b1f3be', - 'green-lightest': '#eefff1', - 'teal-dark': '#249e9a', - 'teal': '#4dc0b5', - 'teal-light': '#9eebe4', - 'teal-lightest': '#eefffd', - 'blue-dark': '#3687c8', - 'blue': '#4aa2ea', - 'blue-light': '#acdaff', - 'blue-lightest': '#f1f9ff', - 'indigo-dark': '#4957a5', - 'indigo': '#6574cd', - 'indigo-light': '#bcc5fb', - 'indigo-lightest': '#f4f5ff', - 'purple-dark': '#714cb4', - 'purple': '#976ae6', - 'purple-light': '#ceb3ff', - 'purple-lightest': '#f7f3ff', - 'pink-dark': '#d84f7d', - 'pink': '#f66d9b', - 'pink-light': '#ffa5c3', - 'pink-lightest': '#fdf2f5', - }, - text: { - fonts: { - 'sans': 'system-ui', - 'serif': 'Constantia, "Lucida Bright", Lucidabright, "Lucida Serif", Lucida, "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif', - 'mono': 'Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace', - }, - sizes: { - 'xs': '.75rem', // 12px - 'sm': '.875rem', // 14px - 'base': '1rem', // 16px - 'lg': '1.125rem', // 18px - 'xl': '1.25rem', // 20px - '2xl': '1.75rem', // 28px - '3xl': '2.375rem', // 38px - '4xl': '2.875rem', // 46px - }, - weights: { - 'hairline': 200, - 'thin': 300, - 'regular': 400, - 'medium': 600, - 'bold': 700, - }, - leading: { - 'none': 1, - 'tight': 1.25, - 'normal': 1.5, - 'loose': 2, - }, - tracking: { - 'tight': '-0.05em', - 'normal': '0', - 'wide': '0.1em', - }, - colors: [ - { - 'light': 'white', - 'light-soft': 'rgba(255, 255, 255, 60%)', - 'light-softer': 'rgba(255, 255, 255, 45%)', - 'light-softest': 'rgba(255, 255, 255, 35%)', - 'dark': 'grey-900', - 'dark-soft': 'grey-700', - 'dark-softer': 'grey-600', - 'dark-softest': 'grey-500', - }, - 'red-dark', - 'red', - 'red-light', - 'red-lightest', - 'orange-dark', - 'orange', - 'orange-light', - 'orange-lightest', - 'yellow-dark', - 'yellow', - 'yellow-light', - 'yellow-lightest', - 'green-dark', - 'green', - 'green-light', - 'green-lightest', - 'teal-dark', - 'teal', - 'teal-light', - 'teal-lightest', - 'blue-dark', - 'blue', - 'blue-light', - 'blue-lightest', - 'indigo-dark', - 'indigo', - 'indigo-light', - 'indigo-lightest', - 'purple-dark', - 'purple', - 'purple-light', - 'purple-lightest', - 'pink-dark', - 'pink', - 'pink-light', - 'pink-lightest', - ] - }, - backgrounds: { - colors: [ - { - 'light': 'white', - 'light-soft': 'grey-200', - 'light-softer': 'grey-300', - 'light-softest': 'grey-400', - 'dark': 'grey-900', - 'dark-soft': 'grey-800', - 'dark-softer': 'grey-700', - 'dark-softest': 'grey-600', - }, - 'red-dark', - 'red', - 'red-light', - 'red-lightest', - 'orange-dark', - 'orange', - 'orange-light', - 'orange-lightest', - 'yellow-dark', - 'yellow', - 'yellow-light', - 'yellow-lightest', - 'green-dark', - 'green', - 'green-light', - 'green-lightest', - 'teal-dark', - 'teal', - 'teal-light', - 'teal-lightest', - 'blue-dark', - 'blue', - 'blue-light', - 'blue-lightest', - 'indigo-dark', - 'indigo', - 'indigo-light', - 'indigo-lightest', - 'purple-dark', - 'purple', - 'purple-light', - 'purple-lightest', - 'pink-dark', - 'pink', - 'pink-light', - 'pink-lightest', - ], - }, - borders: { - defaults: { - width: '1px', - color: 'grey-500', - }, - widths: { - '0': '0', - '2': '2px', - '4': '4px', - '8': '8px', - }, - rounded: { - default: '.25rem', - modifiers: { - sm: '.125rem', - lg: '.5rem', - pill: '9999px', - } - }, - colors: [ - { - 'light': 'white', - 'light-soft': 'grey-200', - 'light-softer': 'grey-300', - 'dark': 'grey-600', - 'dark-soft': 'grey-500', - 'dark-softer': 'grey-400', - 'light-overlay': 'hsla(0, 0%, 0%, 20%)', - 'light-overlay-soft': 'hsla(0, 0%, 0%, 10%)', - 'light-overlay-softer': 'hsla(0, 0%, 0%, 5%)', - 'dark-overlay': 'hsla(0, 0%, 100%, 100%)', - 'dark-overlay-soft': 'hsla(0, 0%, 100%, 60%)', - 'dark-overlay-softer': 'hsla(0, 0%, 100%, 35%)', - }, - 'red-dark', - 'red', - 'red-light', - 'red-lightest', - 'orange-dark', - 'orange', - 'orange-light', - 'orange-lightest', - 'yellow-dark', - 'yellow', - 'yellow-light', - 'yellow-lightest', - 'green-dark', - 'green', - 'green-light', - 'green-lightest', - 'teal-dark', - 'teal', - 'teal-light', - 'teal-lightest', - 'blue-dark', - 'blue', - 'blue-light', - 'blue-lightest', - 'indigo-dark', - 'indigo', - 'indigo-light', - 'indigo-lightest', - 'purple-dark', - 'purple', - 'purple-light', - 'purple-lightest', - 'pink-dark', - 'pink', - 'pink-light', - 'pink-lightest', - ] - }, - sizing: { - common: { - '1': '0.25rem', - '2': '0.5rem', - '3': '0.75rem', - '4': '1rem', - '6': '1.5rem', - '8': '2rem', - '10': '2.5rem', - '12': '3rem', - '16': '4rem', - '24': '6rem', - '32': '8rem', - '40': '10rem', - '48': '12rem', - '64': '16rem', - '96': '24rem', - '128': '32rem', - 'full': '100%', - }, - width: { - '1/2': '50%', - '1/3': 'calc(100% / 3)', - '2/3': 'calc(100% / 3 * 2)', - '1/4': '25%', - '3/4': '75%', - '1/5': '20%', - '2/5': '40%', - '3/5': '60%', - '4/5': '80%', - '1/6': 'calc(100% / 6)', - '5/6': 'calc(100% / 6 * 5)', - 'screen': '100vw' - }, - height: { - 'screen': '100vh' - } - }, - spacing: { - common: { - 'px': '1px', - '0': '0', - '1': '0.25rem', - '2': '0.5rem', - '3': '0.75rem', - '4': '1rem', - '6': '1.5rem', - '8': '2rem', - '12': '3rem', - '16': '4rem', - }, - padding: {}, - margin: {}, - pull: {}, - }, - constrain: { - 'xs': '20rem', - 'sm': '30rem', - 'md': '40rem', - 'lg': '50rem', - 'xl': '60rem', - '2xl': '70rem', - '3xl': '80rem', - '4xl': '90rem', - '5xl': '100rem', - }, - shadows: { - '1': '0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.15)', - '2': '0 3px 6px rgba(0,0,0,0.12), 0 3px 6px rgba(0,0,0,0.13)', - '3': '0 10px 20px rgba(0,0,0,0.13), 0 6px 6px rgba(0,0,0,0.13)', - '4': '0 14px 28px rgba(0,0,0,0.16), 0 10px 10px rgba(0,0,0,0.11)', - '5': '0 19px 38px rgba(0,0,0,0.18), 0 15px 12px rgba(0,0,0,0.11)', - }, -} diff --git a/docs/tailwind.json b/docs/tailwind.json new file mode 100644 index 000000000..5a886666e --- /dev/null +++ b/docs/tailwind.json @@ -0,0 +1,10 @@ +{ + "extend": { + "spacing": { + "common": { + "12": "3rem", + "16": "4rem" + } + } + } +} diff --git a/docs/webpack.mix.js b/docs/webpack.mix.js index ed6d45737..ab5dc3642 100644 --- a/docs/webpack.mix.js +++ b/docs/webpack.mix.js @@ -1,5 +1,5 @@ const mix = require('laravel-mix'); -const tailwind = require('./../lib/tailwind.js').default; +const tailwind = require('./../lib/index.js'); /* |-------------------------------------------------------------------------- @@ -15,7 +15,7 @@ const tailwind = require('./../lib/tailwind.js').default; mix.less('source/_assets/less/main.less', 'source/css') .options({ postCss: [ - tailwind(require('./tailwind-config')), + tailwind(require('./tailwind.json')), ] }) diff --git a/src/index.js b/src/index.js index a8f3dbbf1..828230129 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ import cssnext from 'postcss-cssnext' import stylefmt from 'stylefmt' import defaultConfig from './defaultConfig' +import mergeConfig from './util/mergeConfig' import addCustomMediaQueries from './lib/addCustomMediaQueries' import generateUtilities from './lib/generateUtilities' @@ -12,15 +13,15 @@ import substituteHoverableAtRules from './lib/substituteHoverableAtRules' import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules' import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules' -const plugin = postcss.plugin('tailwind', options => { - options = options || defaultConfig +const plugin = postcss.plugin('tailwind', (options = {}) => { + const config = mergeConfig(defaultConfig, options) return postcss([ - addCustomMediaQueries(options), - generateUtilities(options), - substituteHoverableAtRules(options), - substituteResponsiveAtRules(options), - substituteClassApplyAtRules(options), + addCustomMediaQueries(config), + generateUtilities(config), + substituteHoverableAtRules(config), + substituteResponsiveAtRules(config), + substituteClassApplyAtRules(config), cssnext(), stylefmt, ]) diff --git a/src/util/mergeConfig.js b/src/util/mergeConfig.js new file mode 100644 index 000000000..34ea914ec --- /dev/null +++ b/src/util/mergeConfig.js @@ -0,0 +1,67 @@ +import _ from 'lodash' + +const configTemplate = { + breakpoints: null, + colors: null, + text: { + fonts: null, + sizes: null, + weights: null, + leading: null, + tracking: null, + colors: null + }, + backgrounds: { + colors: null, + }, + borders: { + defaults: null, + widths: null, + rounded: { + default: null, + modifiers: null + }, + colors: null + }, + sizing: { + common: null, + width: null, + height: null + }, + spacing: { + common: null, + padding: null, + margin: null, + pull: null + }, + constrain: null, + shadows: null, + zIndex: null +} + +function replaceDefaults(template, defaults, replacements) { + return Object.keys(template).reduce((merged, key) => { + const value = template[key] + + if (_.isPlainObject(value)) { + merged[key] = replaceDefaults(value, _.get(defaults, key), _.get(replacements, key)) + } else { + merged[key] = _.get(replacements, key, _.get(defaults, key)) + } + + return merged + }, {}) +} + +function appendConfig(base, appends) { + return _.mergeWith({}, base, appends, (baseValue, appendsValue) => { + if (_.isArray(baseValue)) { + return baseValue.concat(appendsValue); + } + }) +} + +export default function mergeConfig(base, other) { + const replaced = replaceDefaults(configTemplate, base, other) + return appendConfig(replaced, _.get(other, 'extend', {})) +}