linaria
linaria copied to clipboard
Support for responsive props
Describe the feature
Is there a way to implement responsive props with linaria similar to https://github.com/emotion-js/facepaint?
Motivation
When implemening a primitive component using Box
or Text
, it is absolutely necessary to support responsive behaviour.
Currently it is only possible to control props supplied to component externally such as
const isMobile = useMediaQuery(breakpoints.MOBILE)
<Box padding={isMobile ? 20 : 40} />
Possible implementations
It would be great if we could do something like:
<Box padding={[20, 40]} />
which could generate css media queries based on a static config.
Related Issues
Probably #465
Like in Theme-UI. I like it!
Is that something linaria has on its roadmap? I'm struggling with the same use case as mentioned by @afzalsayed96, I think this pattern is quite common, and really helpful when building kit libraries. Look at mentioned Theme-UI, styled-system, rebassjs, chakra-ui, and probably much more.
Idk how it can be implemented without runtime and without multiplying the size of generated styles. I mean, we can probably do something like this:
const cssWithMq = facepaint([
'@media(min-width: 420px)',
'@media(min-width: 920px)',
'@media(min-width: 1120px)'
])
const myClassName = cssWithMq`
background-color: 'hotpink';
text-align: 'center';
width: ${['25%', '50%', '75%', '100%']};
& .foo: {
color: ${['red', 'green', 'blue', 'darkorchid']};
& img: {
height: ${[10, 15, 20, 25]};
}
}
`;
but since we don't parse css, all we can do is repeat that block 4 times with media queries. And that will quadruple the size of generated css. It's not a problem for styled-components and emotion, because they generate styles on a client-side and in that case size doesn't really matter.
Yup, probably need to wait until we could use css properties in media queries like this:
@media (min-width: --theme-breakpoint-sm}) { color: blue; }
Probably, we can generate something like this:
@media(min-width: 420px) {
--some-generated-variable-for-color-in-myClassName: 'green';
}
@media(min-width: 920px) {
--some-generated-variable-for-color-in-myClassName: 'blue';
}
@media(min-width: 1120px) {
--some-generated-variable-for-color-in-myClassName: 'darkorchid';
}
. myClassName {
color: var(--some-generated-variable-for-color-in-myClassName, 'red');
}
Yes, but the problem is occurring when those breakpoints are customizable, not static. Usually passed as theme properties. I'm assuming it can't be done at compilation time and requires some runtime to work.
Yep, you are right. It will work only for constants.
I wonder if there's a good pattern to integrate with an existing runtime-library that adds breakpoints into the render tree.
https://www.npmjs.com/package/@artsy/fresnel#why-not-conditionally-render Is nice due to it's SSR support, but it's also a bit odd that it simply ships server-rendered html for all breakpoints. Almost works against the whole point of ssr... performance.
React-responsive has some nice hooks for media queries.
@devinrhode2 I don't sure that it's what you are looking for but that plugin allows to use Linaria and styled/emotion at the same time.
It's not a problem for styled-components and emotion, because they generate styles on a client-side and in that case size doesn't really matter.
Not true for SSR and SSG.
Not true for SSR and SSG.
That's a good point. But do they really care about the size of styles in that case?
You can kind of do this:
const breakpoints = [
["max-width", "40em"],
["min-width", "52em"],
["min-width", "64em"],
["min-width", "80em"]
]
const fontSteps = {
1: "10px",
2: "90px",
3: "15px",
4: "25px",
6: "30px",
7: "40px"
};
export function fontSize(values){
const styles = {};
breakpoints.forEach((breakpoint, idx) => {
styles[`@media (${breakpoint[0]}: ${breakpoint[1]})`] = {
fontSize: fontSteps[values[idx]],
};
});
return styles;
}
A responsive style function like styled-system!
Then use it:
import { styled } from "@linaria/react";
export const H1 = styled.h1`
... other styles ...
${fontSize([4, 6, 7])}
`;
I've struggled to find a way to pass in the [4, 6, 7]
via props but can't get it to work. That seems like the only missing piece. Also, as soon as I start abstracting away that line styles[`@media (${breakpoint[0]}: ${breakpoint[1]})`]
I get a Linaria error saying the static compiler can't infer the styles (or something along those lines). But I'm happy enough with zero runtime if I have to duplicate code. This is pretty much the best of both worlds.
I've struggled to find a way to pass in the [4, 6, 7] via props but can't get it to work.
I'm afraid, that this is impossible because props interpolation is implemented through css variables, that can be used only as values of css properties.
@Anber can not this be achieved at the component level by using matchMedia under the hood
@joseDaKing will it make Linaria a non-zero-runtime library? :)
Idk how it can be implemented without runtime and without multiplying the size of generated styles. I mean, we can probably do something like this:
const cssWithMq = facepaint([ '@media(min-width: 420px)', '@media(min-width: 920px)', '@media(min-width: 1120px)' ]) const myClassName = cssWithMq` background-color: 'hotpink'; text-align: 'center'; width: ${['25%', '50%', '75%', '100%']}; & .foo: { color: ${['red', 'green', 'blue', 'darkorchid']}; & img: { height: ${[10, 15, 20, 25]}; } } `;
but since we don't parse css, all we can do is repeat that block 4 times with media queries. And that will quadruple the size of generated css.
Would it be an option to start parsing the css string and only repeat the css attribute for the according responsive values, or would compile times explode? With object interpolations it would even be simpler, no css parsing necessary but I guess we need a solution that works for template literals too.
maybe an alternative would be something like this? Not as nice to read though:
const medias = [
'@media(min-width: 420px)',
'@media(min-width: 920px)',
'@media(min-width: 1120px)'
]
const responsive = (attribute, responsiveValues) => responsiveValues.map((value, index) => index === 0 ? `${attribute}: ${value}` : `${medias[index-1]} { ${attribute}: ${value} }`).join(';'))
const myClassName = css`
background-color: 'hotpink';
text-align: 'center';
${responsive('width', ['25%', '50%', '75%', '100%']};
`;
another option I could think of would be a PostCSS plugin that turns array values into media queries, but i think that transpilation would be better done inside linaria for encapsulation reasons.
I don't think it's a silver bullet, but it may be worth seeing how the 4x larger css compresses under gzip, and then serving that with an http2/spdy server.. and of course, browser parse time will also need to be measured