@jobber/components-native 0.113.2 → 0.113.4-implement--94d34f8.2

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.
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import type { AtlantisThemeContextProviderProps, AtlantisThemeContextValue } from "./types";
3
- export declare function AtlantisThemeContextProvider({ children, dangerouslyOverrideTheme, }: AtlantisThemeContextProviderProps): React.JSX.Element;
3
+ export declare function AtlantisThemeContextProvider({ children, theme, onThemeChange, dangerouslyOverrideTheme, }: AtlantisThemeContextProviderProps): React.JSX.Element;
4
4
  export declare function useAtlantisTheme(): AtlantisThemeContextValue;
@@ -1,3 +1,3 @@
1
1
  export { AtlantisThemeContextProvider, useAtlantisTheme, } from "./AtlantisThemeContext";
2
- export type { Theme, AtlantisThemeContextProviderProps, AtlantisThemeContextValue, } from "./types";
2
+ export type { EffectiveTheme, Theme, AtlantisThemeContextProviderProps, AtlantisThemeContextValue, } from "./types";
3
3
  export { buildThemedStyles } from "./buildThemedStyles";
@@ -1,9 +1,13 @@
1
1
  import type { iosTokens } from "@jobber/design";
2
2
  export interface AtlantisThemeContextValue {
3
3
  /**
4
- * The active theme.
4
+ * The selected theme.
5
5
  */
6
6
  readonly theme: Theme;
7
+ /**
8
+ * The resolved theme used to select tokens.
9
+ */
10
+ readonly effectiveTheme: EffectiveTheme;
7
11
  /**
8
12
  * The design tokens for the current theme.
9
13
  */
@@ -18,10 +22,19 @@ export interface AtlantisThemeContextProviderProps {
18
22
  * The children to render.
19
23
  */
20
24
  readonly children: React.ReactNode;
25
+ /**
26
+ * Control the selected theme for this provider.
27
+ */
28
+ readonly theme?: Theme;
29
+ /**
30
+ * Called when children request a selected theme change through setTheme.
31
+ */
32
+ readonly onThemeChange?: (theme: Theme) => void;
21
33
  /**
22
34
  * Force the theme for this provider to always be the same as the provided theme. Useful for sections that should remain the same theme regardless of the rest of the application's theme.
23
35
  * This is dangerous because the children in this provider will not be able to change the theme.
24
36
  */
25
- readonly dangerouslyOverrideTheme?: Theme;
37
+ readonly dangerouslyOverrideTheme?: EffectiveTheme;
26
38
  }
27
- export type Theme = "light" | "dark";
39
+ export type Theme = "system" | EffectiveTheme;
40
+ export type EffectiveTheme = "light" | "dark";
@@ -27,7 +27,6 @@ Pod::Spec.new do |s|
27
27
  "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
28
28
  }
29
29
  s.dependency "React-Codegen"
30
- s.dependency "RCT-Folly"
31
30
  s.dependency "RCTRequired"
32
31
  s.dependency "RCTTypeSafety"
33
32
  s.dependency "ReactCommon/turbomodule/core"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.113.2",
3
+ "version": "0.113.4-implement--94d34f8.2+94d34f81",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -124,5 +124,5 @@
124
124
  "react-native-screens": ">=4.18.0",
125
125
  "react-native-svg": ">=12.0.0"
126
126
  },
127
- "gitHead": "106e281adc9a3b0e87e125e1a9f1c945ce2de3cd"
127
+ "gitHead": "94d34f81c5579bf7aec109cd129a94ea91aadf56"
128
128
  }
@@ -2,21 +2,43 @@ import React from "react";
2
2
  import { act, renderHook } from "@testing-library/react-native";
3
3
  import { darkTokens, iosTokens } from "@jobber/design";
4
4
  import merge from "lodash/merge";
5
+ import { type ColorSchemeName, useColorScheme } from "react-native";
5
6
  import {
6
7
  AtlantisThemeContextProvider,
7
8
  useAtlantisTheme,
8
9
  } from "./AtlantisThemeContext";
