react-native-ui-lib 8.3.4 → 8.4.0

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.
Files changed (30) hide show
  1. package/lib/android/build.gradle +3 -3
  2. package/package.json +1 -1
  3. package/screenFooter.d.ts +2 -0
  4. package/screenFooter.js +1 -0
  5. package/scripts/release/prReleaseNotesCommon.js +2 -1
  6. package/src/assets/internal/images/bottomGradient.png +0 -0
  7. package/src/assets/internal/images/bottomGradient@1.5x.png +0 -0
  8. package/src/assets/internal/images/bottomGradient@2x.png +0 -0
  9. package/src/assets/internal/images/bottomGradient@3x.png +0 -0
  10. package/src/assets/internal/images/bottomGradient@4x.png +0 -0
  11. package/src/assets/internal/images/index.js +3 -0
  12. package/src/components/floatingButton/floatingButton.api.json +10 -15
  13. package/src/components/floatingButton/index.d.ts +13 -39
  14. package/src/components/floatingButton/index.js +57 -167
  15. package/src/components/screenFooter/index.d.ts +8 -0
  16. package/src/components/screenFooter/index.js +214 -0
  17. package/src/components/screenFooter/screenFooter.api.json +250 -0
  18. package/src/components/screenFooter/types.d.ts +105 -0
  19. package/src/components/screenFooter/types.js +39 -0
  20. package/src/components/textField/TextField.driver.new.js +12 -7
  21. package/src/components/textField/index.js +31 -9
  22. package/src/hooks/index.d.ts +1 -0
  23. package/src/hooks/index.js +1 -0
  24. package/src/hooks/useScrollToHide/index.d.ts +24 -0
  25. package/src/hooks/useScrollToHide/index.js +48 -0
  26. package/src/incubator/expandableOverlay/index.js +7 -3
  27. package/src/index.d.ts +1 -0
  28. package/src/index.js +70 -0
  29. package/src/style/colors.d.ts +12 -13
  30. package/src/style/colors.js +40 -39
@@ -1,10 +1,10 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  project.ext {
4
- buildToolsVersion = rootProject.ext.has("buildToolsVersion") ? rootProject.ext.buildToolsVersion : '35.0.0'
4
+ buildToolsVersion = rootProject.ext.has("buildToolsVersion") ? rootProject.ext.buildToolsVersion : '36.0.0'
5
5
  minSdkVersion = rootProject.ext.has("minSdkVersion") ? rootProject.ext.minSdkVersion : 24
6
- compileSdkVersion = rootProject.ext.has("compileSdkVersion") ? rootProject.ext.compileSdkVersion : 35
7
- targetSdkVersion = rootProject.ext.has("targetSdkVersion") ? rootProject.ext.targetSdkVersion : 34
6
+ compileSdkVersion = rootProject.ext.has("compileSdkVersion") ? rootProject.ext.compileSdkVersion : 36
7
+ targetSdkVersion = rootProject.ext.has("targetSdkVersion") ? rootProject.ext.targetSdkVersion : 36
8
8
  supportLibVersion = rootProject.ext.has("supportLibVersion") ? rootProject.ext.supportLibVersion : '28.0.0'
9
9
  }
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-ui-lib",
3
- "version": "8.3.4",
3
+ "version": "8.4.0",
4
4
  "main": "src/index.js",
5
5
  "types": "src/index.d.ts",
6
6
  "author": "Ethan Sharabi <ethan.shar@gmail.com>",
@@ -0,0 +1,2 @@
1
+ import {ScreenFooter} from './src';
2
+ export default ScreenFooter;
@@ -0,0 +1 @@
1
+ module.exports = require('./src/components/screenFooter').default;
@@ -9,6 +9,7 @@ const BRANCH_CATEGORIES = [
9
9
  {name: 'fixes', branch: 'fix/', title: ':wrench: Fixes'},
10
10
  {name: 'infra', branch: 'infra/', title: ':gear: Maintenance & Infra'}
11
11
  ];
12
+ const SILENT_PRS = ['none', 'n/a', 'na'];
12
13
 
