Skip to content

Commit 9f3a3e1

Browse files
feat: flex gap bindings (#34974)
Summary: This PR adds React native binding for facebook/yoga#1116 ## Changelog [General] [Added] - Flex gap yoga bindings <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> Pull Request resolved: #34974 Test Plan: Run rn tester app and go to view example. You'll find a flex gap example. Example location - `packages/rn-tester/js/examples/View/ViewExample.js` ### Tested on - [x] iOS Fabric - [x] iOS non-fabric - [x] Android Fabric - [x] Android non-fabric To test on non-fabric Android, I just switched these booleans. Let me know if there's anything else I might have missed. <img width="674" alt="Screenshot 2022-10-14 at 3 30 48 AM" src="https://user-images.githubusercontent.com/23293248/195718971-7aee4e7e-dbf0-4452-9d47-7925919c61dc.png"> Reviewed By: mdvacca Differential Revision: D40527474 Pulled By: NickGerleman fbshipit-source-id: 81c2c97c76f58fad3bb40fb378aaf8b6ebd30d63
1 parent a68c418 commit 9f3a3e1

File tree

17 files changed

+186
-0
lines changed

17 files changed

+186
-0
lines changed

Libraries/Components/View/ReactNativeStyleAttributes.js

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
3232
borderRightWidth: true,
3333
borderStartWidth: true,
3434
borderTopWidth: true,
35+
columnGap: true,
3536
borderWidth: true,
3637
bottom: true,
3738
direction: true,
@@ -43,6 +44,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
4344
flexGrow: true,
4445
flexShrink: true,
4546
flexWrap: true,
47+
gap: true,
4648
height: true,
4749
justifyContent: true,
4850
left: true,
@@ -71,6 +73,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
7173
paddingVertical: true,
7274
position: true,
7375
right: true,
76+
rowGap: true,
7477
start: true,
7578
top: true,
7679
width: true,

Libraries/NativeComponent/BaseViewConfig.android.js

+3
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ const validAttributesForNonEventProps = {
196196
maxHeight: true,
197197
flex: true,
198198
flexGrow: true,
199+
rowGap: true,
200+
columnGap: true,
201+
gap: true,
199202
flexShrink: true,
200203
flexBasis: true,
201204
aspectRatio: true,

Libraries/NativeComponent/BaseViewConfig.ios.js

+3
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ const validAttributesForNonEventProps = {
271271

272272
flex: true,
273273
flexGrow: true,
274+
rowGap: true,
275+
columnGap: true,
276+
gap: true,
274277
flexShrink: true,
275278
flexBasis: true,
276279
flexDirection: true,

Libraries/StyleSheet/StyleSheetTypes.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export interface FlexStyle {
5252
| 'row-reverse'
5353
| 'column-reverse'
5454
| undefined;
55+
rowGap?: number | undefined;
56+
gap?: number | undefined;
57+
columnGap?: number | undefined;
5558
flexGrow?: number | undefined;
5659
flexShrink?: number | undefined;
5760
flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse' | undefined;

Libraries/StyleSheet/StyleSheetTypes.js

+13
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,19 @@ export type ____ShadowStyle_InternalCore = $ReadOnly<{
518518
* @platform ios
519519
*/
520520
shadowRadius?: number,
521+
522+
/**
523+
* In React Native, gap works the same way it does in CSS.
524+
* If there are two or more children in a container, they will be separated from each other
525+
* by the value of the gap - but the children will not be separated from the edges of their parent container.
526+
* For horizontal gaps, use columnGap, for vertical gaps, use rowGap, and to apply both at the same time, it's gap.
527+
* When align-content or justify-content are set to space-between or space-around, the separation
528+
* between children may be larger than the gap value.
529+
* See https://developer.mozilla.org/en-US/docs/Web/CSS/gap for more details.
530+
*/
531+
rowGap?: number,
532+
columnGap?: number,
533+
gap?: number,
521534
}>;
522535

523536
export type ____ShadowStyle_Internal = $ReadOnly<{

Libraries/StyleSheet/splitLayoutProps.js

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): {
4949
case 'bottom':
5050
case 'top':
5151
case 'transform':
52+
case 'rowGap':
53+
case 'columnGap':
54+
case 'gap':
5255
// $FlowFixMe[cannot-write]
5356
// $FlowFixMe[incompatible-use]
5457
// $FlowFixMe[prop-missing]

React/Views/RCTShadowView.h

+3
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
152152

153153
@property (nonatomic, assign) float flex;
154154
@property (nonatomic, assign) float flexGrow;
155+
@property (nonatomic, assign) float rowGap;
156+
@property (nonatomic, assign) float columnGap;
157+
@property (nonatomic, assign) float gap;
155158
@property (nonatomic, assign) float flexShrink;
156159
@property (nonatomic, assign) YGValue flexBasis;
157160

React/Views/RCTShadowView.m

+14
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,20 @@ - (YGValue)flexBasis
642642
return YGNodeStyleGetFlexBasis(_yogaNode);
643643
}
644644

645+
#define RCT_GAP_PROPERTY(setProp, getProp, cssProp, type, gap) \
646+
-(void)set##setProp : (type)value \
647+
{ \
648+
YGNodeStyleSet##cssProp(_yogaNode, gap, value); \
649+
} \
650+
-(type)getProp \
651+
{ \
652+
return YGNodeStyleGet##cssProp(_yogaNode, gap); \
653+
}
654+
655+
RCT_GAP_PROPERTY(RowGap, rowGap, Gap, float, YGGutterRow);
656+
RCT_GAP_PROPERTY(ColumnGap, columnGap, Gap, float, YGGutterColumn);
657+
RCT_GAP_PROPERTY(Gap, gap, Gap, float, YGGutterAll);
658+
645659
#define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \
646660
-(void)set##setProp : (type)value \
647661
{ \

React/Views/RCTViewManager.m

+3
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ - (RCTShadowView *)shadowView
426426
RCT_EXPORT_SHADOW_PROPERTY(alignContent, YGAlign)
427427
RCT_EXPORT_SHADOW_PROPERTY(position, YGPositionType)
428428
RCT_EXPORT_SHADOW_PROPERTY(aspectRatio, float)
429+
RCT_EXPORT_SHADOW_PROPERTY(rowGap, float)
430+
RCT_EXPORT_SHADOW_PROPERTY(columnGap, float)
431+
RCT_EXPORT_SHADOW_PROPERTY(gap, float)
429432

430433
RCT_EXPORT_SHADOW_PROPERTY(overflow, YGOverflow)
431434
RCT_EXPORT_SHADOW_PROPERTY(display, YGDisplay)

ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java

+24
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,30 @@ public void setFlexGrow(float flexGrow) {
224224
super.setFlexGrow(flexGrow);
225225
}
226226

227+
@ReactProp(name = ViewProps.ROW_GAP, defaultFloat = YogaConstants.UNDEFINED)
228+
public void setRowGap(float rowGap) {
229+
if (isVirtual()) {
230+
return;
231+
}
232+
super.setRowGap(PixelUtil.toPixelFromDIP(rowGap));
233+
}
234+
235+
@ReactProp(name = ViewProps.COLUMN_GAP, defaultFloat = YogaConstants.UNDEFINED)
236+
public void setColumnGap(float columnGap) {
237+
if (isVirtual()) {
238+
return;
239+
}
240+
super.setColumnGap(PixelUtil.toPixelFromDIP(columnGap));
241+
}
242+
243+
@ReactProp(name = ViewProps.GAP, defaultFloat = YogaConstants.UNDEFINED)
244+
public void setGap(float gap) {
245+
if (isVirtual()) {
246+
return;
247+
}
248+
super.setGap(PixelUtil.toPixelFromDIP(gap));
249+
}
250+
227251
@ReactProp(name = ViewProps.FLEX_SHRINK, defaultFloat = 0f)
228252
public void setFlexShrink(float flexShrink) {
229253
if (isVirtual()) {

ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java

+6
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,12 @@ public interface ReactShadowNode<T extends ReactShadowNode> {
308308

309309
void setFlexGrow(float flexGrow);
310310

311+
void setRowGap(float rowGap);
312+
313+
void setColumnGap(float columnGap);
314+
315+
void setGap(float gap);
316+
311317
void setFlexShrink(float flexShrink);
312318

313319
void setFlexBasis(float flexBasis);

ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java

+16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.facebook.yoga.YogaDisplay;
1919
import com.facebook.yoga.YogaEdge;
2020
import com.facebook.yoga.YogaFlexDirection;
21+
import com.facebook.yoga.YogaGutter;
2122
import com.facebook.yoga.YogaJustify;
2223
import com.facebook.yoga.YogaMeasureFunction;
2324
import com.facebook.yoga.YogaNode;
@@ -794,6 +795,21 @@ public void setFlexGrow(float flexGrow) {
794795
mYogaNode.setFlexGrow(flexGrow);
795796
}
796797

798+
@Override
799+
public void setRowGap(float rowGap) {
800+
mYogaNode.setGap(YogaGutter.ROW, rowGap);
801+
}
802+
803+
@Override
804+
public void setColumnGap(float columnGap) {
805+
mYogaNode.setGap(YogaGutter.COLUMN, columnGap);
806+
}
807+
808+
@Override
809+
public void setGap(float gap) {
810+
mYogaNode.setGap(YogaGutter.ALL, gap);
811+
}
812+
797813
@Override
798814
public void setFlexShrink(float flexShrink) {
799815
mYogaNode.setFlexShrink(flexShrink);

ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public class ViewProps {
3232
public static final String FLEX_BASIS = "flexBasis";
3333
public static final String FLEX_DIRECTION = "flexDirection";
3434
public static final String FLEX_WRAP = "flexWrap";
35+
public static final String ROW_GAP = "rowGap";
36+
public static final String COLUMN_GAP = "columnGap";
37+
public static final String GAP = "gap";
3538
public static final String HEIGHT = "height";
3639
public static final String JUSTIFY_CONTENT = "justifyContent";
3740
public static final String LEFT = "left";
@@ -203,6 +206,9 @@ public class ViewProps {
203206
FLEX_BASIS,
204207
FLEX_DIRECTION,
205208
FLEX_GROW,
209+
ROW_GAP,
210+
COLUMN_GAP,
211+
GAP,
206212
FLEX_SHRINK,
207213
FLEX_WRAP,
208214
JUSTIFY_CONTENT,

ReactCommon/react/renderer/components/view/YogaStylableProps.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ static inline T const getFieldValue(
6565
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGDimensionWidth, widthStr); \
6666
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGDimensionHeight, heightStr);
6767

68+
#define REBUILD_FIELD_YG_GUTTER(field, rowGapStr, columnGapStr, gapStr) \
69+
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGGutterRow, rowGapStr); \
70+
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGGutterColumn, columnGapStr); \
71+
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGGutterAll, gapStr);
72+
6873
#define REBUILD_FIELD_YG_EDGES(field, prefix, suffix) \
6974
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
7075
field, YGEdgeLeft, prefix "Left" suffix); \
@@ -114,6 +119,7 @@ void YogaStylableProps::setProp(
114119
REBUILD_FIELD_SWITCH_CASE_YSP(flexShrink);
115120
REBUILD_FIELD_SWITCH_CASE_YSP(flexBasis);
116121
REBUILD_FIELD_SWITCH_CASE2(positionType, "position");
122+
REBUILD_FIELD_YG_GUTTER(gap, "rowGap", "columnGap", "gap");
117123
REBUILD_FIELD_SWITCH_CASE_YSP(aspectRatio);
118124
REBUILD_FIELD_YG_DIMENSION(dimensions, "width", "height");
119125
REBUILD_FIELD_YG_DIMENSION(minDimensions, "minWidth", "minHeight");
@@ -163,6 +169,18 @@ SharedDebugStringConvertibleList YogaStylableProps::getDebugProps() const {
163169
"flex", yogaStyle.flex(), defaultYogaStyle.flex()),
164170
debugStringConvertibleItem(
165171
"flexGrow", yogaStyle.flexGrow(), defaultYogaStyle.flexGrow()),
172+
debugStringConvertibleItem(
173+
"rowGap",
174+
yogaStyle.gap()[YGGutterRow],
175+
defaultYogaStyle.gap()[YGGutterRow]),
176+
debugStringConvertibleItem(
177+
"columnGap",
178+
yogaStyle.gap()[YGGutterColumn],
179+
defaultYogaStyle.gap()[YGGutterColumn]),
180+
debugStringConvertibleItem(
181+
"gap",
182+
yogaStyle.gap()[YGGutterAll],
183+
defaultYogaStyle.gap()[YGGutterAll]),
166184
debugStringConvertibleItem(
167185
"flexShrink", yogaStyle.flexShrink(), defaultYogaStyle.flexShrink()),
168186
debugStringConvertibleItem(

ReactCommon/react/renderer/components/view/propsConversions.h

+22
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,28 @@ static inline YGStyle convertRawProp(
266266
"",
267267
sourceValue.padding(),
268268
yogaStyle.padding());
269+
270+
yogaStyle.gap()[YGGutterRow] = convertRawProp(
271+
context,
272+
rawProps,
273+
"rowGap",
274+
sourceValue.gap()[YGGutterRow],
275+
yogaStyle.gap()[YGGutterRow]);
276+
277+
yogaStyle.gap()[YGGutterColumn] = convertRawProp(
278+
context,
279+
rawProps,
280+
"columnGap",
281+
sourceValue.gap()[YGGutterColumn],
282+
yogaStyle.gap()[YGGutterColumn]);
283+
284+
yogaStyle.gap()[YGGutterAll] = convertRawProp(
285+
context,
286+
rawProps,
287+
"gap",
288+
sourceValue.gap()[YGGutterAll],
289+
yogaStyle.gap()[YGGutterAll]);
290+
269291
yogaStyle.border() = convertRawProp(
270292
context,
271293
rawProps,

ReactCommon/react/test_utils/shadowTreeGeneration.h

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#pragma once
99

1010
#include <glog/logging.h>
11+
#include <gtest/gtest.h>
1112
#include <algorithm>
1213
#include <iostream>
1314
#include <memory>
@@ -233,6 +234,11 @@ static inline ShadowNode::Unshared messWithYogaStyles(
233234
"maxWidth", "maxHeight", "minWidth", "minHeight",
234235
};
235236

237+
// It is not safe to add new Yoga properties to this list. Unit tests
238+
// validate specific seeds, and what they test may change and cause unrelated
239+
// failures if the size of properties also changes.
240+
EXPECT_EQ(properties.size(), 20);
241+
236242
for (auto const &property : properties) {
237243
if (entropy.random<bool>(0.1)) {
238244
dynamic[property] = entropy.random<int>(0, 1024);

packages/rn-tester/js/examples/View/ViewExample.js

+40
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,40 @@ class DisplayNoneStyle extends React.Component<
299299
this.setState({index: this.state.index + 1});
300300
};
301301
}
302+
303+
class FlexGapExample extends React.Component<$ReadOnly<{||}>> {
304+
render(): React.Node {
305+
return (
306+
<View
307+
style={{
308+
flexDirection: 'row',
309+
flexWrap: 'wrap',
310+
borderWidth: 1,
311+
rowGap: 20,
312+
columnGap: 30,
313+
}}>
314+
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
315+
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
316+
<View
317+
style={{
318+
backgroundColor: 'pink',
319+
height: 30,
320+
flexBasis: 30,
321+
}}
322+
/>
323+
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
324+
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
325+
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
326+
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
327+
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
328+
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
329+
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
330+
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
331+
</View>
332+
);
333+
}
334+
}
335+
302336
exports.title = 'View';
303337
exports.documentationURL = 'https://reactnative.dev/docs/view';
304338
exports.category = 'Basic';
@@ -612,4 +646,10 @@ exports.examples = [
612646
);
613647
},
614648
},
649+
{
650+
title: 'FlexGap',
651+
render(): React.Node {
652+
return <FlexGapExample />;
653+
},
654+
},
615655
];

0 commit comments

Comments
 (0)