diff --git a/example/src/Example.tsx b/example/src/Example.tsx index ee90325..f8800c5 100644 --- a/example/src/Example.tsx +++ b/example/src/Example.tsx @@ -48,6 +48,51 @@ export default function Example() { Heading + + Media + + + Underlined appears on phone + + + Square border appears on md Phone + )} {!example && ( diff --git a/example/src/components/Heading.tsx b/example/src/components/Heading.tsx index 92fbcf8..43c4756 100644 --- a/example/src/components/Heading.tsx +++ b/example/src/components/Heading.tsx @@ -19,7 +19,6 @@ const underLinedStyle = css({ underlined: true, css: { borderBottomColor: 'black', - borderBottomWidth: 1, }, }, { @@ -27,7 +26,6 @@ const underLinedStyle = css({ underlined: true, css: { borderBottomColor: 'red', - borderBottomWidth: 1, }, }, { @@ -35,7 +33,6 @@ const underLinedStyle = css({ underlined: true, css: { borderBottomColor: 'blue', - borderBottomWidth: 1, }, }, { @@ -43,7 +40,6 @@ const underLinedStyle = css({ underlined: true, css: { borderBottomColor: 'green', - borderBottomWidth: 1, }, }, { @@ -51,7 +47,6 @@ const underLinedStyle = css({ underlined: true, css: { borderBottomColor: 'purple', - borderBottomWidth: 1, }, }, { @@ -82,6 +77,7 @@ export const Heading = styled( true: { paddingRight: 4, paddingLeft: 4, + borderBottomWidth: 1, }, }, }, diff --git a/src/internals/index.js b/src/internals/index.js index 4125c6f..a7217b2 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -118,8 +118,9 @@ export function createStitches(config = {}) { let variantStyles = []; let compoundVariantStyles = []; + const appliedVariants = {}; - const { mediaKey, breakpoint } = useMemo(() => { + const matchedMedias = useMemo(() => { if (typeof config.media === 'object') { const correctedWindowWidth = PixelRatio.getPixelSizeForLayoutSize(windowWidth); @@ -128,63 +129,58 @@ export function createStitches(config = {}) { // The order of the media key value pairs should be constant // but is that guaranteed? So if the keys are ordered from // smallest screen size to largest everything should work ok... - const _mediaKey = utils.resolveMediaRangeQuery( + const matchedMedias = utils.resolveMediaRangeQuery( config.media, correctedWindowWidth ); - return { - mediaKey: _mediaKey, - breakpoint: _mediaKey && `@${_mediaKey}`, - }; + return matchedMedias; } - return {}; + return []; }, [windowWidth]); if (variants) { variantStyles = Object.keys(variants) .map((prop) => { - let propValue = props[prop]; + const propValue = props[prop] ?? defaultVariants[prop]; - if (propValue === undefined) { - propValue = defaultVariants[prop]; - } - - let styleSheetKey = `${prop}_${propValue}`; + const styleSheetKey = `${prop}_${propValue}`; - // Handle responsive prop value - // NOTE: only one media query will be applied since the `styleSheetKey` - // is being rewritten by the last matching media query and defaults to `@initial` - if ( - typeof propValue === 'object' && - typeof config.media === 'object' - ) { - // `@initial` acts as the default value if none of the media query values match - // It's basically the as setting `prop="value"`, eg. `color="primary"` - if (typeof propValue['@initial'] === 'string') { - styleSheetKey = `${prop}_${propValue['@initial']}`; - } - - if (breakpoint && propValue[breakpoint] !== undefined) { - const val = config.media[mediaKey]; - - if (val === true || typeof val === 'string') { - styleSheetKey = `${prop}_${propValue[breakpoint]}`; - } + if (typeof propValue !== 'object') { + if (propValue) { + appliedVariants[prop] = propValue; } + return styleSheet[styleSheetKey]; } - const extractedStyle = styleSheetKey - ? styleSheet[styleSheetKey] - : undefined; - - if (extractedStyle && breakpoint in extractedStyle) { - // WARNING: lodash merge modifies the first argument reference or skips if object is frozen. - return merge({}, extractedStyle, extractedStyle[breakpoint]); - } + const matchedMediasDetected = [ + { + mediaKey: 'initial', + breakpoint: '@initial', + }, + ...matchedMedias, + ]; + + // NOTE: Even if multiple values are matched, restict variant value is last matched and + // compensated to be determined uniquely. + const finalVariantStyle = matchedMediasDetected.reduce( + (currentStyle, matchedMedia) => { + const breakpoint = matchedMedia.breakpoint; + if (breakpoint && propValue[breakpoint] !== undefined) { + const styleSheetKey = `${prop}_${propValue[breakpoint]}`; + const extractedStyle = styleSheet[styleSheetKey]; + if (extractedStyle) { + appliedVariants[prop] = propValue[breakpoint]; + return extractedStyle; + } + } + return currentStyle; + }, + {} + ); - return extractedStyle; + return finalVariantStyle; }) .filter(Boolean); } @@ -198,25 +194,19 @@ export function createStitches(config = {}) { if ( compoundEntries.every(([prop, value]) => { - const propValue = props[prop] ?? defaultVariants[prop]; + const propValue = + appliedVariants[prop] ?? defaultVariants[prop]; return propValue === value; }) ) { const key = utils.getCompoundKey(compoundEntries); - const extractedStyle = styleSheet[key]; - - if (extractedStyle && breakpoint in extractedStyle) { - // WARNING: lodash merge modifies the first argument reference or skips if object is frozen. - return merge({}, extractedStyle, extractedStyle[breakpoint]); - } - - return extractedStyle; + return styleSheet[key]; } }) .filter(Boolean); } - let cssStyles = props.css + const cssStyles = props.css ? utils.processStyles({ styles: props.css || {}, theme: theme.values, @@ -224,20 +214,12 @@ export function createStitches(config = {}) { }) : {}; - if (cssStyles && breakpoint in cssStyles) { - // WARNING: lodash merge modifies the first argument reference or skips if object is frozen. - cssStyles = merge({}, cssStyles, cssStyles[breakpoint]); - } - - const mediaStyle = styleSheet.base[breakpoint] || {}; - const stitchesStyles = [ styleSheet.base, - mediaStyle, ...variantStyles, ...compoundVariantStyles, cssStyles, - ]; + ].map((style) => utils.applyMediaStyles(style, matchedMedias)); const allStyles = typeof props.style === 'function' diff --git a/src/internals/utils.js b/src/internals/utils.js index 81e10d6..bc356ba 100644 --- a/src/internals/utils.js +++ b/src/internals/utils.js @@ -59,24 +59,41 @@ function matchMediaRangeQuery(query, windowWidth) { return result; } -export function resolveMediaRangeQuery(media, windowWidth) { - const entries = Object.entries(media); - let result; - - for (let i = 0; i < entries.length; i++) { - const [breakpoint, queryOrFlag] = entries[i]; - - // TODO: handle boolean flag - if (typeof queryOrFlag !== 'string') continue; - - const match = matchMediaRangeQuery(queryOrFlag, windowWidth); +function createMatchedMedia(mediaKey) { + return { + mediaKey: mediaKey, + breakpoint: mediaKey && `@${mediaKey}`, + }; +} +export function resolveMediaRangeQuery(queryObjects, windowWidth) { + const iterator = Object.entries(queryObjects); + const mediaAppliedKeys = []; + for (let i = 0; i < iterator.length; i++) { + const [key, query] = iterator[i]; + if (query === true) { + mediaAppliedKeys.push(createMatchedMedia(key)); + } + if (typeof query !== 'string') continue; + const match = matchMediaRangeQuery(query, windowWidth); if (match) { - result = breakpoint; + mediaAppliedKeys.push(createMatchedMedia(key)); } } + return mediaAppliedKeys; +} - return result; +export function applyMediaStyles(originalStyle, matchedMedias) { + return matchedMedias.reduce( + (currentStyle, matcheMedia) => { + const breakpoint = matcheMedia.breakpoint; + if (breakpoint && breakpoint in originalStyle) { + return merge(currentStyle, originalStyle[breakpoint]); + } + return currentStyle; + }, + { ...originalStyle } + ); } export function processThemeMap(themeMap) {