@utilitywarehouse/hearth-react-native 0.30.4 → 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 (63) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +12 -15
  3. package/CHANGELOG.md +149 -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 +1 -1
  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
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.30.4",
3
+ "version": "0.31.0",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -20,6 +20,7 @@ interface BadgeProps extends ViewProps {
20
20
  icon?: ComponentType;
21
21
  flatBase?: boolean;
22
22
  text?: string | number;
23
+ numberOfLines?: number;
23
24
  }
24
25
 
25
26
  export default BadgeProps;
@@ -15,6 +15,7 @@ const Badge = ({ children, ...props }: BadgeProps) => {
15
15
  size = 'sm',
16
16
  style,
17
17
  text,
18
+ numberOfLines,
18
19
  ...rest
19
20
  } = props;
20
21
 
@@ -31,7 +32,11 @@ const Badge = ({ children, ...props }: BadgeProps) => {
31
32
  <BadgeContext.Provider value={value}>
32
33
  <View {...rest} style={[styles.container, style]}>
33
34
  {!!icon && <BadgeIcon as={icon} />}
34
- {childIsText ? <BadgeText>{text ?? children}</BadgeText> : children}
35
+ {childIsText ? (
36
+ <BadgeText numberOfLines={numberOfLines}>{text ?? children}</BadgeText>
37
+ ) : (
38
+ children
39
+ )}
35
40
  </View>
36
41
  </BadgeContext.Provider>
37
42
  );
@@ -3,11 +3,17 @@ import { StyleSheet } from 'react-native-unistyles';
3
3
  import { BodyText } from '../BodyText';
4
4
  import { useBadgeContext } from './Badge.context';
5
5
 
6
- const BadgeText = ({ children, style, ...props }: TextProps) => {
6
+ const BadgeText = ({ children, style, numberOfLines = 1, ...props }: TextProps) => {
7
7
  const { variant, colorScheme, size } = useBadgeContext();
8
8
  styles.useVariants({ variant, colorScheme });
9
9
  return (
10
- <BodyText {...props} size={size} weight="semibold" style={[styles.text, style]}>
10
+ <BodyText
11
+ {...props}
12
+ numberOfLines={numberOfLines}
13
+ size={size}
14
+ weight="semibold"
15
+ style={[styles.text, style]}
16
+ >
11
17
  {children}
12
18
  </BodyText>
13
19
  );
@@ -1,6 +1,8 @@
1
1
  import type { ViewProps } from 'react-native';
2
2
  import type {
3
3
  BackgroundColorProps,
4
+ DisplayProps,
5
+ FlexLayoutProps,
4
6
  GapProps,
5
7
  MarginProps,
6
8
  PaddingProps,
@@ -8,7 +10,14 @@ import type {
8
10
  } from '../../types';
9
11
 
10
12
  interface ContainerProps
11
- extends ViewProps, MarginProps, PaddingProps, GapProps, BackgroundColorProps {
13
+ extends
14
+ ViewProps,
15
+ MarginProps,
16
+ PaddingProps,
17
+ GapProps,
18
+ BackgroundColorProps,
19
+ FlexLayoutProps,
20
+ DisplayProps {
12
21
  /**
13
22
  * The spacing between child elements (gap).
14
23
  */