Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Allow JS theme to extend CSS theme
  • Loading branch information
philipp-spiess committed Sep 17, 2024
commit 5fbc78206aecf7124ffc2b6eec3fd411b4e8b87d
96 changes: 95 additions & 1 deletion packages/tailwindcss/src/compat/screens-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { compile } from '..'

const css = String.raw

test('merges CSS `--breakpoint-*` with JS config `screens`', async () => {
test('CSS `--breakpoint-*` merge with JS config `screens`', async () => {
let input = css`
@theme default {
--breakpoint-sm: 40rem;
Expand Down Expand Up @@ -85,3 +85,97 @@ test('merges CSS `--breakpoint-*` with JS config `screens`', async () => {
"
`)
})

test('JS config `screens` extend CSS `--breakpoint-*`', async () => {
let input = css`
@theme default {
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
}
@theme {
--breakpoint-md: 50rem;
}
@config "./config.js";
@tailwind utilities;
`

let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
extend: {
screens: {
xs: '30rem',
sm: '44rem',
md: '49rem',
lg: '64rem',
},
},
},
}),
})

expect(
compiler.build([
// Order is messed up on purpose
'md:flex',
'sm:flex',
'lg:flex',
'xs:flex',
'min-md:max-lg:underline',
'min-sm:max-md:underline',
'min-xs:max-md:underline',

// Ensure other core variants appear at the end
'print:items-end',
]),
).toMatchInlineSnapshot(`
":root {
--breakpoint-md: 50rem;
}
.xs\\:flex {
@media (width >= 30rem) {
display: flex;
}
}
.min-xs\\:max-md\\:underline {
@media (width >= 30rem) {
@media (width < 50rem) {
text-decoration-line: underline;
}
}
}
.sm\\:flex {
@media (width >= 44rem) {
display: flex;
}
}
.min-sm\\:max-md\\:underline {
@media (width >= 44rem) {
@media (width < 50rem) {
text-decoration-line: underline;
}
}
}
.md\\:flex {
@media (width >= 50rem) {
display: flex;
}
}
.min-md\\:max-lg\\:underline {
@media (width >= 50rem) {
@media (width < 64rem) {
text-decoration-line: underline;
}
}
}
.print\\:items-end {
@media print {
align-items: flex-end;
}
}
"
`)
})

test('JS config `screens` only setup')
test('JS config `screens` overwrite CSS `--breakpoint-*`')
32 changes: 26 additions & 6 deletions packages/tailwindcss/src/compat/screens-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,35 @@ import DefaultTheme from './default-theme'
export function registerScreensConfig(config: ResolvedConfig, designSystem: DesignSystem) {
let screens = config.theme.screens || {}

// We want to insert the breakpoints in the right order as best we can, we
// know that the `max` functional variant is added before all min variants, so
// we use this as our lower bound.
let lastKnownOrder = designSystem.variants.get('max')?.order ?? 0

// Case 1: All theme config are simple (non-nested-object) values. This means
// all breakpoints are used as `min` values, like we do in the core.
for (let [name, value] of Object.entries(screens)) {
// Ignore defaults
if (DefaultTheme.screens[name as 'sm'] === screens[name]) continue

let coreVariant = designSystem.variants.get(name)
if (!coreVariant) {
throw new Error('not yet supported, needs more thought')

// Ignore defaults, but update the order accordingly
//
// Note: We can't rely on the `designSystem.theme` for this, as it has the
// JS config values applied already.
if (DefaultTheme.screens[name as 'sm'] === screens[name]) {
if (coreVariant) lastKnownOrder = coreVariant.order
continue
}

// Ignore it if there's a CSS value that takes precedence over the JS config
//
// This happens when a `@theme { }` block is used that overwrites all JS
// config options. We rely on the order inside the Theme for 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 (cssValue && cssValue !== value) {
if (coreVariant) lastKnownOrder = coreVariant.order
continue
}

// min-${breakpoint} and max-${breakpoint} rules do not need to be
Expand All @@ -25,7 +45,7 @@ export function registerScreensConfig(config: ResolvedConfig, designSystem: Desi
(ruleNode) => {
ruleNode.nodes = [rule(`@media (width >= ${value})`, ruleNode.nodes)]
},
{ order: coreVariant.order },
{ order: lastKnownOrder },
)
}
}