Thoughts on supporting an API something like:
/** @jsx jsx */
import { jsx, useTheme } from 'theme-ui'
function SomeText (props) {
const defaultTheme = {
breakpoints: ['40em', '56em'],
fontWeight: 'bold',
fontSize: [ 4, 3 ],
color: 'primary'
backgroundColor: 'lightgray',
margin: [ 1, 3 ]
}
// this would allow setting a component theme via a theme prop or via context
const theme = useTheme(defaultTheme, props.theme)
return (
<div
css={{
fontWeight: theme.fontWeight,
fontSize: theme.fontSize,
color: theme.color,
backgroundColor: theme.backgroundColor,
margin: theme.margin
}}
{...props}
/>
)
}
Where the breakpoint theme values are computed using a container query using the component root element instead of a media query? This would better support themes for reusable components that adjust based on container size. FWIW ResizeObserver has pretty decent support now.
Sorry for the slow response! Using ResizeObserver for "responsive" values is an interesting idea! I think to keep inline with the current API, the responsive values should probably be defined at the component level rather than the theme level though. If you'd like to add an optional package to support something like this, feel free to open a PR
It would be really great to have breakpoints based on the component's width. Then we will be able to do cool things like this: https://philipwalton.github.io/responsive-components/#calendar
ResizeObserver is probably the best option, but we need JS - so, it won't be a part of styled-system css() function.
Should Box component handle this?
Another question, if we define a component this way:
<Text width={['100%', '50%']} breakpoints={['40em', '52em']}>Hello</Text>
Does prop-breakpoints override breakpoints for the theme?
@jxnblk What do you think about it?
@alexpermyakov if container queries are an option, I would suggest not setting global/theme level breakpoints and instead always define them at the component level.
@junyper Agree.
Actually, we can slightly extend css() function and provide a dynamically calculated breakpoint value (via ResizeObserver):
test('returns styles for specified breakpoint', () => {
const result = css({
width: ['100%', '50%', '25%'],
breakpoints: ['40em', '52em'],
})({ breakpoint: 0 })
expect(result).toEqual({
width: '50%',
})
})
Just revisiting this again, could a React hook make sense for this? I'm thinking defaulting to using theme.breakpoints, but allowing for custom breakpoints as an argument, then returning a value that could be used to pass styles to the sx prop -- curious to hear what others think
I've created a draft PR, this example also uses hooks: https://github.com/system-ui/theme-ui/pull/579/files. Is that what you meant? I'm not sure what is the best way to pass styles.
@alexpermyakov thanks for the PR! I don't think the Box component's implementation should change at all for what I mentioned above. Instead, I'd suggest a separate/optional package that is a theme-aware resize observer hook. I'd suggest starting with either a README or failing unit tests to document the API
Not sure I've got it all correctly. Do you mean a hook like this?
const sxStyles = useBreakpoint(theme, breakpoints);
Also, we need to provide a dom-element for ResizeObserver, so this hook also needs this info.
So, it can be something like it:
const ResizedText = (props) => {
const ref = useRef(null);
const sxStyles = useBreakpoint({theme, props.breakpoints, domEl: ref.current});
return <Text ref={ref} {...props} sx={sxStyles}>Hello</Text>
};
It means we need to use ref. This is a reason I thought that changing Box component might be a working solution, how do you see this API @jxnblk?
Also, custom breakpoints won't work with the current styled-system css function, any thoughts on this one?
I think it could be similar to the @theme-ui/match-media API, but also return a ref, like you mentioned -- I don't think it needs to return a style object, just an index or key for conditionally applying other styles in the sx prop
@junyper Looks like it's not easy to achieve, check out this tweet:
https://twitter.com/philwalton/status/1220790253956395008
and this:
https://github.com/system-ui/theme-ui/pull/587#discussion_r370108817
It won't work when we change the width based on a breakpoint value.
And react-container-query has the same issue:
https://github.com/react-container-query/react-container-query/issues/70
There are two options:
@alexpermyakov I think you can use requestAnimationFrame to get around that Chrome issue. At least I have done that in the past. There is a mention of it in that react-container-query issue: https://github.com/react-container-query/react-container-query/pull/77
@junyper yep, we can avoid the loop and show a warning, but the width cannot be set correctly (because there is _no correct_ width for some cases):
Example
<ResponsiveText breakpoints={['480px']} widths={['100%', '25%']}>
Hello
</ResponsiveText>
If the component width is more than 480px - use breakpoint index 0 and set the width to 25%.
But if 25% is less than 480px - use breakpoint index 1 and set the width to 100% and if the width is more than 480px...
I think requestAnimationFrame and the warning still make sense, we can do a lot with the component queries.
@alexpermyakov I'm not sure it's that much of an issue. At least maybe it's not up to the library to solve this issue for the user. Think of an infinite loop in a React.useEffect: this happens quite often and is easy to achieve in userland but still React doesn't provide any arbitrary built-in solution and let it up to the user to solve it. They don't even provide a warning or any way to debug it...(not saying it's a good thing).
Also in the case of container queries, it's quite obvious for the user why an infinite loop happened.
@Grsmto Agree, I still make a lot of sense. Not sure we can do much though, @jxnblk is not responding about it.
We鈥檙e not intending to support these features in Theme UI. You can already nest ThemeProviders, & we鈥檒l support container queries when browsers do. If you鈥檇 like to contribute a package for this functionality though, go for it!
Most helpful comment
Sorry for the slow response! Using ResizeObserver for "responsive" values is an interesting idea! I think to keep inline with the current API, the responsive values should probably be defined at the component level rather than the theme level though. If you'd like to add an optional package to support something like this, feel free to open a PR