react-native icon indicating copy to clipboard operation
react-native copied to clipboard

[w3c] ☂️ Web Styles (Part 1) umbrella issue

Open necolas opened this issue 3 years ago • 61 comments

Add support for Web styles to components

This is the umbrella issue for basic React DOM / Web style additions to React Native components, as described in this proposal: "RFC: Reduce fragmentation across platforms".

Each of the tasks listed below can be tackled with individual PRs that link back to this issue. Not every task has a known solution or implementation yet, so feel free to discuss implementation details in the comments. Each new style property should take priority over any existing equivalents.

Styles

Basic extensions (All available in React Native 0.71)

  • [x] aspectRatio support for string values, i.e., '16 / 9', to align with CSS. https://github.com/facebook/react-native/pull/34629
  • [x] fontVariant support for space-separated string values, i.e., 'small-caps common-ligatures'.
    • [x] https://github.com/facebook/react-native/pull/34641
    • [x] https://github.com/facebook/react-native/pull/36740
  • [x] fontWeight support for number values, i.e., 900. https://github.com/facebook/react-native/pull/34598
  • [x] transform support for string values, i.e., 'scaleX(2) translateX(20px)'. https://github.com/facebook/react-native/pull/34660#pullrequestreview-1104804653

Examples:

<View
  style={{
    aspectRatio: '16 / 9',
    transform: 'scaleX(2) translateX(20px)'
  }}
>

<Text
  style={{
    fontVariant: 'small-caps common-ligatures',
    fontWeight: 900
  }}
>

Equivalents

Available in React Native 0.71

  • [x] direction.
  • [x] Add objectFit. Partial equivalent to the resizeMode style and prop of <Image>. https://github.com/facebook/react-native/pull/34576
    • [x] Map objectFit === 'contain' to resizeMode = 'contain'.
    • [x] Map objectFit === 'cover' to resizeMode = 'cover'
    • [x] Map objectFit === 'fill' to resizeMode = 'stretch'
    • [x] Map objectFit === 'scale-down' to resizeMode = 'contain'
    • [ ] Support objectFit value of 'none'.
  • [x] Add pointerEvents. Equivalent to the pointerEvents prop of <View>. https://github.com/facebook/react-native/pull/34586/
    • [x] Retain the React Native specific box-none and box-only values.
  • [x] Add userSelect. Equivalent to using selectable prop on <Text>. https://github.com/facebook/react-native/pull/34575
  • [x] Add verticalAlign. https://github.com/facebook/react-native/pull/34567
    • [x] Map verticalAlign to textAlignVertical.
    • [x] Map verticalAlign === 'middle' to textAlignVertical = 'center';

Available in React Native 0.72

  • Add CSS Logical Properties
    • [x] Logical Border Radius
      • [x] https://github.com/facebook/react-native/pull/35342
      • [x] https://github.com/facebook/react-native/pull/35572
      • [x] borderEndEndRadius is equivalent to borderBottomEndRadius.
      • [x] borderEndStartRadius is equivalent to borderBottomStartRadius.
      • [x] borderStartEndRadius is equivalent to borderTopEndRadius.
      • [x] borderStartStartRadius is equivalent to borderTopStartRadius.
    • [x] Logical margin (Fabric-only)
      • [x] Map marginInlineStart to marginStart.
      • [x] Map marginInlineEnd to marginEnd.
      • [x] Map marginBlockStart to marginTop.
      • [x] Map marginBlockEnd to marginBottom.
      • [x] Map marginBlock to marginVertical.
      • [x] Map marginInline to marginHorizontal.
    • [x] Logical padding (Fabric-only)
      • [x] Map paddingInlineStart to paddingStart.
      • [x] Map paddingInlineEnd to paddingEnd.
      • [x] Map paddingBlockStart to paddingTop.
      • [x] Map paddingBlockEnd to paddingBottom.
      • [x] Map paddingBlock to paddingVertical.
      • [x] Map paddingInline to paddingHorizontal.
    • [x] Logical insets (Fabric-only) https://github.com/facebook/react-native/commit/9669c10afceef65626c82149210afc07d47df98b
      • [x] inset is equivalent to top & bottom & right & left.
      • [x] insetBlock is equivalent to top & bottom.
      • [x] insetBlockEnd is equivalent to bottom.
      • [x] insetBlockStart is equivalent to top.
      • [x] insetInline is equivalent to right & left.
      • [x] insetInlineEnd is equivalent to right or left.
      • [x] insetInlineStart is equivalent to right or left.
    • [x] https://github.com/facebook/react-native/pull/35999
    • [x] borderBlockColor is equivalent to borderTopColor & borderBottomColor.
    • [x] borderBlockEndColor is equivalent to borderBottomColor.
    • [x] borderBlockStartColor is equivalent to borderTopColor.

