diff --git a/CHANGELOG.md b/CHANGELOG.md index a30cd6914..19f107a6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Handle arrays in the CSS `theme()` function when using plugins ([#14262](https://github.com/tailwindlabs/tailwindcss/pull/14262)) - Fix fallback values when using the CSS `theme()` function ([#14262](https://github.com/tailwindlabs/tailwindcss/pull/14262)) - Fix support for declaration fallbacks in plugins ([#14265](https://github.com/tailwindlabs/tailwindcss/pull/14265)) +- Support bare values when using `tailwindcss/defaultTheme` ([#14257](https://github.com/tailwindlabs/tailwindcss/pull/14257)) ## [4.0.0-alpha.20] - 2024-08-23 diff --git a/packages/tailwindcss/src/compat/config/create-compat-config.ts b/packages/tailwindcss/src/compat/config/create-compat-config.ts index 540886f0d..785167db7 100644 --- a/packages/tailwindcss/src/compat/config/create-compat-config.ts +++ b/packages/tailwindcss/src/compat/config/create-compat-config.ts @@ -1,179 +1,26 @@ -import type { NamedUtilityValue } from '../../candidate' import type { Theme } from '../../theme' -import { segment } from '../../utils/segment' +import defaultTheme from '../default-theme' import type { UserConfig } from './types' -function bareValues(fn: (value: NamedUtilityValue) => string | undefined) { - return { - // Ideally this would be a Symbol but some of the ecosystem assumes object with - // string / number keys for example by using `Object.entries()` which means that - // the function that handles the bare value would be lost - __BARE_VALUE__: fn, - } -} - -let bareIntegers = bareValues((value) => { - if (!Number.isNaN(Number(value.value))) { - return value.value - } -}) - -let barePercentages = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}%` - } -}) - -let barePixels = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}px` - } -}) - -let bareMilliseconds = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}ms` - } -}) - -let bareDegrees = bareValues((value: NamedUtilityValue) => { - if (!Number.isNaN(Number(value.value))) { - return `${value.value}deg` - } -}) - -export function createCompatConfig(theme: Theme): UserConfig { +export function createCompatConfig(cssTheme: Theme): UserConfig { return { theme: { + ...defaultTheme, + + // In the defaultTheme config, the `colors` key is not a function but a + // shallow object. We don't want to define the color namespace unless it + // is in the CSS theme so here we explicitly overwrite the defaultTheme + // and only allow colors from the CSS theme. colors: ({ theme }) => theme('color', {}), - accentColor: ({ theme }) => theme('colors'), - aspectRatio: bareValues((value) => { - if (value.fraction === null) return - let [lhs, rhs] = segment(value.fraction, '/') - if (!Number.isInteger(Number(lhs)) || !Number.isInteger(Number(rhs))) return - return value.fraction - }), - backdropBlur: ({ theme }) => theme('blur'), - backdropBrightness: ({ theme }) => ({ - ...theme('brightness'), - ...barePercentages, - }), - backdropContrast: ({ theme }) => ({ - ...theme('contrast'), - ...barePercentages, - }), - backdropGrayscale: ({ theme }) => ({ - ...theme('grayscale'), - ...barePercentages, - }), - backdropHueRotate: ({ theme }) => ({ - ...theme('hueRotate'), - ...bareDegrees, - }), - backdropInvert: ({ theme }) => ({ - ...theme('invert'), - ...barePercentages, - }), - backdropOpacity: ({ theme }) => ({ - ...theme('opacity'), - ...barePercentages, - }), - backdropSaturate: ({ theme }) => ({ - ...theme('saturate'), - ...barePercentages, - }), - backdropSepia: ({ theme }) => ({ - ...theme('sepia'), - ...barePercentages, - }), - backgroundColor: ({ theme }) => theme('colors'), - backgroundOpacity: ({ theme }) => theme('opacity'), - border: barePixels, - borderColor: ({ theme }) => theme('colors'), - borderOpacity: ({ theme }) => theme('opacity'), - borderSpacing: ({ theme }) => theme('spacing'), - boxShadowColor: ({ theme }) => theme('colors'), - brightness: barePercentages, - caretColor: ({ theme }) => theme('colors'), - columns: bareIntegers, - contrast: barePercentages, - divideColor: ({ theme }) => theme('borderColor'), - divideOpacity: ({ theme }) => theme('borderOpacity'), - divideWidth: ({ theme }) => ({ - ...theme('borderWidth'), - ...barePixels, - }), - fill: ({ theme }) => theme('colors'), - flexBasis: ({ theme }) => theme('spacing'), - flexGrow: bareIntegers, - flexShrink: bareIntegers, - gap: ({ theme }) => theme('spacing'), - gradientColorStopPositions: barePercentages, - gradientColorStops: ({ theme }) => theme('colors'), - grayscale: barePercentages, - gridRowEnd: bareIntegers, - gridRowStart: bareIntegers, - gridTemplateColumns: bareValues((value) => { - if (!Number.isNaN(Number(value.value))) { - return `repeat(${value.value}, minmax(0, 1fr))` - } - }), - gridTemplateRows: bareValues((value) => { - if (!Number.isNaN(Number(value.value))) { - return `repeat(${value.value}, minmax(0, 1fr))` - } - }), - height: ({ theme }) => theme('spacing'), - hueRotate: bareDegrees, - inset: ({ theme }) => theme('spacing'), - invert: barePercentages, - lineClamp: bareIntegers, - margin: ({ theme }) => theme('spacing'), - maxHeight: ({ theme }) => theme('spacing'), - maxWidth: ({ theme }) => theme('spacing'), - minHeight: ({ theme }) => theme('spacing'), - minWidth: ({ theme }) => theme('spacing'), - opacity: barePercentages, - order: bareIntegers, - outlineColor: ({ theme }) => theme('colors'), - outlineOffset: barePixels, - outlineWidth: barePixels, - padding: ({ theme }) => theme('spacing'), - placeholderColor: ({ theme }) => theme('colors'), - placeholderOpacity: ({ theme }) => theme('opacity'), - ringColor: ({ theme }) => theme('colors'), - ringOffsetColor: ({ theme }) => theme('colors'), - ringOffsetWidth: barePixels, - ringOpacity: ({ theme }) => theme('opacity'), - ringWidth: barePixels, - rotate: bareDegrees, - saturate: barePercentages, - scale: barePercentages, - scrollMargin: ({ theme }) => theme('spacing'), - scrollPadding: ({ theme }) => theme('spacing'), - sepia: barePercentages, - size: ({ theme }) => theme('spacing'), - skew: bareDegrees, - space: ({ theme }) => theme('spacing'), - stroke: ({ theme }) => theme('colors'), - strokeWidth: barePixels, - textColor: ({ theme }) => theme('colors'), - textDecorationColor: ({ theme }) => theme('colors'), - textDecorationThickness: barePixels, - textIndent: ({ theme }) => theme('spacing'), - textOpacity: ({ theme }) => theme('opacity'), - textUnderlineOffset: barePixels, - transitionDelay: bareMilliseconds, + transitionDuration: { - DEFAULT: theme.get(['--default-transition-duration']) ?? null, - ...bareMilliseconds, + ...defaultTheme.transitionDuration, + DEFAULT: cssTheme.get(['--default-transition-duration']) ?? null, }, transitionTimingFunction: { - DEFAULT: theme.get(['--default-transition-timing-function']) ?? null, + ...defaultTheme.transitionTimingFunction, + DEFAULT: cssTheme.get(['--default-transition-timing-function']) ?? null, }, - translate: ({ theme }) => theme('spacing'), - width: ({ theme }) => theme('spacing'), - zIndex: bareIntegers, }, } } diff --git a/packages/tailwindcss/src/compat/default-theme.ts b/packages/tailwindcss/src/compat/default-theme.ts index c64b7136c..5b5c3d2ab 100644 --- a/packages/tailwindcss/src/compat/default-theme.ts +++ b/packages/tailwindcss/src/compat/default-theme.ts @@ -1,10 +1,62 @@ -import { Theme } from '../theme' -import { createCompatConfig } from './config/create-compat-config' +import type { NamedUtilityValue } from '../candidate' +import { segment } from '../utils/segment' +import colors from './colors' +import type { UserConfig } from './config/types' -let theme = new Theme() +function bareValues(fn: (value: NamedUtilityValue) => string | undefined) { + return { + // Ideally this would be a Symbol but some of the ecosystem assumes object with + // string / number keys for example by using `Object.entries()` which means that + // the function that handles the bare value would be lost + __BARE_VALUE__: fn, + } +} + +let bareIntegers = bareValues((value) => { + if (!Number.isNaN(Number(value.value))) { + return value.value + } +}) + +let barePercentages = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}%` + } +}) + +let barePixels = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}px` + } +}) + +let bareMilliseconds = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}ms` + } +}) + +let bareDegrees = bareValues((value: NamedUtilityValue) => { + if (!Number.isNaN(Number(value.value))) { + return `${value.value}deg` + } +}) + +let bareAspectRatio = bareValues((value) => { + if (value.fraction === null) return + let [lhs, rhs] = segment(value.fraction, '/') + if (!Number.isInteger(Number(lhs)) || !Number.isInteger(Number(rhs))) return + return value.fraction +}) + +let bareRepeatValues = bareValues((value) => { + if (!Number.isNaN(Number(value.value))) { + return `repeat(${value.value}, minmax(0, 1fr))` + } +}) export default { - ...createCompatConfig(theme).theme, + accentColor: ({ theme }) => theme('colors'), animation: { none: 'none', spin: 'spin 1s linear infinite', @@ -27,7 +79,42 @@ export default { auto: 'auto', square: '1 / 1', video: '16 / 9', + ...bareAspectRatio, }, + backdropBlur: ({ theme }) => theme('blur'), + backdropBrightness: ({ theme }) => ({ + ...theme('brightness'), + ...barePercentages, + }), + backdropContrast: ({ theme }) => ({ + ...theme('contrast'), + ...barePercentages, + }), + backdropGrayscale: ({ theme }) => ({ + ...theme('grayscale'), + ...barePercentages, + }), + backdropHueRotate: ({ theme }) => ({ + ...theme('hueRotate'), + ...bareDegrees, + }), + backdropInvert: ({ theme }) => ({ + ...theme('invert'), + ...barePercentages, + }), + backdropOpacity: ({ theme }) => ({ + ...theme('opacity'), + ...barePercentages, + }), + backdropSaturate: ({ theme }) => ({ + ...theme('saturate'), + ...barePercentages, + }), + backdropSepia: ({ theme }) => ({ + ...theme('sepia'), + ...barePercentages, + }), + backgroundColor: ({ theme }) => theme('colors'), backgroundImage: { none: 'none', 'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))', @@ -39,6 +126,7 @@ export default { 'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))', 'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))', }, + backgroundOpacity: ({ theme }) => theme('opacity'), backgroundPosition: { bottom: 'bottom', center: 'center', @@ -66,6 +154,11 @@ export default { '2xl': '40px', '3xl': '64px', }, + borderColor: ({ theme }) => ({ + DEFAULT: 'currentColor', + ...theme('colors'), + }), + borderOpacity: ({ theme }) => theme('opacity'), borderRadius: { none: '0px', sm: '0.125rem', @@ -77,12 +170,14 @@ export default { '3xl': '1.5rem', full: '9999px', }, + borderSpacing: ({ theme }) => theme('spacing'), borderWidth: { DEFAULT: '1px', 0: '0px', 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, boxShadow: { sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)', @@ -94,6 +189,7 @@ export default { inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)', none: 'none', }, + boxShadowColor: ({ theme }) => theme('colors'), brightness: { 0: '0', 50: '.5', @@ -106,7 +202,10 @@ export default { 125: '1.25', 150: '1.5', 200: '2', + ...barePercentages, }, + caretColor: ({ theme }) => theme('colors'), + colors: () => ({ ...colors }), columns: { auto: 'auto', 1: '1', @@ -134,6 +233,7 @@ export default { '5xl': '64rem', '6xl': '72rem', '7xl': '80rem', + ...bareIntegers, }, container: {}, content: { @@ -147,6 +247,7 @@ export default { 125: '1.25', 150: '1.5', 200: '2', + ...barePercentages, }, cursor: { auto: 'auto', @@ -186,6 +287,12 @@ export default { 'zoom-in': 'zoom-in', 'zoom-out': 'zoom-out', }, + divideColor: ({ theme }) => theme('borderColor'), + divideOpacity: ({ theme }) => theme('borderOpacity'), + divideWidth: ({ theme }) => ({ + ...theme('borderWidth'), + ...barePixels, + }), dropShadow: { sm: '0 1px 1px rgb(0 0 0 / 0.05)', DEFAULT: ['0 1px 2px rgb(0 0 0 / 0.1)', '0 1px 1px rgb(0 0 0 / 0.06)'], @@ -195,19 +302,53 @@ export default { '2xl': '0 25px 25px rgb(0 0 0 / 0.15)', none: '0 0 #0000', }, + fill: ({ theme }) => theme('colors'), flex: { 1: '1 1 0%', auto: '1 1 auto', initial: '0 1 auto', none: 'none', }, + flexBasis: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + '1/12': '8.333333%', + '2/12': '16.666667%', + '3/12': '25%', + '4/12': '33.333333%', + '5/12': '41.666667%', + '6/12': '50%', + '7/12': '58.333333%', + '8/12': '66.666667%', + '9/12': '75%', + '10/12': '83.333333%', + '11/12': '91.666667%', + full: '100%', + ...theme('spacing'), + }), flexGrow: { 0: '0', DEFAULT: '1', + ...bareIntegers, }, flexShrink: { 0: '0', DEFAULT: '1', + ...bareIntegers, }, fontFamily: { sans: [ @@ -257,6 +398,8 @@ export default { extrabold: '800', black: '900', }, + gap: ({ theme }) => theme('spacing'), + gradientColorStops: ({ theme }) => theme('colors'), gradientColorStopPositions: { '0%': '0%', '5%': '5%', @@ -279,10 +422,12 @@ export default { '90%': '90%', '95%': '95%', '100%': '100%', + ...barePercentages, }, grayscale: { 0: '0', DEFAULT: '100%', + ...barePercentages, }, gridAutoColumns: { auto: 'auto', @@ -327,6 +472,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridColumnStart: { auto: 'auto', @@ -343,6 +489,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridRow: { auto: 'auto', @@ -375,6 +522,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridRowStart: { auto: 'auto', @@ -391,6 +539,7 @@ export default { 11: '11', 12: '12', 13: '13', + ...bareIntegers, }, gridTemplateColumns: { none: 'none', @@ -407,6 +556,7 @@ export default { 10: 'repeat(10, minmax(0, 1fr))', 11: 'repeat(11, minmax(0, 1fr))', 12: 'repeat(12, minmax(0, 1fr))', + ...bareRepeatValues, }, gridTemplateRows: { none: 'none', @@ -423,7 +573,35 @@ export default { 10: 'repeat(10, minmax(0, 1fr))', 11: 'repeat(11, minmax(0, 1fr))', 12: 'repeat(12, minmax(0, 1fr))', + ...bareRepeatValues, }, + height: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + full: '100%', + screen: '100vh', + svh: '100svh', + lvh: '100lvh', + dvh: '100dvh', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), hueRotate: { 0: '0deg', 15: '15deg', @@ -431,10 +609,23 @@ export default { 60: '60deg', 90: '90deg', 180: '180deg', + ...bareDegrees, }, + inset: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + full: '100%', + ...theme('spacing'), + }), invert: { 0: '0', DEFAULT: '100%', + ...barePercentages, }, keyframes: { spin: { @@ -496,6 +687,10 @@ export default { listStyleImage: { none: 'none', }, + margin: ({ theme }) => ({ + auto: 'auto', + ...theme('spacing'), + }), lineClamp: { 1: '1', 2: '2', @@ -503,7 +698,58 @@ export default { 4: '4', 5: '5', 6: '6', + ...bareIntegers, }, + maxHeight: ({ theme }) => ({ + none: 'none', + full: '100%', + screen: '100vh', + svh: '100svh', + lvh: '100lvh', + dvh: '100dvh', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), + maxWidth: ({ theme }) => ({ + none: 'none', + xs: '20rem', + sm: '24rem', + md: '28rem', + lg: '32rem', + xl: '36rem', + '2xl': '42rem', + '3xl': '48rem', + '4xl': '56rem', + '5xl': '64rem', + '6xl': '72rem', + '7xl': '80rem', + full: '100%', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + prose: '65ch', + ...theme('spacing'), + }), + minHeight: ({ theme }) => ({ + full: '100%', + screen: '100vh', + svh: '100svh', + lvh: '100lvh', + dvh: '100dvh', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), + minWidth: ({ theme }) => ({ + full: '100%', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), objectPosition: { bottom: 'bottom', center: 'center', @@ -537,6 +783,7 @@ export default { 90: '0.9', 95: '0.95', 100: '1', + ...barePercentages, }, order: { first: '-9999', @@ -554,13 +801,16 @@ export default { 10: '10', 11: '11', 12: '12', + ...bareIntegers, }, + outlineColor: ({ theme }) => theme('colors'), outlineOffset: { 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, outlineWidth: { 0: '0px', @@ -568,14 +818,28 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, + padding: ({ theme }) => theme('spacing'), + placeholderColor: ({ theme }) => theme('colors'), + placeholderOpacity: ({ theme }) => theme('opacity'), + ringColor: ({ theme }) => ({ + DEFAULT: 'currentColor', + ...theme('colors'), + }), + ringOffsetColor: ({ theme }) => theme('colors'), ringOffsetWidth: { 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, + ringOpacity: ({ theme }) => ({ + DEFAULT: '0.5', + ...theme('opacity'), + }), ringWidth: { DEFAULT: '3px', 0: '0px', @@ -583,6 +847,7 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, rotate: { 0: '0deg', @@ -594,6 +859,7 @@ export default { 45: '45deg', 90: '90deg', 180: '180deg', + ...bareDegrees, }, saturate: { 0: '0', @@ -601,6 +867,7 @@ export default { 100: '1', 150: '1.5', 200: '2', + ...barePercentages, }, scale: { 0: '0', @@ -613,6 +880,7 @@ export default { 110: '1.1', 125: '1.25', 150: '1.5', + ...barePercentages, }, screens: { sm: '640px', @@ -621,9 +889,12 @@ export default { xl: '1280px', '2xl': '1536px', }, + scrollMargin: ({ theme }) => theme('spacing'), + scrollPadding: ({ theme }) => theme('spacing'), sepia: { 0: '0', DEFAULT: '100%', + ...barePercentages, }, skew: { 0: '0deg', @@ -632,7 +903,9 @@ export default { 3: '3deg', 6: '6deg', 12: '12deg', + ...bareDegrees, }, + space: ({ theme }) => theme('spacing'), spacing: { px: '1px', 0: '0px', @@ -670,13 +943,20 @@ export default { 80: '20rem', 96: '24rem', }, + stroke: ({ theme }) => ({ + none: 'none', + ...theme('colors'), + }), strokeWidth: { 0: '0', 1: '1', 2: '2', + ...bareIntegers, }, supports: {}, data: {}, + textColor: ({ theme }) => theme('colors'), + textDecorationColor: ({ theme }) => theme('colors'), textDecorationThickness: { auto: 'auto', 'from-font': 'from-font', @@ -685,7 +965,10 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, + textIndent: ({ theme }) => theme('spacing'), + textOpacity: ({ theme }) => theme('opacity'), textUnderlineOffset: { auto: 'auto', 0: '0px', @@ -693,6 +976,7 @@ export default { 2: '2px', 4: '4px', 8: '8px', + ...barePixels, }, transformOrigin: { center: 'center', @@ -715,6 +999,7 @@ export default { 500: '500ms', 700: '700ms', 1000: '1000ms', + ...bareMilliseconds, }, transitionDuration: { DEFAULT: '150ms', @@ -727,6 +1012,7 @@ export default { 500: '500ms', 700: '700ms', 1000: '1000ms', + ...bareMilliseconds, }, transitionProperty: { none: 'none', @@ -745,6 +1031,89 @@ export default { out: 'cubic-bezier(0, 0, 0.2, 1)', 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', }, + translate: ({ theme }) => ({ + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + full: '100%', + ...theme('spacing'), + }), + size: ({ theme }) => ({ + auto: 'auto', + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + '1/12': '8.333333%', + '2/12': '16.666667%', + '3/12': '25%', + '4/12': '33.333333%', + '5/12': '41.666667%', + '6/12': '50%', + '7/12': '58.333333%', + '8/12': '66.666667%', + '9/12': '75%', + '10/12': '83.333333%', + '11/12': '91.666667%', + full: '100%', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), + width: ({ theme }) => ({ + auto: 'auto', + + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + '1/12': '8.333333%', + '2/12': '16.666667%', + '3/12': '25%', + '4/12': '33.333333%', + '5/12': '41.666667%', + '6/12': '50%', + '7/12': '58.333333%', + '8/12': '66.666667%', + '9/12': '75%', + '10/12': '83.333333%', + '11/12': '91.666667%', + full: '100%', + screen: '100vw', + svw: '100svw', + lvw: '100lvw', + dvw: '100dvw', + min: 'min-content', + max: 'max-content', + fit: 'fit-content', + ...theme('spacing'), + }), willChange: { auto: 'auto', scroll: 'scroll-position', @@ -759,5 +1128,6 @@ export default { 30: '30', 40: '40', 50: '50', + ...bareIntegers, }, -} +} satisfies UserConfig['theme'] diff --git a/packages/tailwindcss/src/functions.test.ts b/packages/tailwindcss/src/functions.test.ts index 6232faba7..e6703227a 100644 --- a/packages/tailwindcss/src/functions.test.ts +++ b/packages/tailwindcss/src/functions.test.ts @@ -285,6 +285,20 @@ describe('theme function', () => { }" `) }) + + test('theme(fontFamily.sans)', async () => { + expect( + await compileCss(css` + .fam { + font-family: theme(fontFamily.sans); + } + `), + ).toMatchInlineSnapshot(` + ".fam { + font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + }" + `) + }) }) test('theme(colors.unknown.500)', async () => diff --git a/packages/tailwindcss/src/plugin-api.test.ts b/packages/tailwindcss/src/plugin-api.test.ts index 7e74572b0..d911b09e5 100644 --- a/packages/tailwindcss/src/plugin-api.test.ts +++ b/packages/tailwindcss/src/plugin-api.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test, vi } from 'vitest' import { compile } from '.' +import defaultTheme from './compat/default-theme' import plugin from './plugin' import type { CssInJs, PluginAPI } from './plugin-api' import { optimizeCss } from './test-utils/run' @@ -510,7 +511,7 @@ describe('theme', async () => { utility('my-backdrop-opacity', 'backdropOpacity') utility('my-backdrop-saturate', 'backdropSaturate') utility('my-backdrop-sepia', 'backdropSepia') - utility('my-border', 'border') + utility('my-border-width', 'borderWidth') utility('my-brightness', 'brightness') utility('my-columns', 'columns') utility('my-contrast', 'contrast') @@ -557,7 +558,7 @@ describe('theme', async () => { 'my-backdrop-opacity-1', 'my-backdrop-saturate-1', 'my-backdrop-sepia-1', - 'my-border-1', + 'my-border-width-1', 'my-brightness-1', 'my-columns-1', 'my-contrast-1', @@ -620,7 +621,7 @@ describe('theme', async () => { .my-backdrop-sepia-1 { --value: 1%; } - .my-border-1 { + .my-border-width-1 { --value: 1px; } .my-brightness-1 { @@ -702,7 +703,7 @@ describe('theme', async () => { --value: 1deg; } .my-stroke-width-1 { - --value: 1px; + --value: 1; } .my-text-decoration-thickness-1 { --value: 1px; @@ -866,6 +867,221 @@ describe('theme', async () => { " `) }) + + test('spreading `tailwindcss/defaultTheme` exports keeps bare values', async () => { + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let { build } = await compile(input, { + loadPlugin: async () => { + return plugin(function ({ matchUtilities }) { + function utility(name: string, themeKey: string) { + matchUtilities( + { [name]: (value) => ({ '--value': value }) }, + // @ts-ignore + { values: defaultTheme[themeKey] }, + ) + } + + utility('my-aspect', 'aspectRatio') + // The following keys deliberately doesn't work as these are exported + // as functions from the compat config. + // + // utility('my-backdrop-brightness', 'backdropBrightness') + // utility('my-backdrop-contrast', 'backdropContrast') + // utility('my-backdrop-grayscale', 'backdropGrayscale') + // utility('my-backdrop-hue-rotate', 'backdropHueRotate') + // utility('my-backdrop-invert', 'backdropInvert') + // utility('my-backdrop-opacity', 'backdropOpacity') + // utility('my-backdrop-saturate', 'backdropSaturate') + // utility('my-backdrop-sepia', 'backdropSepia') + // utility('my-divide-width', 'divideWidth') + utility('my-border-width', 'borderWidth') + utility('my-brightness', 'brightness') + utility('my-columns', 'columns') + utility('my-contrast', 'contrast') + utility('my-flex-grow', 'flexGrow') + utility('my-flex-shrink', 'flexShrink') + utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') + utility('my-grayscale', 'grayscale') + utility('my-grid-row-end', 'gridRowEnd') + utility('my-grid-row-start', 'gridRowStart') + utility('my-grid-template-columns', 'gridTemplateColumns') + utility('my-grid-template-rows', 'gridTemplateRows') + utility('my-hue-rotate', 'hueRotate') + utility('my-invert', 'invert') + utility('my-line-clamp', 'lineClamp') + utility('my-opacity', 'opacity') + utility('my-order', 'order') + utility('my-outline-offset', 'outlineOffset') + utility('my-outline-width', 'outlineWidth') + utility('my-ring-offset-width', 'ringOffsetWidth') + utility('my-ring-width', 'ringWidth') + utility('my-rotate', 'rotate') + utility('my-saturate', 'saturate') + utility('my-scale', 'scale') + utility('my-sepia', 'sepia') + utility('my-skew', 'skew') + utility('my-stroke-width', 'strokeWidth') + utility('my-text-decoration-thickness', 'textDecorationThickness') + utility('my-text-underline-offset', 'textUnderlineOffset') + utility('my-transition-delay', 'transitionDelay') + utility('my-transition-duration', 'transitionDuration') + utility('my-z-index', 'zIndex') + }) + }, + }) + + let output = build([ + 'my-aspect-2/5', + // 'my-backdrop-brightness-1', + // 'my-backdrop-contrast-1', + // 'my-backdrop-grayscale-1', + // 'my-backdrop-hue-rotate-1', + // 'my-backdrop-invert-1', + // 'my-backdrop-opacity-1', + // 'my-backdrop-saturate-1', + // 'my-backdrop-sepia-1', + // 'my-divide-width-1', + 'my-border-width-1', + 'my-brightness-1', + 'my-columns-1', + 'my-contrast-1', + 'my-flex-grow-1', + 'my-flex-shrink-1', + 'my-gradient-color-stop-positions-1', + 'my-grayscale-1', + 'my-grid-row-end-1', + 'my-grid-row-start-1', + 'my-grid-template-columns-1', + 'my-grid-template-rows-1', + 'my-hue-rotate-1', + 'my-invert-1', + 'my-line-clamp-1', + 'my-opacity-1', + 'my-order-1', + 'my-outline-offset-1', + 'my-outline-width-1', + 'my-ring-offset-width-1', + 'my-ring-width-1', + 'my-rotate-1', + 'my-saturate-1', + 'my-scale-1', + 'my-sepia-1', + 'my-skew-1', + 'my-stroke-width-1', + 'my-text-decoration-thickness-1', + 'my-text-underline-offset-1', + 'my-transition-delay-1', + 'my-transition-duration-1', + 'my-z-index-1', + ]) + + expect(output).toMatchInlineSnapshot(` + ".my-aspect-2\\/5 { + --value: 2/5; + } + .my-border-width-1 { + --value: 1px; + } + .my-brightness-1 { + --value: 1%; + } + .my-columns-1 { + --value: 1; + } + .my-contrast-1 { + --value: 1%; + } + .my-flex-grow-1 { + --value: 1; + } + .my-flex-shrink-1 { + --value: 1; + } + .my-gradient-color-stop-positions-1 { + --value: 1%; + } + .my-grayscale-1 { + --value: 1%; + } + .my-grid-row-end-1 { + --value: 1; + } + .my-grid-row-start-1 { + --value: 1; + } + .my-grid-template-columns-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-grid-template-rows-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-hue-rotate-1 { + --value: 1deg; + } + .my-invert-1 { + --value: 1%; + } + .my-line-clamp-1 { + --value: 1; + } + .my-opacity-1 { + --value: 1%; + } + .my-order-1 { + --value: 1; + } + .my-outline-offset-1 { + --value: 1px; + } + .my-outline-width-1 { + --value: 1px; + } + .my-ring-offset-width-1 { + --value: 1px; + } + .my-ring-width-1 { + --value: 1px; + } + .my-rotate-1 { + --value: 1deg; + } + .my-saturate-1 { + --value: 1%; + } + .my-scale-1 { + --value: 1%; + } + .my-sepia-1 { + --value: 1%; + } + .my-skew-1 { + --value: 1deg; + } + .my-stroke-width-1 { + --value: 1; + } + .my-text-decoration-thickness-1 { + --value: 1px; + } + .my-text-underline-offset-1 { + --value: 1px; + } + .my-transition-delay-1 { + --value: 1ms; + } + .my-transition-duration-1 { + --value: 1ms; + } + .my-z-index-1 { + --value: 1; + } + " + `) + }) }) describe('addUtilities()', () => {