Ensure --default-font-* and --default-mono-font-* variables respect theme customizations in JS config files (#14344)
This PR fixes an issue where variables like `--default-font-family` wouldn't behave as expected when customizing `fontFamily.sans` or `fontFamily.mono` in a JS config. Because theme values added by JS config files are added as `reference`, customizing `fontFamily.sans` means the `--font-family-sans` variable no longer exists in the generated CSS. The `--default-font-family` variable is set to `var(--font-family-sans)` by default, so because that variable doesn't exist, `--default-font-family` is effectively undefined and the browser default font stack is used. This is unexpected because historically customizing `fontFamily.sans` has updated your default font for your entire project. --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
parent
262e99e5a9
commit
530774b186
4 changed files with 555 additions and 2 deletions
|
|
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Don’t suggest named opacity modifiers in intellisense ([#14339](https://github.com/tailwindlabs/tailwindcss/pull/14339))
|
||||
- Fix a crash with older Node.js versions ([#14342](https://github.com/tailwindlabs/tailwindcss/pull/14342))
|
||||
- Support defining theme values as arrays of strings in JS config files ([#14343](https://github.com/tailwindlabs/tailwindcss/pull/14343))
|
||||
- Ensure `--default-font-*` and `--default-mono-font-*` variables respect theme customizations in JS config files ([#14344](https://github.com/tailwindlabs/tailwindcss/pull/14344))
|
||||
|
||||
## [4.0.0-alpha.21] - 2024-09-02
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,28 @@ import type { DesignSystem } from '../design-system'
|
|||
import { resolveConfig, type ConfigFile } from './config/resolve-config'
|
||||
import type { ResolvedConfig } from './config/types'
|
||||
|
||||
function resolveThemeValue(value: unknown, subValue: string | null = null): string | null {
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
value.length === 2 &&
|
||||
typeof value[1] === 'object' &&
|
||||
typeof value[1] !== null
|
||||
) {
|
||||
return subValue ? (value[1][subValue] ?? null) : value[0]
|
||||
} else if (Array.isArray(value) && subValue === null) {
|
||||
return value.join(', ')
|
||||
} else if (typeof value === 'string' && subValue === null) {
|
||||
return value
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function applyConfigToTheme(designSystem: DesignSystem, configs: ConfigFile[]) {
|
||||
let theme = resolveConfig(designSystem, configs).theme
|
||||
|
||||
for (let [path, value] of themeableValues(theme)) {
|
||||
let name = keyPathToCssProperty(path)
|
||||
|
||||
designSystem.theme.add(`--${name}`, value as any, {
|
||||
isInline: true,
|
||||
isReference: true,
|
||||
|
|
@ -15,6 +31,56 @@ export function applyConfigToTheme(designSystem: DesignSystem, configs: ConfigFi
|
|||
})
|
||||
}
|
||||
|
||||
// If someone has updated `fontFamily.sans` or `fontFamily.mono` in a JS
|
||||
// config, we need to make sure variables like `--default-font-family` and
|
||||
// `--default-font-feature-settings` are updated to match those explicit
|
||||
// values, because variables like `--font-family-sans` and
|
||||
// `--font-family-sans--feature-settings` (which the `--default-font-*`
|
||||
// variables reference) won't exist in the generated CSS.
|
||||
if (Object.hasOwn(theme, 'fontFamily')) {
|
||||
let options = {
|
||||
isInline: true,
|
||||
isReference: false,
|
||||
isDefault: true,
|
||||
}
|
||||
|
||||
// Replace `--default-font-*` with `fontFamily.sans` values
|
||||
{
|
||||
let fontFamily = resolveThemeValue(theme.fontFamily.sans)
|
||||
if (fontFamily && designSystem.theme.hasDefault('--font-family-sans')) {
|
||||
designSystem.theme.add('--default-font-family', fontFamily, options)
|
||||
designSystem.theme.add(
|
||||
'--default-font-feature-settings',
|
||||
resolveThemeValue(theme.fontFamily.sans, 'fontFeatureSettings') ?? 'normal',
|
||||
options,
|
||||
)
|
||||
designSystem.theme.add(
|
||||
'--default-font-variation-settings',
|
||||
resolveThemeValue(theme.fontFamily.sans, 'fontVariationSettings') ?? 'normal',
|
||||
options,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace `--default-mono-font-*` with `fontFamily.mono` values
|
||||
{
|
||||
let fontFamily = resolveThemeValue(theme.fontFamily.mono)
|
||||
if (fontFamily && designSystem.theme.hasDefault('--font-family-mono')) {
|
||||
designSystem.theme.add('--default-mono-font-family', 'theme(fontFamily.mono)', options)
|
||||
designSystem.theme.add(
|
||||
'--default-mono-font-feature-settings',
|
||||
resolveThemeValue(theme.fontFamily.mono, 'fontFeatureSettings') ?? 'normal',
|
||||
options,
|
||||
)
|
||||
designSystem.theme.add(
|
||||
'--default-mono-font-variation-settings',
|
||||
resolveThemeValue(theme.fontFamily.mono, 'fontVariationSettings') ?? 'normal',
|
||||
options,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return theme
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { test } from 'vitest'
|
||||
import { describe, test } from 'vitest'
|
||||
import { compile } from '..'
|
||||
import plugin from '../plugin'
|
||||
|
||||
|
|
@ -229,3 +229,485 @@ test('Variants in CSS overwrite variants from plugins', async ({ expect }) => {
|
|||
"
|
||||
`)
|
||||
})
|
||||
|
||||
describe('default font family compatibility', () => {
|
||||
test('overriding `fontFamily.sans` sets `--default-font-family`', async ({ expect }) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: 'Potato Sans',
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-font-family: Potato Sans;
|
||||
--default-font-feature-settings: normal;
|
||||
--default-font-variation-settings: normal;
|
||||
}
|
||||
.font-sans {
|
||||
font-family: Potato Sans;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.sans[1].fontFeatureSettings` sets `--default-font-feature-settings`', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-font-family: Potato Sans;
|
||||
--default-font-feature-settings: "cv06";
|
||||
--default-font-variation-settings: normal;
|
||||
}
|
||||
.font-sans {
|
||||
font-family: Potato Sans;
|
||||
font-feature-settings: "cv06";
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.sans[1].fontVariationSettings` sets `--default-font-variation-settings`', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-font-family: Potato Sans;
|
||||
--default-font-feature-settings: normal;
|
||||
--default-font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
.font-sans {
|
||||
font-family: Potato Sans;
|
||||
font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.sans` sets `--default-font-feature-settings` and `--default-font-variation-settings`', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'Potato Sans',
|
||||
{ fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-font-family: Potato Sans;
|
||||
--default-font-feature-settings: "cv06";
|
||||
--default-font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
.font-sans {
|
||||
font-family: Potato Sans;
|
||||
font-feature-settings: "cv06";
|
||||
font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `--font-family-sans` in `@theme` without `default` preserves the original `--default-font-*` values', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@theme {
|
||||
--font-family-sans: Sandwich Sans;
|
||||
}
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: 'Potato Sans',
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
--font-family-sans: Sandwich Sans;
|
||||
}
|
||||
.font-sans {
|
||||
font-family: var(--font-family-sans, Sandwich Sans);
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.sans` in a config file with an array sets `--default-font-family`', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-font-family: Inter, system-ui, sans-serif;
|
||||
--default-font-feature-settings: normal;
|
||||
--default-font-variation-settings: normal;
|
||||
}
|
||||
.font-sans {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.sans` in a config file with an unexpected type is ignored', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: { foo: 'bar', banana: 'sandwich' },
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-font-family: var(--font-family-sans);
|
||||
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async ({ expect }) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
mono: 'Potato Mono',
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-mono-font-family: Potato Mono;
|
||||
--default-mono-font-feature-settings: normal;
|
||||
--default-mono-font-variation-settings: normal;
|
||||
}
|
||||
.font-mono {
|
||||
font-family: Potato Mono;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.mono[1].fontFeatureSettings` sets `--default-mono-font-feature-settings`', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-mono-font-family: Potato Mono;
|
||||
--default-mono-font-feature-settings: "cv06";
|
||||
--default-mono-font-variation-settings: normal;
|
||||
}
|
||||
.font-mono {
|
||||
font-family: Potato Mono;
|
||||
font-feature-settings: "cv06";
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.mono[1].fontVariationSettings` sets `--default-mono-font-variation-settings`', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-mono-font-family: Potato Mono;
|
||||
--default-mono-font-feature-settings: normal;
|
||||
--default-mono-font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
.font-mono {
|
||||
font-family: Potato Mono;
|
||||
font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.mono` sets `--default-mono-font-feature-settings` and `--default-mono-font-variation-settings`', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
mono: [
|
||||
'Potato Mono',
|
||||
{ fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-mono-font-family: Potato Mono;
|
||||
--default-mono-font-feature-settings: "cv06";
|
||||
--default-mono-font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
.font-mono {
|
||||
font-family: Potato Mono;
|
||||
font-feature-settings: "cv06";
|
||||
font-variation-settings: "XHGT" 0.7;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `--font-family-mono` in `@theme` without `default` preserves the original `--default-mono-font-*` values', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@theme {
|
||||
--font-family-mono: Sandwich Mono;
|
||||
}
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
mono: 'Potato Mono',
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
--font-family-mono: Sandwich Mono;
|
||||
}
|
||||
.font-mono {
|
||||
font-family: var(--font-family-mono, Sandwich Mono);
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overriding `fontFamily.mono` in a config file with an unexpected type is ignored', async ({
|
||||
expect,
|
||||
}) => {
|
||||
let input = css`
|
||||
@theme default {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
}
|
||||
@config "./config.js";
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let compiler = await compile(input, {
|
||||
loadConfig: async () => ({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
mono: { foo: 'bar', banana: 'sandwich' },
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--default-mono-font-family: var(--font-family-mono);
|
||||
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ export class Theme {
|
|||
return null
|
||||
}
|
||||
|
||||
hasDefault(key: string): boolean {
|
||||
return this.values.get(key)?.isDefault ?? false
|
||||
}
|
||||
|
||||
entries() {
|
||||
return this.values.entries()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue