@applicaster/zapp-react-native-ui-components 13.0.6-alpha.4390983410 → 14.0.0-alpha.1015356256

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -110,22 +110,24 @@ export function AudioPlayerTVLayout({
110
110
  ...style,
111
111
  },
112
112
  samsung_tv: {
113
+ position: "absolute",
113
114
  margin: "auto",
114
115
  display: "flex",
115
116
  flexWrap: "wrap",
117
+ height: "100vh",
116
118
  width: "100vw",
117
- flex: 1,
118
119
  alignItems: "center",
119
120
  justifyContent: "center",
120
121
  flexDirection: directionStyles(isRTL).flexDirection,
121
122
  backgroundColor: backgroundColorStyle,
122
123
  },
123
124
  lg_tv: {
125
+ position: "absolute",
124
126
  margin: "auto",
125
127
  display: "flex",
126
128
  flexWrap: "wrap",
129
+ height: "100vh",
127
130
  width: "100vw",
128
- flex: 1,
129
131
  alignItems: "center",
130
132
  justifyContent: "center",
131
133
  flexDirection: directionStyles(isRTL).flexDirection,
@@ -70,6 +70,8 @@ type State = {
70
70
  };
71
71
 
72
72
  export class CellComponent extends React.Component<Props, State> {
73
+ accessibilityManager: AccessibilityManager;
74
+
73
75
  constructor(props) {
74
76
  super(props);
75
77
  this.onPress = this.onPress.bind(this);
@@ -83,6 +85,8 @@ export class CellComponent extends React.Component<Props, State> {
83
85
  this.state = {
84
86
  hasFocusableInside: props.CellRenderer.hasFocusableInside?.(props.item),
85
87
  };
88
+
89
+ this.accessibilityManager = AccessibilityManager.getInstance();
86
90
  }
87
91
 
88
92
  setScreenLayout(componentAnchorPointY, screenLayout) {
@@ -257,20 +261,21 @@ export class CellComponent extends React.Component<Props, State> {
257
261
  style={styles.baseCell}
258
262
  isFocusable={isFocusable}
259
263
  skipFocusManagerRegistration={skipFocusManagerRegistration}
264
+ {...this.accessibilityManager.getButtonAccessibilityProps(
265
+ item?.extensions?.accessibility?.label || item?.title
266
+ )}
260
267
  >
261
268
  {(focused, event) => {
262
269
  const isFocused = this.isCellFocused(focused);
263
270
 
264
271
  if (isFocused) {
265
- const accessibilityManager = AccessibilityManager.getInstance();
266
-
267
272
  const accessibilityTitle =
268
273
  item?.extensions?.accessibility?.label || item?.title || "";
269
274
 
270
275
  const accessibilityHint =
271
276
  item?.extensions?.accessibility?.hint || "";
272
277
 
273
- accessibilityManager.readText({
278
+ this.accessibilityManager.readText({
274
279
  text: `${accessibilityTitle} ${accessibilityHint}`,
275
280
  });
276
281
  }
@@ -3,11 +3,15 @@ import * as R from "ramda";
3
3
  import { connectToStore } from "@applicaster/zapp-react-native-redux";
4
4
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
5
 
6
- import { HorizontalScrollContext, RiverOffsetContext } from "../../Contexts";
6
+ import {
7
+ HorizontalScrollContext,
8
+ RiverOffsetContext,
9
+ ScreenScrollingContext,
10
+ } from "../../Contexts";
11
+
7
12
  import { CellComponent } from "./Cell";
8
13
  import { TvOSCellComponent } from "./TvOSCellComponent";
9
14
  import { withConsumer } from "../../Contexts/HeaderOffsetContext";
10
- import { ScreenScrollingContext } from "../../Contexts/ScreenScrollingContext";
11
15
 
12
16
  import { ScreenLayoutContextConsumer } from "../../Contexts/ScreenLayoutContext";
13
17
  import { createContext } from "@applicaster/zapp-react-native-utils/reactUtils/createContext";
@@ -5,6 +5,8 @@ import { BaseFocusable } from "../BaseFocusable";
5
5
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
6
6
  import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
7
7
  import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
8
+ import { StyleSheet, ViewStyle } from "react-native";
9
+ import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
8
10
 
9
11
  type Props = {
10
12
  initialFocus?: boolean;
@@ -21,13 +23,19 @@ type Props = {
21
23
  handleFocus?: ({ mouse }: { mouse: boolean }) => void;
22
24
  children: (boolean, string) => React.ComponentType<any>;
23
25
  selected?: boolean;
24
- style?: React.CSSProperties;
26
+ style?: ViewStyle[] | ViewStyle;
27
+ "aria-label"?: string;
28
+ "aria-description"?: string;
29
+ "aria-role"?: string;
30
+ role?: string;
31
+ tabindex?: number;
25
32
  };
26
33
 
27
34
  class Focusable extends BaseFocusable<Props> {
28
35
  isGroup: boolean;
29
36
  mouse: boolean;
30
37
  longPressTimeout = null;
38
+ accessibilityManager: AccessibilityManager;
31
39
 
32
40
  constructor(props) {
33
41
  super(props);
@@ -42,6 +50,8 @@ class Focusable extends BaseFocusable<Props> {
42
50
  this.resetLongPressTimeout = this.resetLongPressTimeout.bind(this);
43
51
  this.longPress = this.longPress.bind(this);
44
52
  this.press = this.press.bind(this);
53
+
54
+ this.accessibilityManager = AccessibilityManager.getInstance();
45
55
  }
46
56
 
47
57
  /**
@@ -128,6 +138,9 @@ class Focusable extends BaseFocusable<Props> {
128
138
  const id = this.getId();
129
139
  const focusableId = `focusable-${id}`;
130
140
 
141
+ const accessibilityProps =
142
+ this.accessibilityManager.getWebAccessibilityProps(this.props);
143
+
131
144
  return (
132
145
  <div
133
146
  id={focusableId}
@@ -139,7 +152,8 @@ class Focusable extends BaseFocusable<Props> {
139
152
  onMouseUp={this.pressOut}
140
153
  data-testid={focusableId}
141
154
  focused-teststate={focused ? "focused" : "default"}
142
- style={style}
155
+ style={StyleSheet.flatten(style) as any as React.CSSProperties}
156
+ {...accessibilityProps}
143
157
  >
144
158
  {children(focused, { mouse: this.mouse })}
145
159
  </div>
@@ -12,4 +12,4 @@ function FocusableiOSComponent({ children }: Props) {
12
12
  return children;
13
13
  }
14
14
 
15
- export const FocusableiOS = React.forwardRef(FocusableiOSComponent);
15
+ export const FocusableiOS = FocusableiOSComponent;
@@ -38,6 +38,9 @@ describe("Focusable", () => {
38
38
  });
39
39
 
40
40
  it("updates disableFocus state when disableFocus prop changes", () => {
41
+ const unregister = jest.fn();
42
+ mockFocusManager.registerFocusable.mockReturnValue(unregister);
43
+
41
44
  const { rerender } = render(
42
45
  <Focusable id="test-id" disableFocus={false}>
43
46
  <Touchable testID="touchable" />
@@ -43,11 +43,13 @@ export const FocusableContext = React.createContext<
43
43
  // eslint-disable-next-line
44
44
  setIsFocusable: (enableFocus: boolean) => void;
45
45
  ref: FocusManager.FocusableRef;
46
+ parentFocusableId: Option<string>;
46
47
  } & ParentFocus
47
48
  >({
48
49
  focused: false,
49
50
  setIsFocusable: () => {},
50
51
  ref: { current: null },
52
+ parentFocusableId: undefined,
51
53
  });
52
54
 
53
55
  export const useFocusable = () => React.useContext(FocusableContext);
@@ -74,7 +76,7 @@ function FocusableComponent(props: Props, forwardedRef) {
74
76
 
75
77
  const isRTL = useIsRTL();
76
78
  const focusManager = useFocusManager();
77
- const { ref: parentFocusable } = useFocusable();
79
+ const { ref: parentFocusableRef, parentFocusableId } = useFocusable();
78
80
  const touchableRef = React.useRef(null);
79
81
 
80
82
  const [focused, setFocused] = React.useState(() =>
@@ -98,13 +100,14 @@ function FocusableComponent(props: Props, forwardedRef) {
98
100
  }
99
101
  }, [disableFocus]);
100
102
 
101
- React.useEffect(() => {
103
+ React.useLayoutEffect(() => {
102
104
  if (id) {
103
- const unregister = focusManager.registerFocusable(
105
+ const unregister = focusManager.registerFocusable({
104
106
  touchableRef,
105
- parentFocusable,
106
- isFocusableCell
107
- );
107
+ parentFocusableRef,
108
+ isFocusableCell,
109
+ parentFocusableId,
110
+ });
108
111
 
109
112
  onRegister();
110
113
 
@@ -112,7 +115,7 @@ function FocusableComponent(props: Props, forwardedRef) {
112
115
  unregister();
113
116
  };
114
117
  }
115
- }, [id, onRegister, isFocusableCell]);
118
+ }, [id, onRegister, isFocusableCell, parentFocusableId]);
116
119
 
117
120
  if (R.isNil(id)) {
118
121
  // eslint-disable-next-line no-console
@@ -164,8 +167,9 @@ function FocusableComponent(props: Props, forwardedRef) {
164
167
  ...parentFocus,
165
168
  ref: touchableRef,
166
169
  setIsFocusable,
170
+ parentFocusableId: id,
167
171
  };
168
- }, [parentFocus, focused]);
172
+ }, [parentFocus, focused, id]);
169
173
 
170
174
  return (
171
175
  <Touchable
@@ -30,7 +30,6 @@ export const GeneralContentScreen = ({
30
30
  isScreenWrappedInContainer,
31
31
  componentsMapExtraProps = {},
32
32
  focused,
33
- extraOffset,
34
33
  parentFocus,
35
34
  containerHeight,
36
35
  preferredFocus = false,
@@ -122,7 +121,6 @@ export const GeneralContentScreen = ({
122
121
  isScreenWrappedInContainer={isScreenWrappedInContainer}
123
122
  parentFocus={parentFocus}
124
123
  focused={focused}
125
- extraOffset={extraOffset}
126
124
  containerHeight={containerHeight}
127
125
  preferredFocus={preferredFocus}
128
126
  {...componentsMapExtraProps}
@@ -2,7 +2,6 @@ import React, { useMemo } from "react";
2
2
  import { ImageStyle } from "react-native";
3
3
  import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
4
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
- import * as R from "ramda";
6
5
  import { getXray } from "@applicaster/zapp-react-native-utils/logger";
7
6
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
8
7
  import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
@@ -67,32 +66,10 @@ export function FocusableView({ style, children, item, ...otherProps }: Props) {
67
66
  const handleFocus = (focusable) => {
68
67
  const focusedButtonId = getFocusedButtonId(focusable);
69
68
 
70
- wrapperRef?.current?.measure((x, y, width, height, pageX, pageY) => {
71
- const top = pageY;
72
- const bottom = top + height;
73
- const left = pageX;
74
- const right = left + width;
75
-
76
- const boundingRect = {
77
- x,
78
- y,
79
- pageX,
80
- pageY,
81
- width,
82
- height,
83
- top,
84
- bottom,
85
- left,
86
- right,
87
- };
88
-
89
- otherProps?.onToggleFocus?.({
90
- focusable: {
91
- getRect: R.always(boundingRect),
92
- },
93
- focusedButtonId,
94
- mouse: focusable.mouse,
95
- });
69
+ otherProps?.onToggleFocus?.({
70
+ focusable: wrapperRef.current,
71
+ focusedButtonId,
72
+ mouse: focusable.mouse,
96
73
  });
97
74
 
98
75
  if (ttsLabel) {
@@ -52,6 +52,7 @@ const _Text = ({
52
52
  withScaledLineHeight(withFocusedStyles({ style, otherProps })),
53
53
  { height },
54
54
  ]}
55
+ allowFontScaling={false}
55
56
  {...withoutLabel(otherProps)}
56
57
  >
57
58
  {dateTransformEnabled
@@ -105,11 +105,6 @@ const isTvOS = isTvOSPlatform();
105
105
  // height for container with additional content below player
106
106
  const INLINE_CONTAINER_CONTENT_HEIGHT = 400;
107
107
 
108
- // Default Y offset for anchor points on non-Android TV platforms
109
- const DEFAULT_OFFSET_Y = -600;
110
-
111
- const EXTRA_ANCHOR_POINT_Y_OFFSET = isAndroidTV ? 0 : DEFAULT_OFFSET_Y;
112
-
113
108
  const withBorderHack = () => {
114
109
  if (isAndroidTV) {
115
110
  /* @HACK: see GH#7269 */
@@ -730,13 +725,11 @@ const PlayerContainerComponent = (props: Props) => {
730
725
  key={item.id}
731
726
  groupId={FocusableGroupMainContainerId}
732
727
  cellTapAction={onCellTap}
733
- extraAnchorPointYOffset={
734
- EXTRA_ANCHOR_POINT_Y_OFFSET
735
- }
728
+ extraAnchorPointYOffset={0}
736
729
  isScreenWrappedInContainer={true}
737
730
  containerHeight={styles.inlineRiver.height}
738
731
  componentsMapExtraProps={{
739
- isNestedComponentsMap: R.T,
732
+ isNestedComponentsMap: true,
740
733
  }}
741
734
  />
742
735
  )}
@@ -26,7 +26,6 @@ type Props = {
26
26
  componentsMapExtraProps?: any;
27
27
  isInsideContainer?: boolean;
28
28
  extraAnchorPointYOffset: number;
29
- extraOffset: number;
30
29
  river?: ZappRiver | ZappEntry;
31
30
  };
32
31
 
@@ -39,7 +38,6 @@ export const River = (props: Props) => {
39
38
  componentsMapExtraProps,
40
39
  isInsideContainer,
41
40
  extraAnchorPointYOffset,
42
- extraOffset,
43
41
  } = props;
44
42
 
45
43
  const { title: screenTitle, summary: screenSummary } = useNavbarState();
@@ -120,7 +118,6 @@ export const River = (props: Props) => {
120
118
  <GeneralContentScreen
121
119
  feed={feedData}
122
120
  screenId={screenId}
123
- extraOffset={extraOffset}
124
121
  isScreenWrappedInContainer={isInsideContainer}
125
122
  extraAnchorPointYOffset={extraAnchorPointYOffset}
126
123
  componentsMapExtraProps={componentsMapExtraProps}
@@ -2,6 +2,19 @@
2
2
 
3
3
  exports[`<TextInputTv /> renders 1`] = `
4
4
  <input
5
+ accessibilityProps={
6
+ {
7
+ "accessibilityHint": "Enter text into Search",
8
+ "accessibilityLabel": "Search",
9
+ "accessibilityRole": "text",
10
+ "accessible": true,
11
+ "aria-description": "Enter text into Search",
12
+ "aria-label": "Search",
13
+ "aria-role": "text",
14
+ "role": "text",
15
+ "tabindex": 0,
16
+ }
17
+ }
5
18
  testID="TextInput-tv"
6
19
  />
7
20
  `;
@@ -4,6 +4,7 @@ import { Appearance, Platform, StyleSheet, TextInput } from "react-native";
4
4
  import { isFunction } from "@applicaster/zapp-react-native-utils/functionUtils";
5
5
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
6
6
  import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
7
+ import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
7
8
 
8
9
  type Props = Partial<{
9
10
  style: any;
@@ -42,6 +43,8 @@ function TextInputTV(props: Props, ref) {
42
43
  const [colorScheme, setColorScheme] = useState(getInitialColorScheme());
43
44
  const isRTL = useIsRTL();
44
45
 
46
+ const accessibilityManager = useAccessibilityManager({});
47
+
45
48
  const onColorChange = useCallback(
46
49
  ({ colorScheme: color }) => {
47
50
  if (color !== colorScheme) {
@@ -153,6 +156,13 @@ function TextInputTV(props: Props, ref) {
153
156
  ])
154
157
  )(props);
155
158
 
159
+ const getAccessibilityProps = () => {
160
+ return {
161
+ accessibilityProps:
162
+ accessibilityManager.getInputAccessibilityProps("Search"),
163
+ };
164
+ };
165
+
156
166
  const inputProps = {
157
167
  ...getProps(),
158
168
  ...getStyle(),
@@ -161,6 +171,7 @@ function TextInputTV(props: Props, ref) {
161
171
  ...getSecureTextEntry(),
162
172
  ...getOnEndEditing(),
163
173
  ...getOnPress(),
174
+ ...getAccessibilityProps(),
164
175
  };
165
176
 
166
177
  if (
@@ -1,17 +1,17 @@
1
1
  import React from "react";
2
- import { View, ViewProps, ViewStyle } from "react-native";
2
+ import { View, ViewStyle } from "react-native";
3
3
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
4
4
  import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks";
5
5
  import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
6
+ import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
6
7
 
7
8
  interface IProps {
8
9
  targetScreenId?: string;
9
10
  children?: React.ReactNode;
10
11
  style?: ViewStyle;
11
- extraOffset?: number;
12
+ extraVerticalOffset: Option<number>;
12
13
  }
13
14
 
14
- type CombinedProps = IProps & ViewProps;
15
15
  /**
16
16
  * The MarginTop is essentially a feature used for managing the visibility of components on your screen.
17
17
  * A more accurate term for this might be something like a 'component visibility threshold' or 'cut-off point'.
@@ -47,7 +47,7 @@ export const useMarginTop = (targetScreenId: string): number => {
47
47
 
48
48
  // Empty string means that value is blank in the CMS. Fallback to theme
49
49
  if (String(screenData?.styles?.screen_margin_top) === "") {
50
- return Number(theme.screen_margin_top);
50
+ return toNumberWithDefault(0, theme.screen_margin_top);
51
51
  }
52
52
 
53
53
  /**
@@ -58,28 +58,29 @@ export const useMarginTop = (targetScreenId: string): number => {
58
58
  */
59
59
  if (screenData?.styles?.screen_margin_top === undefined) {
60
60
  if (isGeneralContentScreen || supportsUiComponents) {
61
- return Number(theme.screen_margin_top);
61
+ return toNumberWithDefault(0, theme.screen_margin_top);
62
62
  }
63
63
 
64
64
  return 0;
65
65
  }
66
66
 
67
- return Number(screenData?.styles?.screen_margin_top);
67
+ return toNumberWithDefault(0, screenData?.styles?.screen_margin_top);
68
68
  };
69
69
 
70
- export const TopMarginApplicator: React.FC<CombinedProps> = (
71
- props: CombinedProps
72
- ) => {
73
- const extraOffset = props?.extraOffset ?? 0;
70
+ export const TopMarginApplicator: React.FC<IProps> = ({
71
+ targetScreenId,
72
+ style,
73
+ children,
74
+ extraVerticalOffset,
75
+ }: IProps) => {
76
+ const extraOffset = toNumberWithDefault(0, extraVerticalOffset);
74
77
 
75
78
  // HACK: Remove extraOffset when focusIssue with absolute elements is fixed on tvos
76
- const marginTop = useMarginTop(props.targetScreenId) + extraOffset;
77
- const style = { ...((props.style as {}) || {}), marginTop };
79
+ const marginTop = useMarginTop(targetScreenId);
78
80
 
79
- // Then, spread the rest of the props on your returned JSX.
80
81
  return (
81
- <View {...props} style={style}>
82
- {props.children}
82
+ <View style={[style, { marginTop: marginTop + extraOffset }]}>
83
+ {children}
83
84
  </View>
84
85
  );
85
86
  };
@@ -4,8 +4,8 @@ which helps in detecting whether a given component is visible within the viewpor
4
4
  It is useful for implementing lazy loading or triggering specific actions when a component comes into view.
5
5
  */
6
6
 
7
- import React, { useEffect, useState, useRef, ReactNode, FC } from "react";
8
- import { View, Dimensions } from "react-native";
7
+ import React, { FC, ReactNode, useEffect, useRef, useState } from "react";
8
+ import { Dimensions, View } from "react-native";
9
9
  import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
10
10
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
11
11
 
@@ -78,7 +78,7 @@ const VisibilitySensorComponent: FC<Props> = ({
78
78
  };
79
79
 
80
80
  const stopWatching = () => {
81
- interval = clearInterval(interval);
81
+ clearInterval(interval);
82
82
  };
83
83
 
84
84
  const isInViewPort = () => {
@@ -1,10 +1,10 @@
1
1
  import {
2
- getColor,
3
2
  ACTIVE_COLOR,
4
3
  BACKGROUND_COLOR,
5
- MAIN_TEXT_COLOR,
6
4
  FOCUSED_TEXT_COLOR,
7
- } from "../../../colors/index";
5
+ getColor,
6
+ MAIN_TEXT_COLOR,
7
+ } from "../../../colors";
8
8
 
9
9
  const Image = "Image";
10
10
  const View = "View";
package/index.d.ts CHANGED
@@ -24,7 +24,6 @@ type GeneralContentScreenProps = {
24
24
  } & Record<string, any>;
25
25
  focused?: boolean;
26
26
  parentFocus?: ParentFocus;
27
- extraOffset?: number;
28
27
  containerHeight?: number;
29
28
  };
30
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "13.0.6-alpha.4390983410",
3
+ "version": "14.0.0-alpha.1015356256",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,16 +27,12 @@
27
27
  "url": "https://github.com/applicaster/quickbrick/issues"
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
- "devDependencies": {
31
- "redux-mock-store": "^1.5.3"
32
- },
33
30
  "dependencies": {
34
- "@applicaster/applicaster-types": "13.0.6-alpha.4390983410",
35
- "@applicaster/zapp-react-native-bridge": "13.0.6-alpha.4390983410",
36
- "@applicaster/zapp-react-native-redux": "13.0.6-alpha.4390983410",
37
- "@applicaster/zapp-react-native-utils": "13.0.6-alpha.4390983410",
31
+ "@applicaster/applicaster-types": "14.0.0-alpha.1015356256",
32
+ "@applicaster/zapp-react-native-bridge": "14.0.0-alpha.1015356256",
33
+ "@applicaster/zapp-react-native-redux": "14.0.0-alpha.1015356256",
34
+ "@applicaster/zapp-react-native-utils": "14.0.0-alpha.1015356256",
38
35
  "promise": "^8.3.0",
39
- "react-router-native": "^5.1.2",
40
36
  "url": "^0.11.0",
41
37
  "uuid": "^3.3.2"
42
38
  },
@@ -1,11 +0,0 @@
1
- import React from "react";
2
-
3
- const reactRouter = jest.genMockFromModule("react-router-native");
4
-
5
- function withRouter(Component) {
6
- return (props) => <Component {...props} />; // eslint-disable-line react/display-name
7
- }
8
-
9
- reactRouter.withRouter = withRouter;
10
-
11
- module.exports = reactRouter;