@utilitywarehouse/hearth-react-native 0.30.4 → 0.31.1

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 (121) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +15 -18
  3. package/CHANGELOG.md +165 -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/Table/TableHeaderCell.js +10 -1
  30. package/build/components/Textarea/Textarea.d.ts +1 -1
  31. package/build/components/Textarea/Textarea.js +64 -5
  32. package/build/components/Textarea/Textarea.props.d.ts +10 -0
  33. package/build/components/Textarea/TextareaRoot.js +4 -1
  34. package/build/core/themes.d.ts +92 -88
  35. package/build/tokens/color.d.ts +82 -80
  36. package/build/tokens/color.js +41 -40
  37. package/build/tokens/components/dark/alert.d.ts +6 -6
  38. package/build/tokens/components/dark/alert.js +6 -6
  39. package/build/tokens/components/dark/bottom-navigation.d.ts +2 -2
  40. package/build/tokens/components/dark/bottom-navigation.js +2 -2
  41. package/build/tokens/components/dark/checkbox.d.ts +1 -1
  42. package/build/tokens/components/dark/checkbox.js +1 -1
  43. package/build/tokens/components/dark/icon-button.d.ts +3 -3
  44. package/build/tokens/components/dark/icon-button.js +3 -3
  45. package/build/tokens/components/dark/inline-link.d.ts +1 -1
  46. package/build/tokens/components/dark/inline-link.js +1 -1
  47. package/build/tokens/components/dark/link.d.ts +3 -3
  48. package/build/tokens/components/dark/link.js +3 -3
  49. package/build/tokens/components/dark/navigation.d.ts +2 -2
  50. package/build/tokens/components/dark/navigation.js +2 -2
  51. package/build/tokens/components/dark/parts.d.ts +2 -2
  52. package/build/tokens/components/dark/parts.js +2 -2
  53. package/build/tokens/components/dark/progress-bar.d.ts +3 -3
  54. package/build/tokens/components/dark/progress-bar.js +3 -3
  55. package/build/tokens/components/dark/progress-stepper.d.ts +1 -1
  56. package/build/tokens/components/dark/progress-stepper.js +1 -1
  57. package/build/tokens/components/dark/spinner.d.ts +1 -1
  58. package/build/tokens/components/dark/spinner.js +1 -1
  59. package/build/tokens/components/dark/table.d.ts +2 -0
  60. package/build/tokens/components/dark/table.js +2 -0
  61. package/build/tokens/components/dark/time-picker.d.ts +1 -0
  62. package/build/tokens/components/dark/time-picker.js +1 -0
  63. package/build/tokens/components/light/parts.d.ts +3 -3
  64. package/build/tokens/components/light/parts.js +3 -3
  65. package/build/tokens/components/light/table.d.ts +2 -0
  66. package/build/tokens/components/light/table.js +2 -0
  67. package/build/tokens/components/light/time-picker.d.ts +1 -0
  68. package/build/tokens/components/light/time-picker.js +1 -0
  69. package/build/tokens/semantic-dark.d.ts +40 -40
  70. package/build/tokens/semantic-dark.js +40 -40
  71. package/docs/changelog.mdx +170 -0
  72. package/package.json +3 -3
  73. package/src/components/Badge/Badge.props.ts +1 -0
  74. package/src/components/Badge/Badge.tsx +6 -1
  75. package/src/components/Badge/BadgeText.tsx +8 -2
  76. package/src/components/Container/Container.props.ts +10 -1
  77. package/src/components/ExpandableCard/ExpandableCard.docs.mdx +89 -37
  78. package/src/components/ExpandableCard/ExpandableCard.props.ts +51 -27
  79. package/src/components/ExpandableCard/ExpandableCard.stories.tsx +67 -17
  80. package/src/components/ExpandableCard/ExpandableCard.tsx +15 -7
  81. package/src/components/ExpandableCard/ExpandableCardText.tsx +1 -1
  82. package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +37 -7
  83. package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +36 -2
  84. package/src/components/Flex/Flex.props.ts +16 -2
  85. package/src/components/FormField/FormField.tsx +2 -1
  86. package/src/components/List/List.stories.tsx +35 -0
  87. package/src/components/Modal/Modal.docs.mdx +52 -1
  88. package/src/components/Modal/Modal.props.ts +21 -6
  89. package/src/components/Modal/Modal.shared.types.ts +23 -4
  90. package/src/components/Modal/Modal.stories.tsx +165 -1
  91. package/src/components/Modal/Modal.tsx +101 -81
  92. package/src/components/Modal/Modal.web.tsx +29 -23
  93. package/src/components/NavModal/NavModal.docs.mdx +29 -0
  94. package/src/components/NavModal/NavModal.props.ts +11 -3
  95. package/src/components/NavModal/NavModal.stories.tsx +29 -0
  96. package/src/components/NavModal/NavModal.tsx +39 -33
  97. package/src/components/Table/TableHeaderCell.tsx +10 -1
  98. package/src/components/Textarea/Textarea.docs.mdx +33 -1
  99. package/src/components/Textarea/Textarea.props.ts +11 -2
  100. package/src/components/Textarea/Textarea.stories.tsx +21 -1
  101. package/src/components/Textarea/Textarea.tsx +107 -3
  102. package/src/components/Textarea/TextareaRoot.tsx +6 -2
  103. package/src/tokens/color.ts +41 -40
  104. package/src/tokens/components/dark/alert.ts +6 -6
  105. package/src/tokens/components/dark/bottom-navigation.ts +2 -2
  106. package/src/tokens/components/dark/checkbox.ts +1 -1
  107. package/src/tokens/components/dark/icon-button.ts +3 -3
  108. package/src/tokens/components/dark/inline-link.ts +1 -1
  109. package/src/tokens/components/dark/link.ts +3 -3
  110. package/src/tokens/components/dark/navigation.ts +2 -2
  111. package/src/tokens/components/dark/parts.ts +2 -2
  112. package/src/tokens/components/dark/progress-bar.ts +3 -3
  113. package/src/tokens/components/dark/progress-stepper.ts +1 -1
  114. package/src/tokens/components/dark/spinner.ts +1 -1
  115. package/src/tokens/components/dark/table.ts +2 -0
  116. package/src/tokens/components/dark/time-picker.ts +1 -0
  117. package/src/tokens/components/light/parts.ts +3 -3
  118. package/src/tokens/components/light/table.ts +2 -0
  119. package/src/tokens/components/light/time-picker.ts +1 -0
  120. package/src/tokens/semantic-dark.ts +40 -40
  121. package/vercel.json +0 -21
@@ -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;
@@ -6,8 +6,9 @@ import { BodyText } from '../BodyText';
6
6
  import { useTableContext } from './Table.context';
7
7
  import { getColumnStyle } from './Table.utils';
8
8
  const renderContent = (children, weight = 'semibold', color = 'white') => {
9
+ styles.useVariants({ color });
9
10
  if (typeof children === 'string' || typeof children === 'number') {
10
- return (_jsx(BodyText, { size: "md", weight: weight, style: styles.text, inverted: color === 'purple', children: children }));
11
+ return (_jsx(BodyText, { size: "md", weight: weight, style: styles.text, children: children }));
11
12
  }
12
13
  return children;
13
14
  };
@@ -91,6 +92,14 @@ const styles = StyleSheet.create(theme => ({
91
92
  color: theme.color.text.primary,
92
93
  },
93
94
  },
95
+ color: {
96
+ purple: {
97
+ color: theme.components.table.headerCell.foregoundColorInverted,
98
+ },
99
+ white: {
100
+ color: theme.components.table.headerCell.foregoundColor,
101
+ },
102
+ },
94
103
  },
95
104
  },
96
105
  }));
@@ -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',