@utilitywarehouse/hearth-react-native 0.30.4-testid-fix-2 → 0.31.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 (93) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/.turbo/turbo-lint.log +62 -70
  3. package/CHANGELOG.md +155 -0
  4. package/build/components/Badge/Badge.js +2 -2
  5. package/build/components/Badge/Badge.props.d.ts +1 -0
  6. package/build/components/Badge/BadgeText.d.ts +1 -1
  7. package/build/components/Badge/BadgeText.js +2 -2
  8. package/build/components/Container/Container.props.d.ts +2 -2
  9. package/build/components/ExpandableCard/ExpandableCard.d.ts +1 -1
  10. package/build/components/ExpandableCard/ExpandableCard.js +13 -2
  11. package/build/components/ExpandableCard/ExpandableCard.props.d.ts +43 -23
  12. package/build/components/ExpandableCard/ExpandableCardText.js +1 -1
  13. package/build/components/ExpandableCard/ExpandableCardTrigger.d.ts +3 -3
  14. package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +31 -6
  15. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.d.ts +1 -1
  16. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +13 -2
  17. package/build/components/Flex/Flex.props.d.ts +2 -2
  18. package/build/components/FormField/FormField.d.ts +5 -5
  19. package/build/components/FormField/FormField.js +3 -2
  20. package/build/components/Modal/Modal.d.ts +1 -1
  21. package/build/components/Modal/Modal.js +33 -39
  22. package/build/components/Modal/Modal.props.d.ts +8 -3
  23. package/build/components/Modal/Modal.shared.types.d.ts +19 -4
  24. package/build/components/Modal/Modal.web.d.ts +1 -1
  25. package/build/components/Modal/Modal.web.js +6 -3
  26. package/build/components/NavModal/NavModal.d.ts +1 -1
  27. package/build/components/NavModal/NavModal.js +10 -7
  28. package/build/components/NavModal/NavModal.props.d.ts +4 -3
  29. package/build/components/Textarea/Textarea.d.ts +1 -1
  30. package/build/components/Textarea/Textarea.js +64 -5
  31. package/build/components/Textarea/Textarea.props.d.ts +10 -0
  32. package/build/components/Textarea/TextareaRoot.js +4 -1
  33. package/docs/changelog.mdx +21 -0
  34. package/package.json +4 -4
  35. package/src/components/Badge/Badge.props.ts +1 -0
  36. package/src/components/Badge/Badge.tsx +6 -1
  37. package/src/components/Badge/BadgeText.tsx +8 -2
  38. package/src/components/Container/Container.props.ts +10 -1
  39. package/src/components/ExpandableCard/ExpandableCard.docs.mdx +89 -37
  40. package/src/components/ExpandableCard/ExpandableCard.props.ts +51 -27
  41. package/src/components/ExpandableCard/ExpandableCard.stories.tsx +67 -17
  42. package/src/components/ExpandableCard/ExpandableCard.tsx +15 -7
  43. package/src/components/ExpandableCard/ExpandableCardText.tsx +1 -1
  44. package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +37 -7
  45. package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +36 -2
  46. package/src/components/Flex/Flex.props.ts +16 -2
  47. package/src/components/FormField/FormField.tsx +2 -1
  48. package/src/components/List/List.stories.tsx +35 -0
  49. package/src/components/Modal/Modal.docs.mdx +52 -1
  50. package/src/components/Modal/Modal.props.ts +21 -6
  51. package/src/components/Modal/Modal.shared.types.ts +23 -4
  52. package/src/components/Modal/Modal.stories.tsx +165 -1
  53. package/src/components/Modal/Modal.tsx +101 -81
  54. package/src/components/Modal/Modal.web.tsx +29 -23
  55. package/src/components/NavModal/NavModal.docs.mdx +29 -0
  56. package/src/components/NavModal/NavModal.props.ts +11 -3
  57. package/src/components/NavModal/NavModal.stories.tsx +29 -0
  58. package/src/components/NavModal/NavModal.tsx +39 -33
  59. package/src/components/Textarea/Textarea.docs.mdx +33 -1
  60. package/src/components/Textarea/Textarea.props.ts +11 -2
  61. package/src/components/Textarea/Textarea.stories.tsx +21 -1
  62. package/src/components/Textarea/Textarea.tsx +107 -3
  63. package/src/components/Textarea/TextareaRoot.tsx +6 -2
  64. package/build/components/DatePicker/TimePicker.d.ts +0 -3
  65. package/build/components/DatePicker/TimePicker.js +0 -84
  66. package/build/components/DatePicker/time-picker/animated-math.d.ts +0 -4
  67. package/build/components/DatePicker/time-picker/animated-math.js +0 -19
  68. package/build/components/DatePicker/time-picker/period-native.d.ts +0 -6
  69. package/build/components/DatePicker/time-picker/period-native.js +0 -17
  70. package/build/components/DatePicker/time-picker/period-picker.d.ts +0 -6
  71. package/build/components/DatePicker/time-picker/period-picker.js +0 -10
  72. package/build/components/DatePicker/time-picker/period-web.d.ts +0 -6
  73. package/build/components/DatePicker/time-picker/period-web.js +0 -21
  74. package/build/components/DatePicker/time-picker/wheel-native.d.ts +0 -8
  75. package/build/components/DatePicker/time-picker/wheel-native.js +0 -19
  76. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +0 -2
  77. package/build/components/DatePicker/time-picker/wheel-picker/index.js +0 -2
  78. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +0 -16
  79. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +0 -97
  80. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +0 -21
  81. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +0 -88
  82. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +0 -23
  83. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +0 -21
  84. package/build/components/DatePicker/time-picker/wheel-web.d.ts +0 -8
  85. package/build/components/DatePicker/time-picker/wheel-web.js +0 -146
  86. package/build/components/DatePicker/time-picker/wheel.d.ts +0 -8
  87. package/build/components/DatePicker/time-picker/wheel.js +0 -10
  88. package/build/components/SafeAreaView/SafeAreaView.d.ts +0 -5
  89. package/build/components/SafeAreaView/SafeAreaView.js +0 -117
  90. package/build/components/SafeAreaView/SafeAreaView.props.d.ts +0 -17
  91. package/build/components/SafeAreaView/SafeAreaView.props.js +0 -1
  92. package/build/components/SafeAreaView/index.d.ts +0 -2
  93. package/build/components/SafeAreaView/index.js +0 -1
