Skip to content

Add support for inline option when defining @theme values #14095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
2 changes: 1 addition & 1 deletion packages/@tailwindcss-postcss/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ test('@apply can be used without emitting the theme in the CSS file', async () =
// `@apply` is used.
let result = await processor.process(
css`
@import 'tailwindcss/theme.css' reference;
@import 'tailwindcss/theme.css' theme(reference);
.foo {
@apply text-red-500;
}
Expand Down
140 changes: 136 additions & 4 deletions packages/tailwindcss/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ describe('Parsing themes values from CSS', () => {
@theme {
--color-tomato: #e10c04;
}
@media reference {
@media theme(reference) {
@theme {
--color-potato: #ac855b;
}
Expand Down Expand Up @@ -1106,11 +1106,11 @@ describe('Parsing themes values from CSS', () => {
`)
})

test('`@media reference` can only contain `@theme` rules', () => {
test('`@media theme(…)` can only contain `@theme` rules', () => {
expect(() =>
compileCss(
css`
@media reference {
@media theme(reference) {
.not-a-theme-rule {
color: cursed;
}
Expand All @@ -1120,9 +1120,141 @@ describe('Parsing themes values from CSS', () => {
['bg-tomato', 'bg-potato', 'bg-avocado'],
),
).toThrowErrorMatchingInlineSnapshot(
`[Error: Files imported with \`@import "…" reference\` must only contain \`@theme\` blocks.]`,
`[Error: Files imported with \`@import "…" theme(…)\` must only contain \`@theme\` blocks.]`,
)
})

test('theme values added as `inline` are not wrapped in `var(…)` when used as utility values', () => {
expect(
compileCss(
css`
@theme inline {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}

@tailwind utilities;
`,
['bg-tomato', 'bg-potato', 'bg-primary'],
),
).toMatchInlineSnapshot(`
":root {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}

.bg-potato {
background-color: #ac855b;
}

.bg-primary {
background-color: var(--primary);
}

.bg-tomato {
background-color: #e10c04;
}"
`)
})

test('wrapping `@theme` with `@media theme(inline)` behaves like `@theme inline` to support `@import` statements', () => {
expect(
compileCss(
css`
@media theme(inline) {
@theme {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}
}

@tailwind utilities;
`,
['bg-tomato', 'bg-potato', 'bg-primary'],
),
).toMatchInlineSnapshot(`
":root {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}

.bg-potato {
background-color: #ac855b;
}

.bg-primary {
background-color: var(--primary);
}

.bg-tomato {
background-color: #e10c04;
}"
`)
})

test('`inline` and `reference` can be used together', () => {
expect(
compileCss(
css`
@theme reference inline {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}

@tailwind utilities;
`,
['bg-tomato', 'bg-potato', 'bg-primary'],
),
).toMatchInlineSnapshot(`
".bg-potato {
background-color: #ac855b;
}

.bg-primary {
background-color: var(--primary);
}

.bg-tomato {
background-color: #e10c04;
}"
`)
})

test('`inline` and `reference` can be used together in `media(…)`', () => {
expect(
compileCss(
css`
@media theme(reference inline) {
@theme {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}
}

@tailwind utilities;
`,
['bg-tomato', 'bg-potato', 'bg-primary'],
),
).toMatchInlineSnapshot(`
".bg-potato {
background-color: #ac855b;
}

.bg-primary {
background-color: var(--primary);
}

.bg-tomato {
background-color: #e10c04;
}"
`)
})
})

describe('plugins', () => {
Expand Down
33 changes: 21 additions & 12 deletions packages/tailwindcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,28 +152,35 @@ export function compile(
}
}

// Drop instances of `@media reference`
// Drop instances of `@media theme(…)`
//
// We support `@import "tailwindcss/theme" reference` as a way to import an external theme file
// as a reference, which becomes `@media reference { … }` when the `@import` is processed.
if (node.selector === '@media reference') {
// We support `@import "tailwindcss/theme" theme(reference)` as a way to
// import an external theme file as a reference, which becomes `@media
// theme(reference) { … }` when the `@import` is processed.
if (node.selector.startsWith('@media theme(')) {
let themeParams = node.selector.slice(13, -1)

walk(node.nodes, (child) => {
if (child.kind !== 'rule') {
throw new Error(
'Files imported with `@import "…" reference` must only contain `@theme` blocks.',
'Files imported with `@import "…" theme(…)` must only contain `@theme` blocks.',
)
}
if (child.selector === '@theme') {
child.selector = '@theme reference'
child.selector = '@theme ' + themeParams
return WalkAction.Skip
}
})
replaceWith(node.nodes)
return WalkAction.Skip
}

if (node.selector !== '@theme' && node.selector !== '@theme reference') return
if (node.selector !== '@theme' && !node.selector.startsWith('@theme ')) return

let themeOptions = segment(node.selector, ' ')

let isReference = node.selector === '@theme reference'
let isReference = themeOptions.includes('reference', 1)
let isInline = themeOptions.includes('inline', 1)

// Record all custom properties in the `@theme` declaration
walk(node.nodes, (child, { replaceWith }) => {
Expand All @@ -187,7 +194,7 @@ export function compile(

if (child.kind === 'comment') return
if (child.kind === 'declaration' && child.property.startsWith('--')) {
theme.add(child.property, child.value, isReference)
theme.add(child.property, child.value, { isReference, isInline })
return
}

Expand Down Expand Up @@ -395,15 +402,17 @@ export function __unstable__loadDesignSystem(css: string) {

walk(ast, (node) => {
if (node.kind !== 'rule') return
if (node.selector !== '@theme' && node.selector !== '@theme reference') return
let isReference = node.selector === '@theme reference'
if (node.selector !== '@theme' && !node.selector.startsWith('@theme ')) return
let themeOptions = segment(node.selector, ' ')
let isReference = themeOptions.includes('reference', 1)
let isInline = themeOptions.includes('inline', 1)

// Record all custom properties in the `@theme` declaration
walk([node], (node) => {
if (node.kind !== 'declaration') return
if (!node.property.startsWith('--')) return

theme.add(node.property, node.value, isReference)
theme.add(node.property, node.value, { isReference, isInline })
})
})

Expand Down
14 changes: 11 additions & 3 deletions packages/tailwindcss/src/theme.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { escape } from './utils/escape'

export class Theme {
constructor(private values = new Map<string, { value: string; isReference: boolean }>()) {}
constructor(
private values = new Map<string, { value: string; isReference: boolean; isInline: boolean }>(),
) {}

add(key: string, value: string, isReference = false): void {
add(key: string, value: string, { isReference = false, isInline = false } = {}): void {
if (key.endsWith('-*')) {
if (value !== 'initial') {
throw new Error(`Invalid theme value \`${value}\` for namespace \`${key}\``)
Expand All @@ -18,7 +20,7 @@ export class Theme {
if (value === 'initial') {
this.values.delete(key)
} else {
this.values.set(key, { value, isReference })
this.values.set(key, { value, isReference, isInline })
}
}

Expand Down Expand Up @@ -91,6 +93,12 @@ export class Theme {

if (!themeKey) return null

let value = this.values.get(themeKey)!

if (value.isInline) {
return value.value
}

return this.#var(themeKey)
}

Expand Down
Loading