Outstanding (version to be determined)

  • [ ] borderStyle value of 'none'.
  • [ ] transformOrigin https://github.com/facebook/react-native/pull/37606
  • [ ] Add CSS Logical Properties.
    • [ ] https://github.com/facebook/react-native/pull/36046
    • [ ] https://github.com/facebook/react-native/pull/36242
    • [ ] borderInlineColor is equivalent to borderEndColor & borderStartColor.
    • [ ] borderInlineEndColor is equivalent to borderEndColor.
    • [ ] borderInlineStartColor is equivalent to borderStartColor.
    • [ ] borderBlockStyle is equivalent to borderTopStyle & borderBottomStyle.
    • [ ] borderBlockEndStyle is equivalent to borderBottomStyle.
    • [ ] borderBlockStartStyle is equivalent to borderTopStyle.
    • [ ] borderInlineStyle is equivalent to borderEndStyle & borderStartStyle.
    • [ ] borderInlineEndStyle is equivalent to borderEndStyle.
    • [ ] borderInlineStartStyle is equivalent to borderStartStyle.
    • [ ] borderBlockWidth is equivalent to borderTopWidth & borderBottomWidth.
    • [ ] borderBlockEndWidth is equivalent to borderBottomWidth.
    • [ ] borderBlockStartWidth is equivalent to borderTopWidth.
    • [ ] borderInlineWidth is equivalent to borderEndWidth & borderStartWidth.
    • [ ] borderInlineEndWidth is equivalent to borderEndWidth.
    • [ ] borderInlineStartWidth is equivalent to borderStartWidth.

Examples:

<View
  style={{
    pointerEvents: 'none'
  }}
>

<Text
  style={{
    userSelect: 'none',
    verticalAlign: 'middle'
  }}
>

<Image
  style={{
    objectFit: 'cover'
  }}
>

New features

Available in React Native 0.71

  • [x] Add expanded support for CSS Colors, e.g., hsla(). Potentially via Colorjs.io. https://github.com/facebook/react-native/pull/34600
  • [x] Add gap. https://github.com/facebook/yoga/pull/1116
<View
  style={{
    boxShadow: '1px 1px 1px 1px #eee',
    backgroundColor: 'hsla(50,50,50,0.5)',
    backgroundImage: 'url(https://image.png)',
    pointerEvents: 'none',
    transform: 'scale(0.9)',
    width: '5rem'
  }}
>

<Text
  style={{
    textShadow: '1px 1px 1px #eee',
    userSelect: 'none',
    verticalAlign: 'middle'
  }}
>

<Image
  style={{
    objectFit: 'cover'
  }}
>

necolas avatar Aug 15 '22 18:08 necolas

See companion issue related to props https://github.com/facebook/react-native/issues/34424

necolas avatar Aug 15 '22 18:08 necolas

Would flex gap apply here, or does that fall out of scope since it's still missing in Yoga?

From experience, the lack of gap support is one of the worst parts of styling on native vs. web. Shadows are up there too, but I use them less often.

It would be great to add objectPosition for images too.

nandorojo avatar Aug 15 '22 18:08 nandorojo

Would flex gap apply here, or does that fall out of scope since it's still missing in Yoga?

Yes, that needs to be added to Yoga which is a whole other project.

It would be great to add objectPosition for images too.

Done.

necolas avatar Aug 15 '22 18:08 necolas

please support for 'transform-origin' and 'display:grid'

meftunca avatar Aug 15 '22 21:08 meftunca

Percentage values for translate would be good. We already have this on web. Not sure if it's related to yoga, like flex gap.

translateX(50%), translateY(100%)

burakgormek avatar Aug 15 '22 22:08 burakgormek

Yes, that needs to be added to Yoga which is a whole other project.

already done, waiting for merge for 6 months

escaton avatar Aug 15 '22 23:08 escaton

  • Add expanded support for CSS Colors, e.g., hsla(). Potentially via Colorjs.io.

Hi @necolas, which CSS Color Module Level do we wanna meet? React native already supports rgb(), rgba(), #rgb, #rrggbb, #rgba, #rrggbbaa, hsl(), hsla(), colors as int values (in RGB mode) and named colors, what else do we want to support? hwb()? lab()?