@@ -10,7 +10,7 @@ import ExpandableCardLeadingContent from './ExpandableCardLeadingContent';
10
10
  import ExpandableCardText from './ExpandableCardText';
11
11
  import ExpandableCardTrailingContent from './ExpandableCardTrailingContent';
12
12
  import ExpandableCardTrailingIcon from './ExpandableCardTrailingIcon';
13
- const ExpandableCardTriggerRoot = ({ heading, helperText, leadingIcon, leadingContent, badge, badgePosition = 'bottom', numericValue, isExpanded, disabled, states, children, ...props }) => {
13
+ const ExpandableCardTriggerRoot = ({ heading, helperText, leadingIcon, leadingContent, badge, badgePosition = 'bottom', numericValue, triggerContent, isExpanded, showChevron = true, disabled, states, children, ...props }) => {
14
14
  const { active } = states || { active: false };
15
15
  const testID = props.testID || 'expandable-card-trigger';
16
16
  styles.useVariants({
@@ -26,8 +26,19 @@ const ExpandableCardTriggerRoot = ({ heading, helperText, leadingIcon, leadingCo
26
26
  }
27
27
  return null;
28
28
  };
29
+ const defaultAccessibilityLabel = [heading, helperText].filter(Boolean).join(', ');
29
30
  const renderDefaultContent = () => (_jsxs(_Fragment, { children: [renderLeadingContent(), _jsxs(ExpandableCardContent, { children: [badgePosition === 'top' ? badge : null, _jsx(ExpandableCardText, { children: heading }), helperText && _jsx(ExpandableCardHelperText, { children: helperText }), badgePosition === 'bottom' ? badge : null] }), !!numericValue && (_jsx(BodyText, { weight: "semibold", style: styles.numericValue, children: numericValue })), _jsx(ExpandableCardTrailingContent, { style: styles.chevron, children: _jsx(ExpandableCardTrailingIcon, { as: isExpanded ? ChevronUpSmallIcon : ChevronDownSmallIcon }) })] }));
30
- return (_jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: disabled, accessibilityRole: "button", accessibilityState: { expanded: isExpanded, disabled }, accessibilityLabel: `${heading}${helperText ? `, ${helperText}` : ''}`, children: children || renderDefaultContent() }));
31
+ const renderChevron = () => (_jsx(ExpandableCardTrailingContent, { style: styles.chevron, children: _jsx(ExpandableCardTrailingIcon, { as: isExpanded ? ChevronUpSmallIcon : ChevronDownSmallIcon }) }));
32
+ const renderCustomTriggerContent = () => (_jsxs(_Fragment, { children: [triggerContent, showChevron ? renderChevron() : null] }));
33
+ const renderChildrenContent = () => (_jsxs(_Fragment, { children: [children, showChevron ? renderChevron() : null] }));
34
+ let triggerBody = renderDefaultContent();
35
+ if (triggerContent !== undefined) {
36
+ triggerBody = renderCustomTriggerContent();
37
+ }
38
+ if (children) {
39
+ triggerBody = renderChildrenContent();
40
+ }
41
+ return (_jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: disabled, accessibilityRole: "button", accessibilityState: { expanded: isExpanded, disabled }, accessibilityLabel: props.accessibilityLabel || defaultAccessibilityLabel || undefined, children: triggerBody }));
31
42
  };
32
43
  ExpandableCardTriggerRoot.displayName = 'ExpandableCardTriggerRoot';