9
- import type { AtlantisThemeContextProviderProps, Theme } from "./types";
10
+ import type {
11
+ AtlantisThemeContextProviderProps,
12
+ EffectiveTheme,
13
+ } from "./types";
14
+
15
+ jest.mock("react-native", () => {
16
+ const actual = jest.requireActual("react-native");
17
+
18
+ Object.defineProperty(actual, "useColorScheme", {
19
+ configurable: true,
20
+ value: jest.fn(),
21
+ });
22
+
23
+ return actual;
24
+ });
10
25
 
11
26
  const expectedDarkTokens = merge({}, iosTokens, darkTokens);
12
27
  const expectedLightTokens = iosTokens;
28
+ const mockUseColorScheme = useColorScheme as jest.MockedFunction<
29
+ typeof useColorScheme
30
+ >;
13
31
 
14
32
  function Wrapper({
15
33
  children,
34
+ theme,
35
+ onThemeChange,
16
36
  dangerouslyOverrideTheme,
17
37
  }: AtlantisThemeContextProviderProps) {
18
38
  return (
19
39
  <AtlantisThemeContextProvider
40
+ theme={theme}
41
+ onThemeChange={onThemeChange}
20
42
  dangerouslyOverrideTheme={dangerouslyOverrideTheme}
21
43
  >
22
44
  {children}
@@ -40,6 +62,10 @@ function WrapperWithOverride({
40
62
  }
41
63
 
42
64
  describe("ThemeContext", () => {
65
+ beforeEach(() => {
66
+ mockUseColorScheme.mockReturnValue("light");
67
+ });
68
+
43
69
  it("defaults to the light theme", () => {
44
70
  const { result } = renderHook(useAtlantisTheme, {
45
71
  wrapper: (props: AtlantisThemeContextProviderProps) => (
@@ -48,6 +74,7 @@ describe("ThemeContext", () => {
48
74
  });
49
75
 
50
76
  expect(result.current.theme).toBe("light");
77
+ expect(result.current.effectiveTheme).toBe("light");
51
78
  expect(result.current.tokens).toEqual(expectedLightTokens);
52
79
  });
53
80
 
@@ -60,10 +87,81 @@ describe("ThemeContext", () => {
60
87
 
61
88
  await act(async () => result.current.setTheme("dark"));
62
89
  expect(result.current.theme).toBe("dark");
90
+ expect(result.current.effectiveTheme).toBe("dark");
63
91
  expect(result.current.tokens).toEqual(expectedDarkTokens);
64
92
 
65
93
  await act(async () => result.current.setTheme("light"));
66
94
  expect(result.current.theme).toBe("light");
95
+ expect(result.current.effectiveTheme).toBe("light");
96
+ expect(result.current.tokens).toEqual(expectedLightTokens);
97
+ });
98
+
99
+ it("resolves the system theme from the OS color scheme", async () => {
100
+ mockUseColorScheme.mockReturnValue("dark");
101
+
102
+ const { result, rerender } = renderHook(useAtlantisTheme, {
103
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
104
+ <Wrapper {...props} />
105
+ ),
106
+ });
107
+
108
+ await act(async () => result.current.setTheme("system"));
109
+ expect(result.current.theme).toBe("system");
110
+ expect(result.current.effectiveTheme).toBe("dark");
111
+ expect(result.current.tokens).toEqual(expectedDarkTokens);
112
+
113
+ mockUseColorScheme.mockReturnValue("light");
114
+ rerender({});
115
+
116
+ expect(result.current.theme).toBe("system");
117
+ expect(result.current.effectiveTheme).toBe("light");
118
+ expect(result.current.tokens).toEqual(expectedLightTokens);
119
+ });
120
+
121
+ it("uses a controlled selected theme", () => {
122
+ mockUseColorScheme.mockReturnValue("dark");
123
+
124
+ const { result } = renderHook(useAtlantisTheme, {
125
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
126
+ <Wrapper {...props} theme="system" />
127
+ ),
128
+ });
129
+
130
+ expect(result.current.theme).toBe("system");
131
+ expect(result.current.effectiveTheme).toBe("dark");
132
+ expect(result.current.tokens).toEqual(expectedDarkTokens);
133
+ });
134
+
135
+ it("reports selected theme changes from a controlled provider", async () => {
136
+ const onThemeChange = jest.fn();
137
+ const { result } = renderHook(useAtlantisTheme, {
138
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
139
+ <Wrapper {...props} theme="light" onThemeChange={onThemeChange} />
140
+ ),
141
+ });
142
+
143
+ await act(async () => result.current.setTheme("system"));
144
+
145
+ expect(onThemeChange).toHaveBeenCalledWith("system");
146
+ expect(result.current.theme).toBe("light");
147
+ expect(result.current.effectiveTheme).toBe("light");
148
+ expect(result.current.tokens).toEqual(expectedLightTokens);
149
+ });
150
+
151
+ it("falls back to light when system color scheme is unavailable", async () => {
152
+ mockUseColorScheme.mockReturnValue(
153
+ null as unknown as ReturnType<typeof useColorScheme>,
154
+ );
155
+
156
+ const { result } = renderHook(useAtlantisTheme, {
157
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
158
+ <Wrapper {...props} />
159
+ ),
160
+ });
161
+
162
+ await act(async () => result.current.setTheme("system"));
163
+ expect(result.current.theme).toBe("system");
164
+ expect(result.current.effectiveTheme).toBe("light");
67
165
  expect(result.current.tokens).toEqual(expectedLightTokens);
68
166
  });
69
167
 
@@ -80,15 +178,38 @@ describe("ThemeContext", () => {
80
178
 
81
179
  // This hook shouldn't be affected by it because it's set to the light theme
82
180
  expect(result.current.theme).toBe("light");
181
+ expect(result.current.effectiveTheme).toBe("light");
83
182
  expect(result.current.tokens).toEqual(expectedLightTokens);
84
183
  });
85
184
 
86
185
  it.each([
87
- { defaultTheme: "light", expectedTokens: expectedLightTokens },
88
- { defaultTheme: "dark", expectedTokens: expectedDarkTokens },
89
- ] as { defaultTheme: Theme; expectedTokens: typeof iosTokens }[])(
186
+ {
187
+ defaultTheme: "light",
188
+ colorScheme: "dark",
189
+ expectedEffectiveTheme: "light",
190
+ expectedTokens: expectedLightTokens,
191
+ },
192
+ {
193
+ defaultTheme: "dark",
194
+ colorScheme: "light",
195
+ expectedEffectiveTheme: "dark",
196
+ expectedTokens: expectedDarkTokens,
197
+ },
198
+ ] as {
199
+ defaultTheme: EffectiveTheme;
200
+ colorScheme: ColorSchemeName;
201
+ expectedEffectiveTheme: EffectiveTheme;
202
+ expectedTokens: typeof iosTokens;
203
+ }[])(
90
204
  "provides the dangerouslyOverrideTheme $defaultTheme tokens",
91
- ({ defaultTheme, expectedTokens }) => {
205
+ ({
206
+ defaultTheme,
207
+ colorScheme,
208
+ expectedEffectiveTheme,
209
+ expectedTokens,
210
+ }) => {
211
+ mockUseColorScheme.mockReturnValue(colorScheme);
212
+
92
213
  const { result } = renderHook(useAtlantisTheme, {
93
214
  wrapper: (props: AtlantisThemeContextProviderProps) => (
94
215
  <WrapperWithOverride
@@ -99,6 +220,7 @@ describe("ThemeContext", () => {
99
220
  });
100
221
 
101
222
  expect(result.current.theme).toBe(defaultTheme);
223
+ expect(result.current.effectiveTheme).toBe(expectedEffectiveTheme);
102
224
  expect(result.current.tokens).toEqual(expectedTokens);
103
225
  },
104
226
  );
@@ -1,10 +1,11 @@
1
1
  import { androidTokens, darkTokens, iosTokens } from "@jobber/design";
2
- import React, { createContext, useContext, useState } from "react";
2
+ import React, { createContext, useCallback, useContext, useState } from "react";
3
3
  import merge from "lodash/merge";
4
- import { Platform } from "react-native";
4
+ import { Platform, useColorScheme } from "react-native";
5
5
  import type {
6
6
  AtlantisThemeContextProviderProps,
7
7
  AtlantisThemeContextValue,
8
+ EffectiveTheme,
8
9
  Theme,
9
10
  } from "./types";
10
11
 
@@ -18,6 +19,7 @@ const completeDarkTokens = merge({}, lightTokens, darkTokens);
18
19
 
19
20
  const AtlantisThemeContext = createContext<AtlantisThemeContextValue>({
20
21
  theme: "light",
22
+ effectiveTheme: "light",
21
23
  tokens: lightTokens,
22
24
  setTheme: () => {
23
25
  console.error(
@@ -28,22 +30,36 @@ const AtlantisThemeContext = createContext<AtlantisThemeContextValue>({
28
30
 
29
31
  export function AtlantisThemeContextProvider({
30
32
  children,
33
+ theme,
34
+ onThemeChange,
31
35
  dangerouslyOverrideTheme,
32
36
  }: AtlantisThemeContextProviderProps) {
33
- // TODO: check last saved theme from local/device storage
34
- const initialTheme: Theme = "light";
35
- const [globalTheme, setGlobalTheme] = useState<Theme>(initialTheme);
37
+ const [uncontrolledTheme, setUncontrolledTheme] = useState<Theme>("light");
38
+ const colorScheme = useColorScheme();
36
39
 
37
- const currentTheme = dangerouslyOverrideTheme ?? globalTheme;
40
+ const setTheme = useCallback(
41
+ (nextTheme: Theme) => {
42
+ onThemeChange?.(nextTheme);
43
+
44
+ if (theme === undefined) {
45
+ setUncontrolledTheme(nextTheme);
46
+ }
47
+ },
48
+ [onThemeChange, theme],
49
+ );
50
+
51
+ const currentTheme = dangerouslyOverrideTheme ?? theme ?? uncontrolledTheme;
52
+ const effectiveTheme = resolveEffectiveTheme(currentTheme, colorScheme);
38
53
  const currentTokens =
39
- currentTheme === "dark" ? completeDarkTokens : lightTokens;
54
+ effectiveTheme === "dark" ? completeDarkTokens : lightTokens;
40
55
 
41
56
  return (
42
57
  <AtlantisThemeContext.Provider
43
58
  value={{
44
59
  theme: currentTheme,
60
+ effectiveTheme,
45
61
  tokens: currentTokens,
46
- setTheme: setGlobalTheme,
62
+ setTheme,
47
63
  }}
48
64
  >
49
65
  {children}
@@ -54,3 +70,12 @@ export function AtlantisThemeContextProvider({
54
70
  export function useAtlantisTheme() {
55
71
  return useContext(AtlantisThemeContext);
56
72
  }
73
+
74
+ function resolveEffectiveTheme(
75
+ theme: Theme,
76
+ colorScheme: ReturnType<typeof useColorScheme>,
77
+ ): EffectiveTheme {
78
+ if (theme !== "system") return theme;
79
+
80
+ return colorScheme === "dark" ? "dark" : "light";
81
+ }
@@ -14,15 +14,17 @@ function ChildrenComponent({
14
14
  }: {
15
15
  readonly message?: string;
16
16
  }) {
17
- const { theme, tokens, setTheme } = useAtlantisTheme();
17
+ const { theme, effectiveTheme, tokens, setTheme } = useAtlantisTheme();
18
18
 
19
19
  return (
20
20
  <View style={{ backgroundColor: tokens["color-surface"] }}>
21
21
  <Content>
22
22
  <Text>{message}</Text>
23
- <Text>{`Current theme: ${theme}`}</Text>
23
+ <Text>{`Selected theme: ${theme}`}</Text>
24
+ <Text>{`Effective theme: ${effectiveTheme}`}</Text>
24
25
  <Text>Tokens can be accessed using tokens[token-name]</Text>
25
26
  <Text>{`For example color-surface: ${tokens["color-surface"]}`}</Text>
27
+ <Button label="Set system theme" onPress={() => setTheme("system")} />
26
28
  <Button label="Set dark theme" onPress={() => setTheme("dark")} />
27
29
  <Button label="Set light theme" onPress={() => setTheme("light")} />
28
30
  </Content>
@@ -14,15 +14,17 @@ function ChildrenComponent({
14
14
  }: {
15
15
  readonly message?: string;
16
16
  }) {
17
- const { theme, tokens, setTheme } = useAtlantisTheme();
17
+ const { theme, effectiveTheme, tokens, setTheme } = useAtlantisTheme();
18
18
 
19
19
  return (
20
20
  <View style={{ backgroundColor: tokens["color-surface"] }}>
21
21
  <Content>
22
22
  <Text>{message}</Text>
23
- <Text>{`Current theme: ${theme}`}</Text>
23
+ <Text>{`Selected theme: ${theme}`}</Text>
24
+ <Text>{`Effective theme: ${effectiveTheme}`}</Text>
24
25
  <Text>Tokens can be accessed using tokens[token-name]</Text>
25
26
  <Text>{`For example color-surface: ${tokens["color-surface"]}`}</Text>
27
+ <Button label="Set system theme" onPress={() => setTheme("system")} />
26
28
  <Button label="Set dark theme" onPress={() => setTheme("dark")} />
27
29
  <Button label="Set light theme" onPress={() => setTheme("light")} />
28
30
  </Content>
@@ -3,6 +3,7 @@ export {
3
3
  useAtlantisTheme,
4
4
  } from "./AtlantisThemeContext";
5
5
  export type {
6
+ EffectiveTheme,
6
7
  Theme,
7
8
  AtlantisThemeContextProviderProps,
8
9
  AtlantisThemeContextValue,
@@ -2,10 +2,15 @@ import type { iosTokens } from "@jobber/design";
2
2
 
3
3
  export interface AtlantisThemeContextValue {
4
4
  /**
5
- * The active theme.
5
+ * The selected theme.
6
6
  */
7
7
  readonly theme: Theme;
8
8
 
9
+ /**
10
+ * The resolved theme used to select tokens.
11
+ */
12
+ readonly effectiveTheme: EffectiveTheme;
13
+
9
14
  /**
10
15
  * The design tokens for the current theme.
11
16
  */
@@ -23,11 +28,23 @@ export interface AtlantisThemeContextProviderProps {
23
28
  */
24
29
  readonly children: React.ReactNode;
25
30
 
31
+ /**
32
+ * Control the selected theme for this provider.
33
+ */
34
+ readonly theme?: Theme;
35
+
36
+ /**
37
+ * Called when children request a selected theme change through setTheme.
38
+ */
39
+ readonly onThemeChange?: (theme: Theme) => void;
40
+
26
41
  /**
27
42
  * Force the theme for this provider to always be the same as the provided theme. Useful for sections that should remain the same theme regardless of the rest of the application's theme.
28
43
  * This is dangerous because the children in this provider will not be able to change the theme.
29
44
  */
30
- readonly dangerouslyOverrideTheme?: Theme;
45
+ readonly dangerouslyOverrideTheme?: EffectiveTheme;
31
46
  }
32
47
 
33
- export type Theme = "light" | "dark";
48
+ export type Theme = "system" | EffectiveTheme;
49
+
50
+ export type EffectiveTheme = "light" | "dark";
@@ -99,7 +99,7 @@ export function ContentOverlay({
99
99
 
100
100
  const styles = useStyles();
101
101
  const { t } = useAtlantisI18n();
102
- const { theme, tokens } = useAtlantisTheme();
102
+ const { effectiveTheme, tokens } = useAtlantisTheme();
103
103
  const isScreenReaderEnabled = useIsScreenReaderEnabled();
104
104
 
105
105
  const behavior = computeContentOverlayBehavior(
@@ -327,7 +327,7 @@ export function ContentOverlay({
327
327
  the active theme here so descendants (e.g. the header title) resolve the
328
328
  correct themed tokens.
329
329
  */}
330
- <AtlantisThemeContextProvider dangerouslyOverrideTheme={theme}>
330
+ <AtlantisThemeContextProvider dangerouslyOverrideTheme={effectiveTheme}>
331
331
  <ContentOverlayKeyboardContext.Provider value={scrollEnabled}>
332
332
  {scrollEnabled ? (
333
333
  <BottomSheetKeyboardAwareScrollView
@@ -1,6 +1,7 @@
1
1
  import React, { type ReactElement } from "react";
2
2
  import { act, fireEvent, render, waitFor } from "@testing-library/react-native";
3
3
  import { Alert, Keyboard } from "react-native";
4
+ import { KeyboardEvents } from "react-native-keyboard-controller";
4
5
  import type { FormBannerMessage } from ".";
5
6
  import { Form, FormBannerMessageType } from ".";
6
7
  import type { FormBannerErrors } from "./types";
@@ -32,15 +33,37 @@ const onChangeSelectMock = jest.fn();
32
33
  const onChangeSwitchMock = jest.fn();
33
34
 
34
35
  const mockScrollTo = jest.fn();
36
+ const mockScrollToEnd = jest.fn();
35
37
  const mockScrollToTop = jest.fn();
38
+ const mockMeasureInWindow = jest.fn(
39
+ (callback: (x: number, y: number, width: number, height: number) => void) => {
40
+ callback(0, 0, 0, 0);
41
+ },
42
+ );
43
+ const mockScrollViewRef = {
44
+ get current() {
45
+ return { scrollTo: mockScrollTo, scrollToEnd: mockScrollToEnd };
46
+ },
47
+ // React assigns the ref while rendering KeyboardAwareScrollView. Keep the
48
+ // test double available to the keyboard-hide callback instead.
49
+ set current(value: unknown) {
50
+ void value;
51
+ },
52
+ };
53
+ const mockBottomViewRef = {
54
+ get current() {
55
+ return { measureInWindow: mockMeasureInWindow };
56
+ },
57
+ set current(value: unknown) {
58
+ void value;
59
+ },
60
+ };
36
61
 
37
62
  jest.mock("./hooks/useFormViewRefs", () => ({
38
63
  useFormViewRefs: () => {
39
64
  return {
40
- scrollViewRef: {
41
- current: { scrollTo: mockScrollTo },
42
- },
43
- bottomViewRef: { current: {} },
65
+ scrollViewRef: mockScrollViewRef,
66
+ bottomViewRef: mockBottomViewRef,
44
67
  scrollToTop: mockScrollToTop,
45
68
  };
46
69
  },
@@ -566,6 +589,40 @@ describe("Form", () => {
566
589
  });
567
590
  });
568
591
 
592
+ describe("Keyboard dismissal", () => {
593
+ it("does not imperatively scroll the form to its end", async () => {
594
+ const listeners = new Map<string, () => void>();
595
+ const addListener = KeyboardEvents.addListener as jest.Mock;
596
+
597
+ const registerListener = (eventName: string, listener: () => void) => {
598
+ listeners.set(eventName, listener);
599
+
600
+ return { remove: jest.fn() };
601
+ };
602
+ addListener
603
+ .mockImplementationOnce(registerListener)
604
+ .mockImplementationOnce(registerListener);
605
+
606
+ render(<FormTest onSubmit={onSubmitMock} />);
607
+
608
+ const hideListener = [...listeners.entries()].find(([eventName]) =>
609
+ eventName.endsWith("Hide"),
610
+ )?.[1];
611
+
612
+ expect(hideListener).toBeDefined();
613
+
614
+ if (!hideListener) {
615
+ throw new Error("Expected a keyboard hide listener");
616
+ }
617
+
618
+ await act(async () => {
619
+ hideListener();
620
+ });
621
+
622
+ expect(mockScrollToEnd).not.toHaveBeenCalled();
623
+ });
624
+ });
625
+
569
626
  describe("Leaving the form", () => {
570
627
  let mockUseConfirmBeforeBack: jest.Mock;
571
628
  let mockRemoveLocalCache: jest.Mock;
package/src/Form/Form.tsx CHANGED
@@ -138,19 +138,9 @@ function InternalForm<T extends FieldValues, S>({
138
138
  );
139
139
 
140
140
  const handleKeyboardHide = useCallback(() => {
141
- bottomViewRef?.current?.measureInWindow(
142
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
143
- (_x: number, y: number, _width: number, _height: number) => {
144
- // This fixes extra whitespace below the form if it was scrolled down while the keyboard was open
145
- // i.e. a View below the form is higher than the bottom of the window
146
- if (y < windowHeight) {
147
- scrollViewRef?.current?.scrollToEnd();
148
- }
149
- },
150
- );
151
141
  setKeyboardHeight(0);
152
142
  setKeyboardScreenY(0);
153
- }, [bottomViewRef, scrollViewRef, windowHeight]);
143
+ }, []);
154
144
 
155
145
  useEffect(() => {
156
146
  const showEvent =