Skip to content
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

Reintroduce container component as a utility #14993

Merged
merged 7 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Support opacity values in increments of `0.25` by default ([#14980](https://github.com/tailwindlabs/tailwindcss/pull/14980))
- Support specifying the color interpolation method for gradients via modifier ([#14984](https://github.com/tailwindlabs/tailwindcss/pull/14984))
- Reintroduce `container` component as a utility ([#14993](https://github.com/tailwindlabs/tailwindcss/pull/14993))

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3375,6 +3375,7 @@ exports[`getClassList 1`] = `
"contain-size",
"contain-strict",
"contain-style",
"container",
"content-around",
"content-baseline",
"content-between",
Expand Down
4 changes: 4 additions & 0 deletions packages/tailwindcss/src/property-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export default [
'float',
'clear',

// Ensure that the included `container` class is always sorted before any
// custom container extensions
'--tw-container-component',

// How do we make `mx-0` come before `mt-0`?
// Idea: `margin-x` property that we compile away with a Visitor plugin?
'margin',
Expand Down
226 changes: 225 additions & 1 deletion packages/tailwindcss/src/utilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3148,6 +3148,230 @@ test('max-height', async () => {
).toEqual('')
})

describe('container', () => {
test('creates the right media queries and sorts it before width', async () => {
expect(
await compileCss(
css`
@theme {
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-2xl: 96rem;
}
@tailwind utilities;
`,
['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'],
),
).toMatchInlineSnapshot(`
":root {
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-2xl: 96rem;
}

.container {
width: 100%;
}

@media (width >= 40rem) {
.container {
max-width: 40rem;
}
}

@media (width >= 48rem) {
.container {
max-width: 48rem;
}
}

@media (width >= 64rem) {
.container {
max-width: 64rem;
}
}

@media (width >= 80rem) {
.container {
max-width: 80rem;
}
}

@media (width >= 96rem) {
.container {
max-width: 96rem;
}
}

.w-1\\/2 {
width: 50%;
}

.max-w-\\[var\\(--breakpoint-sm\\)\\] {
max-width: var(--breakpoint-sm);
}"
`)
})

test('sorts breakpoints based on unit and then in ascending aOrder', async () => {
expect(
await compileCss(
css`
@theme reference {
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-3xl: 1600px;
--breakpoint-sm: 40em;
--breakpoint-2xl: 96rem;
--breakpoint-xs: 30px;
--breakpoint-md: 48em;
}
@tailwind utilities;
`,
['container'],
),
).toMatchInlineSnapshot(`
".container {
width: 100%;
}

@media (width >= 40em) {
.container {
max-width: 40em;
}
}

@media (width >= 48em) {
.container {
max-width: 48em;
}
}

@media (width >= 30px) {
.container {
max-width: 30px;
}
}

@media (width >= 1600px) {
.container {
max-width: 1600px;
}
}

@media (width >= 64rem) {
.container {
max-width: 64rem;
}
}

@media (width >= 80rem) {
.container {
max-width: 80rem;
}
}

@media (width >= 96rem) {
.container {
max-width: 96rem;
}
}"
`)
})

test('custom `@utility container` always follow the core utility ', async () => {
expect(
await compileCss(
css`
@theme {
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-2xl: 96rem;
}
@tailwind utilities;

@utility container {
margin-inline: auto;
padding-inline: 1rem;

@media (width >= theme(--breakpoint-sm)) {
padding-inline: 2rem;
}
}
`,
['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'],
),
).toMatchInlineSnapshot(`
":root {
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-2xl: 96rem;
}

.container {
width: 100%;
}

@media (width >= 40rem) {
.container {
max-width: 40rem;
}
}

@media (width >= 48rem) {
.container {
max-width: 48rem;
}
}

@media (width >= 64rem) {
.container {
max-width: 64rem;
}
}

@media (width >= 80rem) {
.container {
max-width: 80rem;
}
}

@media (width >= 96rem) {
.container {
max-width: 96rem;
}
}

.container {
margin-inline: auto;
padding-inline: 1rem;
}

@media (width >= 40rem) {
.container {
padding-inline: 2rem;
}
}

.w-1\\/2 {
width: 50%;
}

.max-w-\\[var\\(--breakpoint-sm\\)\\] {
max-width: var(--breakpoint-sm);
}"
`)
})
})

test('flex', async () => {
expect(
await run([
Expand Down Expand Up @@ -16680,7 +16904,7 @@ describe('spacing utilities', () => {
`)
})

test('only multiples of 0.25 with no trailing zeroes are supported with the spacing multipler', async () => {
test('only multiples of 0.25 with no trailing zeroes are supported with the spacing multiplier', async () => {
let { build } = await compile(css`
@theme {
--spacing: 4px;
Expand Down
13 changes: 13 additions & 0 deletions packages/tailwindcss/src/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { atRoot, atRule, decl, styleRule, type AstNode } from './ast'
import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate'
import type { Theme, ThemeKey } from './theme'
import { compareBreakpoints } from './utils/compare-breakpoints'
import { DefaultMap } from './utils/default-map'
import {
inferDataType,
Expand Down Expand Up @@ -897,6 +898,18 @@ export function createUtilities(theme: Theme) {
})
}

utilities.static('container', () => {
let breakpoints = [...theme.namespace('--breakpoint').values()]
breakpoints.sort((a, z) => compareBreakpoints(a, z, 'asc'))

let decls: AstNode[] = [decl('--tw-sort', '--tw-container-component'), decl('width', '100%')]
for (let breakpoint of breakpoints) {
decls.push(atRule('@media', `(min-width: ${breakpoint})`, [decl('max-width', breakpoint)]))
}

return decls
})

/**
* @css `flex`
*/
Expand Down
48 changes: 48 additions & 0 deletions packages/tailwindcss/src/utils/compare-breakpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function compareBreakpoints(a: string, z: string, direction: 'asc' | 'desc') {
if (a === z) return 0

// Assumption: when a `(` exists, we are dealing with a CSS function.
//
// E.g.: `calc(100% - 1rem)`
let aIsCssFunction = a.indexOf('(')
let zIsCssFunction = z.indexOf('(')

let aBucket =
aIsCssFunction === -1
? // No CSS function found, bucket by unit instead
a.replace(/[\d.]+/g, '')
: // CSS function found, bucket by function name
a.slice(0, aIsCssFunction)

let zBucket =
zIsCssFunction === -1
? // No CSS function found, bucket by unit
z.replace(/[\d.]+/g, '')
: // CSS function found, bucket by function name
z.slice(0, zIsCssFunction)

let order =
// Compare by bucket name
(aBucket === zBucket ? 0 : aBucket < zBucket ? -1 : 1) ||
// If bucket names are the same, compare by value
(direction === 'asc' ? parseInt(a) - parseInt(z) : parseInt(z) - parseInt(a))

// If the groups are the same, and the contents are not numbers, the
// `order` will result in `NaN`. In this case, we want to make sorting
// stable by falling back to a string comparison.
//
// This can happen when using CSS functions such as `calc`.
//
// E.g.:
//
// - `min-[calc(100%-1rem)]` and `min-[calc(100%-2rem)]`
// - `@[calc(100%-1rem)]` and `@[calc(100%-2rem)]`
//
// In this scenario, we want to alphabetically sort `calc(100%-1rem)` and
// `calc(100%-2rem)` to make it deterministic.
if (Number.isNaN(order)) {
return a < z ? -1 : 1
}

return order
}
Loading