Open jtag05 opened 7 years ago
Not yet, also I need to learn react-native first, so can't predict when this may happen. If someone is experienced in react native and can tell what jss needs to addd to support it I am happy to support it of course. Will leave this issue as a reminder in case some one wants to help out.
@kof In the case of ReactNative, the styling is already a JS object configuration written inside a js module. And the styling attributes are sort of camelcase CSS attributes, just like this tool.
I'm attaching here a component I've created which behaves as a searchbar for ReactNative, you can take it as reference to understand how it works: https://github.com/fmsouza/rn-seed-simple/blob/master/src/common/searchbar.js
@fmsouza thanks, yes, the question is what specifically does jss needs to do to have a full RN support. If its just about sharing style declarations, it should work already.
@kof If this solution idea is to make these styles cross-platform between web and mobile, maybe it can be a little more tricky, as you don't have nested styles or events like hover
and active
to deal with in RN. For RN the output will be mostly a raw js object for each element.
Yeah, cross-platform styles will need to use only subset of css supported by rn.
I think its a matter of just structuring the code, one can put it in separate dirs or use suffixes.
I guess we are not using exact same presentational components for rn and web, right?
Indeed, the fondamental element are different. For example DIV is VIEW in rn.
Here is another CSS in JS project which is compatible with RN. Maybe worth to take a look but I agree that the main effort should be to lint all incompatible rules.
Sample from Official Documentation:
const styles = StyleSheet.create({
bigblue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
})
class LotsOfStyles extends Component {
render() {
return (
<View>
<Text style={styles.red}>just red</Text>
<Text style={styles.bigblue}>just bigblue</Text>
<Text style={[styles.bigblue, styles.red]}>bigblue, then red</Text>
<Text style={[styles.red, styles.bigblue]}>red, then bigblue</Text>
</View>
)
}
}
We could use this wrapper:
To get API like:
import { fromCSS } from 'jss-react-native'
const styles = fromCSS`
bigblue {
color: blue,
font-weight: 'bold',
font-size: 30px
}
red {
color: 'red'
}
`
class LotsOfStyles extends Component {
render() {
return (
<View>
<Text style={styles['red']}>just red</Text>
<Text style={styles['bigblue']}>just bigblue</Text>
<Text style={[styles['bigblue'], styles['red']]}>bigblue, then red</Text>
<Text style={[styles['red'], styles['bigblue']]}>red, then bigblue</Text>
</View>
)
}
}
The same approach we could apply for original JSS. I meant fromCSS
function by using Template Strings. According to #220.
Media queries for react-native https://github.com/tuckerconnelly/uranium
Not sure if it's the best approach, but we are using the following structure for sharing JSS on React Web and Stylesheets on React Native
//platform.js
export function createStyles(styles, component) {
return injectSheet(styles, component);
}
//platform.native.js
export function createStyles(styles){
return WrappedComponent => class extends React.Component {
render() {
return (
<WrappedComponent
classes={StyleSheet.create(styles)}
{...this.props}
/>
);
}
}
}
//commonComponent.js
import createStyles from "./platform.js"
const styles = {button: {backgroundColor: 'blue'}}
@createStyles(styles)
export class SearchListView extends React.Component {
render() {
const {classes} = this.props;
return <View style={classes.button}/>;
}
}
Sounds good, what about structure? How do you organize react-native, web and shared styles? Please write a blog post about this stuff!
@kof you have:
button/index.js
for webbutton/index.ios.js
for iosbutton/index.android.js
for androidWe have just began unifying the React and React Native components, will write a blog post/sample github repo once we are ready to deploy in production, as we still have to sort some issues. But basically we are using what @iamstarkov mentioned: we use file.js, file.native.js (if we need separate ios/android files, then file.ios.js and file.android.js also works). Webpack only looks at file.js when bundling our web app, and the react native packager is smart enough to pick the right file.
there is also interesting react-primitives project
@grigored also interested by this stuff feel free to share more examples. thanks.
@canercandan tldr: have a look here https://github.com/grigored/cross-platform-react/tree/master/src/primitives/createStyles .
long story: I'm in the very early stage of open sourcing a library to help with writing code once, rendering everywhere (web, native, sketch). Should have an alpha in a week or two. This is based on a project on which my team worked in the past two-three years. The key points: 1) have most of the material-ui components available on native, with a native Android/IOS look and feel. And have other native components available for web. 2) be able to write your own styles like this:
const styles = {
container: {
ios: {
padding: 5,
},
web: {
margin: 5
},
}
}
or
const styles = {
container: {
padding: {
ios: 5,
web: 10,
sketch: 15
},
}
}
3) be able to reuse navigation/ redux etc across platforms.
@grigored look nice thanks for sharing it, I am definitely interested for seeing more about this API
Just a little feedback folks, coming from styled-components
which I love it,
but I learned two things from there.
I wouldn't like to see any string based driven styles, JS objects are fine specially creating tooling around, it is always easier to work with than manipulating strings.
I wouldn't expect any unit added to anything from the package. styled-components assumes that px
are the unitless
unit for RN and creates more chaos than helping the project and one of the main reason for them is that they were Web developers forcing Mobile developer to their decisions. As a 6+ web developer and less than 1 year on RN, I can't agree on that at all. I used it and I added more chaos to the code without any reason. Each platform has it own unique constrains and it is better to embrace them and improve the quality of the development with the tools, but definitely do not force things on developers.
How about the following ideas: (I'm thinking about this from the context of the JSS configuration Material UI uses)
StyleSheet.create
using the style object instead of css classes. Though allow for a factory to be passed in to use instead of StyleSheet.create
(in case a wrapper exists)StyleSheet.create
currently permanently registers object and never releases them, so if JSS is used dynamically like primaryButton: { color: theme.palette.primary }
and when the theme changes (primary color blue, then green, then back to blue) we create styles for {color: 'blue'}
, then {color: 'green'}
, then {color: 'blue'}
the first two objects will still exist in memory. And if this is done continually (flipping between two colors for some reason) we will leak memory. So we should at least re-use styles when they haven't changed. (This issue could be solved if work on this and this is resumed)style
instead of registering stylesheets)StyleSheet.create
ids instead of classes. Though maybe we should pass styles
instead of classes
. Hard to say, there are pros and cons.{foo: {color: 'red', '&:active': {color: 'blue'}}}
into styles = {foo: {color: 'red'}, fooActive: {'color': 'blue'}}
then write something like web:className={classes.foo}
native:style={this.state.isPressed ? styles.fooActive : styles.foo}
./^\&+$/
then merge the styles from that into the main object.
foo: {'&': {...mainStyles}, 'span': {...labelStyles}}
to keep things on the same level instead of writing foo: {...mainStyles, 'span': {...labelStyles}}
and '&&', '&&&', etc which are used to make selectors like .foo-1.foo-1
that have a higher specificity and override other styles.{width: '24px'}
is written emit a warning that it should be written as {width: 24}
instead so it'll work in both React DOM and React Native. This will help avoid having people write a bunch of code for the web and have it work fine, then switch over to the native version of their app and find it's breaking and they need to refactor a bunch of things like '24px'
to 24
just to see that the actual code they wrote works fine in React Native.
withStyles({
foo: {
backgroundColor: 'red',
web: {
// We use a h2 on the web, so reset a few things on web
margin: 0,
padding: 0,
fontSize: 'inherit',
color: 'inherit',
},
native: {
aspectRatio: 16/9,
},
android: {
elevation: 1,
},
ios: {
...shadowStylesInsteadOfElevation,
},
}
})
Or perhaps more web-like without overloading the selector pattern and forcing us to hard code a platform list (others exist like windows
/uwp and hopefully people will create more platform implementation):
withStyles({
foo: {
backgroundColor: 'red',
'@media (platform: web)': {
...
},
'@media (platform: native)': {
...
},
'@media (platform: android)': {
...
},
'@media (platform: ios)': {
...
},
}
})
Or if we don't want to come up with special cased non-standard css and instead want to go the JS route using Symbols or special prefixes:
withStyles({
foo: {
backgroundColor: 'red',
...
},
...
},
...
},
...
},
}
})
Where JSSPlatform is a function (which could be given a better name) that just accepts a platform name (or 'web' or 'native') and outputs a Symbol (or just prefixes the string with a special internal prefix).
I love this
withStyles({
foo: {
backgroundColor: 'red',
[JSSPlatform('web')]: {
...
},
[JSSPlatform('native')]: {
...
},
[JSSPlatform('android')]: {
...
},
[JSSPlatform('ios')]: {
...
},
}
})
Yes it could be more noice but it is really clear what is going on. Also
withStyles({
foo: {
backgroundColor: 'red',
...JSSPlatform({
// insert platform key here
web: {},
ios: {}
})
}
})
@yordis your last code block just made me remember this is actually 90% taken care of.
withStyles({
foo: {
backgroundColor: 'red',
...Platform.select({
web: {},
ios: {}
})
}
})
We just need to wrap react-native's Platform
module with one that exposes a browser/web version and accepts the native
key to refer to all react-native platforms (it'll have to be up to react-native-web what it wants to consider itself, depending on how it handles jss like web styles). Heck, someone has been squatting on the react-platform
package name all this time, we could easily take that package name over and provide a standard-ish react package for universal platform code splitting of React.
If we're going to go that route, we might as well also implement a babel transform that will see Platform.select
and strip out certain keys. It can be set to a web
preset that will strip out everything except web
, a native
preset that will strip out web
, and any other string will be presumed to be a specific native platform (even if it's something like 'windows' which isn't part of the standard react-native) and will strip out any thing except native
and a platform key matching the string given. Note that this may need extra though for the electron
platform. Maybe the transform should just accept either an includes
or an excludes
array. (includes: ['web']
, excludes: ['web']
, includes: ['native', 'android']
, includes: ['web', 'electron']
)
Write an optional plugin that will warn you about coding mistakes that work on web but will break React Native. For example, when {width: '24px'} is written emit a warning that it should be written as {width: 24} instead so it'll work in both React DOM and React Native. This will help avoid having people write a bunch of code for the web and have it work fine, then switch over to the native version of their app and find it's breaking and they need to refactor a bunch of things like '24px' to 24 just to see that the actual code they wrote works fine in React Native. We could also write an eslint plugin.
Maybe I'll change my suggestion on this. Like how JSS acts as an enhancement for browser CSS (camel cased named, automatic prefixing, nesting selectors and media queries, etc...) perhaps we should actually think of it also as an enhancer for React Native StyleSheets.
'24px'
values to 24
padding: '0 24px
to their longer versions paddingLeft: 24, paddingRight: 24, paddingTop: 0, paddingBottom: 0
. Likewise for most other basic css shorthands. Especially the flexbox ones.This of course won't be perfect, we will still need to emit warnings for things like width: '50%'
because RN doesn't support any type of size value except density-independent pixels.
I am also wondering if it makes sense to mix cross-platform styles in one rule. How much can mobile styles be similar to the web? Wouldn't it be easier to just maintain separate styles for e.g. by using separate files for web/mobile? Or/and extract common styles for all platforms and just import/merge them. Then you only need something that picks the right file, for e.g. during build step for just a function.
@kof They are billed at about 90% as just a subset of css styles. The 10% being a custom aspectRatio
, elevation
for Android, maybe some quirks in the flexbox implementation though they've been fixing things, the fact you pass objects/styles/arrays of those to style
instead of classes to className
and a single object with bad practice inline styles to style
, and the fact that specificity is based on order in the styles array instead CSS's rules on cascading.
i.e. If you write styles for RN's StyleSheet.create and then just dump them into JSS they will pretty much just work. In fact instead of using screenshots or emulators RN's own styling documentation now just uses a react-native-web
based player to display examples live in your browser.
In fact there is already a react-primitives project that exposes View, Text, and StyleSheet that have the RN api but are used on the web.It does attempt to handle specificity by mocking the RN behaviour by controlling the order of the css classes from the effect of the style
prop. And it seems to support selectors. But then you're designing your web code based on React Native's api (which is a problem if your library is web first but also wants to support native apps.
90% between web and mobile? It might heavily depend on design. I can imagine some buttons being 90% the same, but layouts, margins and more complex components…
Also depending on wether the same person is working on mobile and web, it might be better to have stronger separation if people are different or even teams are different. If you have one component, mobile developer needs to test on the web for each change. It might be handy though if really 90% of the code are the same and same people work on web and mobile.
So I can see both cases useful:
// commonStyles.js
export default {
button: {
// common styles
}
}
// buttonStyles.web.js
import styles from './commonStyles'
export default merge({}, styles, {
button: {
// specific web styles
}
}
Then picking the file based on a platform evtl using some build magic. Separate builds can allow to have separate bundles and not load mobile code on the web and vice-versa.
const styles = {
button: {
// common styles
display: 'inline-block',
web: {
fontSize: X
},
mobile: {
fontSize: Y
},
android: {
// something specific to android only
}
}
}
For the later we would need a JSS plugin which takes the platform specific names and merges/removes them based on a platform. All platforms code would be loaded in that case. A babel plugin could optimize that though.
Also for RN, it could be a different preset for e.g. "jss-react-native-preset" which will only use plugins which make sense there.
As a sample, here are the src/List/ListItem.js
styles from Material UI annotated with how they relate to React Native:
border: '1px ...
-> borderWidth: 1, ...
) which a react-jss plugin could do automaticallytextDecoration
line also needs this)https://gistpreview.github.io/?0f6ee2998220b2965e2e0280bccd4852
As you can see the majority of the styles that define what the component looks like could be used with JSS in both the web and React Native. The web-only styles are generally things like :hover, animations, and a few css properties that are only necessary on the web because of the long legacy of things we don't really want anymore.
This is library code and no layout stuff.
On Sun, Apr 1, 2018, 16:13 Daniel Friesen notifications@github.com wrote:
As a sample, here are the src/List/ListItem.js styles from Material UI annotated with how they relate to React Native:
- red lines are only relevant to the browser
- green lines should work the same in RN and browsers
- yellow lines are relevant to RN but need a tweak to work in RN (e.g border: '1px ... -> borderWidth: 1, ...) which a react-jss plugin could do automatically
- grey lines need to be moved to a separate class since you need to apply them to a different element in RN, but the style otherwise works the same and you can use the same 2 css classes in both web and React Native (the yellow textDecoration line also needs this)
https://gistpreview.github.io/?0f6ee2998220b2965e2e0280bccd4852
As you can see the majority of the styles that define what the component looks like could be used with JSS in both the web and React Native. The web-only styles are generally things like :hover, animations, and a few css properties that are only necessary on the web because of the long legacy of things we don't really want anymore.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/cssinjs/jss/issues/368#issuecomment-377789577, or mute the thread https://github.com/notifications/unsubscribe-auth/AADOWPEXX5sXRIVQ7B_zqGWXfSQa5nI9ks5tkOBvgaJpZM4K5toz .
This is library code and no layout stuff.
That's what I have to work with. And if we can get libraries working in both RDom and RN this will still be legitimately useful. Then even if you have to do layout differently, for all the components you use you can just use the same library on RN and RDom.
I'd be happy to look at any website layout examples you have.
Yes, totally, just saying that the amount of same code is because it is lib code. As I said before, this will be different in application code, which leads me to: "We need both strategies"
Yes, totally, just saying that the amount of same code is because it is lib code. As I said before, this will be different in application code, which leads me to: "We need both strategies"
Ok, but I still would enjoy seeing an application code example that I can annotate to see how it behaves in web vs mobile.
My latest projects have all been Material Design based and focused on components, so a lot of that large blob of application styles has just disappeared for me. And my current project is one that started in Polymer 2.x, went to Polymer+React, and only started using React+Material UI now. My major application styles are still in a <custom-style>
and this project is web only.
Btw. more ideas for the syntax with built-in platform dependent code:
const styles = {
button: {
// common styles
display: 'inline-block',
},
'button:web': {
fontSize: X
},
'button:mobile': {
fontSize: Y
},
'button:android': {
// something specific to android only
}
}
Benefit here:
downside - quotes (mb a different separator can be used though).
Another idea:
const styles = {
button: {
// common styles
display: 'inline-block',
},
buttonWeb: {
extend: 'button',
fontSize: X
},
'buttonMobile': {
extend: 'button',
fontSize: Y
},
'buttonAndroid': {
extend: 'button',
// something specific to android only
}
}
When using it, just do this: className={classes[`${button}${platform}`]}
A helper function: const ps = (name, platform) => name + platform || currentPlatform
could result in:
const styles = {
button: {
// common styles
display: 'inline-block',
},
[pl('button', 'web')]: {
extend: 'button',
fontSize: X
}
}
className={classes[pl('button')]}
Update: looks worse than other suggestions.
Hmmm... I was going to say switching styles is probably irrelevant because it's possible to do this without jss
handling it by using Platform.switch
and by taking advantage of fooStyles.web.js
, fooStyles.android.js
, fooStyles.native.js
(proposal if we need it), fooStyles.common.js
(import 'fooStyles.common'
).
However I just realized, while that is sort of true. The Platform.switch
pattern falls apart when you don't want something to be overriden.
import Platform from 'a-react-native-like-platform-wrapper-we-create';
const styles = {
button: {
background: 'red',
'&:focus': {
// Assume we have a plugin that makes &:focus work in native
background: 'blue',
},
...Platform.switch({
web: {
// Uh oh... this will overwrite &:focus instead of merging
// the background and outline together. We will have a confusing
// error unless we use `...Platform.switch({` multiple times.
'&:focus': {
// Web needs a few resets for the default styles
outline: 'none'
}
},
})
}
}
Though perhaps it could work if it was inside a JssPlatform.switch
and knew how to merge styles. Then we'd tell everyone to use an all
key instead of using ...
.
However I just realized, while that is sort of true. The Platform.switch pattern falls apart when you don't want something to be overriden.
Not really, switch() could be doing a deep merge or it could be Platform.switch and Platform.merge
Btw. looking back at previous proposals, @media android
looks semantically the best. What I dislike about @media in CSSinJS is that it requires the quotes and stuff inside needs expensive parsing. What if we introduce a new dsl for @media and leverage it to implement that switch thing?
Here is an example:
It can work for any browser media query as well as native
const styles = {
button: {
background: 'red',
media: {
type: 'screen',
query: {
width: 360
},
background: 'yellow',
color: 'red'
}
}
}
Similar to how @media query is currently supported, here is an example for top level media DSL:
const styles = {
button: {
background: 'red'
},
media: {
type: ['android', 'ios']
query: {
width: 360
},
button: {
background: 'yellow',
color: 'red'
}
}
}
It maps exactly to how @media is designed: @media {type} ($condition) { {rules} }
Not really, switch() could be doing a deep merge or it could be Platform.switch and Platform.merge
The problem stems from the fact that the Platform
I was proposing is not a Jss feature. It's a generic tool useful for everyone which is just a light polyfill/wrapper over what React Native already provides. And the use of ...
to merge with the common styles. And merging isn't supposed to be part of Platform.switch
's job. In fact, a proper Platform.switch
would do the opposite, given {all: 'a', native: 'b', android: 'c'}
a proper Platform.switch
would return 'c'
on Android, 'b'
on iOS, and 'a'
everywhere else. It would not try to merge anything.
That is why I mentioned a JssPlatform alternative at the end of my comment. We could still implement a react-platform
to handle the general use case. But we could use that as a base to create a JssPlatform that knows how to merge things in the best way for Jss.
Please don't merge the unitless from the native platform with units from the web. I really wouldn't like to see any px to non-unit
thing at all, leave each platform as it is.
Don't commit the same mistake of others where every single RN and Native developer hate the decision driven by Web developers (included me after so many years on web, I dont want to see this happening on jss, specially that carry more issues than the one that solves).
If you want to keep separate the unit vs non-unit
then create a factory for your values that knows when to add unit or not.
For example, https://github.com/straw-hat-team/design/blob/e707bc3d6094aa2c36a5a94cb764638390ab99c0/src/modular-scale.ts#L21-L26 that package let you to configure if you have units or not so every time you do modularScale.scale(number)
you don't need to pay attention to the unit issues.
Please, don't go for that route, or at least, do not force me to write px
for my RN code, each platform has it own advantage.
Just a note because some folks mention the px
to nothing
idea, this is CSS driven by Javascript anyway, let the people decide how to manage that (it is not that hard to deal with this at the end of the day, the example above is one solution).
Please, don't go for that route, or at least, do not force me to write px for my RN code, each platform has it own advantage.
I don't think anyone has suggested that we force RN code to use explicit px
units.
My first suggestion was a jss plugin and/or eslint plugin that will emit warnings when using unit values like 4px
in common styles so while developing for the web you know you're accidentally writing code that won't work in the native version.
Then I changed my mind. Since JSS is partially an enhancement over CSS (nesting selectors and media queries, scoping, etc) it makes sense for it to be an enhancement over StyleSheet.create
as well. So I switched to a "if it makes sense it should work" ideal, instead of forcing everyone to write styles one way for native compatibility JSS should just make whatever the user writes work on the platform if it's a trivial difference. In this case meaning, if it's a px
value then in RN convert it to a unitless number so you can just use either one.
The goal is to make as much code as possible work on both web and native (otherwise there is no value in bringing native support to JSS), not to force once platform's conventions on the other.
@kof any updates on this?
Would be interesting to see this working. It seems like styled-components
support react-native https://www.styled-components.com/docs/basics#react-native
Sorry to necro such an old thread but I'm wondering if any progress has been made recently. We are currently developing our RN app and would love to be able to share styles between web and mobile. We are using a Typescript enum to store colors which we share, but sharing a font style etc would be amazing.
What are the current blockers/limitations in terms of getting it working at a high or low level?
@kof I have a css parser for react native if you are willing to we can discuss about add this feature to this lib, it will be awesome because the way this handles the css traverse is really good, but I dont have much knowledge about css on web, but I know how RN works and make it compatible with basic css.
First of all, thank you for the incredible library. It really solves so many fundamental issues I had with embedding CSS in my react projects.
I recently began work on a bit of a test project using react-native to build a universal app and would really like to incorporate JSS. I see that react-native support is on the roadmap, so I was wondering if there has been any attempts at this thus far? If not, I may be so inclined to take a stab at it on a fork.