gabrieldonadel avatar Aug 21 '22 21:08 gabrieldonadel

Level 4, as Colorjs.io does. Functions should also support space-separated values, not just comma separated.

necolas avatar Aug 22 '22 16:08 necolas

I added a new section for improving cross-platform compatibility of Animated.

It should be relatively easy to make Animated a separate package that works on web for React Native for Web. This would allow developers to have the same version of Animated running and avoid me needing to update Animated at arbitrary points in time, which is also tedious and error prone, as I have to check for new sources of crashes and copy-paste-edit dozens of files that use different module syntaxes and internal import paths to RN public APIs, like StyleSheet.

necolas avatar Aug 29 '22 20:08 necolas

already https://github.com/facebook/yoga/pull/1116, waiting for merge for 6 months

The RN team had planned to move to a new layout engine, but probably not anymore. So since there is an existing PR and it has a chance of being merged soon, I'll add gap too.

necolas avatar Aug 31 '22 18:08 necolas

HI @necolas i am taking this.

Add [objectFit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit). Partial equivalent to the resizeMode style and prop of <Image>.
 Map objectFit === 'contain' to resizeMode = 'contain'.
 Map objectFit === 'cover' to resizeMode = 'cover'
 Map objectFit === 'fill' to resizeMode = 'stretch'
 Map objectFit === 'scale-down' to resizeMode = 'contain'

Updates: PR Here https://github.com/facebook/react-native/pull/34576

gedeagas avatar Sep 02 '22 07:09 gedeagas

What about marginBlock for marginVertical and marginInline for marginHorizontal? And of course same for padding

efoken avatar Sep 05 '22 07:09 efoken

Ah and also I wanted to note, that lineHeight currently only supports pixel values. What about adding support for values like 1.5 as a multiplier of the current font-size?

efoken avatar Sep 05 '22 08:09 efoken

What about adding support for values like 1.5 as a multiplier of the current font-size?

That would be great. Unfortunately, it would that be a breaking change since RN currently treats unitless line-height as pixels. So first we'd probably need something like a codemod to change all existing lineHeight values from e.g. 14 to '14px', and then eventually re-introduce unitless value support as a multipler.

necolas avatar Sep 05 '22 18:09 necolas

