Merge branch 'master' into patch-2
This commit is contained in:
commit
6070ba72dc
93 changed files with 223000 additions and 2387 deletions
|
|
@ -9,7 +9,8 @@
|
|||
"extends": ["eslint-config-postcss", "prettier"],
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"no-unused-vars": [2, {"args": "all", "argsIgnorePattern": "^_"}],
|
||||
"no-unused-vars": [2, { "args": "all", "argsIgnorePattern": "^_" }],
|
||||
"no-warning-comments": 0,
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
|
|
|
|||
12
.github/CONTRIBUTING.md
vendored
12
.github/CONTRIBUTING.md
vendored
|
|
@ -19,7 +19,7 @@ npm run style
|
|||
To automatically fix any style violations in your code, you can run:
|
||||
|
||||
```sh
|
||||
npm run style --fix
|
||||
npm run style -- --fix
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
|
@ -43,19 +43,19 @@ npm run prepare
|
|||
This will create new CSS files in the `/dist` folder which you can reference in your own test HTML file. We often test our own changes by creating an `index.html` file in the root of the Tailwind project itself that pulls in the `/dist/tailwind.css` stylesheet:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="/dist/tailwind.css">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<link rel="stylesheet" href="/dist/tailwind.css" />
|
||||
<title>Hello, world!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="text-2xl font-bold text-center">Hello world!</h1>
|
||||
</body>
|
||||
</html>
|
||||
````
|
||||
```
|
||||
|
||||
You can then use a tool like [live-server](https://www.npmjs.com/package/live-server) to preview it in the browser:
|
||||
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
👋 Hey, thanks for taking an interest in Tailwind!
|
||||
|
||||
Please only open an issue here if you have a bug to report or a feature proposal you'd like to discuss.
|
||||
We don't accept issues that don't use our issue template.
|
||||
|
||||
If you need help, have questions about best practices, or want to start a discussion about anything else related to Tailwind, open an issue on the `tailwindcss/discuss` repo instead:
|
||||
Please use this link to create your issue:
|
||||
|
||||
https://github.com/tailwindcss/discuss/issues
|
||||
https://github.com/tailwindcss/tailwindcss/issues/new/choose
|
||||
|
||||
Issues that don't follow our issue template will be closed, as it's just impossible to triage and resolve issues without some sort of system with the small team we have.
|
||||
|
||||
-->
|
||||
|
|
|
|||
25
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
name: 'Bug Report'
|
||||
about: 'Report a reproducible bug in the framework.'
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
### Describe the problem:
|
||||
|
||||
<!--
|
||||
|
||||
Explain the behavior you're seeing that you think is a bug, and explain how you
|
||||
think things should behave instead.
|
||||
|
||||
-->
|
||||
|
||||
### Link to a minimal reproduction:
|
||||
|
||||
<!--
|
||||
|
||||
Please provide a link to a GitHub repository that reliably reproduces the issue
|
||||
with the least amount of extraneous code possible.
|
||||
|
||||
-->
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Request
|
||||
url: https://github.com/tailwindcss/tailwindcss/discussions/new?category_id=65335
|
||||
about: 'Suggest any ideas you have using our discussion forums.'
|
||||
- name: Help
|
||||
url: https://github.com/tailwindcss/tailwindcss/discussions/new?category_id=65334
|
||||
about: 'If you have a question or need help, ask a question on the discussion forums.'
|
||||
- name: Documentation Issue
|
||||
url: https://github.com/tailwindcss/docs
|
||||
about: 'For documentation issues, suggest changes on our documentation repository.'
|
||||
27
.github/workflows/nodejs.yml
vendored
27
.github/workflows/nodejs.yml
vendored
|
|
@ -5,27 +5,26 @@ name: Node.js CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x]
|
||||
node-version: [8.x, 10.x, 12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm run prepare
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm run prepare
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
/__tests__/
|
||||
/jest/
|
||||
/src/
|
||||
index.html
|
||||
yarn-error.log
|
||||
10
README.md
10
README.md
|
|
@ -1,9 +1,11 @@
|
|||
<p align="center">
|
||||
<a href="https://tailwindcss.com/" target="_blank"><img width="200" src="https://tailwindcss.com/img/tailwind.svg"></a><br>
|
||||
<p>
|
||||
<a href="https://tailwindcss.com/" target="_blank">
|
||||
<img alt="Tailwind CSS" width="350" src="https://refactoringui.nyc3.cdn.digitaloceanspaces.com/tailwind-logo.svg">
|
||||
</a><br>
|
||||
A utility-first CSS framework for rapidly building custom user interfaces.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<p>
|
||||
<a href="https://travis-ci.org/tailwindcss/tailwindcss"><img src="https://img.shields.io/travis/tailwindcss/tailwindcss/master.svg" alt="Build Status"></a>
|
||||
<a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a>
|
||||
<a href="https://github.com/tailwindcss/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a>
|
||||
|
|
@ -20,7 +22,7 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com/).
|
|||
|
||||
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
|
||||
|
||||
[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/discuss/issues?q=is%3Aissue+sort%3Aupdated-desc)
|
||||
[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions)
|
||||
|
||||
For casual chit-chat with others using the framework:
|
||||
|
||||
|
|
|
|||
|
|
@ -43,12 +43,20 @@ describe('cli', () => {
|
|||
})
|
||||
|
||||
describe('build', () => {
|
||||
it('compiles CSS file', () => {
|
||||
it('compiles CSS file using an input css file', () => {
|
||||
return cli(['build', inputCssPath]).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('.example')
|
||||
})
|
||||
})
|
||||
|
||||
it('compiles CSS file without an input css file', () => {
|
||||
return cli(['build']).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('normalize.css') // base
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('.container') // components
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('.mx-auto') // utilities
|
||||
})
|
||||
})
|
||||
|
||||
it('compiles CSS file using custom configuration', () => {
|
||||
return cli(['build', inputCssPath, '--config', customConfigPath]).then(() => {
|
||||
expect(process.stdout.write.mock.calls[0][0]).toContain('400px')
|
||||
|
|
|
|||
|
|
@ -21,27 +21,31 @@ function config(overrides) {
|
|||
})
|
||||
}
|
||||
|
||||
test.only('options are not required', () => {
|
||||
test('options are not required', () => {
|
||||
const { components } = processPlugins([container()], config())
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
.container { width: 100% }
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test.only('screens can be passed explicitly', () => {
|
||||
test('screens can be passed explicitly', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
|
|
@ -54,17 +58,21 @@ test.only('screens can be passed explicitly', () => {
|
|||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
.container { width: 100% }
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test.only('screens are ordered ascending by min-width', () => {
|
||||
test('screens are ordered ascending by min-width', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
|
|
@ -77,17 +85,21 @@ test.only('screens are ordered ascending by min-width', () => {
|
|||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
.container { width: 100% }
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test.only('screens are deduplicated by min-width', () => {
|
||||
test('screens are deduplicated by min-width', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
|
|
@ -104,17 +116,21 @@ test.only('screens are deduplicated by min-width', () => {
|
|||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
.container { width: 100% }
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
@layer components {
|
||||
@variants {
|
||||
.container { width: 100% }
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test.only('the container can be centered by default', () => {
|
||||
test('the container can be centered by default', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
|
|
@ -127,27 +143,31 @@ test.only('the container can be centered by default', () => {
|
|||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test.only('horizontal padding can be included by default', () => {
|
||||
test('horizontal padding can be included by default', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
|
|
@ -160,27 +180,85 @@ test.only('horizontal padding can be included by default', () => {
|
|||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container { max-width: 576px }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container { max-width: 992px }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container { max-width: 1200px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test.only('setting all options at once', () => {
|
||||
test('responsive horizontal padding can be included by default', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
padding: {
|
||||
default: '1rem',
|
||||
sm: '2rem',
|
||||
lg: '4rem',
|
||||
xl: '5rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: 576px;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 768px }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
max-width: 992px;
|
||||
padding-right: 4rem;
|
||||
padding-left: 4rem
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
padding-right: 5rem;
|
||||
padding-left: 5rem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('setting all options at once', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
|
|
@ -195,18 +273,58 @@ test.only('setting all options at once', () => {
|
|||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
@layer components {
|
||||
@variants {
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container { max-width: 400px }
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container { max-width: 500px }
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('container can use variants', () => {
|
||||
const { components } = processPlugins(
|
||||
[container()],
|
||||
config({
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
container: ['responsive', 'hover'],
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(css(components)).toMatchCss(`
|
||||
@layer components {
|
||||
@variants responsive, hover {
|
||||
.container {
|
||||
width: 100%
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
max-width: 400px
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container {
|
||||
max-width: 500px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
|
|
|||
39
__tests__/fixtures/purge-example.html
Normal file
39
__tests__/fixtures/purge-example.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!-- Basic HTML -->
|
||||
<html>
|
||||
<head>
|
||||
<title>My Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="bg-red-500 md:bg-blue-300 w-1/2"></div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- Vue dynamic classes -->
|
||||
<span :class="{ block: enabled, 'md:flow-root': !enabled }"></span>
|
||||
|
||||
<!-- JSX with template strings -->
|
||||
<script>
|
||||
function Component() {
|
||||
return <div class={`h-screen`}></div>
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Custom classes with really weird characters -->
|
||||
<div class="min-h-(screen-4) bg-black! font-%#$@ w-(1/2+8)"></div>
|
||||
|
||||
<!-- Pug -->
|
||||
span.inline-grid.grid-cols-3(class="px-1.5")
|
||||
.col-span-2
|
||||
Hello
|
||||
.col-span-1.text-center
|
||||
World!
|
||||
|
||||
<!-- Haml -->
|
||||
.flow-root
|
||||
.text-green-700.bg-green-100
|
||||
.text-left= content
|
||||
%samp.font-mono{:data-foo => "bar"}= output
|
||||
.col-span-4[aria-hidden=true]
|
||||
.tracking-tight#headline
|
||||
73330
__tests__/fixtures/tailwind-output-flagged.css
Normal file
73330
__tests__/fixtures/tailwind-output-flagged.css
Normal file
File diff suppressed because it is too large
Load diff
52620
__tests__/fixtures/tailwind-output-ie11.css
Normal file
52620
__tests__/fixtures/tailwind-output-ie11.css
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
57490
__tests__/fixtures/tailwind-output-no-color-opacity.css
Normal file
57490
__tests__/fixtures/tailwind-output-no-color-opacity.css
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
45
__tests__/flattenColorPalette.test.js
Normal file
45
__tests__/flattenColorPalette.test.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import flattenColorPalette from '../src/util/flattenColorPalette'
|
||||
|
||||
test('it flattens nested color objects', () => {
|
||||
expect(
|
||||
flattenColorPalette({
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
default: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
purple: 'purple',
|
||||
'white-25': 'rgba(255,255,255,.25)',
|
||||
'white-50': 'rgba(255,255,255,.5)',
|
||||
'white-75': 'rgba(255,255,255,.75)',
|
||||
white: '#fff',
|
||||
'red-1': 'rgb(33,0,0)',
|
||||
'red-2': 'rgb(67,0,0)',
|
||||
'red-3': 'rgb(100,0,0)',
|
||||
'green-1': 'rgb(0,33,0)',
|
||||
'green-2': 'rgb(0,67,0)',
|
||||
'green-3': 'rgb(0,100,0)',
|
||||
'blue-1': 'rgb(0,0,33)',
|
||||
'blue-2': 'rgb(0,0,67)',
|
||||
'blue-3': 'rgb(0,0,100)',
|
||||
})
|
||||
})
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import _ from 'lodash'
|
||||
import escapeClassName from '../../src/util/escapeClassName'
|
||||
import plugin from '../../src/plugins/backgroundColor'
|
||||
|
||||
test('colors can be a nested object', () => {
|
||||
const addedUtilities = []
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
backgroundColor: {
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
default: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
backgroundColor: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
|
||||
const pluginApi = {
|
||||
config: getConfigValue,
|
||||
e: escapeClassName,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
variants: (path, defaultValue) => {
|
||||
if (_.isArray(config.variants)) {
|
||||
return config.variants
|
||||
}
|
||||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
addUtilities(utilities, variants) {
|
||||
addedUtilities.push({
|
||||
utilities,
|
||||
variants,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
plugin()(pluginApi)
|
||||
|
||||
expect(addedUtilities).toEqual([
|
||||
{
|
||||
utilities: {
|
||||
'.bg-purple': { 'background-color': 'purple' },
|
||||
'.bg-white-25': { 'background-color': 'rgba(255,255,255,.25)' },
|
||||
'.bg-white-50': { 'background-color': 'rgba(255,255,255,.5)' },
|
||||
'.bg-white-75': { 'background-color': 'rgba(255,255,255,.75)' },
|
||||
'.bg-white': { 'background-color': '#fff' },
|
||||
'.bg-red-1': { 'background-color': 'rgb(33,0,0)' },
|
||||
'.bg-red-2': { 'background-color': 'rgb(67,0,0)' },
|
||||
'.bg-red-3': { 'background-color': 'rgb(100,0,0)' },
|
||||
'.bg-green-1': { 'background-color': 'rgb(0,33,0)' },
|
||||
'.bg-green-2': { 'background-color': 'rgb(0,67,0)' },
|
||||
'.bg-green-3': { 'background-color': 'rgb(0,100,0)' },
|
||||
'.bg-blue-1': { 'background-color': 'rgb(0,0,33)' },
|
||||
'.bg-blue-2': { 'background-color': 'rgb(0,0,67)' },
|
||||
'.bg-blue-3': { 'background-color': 'rgb(0,0,100)' },
|
||||
},
|
||||
variants: ['responsive'],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import _ from 'lodash'
|
||||
import escapeClassName from '../../src/util/escapeClassName'
|
||||
import plugin from '../../src/plugins/borderColor'
|
||||
|
||||
test('colors can be a nested object', () => {
|
||||
const addedUtilities = []
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
borderColor: {
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
default: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
borderColor: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
|
||||
const pluginApi = {
|
||||
config: getConfigValue,
|
||||
e: escapeClassName,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
variants: (path, defaultValue) => {
|
||||
if (_.isArray(config.variants)) {
|
||||
return config.variants
|
||||
}
|
||||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
addUtilities(utilities, variants) {
|
||||
addedUtilities.push({
|
||||
utilities,
|
||||
variants,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
plugin()(pluginApi)
|
||||
|
||||
expect(addedUtilities).toEqual([
|
||||
{
|
||||
utilities: {
|
||||
'.border-purple': { 'border-color': 'purple' },
|
||||
'.border-white-25': { 'border-color': 'rgba(255,255,255,.25)' },
|
||||
'.border-white-50': { 'border-color': 'rgba(255,255,255,.5)' },
|
||||
'.border-white-75': { 'border-color': 'rgba(255,255,255,.75)' },
|
||||
'.border-white': { 'border-color': '#fff' },
|
||||
'.border-red-1': { 'border-color': 'rgb(33,0,0)' },
|
||||
'.border-red-2': { 'border-color': 'rgb(67,0,0)' },
|
||||
'.border-red-3': { 'border-color': 'rgb(100,0,0)' },
|
||||
'.border-green-1': { 'border-color': 'rgb(0,33,0)' },
|
||||
'.border-green-2': { 'border-color': 'rgb(0,67,0)' },
|
||||
'.border-green-3': { 'border-color': 'rgb(0,100,0)' },
|
||||
'.border-blue-1': { 'border-color': 'rgb(0,0,33)' },
|
||||
'.border-blue-2': { 'border-color': 'rgb(0,0,67)' },
|
||||
'.border-blue-3': { 'border-color': 'rgb(0,0,100)' },
|
||||
},
|
||||
variants: ['responsive'],
|
||||
},
|
||||
])
|
||||
})
|
||||
75
__tests__/plugins/divideWidth.test.js
Normal file
75
__tests__/plugins/divideWidth.test.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/divideWidth'
|
||||
|
||||
test('generating divide width utilities', () => {
|
||||
const config = {
|
||||
target: 'relaxed',
|
||||
theme: {
|
||||
divideWidth: {
|
||||
default: '1px',
|
||||
'0': '0',
|
||||
'2': '2px',
|
||||
'4': '4px',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
divideWidth: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.divide-y > :not(template) ~ :not(template)': {
|
||||
'--divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(1px * calc(1 - var(--divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(1px * var(--divide-y-reverse))',
|
||||
},
|
||||
'.divide-x > :not(template) ~ :not(template)': {
|
||||
'--divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(1px * var(--divide-x-reverse))',
|
||||
'border-left-width': 'calc(1px * calc(1 - var(--divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-0 > :not(template) ~ :not(template)': {
|
||||
'--divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(0px * calc(1 - var(--divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(0px * var(--divide-y-reverse))',
|
||||
},
|
||||
'.divide-x-0 > :not(template) ~ :not(template)': {
|
||||
'--divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(0px * var(--divide-x-reverse))',
|
||||
'border-left-width': 'calc(0px * calc(1 - var(--divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-2 > :not(template) ~ :not(template)': {
|
||||
'--divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(2px * calc(1 - var(--divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(2px * var(--divide-y-reverse))',
|
||||
},
|
||||
'.divide-x-2 > :not(template) ~ :not(template)': {
|
||||
'--divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(2px * var(--divide-x-reverse))',
|
||||
'border-left-width': 'calc(2px * calc(1 - var(--divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-4 > :not(template) ~ :not(template)': {
|
||||
'--divide-y-reverse': '0',
|
||||
'border-top-width': 'calc(4px * calc(1 - var(--divide-y-reverse)))',
|
||||
'border-bottom-width': 'calc(4px * var(--divide-y-reverse))',
|
||||
},
|
||||
'.divide-x-4 > :not(template) ~ :not(template)': {
|
||||
'--divide-x-reverse': '0',
|
||||
'border-right-width': 'calc(4px * var(--divide-x-reverse))',
|
||||
'border-left-width': 'calc(4px * calc(1 - var(--divide-x-reverse)))',
|
||||
},
|
||||
'.divide-y-reverse > :not(template) ~ :not(template)': {
|
||||
'--divide-y-reverse': '1',
|
||||
},
|
||||
'.divide-x-reverse > :not(template) ~ :not(template)': {
|
||||
'--divide-x-reverse': '1',
|
||||
},
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import _ from 'lodash'
|
||||
import escapeClassName from '../../src/util/escapeClassName'
|
||||
import plugin from '../../src/plugins/fill'
|
||||
|
||||
test('colors can be a nested object', () => {
|
||||
const addedUtilities = []
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
fill: {
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
default: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fill: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
|
||||
const pluginApi = {
|
||||
config: getConfigValue,
|
||||
e: escapeClassName,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
variants: (path, defaultValue) => {
|
||||
if (_.isArray(config.variants)) {
|
||||
return config.variants
|
||||
}
|
||||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
addUtilities(utilities, variants) {
|
||||
addedUtilities.push({
|
||||
utilities,
|
||||
variants,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
plugin()(pluginApi)
|
||||
|
||||
expect(addedUtilities).toEqual([
|
||||
{
|
||||
utilities: {
|
||||
'.fill-purple': { fill: 'purple' },
|
||||
'.fill-white-25': { fill: 'rgba(255,255,255,.25)' },
|
||||
'.fill-white-50': { fill: 'rgba(255,255,255,.5)' },
|
||||
'.fill-white-75': { fill: 'rgba(255,255,255,.75)' },
|
||||
'.fill-white': { fill: '#fff' },
|
||||
'.fill-red-1': { fill: 'rgb(33,0,0)' },
|
||||
'.fill-red-2': { fill: 'rgb(67,0,0)' },
|
||||
'.fill-red-3': { fill: 'rgb(100,0,0)' },
|
||||
'.fill-green-1': { fill: 'rgb(0,33,0)' },
|
||||
'.fill-green-2': { fill: 'rgb(0,67,0)' },
|
||||
'.fill-green-3': { fill: 'rgb(0,100,0)' },
|
||||
'.fill-blue-1': { fill: 'rgb(0,0,33)' },
|
||||
'.fill-blue-2': { fill: 'rgb(0,0,67)' },
|
||||
'.fill-blue-3': { fill: 'rgb(0,0,100)' },
|
||||
},
|
||||
variants: ['responsive'],
|
||||
},
|
||||
])
|
||||
})
|
||||
86
__tests__/plugins/fontSize.test.js
Normal file
86
__tests__/plugins/fontSize.test.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/fontSize'
|
||||
|
||||
test('font-size utilities can include a default line-height', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', '24px'],
|
||||
lg: ['20px', '28px'],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fontSize: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.text-sm': { 'font-size': '12px' },
|
||||
'.text-md': { 'font-size': '16px', 'line-height': '24px' },
|
||||
'.text-lg': { 'font-size': '20px', 'line-height': '28px' },
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default letter-spacing', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fontSize: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.text-sm': { 'font-size': '12px' },
|
||||
'.text-md': { 'font-size': '16px', 'letter-spacing': '-0.01em' },
|
||||
'.text-lg': { 'font-size': '20px', 'letter-spacing': '-0.02em' },
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default line-height and letter-spacing', () => {
|
||||
const config = {
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
fontSize: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.text-sm': { 'font-size': '12px' },
|
||||
'.text-md': { 'font-size': '16px', 'line-height': '24px', 'letter-spacing': '-0.01em' },
|
||||
'.text-lg': { 'font-size': '20px', 'line-height': '28px', 'letter-spacing': '-0.02em' },
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
97
__tests__/plugins/space.test.js
Normal file
97
__tests__/plugins/space.test.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import invokePlugin from '../util/invokePlugin'
|
||||
import plugin from '../../src/plugins/space'
|
||||
|
||||
test('generating space utilities', () => {
|
||||
const config = {
|
||||
target: 'relaxed',
|
||||
theme: {
|
||||
space: {
|
||||
'0': '0',
|
||||
'1': '1px',
|
||||
'2': '2px',
|
||||
'4': '4px',
|
||||
'-2': '-2px',
|
||||
'-1': '-1px',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
space: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const { utilities } = invokePlugin(plugin(), config)
|
||||
|
||||
expect(utilities).toEqual([
|
||||
[
|
||||
{
|
||||
'.space-y-0 > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '0',
|
||||
'margin-top': 'calc(0px * calc(1 - var(--space-y-reverse)))',
|
||||
'margin-bottom': 'calc(0px * var(--space-y-reverse))',
|
||||
},
|
||||
'.space-x-0 > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '0',
|
||||
'margin-right': 'calc(0px * var(--space-x-reverse))',
|
||||
'margin-left': 'calc(0px * calc(1 - var(--space-x-reverse)))',
|
||||
},
|
||||
'.space-y-1 > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '0',
|
||||
'margin-top': 'calc(1px * calc(1 - var(--space-y-reverse)))',
|
||||
'margin-bottom': 'calc(1px * var(--space-y-reverse))',
|
||||
},
|
||||
'.space-x-1 > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '0',
|
||||
'margin-right': 'calc(1px * var(--space-x-reverse))',
|
||||
'margin-left': 'calc(1px * calc(1 - var(--space-x-reverse)))',
|
||||
},
|
||||
'.space-y-2 > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '0',
|
||||
'margin-top': 'calc(2px * calc(1 - var(--space-y-reverse)))',
|
||||
'margin-bottom': 'calc(2px * var(--space-y-reverse))',
|
||||
},
|
||||
'.space-x-2 > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '0',
|
||||
'margin-right': 'calc(2px * var(--space-x-reverse))',
|
||||
'margin-left': 'calc(2px * calc(1 - var(--space-x-reverse)))',
|
||||
},
|
||||
'.space-y-4 > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '0',
|
||||
'margin-top': 'calc(4px * calc(1 - var(--space-y-reverse)))',
|
||||
'margin-bottom': 'calc(4px * var(--space-y-reverse))',
|
||||
},
|
||||
'.space-x-4 > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '0',
|
||||
'margin-right': 'calc(4px * var(--space-x-reverse))',
|
||||
'margin-left': 'calc(4px * calc(1 - var(--space-x-reverse)))',
|
||||
},
|
||||
'.-space-y-2 > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '0',
|
||||
'margin-top': 'calc(-2px * calc(1 - var(--space-y-reverse)))',
|
||||
'margin-bottom': 'calc(-2px * var(--space-y-reverse))',
|
||||
},
|
||||
'.-space-x-2 > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '0',
|
||||
'margin-right': 'calc(-2px * var(--space-x-reverse))',
|
||||
'margin-left': 'calc(-2px * calc(1 - var(--space-x-reverse)))',
|
||||
},
|
||||
'.-space-y-1 > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '0',
|
||||
'margin-top': 'calc(-1px * calc(1 - var(--space-y-reverse)))',
|
||||
'margin-bottom': 'calc(-1px * var(--space-y-reverse))',
|
||||
},
|
||||
'.-space-x-1 > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '0',
|
||||
'margin-right': 'calc(-1px * var(--space-x-reverse))',
|
||||
'margin-left': 'calc(-1px * calc(1 - var(--space-x-reverse)))',
|
||||
},
|
||||
'.space-y-reverse > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '1',
|
||||
},
|
||||
'.space-x-reverse > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '1',
|
||||
},
|
||||
},
|
||||
['responsive'],
|
||||
],
|
||||
])
|
||||
})
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import _ from 'lodash'
|
||||
import escapeClassName from '../../src/util/escapeClassName'
|
||||
import plugin from '../../src/plugins/stroke'
|
||||
|
||||
test('colors can be a nested object', () => {
|
||||
const addedUtilities = []
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
stroke: {
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
default: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
stroke: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
|
||||
const pluginApi = {
|
||||
config: getConfigValue,
|
||||
e: escapeClassName,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
variants: (path, defaultValue) => {
|
||||
if (_.isArray(config.variants)) {
|
||||
return config.variants
|
||||
}
|
||||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
addUtilities(utilities, variants) {
|
||||
addedUtilities.push({
|
||||
utilities,
|
||||
variants,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
plugin()(pluginApi)
|
||||
|
||||
expect(addedUtilities).toEqual([
|
||||
{
|
||||
utilities: {
|
||||
'.stroke-purple': { stroke: 'purple' },
|
||||
'.stroke-white-25': { stroke: 'rgba(255,255,255,.25)' },
|
||||
'.stroke-white-50': { stroke: 'rgba(255,255,255,.5)' },
|
||||
'.stroke-white-75': { stroke: 'rgba(255,255,255,.75)' },
|
||||
'.stroke-white': { stroke: '#fff' },
|
||||
'.stroke-red-1': { stroke: 'rgb(33,0,0)' },
|
||||
'.stroke-red-2': { stroke: 'rgb(67,0,0)' },
|
||||
'.stroke-red-3': { stroke: 'rgb(100,0,0)' },
|
||||
'.stroke-green-1': { stroke: 'rgb(0,33,0)' },
|
||||
'.stroke-green-2': { stroke: 'rgb(0,67,0)' },
|
||||
'.stroke-green-3': { stroke: 'rgb(0,100,0)' },
|
||||
'.stroke-blue-1': { stroke: 'rgb(0,0,33)' },
|
||||
'.stroke-blue-2': { stroke: 'rgb(0,0,67)' },
|
||||
'.stroke-blue-3': { stroke: 'rgb(0,0,100)' },
|
||||
},
|
||||
variants: ['responsive'],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import _ from 'lodash'
|
||||
import escapeClassName from '../../src/util/escapeClassName'
|
||||
import plugin from '../../src/plugins/textColor'
|
||||
|
||||
test('colors can be a nested object', () => {
|
||||
const addedUtilities = []
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
textColor: {
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
default: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
textColor: ['responsive'],
|
||||
},
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
|
||||
const pluginApi = {
|
||||
config: getConfigValue,
|
||||
e: escapeClassName,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
variants: (path, defaultValue) => {
|
||||
if (_.isArray(config.variants)) {
|
||||
return config.variants
|
||||
}
|
||||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
addUtilities(utilities, variants) {
|
||||
addedUtilities.push({
|
||||
utilities,
|
||||
variants,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
plugin()(pluginApi)
|
||||
|
||||
expect(addedUtilities).toEqual([
|
||||
{
|
||||
utilities: {
|
||||
'.text-purple': { color: 'purple' },
|
||||
'.text-white-25': { color: 'rgba(255,255,255,.25)' },
|
||||
'.text-white-50': { color: 'rgba(255,255,255,.5)' },
|
||||
'.text-white-75': { color: 'rgba(255,255,255,.75)' },
|
||||
'.text-white': { color: '#fff' },
|
||||
'.text-red-1': { color: 'rgb(33,0,0)' },
|
||||
'.text-red-2': { color: 'rgb(67,0,0)' },
|
||||
'.text-red-3': { color: 'rgb(100,0,0)' },
|
||||
'.text-green-1': { color: 'rgb(0,33,0)' },
|
||||
'.text-green-2': { color: 'rgb(0,67,0)' },
|
||||
'.text-green-3': { color: 'rgb(0,100,0)' },
|
||||
'.text-blue-1': { color: 'rgb(0,0,33)' },
|
||||
'.text-blue-2': { color: 'rgb(0,0,67)' },
|
||||
'.text-blue-3': { color: 'rgb(0,0,100)' },
|
||||
},
|
||||
variants: ['responsive'],
|
||||
},
|
||||
])
|
||||
})
|
||||
File diff suppressed because it is too large
Load diff
319
__tests__/purgeUnusedStyles.test.js
Normal file
319
__tests__/purgeUnusedStyles.test.js
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import postcss from 'postcss'
|
||||
import tailwind from '../src/index'
|
||||
import defaultConfig from '../stubs/defaultConfig.stub.js'
|
||||
|
||||
const config = {
|
||||
...defaultConfig,
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'black!': '#000',
|
||||
},
|
||||
spacing: {
|
||||
'1.5': '0.375rem',
|
||||
'(1/2+8)': 'calc(50% + 2rem)',
|
||||
},
|
||||
minHeight: {
|
||||
'(screen-4)': 'calc(100vh - 1rem)',
|
||||
},
|
||||
fontFamily: {
|
||||
'%#$@': 'Comic Sans',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function assertPurged(result) {
|
||||
expect(result.css).not.toContain('.bg-red-600')
|
||||
expect(result.css).not.toContain('.w-1\\/3')
|
||||
expect(result.css).not.toContain('.flex')
|
||||
expect(result.css).not.toContain('.font-sans')
|
||||
expect(result.css).not.toContain('.text-right')
|
||||
expect(result.css).not.toContain('.px-4')
|
||||
expect(result.css).not.toContain('.h-full')
|
||||
|
||||
expect(result.css).toContain('.bg-red-500')
|
||||
expect(result.css).toContain('.md\\:bg-blue-300')
|
||||
expect(result.css).toContain('.w-1\\/2')
|
||||
expect(result.css).toContain('.block')
|
||||
expect(result.css).toContain('.md\\:flow-root')
|
||||
expect(result.css).toContain('.h-screen')
|
||||
expect(result.css).toContain('.min-h-\\(screen-4\\)')
|
||||
expect(result.css).toContain('.bg-black\\!')
|
||||
expect(result.css).toContain('.font-\\%\\#\\$\\@')
|
||||
expect(result.css).toContain('.w-\\(1\\/2\\+8\\)')
|
||||
expect(result.css).toContain('.inline-grid')
|
||||
expect(result.css).toContain('.grid-cols-3')
|
||||
expect(result.css).toContain('.px-1\\.5')
|
||||
expect(result.css).toContain('.col-span-2')
|
||||
expect(result.css).toContain('.col-span-1')
|
||||
expect(result.css).toContain('.text-center')
|
||||
expect(result.css).toContain('.flow-root')
|
||||
expect(result.css).toContain('.text-green-700')
|
||||
expect(result.css).toContain('.bg-green-100')
|
||||
expect(result.css).toContain('.text-left')
|
||||
expect(result.css).toContain('.font-mono')
|
||||
expect(result.css).toContain('.col-span-4')
|
||||
expect(result.css).toContain('.tracking-tight')
|
||||
expect(result.css).toContain('.tracking-tight')
|
||||
}
|
||||
|
||||
test('purges unused classes', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'production'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...config,
|
||||
purge: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
|
||||
assertPurged(result)
|
||||
})
|
||||
})
|
||||
|
||||
test('does not purge components', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'production'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...config,
|
||||
purge: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
|
||||
expect(result.css).toContain('.container')
|
||||
assertPurged(result)
|
||||
})
|
||||
})
|
||||
|
||||
test('does not purge except in production', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'development'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...defaultConfig,
|
||||
purge: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('does not purge if the array is empty', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'production'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...defaultConfig,
|
||||
purge: [],
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('does not purge if explicitly disabled', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'production'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...defaultConfig,
|
||||
purge: { enabled: false },
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('does not purge if purge is simply false', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'production'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...defaultConfig,
|
||||
purge: false,
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('purges outside of production if explicitly enabled', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'development'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...config,
|
||||
purge: { enabled: true, content: [path.resolve(`${__dirname}/fixtures/**/*.html`)] },
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
|
||||
assertPurged(result)
|
||||
})
|
||||
})
|
||||
|
||||
test('purgecss options can be provided', () => {
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...config,
|
||||
purge: {
|
||||
enabled: true,
|
||||
options: {
|
||||
content: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
|
||||
whitelist: ['md:bg-green-500'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
expect(result.css).toContain('.md\\:bg-green-500')
|
||||
assertPurged(result)
|
||||
})
|
||||
})
|
||||
|
||||
test('can purge all CSS, not just Tailwind classes', () => {
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...config,
|
||||
purge: {
|
||||
enabled: true,
|
||||
mode: 'all',
|
||||
content: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
|
||||
},
|
||||
}),
|
||||
function(css) {
|
||||
// Remove any comments to avoid accidentally asserting against them
|
||||
// instead of against real CSS rules.
|
||||
css.walkComments(c => c.remove())
|
||||
},
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
expect(result.css).toContain('html')
|
||||
expect(result.css).toContain('body')
|
||||
expect(result.css).toContain('samp')
|
||||
expect(result.css).not.toContain('button')
|
||||
expect(result.css).not.toContain('legend')
|
||||
expect(result.css).not.toContain('progress')
|
||||
|
||||
assertPurged(result)
|
||||
})
|
||||
})
|
||||
|
||||
test('the `conservative` mode can be set explicitly', () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'production'
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...config,
|
||||
purge: {
|
||||
mode: 'conservative',
|
||||
content: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
process.env.NODE_ENV = OLD_NODE_ENV
|
||||
|
||||
expect(result.css).not.toContain('.bg-red-600')
|
||||
expect(result.css).not.toContain('.w-1\\/3')
|
||||
expect(result.css).not.toContain('.flex')
|
||||
expect(result.css).not.toContain('.font-sans')
|
||||
expect(result.css).not.toContain('.text-right')
|
||||
expect(result.css).not.toContain('.px-4')
|
||||
expect(result.css).not.toContain('.h-full')
|
||||
|
||||
expect(result.css).toContain('.bg-red-500')
|
||||
expect(result.css).toContain('.md\\:bg-blue-300')
|
||||
expect(result.css).toContain('.w-1\\/2')
|
||||
expect(result.css).toContain('.block')
|
||||
expect(result.css).toContain('.md\\:flow-root')
|
||||
expect(result.css).toContain('.h-screen')
|
||||
expect(result.css).toContain('.min-h-\\(screen-4\\)')
|
||||
expect(result.css).toContain('.bg-black\\!')
|
||||
expect(result.css).toContain('.font-\\%\\#\\$\\@')
|
||||
expect(result.css).toContain('.w-\\(1\\/2\\+8\\)')
|
||||
expect(result.css).toContain('.inline-grid')
|
||||
expect(result.css).toContain('.grid-cols-3')
|
||||
expect(result.css).toContain('.px-1\\.5')
|
||||
expect(result.css).toContain('.col-span-2')
|
||||
expect(result.css).toContain('.col-span-1')
|
||||
expect(result.css).toContain('.text-center')
|
||||
})
|
||||
})
|
||||
|
|
@ -12,23 +12,33 @@ test('it can generate responsive variants', () => {
|
|||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
@layer utilities {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
@media (min-width: 500px) {
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.sm\\:banana { color: yellow; }
|
||||
.sm\\:chocolate { color: brown; }
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
@layer utilities {
|
||||
.md\\:banana { color: yellow; }
|
||||
.md\\:chocolate { color: brown; }
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.lg\\:banana { color: yellow; }
|
||||
.lg\\:chocolate { color: brown; }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
|
|
@ -52,23 +62,33 @@ test('it can generate responsive variants with a custom separator', () => {
|
|||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
@layer utilities {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
@media (min-width: 500px) {
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.sm__banana { color: yellow; }
|
||||
.sm__chocolate { color: brown; }
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
@layer utilities {
|
||||
.md__banana { color: yellow; }
|
||||
.md__chocolate { color: brown; }
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.lg__banana { color: yellow; }
|
||||
.lg__chocolate { color: brown; }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
|
|
@ -92,23 +112,33 @@ test('it can generate responsive variants when classes have non-standard charact
|
|||
.hover\\:banana { color: yellow; }
|
||||
.chocolate-2\\.5 { color: brown; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
@layer utilities {
|
||||
.hover\\:banana { color: yellow; }
|
||||
.chocolate-2\\.5 { color: brown; }
|
||||
@media (min-width: 500px) {
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.sm\\:hover\\:banana { color: yellow; }
|
||||
.sm\\:chocolate-2\\.5 { color: brown; }
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
@layer utilities {
|
||||
.md\\:hover\\:banana { color: yellow; }
|
||||
.md\\:chocolate-2\\.5 { color: brown; }
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.lg\\:hover\\:banana { color: yellow; }
|
||||
.lg\\:chocolate-2\\.5 { color: brown; }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
|
|
@ -137,24 +167,42 @@ test('responsive variants are grouped', () => {
|
|||
@responsive {
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
@layer utilities {
|
||||
.banana { color: yellow; }
|
||||
.apple { color: red; }
|
||||
}
|
||||
.apple { color: red; }
|
||||
@layer utilities {
|
||||
.chocolate { color: brown; }
|
||||
@media (min-width: 500px) {
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.sm\\:banana { color: yellow; }
|
||||
}
|
||||
@layer utilities {
|
||||
.sm\\:chocolate { color: brown; }
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
@layer utilities {
|
||||
.md\\:banana { color: yellow; }
|
||||
}
|
||||
@layer utilities {
|
||||
.md\\:chocolate { color: brown; }
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.lg\\:banana { color: yellow; }
|
||||
}
|
||||
@layer utilities {
|
||||
.lg\\:chocolate { color: brown; }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
|
|
@ -181,39 +229,47 @@ test('it can generate responsive variants for nested at-rules', () => {
|
|||
.grid\\:banana { color: blue; }
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
.grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.sm\\:banana {
|
||||
@layer utilities {
|
||||
.banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
.sm\\:grid\\:banana {
|
||||
.grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.lg\\:banana {
|
||||
color: yellow;
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.sm\\:banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
.lg\\:grid\\:banana {
|
||||
color: blue;
|
||||
@supports(display: grid) {
|
||||
.sm\\:grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.lg\\:banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
.lg\\:grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -244,44 +300,52 @@ test('it can generate responsive variants for deeply nested at-rules', () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
@supports(display: flex) {
|
||||
.flex-grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.sm\\:banana {
|
||||
@layer utilities {
|
||||
.banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
@supports(display: flex) {
|
||||
.sm\\:flex-grid\\:banana {
|
||||
.flex-grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.lg\\:banana {
|
||||
color: yellow;
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.sm\\:banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
@supports(display: flex) {
|
||||
.lg\\:flex-grid\\:banana {
|
||||
color: blue;
|
||||
@supports(display: grid) {
|
||||
@supports(display: flex) {
|
||||
.sm\\:flex-grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.lg\\:banana {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@supports(display: grid) {
|
||||
@supports(display: flex) {
|
||||
.lg\\:flex-grid\\:banana {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -307,19 +371,29 @@ test('screen prefix is only applied to the last class in a selector', () => {
|
|||
@responsive {
|
||||
.banana li * .sandwich #foo > div { color: yellow; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
@layer utilities {
|
||||
.banana li * .sandwich #foo > div { color: yellow; }
|
||||
@media (min-width: 500px) {
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.banana li * .sm\\:sandwich #foo > div { color: yellow; }
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
@layer utilities {
|
||||
.banana li * .md\\:sandwich #foo > div { color: yellow; }
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.banana li * .lg\\:sandwich #foo > div { color: yellow; }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
|
|
@ -342,19 +416,29 @@ test('responsive variants are generated for all selectors in a rule', () => {
|
|||
@responsive {
|
||||
.foo, .bar { color: yellow; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
|
||||
const output = `
|
||||
@layer utilities {
|
||||
.foo, .bar { color: yellow; }
|
||||
@media (min-width: 500px) {
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
@layer utilities {
|
||||
.sm\\:foo, .sm\\:bar { color: yellow; }
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
}
|
||||
@media (min-width: 750px) {
|
||||
@layer utilities {
|
||||
.md\\:foo, .md\\:bar { color: yellow; }
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
@layer utilities {
|
||||
.lg\\:foo, .lg\\:bar { color: yellow; }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
|
|
@ -377,6 +461,8 @@ test('selectors with no classes cannot be made responsive', () => {
|
|||
@responsive {
|
||||
div { color: yellow; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
expect.assertions(1)
|
||||
return run(input, {
|
||||
|
|
@ -398,6 +484,8 @@ test('all selectors in a rule must contain classes', () => {
|
|||
@responsive {
|
||||
.foo, div { color: yellow; }
|
||||
}
|
||||
|
||||
@tailwind screens;
|
||||
`
|
||||
expect.assertions(1)
|
||||
return run(input, {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,56 @@ it('generates the right CSS when using @import instead of @tailwind', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('generates the right CSS when enabling flagged features', () => {
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
experimental: {
|
||||
uniformColorPalette: true,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output-flagged.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Move to per plugin unit tests for this sort of thing
|
||||
it('generates the right CSS when color opacity plugins are disabled', () => {
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([
|
||||
tailwind({
|
||||
...config,
|
||||
corePlugins: {
|
||||
textOpacity: false,
|
||||
backgroundOpacity: false,
|
||||
borderOpacity: false,
|
||||
placeholderOpacity: false,
|
||||
divideOpacity: false,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output-no-color-opacity.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not add any CSS if no Tailwind features are used', () => {
|
||||
return postcss([tailwind()])
|
||||
.process('.foo { color: blue; }', { from: undefined })
|
||||
|
|
@ -77,3 +127,19 @@ it('generates the right CSS with implicit screen utilities', () => {
|
|||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('generates the right CSS when "important" is enabled', () => {
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([tailwind({ ...config, important: true })])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output-important.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
201
__tests__/targetMode.test.js
Normal file
201
__tests__/targetMode.test.js
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import postcss from 'postcss'
|
||||
import tailwind from '../src/index'
|
||||
import config from '../stubs/defaultConfig.stub.js'
|
||||
import processPlugins from '../src/util/processPlugins'
|
||||
import runInTempDirectory from '../jest/runInTempDirectory'
|
||||
|
||||
function css(nodes) {
|
||||
return postcss.root({ nodes }).toString()
|
||||
}
|
||||
|
||||
it('generates the right CSS in IE11 mode', () => {
|
||||
const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
|
||||
const input = fs.readFileSync(inputPath, 'utf8')
|
||||
|
||||
return postcss([tailwind({ ...config, target: 'ie11' })])
|
||||
.process(input, { from: inputPath })
|
||||
.then(result => {
|
||||
const expected = fs.readFileSync(
|
||||
path.resolve(`${__dirname}/fixtures/tailwind-output-ie11.css`),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(result.css).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('plugins can request the target for a specific plugin key', () => {
|
||||
const { utilities } = processPlugins(
|
||||
[
|
||||
function({ addUtilities, target }) {
|
||||
addUtilities({
|
||||
'.testA': {
|
||||
target: target('testPluginA'),
|
||||
},
|
||||
})
|
||||
},
|
||||
function({ addUtilities, target }) {
|
||||
addUtilities({
|
||||
'.testB': {
|
||||
target: target('testPluginB'),
|
||||
},
|
||||
})
|
||||
},
|
||||
function({ addUtilities, target }) {
|
||||
addUtilities({
|
||||
'.testC': {
|
||||
target: target('testPluginC'),
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
{
|
||||
...config,
|
||||
target: [
|
||||
'ie11',
|
||||
{
|
||||
testPluginA: 'modern',
|
||||
testPluginB: 'relaxed',
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(css(utilities)).toMatchCss(`
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.testA {
|
||||
target: modern
|
||||
}
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.testB {
|
||||
target: relaxed
|
||||
}
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.testC {
|
||||
target: ie11
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('browserslist target is translated to a target preset', () => {
|
||||
return runInTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve('./.browserslistrc'),
|
||||
`
|
||||
last 2 versions
|
||||
IE 11
|
||||
`
|
||||
)
|
||||
const { utilities } = processPlugins(
|
||||
[
|
||||
function({ addUtilities, target }) {
|
||||
addUtilities({
|
||||
'.test': {
|
||||
target: target('testPlugin'),
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
{
|
||||
...config,
|
||||
target: 'browserslist',
|
||||
}
|
||||
)
|
||||
|
||||
expect(css(utilities)).toMatchCss(`
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.test {
|
||||
target: ie11
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
})
|
||||
|
||||
test('browserslist target is translated to a target preset with overrides', () => {
|
||||
return runInTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve('./.browserslistrc'),
|
||||
`
|
||||
last 2 versions
|
||||
IE 11
|
||||
`
|
||||
)
|
||||
const { utilities } = processPlugins(
|
||||
[
|
||||
function({ addUtilities, target }) {
|
||||
addUtilities({
|
||||
'.testA': {
|
||||
target: target('testPluginA'),
|
||||
},
|
||||
})
|
||||
},
|
||||
function({ addUtilities, target }) {
|
||||
addUtilities({
|
||||
'.testB': {
|
||||
target: target('testPluginB'),
|
||||
},
|
||||
})
|
||||
},
|
||||
function({ addUtilities, target }) {
|
||||
addUtilities({
|
||||
'.testC': {
|
||||
target: target('testPluginC'),
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
{
|
||||
...config,
|
||||
target: [
|
||||
'browserslist',
|
||||
{
|
||||
testPluginA: 'modern',
|
||||
testPluginB: 'relaxed',
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(css(utilities)).toMatchCss(`
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.testA {
|
||||
target: modern
|
||||
}
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.testB {
|
||||
target: relaxed
|
||||
}
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
@variants {
|
||||
.testC {
|
||||
target: ie11
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
})
|
||||
|
|
@ -16,6 +16,15 @@ export default function(plugin, config) {
|
|||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
target: path => {
|
||||
if (_.isString(config.target)) {
|
||||
return config.target
|
||||
}
|
||||
|
||||
const [defaultTarget, targetOverrides] = getConfigValue('target')
|
||||
|
||||
return _.get(targetOverrides, path, defaultTarget)
|
||||
},
|
||||
addUtilities(utilities, variants) {
|
||||
addedUtilities.push([utilities, variants])
|
||||
},
|
||||
|
|
@ -24,6 +33,9 @@ export default function(plugin, config) {
|
|||
plugin(pluginApi)
|
||||
|
||||
return {
|
||||
utilities: addedUtilities,
|
||||
utilities: addedUtilities.map(([utilities, variants]) => [
|
||||
_.merge({}, ..._.castArray(utilities)),
|
||||
variants,
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,27 @@ test('it can generate disabled variants', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('it can generate checked variants', () => {
|
||||
const input = `
|
||||
@variants checked {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
.checked\\:banana:checked { color: yellow; }
|
||||
.checked\\:chocolate:checked { color: brown; }
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('it can generate active variants', () => {
|
||||
const input = `
|
||||
@variants active {
|
||||
|
|
@ -135,6 +156,187 @@ test('it can generate focus-within variants', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('it can generate focus-visible variants', () => {
|
||||
const input = `
|
||||
@variants focus-visible {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
.focus-visible\\:banana:focus-visible { color: yellow; }
|
||||
.focus-visible\\:chocolate:focus-visible { color: brown; }
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('it can generate motion-reduce variants', () => {
|
||||
const input = `
|
||||
@variants motion-reduce {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-reduce\\:banana { color: yellow; }
|
||||
.motion-reduce\\:chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('it can generate motion-safe variants', () => {
|
||||
const input = `
|
||||
@variants motion-safe {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\\:banana { color: yellow; }
|
||||
.motion-safe\\:chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('it can generate motion-safe and motion-reduce variants', () => {
|
||||
const input = `
|
||||
@variants motion-safe, motion-reduce {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\\:banana { color: yellow; }
|
||||
.motion-safe\\:chocolate { color: brown; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-reduce\\:banana { color: yellow; }
|
||||
.motion-reduce\\:chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('motion-reduce variants stack with basic variants', () => {
|
||||
const input = `
|
||||
@variants motion-reduce, hover {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
.hover\\:banana:hover { color: yellow; }
|
||||
.hover\\:chocolate:hover { color: brown; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-reduce\\:banana { color: yellow; }
|
||||
.motion-reduce\\:chocolate { color: brown; }
|
||||
.motion-reduce\\:hover\\:banana:hover { color: yellow; }
|
||||
.motion-reduce\\:hover\\:chocolate:hover { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('motion-safe variants stack with basic variants', () => {
|
||||
const input = `
|
||||
@variants motion-safe, hover {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
.hover\\:banana:hover { color: yellow; }
|
||||
.hover\\:chocolate:hover { color: brown; }
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\\:banana { color: yellow; }
|
||||
.motion-safe\\:chocolate { color: brown; }
|
||||
.motion-safe\\:hover\\:banana:hover { color: yellow; }
|
||||
.motion-safe\\:hover\\:chocolate:hover { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('motion-safe and motion-reduce variants stack with basic variants', () => {
|
||||
const input = `
|
||||
@variants motion-reduce, motion-safe, hover {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
.hover\\:banana:hover { color: yellow; }
|
||||
.hover\\:chocolate:hover { color: brown; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-reduce\\:banana { color: yellow; }
|
||||
.motion-reduce\\:chocolate { color: brown; }
|
||||
.motion-reduce\\:hover\\:banana:hover { color: yellow; }
|
||||
.motion-reduce\\:hover\\:chocolate:hover { color: brown; }
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\\:banana { color: yellow; }
|
||||
.motion-safe\\:chocolate { color: brown; }
|
||||
.motion-safe\\:hover\\:banana:hover { color: yellow; }
|
||||
.motion-safe\\:hover\\:chocolate:hover { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('it can generate first-child variants', () => {
|
||||
const input = `
|
||||
@variants first {
|
||||
|
|
@ -264,6 +466,51 @@ test('group-hover variants respect any configured prefix', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('it can generate group-focus variants', () => {
|
||||
const input = `
|
||||
@variants group-focus {
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: yellow; }
|
||||
.chocolate { color: brown; }
|
||||
.group:focus .group-focus\\:banana { color: yellow; }
|
||||
.group:focus .group-focus\\:chocolate { color: brown; }
|
||||
`
|
||||
|
||||
return run(input).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('group-focus variants respect any configured prefix', () => {
|
||||
const input = `
|
||||
@variants group-focus {
|
||||
.tw-banana { color: yellow; }
|
||||
.tw-chocolate { color: brown; }
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.tw-banana { color: yellow; }
|
||||
.tw-chocolate { color: brown; }
|
||||
.tw-group:focus .group-focus\\:tw-banana { color: yellow; }
|
||||
.tw-group:focus .group-focus\\:tw-chocolate { color: brown; }
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
...config,
|
||||
prefix: 'tw-',
|
||||
}).then(result => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
test('it can generate hover, active and focus variants', () => {
|
||||
const input = `
|
||||
@variants group-hover, hover, focus, active {
|
||||
|
|
@ -370,7 +617,7 @@ test('variants are generated in the order specified', () => {
|
|||
|
||||
test('the built-in variant pseudo-selectors are appended before any pseudo-elements', () => {
|
||||
const input = `
|
||||
@variants hover, focus-within, focus, active, group-hover {
|
||||
@variants hover, focus-within, focus-visible, focus, active, group-hover {
|
||||
.placeholder-yellow::placeholder { color: yellow; }
|
||||
}
|
||||
`
|
||||
|
|
@ -379,6 +626,7 @@ test('the built-in variant pseudo-selectors are appended before any pseudo-eleme
|
|||
.placeholder-yellow::placeholder { color: yellow; }
|
||||
.hover\\:placeholder-yellow:hover::placeholder { color: yellow; }
|
||||
.focus-within\\:placeholder-yellow:focus-within::placeholder { color: yellow; }
|
||||
.focus-visible\\:placeholder-yellow:focus-visible::placeholder { color: yellow; }
|
||||
.focus\\:placeholder-yellow:focus::placeholder { color: yellow; }
|
||||
.active\\:placeholder-yellow:active::placeholder { color: yellow; }
|
||||
.group:hover .group-hover\\:placeholder-yellow::placeholder { color: yellow; }
|
||||
|
|
@ -579,7 +827,10 @@ test('plugin variants can wrap rules in another at-rule using the raw PostCSS AP
|
|||
...config.plugins,
|
||||
function({ addVariant, e }) {
|
||||
addVariant('supports-grid', ({ container, separator }) => {
|
||||
const supportsRule = postcss.atRule({ name: 'supports', params: '(display: grid)' })
|
||||
const supportsRule = postcss.atRule({
|
||||
name: 'supports',
|
||||
params: '(display: grid)',
|
||||
})
|
||||
supportsRule.nodes = container.nodes
|
||||
container.nodes = [supportsRule]
|
||||
supportsRule.walkRules(rule => {
|
||||
|
|
|
|||
97
__tests__/withAlphaVariable.test.js
Normal file
97
__tests__/withAlphaVariable.test.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import withAlphaVariable from '../src/util/withAlphaVariable'
|
||||
|
||||
test('it adds the right custom property', () => {
|
||||
expect(
|
||||
withAlphaVariable({ color: '#ff0000', property: 'color', variable: '--text-opacity' })
|
||||
).toEqual({
|
||||
'--text-opacity': '1',
|
||||
color: ['#ff0000', 'rgba(255, 0, 0, var(--text-opacity))'],
|
||||
})
|
||||
})
|
||||
|
||||
test('it ignores colors that cannot be parsed', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'currentColor',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'currentColor',
|
||||
})
|
||||
})
|
||||
|
||||
test('it ignores colors that already have an alpha channel', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#ff0000ff',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#ff0000ff',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#ff000080',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#ff000080',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#f00a',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#f00a',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#f00f',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#f00f',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255, 255, 255, 1)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255, 255, 255, 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255, 255, 255, 0.5)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsla(240, 100%, 50%, 1)',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsla(240, 100%, 50%, 1)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsla(240, 100%, 50%, 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsla(240, 100%, 50%, 0.5)',
|
||||
})
|
||||
})
|
||||
19
package.json
19
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tailwindcss",
|
||||
"version": "1.2.0",
|
||||
"version": "1.6.2",
|
||||
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
|
|
@ -25,12 +25,20 @@
|
|||
"style": "eslint .",
|
||||
"test": "jest && eslint ."
|
||||
},
|
||||
"files": [
|
||||
"dist/*.css",
|
||||
"lib/*",
|
||||
"scripts/*.js",
|
||||
"stubs/*.stub.js",
|
||||
"*.css",
|
||||
"*.js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/node": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"babel-jest": "^25.1.0",
|
||||
"babel-jest": "^26.0.0",
|
||||
"clean-css": "^4.1.9",
|
||||
"eslint": "^6.0.1",
|
||||
"eslint-config-postcss": "^2.0.2",
|
||||
|
|
@ -41,11 +49,14 @@
|
|||
"rimraf": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fullhuman/postcss-purgecss": "^2.1.2",
|
||||
"autoprefixer": "^9.4.5",
|
||||
"browserslist": "^4.12.0",
|
||||
"bytes": "^3.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"chalk": "^3.0.0 || ^4.0.0",
|
||||
"color": "^3.1.2",
|
||||
"detective": "^5.2.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"fs-extra": "^8.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"node-emoji": "^1.8.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
|
|
|||
|
|
@ -14,13 +14,9 @@ function buildDistFile(filename) {
|
|||
.process(css, {
|
||||
from: `./${filename}.css`,
|
||||
to: `./dist/${filename}.css`,
|
||||
map: { inline: false },
|
||||
})
|
||||
.then(result => {
|
||||
fs.writeFileSync(`./dist/${filename}.css`, result.css)
|
||||
if (result.map) {
|
||||
fs.writeFileSync(`./dist/${filename}.css.map`, result.map)
|
||||
}
|
||||
return result
|
||||
})
|
||||
.then(result => {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,34 @@ Promise.all([
|
|||
to: '__tests__/fixtures/tailwind-output-important.css',
|
||||
config: { important: true },
|
||||
}),
|
||||
build({
|
||||
from: '__tests__/fixtures/tailwind-input.css',
|
||||
to: '__tests__/fixtures/tailwind-output-ie11.css',
|
||||
config: { target: 'ie11' },
|
||||
}),
|
||||
build({
|
||||
from: '__tests__/fixtures/tailwind-input.css',
|
||||
to: '__tests__/fixtures/tailwind-output-no-color-opacity.css',
|
||||
config: {
|
||||
corePlugins: {
|
||||
textOpacity: false,
|
||||
backgroundOpacity: false,
|
||||
borderOpacity: false,
|
||||
placeholderOpacity: false,
|
||||
divideOpacity: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
build({
|
||||
from: '__tests__/fixtures/tailwind-input.css',
|
||||
to: '__tests__/fixtures/tailwind-output-flagged.css',
|
||||
config: {
|
||||
future: {},
|
||||
experimental: {
|
||||
uniformColorPalette: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]).then(() => {
|
||||
console.log('\nFinished rebuilding fixtures.')
|
||||
console.log(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import prettyHrtime from 'pretty-hrtime'
|
|||
|
||||
import tailwind from '../..'
|
||||
|
||||
import commands from '.'
|
||||
import compile from '../compile'
|
||||
import * as colors from '../colors'
|
||||
import * as emoji from '../emoji'
|
||||
|
|
@ -45,18 +44,6 @@ function stop(...msgs) {
|
|||
utils.die()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the error message and help for this command, then stops the process.
|
||||
*
|
||||
* @param {...string} [msgs]
|
||||
*/
|
||||
function stopWithHelp(...msgs) {
|
||||
utils.header()
|
||||
utils.error(...msgs)
|
||||
commands.help.forCommand(commands.build)
|
||||
utils.die()
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles CSS file and writes it to stdout.
|
||||
*
|
||||
|
|
@ -80,7 +67,12 @@ function buildToFile(compileOptions, startTime) {
|
|||
|
||||
utils.header()
|
||||
utils.log()
|
||||
utils.log(emoji.go, 'Building...', colors.file(inputFileSimplePath))
|
||||
utils.log(
|
||||
emoji.go,
|
||||
...(inputFileSimplePath
|
||||
? ['Building:', colors.file(inputFileSimplePath)]
|
||||
: ['Building from default CSS...', colors.info('(No input file provided)')])
|
||||
)
|
||||
|
||||
return compile(compileOptions).then(result => {
|
||||
utils.writeFile(compileOptions.outputFile, result.css)
|
||||
|
|
@ -112,8 +104,9 @@ export function run(cliParams, cliOptions) {
|
|||
const inputFileSimplePath = utils.getSimplePath(inputFile)
|
||||
const configFileSimplePath = utils.getSimplePath(configFile)
|
||||
|
||||
!inputFile && stopWithHelp('CSS file is required.')
|
||||
!utils.exists(inputFile) && stop(colors.file(inputFileSimplePath), 'does not exist.')
|
||||
if (inputFile) {
|
||||
!utils.exists(inputFile) && stop(colors.file(inputFileSimplePath), 'does not exist.')
|
||||
}
|
||||
|
||||
configFile &&
|
||||
!utils.exists(configFile) &&
|
||||
|
|
|
|||
|
|
@ -25,7 +25,14 @@ const defaultOptions = {
|
|||
*/
|
||||
export default function compile(options = {}) {
|
||||
const config = { ...defaultOptions, ...options }
|
||||
const css = utils.readFile(config.inputFile)
|
||||
|
||||
const css = config.inputFile
|
||||
? utils.readFile(config.inputFile)
|
||||
: `
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
postcss(config.plugins)
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ export const no = get('no_entry_sign')
|
|||
export const go = get('rocket')
|
||||
export const pack = get('package')
|
||||
export const disk = get('floppy_disk')
|
||||
export const warning = get('warning')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import preflight from './plugins/preflight'
|
||||
import container from './plugins/container'
|
||||
import space from './plugins/space'
|
||||
import divideWidth from './plugins/divideWidth'
|
||||
import divideColor from './plugins/divideColor'
|
||||
import accessibility from './plugins/accessibility'
|
||||
import appearance from './plugins/appearance'
|
||||
import backgroundAttachment from './plugins/backgroundAttachment'
|
||||
|
|
@ -43,6 +46,7 @@ import objectPosition from './plugins/objectPosition'
|
|||
import opacity from './plugins/opacity'
|
||||
import outline from './plugins/outline'
|
||||
import overflow from './plugins/overflow'
|
||||
import overscrollBehavior from './plugins/overscrollBehavior'
|
||||
import padding from './plugins/padding'
|
||||
import placeholderColor from './plugins/placeholderColor'
|
||||
import pointerEvents from './plugins/pointerEvents'
|
||||
|
|
@ -89,6 +93,12 @@ import transitionProperty from './plugins/transitionProperty'
|
|||
import transitionTimingFunction from './plugins/transitionTimingFunction'
|
||||
import transitionDuration from './plugins/transitionDuration'
|
||||
import transitionDelay from './plugins/transitionDelay'
|
||||
import divideOpacity from './plugins/divideOpacity'
|
||||
import backgroundOpacity from './plugins/backgroundOpacity'
|
||||
import borderOpacity from './plugins/borderOpacity'
|
||||
import textOpacity from './plugins/textOpacity'
|
||||
import placeholderOpacity from './plugins/placeholderOpacity'
|
||||
import animation from './plugins/animation'
|
||||
|
||||
import configurePlugins from './util/configurePlugins'
|
||||
|
||||
|
|
@ -96,15 +106,21 @@ export default function({ corePlugins: corePluginConfig }) {
|
|||
return configurePlugins(corePluginConfig, {
|
||||
preflight,
|
||||
container,
|
||||
space,
|
||||
divideWidth,
|
||||
divideColor,
|
||||
divideOpacity,
|
||||
accessibility,
|
||||
appearance,
|
||||
backgroundAttachment,
|
||||
backgroundColor,
|
||||
backgroundOpacity,
|
||||
backgroundPosition,
|
||||
backgroundRepeat,
|
||||
backgroundSize,
|
||||
borderCollapse,
|
||||
borderColor,
|
||||
borderOpacity,
|
||||
borderRadius,
|
||||
borderStyle,
|
||||
borderWidth,
|
||||
|
|
@ -126,6 +142,7 @@ export default function({ corePlugins: corePluginConfig }) {
|
|||
fontFamily,
|
||||
fontWeight,
|
||||
height,
|
||||
fontSize,
|
||||
lineHeight,
|
||||
listStylePosition,
|
||||
listStyleType,
|
||||
|
|
@ -139,8 +156,10 @@ export default function({ corePlugins: corePluginConfig }) {
|
|||
opacity,
|
||||
outline,
|
||||
overflow,
|
||||
overscrollBehavior,
|
||||
padding,
|
||||
placeholderColor,
|
||||
placeholderOpacity,
|
||||
pointerEvents,
|
||||
position,
|
||||
inset,
|
||||
|
|
@ -152,7 +171,7 @@ export default function({ corePlugins: corePluginConfig }) {
|
|||
tableLayout,
|
||||
textAlign,
|
||||
textColor,
|
||||
fontSize,
|
||||
textOpacity,
|
||||
fontStyle,
|
||||
textTransform,
|
||||
textDecoration,
|
||||
|
|
@ -185,5 +204,6 @@ export default function({ corePlugins: corePluginConfig }) {
|
|||
transitionTimingFunction,
|
||||
transitionDuration,
|
||||
transitionDelay,
|
||||
animation,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
132
src/flagged/uniformColorPalette.js
Normal file
132
src/flagged/uniformColorPalette.js
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
export default {
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
|
||||
black: '#000000',
|
||||
white: '#ffffff',
|
||||
|
||||
gray: {
|
||||
'50': '#f9fafb',
|
||||
'100': '#f4f5f7',
|
||||
'200': '#e5e7eb',
|
||||
'300': '#d2d6dc',
|
||||
'400': '#9fa6b2',
|
||||
'500': '#6b7280',
|
||||
'600': '#4b5563',
|
||||
'700': '#374151',
|
||||
'800': '#252f3f',
|
||||
'900': '#161e2e',
|
||||
},
|
||||
red: {
|
||||
'50': '#fdf2f2',
|
||||
'100': '#fde8e8',
|
||||
'200': '#fbd5d5',
|
||||
'300': '#f8b4b4',
|
||||
'400': '#f98080',
|
||||
'500': '#f05252',
|
||||
'600': '#e02424',
|
||||
'700': '#c81e1e',
|
||||
'800': '#9b1c1c',
|
||||
'900': '#771d1d',
|
||||
},
|
||||
orange: {
|
||||
'50': '#fff8f1',
|
||||
'100': '#feecdc',
|
||||
'200': '#fcd9bd',
|
||||
'300': '#fdba8c',
|
||||
'400': '#ff8a4c',
|
||||
'500': '#ff5a1f',
|
||||
'600': '#d03801',
|
||||
'700': '#b43403',
|
||||
'800': '#8a2c0d',
|
||||
'900': '#73230d',
|
||||
},
|
||||
yellow: {
|
||||
'50': '#fdfdea',
|
||||
'100': '#fdf6b2',
|
||||
'200': '#fce96a',
|
||||
'300': '#faca15',
|
||||
'400': '#e3a008',
|
||||
'500': '#c27803',
|
||||
'600': '#9f580a',
|
||||
'700': '#8e4b10',
|
||||
'800': '#723b13',
|
||||
'900': '#633112',
|
||||
},
|
||||
green: {
|
||||
'50': '#f3faf7',
|
||||
'100': '#def7ec',
|
||||
'200': '#bcf0da',
|
||||
'300': '#84e1bc',
|
||||
'400': '#31c48d',
|
||||
'500': '#0e9f6e',
|
||||
'600': '#057a55',
|
||||
'700': '#046c4e',
|
||||
'800': '#03543f',
|
||||
'900': '#014737',
|
||||
},
|
||||
teal: {
|
||||
'50': '#edfafa',
|
||||
'100': '#d5f5f6',
|
||||
'200': '#afecef',
|
||||
'300': '#7edce2',
|
||||
'400': '#16bdca',
|
||||
'500': '#0694a2',
|
||||
'600': '#047481',
|
||||
'700': '#036672',
|
||||
'800': '#05505c',
|
||||
'900': '#014451',
|
||||
},
|
||||
blue: {
|
||||
'50': '#ebf5ff',
|
||||
'100': '#e1effe',
|
||||
'200': '#c3ddfd',
|
||||
'300': '#a4cafe',
|
||||
'400': '#76a9fa',
|
||||
'500': '#3f83f8',
|
||||
'600': '#1c64f2',
|
||||
'700': '#1a56db',
|
||||
'800': '#1e429f',
|
||||
'900': '#233876',
|
||||
},
|
||||
indigo: {
|
||||
'50': '#f0f5ff',
|
||||
'100': '#e5edff',
|
||||
'200': '#cddbfe',
|
||||
'300': '#b4c6fc',
|
||||
'400': '#8da2fb',
|
||||
'500': '#6875f5',
|
||||
'600': '#5850ec',
|
||||
'700': '#5145cd',
|
||||
'800': '#42389d',
|
||||
'900': '#362f78',
|
||||
},
|
||||
purple: {
|
||||
'50': '#f6f5ff',
|
||||
'100': '#edebfe',
|
||||
'200': '#dcd7fe',
|
||||
'300': '#cabffd',
|
||||
'400': '#ac94fa',
|
||||
'500': '#9061f9',
|
||||
'600': '#7e3af2',
|
||||
'700': '#6c2bd9',
|
||||
'800': '#5521b5',
|
||||
'900': '#4a1d96',
|
||||
},
|
||||
pink: {
|
||||
'50': '#fdf2f8',
|
||||
'100': '#fce8f3',
|
||||
'200': '#fad1e8',
|
||||
'300': '#f8b4d9',
|
||||
'400': '#f17eb8',
|
||||
'500': '#e74694',
|
||||
'600': '#d61f69',
|
||||
'700': '#bf125d',
|
||||
'800': '#99154b',
|
||||
'900': '#751a3d',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
28
src/index.js
28
src/index.js
|
|
@ -10,9 +10,20 @@ import processTailwindFeatures from './processTailwindFeatures'
|
|||
import formatCSS from './lib/formatCSS'
|
||||
import resolveConfig from './util/resolveConfig'
|
||||
import { defaultConfigFile } from './constants'
|
||||
|
||||
import defaultConfig from '../stubs/defaultConfig.stub.js'
|
||||
|
||||
import uniformColorPalette from './flagged/uniformColorPalette.js'
|
||||
|
||||
function getDefaultConfigs(config) {
|
||||
const configs = [defaultConfig]
|
||||
|
||||
if (_.get(config, ['experimental', 'uniformColorPalette'], false)) {
|
||||
configs.unshift(uniformColorPalette)
|
||||
}
|
||||
|
||||
return configs
|
||||
}
|
||||
|
||||
function resolveConfigPath(filePath) {
|
||||
// require('tailwindcss')({ theme: ..., variants: ... })
|
||||
if (_.isObject(filePath) && !_.has(filePath, 'config') && !_.isEmpty(filePath)) {
|
||||
|
|
@ -46,18 +57,21 @@ function resolveConfigPath(filePath) {
|
|||
|
||||
const getConfigFunction = config => () => {
|
||||
if (_.isUndefined(config) && !_.isObject(config)) {
|
||||
return resolveConfig([defaultConfig])
|
||||
return resolveConfig([...getDefaultConfigs(defaultConfig)])
|
||||
}
|
||||
|
||||
if (!_.isObject(config)) {
|
||||
getModuleDependencies(config).forEach(mdl => {
|
||||
delete require.cache[require.resolve(mdl.file)]
|
||||
})
|
||||
// Skip this if Jest is running: https://github.com/facebook/jest/pull/9841#issuecomment-621417584
|
||||
if (process.env.JEST_WORKER_ID === undefined) {
|
||||
if (!_.isObject(config)) {
|
||||
getModuleDependencies(config).forEach(mdl => {
|
||||
delete require.cache[require.resolve(mdl.file)]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const configObject = _.isObject(config) ? _.get(config, 'config', config) : require(config)
|
||||
|
||||
return resolveConfig([configObject, defaultConfig])
|
||||
return resolveConfig([configObject, ...getDefaultConfigs(configObject)])
|
||||
}
|
||||
|
||||
const plugin = postcss.plugin('tailwind', config => {
|
||||
|
|
|
|||
13
src/lib/convertLayerAtRulesToControlComments.js
Normal file
13
src/lib/convertLayerAtRulesToControlComments.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import postcss from 'postcss'
|
||||
|
||||
export default function convertLayerAtRulesToControlComments() {
|
||||
return function(css) {
|
||||
css.walkAtRules('layer', atRule => {
|
||||
const layer = atRule.params
|
||||
atRule.before(postcss.comment({ text: `tailwind start ${layer}` }))
|
||||
atRule.before(atRule.nodes)
|
||||
atRule.before(postcss.comment({ text: `tailwind end ${layer}` }))
|
||||
atRule.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
91
src/lib/purgeUnusedStyles.js
Normal file
91
src/lib/purgeUnusedStyles.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import _ from 'lodash'
|
||||
import postcss from 'postcss'
|
||||
import purgecss from '@fullhuman/postcss-purgecss'
|
||||
import chalk from 'chalk'
|
||||
import { log } from '../cli/utils'
|
||||
import * as emoji from '../cli/emoji'
|
||||
|
||||
function removeTailwindComments(css) {
|
||||
css.walkComments(comment => {
|
||||
switch (comment.text.trim()) {
|
||||
case 'tailwind start components':
|
||||
case 'tailwind start utilities':
|
||||
case 'tailwind end components':
|
||||
case 'tailwind end utilities':
|
||||
comment.remove()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default function purgeUnusedUtilities(config) {
|
||||
const purgeEnabled = _.get(
|
||||
config,
|
||||
'purge.enabled',
|
||||
config.purge !== false && config.purge !== undefined && process.env.NODE_ENV === 'production'
|
||||
)
|
||||
|
||||
if (!purgeEnabled) {
|
||||
return removeTailwindComments
|
||||
}
|
||||
|
||||
// Skip if `purge: []` since that's part of the default config
|
||||
if (Array.isArray(config.purge) && config.purge.length === 0) {
|
||||
log()
|
||||
log(
|
||||
emoji.warning,
|
||||
chalk.yellow(
|
||||
' Tailwind is not purging unused styles because no template paths have been provided.'
|
||||
)
|
||||
)
|
||||
log(
|
||||
chalk.white(
|
||||
' If you have manually configured PurgeCSS outside of Tailwind or are deliberately not\n removing unused styles, set `purge: false` in your Tailwind config file to silence\n this warning.'
|
||||
)
|
||||
)
|
||||
log(
|
||||
chalk.white('\n https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css')
|
||||
)
|
||||
return removeTailwindComments
|
||||
}
|
||||
|
||||
return postcss([
|
||||
function(css) {
|
||||
const mode = _.get(config, 'purge.mode', 'conservative')
|
||||
|
||||
if (mode === 'conservative') {
|
||||
css.prepend(postcss.comment({ text: 'purgecss start ignore' }))
|
||||
css.append(postcss.comment({ text: 'purgecss end ignore' }))
|
||||
|
||||
css.walkComments(comment => {
|
||||
switch (comment.text.trim()) {
|
||||
case 'tailwind start utilities':
|
||||
comment.text = 'purgecss end ignore'
|
||||
break
|
||||
case 'tailwind end utilities':
|
||||
comment.text = 'purgecss start ignore'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
removeTailwindComments,
|
||||
purgecss({
|
||||
content: Array.isArray(config.purge) ? config.purge : config.purge.content,
|
||||
defaultExtractor: content => {
|
||||
// Capture as liberally as possible, including things like `h-(screen-1.5)`
|
||||
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []
|
||||
|
||||
// Capture classes within other delimiters like .block(class="w-1/2") in Pug
|
||||
const innerMatches = content.match(/[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g) || []
|
||||
|
||||
return broadMatches.concat(innerMatches)
|
||||
},
|
||||
...config.purge.options,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
|
@ -4,8 +4,51 @@ import cloneNodes from '../util/cloneNodes'
|
|||
import buildMediaQuery from '../util/buildMediaQuery'
|
||||
import buildSelectorVariant from '../util/buildSelectorVariant'
|
||||
|
||||
function isLayer(node) {
|
||||
if (Array.isArray(node)) {
|
||||
return node.length === 1 && isLayer(node[0])
|
||||
}
|
||||
return node.type === 'atrule' && node.name === 'layer'
|
||||
}
|
||||
|
||||
function layerNodes(nodes) {
|
||||
return isLayer(nodes) ? nodes[0].nodes : nodes
|
||||
}
|
||||
|
||||
export default function(config) {
|
||||
return function(css) {
|
||||
// Wrap any `responsive` rules with a copy of their parent `layer` to
|
||||
// ensure the layer isn't lost when copying to the `screens` location.
|
||||
css.walkAtRules('layer', layerAtRule => {
|
||||
const layer = layerAtRule.params
|
||||
layerAtRule.walkAtRules('responsive', responsiveAtRule => {
|
||||
const nestedlayerAtRule = postcss.atRule({
|
||||
name: 'layer',
|
||||
params: layer,
|
||||
})
|
||||
nestedlayerAtRule.prepend(responsiveAtRule.nodes)
|
||||
responsiveAtRule.removeAll()
|
||||
responsiveAtRule.prepend(nestedlayerAtRule)
|
||||
})
|
||||
})
|
||||
|
||||
// Find any `responsive` rules with no parent `layer` (these only come
|
||||
// from the user's CSS, Tailwind never generates things this way itself)
|
||||
// and wrap them with a default `layer` of `utilities`.
|
||||
css.walkAtRules('responsive', responsiveAtRule => {
|
||||
const [node] = responsiveAtRule.nodes
|
||||
if (node.type === 'atrule' && node.name === 'layer') {
|
||||
return
|
||||
}
|
||||
const nestedlayerAtRule = postcss.atRule({
|
||||
name: 'layer',
|
||||
params: 'utilities',
|
||||
})
|
||||
nestedlayerAtRule.prepend(responsiveAtRule.nodes)
|
||||
responsiveAtRule.removeAll()
|
||||
responsiveAtRule.prepend(nestedlayerAtRule)
|
||||
})
|
||||
|
||||
const {
|
||||
theme: { screens },
|
||||
separator,
|
||||
|
|
@ -16,7 +59,16 @@ export default function(config) {
|
|||
css.walkAtRules('responsive', atRule => {
|
||||
const nodes = atRule.nodes
|
||||
responsiveRules.append(...cloneNodes(nodes))
|
||||
atRule.before(nodes)
|
||||
|
||||
// If the parent is already a `layer` (this is true for anything coming from
|
||||
// a plugin, including core plugins) we don't want to create a double nested
|
||||
// layer, so only insert the layer children. If there is no parent layer,
|
||||
// preserve the layer information when inserting the nodes.
|
||||
if (isLayer(atRule.parent)) {
|
||||
atRule.before(layerNodes(nodes))
|
||||
} else {
|
||||
atRule.before(nodes)
|
||||
}
|
||||
atRule.remove()
|
||||
})
|
||||
|
||||
|
|
@ -43,22 +95,16 @@ export default function(config) {
|
|||
|
||||
const hasScreenRules = finalRules.some(i => i.nodes.length !== 0)
|
||||
|
||||
if (!hasScreenRules) {
|
||||
return
|
||||
}
|
||||
|
||||
let includesScreensExplicitly = false
|
||||
|
||||
css.walkAtRules('tailwind', atRule => {
|
||||
if (atRule.params === 'screens') {
|
||||
atRule.replaceWith(finalRules)
|
||||
includesScreensExplicitly = true
|
||||
if (atRule.params !== 'screens') {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
if (!includesScreensExplicitly) {
|
||||
css.append(finalRules)
|
||||
return
|
||||
}
|
||||
if (hasScreenRules) {
|
||||
atRule.before(finalRules)
|
||||
}
|
||||
|
||||
atRule.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ export default function(
|
|||
}
|
||||
})
|
||||
|
||||
let includesScreensExplicitly = false
|
||||
|
||||
css.walkAtRules('tailwind', atRule => {
|
||||
if (atRule.params === 'preflight') {
|
||||
// prettier-ignore
|
||||
|
|
@ -60,6 +62,14 @@ export default function(
|
|||
atRule.before(updateSource(pluginUtilities, atRule.source))
|
||||
atRule.remove()
|
||||
}
|
||||
|
||||
if (atRule.params === 'screens') {
|
||||
includesScreensExplicitly = true
|
||||
}
|
||||
})
|
||||
|
||||
if (!includesScreensExplicitly) {
|
||||
css.append([postcss.atRule({ name: 'tailwind', params: 'screens' })])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,33 @@ function ensureIncludesDefault(variants) {
|
|||
|
||||
const defaultVariantGenerators = config => ({
|
||||
default: generateVariantFunction(() => {}),
|
||||
'motion-safe': generateVariantFunction(({ container, separator, modifySelectors }) => {
|
||||
const modified = modifySelectors(({ selector }) => {
|
||||
return selectorParser(selectors => {
|
||||
selectors.walkClasses(sel => {
|
||||
sel.value = `motion-safe${separator}${sel.value}`
|
||||
})
|
||||
}).processSync(selector)
|
||||
})
|
||||
const mediaQuery = postcss.atRule({
|
||||
name: 'media',
|
||||
params: '(prefers-reduced-motion: no-preference)',
|
||||
})
|
||||
mediaQuery.append(modified)
|
||||
container.append(mediaQuery)
|
||||
}),
|
||||
'motion-reduce': generateVariantFunction(({ container, separator, modifySelectors }) => {
|
||||
const modified = modifySelectors(({ selector }) => {
|
||||
return selectorParser(selectors => {
|
||||
selectors.walkClasses(sel => {
|
||||
sel.value = `motion-reduce${separator}${sel.value}`
|
||||
})
|
||||
}).processSync(selector)
|
||||
})
|
||||
const mediaQuery = postcss.atRule({ name: 'media', params: '(prefers-reduced-motion: reduce)' })
|
||||
mediaQuery.append(modified)
|
||||
container.append(mediaQuery)
|
||||
}),
|
||||
'group-hover': generateVariantFunction(({ modifySelectors, separator }) => {
|
||||
return modifySelectors(({ selector }) => {
|
||||
return selectorParser(selectors => {
|
||||
|
|
@ -36,18 +63,55 @@ const defaultVariantGenerators = config => ({
|
|||
}).processSync(selector)
|
||||
})
|
||||
}),
|
||||
'group-focus': generateVariantFunction(({ modifySelectors, separator }) => {
|
||||
return modifySelectors(({ selector }) => {
|
||||
return selectorParser(selectors => {
|
||||
selectors.walkClasses(sel => {
|
||||
sel.value = `group-focus${separator}${sel.value}`
|
||||
sel.parent.insertBefore(
|
||||
sel,
|
||||
selectorParser().astSync(prefixSelector(config.prefix, '.group:focus '))
|
||||
)
|
||||
})
|
||||
}).processSync(selector)
|
||||
})
|
||||
}),
|
||||
hover: generatePseudoClassVariant('hover'),
|
||||
'focus-within': generatePseudoClassVariant('focus-within'),
|
||||
'focus-visible': generatePseudoClassVariant('focus-visible'),
|
||||
focus: generatePseudoClassVariant('focus'),
|
||||
active: generatePseudoClassVariant('active'),
|
||||
visited: generatePseudoClassVariant('visited'),
|
||||
disabled: generatePseudoClassVariant('disabled'),
|
||||
checked: generatePseudoClassVariant('checked'),
|
||||
first: generatePseudoClassVariant('first-child', 'first'),
|
||||
last: generatePseudoClassVariant('last-child', 'last'),
|
||||
odd: generatePseudoClassVariant('nth-child(odd)', 'odd'),
|
||||
even: generatePseudoClassVariant('nth-child(even)', 'even'),
|
||||
})
|
||||
|
||||
function prependStackableVariants(atRule, variants) {
|
||||
const stackableVariants = ['motion-safe', 'motion-reduce']
|
||||
|
||||
if (!_.some(variants, v => stackableVariants.includes(v))) {
|
||||
return variants
|
||||
}
|
||||
|
||||
if (_.every(variants, v => stackableVariants.includes(v))) {
|
||||
return variants
|
||||
}
|
||||
|
||||
const variantsParent = postcss.atRule({
|
||||
name: 'variants',
|
||||
params: variants.filter(v => stackableVariants.includes(v)).join(', '),
|
||||
})
|
||||
atRule.before(variantsParent)
|
||||
variantsParent.append(atRule)
|
||||
variants = _.without(variants, ...stackableVariants)
|
||||
|
||||
return variants
|
||||
}
|
||||
|
||||
export default function(config, { variantGenerators: pluginVariantGenerators }) {
|
||||
return function(css) {
|
||||
const variantGenerators = {
|
||||
|
|
@ -55,20 +119,34 @@ export default function(config, { variantGenerators: pluginVariantGenerators })
|
|||
...pluginVariantGenerators,
|
||||
}
|
||||
|
||||
css.walkAtRules('variants', atRule => {
|
||||
const variants = postcss.list.comma(atRule.params).filter(variant => variant !== '')
|
||||
let variantsFound = false
|
||||
|
||||
if (variants.includes('responsive')) {
|
||||
const responsiveParent = postcss.atRule({ name: 'responsive' })
|
||||
atRule.before(responsiveParent)
|
||||
responsiveParent.append(atRule)
|
||||
}
|
||||
do {
|
||||
variantsFound = false
|
||||
css.walkAtRules('variants', atRule => {
|
||||
variantsFound = true
|
||||
|
||||
_.forEach(_.without(ensureIncludesDefault(variants), 'responsive'), variant => {
|
||||
variantGenerators[variant](atRule, config)
|
||||
let variants = postcss.list.comma(atRule.params).filter(variant => variant !== '')
|
||||
|
||||
if (variants.includes('responsive')) {
|
||||
const responsiveParent = postcss.atRule({ name: 'responsive' })
|
||||
atRule.before(responsiveParent)
|
||||
responsiveParent.append(atRule)
|
||||
}
|
||||
|
||||
const remainingVariants = prependStackableVariants(atRule, variants)
|
||||
|
||||
_.forEach(_.without(ensureIncludesDefault(remainingVariants), 'responsive'), variant => {
|
||||
if (!variantGenerators[variant]) {
|
||||
throw new Error(
|
||||
`Your config mentions the "${variant}" variant, but "${variant}" doesn't appear to be a variant. Did you forget or misconfigure a plugin that supplies that variant?`
|
||||
)
|
||||
}
|
||||
variantGenerators[variant](atRule, config)
|
||||
})
|
||||
|
||||
atRule.remove()
|
||||
})
|
||||
|
||||
atRule.remove()
|
||||
})
|
||||
} while (variantsFound)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
src/plugins/animation.js
Normal file
26
src/plugins/animation.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
const keyframesConfig = theme('keyframes')
|
||||
const keyframesStyles = _.fromPairs(
|
||||
_.toPairs(keyframesConfig).map(([name, keyframes]) => {
|
||||
return [`@keyframes ${name}`, keyframes]
|
||||
})
|
||||
)
|
||||
addUtilities(keyframesStyles, { respectImportant: false })
|
||||
|
||||
const animationConfig = theme('animation')
|
||||
const utilities = _.fromPairs(
|
||||
_.toPairs(animationConfig).map(([suffix, animation]) => {
|
||||
return [
|
||||
`.${e(`animate-${suffix}`)}`,
|
||||
{
|
||||
animation,
|
||||
},
|
||||
]
|
||||
})
|
||||
)
|
||||
addUtilities(utilities, variants('animation'))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,30 @@
|
|||
import _ from 'lodash'
|
||||
import flattenColorPalette from '../util/flattenColorPalette'
|
||||
import withAlphaVariable from '../util/withAlphaVariable'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
return function({ addUtilities, e, theme, variants, target, corePlugins }) {
|
||||
const colors = flattenColorPalette(theme('backgroundColor'))
|
||||
|
||||
const getProperties = value => {
|
||||
if (target('backgroundColor') === 'ie11') {
|
||||
return { 'background-color': value }
|
||||
}
|
||||
|
||||
if (corePlugins('backgroundOpacity')) {
|
||||
return withAlphaVariable({
|
||||
color: value,
|
||||
property: 'background-color',
|
||||
variable: '--bg-opacity',
|
||||
})
|
||||
}
|
||||
|
||||
return { 'background-color': value }
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(flattenColorPalette(theme('backgroundColor')), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`bg-${modifier}`)}`,
|
||||
{
|
||||
'background-color': value,
|
||||
},
|
||||
]
|
||||
_.map(colors, (value, modifier) => {
|
||||
return [`.${e(`bg-${modifier}`)}`, getProperties(value)]
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
14
src/plugins/backgroundOpacity.js
Normal file
14
src/plugins/backgroundOpacity.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return function({ target, ...args }) {
|
||||
if (target('backgroundOpacity') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('backgroundOpacity', [['bg-opacity', ['--bg-opacity']]])({
|
||||
target,
|
||||
...args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,30 @@
|
|||
import _ from 'lodash'
|
||||
import flattenColorPalette from '../util/flattenColorPalette'
|
||||
import withAlphaVariable from '../util/withAlphaVariable'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
return function({ addUtilities, e, theme, variants, target, corePlugins }) {
|
||||
const colors = flattenColorPalette(theme('borderColor'))
|
||||
|
||||
const getProperties = value => {
|
||||
if (target('borderColor') === 'ie11') {
|
||||
return { 'border-color': value }
|
||||
}
|
||||
|
||||
if (corePlugins('borderOpacity')) {
|
||||
return withAlphaVariable({
|
||||
color: value,
|
||||
property: 'border-color',
|
||||
variable: '--border-opacity',
|
||||
})
|
||||
}
|
||||
|
||||
return { 'border-color': value }
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(_.omit(colors, 'default'), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`border-${modifier}`)}`,
|
||||
{
|
||||
'border-color': value,
|
||||
},
|
||||
]
|
||||
return [`.${e(`border-${modifier}`)}`, getProperties(value)]
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
14
src/plugins/borderOpacity.js
Normal file
14
src/plugins/borderOpacity.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return function({ target, ...args }) {
|
||||
if (target('borderOpacity') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('borderOpacity', [['border-opacity', ['--border-opacity']]])({
|
||||
target,
|
||||
...args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -22,9 +22,64 @@ function extractMinWidths(breakpoints) {
|
|||
})
|
||||
}
|
||||
|
||||
function mapMinWidthsToPadding(minWidths, screens, paddings) {
|
||||
if (typeof paddings === 'undefined') {
|
||||
return []
|
||||
}
|
||||
|
||||
if (!_.isObject(paddings)) {
|
||||
return [
|
||||
{
|
||||
screen: 'default',
|
||||
minWidth: 0,
|
||||
padding: paddings,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const mapping = []
|
||||
|
||||
if (paddings.default) {
|
||||
mapping.push({
|
||||
screen: 'default',
|
||||
minWidth: 0,
|
||||
padding: paddings.default,
|
||||
})
|
||||
}
|
||||
|
||||
_.each(minWidths, minWidth => {
|
||||
Object.keys(screens).forEach(screen => {
|
||||
if (`${screens[screen]}` === `${minWidth}`) {
|
||||
mapping.push({
|
||||
screen,
|
||||
minWidth,
|
||||
padding: paddings[screen],
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return mapping
|
||||
}
|
||||
|
||||
module.exports = function() {
|
||||
return function({ addComponents, theme }) {
|
||||
const minWidths = extractMinWidths(theme('container.screens', theme('screens')))
|
||||
return function({ addComponents, theme, variants }) {
|
||||
const screens = theme('container.screens', theme('screens'))
|
||||
const minWidths = extractMinWidths(screens)
|
||||
const paddings = mapMinWidthsToPadding(minWidths, screens, theme('container.padding'))
|
||||
|
||||
const generatePaddingFor = minWidth => {
|
||||
const paddingConfig = _.find(paddings, padding => `${padding.minWidth}` === `${minWidth}`)
|
||||
|
||||
if (!paddingConfig) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
paddingRight: paddingConfig.padding,
|
||||
paddingLeft: paddingConfig.padding,
|
||||
}
|
||||
}
|
||||
|
||||
const atRules = _(minWidths)
|
||||
.sortBy(minWidth => parseInt(minWidth))
|
||||
|
|
@ -34,26 +89,25 @@ module.exports = function() {
|
|||
[`@media (min-width: ${minWidth})`]: {
|
||||
'.container': {
|
||||
'max-width': minWidth,
|
||||
...generatePaddingFor(minWidth),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
.value()
|
||||
|
||||
addComponents([
|
||||
{
|
||||
'.container': Object.assign(
|
||||
{ width: '100%' },
|
||||
theme('container.center', false) ? { marginRight: 'auto', marginLeft: 'auto' } : {},
|
||||
_.has(theme('container', {}), 'padding')
|
||||
? {
|
||||
paddingRight: theme('container.padding'),
|
||||
paddingLeft: theme('container.padding'),
|
||||
}
|
||||
: {}
|
||||
),
|
||||
},
|
||||
...atRules,
|
||||
])
|
||||
addComponents(
|
||||
[
|
||||
{
|
||||
'.container': Object.assign(
|
||||
{ width: '100%' },
|
||||
theme('container.center', false) ? { marginRight: 'auto', marginLeft: 'auto' } : {},
|
||||
generatePaddingFor(0)
|
||||
),
|
||||
},
|
||||
...atRules,
|
||||
],
|
||||
variants('container')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ pre {
|
|||
button {
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export default function() {
|
||||
return function({ addUtilities, variants }) {
|
||||
return function({ addUtilities, variants, target }) {
|
||||
addUtilities(
|
||||
{
|
||||
'.block': {
|
||||
|
|
@ -11,21 +11,12 @@ export default function() {
|
|||
'.inline': {
|
||||
display: 'inline',
|
||||
},
|
||||
'.flow-root': {
|
||||
display: 'flow-root',
|
||||
},
|
||||
'.flex': {
|
||||
display: 'flex',
|
||||
},
|
||||
'.inline-flex': {
|
||||
display: 'inline-flex',
|
||||
},
|
||||
'.grid': {
|
||||
display: 'grid',
|
||||
},
|
||||
'.inline-grid': {
|
||||
display: 'inline-grid',
|
||||
},
|
||||
'.table': {
|
||||
display: 'table',
|
||||
},
|
||||
|
|
@ -53,6 +44,19 @@ export default function() {
|
|||
'.table-row': {
|
||||
display: 'table-row',
|
||||
},
|
||||
...(target('display') === 'ie11'
|
||||
? {}
|
||||
: {
|
||||
'.flow-root': {
|
||||
display: 'flow-root',
|
||||
},
|
||||
'.grid': {
|
||||
display: 'grid',
|
||||
},
|
||||
'.inline-grid': {
|
||||
display: 'inline-grid',
|
||||
},
|
||||
}),
|
||||
'.hidden': {
|
||||
display: 'none',
|
||||
},
|
||||
|
|
|
|||
36
src/plugins/divideColor.js
Normal file
36
src/plugins/divideColor.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import _ from 'lodash'
|
||||
import flattenColorPalette from '../util/flattenColorPalette'
|
||||
import withAlphaVariable from '../util/withAlphaVariable'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants, target, corePlugins }) {
|
||||
const colors = flattenColorPalette(theme('divideColor'))
|
||||
|
||||
const getProperties = value => {
|
||||
if (target('divideColor') === 'ie11') {
|
||||
return { 'border-color': value }
|
||||
}
|
||||
|
||||
if (corePlugins('divideOpacity')) {
|
||||
return withAlphaVariable({
|
||||
color: value,
|
||||
property: 'border-color',
|
||||
variable: '--divide-opacity',
|
||||
})
|
||||
}
|
||||
|
||||
return { 'border-color': value }
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(_.omit(colors, 'default'), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`divide-${modifier}`)} > :not(template) ~ :not(template)`,
|
||||
getProperties(value),
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
addUtilities(utilities, variants('divideColor'))
|
||||
}
|
||||
}
|
||||
22
src/plugins/divideOpacity.js
Normal file
22
src/plugins/divideOpacity.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants, target }) {
|
||||
if (target('divideOpacity') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(theme('divideOpacity'), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`divide-opacity-${modifier}`)} > :not(template) ~ :not(template)`,
|
||||
{
|
||||
'--divide-opacity': value,
|
||||
},
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
addUtilities(utilities, variants('divideOpacity'))
|
||||
}
|
||||
}
|
||||
65
src/plugins/divideWidth.js
Normal file
65
src/plugins/divideWidth.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants, target }) {
|
||||
if (target('divideWidth') === 'ie11') {
|
||||
const generators = [
|
||||
(size, modifier) => ({
|
||||
[`.${e(`divide-y${modifier}`)} > :not(template) ~ :not(template)`]: {
|
||||
'border-top-width': size,
|
||||
},
|
||||
[`.${e(`divide-x${modifier}`)} > :not(template) ~ :not(template)`]: {
|
||||
'border-left-width': size,
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
const utilities = _.flatMap(generators, generator => {
|
||||
return _.flatMap(theme('divideWidth'), (value, modifier) => {
|
||||
return generator(value, modifier === 'default' ? '' : `-${modifier}`)
|
||||
})
|
||||
})
|
||||
|
||||
addUtilities(utilities, variants('divideWidth'))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const generators = [
|
||||
(size, modifier) => ({
|
||||
[`.${e(`divide-y${modifier}`)} > :not(template) ~ :not(template)`]: {
|
||||
'--divide-y-reverse': '0',
|
||||
'border-top-width': `calc(${
|
||||
size === '0' ? '0px' : size
|
||||
} * calc(1 - var(--divide-y-reverse)))`,
|
||||
'border-bottom-width': `calc(${size === '0' ? '0px' : size} * var(--divide-y-reverse))`,
|
||||
},
|
||||
[`.${e(`divide-x${modifier}`)} > :not(template) ~ :not(template)`]: {
|
||||
'--divide-x-reverse': '0',
|
||||
'border-right-width': `calc(${size === '0' ? '0px' : size} * var(--divide-x-reverse))`,
|
||||
'border-left-width': `calc(${
|
||||
size === '0' ? '0px' : size
|
||||
} * calc(1 - var(--divide-x-reverse)))`,
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
const utilities = _.flatMap(generators, generator => {
|
||||
return [
|
||||
..._.flatMap(theme('divideWidth'), (value, modifier) => {
|
||||
return generator(value, modifier === 'default' ? '' : `-${modifier}`)
|
||||
}),
|
||||
{
|
||||
'.divide-y-reverse > :not(template) ~ :not(template)': {
|
||||
'--divide-y-reverse': '1',
|
||||
},
|
||||
'.divide-x-reverse > :not(template) ~ :not(template)': {
|
||||
'--divide-x-reverse': '1',
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
addUtilities(utilities, variants('divideWidth'))
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,11 @@ import flattenColorPalette from '../util/flattenColorPalette'
|
|||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
const colors = flattenColorPalette(theme('fill'))
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(flattenColorPalette(theme('fill')), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`fill-${modifier}`)}`,
|
||||
{
|
||||
fill: value,
|
||||
},
|
||||
]
|
||||
_.map(colors, (value, modifier) => {
|
||||
return [`.${e(`fill-${modifier}`)}`, { fill: value }]
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,27 @@ export default function() {
|
|||
return function({ addUtilities, e, theme, variants }) {
|
||||
const utilities = _.fromPairs(
|
||||
_.map(theme('fontSize'), (value, modifier) => {
|
||||
const [fontSize, options] = Array.isArray(value) ? value : [value]
|
||||
const { lineHeight, letterSpacing } = _.isPlainObject(options)
|
||||
? options
|
||||
: {
|
||||
lineHeight: options,
|
||||
}
|
||||
|
||||
return [
|
||||
`.${e(`text-${modifier}`)}`,
|
||||
{
|
||||
'font-size': value,
|
||||
'font-size': fontSize,
|
||||
...(lineHeight === undefined
|
||||
? {}
|
||||
: {
|
||||
'line-height': lineHeight,
|
||||
}),
|
||||
...(letterSpacing === undefined
|
||||
? {}
|
||||
: {
|
||||
'letter-spacing': letterSpacing,
|
||||
}),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gap', [
|
||||
['gap', ['gridGap', 'gap']],
|
||||
['col-gap', ['gridColumnGap', 'columnGap']],
|
||||
['row-gap', ['gridRowGap', 'rowGap']],
|
||||
])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gap') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gap', [
|
||||
['gap', ['gridGap', 'gap']],
|
||||
['col-gap', ['gridColumnGap', 'columnGap']],
|
||||
['row-gap', ['gridRowGap', 'rowGap']],
|
||||
])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
export default function() {
|
||||
return function({ addUtilities, variants }) {
|
||||
return function({ addUtilities, variants, target }) {
|
||||
if (target('gridAutoFlow') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
addUtilities(
|
||||
{
|
||||
'.grid-flow-row': { gridAutoFlow: 'row' },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridColumn', [['col', ['gridColumn']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridColumn') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridColumn', [['col', ['gridColumn']]])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridColumnEnd', [['col-end', ['gridColumnEnd']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridColumnEnd') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridColumnEnd', [['col-end', ['gridColumnEnd']]])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridColumnStart', [['col-start', ['gridColumnStart']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridColumnStart') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridColumnStart', [['col-start', ['gridColumnStart']]])({
|
||||
target,
|
||||
...args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridRow', [['row', ['gridRow']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridRow') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridRow', [['row', ['gridRow']]])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridRowEnd', [['row-end', ['gridRowEnd']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridRowEnd') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridRowEnd', [['row-end', ['gridRowEnd']]])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridRowStart', [['row-start', ['gridRowStart']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridRowStart') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridRowStart', [['row-start', ['gridRowStart']]])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridTemplateColumns', [['grid-cols', ['gridTemplateColumns']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridTemplateColumns') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridTemplateColumns', [['grid-cols', ['gridTemplateColumns']]])({
|
||||
target,
|
||||
...args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('gridTemplateRows', [['grid-rows', ['gridTemplateRows']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('gridTemplateRows') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('gridTemplateRows', [['grid-rows', ['gridTemplateRows']]])({
|
||||
target,
|
||||
...args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
export default function() {
|
||||
return function({ addUtilities, variants }) {
|
||||
return function({ addUtilities, variants, target }) {
|
||||
if (target('objectFit') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
addUtilities(
|
||||
{
|
||||
'.object-contain': { 'object-fit': 'contain' },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
return function({ addUtilities, e, theme, variants, target }) {
|
||||
if (target('objectPosition') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(theme('objectPosition'), (value, modifier) => {
|
||||
return [
|
||||
|
|
|
|||
22
src/plugins/overscrollBehavior.js
Normal file
22
src/plugins/overscrollBehavior.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export default function() {
|
||||
return function({ addUtilities, variants, target }) {
|
||||
if (target('overscrollBehavior') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
addUtilities(
|
||||
{
|
||||
'.overscroll-auto': { 'overscroll-behavior': 'auto' },
|
||||
'.overscroll-contain': { 'overscroll-behavior': 'contain' },
|
||||
'.overscroll-none': { 'overscroll-behavior': 'none' },
|
||||
'.overscroll-y-auto': { 'overscroll-behavior-y': 'auto' },
|
||||
'.overscroll-y-contain': { 'overscroll-behavior-y': 'contain' },
|
||||
'.overscroll-y-none': { 'overscroll-behavior-y': 'none' },
|
||||
'.overscroll-x-auto': { 'overscroll-behavior-x': 'auto' },
|
||||
'.overscroll-x-contain': { 'overscroll-behavior-x': 'contain' },
|
||||
'.overscroll-x-none': { 'overscroll-behavior-x': 'none' },
|
||||
},
|
||||
variants('overscrollBehavior')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,30 @@
|
|||
import _ from 'lodash'
|
||||
import flattenColorPalette from '../util/flattenColorPalette'
|
||||
import withAlphaVariable from '../util/withAlphaVariable'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
return function({ addUtilities, e, theme, variants, target, corePlugins }) {
|
||||
const colors = flattenColorPalette(theme('placeholderColor'))
|
||||
|
||||
const getProperties = value => {
|
||||
if (target('placeholderColor') === 'ie11') {
|
||||
return { color: value }
|
||||
}
|
||||
|
||||
if (corePlugins('placeholderOpacity')) {
|
||||
return withAlphaVariable({
|
||||
color: value,
|
||||
property: 'color',
|
||||
variable: '--placeholder-opacity',
|
||||
})
|
||||
}
|
||||
|
||||
return { color: value }
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(flattenColorPalette(theme('placeholderColor')), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`placeholder-${modifier}`)}::placeholder`,
|
||||
{
|
||||
color: value,
|
||||
},
|
||||
]
|
||||
_.map(colors, (value, modifier) => {
|
||||
return [`.${e(`placeholder-${modifier}`)}::placeholder`, getProperties(value)]
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
22
src/plugins/placeholderOpacity.js
Normal file
22
src/plugins/placeholderOpacity.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants, target }) {
|
||||
if (target('placeholderOpacity') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(theme('placeholderOpacity'), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`placeholder-opacity-${modifier}`)}::placeholder`,
|
||||
{
|
||||
'--placeholder-opacity': value,
|
||||
},
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
addUtilities(utilities, variants('placeholderOpacity'))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
export default function() {
|
||||
return function({ addUtilities, variants }) {
|
||||
return function({ addUtilities, variants, target }) {
|
||||
addUtilities(
|
||||
{
|
||||
'.static': { position: 'static' },
|
||||
'.fixed': { position: 'fixed' },
|
||||
'.absolute': { position: 'absolute' },
|
||||
'.relative': { position: 'relative' },
|
||||
'.sticky': { position: 'sticky' },
|
||||
...(target('position') === 'ie11'
|
||||
? {}
|
||||
: {
|
||||
'.sticky': {
|
||||
position: 'sticky',
|
||||
},
|
||||
}),
|
||||
},
|
||||
variants('position')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('rotate', [['rotate', ['--transform-rotate']]])
|
||||
return function({ target, ...args }) {
|
||||
if (target('rotate') === 'ie11') {
|
||||
createUtilityPlugin('rotate', [['rotate', ['transform'], value => `rotate(${value})`]])({
|
||||
target,
|
||||
...args,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('rotate', [['rotate', ['--transform-rotate']]])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,21 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('scale', [
|
||||
['scale', ['--transform-scale-x', '--transform-scale-y']],
|
||||
['scale-x', ['--transform-scale-x']],
|
||||
['scale-y', ['--transform-scale-y']],
|
||||
])
|
||||
return function({ target, ...args }) {
|
||||
if (target('scale') === 'ie11') {
|
||||
createUtilityPlugin('scale', [
|
||||
['scale', ['transform'], value => `scale(${value})`],
|
||||
['scale-x', ['transform'], value => `scaleX(${value})`],
|
||||
['scale-y', ['transform'], value => `scaleY(${value})`],
|
||||
])({ target, ...args })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('scale', [
|
||||
['scale', ['--transform-scale-x', '--transform-scale-y']],
|
||||
['scale-x', ['--transform-scale-x']],
|
||||
['scale-y', ['--transform-scale-y']],
|
||||
])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('skew', [
|
||||
['skew-x', ['--transform-skew-x']],
|
||||
['skew-y', ['--transform-skew-y']],
|
||||
])
|
||||
return function({ target, ...args }) {
|
||||
if (target('skew') === 'ie11') {
|
||||
createUtilityPlugin('skew', [
|
||||
['skew-x', ['transform'], value => `skewX(${value})`],
|
||||
['skew-y', ['transform'], value => `skewY(${value})`],
|
||||
])({ target, ...args })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('skew', [
|
||||
['skew-x', ['--transform-skew-x']],
|
||||
['skew-y', ['--transform-skew-y']],
|
||||
])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
62
src/plugins/space.js
Normal file
62
src/plugins/space.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import _ from 'lodash'
|
||||
import prefixNegativeModifiers from '../util/prefixNegativeModifiers'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants, target }) {
|
||||
if (target('space') === 'ie11') {
|
||||
const generators = [
|
||||
(size, modifier) => ({
|
||||
[`.${e(
|
||||
prefixNegativeModifiers('space-y', modifier)
|
||||
)} > :not(template) ~ :not(template)`]: {
|
||||
'margin-top': size,
|
||||
},
|
||||
[`.${e(
|
||||
prefixNegativeModifiers('space-x', modifier)
|
||||
)} > :not(template) ~ :not(template)`]: {
|
||||
'margin-left': size,
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
const utilities = _.flatMap(generators, generator => {
|
||||
return _.flatMap(theme('space'), generator)
|
||||
})
|
||||
|
||||
addUtilities(utilities, variants('space'))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const generators = [
|
||||
(size, modifier) => ({
|
||||
[`.${e(prefixNegativeModifiers('space-y', modifier))} > :not(template) ~ :not(template)`]: {
|
||||
'--space-y-reverse': '0',
|
||||
'margin-top': `calc(${size === '0' ? '0px' : size} * calc(1 - var(--space-y-reverse)))`,
|
||||
'margin-bottom': `calc(${size === '0' ? '0px' : size} * var(--space-y-reverse))`,
|
||||
},
|
||||
[`.${e(prefixNegativeModifiers('space-x', modifier))} > :not(template) ~ :not(template)`]: {
|
||||
'--space-x-reverse': '0',
|
||||
'margin-right': `calc(${size === '0' ? '0px' : size} * var(--space-x-reverse))`,
|
||||
'margin-left': `calc(${size === '0' ? '0px' : size} * calc(1 - var(--space-x-reverse)))`,
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
const utilities = _.flatMap(generators, generator => {
|
||||
return [
|
||||
..._.flatMap(theme('space'), generator),
|
||||
{
|
||||
'.space-y-reverse > :not(template) ~ :not(template)': {
|
||||
'--space-y-reverse': '1',
|
||||
},
|
||||
'.space-x-reverse > :not(template) ~ :not(template)': {
|
||||
'--space-x-reverse': '1',
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
addUtilities(utilities, variants('space'))
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,11 @@ import flattenColorPalette from '../util/flattenColorPalette'
|
|||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
const colors = flattenColorPalette(theme('stroke'))
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(flattenColorPalette(theme('stroke')), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`stroke-${modifier}`)}`,
|
||||
{
|
||||
stroke: value,
|
||||
},
|
||||
]
|
||||
_.map(colors, (value, modifier) => {
|
||||
return [`.${e(`stroke-${modifier}`)}`, { stroke: value }]
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,30 @@
|
|||
import _ from 'lodash'
|
||||
import flattenColorPalette from '../util/flattenColorPalette'
|
||||
import withAlphaVariable from '../util/withAlphaVariable'
|
||||
|
||||
export default function() {
|
||||
return function({ addUtilities, e, theme, variants }) {
|
||||
return function({ addUtilities, e, theme, variants, target, corePlugins }) {
|
||||
const colors = flattenColorPalette(theme('textColor'))
|
||||
|
||||
const getProperties = value => {
|
||||
if (target('textColor') === 'ie11') {
|
||||
return { color: value }
|
||||
}
|
||||
|
||||
if (corePlugins('textOpacity')) {
|
||||
return withAlphaVariable({
|
||||
color: value,
|
||||
property: 'color',
|
||||
variable: '--text-opacity',
|
||||
})
|
||||
}
|
||||
|
||||
return { color: value }
|
||||
}
|
||||
|
||||
const utilities = _.fromPairs(
|
||||
_.map(flattenColorPalette(theme('textColor')), (value, modifier) => {
|
||||
return [
|
||||
`.${e(`text-${modifier}`)}`,
|
||||
{
|
||||
color: value,
|
||||
},
|
||||
]
|
||||
_.map(colors, (value, modifier) => {
|
||||
return [`.${e(`text-${modifier}`)}`, getProperties(value)]
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
11
src/plugins/textOpacity.js
Normal file
11
src/plugins/textOpacity.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return function({ target, ...args }) {
|
||||
if (target('textOpacity') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('textOpacity', [['text-opacity', ['--text-opacity']]])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
export default function() {
|
||||
return function({ addUtilities, variants }) {
|
||||
return function({ addUtilities, variants, target }) {
|
||||
if (target('transform') === 'ie11') {
|
||||
return
|
||||
}
|
||||
|
||||
addUtilities(
|
||||
{
|
||||
'.transform': {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function() {
|
||||
return createUtilityPlugin('translate', [
|
||||
['translate-x', ['--transform-translate-x']],
|
||||
['translate-y', ['--transform-translate-y']],
|
||||
])
|
||||
return function({ target, ...args }) {
|
||||
if (target('translate') === 'ie11') {
|
||||
createUtilityPlugin('translate', [
|
||||
['translate-x', ['transform'], value => `translateX(${value})`],
|
||||
['translate-y', ['transform'], value => `translateY(${value})`],
|
||||
])({ target, ...args })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
createUtilityPlugin('translate', [
|
||||
['translate-x', ['--transform-translate-x']],
|
||||
['translate-y', ['--transform-translate-y']],
|
||||
])({ target, ...args })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,24 +5,97 @@ import substituteTailwindAtRules from './lib/substituteTailwindAtRules'
|
|||
import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
|
||||
import substituteVariantsAtRules from './lib/substituteVariantsAtRules'
|
||||
import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules'
|
||||
import convertLayerAtRulesToControlComments from './lib/convertLayerAtRulesToControlComments'
|
||||
import substituteScreenAtRules from './lib/substituteScreenAtRules'
|
||||
import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules'
|
||||
import purgeUnusedStyles from './lib/purgeUnusedStyles'
|
||||
|
||||
import corePlugins from './corePlugins'
|
||||
import processPlugins from './util/processPlugins'
|
||||
import cloneNodes from './util/cloneNodes'
|
||||
import chalk from 'chalk'
|
||||
|
||||
const log = {
|
||||
info(message) {
|
||||
console.log(chalk.bold.cyan(' info'), '-', message)
|
||||
},
|
||||
warn(message) {
|
||||
console.log(chalk.bold.yellow(' warn'), '-', message)
|
||||
},
|
||||
risk(message) {
|
||||
console.log(chalk.bold.magenta(' risk'), '-', message)
|
||||
},
|
||||
}
|
||||
|
||||
function issueFlagNotices(config) {
|
||||
const featureFlags = {
|
||||
future: [],
|
||||
experimental: ['uniformColorPalette'],
|
||||
}
|
||||
|
||||
if (_.some(featureFlags.future, flag => _.get(config, ['future', flag], false))) {
|
||||
const changes = Object.keys(config.future)
|
||||
.filter(flag => featureFlags.future.includes(flag) && config.future[flag])
|
||||
.map(s => chalk.cyan(s))
|
||||
.join(', ')
|
||||
console.log()
|
||||
log.info(`You have opted-in to future-facing breaking changes: ${changes}`)
|
||||
log.info(
|
||||
'These changes are stable and will be the default behavior in the next major version of Tailwind.'
|
||||
)
|
||||
}
|
||||
|
||||
if (_.some(featureFlags.experimental, flag => _.get(config, ['experimental', flag], false))) {
|
||||
const changes = Object.keys(config.experimental)
|
||||
.filter(flag => featureFlags.experimental.includes(flag) && config.experimental[flag])
|
||||
.map(s => chalk.yellow(s))
|
||||
.join(', ')
|
||||
console.log()
|
||||
log.warn(`You have enabled experimental features: ${changes}`)
|
||||
log.warn(
|
||||
'Experimental features are not covered by semver, may introduce breaking changes, and can change at any time.'
|
||||
)
|
||||
}
|
||||
|
||||
if (Object.keys(_.get(config, 'future', {})).length < featureFlags.future.length) {
|
||||
const changes = featureFlags.future
|
||||
.filter(flag => !_.has(config, ['future', flag]))
|
||||
.map(s => chalk.magenta(s))
|
||||
.join(', ')
|
||||
console.log()
|
||||
log.risk(`There are upcoming breaking changes: ${changes}`)
|
||||
log.risk(
|
||||
'We highly recommend opting-in to these changes now to simplify upgrading Tailwind in the future.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default function(getConfig) {
|
||||
return function(css) {
|
||||
const config = getConfig()
|
||||
|
||||
issueFlagNotices(config)
|
||||
|
||||
const processedPlugins = processPlugins([...corePlugins(config), ...config.plugins], config)
|
||||
|
||||
const getProcessedPlugins = function() {
|
||||
return {
|
||||
...processedPlugins,
|
||||
base: cloneNodes(processedPlugins.base),
|
||||
components: cloneNodes(processedPlugins.components),
|
||||
utilities: cloneNodes(processedPlugins.utilities),
|
||||
}
|
||||
}
|
||||
|
||||
return postcss([
|
||||
substituteTailwindAtRules(config, processedPlugins),
|
||||
substituteTailwindAtRules(config, getProcessedPlugins()),
|
||||
evaluateTailwindFunctions(config),
|
||||
substituteVariantsAtRules(config, processedPlugins),
|
||||
substituteVariantsAtRules(config, getProcessedPlugins()),
|
||||
substituteResponsiveAtRules(config),
|
||||
convertLayerAtRulesToControlComments(config),
|
||||
substituteScreenAtRules(config),
|
||||
substituteClassApplyAtRules(config, processedPlugins.utilities),
|
||||
substituteClassApplyAtRules(config, getProcessedPlugins().utilities),
|
||||
purgeUnusedStyles(config),
|
||||
]).process(css, { from: _.get(css, 'source.input.file') })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import identity from 'lodash/identity'
|
||||
import fromPairs from 'lodash/fromPairs'
|
||||
import toPairs from 'lodash/toPairs'
|
||||
import castArray from 'lodash/castArray'
|
||||
|
|
@ -16,16 +17,18 @@ function className(classPrefix, key) {
|
|||
|
||||
export default function createUtilityPlugin(themeKey, utilityVariations) {
|
||||
return function({ e, addUtilities, variants, theme }) {
|
||||
const utilities = utilityVariations.map(([classPrefix, properties]) => {
|
||||
return fromPairs(
|
||||
toPairs(theme(themeKey)).map(([key, value]) => {
|
||||
return [
|
||||
`.${e(className(classPrefix, key))}`,
|
||||
fromPairs(castArray(properties).map(property => [property, value])),
|
||||
]
|
||||
})
|
||||
)
|
||||
})
|
||||
const utilities = utilityVariations.map(
|
||||
([classPrefix, properties, transformValue = identity]) => {
|
||||
return fromPairs(
|
||||
toPairs(theme(themeKey)).map(([key, value]) => {
|
||||
return [
|
||||
`.${e(className(classPrefix, key))}`,
|
||||
fromPairs(castArray(properties).map(property => [property, transformValue(value)])),
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
return addUtilities(utilities, variants(themeKey))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'lodash'
|
||||
import postcss from 'postcss'
|
||||
import browserslist from 'browserslist'
|
||||
import Node from 'postcss/lib/node'
|
||||
import isFunction from 'lodash/isFunction'
|
||||
import escapeClassName from '../util/escapeClassName'
|
||||
|
|
@ -7,7 +8,9 @@ import generateVariantFunction from '../util/generateVariantFunction'
|
|||
import parseObjectStyles from '../util/parseObjectStyles'
|
||||
import prefixSelector from '../util/prefixSelector'
|
||||
import wrapWithVariants from '../util/wrapWithVariants'
|
||||
import cloneNodes from '../util/cloneNodes'
|
||||
import increaseSpecificity from '../util/increaseSpecificity'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
|
||||
function parseStyles(styles) {
|
||||
if (!Array.isArray(styles)) {
|
||||
|
|
@ -17,6 +20,23 @@ function parseStyles(styles) {
|
|||
return _.flatMap(styles, style => (style instanceof Node ? style : parseObjectStyles(style)))
|
||||
}
|
||||
|
||||
function containsClass(value) {
|
||||
return selectorParser(selectors => {
|
||||
let classFound = false
|
||||
selectors.walkClasses(() => (classFound = true))
|
||||
return classFound
|
||||
}).transformSync(value)
|
||||
}
|
||||
|
||||
function wrapWithLayer(rules, layer) {
|
||||
return postcss
|
||||
.atRule({
|
||||
name: 'layer',
|
||||
params: layer,
|
||||
})
|
||||
.append(cloneNodes(Array.isArray(rules) ? rules : [rules]))
|
||||
}
|
||||
|
||||
export default function(plugins, config) {
|
||||
const pluginBaseStyles = []
|
||||
const pluginComponents = []
|
||||
|
|
@ -26,7 +46,9 @@ export default function(plugins, config) {
|
|||
const applyConfiguredPrefix = selector => {
|
||||
return prefixSelector(config.prefix, selector)
|
||||
}
|
||||
|
||||
const getConfigValue = (path, defaultValue) => (path ? _.get(config, path, defaultValue) : config)
|
||||
const browserslistTarget = browserslist().includes('ie 11') ? 'ie11' : 'relaxed'
|
||||
|
||||
plugins.forEach(plugin => {
|
||||
if (plugin.__isOptionsFunction) {
|
||||
|
|
@ -39,6 +61,13 @@ export default function(plugins, config) {
|
|||
postcss,
|
||||
config: getConfigValue,
|
||||
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
|
||||
corePlugins: path => {
|
||||
if (Array.isArray(config.corePlugins)) {
|
||||
return config.corePlugins.includes(path)
|
||||
}
|
||||
|
||||
return getConfigValue(`corePlugins.${path}`, true)
|
||||
},
|
||||
variants: (path, defaultValue) => {
|
||||
if (Array.isArray(config.variants)) {
|
||||
return config.variants
|
||||
|
|
@ -46,6 +75,17 @@ export default function(plugins, config) {
|
|||
|
||||
return getConfigValue(`variants.${path}`, defaultValue)
|
||||
},
|
||||
target: path => {
|
||||
if (_.isString(config.target)) {
|
||||
return config.target === 'browserslist' ? browserslistTarget : config.target
|
||||
}
|
||||
|
||||
const [defaultTarget, targetOverrides] = getConfigValue('target')
|
||||
|
||||
const target = _.get(targetOverrides, path, defaultTarget)
|
||||
|
||||
return target === 'browserslist' ? browserslistTarget : target
|
||||
},
|
||||
e: escapeClassName,
|
||||
prefix: applyConfiguredPrefix,
|
||||
addUtilities: (utilities, options) => {
|
||||
|
|
@ -66,6 +106,12 @@ export default function(plugins, config) {
|
|||
if (config.important === true) {
|
||||
rule.walkDecls(decl => (decl.important = true))
|
||||
} else if (typeof config.important === 'string') {
|
||||
if (containsClass(config.important)) {
|
||||
throw rule.error(
|
||||
`Classes are not allowed when using the \`important\` option with a string argument. Please use an ID instead.`
|
||||
)
|
||||
}
|
||||
|
||||
rule.selectors = rule.selectors.map(selector => {
|
||||
return increaseSpecificity(config.important, selector)
|
||||
})
|
||||
|
|
@ -73,10 +119,16 @@ export default function(plugins, config) {
|
|||
}
|
||||
})
|
||||
|
||||
pluginUtilities.push(wrapWithVariants(styles.nodes, options.variants))
|
||||
pluginUtilities.push(
|
||||
wrapWithLayer(wrapWithVariants(styles.nodes, options.variants), 'utilities')
|
||||
)
|
||||
},
|
||||
addComponents: (components, options) => {
|
||||
options = Object.assign({ respectPrefix: true }, options)
|
||||
const defaultOptions = { variants: [], respectPrefix: true }
|
||||
|
||||
options = Array.isArray(options)
|
||||
? Object.assign({}, defaultOptions, { variants: options })
|
||||
: _.defaults(options, defaultOptions)
|
||||
|
||||
const styles = postcss.root({ nodes: parseStyles(components) })
|
||||
|
||||
|
|
@ -86,7 +138,9 @@ export default function(plugins, config) {
|
|||
}
|
||||
})
|
||||
|
||||
pluginComponents.push(...styles.nodes)
|
||||
pluginComponents.push(
|
||||
wrapWithLayer(wrapWithVariants(styles.nodes, options.variants), 'components')
|
||||
)
|
||||
},
|
||||
addBase: baseStyles => {
|
||||
pluginBaseStyles.push(...parseStyles(baseStyles))
|
||||
|
|
|
|||
39
src/util/withAlphaVariable.js
Normal file
39
src/util/withAlphaVariable.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import createColor from 'color'
|
||||
|
||||
function hasAlpha(color) {
|
||||
return (
|
||||
color.startsWith('rgba(') ||
|
||||
color.startsWith('hsla(') ||
|
||||
(color.startsWith('#') && color.length === 9) ||
|
||||
(color.startsWith('#') && color.length === 5)
|
||||
)
|
||||
}
|
||||
|
||||
function toRgba(color) {
|
||||
const [r, g, b, a] = createColor(color)
|
||||
.rgb()
|
||||
.array()
|
||||
|
||||
return [r, g, b, a === undefined && hasAlpha(color) ? 1 : a]
|
||||
}
|
||||
|
||||
export default function withAlphaVariable({ color, property, variable }) {
|
||||
try {
|
||||
const [r, g, b, a] = toRgba(color)
|
||||
|
||||
if (a !== undefined) {
|
||||
return {
|
||||
[property]: color,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[variable]: '1',
|
||||
[property]: [color, `rgba(${r}, ${g}, ${b}, var(${variable}))`],
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
[property]: color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,16 @@ import postcss from 'postcss'
|
|||
import cloneNodes from './cloneNodes'
|
||||
|
||||
export default function wrapWithVariants(rules, variants) {
|
||||
let foundVariantAtRule = false
|
||||
|
||||
postcss.root({ nodes: rules }).walkAtRules('variants', () => {
|
||||
foundVariantAtRule = true
|
||||
})
|
||||
|
||||
if (foundVariantAtRule) {
|
||||
return cloneNodes(rules)
|
||||
}
|
||||
|
||||
return postcss
|
||||
.atRule({
|
||||
name: 'variants',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = {
|
||||
purge: [],
|
||||
target: 'relaxed',
|
||||
prefix: '',
|
||||
important: false,
|
||||
separator: ':',
|
||||
|
|
@ -149,6 +151,7 @@ module.exports = {
|
|||
'64': '16rem',
|
||||
},
|
||||
backgroundColor: theme => theme('colors'),
|
||||
backgroundOpacity: theme => theme('opacity'),
|
||||
backgroundPosition: {
|
||||
bottom: 'bottom',
|
||||
center: 'center',
|
||||
|
|
@ -169,6 +172,7 @@ module.exports = {
|
|||
...theme('colors'),
|
||||
default: theme('colors.gray.300', 'currentColor'),
|
||||
}),
|
||||
borderOpacity: theme => theme('opacity'),
|
||||
borderRadius: {
|
||||
none: '0',
|
||||
sm: '0.125rem',
|
||||
|
|
@ -206,6 +210,9 @@ module.exports = {
|
|||
move: 'move',
|
||||
'not-allowed': 'not-allowed',
|
||||
},
|
||||
divideColor: theme => theme('borderColor'),
|
||||
divideOpacity: theme => theme('borderOpacity'),
|
||||
divideWidth: theme => theme('borderWidth'),
|
||||
fill: {
|
||||
current: 'currentColor',
|
||||
},
|
||||
|
|
@ -374,6 +381,11 @@ module.exports = {
|
|||
},
|
||||
padding: theme => theme('spacing'),
|
||||
placeholderColor: theme => theme('colors'),
|
||||
placeholderOpacity: theme => theme('opacity'),
|
||||
space: (theme, { negative }) => ({
|
||||
...theme('spacing'),
|
||||
...negative(theme('spacing')),
|
||||
}),
|
||||
stroke: {
|
||||
current: 'currentColor',
|
||||
},
|
||||
|
|
@ -383,6 +395,7 @@ module.exports = {
|
|||
'2': '2',
|
||||
},
|
||||
textColor: theme => theme('colors'),
|
||||
textOpacity: theme => theme('opacity'),
|
||||
width: theme => ({
|
||||
auto: 'auto',
|
||||
...theme('spacing'),
|
||||
|
|
@ -609,6 +622,37 @@ module.exports = {
|
|||
'700': '700ms',
|
||||
'1000': '1000ms',
|
||||
},
|
||||
animation: {
|
||||
none: 'none',
|
||||
spin: 'spin 1s linear infinite',
|
||||
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
bounce: 'bounce 1s infinite',
|
||||
},
|
||||
keyframes: {
|
||||
spin: {
|
||||
from: { transform: 'rotate(0deg)' },
|
||||
to: { transform: 'rotate(360deg)' },
|
||||
},
|
||||
ping: {
|
||||
'0%': { transform: 'scale(1)', opacity: '1' },
|
||||
'75%, 100%': { transform: 'scale(2)', opacity: '0' },
|
||||
},
|
||||
pulse: {
|
||||
'0%, 100%': { opacity: '1' },
|
||||
'50%': { opacity: '.5' },
|
||||
},
|
||||
bounce: {
|
||||
'0%, 100%': {
|
||||
transform: 'translateY(-25%)',
|
||||
animationTimingFunction: 'cubic-bezier(0.8,0,1,1)',
|
||||
},
|
||||
'50%': {
|
||||
transform: 'translateY(0)',
|
||||
animationTimingFunction: 'cubic-bezier(0,0,0.2,1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
accessibility: ['responsive', 'focus'],
|
||||
|
|
@ -618,18 +662,24 @@ module.exports = {
|
|||
appearance: ['responsive'],
|
||||
backgroundAttachment: ['responsive'],
|
||||
backgroundColor: ['responsive', 'hover', 'focus'],
|
||||
backgroundOpacity: ['responsive', 'hover', 'focus'],
|
||||
backgroundPosition: ['responsive'],
|
||||
backgroundRepeat: ['responsive'],
|
||||
backgroundSize: ['responsive'],
|
||||
borderCollapse: ['responsive'],
|
||||
borderColor: ['responsive', 'hover', 'focus'],
|
||||
borderOpacity: ['responsive', 'hover', 'focus'],
|
||||
borderRadius: ['responsive'],
|
||||
borderStyle: ['responsive'],
|
||||
borderWidth: ['responsive'],
|
||||
boxShadow: ['responsive', 'hover', 'focus'],
|
||||
boxSizing: ['responsive'],
|
||||
container: ['responsive'],
|
||||
cursor: ['responsive'],
|
||||
display: ['responsive'],
|
||||
divideColor: ['responsive'],
|
||||
divideOpacity: ['responsive'],
|
||||
divideWidth: ['responsive'],
|
||||
fill: ['responsive'],
|
||||
flex: ['responsive'],
|
||||
flexDirection: ['responsive'],
|
||||
|
|
@ -661,16 +711,20 @@ module.exports = {
|
|||
order: ['responsive'],
|
||||
outline: ['responsive', 'focus'],
|
||||
overflow: ['responsive'],
|
||||
overscrollBehavior: ['responsive'],
|
||||
padding: ['responsive'],
|
||||
placeholderColor: ['responsive', 'focus'],
|
||||
placeholderOpacity: ['responsive', 'focus'],
|
||||
pointerEvents: ['responsive'],
|
||||
position: ['responsive'],
|
||||
resize: ['responsive'],
|
||||
space: ['responsive'],
|
||||
stroke: ['responsive'],
|
||||
strokeWidth: ['responsive'],
|
||||
tableLayout: ['responsive'],
|
||||
textAlign: ['responsive'],
|
||||
textColor: ['responsive', 'hover', 'focus'],
|
||||
textOpacity: ['responsive', 'hover', 'focus'],
|
||||
textDecoration: ['responsive', 'hover', 'focus'],
|
||||
textTransform: ['responsive'],
|
||||
userSelect: ['responsive'],
|
||||
|
|
@ -700,6 +754,7 @@ module.exports = {
|
|||
transitionTimingFunction: ['responsive'],
|
||||
transitionDuration: ['responsive'],
|
||||
transitionDelay: ['responsive'],
|
||||
animation: ['responsive'],
|
||||
},
|
||||
corePlugins: {},
|
||||
plugins: [],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
purge: [],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue