diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index 8978f5123b5e..dc44850c5cb2 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -2327,6 +2327,15 @@ exports[`getVariants 1`] = ` "sm", ], }, + { + "hasDash": true, + "isArbitrary": true, + "name": "max-h", + "selectors": [Function], + "values": [ + "sm", + ], + }, { "hasDash": true, "isArbitrary": false, @@ -2343,6 +2352,22 @@ exports[`getVariants 1`] = ` "sm", ], }, + { + "hasDash": true, + "isArbitrary": false, + "name": "h-sm", + "selectors": [Function], + "values": [], + }, + { + "hasDash": true, + "isArbitrary": true, + "name": "min-h", + "selectors": [Function], + "values": [ + "sm", + ], + }, { "hasDash": true, "isArbitrary": true, @@ -2368,6 +2393,27 @@ exports[`getVariants 1`] = ` "4", ], }, + { + "hasDash": true, + "isArbitrary": true, + "name": "@max-h", + "selectors": [Function], + "values": [], + }, + { + "hasDash": true, + "isArbitrary": true, + "name": "@-h", + "selectors": [Function], + "values": [], + }, + { + "hasDash": true, + "isArbitrary": true, + "name": "@min-h", + "selectors": [Function], + "values": [], + }, { "hasDash": true, "isArbitrary": false, diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index 18ef39eeb459..ec4c7711d5db 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -998,7 +998,7 @@ test('sorting stacked min-* and max-* variants', () => { } @tailwind utilities; `, - ['min-sm:max-xl:flex', 'min-md:max-xl:flex', 'min-xs:max-xl:flex'], + ['min-sm:max-xl:flex', 'min-md:max-xl:flex', 'min-xs:max-xl:flex', 'min-h-xs:max-xl:flex'], ), ).toMatchInlineSnapshot(` ":root { @@ -1031,6 +1031,14 @@ test('sorting stacked min-* and max-* variants', () => { display: flex; } } + } + + @media (width < 1280px) { + @media (height >= 280px) { + .min-h-xs\\:max-xl\\:flex { + display: flex; + } + } }" `) }) @@ -1062,6 +1070,21 @@ test('min, max and unprefixed breakpoints', () => { 'min-sm:flex', 'sm:flex', 'lg:flex', + 'h-sm:flex', + 'h-lg:flex', + 'min-h-lg:flex', + 'max-h-lg:flex', + 'h-sm:md:flex', + 'h-lg:min-md:flex', + 'h-lg:max-lg:flex', + 'min-h-lg:md:flex', + 'max-h-lg:lg:flex', + 'min-h-lg:max-md:flex', + 'max-h-lg:min-lg:flex', + 'min-lg:max-h-md:flex', + 'max-lg:min-h-lg:flex', + 'min-[900px]:max-h-[700px]:flex', + 'max-[700px]:min-h-[900px]:flex', ], ), ).toMatchInlineSnapshot(` @@ -1095,6 +1118,12 @@ test('min, max and unprefixed breakpoints', () => { } } + @media (height < 1024px) { + .max-h-lg\\:flex { + display: flex; + } + } + @media (width >= 640px) { .min-sm\\:flex { display: flex; @@ -1125,16 +1154,122 @@ test('min, max and unprefixed breakpoints', () => { } } + @media (height < 700px) { + @media (width >= 900px) { + .min-\\[900px\\]\\:max-h-\\[700px\\]\\:flex { + display: flex; + } + } + } + @media (width >= 1024px) { .lg\\:flex { display: flex; } } + @media (width >= 1024px) { + @media (height < 1024px) { + .max-h-lg\\:lg\\:flex { + display: flex; + } + } + } + @media (width >= 1024px) { .min-lg\\:flex { display: flex; } + } + + @media (width >= 1024px) { + @media (height < 1024px) { + .max-h-lg\\:min-lg\\:flex { + display: flex; + } + } + } + + @media (height < 768px) { + @media (width >= 1024px) { + .min-lg\\:max-h-md\\:flex { + display: flex; + } + } + } + + @media (height >= 1024px) { + .h-lg\\:flex { + display: flex; + } + } + + @media (width < 1024px) { + @media (height >= 1024px) { + .h-lg\\:max-lg\\:flex { + display: flex; + } + } + } + + @media (width >= 768px) { + @media (height >= 1024px) { + .h-lg\\:min-md\\:flex { + display: flex; + } + } + } + + @media (height >= 640px) { + .h-sm\\:flex { + display: flex; + } + } + + @media (width >= 768px) { + @media (height >= 640px) { + .h-sm\\:md\\:flex { + display: flex; + } + } + } + + @media (height >= 900px) { + @media (width < 700px) { + .max-\\[700px\\]\\:min-h-\\[900px\\]\\:flex { + display: flex; + } + } + } + + @media (height >= 1024px) { + .min-h-lg\\:flex { + display: flex; + } + } + + @media (height >= 1024px) { + @media (width < 1024px) { + .max-lg\\:min-h-lg\\:flex { + display: flex; + } + } + } + + @media (width < 768px) { + @media (height >= 1024px) { + .min-h-lg\\:max-md\\:flex { + display: flex; + } + } + } + + @media (width >= 768px) { + @media (height >= 1024px) { + .min-h-lg\\:md\\:flex { + display: flex; + } + } }" `) }) @@ -1570,6 +1705,21 @@ test('container queries', () => { '@max-lg/name:flex', '@max-[123px]:flex', '@max-[456px]/name:flex', + + '@h-lg:flex', + '@h-lg/name:flex', + '@h-[123px]:flex', + '@h-[456px]/name:flex', + + '@min-h-lg:flex', + '@min-h-lg/name:flex', + '@min-h-[123px]:flex', + '@min-h-[456px]/name:flex', + + '@max-h-lg:flex', + '@max-h-lg/name:flex', + '@max-h-[123px]:flex', + '@max-h-[456px]/name:flex', ], ), ).toMatchInlineSnapshot(` @@ -1647,6 +1797,30 @@ test('container queries', () => { .\\@min-lg\\/name\\:flex { display: flex; } + } + + @container name (height < 456px) { + .\\@max-h-\\[456px\\]\\/name\\:flex { + display: flex; + } + } + + @container (height < 123px) { + .\\@max-h-\\[123px\\]\\:flex { + display: flex; + } + } + + @container (height >= 123px) { + .\\@min-h-\\[123px\\]\\:flex { + display: flex; + } + } + + @container name (height >= 456px) { + .\\@min-h-\\[456px\\]\\/name\\:flex { + display: flex; + } }" `) }) diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 25a37c9489af..82914c535694 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -548,6 +548,27 @@ export function createVariants(theme: Theme): Variants { () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[], ) + variants.group( + () => { + variants.functional( + 'max-h', + (ruleNode, variant) => { + let value = resolvedBreakpoints.get(variant) + if (value === null) return null + + ruleNode.nodes = [rule(`@media (height < ${value})`, ruleNode.nodes)] + }, + { compounds: false }, + ) + }, + (a, z) => compareBreakpoints(a, z, 'desc', resolvedBreakpoints), + ) + + variants.suggest( + 'max-h', + () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[], + ) + // Min variants.group( () => { @@ -581,6 +602,39 @@ export function createVariants(theme: Theme): Variants { 'min', () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[], ) + + variants.group( + () => { + // Registers breakpoint variants like `h-sm`, `h-md`, `h-lg`, etc. + for (let [key, value] of theme.namespace('--breakpoint')) { + if (key === null) continue + variants.static( + `h-${key}`, + (ruleNode) => { + ruleNode.nodes = [rule(`@media (height >= ${value})`, ruleNode.nodes)] + }, + { compounds: false }, + ) + } + + variants.functional( + 'min-h', + (ruleNode, variant) => { + let value = resolvedBreakpoints.get(variant) + if (value === null) return null + + ruleNode.nodes = [rule(`@media (height >= ${value})`, ruleNode.nodes)] + }, + { compounds: false }, + ) + }, + (a, z) => compareBreakpoints(a, z, 'asc', resolvedBreakpoints), + ) + + variants.suggest( + 'min-h', + () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[], + ) } { @@ -685,6 +739,109 @@ export function createVariants(theme: Theme): Variants { () => Array.from(widths.keys()).filter((key) => key !== null) as string[], ) } + + { + let heights = theme.namespace('--height') + + // Container queries + let resolvedHeights = new DefaultMap((variant: Variant) => { + switch (variant.kind) { + case 'functional': { + if (variant.value === null) return null + + let value: string | null = null + + if (variant.value.kind === 'arbitrary') { + value = variant.value.value + } else if (variant.value.kind === 'named') { + value = theme.resolveValue(variant.value.value, ['--height']) + } + + if (!value) return null + if (value.includes('var(')) return null + + return value + } + case 'static': + case 'arbitrary': + case 'compound': + return null + } + }) + + variants.group( + () => { + variants.functional( + '@max-h', + (ruleNode, variant) => { + let value = resolvedHeights.get(variant) + if (value === null) return null + + ruleNode.nodes = [ + rule( + variant.modifier + ? `@container ${variant.modifier.value} (height < ${value})` + : `@container (height < ${value})`, + ruleNode.nodes, + ), + ] + }, + { compounds: false }, + ) + }, + (a, z) => compareBreakpoints(a, z, 'desc', resolvedHeights), + ) + + variants.suggest( + '@max-h', + () => Array.from(heights.keys()).filter((key) => key !== null) as string[], + ) + + variants.group( + () => { + variants.functional( + '@-h', + (ruleNode, variant) => { + let value = resolvedHeights.get(variant) + if (value === null) return null + + ruleNode.nodes = [ + rule( + variant.modifier + ? `@container ${variant.modifier.value} (height >= ${value})` + : `@container (height >= ${value})`, + ruleNode.nodes, + ), + ] + }, + { compounds: false }, + ) + variants.functional( + '@min-h', + (ruleNode, variant) => { + let value = resolvedHeights.get(variant) + if (value === null) return null + + ruleNode.nodes = [ + rule( + variant.modifier + ? `@container ${variant.modifier.value} (height >= ${value})` + : `@container (height >= ${value})`, + ruleNode.nodes, + ), + ] + }, + { compounds: false }, + ) + }, + (a, z) => compareBreakpoints(a, z, 'asc', resolvedHeights), + ) + + variants.suggest( + '@min-h', + () => Array.from(heights.keys()).filter((key) => key !== null) as string[], + ) + } } staticVariant('portrait', ['@media (orientation: portrait)'], { compounds: false })