mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Add tuple syntax to guarantee screens order (#6104)
* add normalizeScreens function This will allow us to normalize the various kinds of inputs to a stable version that is consistent regardless of the input. * use normalized screens * add dedicated test for new tuple syntax * make test consistent with other tests While working on the normalizeScreens feature, some tests started failing (the one with multiple screens), while looking at them I made them consistent with the rest of the codebase. * add test to ensure consistent order in screens output * update changelog * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
parent
6c4b86d438
commit
ef325ea35b
@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
Nothing yet!
|
||||
|
||||
### Fixed
|
||||
|
||||
- Enforce the order of some variants (like `before` and `after`) ([#6018](https://github.com/tailwindlabs/tailwindcss/pull/6018))
|
||||
@ -16,6 +14,7 @@ Nothing yet!
|
||||
### Added
|
||||
|
||||
- Add `placeholder` variant ([#6106](https://github.com/tailwindlabs/tailwindcss/pull/6106))
|
||||
- Add tuple syntax for configuring screens while guaranteeing order ([#5956](https://github.com/tailwindlabs/tailwindcss/pull/5956))
|
||||
|
||||
## [3.0.0-alpha.2] - 2021-11-08
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import isPlainObject from './util/isPlainObject'
|
||||
import transformThemeValue from './util/transformThemeValue'
|
||||
import { version as tailwindVersion } from '../package.json'
|
||||
import log from './util/log'
|
||||
import { normalizeScreens } from './util/normalizeScreens'
|
||||
import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
|
||||
|
||||
export let variantPlugins = {
|
||||
@ -158,11 +159,10 @@ export let variantPlugins = {
|
||||
},
|
||||
|
||||
screenVariants: ({ theme, addVariant }) => {
|
||||
for (let screen in theme('screens')) {
|
||||
let size = theme('screens')[screen]
|
||||
let query = buildMediaQuery(size)
|
||||
for (let screen of normalizeScreens(theme('screens'))) {
|
||||
let query = buildMediaQuery(screen)
|
||||
|
||||
addVariant(screen, `@media ${query}`)
|
||||
addVariant(screen.name, `@media ${query}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -182,24 +182,10 @@ export let corePlugins = {
|
||||
},
|
||||
|
||||
container: (() => {
|
||||
function extractMinWidths(breakpoints) {
|
||||
return Object.values(breakpoints ?? {}).flatMap((breakpoints) => {
|
||||
if (typeof breakpoints === 'string') {
|
||||
breakpoints = { min: breakpoints }
|
||||
}
|
||||
|
||||
if (!Array.isArray(breakpoints)) {
|
||||
breakpoints = [breakpoints]
|
||||
}
|
||||
|
||||
return breakpoints
|
||||
.filter((breakpoint) => {
|
||||
return breakpoint?.hasOwnProperty?.('min') || breakpoint?.hasOwnProperty('min-width')
|
||||
})
|
||||
.map((breakpoint) => {
|
||||
return breakpoint['min-width'] ?? breakpoint.min
|
||||
})
|
||||
})
|
||||
function extractMinWidths(breakpoints = []) {
|
||||
return breakpoints
|
||||
.flatMap((breakpoint) => breakpoint.values.map((breakpoint) => breakpoint.min))
|
||||
.filter((v) => v !== undefined)
|
||||
}
|
||||
|
||||
function mapMinWidthsToPadding(minWidths, screens, paddings) {
|
||||
@ -228,16 +214,11 @@ export let corePlugins = {
|
||||
}
|
||||
|
||||
for (let minWidth of minWidths) {
|
||||
for (let [screen, value] of Object.entries(screens)) {
|
||||
let screenMinWidth =
|
||||
typeof value === 'object' && value !== null ? value.min || value['min-width'] : value
|
||||
|
||||
if (`${screenMinWidth}` === `${minWidth}`) {
|
||||
mapping.push({
|
||||
screen,
|
||||
minWidth,
|
||||
padding: paddings[screen],
|
||||
})
|
||||
for (let screen of screens) {
|
||||
for (let { min } of screen.values) {
|
||||
if (min === minWidth) {
|
||||
mapping.push({ minWidth, padding: paddings[screen.name] })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -246,12 +227,12 @@ export let corePlugins = {
|
||||
}
|
||||
|
||||
return function ({ addComponents, theme }) {
|
||||
let screens = theme('container.screens', theme('screens'))
|
||||
let screens = normalizeScreens(theme('container.screens', theme('screens')))
|
||||
let minWidths = extractMinWidths(screens)
|
||||
let paddings = mapMinWidthsToPadding(minWidths, screens, theme('container.padding'))
|
||||
|
||||
let generatePaddingFor = (minWidth) => {
|
||||
let paddingConfig = paddings.find((padding) => `${padding.minWidth}` === `${minWidth}`)
|
||||
let paddingConfig = paddings.find((padding) => padding.minWidth === minWidth)
|
||||
|
||||
if (!paddingConfig) {
|
||||
return {}
|
||||
|
||||
@ -2,6 +2,7 @@ import dlv from 'dlv'
|
||||
import didYouMean from 'didyoumean'
|
||||
import transformThemeValue from '../util/transformThemeValue'
|
||||
import parseValue from 'postcss-value-parser'
|
||||
import { normalizeScreens } from '../util/normalizeScreens'
|
||||
import buildMediaQuery from '../util/buildMediaQuery'
|
||||
import { toPath } from '../util/toPath'
|
||||
|
||||
@ -173,12 +174,14 @@ export default function ({ tailwindConfig: config }) {
|
||||
},
|
||||
screen: (node, screen) => {
|
||||
screen = screen.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
|
||||
let screens = normalizeScreens(config.theme.screens)
|
||||
let screenDefinition = screens.find(({ name }) => name === screen)
|
||||
|
||||
if (config.theme.screens[screen] === undefined) {
|
||||
if (!screenDefinition) {
|
||||
throw node.error(`The '${screen}' screen does not exist in your theme.`)
|
||||
}
|
||||
|
||||
return buildMediaQuery(config.theme.screens[screen])
|
||||
return buildMediaQuery(screenDefinition)
|
||||
},
|
||||
}
|
||||
return (root) => {
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import { normalizeScreens } from '../util/normalizeScreens'
|
||||
import buildMediaQuery from '../util/buildMediaQuery'
|
||||
|
||||
export default function ({ tailwindConfig: { theme } }) {
|
||||
return function (css) {
|
||||
css.walkAtRules('screen', (atRule) => {
|
||||
const screen = atRule.params
|
||||
let screen = atRule.params
|
||||
let screens = normalizeScreens(theme.screens)
|
||||
let screenDefinition = screens.find(({ name }) => name === screen)
|
||||
|
||||
if (!theme.screens?.hasOwnProperty?.(screen)) {
|
||||
if (!screenDefinition) {
|
||||
throw atRule.error(`No \`${screen}\` screen found.`)
|
||||
}
|
||||
|
||||
atRule.name = 'media'
|
||||
atRule.params = buildMediaQuery(theme.screens[screen])
|
||||
atRule.params = buildMediaQuery(screenDefinition)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,20 @@
|
||||
export default function buildMediaQuery(screens) {
|
||||
if (typeof screens === 'string') {
|
||||
screens = { min: screens }
|
||||
}
|
||||
|
||||
if (!Array.isArray(screens)) {
|
||||
screens = [screens]
|
||||
}
|
||||
screens = Array.isArray(screens) ? screens : [screens]
|
||||
|
||||
return screens
|
||||
.map((screen) => {
|
||||
if (screen?.hasOwnProperty?.('raw')) {
|
||||
return screen.raw
|
||||
}
|
||||
.map((screen) =>
|
||||
screen.values.map((screen) => {
|
||||
if (screen.raw !== undefined) {
|
||||
return screen.raw
|
||||
}
|
||||
|
||||
return Object.entries(screen)
|
||||
.map(([feature, value]) => {
|
||||
feature = { min: 'min-width', max: 'max-width' }[feature] ?? feature
|
||||
return `(${feature}: ${value})`
|
||||
})
|
||||
.join(' and ')
|
||||
})
|
||||
return [
|
||||
screen.min && `(min-width: ${screen.min})`,
|
||||
screen.max && `(max-width: ${screen.max})`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' and ')
|
||||
})
|
||||
)
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
42
src/util/normalizeScreens.js
Normal file
42
src/util/normalizeScreens.js
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* A function that normalizes the various forms that the screens object can be
|
||||
* provided in.
|
||||
*
|
||||
* Input(s):
|
||||
* - ['100px', '200px'] // Raw strings
|
||||
* - { sm: '100px', md: '200px' } // Object with string values
|
||||
* - { sm: { min: '100px' }, md: { max: '100px' } } // Object with object values
|
||||
* - { sm: [{ min: '100px' }, { max: '200px' }] } // Object with object array (multiple values)
|
||||
* - [['sm', '100px'], ['md', '200px']] // Tuple object
|
||||
*
|
||||
* Output(s):
|
||||
* - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values
|
||||
*/
|
||||
export function normalizeScreens(screens) {
|
||||
if (Array.isArray(screens)) {
|
||||
return screens.map((screen) => {
|
||||
if (typeof screen === 'string') {
|
||||
return { name: screen.toString(), values: [{ min: screen, max: undefined }] }
|
||||
}
|
||||
|
||||
let [name, options] = screen
|
||||
name = name.toString()
|
||||
|
||||
if (typeof options === 'string') {
|
||||
return { name, values: [{ min: options, max: undefined }] }
|
||||
}
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
return { name, values: options.map((option) => resolveValue(option)) }
|
||||
}
|
||||
|
||||
return { name, values: [resolveValue(options)] }
|
||||
})
|
||||
}
|
||||
|
||||
return normalizeScreens(Object.entries(screens ?? {}))
|
||||
}
|
||||
|
||||
function resolveValue({ 'min-width': _minWidth, min = _minWidth, max, raw } = {}) {
|
||||
return { min, max, raw }
|
||||
}
|
||||
@ -343,3 +343,39 @@ test('container can use variants', () => {
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('container can use screens with tuple syntax', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
screens: [
|
||||
[1800, { min: '1800px' }],
|
||||
[1200, { min: '1200px' }],
|
||||
[768, { min: '768px' }],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1800px) {
|
||||
.container {
|
||||
max-width: 1800px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
import postcss from 'postcss'
|
||||
import plugin from '../src/lib/evaluateTailwindFunctions'
|
||||
import { css } from './util/run'
|
||||
|
||||
function run(input, opts = {}) {
|
||||
return postcss([plugin({ tailwindConfig: opts })]).process(input, { from: undefined })
|
||||
}
|
||||
|
||||
test('it looks up values in the theme using dot notation', () => {
|
||||
const input = `
|
||||
.banana { color: theme('colors.yellow'); }
|
||||
let input = css`
|
||||
.banana {
|
||||
color: theme('colors.yellow');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: #f7cc50; }
|
||||
let output = css`
|
||||
.banana {
|
||||
color: #f7cc50;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -27,37 +32,85 @@ test('it looks up values in the theme using dot notation', () => {
|
||||
})
|
||||
|
||||
test('color can be a function', () => {
|
||||
const input = `
|
||||
.backgroundColor { color: theme('backgroundColor.fn') }
|
||||
.borderColor { color: theme('borderColor.fn') }
|
||||
.caretColor { color: theme('caretColor.fn') }
|
||||
.colors { color: theme('colors.fn') }
|
||||
.divideColor { color: theme('divideColor.fn') }
|
||||
.fill { color: theme('fill.fn') }
|
||||
.gradientColorStops { color: theme('gradientColorStops.fn') }
|
||||
.placeholderColor { color: theme('placeholderColor.fn') }
|
||||
.ringColor { color: theme('ringColor.fn') }
|
||||
.ringOffsetColor { color: theme('ringOffsetColor.fn') }
|
||||
.stroke { color: theme('stroke.fn') }
|
||||
.textColor { color: theme('textColor.fn') }
|
||||
let input = css`
|
||||
.backgroundColor {
|
||||
color: theme('backgroundColor.fn');
|
||||
}
|
||||
.borderColor {
|
||||
color: theme('borderColor.fn');
|
||||
}
|
||||
.caretColor {
|
||||
color: theme('caretColor.fn');
|
||||
}
|
||||
.colors {
|
||||
color: theme('colors.fn');
|
||||
}
|
||||
.divideColor {
|
||||
color: theme('divideColor.fn');
|
||||
}
|
||||
.fill {
|
||||
color: theme('fill.fn');
|
||||
}
|
||||
.gradientColorStops {
|
||||
color: theme('gradientColorStops.fn');
|
||||
}
|
||||
.placeholderColor {
|
||||
color: theme('placeholderColor.fn');
|
||||
}
|
||||
.ringColor {
|
||||
color: theme('ringColor.fn');
|
||||
}
|
||||
.ringOffsetColor {
|
||||
color: theme('ringOffsetColor.fn');
|
||||
}
|
||||
.stroke {
|
||||
color: theme('stroke.fn');
|
||||
}
|
||||
.textColor {
|
||||
color: theme('textColor.fn');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.backgroundColor { color: #f00 }
|
||||
.borderColor { color: #f00 }
|
||||
.caretColor { color: #f00 }
|
||||
.colors { color: #f00 }
|
||||
.divideColor { color: #f00 }
|
||||
.fill { color: #f00 }
|
||||
.gradientColorStops { color: #f00 }
|
||||
.placeholderColor { color: #f00 }
|
||||
.ringColor { color: #f00 }
|
||||
.ringOffsetColor { color: #f00 }
|
||||
.stroke { color: #f00 }
|
||||
.textColor { color: #f00 }
|
||||
let output = css`
|
||||
.backgroundColor {
|
||||
color: #f00;
|
||||
}
|
||||
.borderColor {
|
||||
color: #f00;
|
||||
}
|
||||
.caretColor {
|
||||
color: #f00;
|
||||
}
|
||||
.colors {
|
||||
color: #f00;
|
||||
}
|
||||
.divideColor {
|
||||
color: #f00;
|
||||
}
|
||||
.fill {
|
||||
color: #f00;
|
||||
}
|
||||
.gradientColorStops {
|
||||
color: #f00;
|
||||
}
|
||||
.placeholderColor {
|
||||
color: #f00;
|
||||
}
|
||||
.ringColor {
|
||||
color: #f00;
|
||||
}
|
||||
.ringOffsetColor {
|
||||
color: #f00;
|
||||
}
|
||||
.stroke {
|
||||
color: #f00;
|
||||
}
|
||||
.textColor {
|
||||
color: #f00;
|
||||
}
|
||||
`
|
||||
|
||||
const fn = () => `#f00`
|
||||
let fn = () => `#f00`
|
||||
|
||||
return run(input, {
|
||||
theme: {
|
||||
@ -81,12 +134,16 @@ test('color can be a function', () => {
|
||||
})
|
||||
|
||||
test('quotes are optional around the lookup path', () => {
|
||||
const input = `
|
||||
.banana { color: theme(colors.yellow); }
|
||||
let input = css`
|
||||
.banana {
|
||||
color: theme(colors.yellow);
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.banana { color: #f7cc50; }
|
||||
let output = css`
|
||||
.banana {
|
||||
color: #f7cc50;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -102,12 +159,16 @@ test('quotes are optional around the lookup path', () => {
|
||||
})
|
||||
|
||||
test('a default value can be provided', () => {
|
||||
const input = `
|
||||
.cookieMonster { color: theme('colors.blue', #0000ff); }
|
||||
let input = css`
|
||||
.cookieMonster {
|
||||
color: theme('colors.blue', #0000ff);
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.cookieMonster { color: #0000ff; }
|
||||
let output = css`
|
||||
.cookieMonster {
|
||||
color: #0000ff;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -123,12 +184,16 @@ test('a default value can be provided', () => {
|
||||
})
|
||||
|
||||
test('the default value can use the theme function', () => {
|
||||
const input = `
|
||||
.cookieMonster { color: theme('colors.blue', theme('colors.yellow')); }
|
||||
let input = css`
|
||||
.cookieMonster {
|
||||
color: theme('colors.blue', theme('colors.yellow'));
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.cookieMonster { color: #f7cc50; }
|
||||
let output = css`
|
||||
.cookieMonster {
|
||||
color: #f7cc50;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -144,12 +209,16 @@ test('the default value can use the theme function', () => {
|
||||
})
|
||||
|
||||
test('quotes are preserved around default values', () => {
|
||||
const input = `
|
||||
.heading { font-family: theme('fontFamily.sans', "Helvetica Neue"); }
|
||||
let input = css`
|
||||
.heading {
|
||||
font-family: theme('fontFamily.sans', 'Helvetica Neue');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.heading { font-family: "Helvetica Neue"; }
|
||||
let output = css`
|
||||
.heading {
|
||||
font-family: 'Helvetica Neue';
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -165,12 +234,16 @@ test('quotes are preserved around default values', () => {
|
||||
})
|
||||
|
||||
test('an unquoted list is valid as a default value', () => {
|
||||
const input = `
|
||||
.heading { font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif); }
|
||||
let input = css`
|
||||
.heading {
|
||||
font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif);
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.heading { font-family: Helvetica, Arial, sans-serif; }
|
||||
let output = css`
|
||||
.heading {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -186,8 +259,10 @@ test('an unquoted list is valid as a default value', () => {
|
||||
})
|
||||
|
||||
test('a missing root theme value throws', () => {
|
||||
const input = `
|
||||
.heading { color: theme('colours.gray.100'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
color: theme('colours.gray.100');
|
||||
}
|
||||
`
|
||||
|
||||
return expect(
|
||||
@ -204,8 +279,10 @@ test('a missing root theme value throws', () => {
|
||||
})
|
||||
|
||||
test('a missing nested theme property throws', () => {
|
||||
const input = `
|
||||
.heading { color: theme('colors.red'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
color: theme('colors.red');
|
||||
}
|
||||
`
|
||||
|
||||
return expect(
|
||||
@ -223,8 +300,10 @@ test('a missing nested theme property throws', () => {
|
||||
})
|
||||
|
||||
test('a missing nested theme property with a close alternative throws with a suggestion', () => {
|
||||
const input = `
|
||||
.heading { color: theme('colors.yellw'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
color: theme('colors.yellw');
|
||||
}
|
||||
`
|
||||
|
||||
return expect(
|
||||
@ -241,8 +320,10 @@ test('a missing nested theme property with a close alternative throws with a sug
|
||||
})
|
||||
|
||||
test('a path through a non-object throws', () => {
|
||||
const input = `
|
||||
.heading { color: theme('colors.yellow.100'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
color: theme('colors.yellow.100');
|
||||
}
|
||||
`
|
||||
|
||||
return expect(
|
||||
@ -259,8 +340,10 @@ test('a path through a non-object throws', () => {
|
||||
})
|
||||
|
||||
test('a path which exists but is not a string throws', () => {
|
||||
const input = `
|
||||
.heading { color: theme('colors.yellow'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
color: theme('colors.yellow');
|
||||
}
|
||||
`
|
||||
|
||||
return expect(
|
||||
@ -275,8 +358,10 @@ test('a path which exists but is not a string throws', () => {
|
||||
})
|
||||
|
||||
test('a path which exists but is invalid throws', () => {
|
||||
const input = `
|
||||
.heading { color: theme('colors'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
color: theme('colors');
|
||||
}
|
||||
`
|
||||
|
||||
return expect(
|
||||
@ -289,8 +374,10 @@ test('a path which exists but is invalid throws', () => {
|
||||
})
|
||||
|
||||
test('a path which is an object throws with a suggested key', () => {
|
||||
const input = `
|
||||
.heading { color: theme('colors'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
color: theme('colors');
|
||||
}
|
||||
`
|
||||
|
||||
return expect(
|
||||
@ -307,12 +394,16 @@ test('a path which is an object throws with a suggested key', () => {
|
||||
})
|
||||
|
||||
test('array values are joined by default', () => {
|
||||
const input = `
|
||||
.heading { font-family: theme('fontFamily.sans'); }
|
||||
let input = css`
|
||||
.heading {
|
||||
font-family: theme('fontFamily.sans');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.heading { font-family: Inter, Helvetica, sans-serif; }
|
||||
let output = css`
|
||||
.heading {
|
||||
font-family: Inter, Helvetica, sans-serif;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -328,14 +419,22 @@ test('array values are joined by default', () => {
|
||||
})
|
||||
|
||||
test('font sizes are retrieved without default line-heights or letter-spacing', () => {
|
||||
const input = `
|
||||
.heading-1 { font-size: theme('fontSize.lg'); }
|
||||
.heading-2 { font-size: theme('fontSize.xl'); }
|
||||
let input = css`
|
||||
.heading-1 {
|
||||
font-size: theme('fontSize.lg');
|
||||
}
|
||||
.heading-2 {
|
||||
font-size: theme('fontSize.xl');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.heading-1 { font-size: 20px; }
|
||||
.heading-2 { font-size: 24px; }
|
||||
let output = css`
|
||||
.heading-1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
.heading-2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -352,12 +451,16 @@ test('font sizes are retrieved without default line-heights or letter-spacing',
|
||||
})
|
||||
|
||||
test('outlines are retrieved without default outline-offset', () => {
|
||||
const input = `
|
||||
.element { outline: theme('outline.black'); }
|
||||
let input = css`
|
||||
.element {
|
||||
outline: theme('outline.black');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.element { outline: 2px dotted black; }
|
||||
let output = css`
|
||||
.element {
|
||||
outline: 2px dotted black;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -373,12 +476,16 @@ test('outlines are retrieved without default outline-offset', () => {
|
||||
})
|
||||
|
||||
test('font-family values are joined when an array', () => {
|
||||
const input = `
|
||||
.element { font-family: theme('fontFamily.sans'); }
|
||||
let input = css`
|
||||
.element {
|
||||
font-family: theme('fontFamily.sans');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.element { font-family: Helvetica, Arial, sans-serif; }
|
||||
let output = css`
|
||||
.element {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -394,12 +501,16 @@ test('font-family values are joined when an array', () => {
|
||||
})
|
||||
|
||||
test('box-shadow values are joined when an array', () => {
|
||||
const input = `
|
||||
.element { box-shadow: theme('boxShadow.wtf'); }
|
||||
let input = css`
|
||||
.element {
|
||||
box-shadow: theme('boxShadow.wtf');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.element { box-shadow: 0 0 2px black, 1px 2px 3px white; }
|
||||
let output = css`
|
||||
.element {
|
||||
box-shadow: 0 0 2px black, 1px 2px 3px white;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -415,12 +526,16 @@ test('box-shadow values are joined when an array', () => {
|
||||
})
|
||||
|
||||
test('transition-property values are joined when an array', () => {
|
||||
const input = `
|
||||
.element { transition-property: theme('transitionProperty.colors'); }
|
||||
let input = css`
|
||||
.element {
|
||||
transition-property: theme('transitionProperty.colors');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.element { transition-property: color, fill; }
|
||||
let output = css`
|
||||
.element {
|
||||
transition-property: color, fill;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -436,12 +551,16 @@ test('transition-property values are joined when an array', () => {
|
||||
})
|
||||
|
||||
test('transition-duration values are joined when an array', () => {
|
||||
const input = `
|
||||
.element { transition-duration: theme('transitionDuration.lol'); }
|
||||
let input = css`
|
||||
.element {
|
||||
transition-duration: theme('transitionDuration.lol');
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
.element { transition-duration: 1s, 2s; }
|
||||
let output = css`
|
||||
.element {
|
||||
transition-duration: 1s, 2s;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -457,16 +576,18 @@ test('transition-duration values are joined when an array', () => {
|
||||
})
|
||||
|
||||
test('basic screen function calls are expanded', () => {
|
||||
const input = `
|
||||
let input = css`
|
||||
@media screen(sm) {
|
||||
.foo {}
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
@media (min-width: 600px) {
|
||||
.foo {}
|
||||
}
|
||||
let output = css`
|
||||
@media (min-width: 600px) {
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -478,16 +599,18 @@ test('basic screen function calls are expanded', () => {
|
||||
})
|
||||
|
||||
test('screen function supports max-width screens', () => {
|
||||
const input = `
|
||||
let input = css`
|
||||
@media screen(sm) {
|
||||
.foo {}
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
@media (max-width: 600px) {
|
||||
.foo {}
|
||||
}
|
||||
let output = css`
|
||||
@media (max-width: 600px) {
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -499,16 +622,18 @@ test('screen function supports max-width screens', () => {
|
||||
})
|
||||
|
||||
test('screen function supports min-width screens', () => {
|
||||
const input = `
|
||||
let input = css`
|
||||
@media screen(sm) {
|
||||
.foo {}
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
@media (min-width: 600px) {
|
||||
.foo {}
|
||||
}
|
||||
let output = css`
|
||||
@media (min-width: 600px) {
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -520,16 +645,18 @@ test('screen function supports min-width screens', () => {
|
||||
})
|
||||
|
||||
test('screen function supports min-width and max-width screens', () => {
|
||||
const input = `
|
||||
let input = css`
|
||||
@media screen(sm) {
|
||||
.foo {}
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
@media (min-width: 600px) and (max-width: 700px) {
|
||||
.foo {}
|
||||
}
|
||||
let output = css`
|
||||
@media (min-width: 600px) and (max-width: 700px) {
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -541,16 +668,18 @@ test('screen function supports min-width and max-width screens', () => {
|
||||
})
|
||||
|
||||
test('screen function supports raw screens', () => {
|
||||
const input = `
|
||||
let input = css`
|
||||
@media screen(mono) {
|
||||
.foo {}
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
@media monochrome {
|
||||
.foo {}
|
||||
}
|
||||
let output = css`
|
||||
@media monochrome {
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
@ -562,16 +691,18 @@ test('screen function supports raw screens', () => {
|
||||
})
|
||||
|
||||
test('screen arguments can be quoted', () => {
|
||||
const input = `
|
||||
let input = css`
|
||||
@media screen('sm') {
|
||||
.foo {}
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
@media (min-width: 600px) {
|
||||
.foo {}
|
||||
}
|
||||
let output = css`
|
||||
@media (min-width: 600px) {
|
||||
.foo {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
|
||||
98
tests/normalize-screens.test.js
Normal file
98
tests/normalize-screens.test.js
Normal file
@ -0,0 +1,98 @@
|
||||
import { normalizeScreens } from '../src/util/normalizeScreens'
|
||||
|
||||
it('should normalize an array of string values', () => {
|
||||
let screens = ['768px', '1200px']
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: '768px', values: [{ min: '768px', max: undefined }] },
|
||||
{ name: '1200px', values: [{ min: '1200px', max: undefined }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with string values', () => {
|
||||
let screens = {
|
||||
a: '768px',
|
||||
b: '1200px',
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', values: [{ min: '1200px', max: undefined }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with object values', () => {
|
||||
let screens = {
|
||||
a: { min: '768px' },
|
||||
b: { max: '1200px' },
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with multiple object values', () => {
|
||||
let screens = {
|
||||
a: [{ min: '768px' }, { max: '1200px' }],
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{
|
||||
name: 'a',
|
||||
values: [
|
||||
{ max: undefined, min: '768px', raw: undefined },
|
||||
{ max: '1200px', min: undefined, raw: undefined },
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with object values (min-width normalized to width)', () => {
|
||||
let screens = {
|
||||
a: { 'min-width': '768px' },
|
||||
b: { max: '1200px' },
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize a tuple with string values', () => {
|
||||
let screens = [
|
||||
['a', '768px'],
|
||||
['b', '1200px'],
|
||||
]
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', values: [{ min: '1200px', max: undefined }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize a tuple with object values', () => {
|
||||
let screens = [
|
||||
['a', { min: '768px' }],
|
||||
['b', { max: '1200px' }],
|
||||
]
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize a tuple with object values (min-width normalized to width)', () => {
|
||||
let screens = [
|
||||
['a', { 'min-width': '768px' }],
|
||||
['b', { max: '1200px' }],
|
||||
]
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
})
|
||||
@ -65,3 +65,51 @@ test('`@tailwind screens` works as an alias for `@tailwind variants`', async ()
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('screen names are in the correct order', () => {
|
||||
// Using custom css function here, because otherwise with String.raw, we run
|
||||
// into an issue with `\31 ` escapes. If we use `\31 ` then JS complains
|
||||
// about strict mode. But `\\31 ` is not what it expected.
|
||||
function css(templates) {
|
||||
return templates.join('')
|
||||
}
|
||||
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="768:font-light 1200:font-normal 1800:font-bold max-w-768 container"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
screens: [
|
||||
[1800, { max: '1800px' }],
|
||||
[1200, { max: '1200px' }],
|
||||
[768, { max: '768px' }],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities;', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
@media (max-width: 1800px) {
|
||||
.\\31 800\\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.\\31 200\\:font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.\\37 68\\:font-light {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user