33
44
  const styles = StyleSheet.create(theme => ({
@@ -1,6 +1,6 @@
1
1
  import type { FlexAlignType, ViewProps, ViewStyle } from 'react-native';
2
- import { FlexLayoutProps, GapProps, SpacingValues } from '../../types';
3
- interface FlexProps extends ViewProps, FlexLayoutProps, GapProps {
2
+ import { DisplayProps, FlexLayoutProps, GapProps, MarginProps, PaddingProps, SpacingValues } from '../../types';
3
+ interface FlexProps extends ViewProps, MarginProps, PaddingProps, FlexLayoutProps, GapProps, Omit<DisplayProps, 'direction'> {
4
4
  direction?: ViewStyle['flexDirection'];
5
5
  align?: FlexAlignType;
6
6
  justify?: ViewStyle['justifyContent'];
@@ -1,24 +1,24 @@
1
1
  import { View } from 'react-native';
2
2
  import FormFieldProps from './FormField.props';
3
- export declare const FormFieldComponent: import("@gluestack-ui/form-control/lib/types").IFormControlComponentType<import("react-native").ViewProps, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("..").IconProps, "as">, unknown, Omit<import("../Label/Label.props").default, "disabled">, unknown, Omit<import("../Helper/Helper.props").default, "validationStatus">, import("..").BodyTextProps>;
3
+ export declare const FormFieldComponent: import("@gluestack-ui/form-control/lib/types").IFormControlComponentType<import("react-native").ViewProps, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("..").IconProps, "as">, unknown, Omit<import("../Label/Label.props").default, "disabled">, unknown, Omit<import("../Helper/Helper.props").default, "validationStatus">, import("../BodyText").BodyTextProps>;
4
4
  export declare const FormFieldLabel: import("react").ForwardRefExoticComponent<import("react").RefAttributes<unknown>> & {
5
5
  Text: import("react").ForwardRefExoticComponent<Omit<Omit<import("../Label/Label.props").default, "disabled">, "ref"> & import("react").RefAttributes<Omit<import("../Label/Label.props").default, "disabled">>>;
6
6
  };
7
7
  export declare const FormFieldLabelText: import("react").ForwardRefExoticComponent<Omit<Omit<import("../Label/Label.props").default, "disabled">, "ref"> & import("react").RefAttributes<Omit<import("../Label/Label.props").default, "disabled">>>;
8
8
  export declare const FormFieldHelper: import("react").ForwardRefExoticComponent<Omit<import("../Helper/Helper.props").default, "validationStatus"> & import("react").RefAttributes<Omit<import("../Helper/Helper.props").default, "validationStatus">>> & {
9
- Text: import("react").ForwardRefExoticComponent<Omit<import("..").BodyTextProps, "ref"> & import("react").RefAttributes<import("..").BodyTextProps>>;
9
+ Text: import("react").ForwardRefExoticComponent<Omit<import("../BodyText").BodyTextProps, "ref"> & import("react").RefAttributes<import("../BodyText").BodyTextProps>>;
10
10
  };
11
- export declare const FormFieldHelperText: import("react").ForwardRefExoticComponent<Omit<import("..").BodyTextProps, "ref"> & import("react").RefAttributes<import("..").BodyTextProps>>;
11
+ export declare const FormFieldHelperText: import("react").ForwardRefExoticComponent<Omit<import("../BodyText").BodyTextProps, "ref"> & import("react").RefAttributes<import("../BodyText").BodyTextProps>>;
12
12
  export declare const FormFieldHelperIcon: {
13
13
  (props: import("..").IconProps): import("react/jsx-runtime").JSX.Element;
14
14
  displayName: string;
15
15
  };
16
16
  export declare const FormFieldValidText: {
17
- (props: import("..").BodyTextProps): import("react/jsx-runtime").JSX.Element;
17
+ (props: import("../BodyText").BodyTextProps): import("react/jsx-runtime").JSX.Element;
18
18
  displayName: string;
19
19
  };
20
20
  export declare const FormFieldInvalidText: {
21
- (props: import("..").BodyTextProps): import("react/jsx-runtime").JSX.Element;
21
+ (props: import("../BodyText").BodyTextProps): import("react/jsx-runtime").JSX.Element;
22
22
  displayName: string;
23
23
  };
24
24
  export declare const FormFieldTextContent: typeof View;
@@ -1,7 +1,8 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createFormControl } from '@gluestack-ui/form-control';
3
3
  import { useMemo, useState } from 'react';
4
4
  import { View } from 'react-native';
5
+ import { BodyText } from '../BodyText';
5
6
  import { HelperIcon, HelperText } from '../Helper';
6
7
  import { FormFieldContext } from './FormField.context';
7
8
  import FormFieldHelperComponent from './FormFieldHelper';
@@ -56,7 +57,7 @@ const FormField = ({ children, disabled, validationStatus = 'initial', readonly,
56
57
  setShouldHandleAccessibility,
57
58
  shouldHandleAccessibility,
58
59
  ]);
59
- return (_jsx(FormFieldContext.Provider, { value: value, children: _jsxs(FormFieldComponent, { ...props, isDisabled: disabled, isReadOnly: readonly, children: [(!!label || !!helperText) && (_jsxs(FormFieldTextContent, { children: [!!label && (_jsxs(FormFieldLabelText, { variant: labelVariant, importantForAccessibility: shouldHandleAccessibility ? 'no' : undefined, accessibilityElementsHidden: shouldHandleAccessibility, children: [label, !required ? ` (Optional)` : ''] })), !!helperText && (_jsx(FormFieldHelper, { text: helperText, icon: helperIcon, showIcon: !!helperIcon, importantForAccessibility: shouldHandleAccessibility ? 'no' : undefined, accessibilityElementsHidden: shouldHandleAccessibility }))] })), children, !!validText && _jsx(FormFieldValid, { text: validText }), !!invalidText && _jsx(FormFieldInvalidComponent, { text: invalidText })] }) }));
60
+ return (_jsx(FormFieldContext.Provider, { value: value, children: _jsxs(FormFieldComponent, { ...props, isDisabled: disabled, isReadOnly: readonly, children: [(!!label || !!helperText) && (_jsxs(FormFieldTextContent, { children: [!!label && (_jsxs(FormFieldLabelText, { variant: labelVariant, importantForAccessibility: shouldHandleAccessibility ? 'no' : undefined, accessibilityElementsHidden: shouldHandleAccessibility, children: [label, !required ? _jsx(BodyText, { weight: "regular", children: " (Optional)" }) : ''] })), !!helperText && (_jsx(FormFieldHelper, { text: helperText, icon: helperIcon, showIcon: !!helperIcon, importantForAccessibility: shouldHandleAccessibility ? 'no' : undefined, accessibilityElementsHidden: shouldHandleAccessibility }))] })), children, !!validText && _jsx(FormFieldValid, { text: validText }), !!invalidText && _jsx(FormFieldInvalidComponent, { text: invalidText })] }) }));
60
61
  };
61
62
  FormField.displayName = 'FormField';
62
63
  export default FormField;
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
3
3
  type Modal<T = any> = BottomSheetModalMethods<T> & {
4
4
  triggerCloseAnimation?: () => void;
5
5
  };
6
- declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, loadingDescription, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, loadingDescription, fullscreen, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { BottomSheetFooter, } from '@gorhom/bottom-sheet';
3
3
  import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
4
- import { useCallback, useImperativeHandle, useMemo, useRef } from 'react';
4
+ import { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
5
  import { AccessibilityInfo, Platform, View, findNodeHandle } from 'react-native';
6
6
  import { StyleSheet } from 'react-native-unistyles';
7
+ import { useTheme } from '../../hooks';
7
8
  import { BodyText } from '../BodyText';
8
9
  import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
9
10
  import { useBottomSheetContext } from '../BottomSheet/BottomSheet.context';
@@ -11,10 +12,12 @@ import { Button } from '../Button';
11
12
  import { Heading } from '../Heading';
12
13
  import { Spinner } from '../Spinner';
13
14
  import { UnstyledIconButton } from '../UnstyledIconButton';
14
- const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', loadingDescription, fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, ...props }) => {
15
+ const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', loadingDescription, fullscreen = false, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, ...props }) => {
16
+ const theme = useTheme();
15
17
  const bottomSheetModalRef = useRef(null);
16
18
  const viewRef = useRef(null);
17
19
  const scrollViewRef = useRef(null);
20
+ const [stickyFooterHeight, setStickyFooterHeight] = useState(0);
18
21
  const { useSafeAreaInsets } = useBottomSheetContext();
19
22
  useImperativeHandle(ref, () => ({
20
23
  ...bottomSheetModalRef.current,
@@ -59,28 +62,40 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
59
62
  bottomSheetModalRef.current?.dismiss();
60
63
  }
61
64
  }, [closeOnSecondaryButtonPress, onPressSecondaryButton]);
62
- const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
65
+ const handleStickyFooterLayout = useCallback((event) => {
66
+ const nextHeight = Math.ceil(event.nativeEvent.layout.height);
67
+ setStickyFooterHeight(currentHeight => currentHeight === nextHeight ? currentHeight : nextHeight);
68
+ }, []);
69
+ const hasPrimaryButton = !!(onPressPrimaryButton && primaryButtonText);
70
+ const hasSecondaryButton = !!(onPressSecondaryButton && secondaryButtonText);
71
+ const hasFooter = !!footer || hasPrimaryButton || hasSecondaryButton;
72
+ const shouldShowFooter = !loading && hasFooter;
63
73
  styles.useVariants({
64
74
  loading,
65
- bothButtons: !!(onPressPrimaryButton && onPressSecondaryButton),
66
- noButtons,
75
+ noButtons: !shouldShowFooter,
67
76
  stickyFooter,
68
77
  showHandle: props.showHandle,
69
78
  useSafeAreaInsets,
70
79
  });
71
- const footer = useMemo(() => (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })), [
80
+ const footerContent = useMemo(() => footer ?? (_jsxs(View, { style: styles.footer, children: [hasPrimaryButton ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, hasSecondaryButton ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })), [
81
+ footer,
72
82
  handlePrimaryButtonPress,
73
83
  handleSecondaryButtonPress,
74
- onPressPrimaryButton,
75
- onPressSecondaryButton,
84
+ hasPrimaryButton,
85
+ hasSecondaryButton,
76
86
  primaryButtonProps,
77
87
  primaryButtonText,
78
88
  secondaryButtonProps,
79
89
  secondaryButtonText,
80
90
  ]);
81
- const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined, accessibilityHint: Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading }), loadingDescription ? _jsx(BodyText, { textAlign: "center", children: loadingDescription }) : null] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !stickyFooter && !noButtons ? footer : null] })) }));
82
- const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [footer]);
83
- return (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && !noButtons ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.scrollView, ref: scrollViewRef, children: content })] }));
91
+ const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined, accessibilityHint: Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading }), loadingDescription ? _jsx(BodyText, { textAlign: "center", children: loadingDescription }) : null] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !stickyFooter && shouldShowFooter ? (_jsx(View, { style: footerStyle, children: footerContent })) : null] })) }));
92
+ const renderFooter = useCallback((bottomSheetFooterProps) => (_jsx(BottomSheetFooter, { ...bottomSheetFooterProps, children: _jsx(View, { onLayout: handleStickyFooterLayout, style: [styles.footerWrap, footerStyle], children: footerContent }) })), [footerContent, footerStyle, handleStickyFooterLayout]);
93
+ return (_jsxs(_Fragment, { children: [stickyFooter && shouldShowFooter && stickyFooterHeight === 0 ? (_jsx(View, { accessible: false, importantForAccessibility: "no-hide-descendants", pointerEvents: "none", style: styles.footerMeasurementContainer, children: _jsx(View, { onLayout: handleStickyFooterLayout, style: [styles.footerWrap, footerStyle], children: footerContent }) })) : null, _jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && shouldShowFooter ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: [
94
+ styles.scrollView,
95
+ stickyFooter && shouldShowFooter && stickyFooterHeight > 0
96
+ ? { paddingBottom: stickyFooterHeight + theme.components.modal.gap }
97
+ : null,
98
+ ], ref: scrollViewRef, children: content })] })] }));
84
99
  };