13
14
  function getBranchPrefixes() {
14
15
  return BRANCH_CATEGORIES.map(category => category.branch);
@@ -95,7 +96,7 @@ function isSilent(pr) {
95
96
  return true;
96
97
  } else {
97
98
  const changelog = pr.info.changelog.toLowerCase();
98
- if (changelog === 'none' || changelog === 'n/a') {
99
+ if (SILENT_PRS.includes(changelog)) {
99
100
  return true;
100
101
  }
101
102
  }
@@ -1,4 +1,7 @@
1
1
  export const images = {
2
+ get bottomGradient() {
3
+ return require('./bottomGradient.png');
4
+ },
2
5
  get gradient() {
3
6
  return require('./gradient.png');
4
7
  },
@@ -1,12 +1,8 @@
1
1
  {
2
2
  "name": "FloatingButton",
3
3
  "category": "overlays",
4
- "description": "Hovering button with gradient background",
5
- "modifiers": [
6
- "margin",
7
- "background",
8
- "color"
9
- ],
4
+ "description": "Hovering button with gradient background, backed by ScreenFooter",
5
+ "modifiers": ["margin", "background", "color"],
10
6
  "example": "https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/FloatingButtonScreen.tsx",
11
7
  "images": [
12
8
  "https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/FloatingButton/FloatingButton.gif?raw=true"
@@ -58,6 +54,12 @@
58
54
  "type": "boolean",
59
55
  "description": "Whether to show background overlay"
60
56
  },
57
+ {
58
+ "name": "hoisted",
59
+ "type": "boolean",
60
+ "description": "Whether the footer should be hoisted above the keyboard. When true, uses KeyboardAccessoryView for keyboard-aware positioning. When false, uses sticky positioning.",
61
+ "default": "true"
62
+ },
61
63
  {
62
64
  "name": "testID",
63
65
  "type": "string",
@@ -96,11 +98,7 @@
96
98
  },
97
99
  {
98
100
  "type": "table",
99
- "columns": [
100
- "Property",
101
- "Default",
102
- "Full Width"
103
- ],
101
+ "columns": ["Property", "Default", "Full Width"],
104
102
  "items": [
105
103
  {
106
104
  "title": "Main button",
@@ -130,10 +128,7 @@
130
128
  },
131
129
  {
132
130
  "type": "table",
133
- "columns": [
134
- "Layout",
135
- "Component"
136
- ],
131
+ "columns": ["Layout", "Component"],
137
132
  "items": [
138
133
  {
139
134
  "title": "Horizontal",
@@ -1,5 +1,4 @@
1
- import React, { PropsWithChildren, PureComponent } from 'react';
2
- import { Animated } from 'react-native';
1
+ import React, { PropsWithChildren } from 'react';
3
2
  import { ButtonProps } from '../button';
4
3
  export declare enum FloatingButtonLayouts {
5
4
  VERTICAL = "Vertical",
@@ -23,7 +22,7 @@ export interface FloatingButtonProps {
23
22
  */
24
23
  bottomMargin?: number;
25
24
  /**
26
- * Whether the buttons get the container's full with (vertical layout only)
25
+ * Whether the buttons get the container's full width (vertical layout only)
27
26
  */
28
27
  fullWidth?: boolean;
29
28
  /**
@@ -42,6 +41,12 @@ export interface FloatingButtonProps {
42
41
  * Whether to show background overlay
43
42
  */
44
43
  hideBackgroundOverlay?: boolean;
44
+ /**
45
+ * Whether the footer should be hoisted above the keyboard.
46
+ * When true (default), uses KeyboardAccessoryView for keyboard-aware positioning.
47
+ * When false, uses sticky positioning.
48
+ */
49
+ hoisted?: boolean;
45
50
  /**
46
51
  * Used as testing identifier
47
52
  * <TestID> - the floatingButton container
@@ -50,40 +55,9 @@ export interface FloatingButtonProps {
50
55
  */
51
56
  testID?: string;
52
57
  }
53
- /**
54
- * @description: Hovering button with gradient background
55
- * @modifiers: margin, background, color
56
- * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/FloatingButtonScreen.tsx
57
- * @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/FloatingButton/FloatingButton.gif?raw=true
58
- */
59
- declare class FloatingButton extends PureComponent<FloatingButtonProps> {
60
- static displayName: string;
61
- static floatingButtonLayouts: typeof FloatingButtonLayouts;
62
- static defaultProps: {
63
- duration: number;
64
- buttonLayout: FloatingButtonLayouts;
65
- };
66
- initialVisibility?: boolean;
67
- firstLoad: boolean;
68
- visibleAnimated: Animated.Value;
69
- constructor(props: FloatingButtonProps);
70
- componentDidUpdate(prevProps: FloatingButtonProps): void;
71
- getAnimatedStyle: () => {
72
- opacity: Animated.Value;
73
- transform: {
74
- translateY: Animated.AnimatedInterpolation<string | number>;
75
- }[];
76
- };
77
- get isSecondaryOnly(): boolean;
78
- get isHorizontalLayout(): boolean;
79
- get isSecondaryHorizontal(): boolean | undefined;
80
- get isSecondaryVertical(): boolean | undefined;
81
- renderButton(): React.JSX.Element | undefined;
82
- renderOverlay: () => React.JSX.Element | undefined;
83
- renderSecondaryButton(): React.JSX.Element | undefined;
84
- renderHorizontalLayout(): React.JSX.Element;
85
- renderVerticalLayout(): React.JSX.Element;
86
- render(): false | React.JSX.Element | undefined;
87
- }
88
- declare const _default: React.ForwardRefExoticComponent<FloatingButtonProps & React.RefAttributes<any>> & typeof FloatingButton;
58
+ declare const _default: React.ForwardRefExoticComponent<FloatingButtonProps & React.RefAttributes<any>> & {
59
+ (props: FloatingButtonProps): React.JSX.Element | null;
60
+ displayName: string;
61
+ floatingButtonLayouts: typeof FloatingButtonLayouts;
62
+ };
89
63
  export default _default;
@@ -1,190 +1,80 @@
1
- import React, { PureComponent } from 'react';
2
- import { StyleSheet, Animated } from 'react-native';
3
- import { Constants, asBaseComponent } from "../../commons/new";
4
- import { Colors, Shadows, Spacings } from "../../style";
5
- import View from "../view";
6
- import Image from "../image";
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import { asBaseComponent } from "../../commons/new";
4
+ import { LogService } from "../../services";
5
+ import { Colors, Shadows } from "../../style";
7
6
  import Button from "../button";
7
+ import ScreenFooter, { ScreenFooterLayouts, ScreenFooterBackgrounds, KeyboardBehavior, ItemsFit } from "../screenFooter";
8
8
  export let FloatingButtonLayouts = /*#__PURE__*/function (FloatingButtonLayouts) {
9
9
  FloatingButtonLayouts["VERTICAL"] = "Vertical";
10
10
  FloatingButtonLayouts["HORIZONTAL"] = "Horizontal";
11
11
  return FloatingButtonLayouts;
12
12
  }({});
13
- const gradientImage = () => require("./gradient.png");
14
-
15
13
  /**
16
- * @description: Hovering button with gradient background
14
+ * @description: Hovering button with gradient background, backed by ScreenFooter
17
15
  * @modifiers: margin, background, color
18
16
  * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/FloatingButtonScreen.tsx
19
17
  * @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/FloatingButton/FloatingButton.gif?raw=true
20
18
  */
21
- class FloatingButton extends PureComponent {
22
- static displayName = 'FloatingButton';
23
- static floatingButtonLayouts = FloatingButtonLayouts;
24
- static defaultProps = {
25
- duration: 300,
26
- buttonLayout: FloatingButtonLayouts.VERTICAL
27
- };
28
- constructor(props) {
29
- super(props);
30
- this.initialVisibility = props.visible;
31
- this.firstLoad = true;
32
- this.visibleAnimated = new Animated.Value(Number(!!props.visible));
33
- }
34
- componentDidUpdate(prevProps) {
35
- const {
36
- visible,
37
- duration
38
- } = this.props;
39
- if (prevProps.visible !== visible) {
40
- Animated.timing(this.visibleAnimated, {
41
- toValue: Number(!!visible),
42
- duration,
43
- useNativeDriver: true
44
- }).start();
45
- }
46
- }
47
- getAnimatedStyle = () => {
48
- return {
49
- opacity: this.visibleAnimated,
50
- transform: [{
51
- translateY: this.visibleAnimated.interpolate({
52
- inputRange: [0, 1],
53
- outputRange: [Constants.screenHeight / 2, 0]
54
- })
55
- }]
56
- };
57
- };
58
- get isSecondaryOnly() {
59
- const {
60
- secondaryButton,
61
- button
62
- } = this.props;
63
- return !!secondaryButton && !button;
64
- }
65
- get isHorizontalLayout() {
66
- const {
67
- buttonLayout
68
- } = this.props;
69
- return buttonLayout === FloatingButtonLayouts.HORIZONTAL || this.isSecondaryOnly;
70
- }
71
- get isSecondaryHorizontal() {
72
- const {
73
- secondaryButton
74
- } = this.props;
75
- return secondaryButton && this.isHorizontalLayout;
76
- }
77
- get isSecondaryVertical() {
78
- const {
79
- secondaryButton
80
- } = this.props;
81
- return secondaryButton && !this.isHorizontalLayout;
82
- }
83
- renderButton() {
84
- const {
85
- bottomMargin,
86
- button,
87
- fullWidth,
88
- testID
89
- } = this.props;
90
- if (button) {
91
- const shadowStyle = button && !button.outline && !button.link ? styles.shadow : undefined;
92
- const marginStyle = {
93
- marginTop: Spacings.s4,
94
- marginBottom: this.isSecondaryVertical ? Spacings.s4 : bottomMargin || Spacings.s8,
95
- marginLeft: this.isSecondaryHorizontal || fullWidth ? Spacings.s4 : undefined,
96
- marginRight: this.isSecondaryHorizontal ? Spacings.s5 : fullWidth ? Spacings.s4 : undefined
19
+ const FloatingButton = props => {
20
+ const {
21
+ visible = false,
22
+ button,
23
+ secondaryButton,
24
+ bottomMargin,
25
+ fullWidth,
26
+ buttonLayout = FloatingButtonLayouts.VERTICAL,
27
+ duration = 300,
28
+ withoutAnimation,
29
+ hideBackgroundOverlay,
30
+ hoisted = true,
31
+ testID
32
+ } = props;
33
+ useEffect(() => {
34
+ // eslint-disable-next-line max-len
35
+ LogService.warn('RNUILib FloatingButton now uses ScreenFooter internally, which requires a SafeAreaProvider. If you experience safe area issues, please wrap your app (or the relevant screen) with <SafeAreaProvider>.');
36
+ }, []);
37
+ const footerContentContainerStyle = useMemo(() => {
38
+ if (bottomMargin !== undefined) {
39
+ return {
40
+ paddingBottom: bottomMargin
97
41
  };
98
- const shouldFlex = this.isSecondaryHorizontal || fullWidth && this.isHorizontalLayout;
99
- return <Button size={Button.sizes.large} flex={!!shouldFlex} style={[shadowStyle, marginStyle]} testID={`${testID}.button`} {...button} />;
100
42
  }
43
+ return undefined;
44
+ }, [bottomMargin]);
45
+ const isSecondaryOnly = !!secondaryButton && !button;
46
+ const isHorizontal = buttonLayout === FloatingButtonLayouts.HORIZONTAL || isSecondaryOnly;
47
+ if (!button && !secondaryButton) {
48
+ return null;
101
49
  }
102
- renderOverlay = () => {
103
- if (!this.props.hideBackgroundOverlay) {
104
- return <View pointerEvents={'none'} style={styles.image}>
105
- <Image style={styles.image} source={gradientImage()} resizeMode={'stretch'} tintColor={Colors.$backgroundDefault} />
106
- </View>;
50
+ const renderPrimaryButton = () => {
51
+ if (!button) {
52
+ return null;
107
53
  }
54
+ const shadowStyle = !button.outline && !button.link ? styles.shadow : undefined;
55
+ const shouldFlex = isHorizontal && !!secondaryButton || fullWidth && isHorizontal;
56
+ return <Button key="primary" size={Button.sizes.large} flex={!!shouldFlex} style={shadowStyle} testID={testID ? `${testID}.button` : undefined} {...button} />;
108
57
  };
109
- renderSecondaryButton() {
110
- const {
111
- secondaryButton,
112
- bottomMargin,
113
- testID,
114
- fullWidth,
115
- button
116
- } = this.props;
117
- if (secondaryButton) {
118
- const bgColor = secondaryButton.backgroundColor || Colors.$backgroundDefault;
119
- const shouldFlex = this.isHorizontalLayout && !!button || fullWidth && this.isSecondaryOnly;
120
- const buttonStyle = this.isHorizontalLayout ? [styles.shadow, styles.horizontalSecondaryMargin, {
121
- backgroundColor: bgColor
122
- }] : {
123
- marginBottom: bottomMargin || Spacings.s7
124
- };
125
- return <Button outline={this.isHorizontalLayout} flex={shouldFlex} link={!this.isHorizontalLayout} size={Button.sizes.large} testID={`${testID}.secondaryButton`} {...secondaryButton} style={buttonStyle} enableShadow={false} />;
126
- }
127
- }
128
- renderHorizontalLayout() {
129
- return <>
130
- {this.renderOverlay()}
131
- {this.renderSecondaryButton()}
132
- {this.renderButton()}
133
- </>;
134
- }
135
- renderVerticalLayout() {
136
- return <>
137
- {this.renderOverlay()}
138
- {this.renderButton()}
139
- {this.renderSecondaryButton()}
140
- </>;
141
- }
142
- render() {
143
- // NOTE: keep this.firstLoad as true as long as the visibility changed to true
144
- const {
145
- withoutAnimation,
146
- visible,
147
- fullWidth,
148
- testID,
149
- button,
150
- secondaryButton
151
- } = this.props;
152
- this.firstLoad && !visible ? this.firstLoad = true : this.firstLoad = false;
153
-
154
- // NOTE: On first load, don't show if it should not be visible
155
- if (this.firstLoad === true && !this.initialVisibility) {
156
- return false;
58
+ const renderSecondaryButton = () => {
59
+ if (!secondaryButton) {
60
+ return null;
157
61
  }
158
- if (!visible && withoutAnimation) {
159
- return false;
160
- }
161
- if (button || secondaryButton) {
162
- const hasBothButtons = !!(button && secondaryButton);
163
- const shouldCenter = !fullWidth || this.isHorizontalLayout && hasBothButtons;
164
- return <View row={this.isHorizontalLayout} center={shouldCenter} pointerEvents="box-none" animated style={[styles.container, this.getAnimatedStyle()]} testID={testID}>
165
- {this.isHorizontalLayout ? this.renderHorizontalLayout() : this.renderVerticalLayout()}
166
- </View>;
167
- }
168
- }
169
- }
62
+ const shouldFlex = isHorizontal && !!button || fullWidth && isSecondaryOnly;
63
+ const bgColor = secondaryButton.backgroundColor || Colors.$backgroundDefault;
64
+ return <Button key="secondary" outline={isHorizontal} link={!isHorizontal} flex={shouldFlex} size={Button.sizes.large} testID={testID ? `${testID}.secondaryButton` : undefined} {...secondaryButton} style={isHorizontal ? [styles.shadow, {
65
+ backgroundColor: bgColor
66
+ }] : undefined} enableShadow={false} />;
67
+ };
68
+ const children = isHorizontal ? [renderSecondaryButton(), renderPrimaryButton()] : [renderPrimaryButton(), renderSecondaryButton()];
69
+ return <ScreenFooter visible={visible} layout={isHorizontal ? ScreenFooterLayouts.HORIZONTAL : ScreenFooterLayouts.VERTICAL} backgroundType={hideBackgroundOverlay ? ScreenFooterBackgrounds.TRANSPARENT : ScreenFooterBackgrounds.FADING} keyboardBehavior={hoisted ? KeyboardBehavior.HOISTED : KeyboardBehavior.STICKY} animationDuration={withoutAnimation ? 0 : duration} itemsFit={fullWidth ? ItemsFit.STRETCH : undefined} contentContainerStyle={footerContentContainerStyle} testID={testID}>
70
+ {children}
71
+ </ScreenFooter>;
72
+ };
73
+ FloatingButton.displayName = 'FloatingButton';
74
+ FloatingButton.floatingButtonLayouts = FloatingButtonLayouts;
170
75
  const styles = StyleSheet.create({
171
- container: {
172
- // ...StyleSheet.absoluteFillObject, // TODO: this is breaking scenarios where the FloatingButton is inside a KeyboardTrackingView
173
- top: undefined,
174
- zIndex: 99
175
- },
176
- image: {
177
- ...StyleSheet.absoluteFillObject,
178
- width: '100%',
179
- height: '100%'
180
- },
181
76
  shadow: {
182
77
  ...Shadows.sh20.bottom
183
- },
184
- horizontalSecondaryMargin: {
185
- marginTop: Spacings.s4,
186
- marginBottom: Spacings.s7,
187
- marginLeft: Spacings.s5
188
78
  }
189
79
  });
190
80
  export default asBaseComponent(FloatingButton);
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { ScreenFooterProps, ScreenFooterLayouts, ScreenFooterBackgrounds, FooterAlignment, HorizontalItemsDistribution, ItemsFit, KeyboardBehavior, ScreenFooterShadow } from './types';
3
+ export { ScreenFooterProps, ScreenFooterLayouts, ScreenFooterBackgrounds, FooterAlignment, HorizontalItemsDistribution, ItemsFit, KeyboardBehavior, ScreenFooterShadow };
4
+ declare const _default: React.ForwardRefExoticComponent<ScreenFooterProps & React.RefAttributes<any>> & {
5
+ (props: ScreenFooterProps): React.JSX.Element;
6
+ displayName: string;
7
+ };
8
+ export default _default;
@@ -0,0 +1,214 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import Animated, { useAnimatedKeyboard, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
4
+ import { Keyboard } from 'uilib-native';
5
+ import { SafeAreaContextPackage } from "../../optionalDependencies";
6
+ import View from "../view";
7
+ import Image from "../image";
8
+ import Assets from "../../assets";
9
+ import { Colors, Shadows, Spacings } from "../../style";
10
+ import { asBaseComponent, Constants } from "../../commons/new";
11
+ import { useKeyboardHeight } from "../../hooks";
12
+ import { ScreenFooterProps, ScreenFooterLayouts, ScreenFooterBackgrounds, FooterAlignment, HorizontalItemsDistribution, ItemsFit, KeyboardBehavior, ScreenFooterShadow } from "./types";
13
+ export { ScreenFooterProps, ScreenFooterLayouts, ScreenFooterBackgrounds, FooterAlignment, HorizontalItemsDistribution, ItemsFit, KeyboardBehavior, ScreenFooterShadow };
14
+ const ScreenFooter = props => {
15
+ const {
16
+ testID,
17
+ layout,
18
+ alignment,
19
+ horizontalAlignment,
20
+ backgroundType,
21
+ children,
22
+ keyboardBehavior = KeyboardBehavior.STICKY,
23
+ itemsFit,
24
+ itemWidth,
25
+ horizontalItemsDistribution: distribution,
26
+ visible = true,
27
+ animationDuration = 200,
28
+ shadow = ScreenFooterShadow.SH20,
29
+ hideDivider = false,
30
+ contentContainerStyle: contentContainerStyleOverride
31
+ } = props;
32
+ const keyboard = useAnimatedKeyboard();
33
+ const [height, setHeight] = useState(0);
34
+ const visibilityTranslateY = useSharedValue(0);
35
+
36
+ // Update visibility translation when visible or height changes
37
+ useEffect(() => {
38
+ visibilityTranslateY.value = withTiming(visible ? 0 : height, {
39
+ duration: animationDuration
40
+ });
41
+ }, [visible, height, animationDuration, visibilityTranslateY]);
42
+
43
+ // Animated style for STICKY behavior (counters Android system offset + visibility)
44
+ const stickyAnimatedStyle = useAnimatedStyle(() => {
45
+ const counterSystemOffset = Constants.isAndroid ? keyboard.height.value : 0;
46
+ return {
47
+ transform: [{
48
+ translateY: counterSystemOffset + visibilityTranslateY.value
49
+ }]
50
+ };
51
+ });
52
+
53
+ // Animated style for HOISTED behavior (visibility only, keyboard handled by KeyboardAccessoryView)
54
+ const hoistedAnimatedStyle = useAnimatedStyle(() => {
55
+ return {
56
+ transform: [{
57
+ translateY: visibilityTranslateY.value
58
+ }]
59
+ };
60
+ });
61
+ const onLayout = useCallback(event => {
62
+ setHeight(event.nativeEvent.layout.height);
63
+ }, []);
64
+ const isSolid = backgroundType === ScreenFooterBackgrounds.SOLID;
65
+ const isFading = backgroundType === ScreenFooterBackgrounds.FADING;
66
+ const isHorizontal = layout === ScreenFooterLayouts.HORIZONTAL;
67
+ const childrenCount = React.Children.count(children);
68
+ const justifyContent = useMemo(() => {
69
+ if (isHorizontal) {
70
+ if (distribution === HorizontalItemsDistribution.SPREAD) {
71
+ return childrenCount === 1 ? 'center' : 'space-between';
72
+ }
73
+ switch (horizontalAlignment) {
74
+ case FooterAlignment.START:
75
+ return 'flex-start';
76
+ case FooterAlignment.END:
77
+ return 'flex-end';
78
+ default:
79
+ return 'center';
80
+ }
81
+ }
82
+ return 'flex-start';
83
+ }, [isHorizontal, distribution, horizontalAlignment, childrenCount]);
84
+ const alignItems = useMemo(() => {
85
+ if (layout === ScreenFooterLayouts.VERTICAL) {
86
+ if (itemsFit === ItemsFit.STRETCH) {
87
+ return 'stretch';
88
+ }
89
+ }
90
+ switch (alignment) {
91
+ case FooterAlignment.START:
92
+ return 'flex-start';
93
+ case FooterAlignment.END:
94
+ return 'flex-end';
95
+ default:
96
+ return 'center';
97
+ }
98
+ }, [layout, itemsFit, alignment]);
99
+ const useSafeAreaInsets = SafeAreaContextPackage?.useSafeAreaInsets ?? (() => Constants.getSafeAreaInsets());
100
+ const insets = useSafeAreaInsets();
101
+ const keyboardHeight = useKeyboardHeight();
102
+ const isKeyboardVisible = keyboardHeight > 0;
103
+ const contentContainerStyle = useMemo(() => {
104
+ const style = [styles.contentContainer, layout === ScreenFooterLayouts.HORIZONTAL ? styles.horizontalContainer : styles.verticalContainer, {
105
+ alignItems,
106
+ justifyContent
107
+ }];
108
+ if (!isKeyboardVisible) {
109
+ style.push({
110
+ paddingBottom: insets.bottom
111
+ });
112
+ }
113
+ if (isSolid) {
114
+ const shadowStyle = Shadows[shadow]?.top;
115
+ const backgroundElevation = shadowStyle?.elevation || 0;
116
+ style.push({
117
+ elevation: backgroundElevation + 1
118
+ });
119
+ }
120
+ if (contentContainerStyleOverride) {
121
+ style.push(contentContainerStyleOverride);
122
+ }
123
+ return style;
124
+ }, [layout, alignItems, justifyContent, insets.bottom, isSolid, shadow, isKeyboardVisible, contentContainerStyleOverride]);
125
+ const solidBackgroundStyle = useMemo(() => {
126
+ if (!isSolid) {
127
+ return undefined;
128
+ }
129
+ const shadowStyle = Shadows[shadow]?.top;
130
+ const dividerStyle = hideDivider ? undefined : {
131
+ borderTopWidth: 1,
132
+ borderColor: Colors.$outlineDefault
133
+ };
134
+ return [shadowStyle, dividerStyle];
135
+ }, [isSolid, shadow, hideDivider]);
136
+ const renderBackground = useCallback(() => {
137
+ if (isSolid) {
138
+ return <View testID={testID ? `${testID}.solidBackground` : undefined} absF bg-$backgroundElevated style={solidBackgroundStyle} pointerEvents="none" />;
139
+ }
140
+ if (isFading) {
141
+ return <View testID={testID ? `${testID}.fadingBackground` : undefined} absF pointerEvents="none">
142
+ <Image source={Assets.internal.images.bottomGradient} style={styles.background} resizeMode="stretch" tintColor={Colors.$backgroundDefault} />
143
+ </View>;
144
+ }
145
+ return null;
146
+ }, [testID, isSolid, isFading, solidBackgroundStyle]);
147
+ const renderChild = useCallback((child, index) => {
148
+ if (itemsFit === ItemsFit.FIXED && itemWidth) {
149
+ const fixedStyle = isHorizontal ? {
150
+ width: itemWidth,
151
+ flexShrink: 1,
152
+ overflow: 'hidden',
153
+ flexDirection: 'row',
154
+ justifyContent: 'center'
155
+ } : {
156
+ width: itemWidth,
157
+ maxWidth: '100%'
158
+ };
159
+ return <View key={index} style={fixedStyle}>
160
+ {child}
161
+ </View>;
162
+ }
163
+ if (isHorizontal && React.isValidElement(child) && itemsFit === ItemsFit.STRETCH) {
164
+ return <View flex row centerH key={index}>
165
+ {child}
166
+ </View>;
167
+ }
168
+ return child;
169
+ }, [itemsFit, itemWidth, isHorizontal]);
170
+ const childrenArray = React.Children.toArray(children).slice(0, 3).map(renderChild);
171
+ const renderFooterContent = useCallback(() => {
172
+ return <>
173
+ {renderBackground()}
174
+ <View testID={testID ? `${testID}.content` : undefined} style={contentContainerStyle}>
175
+ {childrenArray}
176
+ </View>
177
+ </>;
178
+ }, [renderBackground, testID, contentContainerStyle, childrenArray]);
179
+ if (keyboardBehavior === KeyboardBehavior.HOISTED) {
180
+ return <Animated.View style={[styles.container, hoistedAnimatedStyle]} pointerEvents={visible ? 'box-none' : 'none'}>
181
+ <Keyboard.KeyboardAccessoryView renderContent={renderFooterContent} kbInputRef={undefined} scrollBehavior={Keyboard.KeyboardAccessoryView.scrollBehaviors.FIXED_OFFSET} useSafeArea={false} manageScrollView={false} revealKeyboardInteractive onHeightChanged={setHeight} />
182
+ </Animated.View>;
183
+ }
184
+ return <Animated.View testID={testID} onLayout={onLayout} style={[styles.container, stickyAnimatedStyle]}>
185
+ {renderFooterContent()}
186
+ </Animated.View>;
187
+ };
188
+ ScreenFooter.displayName = 'ScreenFooter';
189
+ const styles = StyleSheet.create({
190
+ container: {
191
+ position: 'absolute',
192
+ bottom: 0,
193
+ left: 0,
194
+ right: 0
195
+ },
196
+ contentContainer: {
197
+ paddingTop: Spacings.s4,
198
+ paddingHorizontal: Spacings.s5,
199
+ paddingBottom: Spacings.s5
200
+ },
201
+ horizontalContainer: {
202
+ flexDirection: 'row',
203
+ gap: Spacings.s5
204
+ },
205
+ verticalContainer: {
206
+ flexDirection: 'column',
207
+ gap: Spacings.s3
208
+ },
209
+ background: {
210
+ width: '100%',
211
+ height: '100%'
212
+ }
213
+ });
214
+ export default asBaseComponent(ScreenFooter);