Hi @necolas I will be taking this

 Add native support for single/multiple CSS box shadows.
 Cross-platform shadows to replace Android elevation style, and buggy iOS shadow* styles.```

sarulathadurai avatar Sep 06 '22 04:09 sarulathadurai

Please Add This Features List

  1. Display Grid & Table
  2. CSS Variable Support (For Global Theming)
  3. transform-origin
  4. position fixed&sticky

meftunca avatar Sep 06 '22 06:09 meftunca

If flow layout is being added, then could grid layout also be considered? It's absolutely ideal for app layouts, and in many ways is a much simpler model than flexbox.

nicoburns avatar Sep 07 '22 23:09 nicoburns

It would also be nice to add inset, a shorthand for setting left, right, top and bottom.

And also then maybe StyleSheet.absoluteFill and StyleSheet.absoluteFillObject can be deprecated?

efoken avatar Sep 28 '22 06:09 efoken

I like this idea. inset-inline and inset-block would also be useful. I'll add a bunch more logical properties too once we have an idea of how to support them

necolas avatar Sep 28 '22 18:09 necolas

@sarulathadurai are you still planning to work on box-shadow?

necolas avatar Oct 12 '22 18:10 necolas

@sarulathadurai are you still planning to work on box-shadow?

@necolas apologies I can't take it up

sarulathadurai avatar Oct 13 '22 05:10 sarulathadurai

Hi @necolas, I'd like to help on this one if it's available

Marcoo09 avatar Oct 25 '22 02:10 Marcoo09

@Marcoo09 yes please, there is no existing PR. Since this isn't a feature that can simply be aliased to existing styles, you might want to first share how you plan to implement this feature, so we can run it past the RN team before you get started. Thanks

necolas avatar Oct 25 '22 22:10 necolas

  • Prevent crashes outside of native environments. This is mostly caused by TurboModule calls in NativeAnimatedHelper. We could either use a *.web.js mock or Platform guards. React Native for Web currently comments out the flushQueue function body to prevent crashes.

Hi @necolas, I was checking out NativeAnimatedHelper and it seems that we already have some Platform guards, do you mind elaborating a bit more on what needs to be updated?

https://github.com/facebook/react-native/blob/c63133202b015adc6cd94e77069586a619aca4a8/Libraries/Animated/NativeAnimatedHelper.js#L30-L33

gabrieldonadel avatar Nov 03 '22 19:11 gabrieldonadel

The entire flushQueue function had to be commented out https://github.com/necolas/react-native-web/pull/2377/commits/ff72921b57951daf9c37939e9b4be108a1552718#diff-51312a304d660152aec0524a0aca4ca3667cbe604afe7f9c38a88bd8631e962d

necolas avatar Nov 03 '22 20:11 necolas

The entire flushQueue function had to be commented out necolas/react-native-web@ff72921#diff-51312a304d660152aec0524a0aca4ca3667cbe604afe7f9c38a88bd8631e962d

@necolas perhaps this was fixed by https://github.com/facebook/react-native/commit/5e863fc42c8a2b27f4a785766eb643de9a243b2d? I did some testing uncommenting that function and updating the code on react-native-web and it seems to work fine

gabrieldonadel avatar Nov 03 '22 20:11 gabrieldonadel

I already did something similar to that patch https://github.com/necolas/react-native-web/commit/ff72921b57951daf9c37939e9b4be108a1552718#diff-6ef00d3c5589ec81f1dbc51139b50801209066f362c5d4d4788cd1bebcd0a13b

I did some testing uncommenting that function and updating the code on react-native-web and it seems to work fine

That function calls turbomodules, which don't exist on web, so I'm curious what testing you did. And what code you updated...

necolas avatar Nov 03 '22 20:11 necolas

I already did something similar to that patch necolas/react-native-web@ff72921#diff-6ef00d3c5589ec81f1dbc51139b50801209066f362c5d4d4788cd1bebcd0a13b

I did some testing uncommenting that function and updating the code on react-native-web and it seems to work fine

That function calls turbomodules, which don't exist on web, so I'm curious what testing you did. And what code you updated...

Actually you commented out the whole flushQueue, https://github.com/facebook/react-native/commit/5e863fc42c8a2b27f4a785766eb643de9a243b2d add some optional chaining to NativeAnimatedModule method calls and most importantly comments out invariant(NativeAnimatedModule, 'Native animated module is not available');

Here is how my `flushQueue` function looks like

 flushQueue: function (): void {
    // invariant(NativeAnimatedModule, 'Native animated module is not available');
    flushQueueTimeout = null;

    // Early returns before calling any APIs
    if (useSingleOpBatching && singleOpQueue.length === 0) {
      return;
    }
    if (!useSingleOpBatching && queue.length === 0) {
      return;
    }

    if (useSingleOpBatching) {
      // Set up event listener for callbacks if it's not set up
      if (
        !globalEventEmitterGetValueListener ||
        !globalEventEmitterAnimationFinishedListener
      ) {
        setupGlobalEventEmitterListeners();
      }
      // Single op batching doesn't use callback functions, instead we
      // use RCTDeviceEventEmitter. This reduces overhead of sending lots of
      // JSI functions across to native code; but also, TM infrastructure currently
      // does not support packing a function into native arrays.
      NativeAnimatedModule?.queueAndExecuteBatchedOperations?.(singleOpQueue);
      singleOpQueue.length = 0;
    } else {
      Platform.OS === 'android' &&
        NativeAnimatedModule?.startOperationBatch?.();
      for (let q = 0, l = queue.length; q < l; q++) {
        queue[q]();
      }
      queue.length = 0;
      Platform.OS === 'android' &&
        NativeAnimatedModule?.finishOperationBatch?.();
    }
  },

And I tested this through the Lists example as it uses an Animated FlatList, am I missing something @necolas ?

https://user-images.githubusercontent.com/11707729/199835759-c7d7fd4e-ac0f-4126-a04b-9c11ad4c2123.mov

gabrieldonadel avatar Nov 03 '22 21:11 gabrieldonadel

OK cool, that looks like it should work then. My only concern is that at any moment an Animated patch could land that breaks web again

necolas avatar Nov 03 '22 21:11 necolas

OK cool, that looks like it should work then. My only concern is that at any moment an Animated patch could land that breaks web again

Got it, but what would be the best way to prevent this from happening? Would it be ok to add a NativeAnimatedHelper.web.js mock file? I don't see any other web.js files in the repo

gabrieldonadel avatar Nov 03 '22 21:11 gabrieldonadel