85
100
  const styles = StyleSheet.create((theme, rt) => ({
86
101
  modal: {
@@ -100,14 +115,6 @@ const styles = StyleSheet.create((theme, rt) => ({
100
115
  scrollView: {
101
116
  flex: 1,
102
117
  variants: {
103
- bothButtons: {
104
- true: {
105
- paddingBottom: 166,
106
- },
107
- false: {
108
- paddingBottom: 102,
109
- },
110
- },
111
118
  noButtons: {
112
119
  true: {
113
120
  paddingBottom: theme.components.modal.padding,
@@ -125,29 +132,10 @@ const styles = StyleSheet.create((theme, rt) => ({
125
132
  },
126
133
  },
127
134
  compoundVariants: [
128
- {
129
- bothButtons: true,
130
- useSafeAreaInsets: true,
131
- styles: {
132
- paddingBottom: 166 +
133
- rt.insets.bottom -
134
- theme.components.modal.padding +
135
- theme.components.bottomSheet.padding,
136
- },
137
- },
138
- {
139
- bothButtons: false,
140
- useSafeAreaInsets: true,
141
- styles: {
142
- paddingBottom: 102 +
143
- rt.insets.bottom -
144
- theme.components.modal.padding +
145
- theme.components.bottomSheet.padding,
146
- },
147
- },
148
135
  {
149
136
  noButtons: true,
150
137
  useSafeAreaInsets: true,
138
+ stickyFooter: false,
151
139
  styles: {
152
140
  paddingBottom: rt.insets.bottom +
153
141
  theme.components.modal.padding +
@@ -206,6 +194,12 @@ const styles = StyleSheet.create((theme, rt) => ({
206
194
  footer: {
207
195
  gap: theme.components.modal.action.gap,
208
196
  },
197
+ footerMeasurementContainer: {
198
+ left: 0,
199
+ opacity: 0,
200
+ position: 'absolute',
201
+ right: 0,
202
+ },
209
203
  footerWrap: {
210
204
  backgroundColor: theme.color.surface.neutral.strong,
211
205
  paddingHorizontal: theme.components.bottomSheet.padding,
@@ -1,8 +1,13 @@
1
1
  import { BottomSheetProps } from '../BottomSheet';
2
- import { ModalCommonProps } from './Modal.shared.types';
3
- interface ModalProps extends Omit<BottomSheetProps, 'children'>, ModalCommonProps {
2
+ import { ModalButtonFooterProps, ModalCommonBaseProps, ModalCustomFooterProps } from './Modal.shared.types';
3
+ type ModalBaseProps = Omit<BottomSheetProps, 'children'> & ModalCommonBaseProps & {
4
4
  fullscreen?: boolean;
5
+ };
6
+ type ModalProps = (ModalBaseProps & ModalButtonFooterProps & {
5
7
  closeOnPrimaryButtonPress?: boolean;
6
8
  closeOnSecondaryButtonPress?: boolean;
7
- }
9
+ }) | (ModalBaseProps & ModalCustomFooterProps & {
10
+ closeOnPrimaryButtonPress?: never;
11
+ closeOnSecondaryButtonPress?: never;
12
+ });
8
13
  export default ModalProps;
@@ -1,8 +1,8 @@
1
1
  import { ReactNode } from 'react';
2
- import { ViewProps } from 'react-native';
2
+ import { StyleProp, ViewProps, ViewStyle } from 'react-native';
3
3
  import { ButtonWithoutChildrenProps } from '../Button/Button.props';
4
4
  import { UnstyledIconButtonProps } from '../UnstyledIconButton';
5
- export interface ModalCommonProps {
5
+ export interface ModalCommonBaseProps {
6
6
  loading?: boolean;
7
7
  image?: ReactNode;
8
8
  showCloseButton?: boolean;
@@ -12,12 +12,27 @@ export interface ModalCommonProps {
12
12
  description?: string;
13
13
  stickyFooter?: boolean;
14
14
  children?: ViewProps['children'];
15
+ onPressCloseButton?: () => void;
16
+ closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
17
+ }
18
+ export interface ModalButtonFooterProps {
15
19
  onPressPrimaryButton?: () => void;
16
20
  primaryButtonText?: string;
17
21
  onPressSecondaryButton?: () => void;
18
22
  secondaryButtonText?: string;
19
- onPressCloseButton?: () => void;
20
23
  primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
21
24
  secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
22
- closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
25
+ footer?: never;
26
+ footerStyle?: StyleProp<ViewStyle>;
27
+ }
28
+ export interface ModalCustomFooterProps {
29
+ footer: ReactNode;
30
+ footerStyle?: StyleProp<ViewStyle>;
31
+ onPressPrimaryButton?: never;
32
+ primaryButtonText?: never;
33
+ onPressSecondaryButton?: never;
34
+ secondaryButtonText?: never;
35
+ primaryButtonProps?: never;
36
+ secondaryButtonProps?: never;
23
37
  }
38
+ export type ModalCommonProps = ModalCommonBaseProps & (ModalButtonFooterProps | ModalCustomFooterProps);
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
3
3
  type Modal<T = any> = BottomSheetModalMethods<T> & {
4
4
  triggerCloseAnimation?: () => void;
5
5
  };
6
- declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -9,7 +9,7 @@ import { Button } from '../Button';
9
9
  import { Heading } from '../Heading';
10
10
  import { Spinner } from '../Spinner';
11
11
  import { UnstyledIconButton } from '../UnstyledIconButton';
12
- const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }) => {
12
+ const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }) => {
13
13
  const bottomSheetModalRef = useRef(null);
14
14
  const viewRef = useRef(null);
15
15
  const scrollViewRef = useRef(null);
@@ -56,8 +56,11 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
56
56
  bottomSheetModalRef.current?.dismiss();
57
57
  }
58
58
  };
59
- const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
60
- const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !noButtons ? (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })) : null] })) }));
59
+ const hasPrimaryButton = !!(onPressPrimaryButton && primaryButtonText);
60
+ const hasSecondaryButton = !!(onPressSecondaryButton && secondaryButtonText);
61
+ const hasFooter = !!footer || hasPrimaryButton || hasSecondaryButton;
62
+ const footerContent = footer ?? (_jsxs(View, { style: styles.footer, children: [hasPrimaryButton ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, hasSecondaryButton ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
63
+ const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, hasFooter ? _jsx(View, { style: footerStyle, children: footerContent }) : null] })) }));
61
64
  return (_jsx(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, ...props, onChange: handleChange, children: _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, ref: scrollViewRef, children: content }) }));
62
65
  };
63
66
  const styles = StyleSheet.create(theme => ({
@@ -1,3 +1,3 @@
1
1
  import NavModalProps from './NavModal.props';
2
- declare const NavModal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading, loadingDescription, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, background, scrollable, presentation, scrollViewProps, useSafeAreaInsets, ...props }: NavModalProps) => import("react/jsx-runtime").JSX.Element;
2
+ declare const NavModal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading, loadingDescription, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, background, scrollable, presentation, scrollViewProps, useSafeAreaInsets, ...props }: NavModalProps) => import("react/jsx-runtime").JSX.Element;
3
3
  export default NavModal;
