diff --git a/__tests__/applyAtRule.test.js b/__tests__/applyAtRule.test.js index 06d2a4416..8ad5af4dc 100644 --- a/__tests__/applyAtRule.test.js +++ b/__tests__/applyAtRule.test.js @@ -14,11 +14,36 @@ test("it copies a class's declarations into itself", () => { }) }) -test('it removes important from applied classes', () => { - const output = '.a { color: red !important; } .b { color: red; }' +test('it removes important from applied classes by default', () => { + const input = ` + .a { color: red !important; } + .b { @apply .a; } + ` - return run('.a { color: red !important; } .b { @apply .a; }').then(result => { - expect(result.css).toEqual(output) + const expected = ` + .a { color: red !important; } + .b { color: red; } + ` + + return run(input).then(result => { + expect(result.css).toEqual(expected) + expect(result.warnings().length).toBe(0) + }) +}) + +test('applied rules can be made !important', () => { + const input = ` + .a { color: red; } + .b { @apply .a !important; } + ` + + const expected = ` + .a { color: red; } + .b { color: red !important; } + ` + + return run(input).then(result => { + expect(result.css).toEqual(expected) expect(result.warnings().length).toBe(0) }) }) diff --git a/docs/source/docs/functions-and-directives.blade.md b/docs/source/docs/functions-and-directives.blade.md index 461331350..e317f388c 100644 --- a/docs/source/docs/functions-and-directives.blade.md +++ b/docs/source/docs/functions-and-directives.blade.md @@ -54,7 +54,69 @@ This is extremely useful when you find a common utility pattern in your HTML tha } ``` -Note that `@@apply` **will not work** for mixing in hover or responsive variants of another utility. Instead, mix in the plain version of that utility into the `:hover` pseudo-selector or a new media query: +Rules can listed on a single line or with multiple calls to `@@apply`: + +```less +.btn { + @@apply .font-bold; + @@apply .py-2; + @@apply .px-4; + @@apply .rounded; +} +``` + +You can mix `@@apply` declarations with normal CSS declarations too of course: + +```less +.btn:hover { + @@apply .bg-blue-dark; + transform: translateY(-1px); +} +``` + +Any rules mixed in with `@@apply` will have `!important` **removed** by default to avoid specificity issues: + +```less +// Input +.foo { + @@apply .bar; +} + +.bar { + color: blue !important; +} + +// Output +.foo { + color: blue; +} + +.bar { + color: blue !important; +} +``` + +If you'd like to `@@apply` an existing class and make it `!important`, simply add `!important` to the end of the declaration: + + +```less +// Input +.btn { + @@apply .font-bold .py-2 .px-4 .rounded !important; +} + +// Output +.btn { + font-weight: 700 !important; + padding-top: .5rem !important; + padding-bottom: .5rem !important; + padding-right: 1rem !important; + padding-left: 1rem !important; + border-radius: .25rem !important; +} +``` + +Note that `@@apply` **will not work** for mixing in hover, focus, or responsive variants of another utility. Instead, mix in the plain version of that utility into the appropriate pseudo-selector or a new media query: ```less // Won't work: diff --git a/src/lib/substituteClassApplyAtRules.js b/src/lib/substituteClassApplyAtRules.js index 48935c9ef..35a5b4bf0 100644 --- a/src/lib/substituteClassApplyAtRules.js +++ b/src/lib/substituteClassApplyAtRules.js @@ -2,10 +2,8 @@ import _ from 'lodash' import postcss from 'postcss' import escapeClassName from '../util/escapeClassName' -function normalizeClassNames(classNames) { - return classNames.map(className => { - return `.${escapeClassName(_.trimStart(className, '.'))}` - }) +function normalizeClassName(className) { + return `.${escapeClassName(_.trimStart(className, '.'))}` } function findMixin(css, mixin, onError) { @@ -52,14 +50,17 @@ export default function() { return _.startsWith(mixin, '--') }) - const decls = _.flatMap(normalizeClassNames(classes), mixin => { - return findMixin(css, mixin, message => { - throw atRule.error(message) + const decls = _(classes) + .reject(mixin => mixin === '!important') + .flatMap(mixin => { + return findMixin(css, normalizeClassName(mixin), message => { + throw atRule.error(message) + }) }) - }) + .value() - decls.forEach(decl => { - decl.important = false + _.tap(_.last(mixins) === '!important', important => { + decls.forEach(decl => (decl.important = important)) }) atRule.before(decls)