Skip to content

Commit aa1f518

Browse files
agriffisgregberge
authored andcommitted
feat(transform): min/max widths in media queries
1 parent 93cba6a commit aa1f518

File tree

4 files changed

+124
-9
lines changed

4 files changed

+124
-9
lines changed

packages/core/src/mediaGetters.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {
2+
getBreakpoints,
3+
getBreakpointMin,
4+
getBreakpointMax,
5+
} from '@xstyled/system'
6+
7+
const getMediaWidth = (getBreakpointBound: Function) => (value: any) => (
8+
props: object,
9+
) => {
10+
const v = getBreakpointBound(getBreakpoints(props), value)
11+
// getters return null for smallest, undefined for not found, and otherwise
12+
// a px string. Since we MUST emit a matcher at this point, use 0px for the
13+
// smallest case (always true for min, never true for max).
14+
return v === null ? '0' : v || value
15+
}
16+
17+
export const mediaGetters = {
18+
'min-width': getMediaWidth(getBreakpointMin),
19+
'max-width': getMediaWidth(getBreakpointMax),
20+
}

packages/core/src/transform.test.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { transform } from './transform'
22

3-
const props = { theme: { colors: { black: '#000' } } }
3+
const props = {
4+
theme: {
5+
screens: { sm: 0, md: 1024, lg: 1920 },
6+
colors: { black: '#000' },
7+
},
8+
}
49

510
const transformToStr = (s) =>
611
transform(s)
@@ -28,4 +33,42 @@ describe('#transform', () => {
2833
it("isn't confused by comments", () => {
2934
expect(transformToStr('/* hi */color:black;')).toBe('/* hi */color:#000;')
3035
})
36+
37+
it('transforms min-width media query', () => {
38+
expect(transformToStr('@media (min-width: md) {}')).toBe(
39+
'@media (min-width: 1024px) {}',
40+
)
41+
})
42+
43+
it('transforms max-width media query', () => {
44+
expect(transformToStr('@media (max-width: md) {}')).toBe(
45+
'@media (max-width: 1023.98px) {}',
46+
)
47+
})
48+
49+
it('transforms mixed media query', () => {
50+
expect(
51+
transformToStr(
52+
'@media screen and (min-width: md) and (color) and (not (max-width: lg)) {}',
53+
),
54+
).toBe(
55+
'@media screen and (min-width: 1024px) and (color) and (not (max-width: 1919.98px)) {}',
56+
)
57+
})
58+
59+
it('transforms smallest value media query', () => {
60+
expect(transformToStr('@media (min-width: sm) {}')).toBe(
61+
'@media (min-width: 0) {}',
62+
)
63+
})
64+
65+
it('handles mixed media query and rules', () => {
66+
expect(
67+
transformToStr(
68+
'div{color:black;}@media(min-width:lg){div{color:black;}}div{color:black;}',
69+
),
70+
).toBe(
71+
'div{color:#000;}@media(min-width:1920px){div{color:#000;}}div{color:#000;}',
72+
)
73+
})
3174
})

packages/core/src/transform.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
2+
import { mediaGetters } from './mediaGetters'
23
import { propGetters } from './propGetters'
34

45
// prop name is an ident: word chars, underscore and dash.
@@ -8,28 +9,79 @@ const PROP_CHAR = `[-\\w]`
89
const VALUE_CHAR = `(?:\\\\.|[^\\\\;])`
910

1011
// prettier-ignore
11-
const PROP_REGEXP = new RegExp(
12+
const PROP_PATT = (
1213
`(${PROP_CHAR}+)` + // capture prop name
1314
`(\\s*:\\s*)` + // colon & whitespace
1415
`(?=\\S)` + // prop value starts with non-whitespace
1516
`(${VALUE_CHAR}*?)` + // capture prop value (non-greedy)
1617
`(\\s*!important)?` + // capture !important
17-
`(\\s*;)`, // semi & whitespace
18-
`gs`, // flags
18+
`(\\s*;)` // semi & whitespace
1919
)
2020

21+
// simplistic but workable media query value.
22+
const MEDIA_CHAR = `[^{]`
23+
24+
// prettier-ignore
25+
const MEDIA_PATT = (
26+
`(@media\\b\\s*)` + // start of media query
27+
`(?=\\S)` + // value starts with non-whitespace
28+
`(${MEDIA_CHAR}+?)` + // capture queries (non-greedy)
29+
`(\\s*\\{)` // brace & whitespace
30+
)
31+
32+
const MATCH_REGEXP = new RegExp(`(?:${PROP_PATT}|${MEDIA_PATT})`, `gs`)
33+
2134
export function transform(rawValue: any) {
2235
if (typeof rawValue !== 'string') return rawValue
2336
let matches
2437
let lastIndex = 0
2538
const values = []
26-
while ((matches = PROP_REGEXP.exec(rawValue))) {
27-
const [, prop, colon, value, imp, semi] = matches
28-
const getter = (propGetters as any)[prop]
39+
while ((matches = MATCH_REGEXP.exec(rawValue))) {
40+
const [, prop, colon, value, imp, semi, media, query, brace] = matches
41+
if (media) {
42+
values.push(rawValue.slice(lastIndex, matches.index))
43+
values.push(media)
44+
mediaTransform(query).forEach((v) => values.push(v))
45+
values.push(brace)
46+
lastIndex = matches.index + matches[0].length
47+
} else {
48+
const getter = (propGetters as any)[prop]
49+
if (getter) {
50+
values.push(rawValue.slice(lastIndex, matches.index))
51+
values.push(
52+
(p: object) =>
53+
`${prop}${colon}${getter(value)(p)}${imp || ''}${semi}`,
54+
)
55+
lastIndex = matches.index + matches[0].length
56+
}
57+
}
58+
}
59+
values.push(rawValue.slice(lastIndex, rawValue.length))
60+
return values
61+
}
62+
63+
// media query prop/value pairs such as (min-width: 1024px)
64+
// prettier-ignore
65+
const QUERY_REGEXP = new RegExp(
66+
`(\\(\\s*)` + // open paren, whitespace
67+
`(${PROP_CHAR}+)` + // capture prop name
68+
`(\\s*:\\s*)` + // colon & whitespace
69+
`([^\\)]*?)` + // capture prop value (non-greedy)
70+
`(\\s*\\))`, // close paren, whitespace
71+
`gs`
72+
)
73+
74+
function mediaTransform(rawValue: string) {
75+
let matches
76+
let lastIndex = 0
77+
const values = []
78+
while ((matches = QUERY_REGEXP.exec(rawValue))) {
79+
const [, open, prop, colon, value, close] = matches
80+
const getter = (mediaGetters as any)[prop]
2981
if (getter) {
3082
values.push(rawValue.slice(lastIndex, matches.index))
3183
values.push(
32-
(p: object) => `${prop}${colon}${getter(value)(p)}${imp || ''}${semi}`,
84+
(p: object) => `${open}${prop}${colon}${getter(value)(p)}${close}`,
3385
)
3486
lastIndex = matches.index + matches[0].length
3587
}

packages/system/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ export * from './transformers'
77
export * from './defaultTheme'
88
export * from './preflight'
99
export * from './theme'
10-
export { getBreakpoints } from './media'
10+
export { getBreakpointMin, getBreakpointMax, getBreakpoints } from './media'

0 commit comments

Comments
 (0)