@@ -11,7 +11,7 @@ import { Button } from '../Button';
11
11
  import { Heading } from '../Heading';
12
12
  import { Spinner } from '../Spinner';
13
13
  import { UnstyledIconButton } from '../UnstyledIconButton';
14
- const NavModal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading = 'Loading...', loadingDescription, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, background = 'default', scrollable = true, presentation = 'modal', scrollViewProps, useSafeAreaInsets = true, ...props }) => {
14
+ const NavModal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading = 'Loading...', loadingDescription, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, background = 'default', scrollable = true, presentation = 'modal', scrollViewProps, useSafeAreaInsets = true, ...props }) => {
15
15
  const theme = useTheme();
16
16
  const backgroundOpacity = useSharedValue(0);
17
17
  const pretendContentTranslateY = useSharedValue(20);
@@ -60,7 +60,9 @@ const NavModal = ({ ref, children, heading, description, showCloseButton = true,
60
60
  const handleSecondaryButtonPress = useCallback(() => {
61
61
  onPressSecondaryButton?.();
62
62
  }, [onPressSecondaryButton]);
63
- const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
63
+ const hasPrimaryButton = !!(onPressPrimaryButton && primaryButtonText);
64
+ const hasSecondaryButton = !!(onPressSecondaryButton && secondaryButtonText);
65
+ const hasFooter = !!footer || hasPrimaryButton || hasSecondaryButton;
64
66
  styles.useVariants({
65
67
  loading,
66
68
  background: isBrandBackground ? 'brand' : 'primary',
@@ -69,12 +71,13 @@ const NavModal = ({ ref, children, heading, description, showCloseButton = true,
69
71
  usesSheetPresentation,
70
72
  stickyFooter,
71
73
  });
72
- const footer = useMemo(() => (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: isBrandBackground, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: isBrandBackground, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })), [
74
+ const footerContent = useMemo(() => footer ?? (_jsxs(View, { style: styles.footer, children: [hasPrimaryButton ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: isBrandBackground, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, hasSecondaryButton ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: isBrandBackground, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })), [
75
+ footer,
73
76
  handlePrimaryButtonPress,
74
77
  handleSecondaryButtonPress,
78
+ hasPrimaryButton,
79
+ hasSecondaryButton,
75
80
  isBrandBackground,
76
- onPressPrimaryButton,
77
- onPressSecondaryButton,
78
81
  primaryButtonProps,
79
82
  primaryButtonText,
80
83
  secondaryButtonProps,
@@ -83,9 +86,9 @@ const NavModal = ({ ref, children, heading, description, showCloseButton = true,
83
86
  const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined, accessibilityHint: Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined, screenReaderFocusable: true, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground, children: loadingHeading }), loadingDescription ? (_jsx(BodyText, { size: "md", textAlign: "center", inverted: isBrandBackground, children: loadingDescription })) : null] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground, children: description })) : null] })] })) : null, scrollable ? (_jsxs(ScrollView, { style: {
84
87
  flex: stickyFooter ? 1 : 0,
85
88
  marginHorizontal: -4,
86
- }, contentContainerStyle: { paddingHorizontal: 4 }, ...scrollViewProps, children: [children, !stickyFooter && !noButtons ? (_jsx(View, { style: styles.inNavModalFooterContainer, children: footer })) : null] })) : (_jsxs(View, { style: {
89
+ }, contentContainerStyle: { paddingHorizontal: 4 }, ...scrollViewProps, children: [children, !stickyFooter && hasFooter ? (_jsx(View, { style: [styles.inNavModalFooterContainer, footerStyle], children: footerContent })) : null] })) : (_jsxs(View, { style: {
87
90
  flex: stickyFooter ? 1 : 0,
88
- }, children: [children, !stickyFooter && !noButtons ? (_jsx(View, { style: styles.inNavModalFooterContainer, children: footer })) : null] })), stickyFooter && !noButtons ? footer : null] })) }));
91
+ }, children: [children, !stickyFooter && hasFooter ? (_jsx(View, { style: [styles.inNavModalFooterContainer, footerStyle], children: footerContent })) : null] })), stickyFooter && hasFooter ? _jsx(View, { style: footerStyle, children: footerContent }) : null] })) }));
89
92
  return (_jsxs(View, { style: styles.root, children: [Platform.OS === 'android' && usesSheetPresentation ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [
90
93
  styles.inNavModalContainer,
91
94
  Platform.OS === 'android' && usesSheetPresentation && animatedBackgroundStyle,
@@ -1,15 +1,16 @@
1
1
  import { Ref } from 'react';
2
2
  import { ScrollViewProps } from 'react-native';
3
- import { ModalCommonProps } from '../Modal/Modal.shared.types';
3
+ import { ModalButtonFooterProps, ModalCommonBaseProps, ModalCustomFooterProps } from '../Modal/Modal.shared.types';
4
4
  export interface NavModalRef {
5
5
  triggerCloseAnimation?: () => void;
6
6
  }
7
- interface NavModalProps extends ModalCommonProps {
7
+ type NavModalBaseProps = ModalCommonBaseProps & {
8
8
  ref?: Ref<NavModalRef>;
9
9
  background?: 'default' | 'brand';
10
10
  scrollable?: boolean;
11
11
  presentation?: 'fullScreenModal' | 'modal' | 'transparentModal' | 'containedModal' | 'containedTransparentModal';
12
12
  useSafeAreaInsets?: boolean;
13
13
  scrollViewProps?: Omit<ScrollViewProps, 'children'>;
14
- }
14
+ };
15
+ type NavModalProps = (NavModalBaseProps & ModalButtonFooterProps) | (NavModalBaseProps & ModalCustomFooterProps);
15
16
  export default NavModalProps;
@@ -7,5 +7,5 @@ export declare const TextareaComponent: import("@gluestack-ui/textarea/lib/types
7
7
  };
8
8
  }, import("react-native").TextInputProps>;
9
9
  export declare const TextareaField: import("react").ForwardRefExoticComponent<import("react-native").TextInputProps & import("react").RefAttributes<import("react-native").TextInputProps> & import("@gluestack-ui/textarea/lib/typescript/types").IInputProps>;
10
- declare const Textarea: ({ validationStatus, children, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, ...props }: TextareaProps) => import("react/jsx-runtime").JSX.Element;
10
+ declare const Textarea: ({ validationStatus, children, resizable, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, onLayout, ...props }: TextareaProps) => import("react/jsx-runtime").JSX.Element;
11
11
  export default Textarea;
@@ -1,6 +1,12 @@
1
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createTextarea } from '@gluestack-ui/textarea';
3
- import { useEffect } from 'react';
3
+ import { useEffect, useMemo, useRef } from 'react';
4
+ import { View, } from 'react-native';
5
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
6
+ import { useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
7
+ import { Path, Svg } from 'react-native-svg';
8
+ import { StyleSheet } from 'react-native-unistyles';
9
+ import { useTheme } from '../../hooks';
4
10
  import { FormField, useFormFieldContext } from '../FormField';
5
11
  import TextareaFieldComponent from './TextareaField';
6
12
  import TextareaRoot from './TextareaRoot';
@@ -9,8 +15,12 @@ export const TextareaComponent = createTextarea({
9
15
  Input: TextareaFieldComponent,
10
16
  });
11
17
  export const TextareaField = TextareaComponent.Input;
12
- const Textarea = ({ validationStatus = 'initial', children, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, ...props }) => {
18
+ const DEFAULT_TEXTAREA_HEIGHT = 96;
19
+ const RESIZE_HANDLE_TOUCH_SIZE = 28;
20
+ const RESIZE_HANDLE_ICON_SIZE = 9;
21
+ const Textarea = ({ validationStatus = 'initial', children, resizable = false, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, onLayout, ...props }) => {
13
22
  const formFieldContext = useFormFieldContext();
23
+ const hasMeasuredHeight = useRef(false);
14
24
  const textareaLabel = label ?? formFieldContext?.label;
15
25
  const textareaHelperText = helperText ?? formFieldContext?.helperText;
16
26
  const textareaValidText = validText ?? formFieldContext?.validText;
@@ -19,11 +29,14 @@ const Textarea = ({ validationStatus = 'initial', children, disabled, focused, r
19
29
  const textareaDisabled = disabled ?? formFieldContext?.disabled;
20
30
  const textareaReadonly = readonly ?? formFieldContext?.readonly;
21
31
  const textareaValidationStatus = formFieldContext?.validationStatus ?? validationStatus;
32
+ const textareaHeight = useSharedValue(DEFAULT_TEXTAREA_HEIGHT);
33
+ const resizeStartHeight = useSharedValue(DEFAULT_TEXTAREA_HEIGHT);
34
+ const theme = useTheme();
22
35
  useEffect(() => {
23
36
  if (formFieldContext?.setShouldHandleAccessibility) {
24
37
  formFieldContext.setShouldHandleAccessibility(true);
25
38
  }
26
- }, []);
39
+ }, [formFieldContext]);
27
40
  const getAccessibilityLabel = () => {
28
41
  let accessibilityLabel = '';
29
42
  if (textareaLabel) {
@@ -52,6 +65,52 @@ const Textarea = ({ validationStatus = 'initial', children, disabled, focused, r
52
65
  }
53
66
  return accessibilityHint || props.accessibilityHint;
54
67
  };
55
- return (_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validText: validText, invalidText: invalidText, required: required, validationStatus: validationStatus, disabled: disabled, readonly: readonly, accessibilityHandledByChildren: true, children: _jsx(TextareaComponent, { ...(children ? props : {}), validationStatus: textareaValidationStatus, isInvalid: textareaValidationStatus === 'invalid', isReadOnly: textareaReadonly, isDisabled: textareaDisabled, isFocused: focused, required: textareaRequired, "aria-label": getAccessibilityLabel(), accessibilityHint: getAccessibilityHint(), children: children ? (_jsx(_Fragment, { children: children })) : (_jsx(_Fragment, { children: _jsx(TextareaField, { ...props }) })) }) }));
68
+ const handleTextareaLayout = (event) => {
69
+ if (!hasMeasuredHeight.current) {
70
+ textareaHeight.value = event.nativeEvent.layout.height;
71
+ resizeStartHeight.value = event.nativeEvent.layout.height;
72
+ hasMeasuredHeight.current = true;
73
+ }
74
+ if (children) {
75
+ onLayout?.(event);
76
+ }
77
+ };
78
+ const resizeGesture = useMemo(() => Gesture.Pan()
79
+ .enabled(resizable && !textareaDisabled)
80
+ .onBegin(() => {
81
+ resizeStartHeight.value = textareaHeight.value;
82
+ })
83
+ .onUpdate(event => {
84
+ const nextHeight = resizeStartHeight.value + event.translationY + event.translationX * 0.35;
85
+ textareaHeight.value = Math.max(DEFAULT_TEXTAREA_HEIGHT, nextHeight);
86
+ }), [resizable, resizeStartHeight, textareaDisabled, textareaHeight]);
87
+ const animatedHeightStyle = useAnimatedStyle(() => ({
88
+ height: textareaHeight.value,
89
+ }), [textareaHeight]);
90
+ const rootStyle = (children ? props.style : undefined);
91
+ const inputStyle = (!children ? props.style : undefined);
92
+ const textareaStyle = (resizable ? [rootStyle, animatedHeightStyle] : rootStyle);
93
+ return (_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validText: validText, invalidText: invalidText, required: required, validationStatus: validationStatus, disabled: disabled, readonly: readonly, accessibilityHandledByChildren: true, children: _jsxs(TextareaComponent, { ...(children ? props : {}), onLayout: handleTextareaLayout, style: textareaStyle, validationStatus: textareaValidationStatus, isInvalid: textareaValidationStatus === 'invalid', isReadOnly: textareaReadonly, isDisabled: textareaDisabled, isFocused: focused, required: textareaRequired, "aria-label": getAccessibilityLabel(), accessibilityHint: getAccessibilityHint(), children: [children ? (_jsx(_Fragment, { children: children })) : (_jsx(_Fragment, { children: _jsx(TextareaField, { ...props, onLayout: onLayout, style: [styles.textarea, inputStyle] }) })), resizable && !textareaDisabled ? (_jsx(GestureDetector, { gesture: resizeGesture, children: _jsx(View, { style: styles.resizeHandle, children: _jsx(Svg, { width: RESIZE_HANDLE_ICON_SIZE, height: RESIZE_HANDLE_ICON_SIZE, viewBox: "0 0 9 9", fill: "none", children: _jsx(Path, { d: "M0.353516 8.35355L8.35352 0.353546M4.35352 8.35355L8.35352 4.35355", stroke: theme.color.icon.primary }) }) }) })) : null] }) }));
56
94
  };
95
+ const styles = StyleSheet.create({
96
+ textarea: {
97
+ padding: 0,
98
+ _web: {
99
+ outlineStyle: 'none',
100
+ _focusVisible: {
101
+ outlineStyle: 'none',
102
+ },
103
+ },
104
+ },
105
+ resizeHandle: {
106
+ position: 'absolute',
107
+ right: 0,
108
+ bottom: 0,
109
+ width: RESIZE_HANDLE_TOUCH_SIZE,
110
+ height: RESIZE_HANDLE_TOUCH_SIZE,
111
+ alignItems: 'center',
112
+ justifyContent: 'center',
113
+ zIndex: 1,
114
+ },
115
+ });
57
116
  export default Textarea;
@@ -1,5 +1,15 @@
1
1
  import type { TextInputProps, ViewProps } from 'react-native';
2
2
  export interface TextareaBaseProps {
3
+ /**
4
+ * If true, the textarea can be resized vertically using a drag handle.
5
+ *
6
+ * @type boolean
7
+ * @example
8
+ * ```tsx
9
+ * <Textarea resizable />
10
+ * ```
11
+ */
12
+ resizable?: boolean;
3
13
  /**
4
14
  * If true, the textarea will be disabled.
5
15
  *
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
3
  import { View } from 'react-native';
4
+ import Animated from 'react-native-reanimated';
4
5
  import { StyleSheet } from 'react-native-unistyles';
5
6
  import { TextareaContext } from './Textarea.context';
7
+ const AnimatedView = Animated.createAnimatedComponent(View);
6
8
  const TextareaRoot = ({ children, style, states, validationStatus, ...props }) => {
7
9
  const { focus = false, disabled = false, readonly = false } = states || {};
8
10
  styles.useVariants({ validationStatus, focus, disabled, readonly });
9
11
  const value = useMemo(() => ({ focused: focus, disabled, readonly, validationStatus }), [focus, disabled, readonly, validationStatus]);
10
- return (_jsx(TextareaContext.Provider, { value: value, children: _jsx(View, { ...props, style: [styles.container, style], children: children }) }));
12
+ return (_jsx(TextareaContext.Provider, { value: value, children: _jsx(AnimatedView, { ...props, style: [styles.container, style], children: children }) }));
11
13
  };
12
14
  TextareaRoot.displayName = 'TextareaRoot';
13
15
  const styles = StyleSheet.create(theme => ({
@@ -15,6 +17,7 @@ const styles = StyleSheet.create(theme => ({
15
17
  borderColor: theme.color.border.strong,
16
18
  height: theme.components.input.textArea.height,
17
19
  borderRadius: theme.components.input.borderRadius,
20
+ position: 'relative',
18
21
  flexDirection: 'row',
19
22
  overflow: 'hidden',
20
23
  alignContent: 'center',
@@ -9,6 +9,27 @@ import { BackToTopButton, NextPrevPage } from './components';
9
9
  The changelog for the Hearth React Native library. Here you can find all the changes, improvements, and bug fixes for each version.
10
10
 
11
11
 
12
+ ## 0.30.4
13
+
14
+ ### Patch Changes
15
+
16
+ - [#1096](https://github.com/utilitywarehouse/hearth/pull/1096) [`6fd9021`](https://github.com/utilitywarehouse/hearth/commit/6fd9021a91ef38b56b78755965515a0807b34fa1) Thanks [@fillyD](https://github.com/fillyD)! - 🐛 [FIX]: Restore visibility of bottom sheet content in iOS accessibility tree, enabling automated tests to interact with `Select` options
17
+
18
+ ## 0.30.3
19
+
20
+ ### Patch Changes
21
+
22
+ - [#1094](https://github.com/utilitywarehouse/hearth/pull/1094) [`a9d8e66`](https://github.com/utilitywarehouse/hearth/commit/a9d8e660b7efa23c7a573af2658fc10ab6c043b9) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Make horizontal pressable `Banner` content flex correctly on native and web.
23
+
24
+ This fixes horizontal pressable `Banner` layouts where the content area did not expand consistently, which could misplace the chevron and action content.
25
+
26
+ **Components affected**:
27
+ - `Banner`
28
+
29
+ **Developer changes**:
30
+
31
+ No changes are required.
32
+
12
33
  ## 0.30.2
13
34
 
14
35
  ### Patch Changes