cssinjs / jss

JSS is an authoring tool for CSS which uses JavaScript as a host language.
https://cssinjs.org
MIT License
7.08k stars 399 forks source link

[react-native] React Native Support #368

Open jtag05 opened 7 years ago

jtag05 commented 7 years ago

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.

kof commented 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.

fmsouza commented 7 years ago

@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

kof commented 7 years ago

@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.

fmsouza commented 7 years ago

@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.

kof commented 7 years ago

Yeah, cross-platform styles will need to use only subset of css supported by rn.

kof commented 7 years ago

I think its a matter of just structuring the code, one can put it in separate dirs or use suffixes.

kof commented 7 years ago

I guess we are not using exact same presentational components for rn and web, right?

dagatsoin commented 7 years ago

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.

DenisIzmaylov commented 7 years ago

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.

kof commented 7 years ago

Media queries for react-native https://github.com/tuckerconnelly/uranium

grigored commented 7 years ago

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}/>;
        }
}
kof commented 7 years ago

Sounds good, what about structure? How do you organize react-native, web and shared styles? Please write a blog post about this stuff!

iamstarkov commented 7 years ago

@kof you have:

grigored commented 7 years ago

We 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.

iamstarkov commented 7 years ago

there is also interesting react-primitives project

canercandan commented 6 years ago

@grigored also interested by this stuff feel free to share more examples. thanks.

grigored commented 6 years ago

@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.

canercandan commented 6 years ago

@grigored look nice thanks for sharing it, I am definitely interested for seeing more about this API

yordis commented 6 years ago

Just a little feedback folks, coming from styled-components which I love it,

but I learned two things from there.

  1. 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.

  2. 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.

dantman commented 6 years ago

How about the following ideas: (I'm thinking about this from the context of the JSS configuration Material UI uses)

yordis commented 6 years ago

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: {}
    })
  }
})
dantman commented 6 years ago

@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'])

dantman commented 6 years ago

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.

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.

kof commented 6 years ago

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.

dantman commented 6 years ago

@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.

kof commented 6 years ago

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…

kof commented 6 years ago

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.

kof commented 6 years ago

So I can see both cases useful:

  1. More separation:

// 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.

  1. Less separation
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.

kof commented 6 years ago

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.

dantman commented 6 years ago

As a sample, here are the src/List/ListItem.js styles from Material UI annotated with how they relate to React Native:

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.

kof commented 6 years ago

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 .

dantman commented 6 years ago

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.

kof commented 6 years ago

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"

dantman commented 6 years ago

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.

kof commented 6 years ago

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).

kof commented 6 years ago

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.

dantman commented 6 years ago

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 ....

kof commented 6 years ago

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

kof commented 6 years ago

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?

kof commented 6 years ago

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} }

dantman commented 6 years ago

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.

yordis commented 6 years ago

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).

dantman commented 6 years ago

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.

yordis commented 6 years ago

@kof any updates on this?

yagudaev commented 6 years ago

Would be interesting to see this working. It seems like styled-components support react-native https://www.styled-components.com/docs/basics#react-native

just-Bri commented 4 years ago

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?

chrisarts commented 1 year ago

@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.