Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
philipp-spiess committed Sep 17, 2024
1 parent 4ff479e commit 552af20
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 19 deletions.
3 changes: 3 additions & 0 deletions packages/tailwindcss/src/compat/apply-config-to-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ function resolveThemeValue(value: unknown, subValue: string | null = null): stri

export function applyConfigToTheme(designSystem: DesignSystem, { theme }: ResolvedConfig) {
for (let [path, value] of themeableValues(theme)) {
if (typeof value !== 'string' && typeof value !== 'number') {
continue
}
let name = keyPathToCssProperty(path)
designSystem.theme.add(
`--${name}`,
Expand Down
152 changes: 151 additions & 1 deletion packages/tailwindcss/src/compat/screens-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, test } from 'vitest'
import { describe, expect, test } from 'vitest'
import { compile } from '..'

const css = String.raw
Expand Down Expand Up @@ -440,3 +440,153 @@ test('JS config with `theme: { extends }` should not include the `default-config
"
`)
})

describe('complex screen configs', () => {
test('generates utilities', async () => {
let input = css`
@config "./config.js";
@tailwind utilities;
`

let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
extend: {
screens: {
sm: { max: '639px' },
md: [
//
{ min: '668px', max: '767px' },
{ min: '868px' },
],
lg: { min: '868px' },
xl: { min: '1024px', max: '1279px' },
tall: { raw: '(min-height: 800px)' },
},
},
},
}),
})

expect(
compiler.build([
'sm:flex',
'md:flex',
'lg:flex',
'xl:flex',
'tall:flex',
'min-sm:flex',
'min-md:flex',
'min-lg:flex',
'min-xl:flex',
'min-tall:flex',
// Ensure other core variants appear at the end
'print:items-end',
]),
).toMatchInlineSnapshot(`
".lg\\:flex {
@media (min-width: 868px) {
display: flex;
}
}
.print\\:items-end {
@media print {
align-items: flex-end;
}
}
.sm\\:flex {
@media (max-width: 639px) {
display: flex;
}
}
.md\\:flex {
@media (min-width: 668px and max-width: 767px), (min-width: 868px) {
display: flex;
}
}
.xl\\:flex {
@media (min-width: 1024px and max-width: 1279px) {
display: flex;
}
}
.tall\\:flex {
@media (min-height: 800px) {
display: flex;
}
}
"
`)
})

test("don't interfere with `min-*` and `max-*` variants of non-complex screen configs", async () => {
let input = css`
@theme default {
--breakpoint-sm: 39rem;
--breakpoint-md: 48rem;
}
@config "./config.js";
@tailwind utilities;
`

let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
extend: {
screens: {
sm: '40rem',
portrait: { raw: 'screen and (orientation: portrait)' },
},
},
},
}),
})

expect(
compiler.build([
'sm:flex',
'md:flex',
'portrait:flex',
'min-sm:flex',
'min-md:flex',
'min-portrait:flex',
// Ensure other core variants appear at the end
'print:items-end',
]),
).toMatchInlineSnapshot(`
":root {
--breakpoint-md: 48rem;
}
.min-sm\\:flex {
@media (width >= 40rem) {
display: flex;
}
}
.sm\\:flex {
@media (width >= 40rem) {
display: flex;
}
}
.md\\:flex {
@media (width >= 48rem) {
display: flex;
}
}
.min-md\\:flex {
@media (width >= 48rem) {
display: flex;
}
}
.portrait\\:flex {
@media screen and (orientation: portrait) {
display: flex;
}
}
.print\\:items-end {
@media print {
align-items: flex-end;
}
}
"
`)
})
})
74 changes: 58 additions & 16 deletions packages/tailwindcss/src/compat/screens-config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { rule } from '../ast'
import type { DesignSystem } from '../design-system'
import type { ResolvedConfig } from './config/types'
import DefaultTheme from './default-theme'

export function registerScreensConfig(config: ResolvedConfig, designSystem: DesignSystem) {
let screens = config.theme.screens || {}
export function registerScreensConfig(userConfig: ResolvedConfig, designSystem: DesignSystem) {
let screens = userConfig.theme.screens || {}

// We want to insert the breakpoints in the right order as best we can. In the
// core utility, all static breakpoint variants and the `min-*` functional
Expand All @@ -18,16 +17,6 @@ export function registerScreensConfig(config: ResolvedConfig, designSystem: Desi
for (let [name, value] of Object.entries(screens)) {
let coreVariant = designSystem.variants.get(name)

// Ignore defaults if they are already registered
//
// Note: We can't rely on the `designSystem.theme` for this, as it has the
// JS config values applied already. However, the DefaultTheme might not
// match what is actually already set in the designSystem since the @theme
// is set at runtime.
if (coreVariant && DefaultTheme.screens[name as 'sm'] === screens[name]) {
continue
}

// Ignore it if there's a CSS value that takes precedence over the JS config
// and the static utilities are already registered.
//
Expand All @@ -36,19 +25,72 @@ export function registerScreensConfig(config: ResolvedConfig, designSystem: Desi
// resolving this. If Theme has a different value, we know that this is not
// coming from the JS plugin and thus we don't need to handle it explicitly.
let cssValue = designSystem.theme.resolveValue(name, ['--breakpoint'])
if (coreVariant && cssValue && cssValue !== value) {
if (coreVariant && cssValue && !designSystem.theme.hasDefault(`--breakpoint-${name}`)) {
continue
}

// min-${breakpoint} and max-${breakpoint} rules do not need to be
// reconfigured, as they are using functional utilities and will not eagerly
// capture the breakpoints before the compat layer runs.
let query: string | undefined
let insertOrder: number | undefined
if (typeof value === 'string') {
query = `(width >= ${value})`
insertOrder = order
} else if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
query = value.map(ruleForComplexScreenValue).join(', ')
} else {
query = ruleForComplexScreenValue(value) ?? ''
if ('min' in value && !('max' in value)) {
insertOrder = order
}
}
} else {
continue
}

designSystem.variants.static(
name,
(ruleNode) => {
ruleNode.nodes = [rule(`@media (width >= ${value})`, ruleNode.nodes)]
ruleNode.nodes = [rule(`@media ${query}`, ruleNode.nodes)]
},
{ order },
{ order: insertOrder },
)
}
}

function allocateOrderAfter(designSystem: DesignSystem, order: number): number {
for (let [, variant] of designSystem.variants.variants) {
if (variant.order > order) variant.order++
}
designSystem.variants.compareFns = new Map(
Array.from(designSystem.variants.compareFns).map(([key, value]) => {
if (key > order) key++
return [key, value]
}),
)

return order + 1
}

function ruleForComplexScreenValue(value: object): string | null {
let query = null
if ('raw' in value && typeof value.raw === 'string') {
query = value.raw
} else {
let rules: string[] = []

if ('min' in value && typeof value.min === 'string') {
rules.push(`min-width: ${value.min}`)
}
if ('max' in value && typeof value.max === 'string') {
rules.push(`max-width: ${value.max}`)
}

if (rules.length !== 0) {
query = `(${rules.join(' and ')})`
}
}
return query
}
4 changes: 2 additions & 2 deletions packages/tailwindcss/src/variants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ type VariantFn<T extends Variant['kind']> = (
type CompareFn = (a: Variant, z: Variant) => number

export class Variants {
private compareFns = new Map<number, CompareFn>()
private variants = new Map<
public compareFns = new Map<number, CompareFn>()
public variants = new Map<
string,
{
kind: Variant['kind']
Expand Down

0 comments on commit 552af20

Please sign in to comment.