Skip to content

Commit 9a2ef15

Browse files
committed
implemented max-width container queries
The @max-[1024px]/container1:underline case requires a regex fix in the default extractor of Tailwind CSS.
1 parent c287ac7 commit 9a2ef15

File tree

2 files changed

+288
-24
lines changed

2 files changed

+288
-24
lines changed

src/index.ts

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,41 +29,60 @@ export = plugin(
2929
}
3030
)
3131

32+
const sort: (
33+
a: { value: string; modifier: string | null },
34+
b: { value: string; modifier: string | null }
35+
) => number = (aVariant, zVariant) => {
36+
let a = parseFloat(aVariant.value)
37+
let z = parseFloat(zVariant.value)
38+
39+
if (a === null || z === null) return 0
40+
41+
// Sort values themselves regardless of unit
42+
if (a - z !== 0) return a - z
43+
44+
let aLabel = aVariant.modifier ?? ''
45+
let zLabel = zVariant.modifier ?? ''
46+
47+
// Explicitly move empty labels to the end
48+
if (aLabel === '' && zLabel !== '') {
49+
return 1
50+
} else if (aLabel !== '' && zLabel === '') {
51+
return -1
52+
}
53+
54+
// Sort labels alphabetically in the English locale
55+
// We are intentionally overriding the locale because we do not want the sort to
56+
// be affected by the machine's locale (be it a developer or CI environment)
57+
return aLabel.localeCompare(zLabel, 'en', { numeric: true })
58+
}
59+
3260
matchVariant(
33-
'@',
61+
'@max',
3462
(value = '', { modifier }) => {
3563
let parsed = parseValue(value)
3664

37-
return parsed !== null ? `@container ${modifier ?? ''} (min-width: ${value})` : []
65+
return parsed !== null ? `@container ${modifier ?? ''} (max-width: ${value})` : []
3866
},
3967
{
4068
values,
41-
sort(aVariant, zVariant) {
42-
let a = parseFloat(aVariant.value)
43-
let z = parseFloat(zVariant.value)
44-
45-
if (a === null || z === null) return 0
46-
47-
// Sort values themselves regardless of unit
48-
if (a - z !== 0) return a - z
49-
50-
let aLabel = aVariant.modifier ?? ''
51-
let zLabel = zVariant.modifier ?? ''
69+
sort,
70+
}
71+
)
5272

53-
// Explicitly move empty labels to the end
54-
if (aLabel === '' && zLabel !== '') {
55-
return 1
56-
} else if (aLabel !== '' && zLabel === '') {
57-
return -1
58-
}
73+
matchVariant(
74+
'@',
75+
(value = '', { modifier }) => {
76+
let parsed = parseValue(value)
5977

60-
// Sort labels alphabetically in the English locale
61-
// We are intentionally overriding the locale because we do not want the sort to
62-
// be affected by the machine's locale (be it a developer or CI environment)
63-
return aLabel.localeCompare(zLabel, 'en', { numeric: true })
64-
},
78+
return parsed !== null ? `@container ${modifier ?? ''} (min-width: ${value})` : []
79+
},
80+
{
81+
values,
82+
sort,
6583
}
6684
)
85+
6786
},
6887
{
6988
theme: {

tests/index.test.ts

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect } from '@jest/globals'
22
import { html, css, run } from './run'
3+
import _defaultExtractor from 'tailwindcss/lib/lib/defaultExtractor'
34

45
it('container queries', () => {
56
let config = {
@@ -245,3 +246,247 @@ it('should be possible to use default container queries', () => {
245246
`)
246247
})
247248
})
249+
250+
it('max-width container queries', () => {
251+
let config = {
252+
content: [
253+
{
254+
raw: html`
255+
<div
256+
class="@container @container-normal @container/sidebar @container-normal/sidebar @container-[size]/sidebar"
257+
>
258+
<div class="@max-md:underline"></div>
259+
<div class="@max-md/container1:underline"></div>
260+
<div class="@max-md/container2:underline"></div>
261+
<div class="@max-md/container10:underline"></div>
262+
263+
<div class="@max-sm:underline"></div>
264+
<div class="@max-sm/container1:underline"></div>
265+
<div class="@max-sm/container2:underline"></div>
266+
<div class="@max-sm/container10:underline"></div>
267+
268+
<div class="@max-lg:underline"></div>
269+
<div class="@max-lg/container1:underline"></div>
270+
<div class="@max-lg/container2:underline"></div>
271+
<div class="@max-lg/container10:underline"></div>
272+
<div class="@max-[1024px]:underline"></div>
273+
<!-- These are not working with the current defaultExtractor:
274+
<div class="@max-[1024px]/container1:underline"></div>
275+
<div class="@max-[1024]/container1:underline"></div>
276+
-->
277+
278+
<div class="@max-[312px]:underline"></div>
279+
<div class="@max-[200rem]:underline"></div>
280+
<div class="@max-[123px]:underline"></div>
281+
</div>
282+
`,
283+
},
284+
],
285+
theme: {
286+
containers: {
287+
sm: '320px',
288+
md: '768px',
289+
lg: '1024px',
290+
},
291+
},
292+
corePlugins: { preflight: false },
293+
}
294+
295+
let input = css`
296+
@tailwind utilities;
297+
`
298+
299+
return run(input, config).then((result) => {
300+
expect(result.css).toMatchFormattedCss(css`
301+
.\@container {
302+
container-type: inline-size;
303+
}
304+
305+
.\@container-normal {
306+
container-type: normal;
307+
}
308+
309+
.\@container\/sidebar {
310+
container-type: inline-size;
311+
container-name: sidebar;
312+
}
313+
314+
.\@container-normal\/sidebar {
315+
container-type: normal;
316+
container-name: sidebar;
317+
}
318+
319+
@container (max-width: 123px) {
320+
.\@max-\[123px\]\:underline {
321+
text-decoration-line: underline;
322+
}
323+
}
324+
325+
@container (max-width: 200rem) {
326+
.\@max-\[200rem\]\:underline {
327+
text-decoration-line: underline;
328+
}
329+
}
330+
331+
@container (max-width: 312px) {
332+
.\@max-\[312px\]\:underline {
333+
text-decoration-line: underline;
334+
}
335+
}
336+
337+
@container container1 (max-width: 320px) {
338+
.\@max-sm\/container1\:underline {
339+
text-decoration-line: underline;
340+
}
341+
}
342+
343+
@container container2 (max-width: 320px) {
344+
.\@max-sm\/container2\:underline {
345+
text-decoration-line: underline;
346+
}
347+
}
348+
349+
@container container10 (max-width: 320px) {
350+
.\@max-sm\/container10\:underline {
351+
text-decoration-line: underline;
352+
}
353+
}
354+
355+
@container (max-width: 320px) {
356+
.\@max-sm\:underline {
357+
text-decoration-line: underline;
358+
}
359+
}
360+
361+
@container container1 (max-width: 768px) {
362+
.\@max-md\/container1\:underline {
363+
text-decoration-line: underline;
364+
}
365+
}
366+
367+
@container container2 (max-width: 768px) {
368+
.\@max-md\/container2\:underline {
369+
text-decoration-line: underline;
370+
}
371+
}
372+
373+
@container container10 (max-width: 768px) {
374+
.\@max-md\/container10\:underline {
375+
text-decoration-line: underline;
376+
}
377+
}
378+
379+
@container (max-width: 768px) {
380+
.\@max-md\:underline {
381+
text-decoration-line: underline;
382+
}
383+
}
384+
385+
@container container1 (max-width: 1024px) {
386+
.\@max-lg\/container1\:underline {
387+
text-decoration-line: underline;
388+
}
389+
}
390+
391+
@container container2 (max-width: 1024px) {
392+
.\@max-lg\/container2\:underline {
393+
text-decoration-line: underline;
394+
}
395+
}
396+
397+
@container container10 (max-width: 1024px) {
398+
.\@max-lg\/container10\:underline {
399+
text-decoration-line: underline;
400+
}
401+
}
402+
403+
@container (max-width: 1024px) {
404+
.\@max-lg\:underline {
405+
text-decoration-line: underline;
406+
}
407+
.\@max-\[1024px\]\:underline {
408+
text-decoration-line: underline;
409+
}
410+
}
411+
`)
412+
})
413+
})
414+
415+
it('should be possible to use default max-width container queries', () => {
416+
let config = {
417+
content: [
418+
{
419+
raw: html`
420+
<div>
421+
<div class="@max-md:underline"></div>
422+
<div class="@max-lg:underline"></div>
423+
<div class="@max-sm:underline"></div>
424+
<div class="@max-xs:underline"></div>
425+
<div class="@max-7xl:underline"></div>
426+
<div class="@max-6xl:underline"></div>
427+
<div class="@max-3xl:underline"></div>
428+
<div class="@max-5xl:underline"></div>
429+
</div>
430+
`,
431+
},
432+
],
433+
theme: {},
434+
corePlugins: { preflight: false },
435+
}
436+
437+
let input = css`
438+
@tailwind utilities;
439+
`
440+
441+
return run(input, config).then((result) => {
442+
expect(result.css).toMatchFormattedCss(css`
443+
@container (max-width: 20rem) {
444+
.\@max-xs\:underline {
445+
text-decoration-line: underline;
446+
}
447+
}
448+
449+
@container (max-width: 24rem) {
450+
.\@max-sm\:underline {
451+
text-decoration-line: underline;
452+
}
453+
}
454+
455+
@container (max-width: 28rem) {
456+
.\@max-md\:underline {
457+
text-decoration-line: underline;
458+
}
459+
}
460+
461+
@container (max-width: 32rem) {
462+
.\@max-lg\:underline {
463+
text-decoration-line: underline;
464+
}
465+
}
466+
467+
@container (max-width: 48rem) {
468+
.\@max-3xl\:underline {
469+
text-decoration-line: underline;
470+
}
471+
}
472+
473+
@container (max-width: 64rem) {
474+
.\@max-5xl\:underline {
475+
text-decoration-line: underline;
476+
}
477+
}
478+
479+
@container (max-width: 72rem) {
480+
.\@max-6xl\:underline {
481+
text-decoration-line: underline;
482+
}
483+
}
484+
485+
@container (max-width: 80rem) {
486+
.\@max-7xl\:underline {
487+
text-decoration-line: underline;
488+
}
489+
}
490+
`)
491+
})
492+
})

0 commit comments

Comments
 (0)