@utilitywarehouse/hearth-react-native 0.22.1 → 0.23.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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.22.1 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.23.0 build /home/runner/work/hearth/hearth/packages/react-native
3
3
  > tsc
4
4
 
@@ -1,5 +1,5 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.22.1 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.23.0 lint /home/runner/work/hearth/hearth/packages/react-native
3
3
  > TIMING=1 eslint .
4
4
 
5
5
 
@@ -31,8 +31,8 @@
31
31
  78:8 warning React Hook useEffect has a missing dependency: 'formFieldContext'. Either include it or remove the dependency array react-hooks/exhaustive-deps
32
32
 
33
33
  /home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.tsx
34
- 73:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
35
- 269:5 warning React Hook useCallback has a missing dependency: 'footer'. Either include it or remove the dependency array react-hooks/exhaustive-deps
34
+ 74:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
35
+ 274:5 warning React Hook useCallback has a missing dependency: 'footer'. Either include it or remove the dependency array react-hooks/exhaustive-deps
36
36
 
37
37
  /home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.web.tsx
38
38
  66:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
@@ -60,13 +60,13 @@
60
60
 
61
61
  Rule | Time (ms) | Relative
62
62
  :-----------------------------------------|----------:|--------:
63
- @typescript-eslint/no-unused-vars | 1480.154 | 63.5%
64
- no-global-assign | 86.111 | 3.7%
65
- react-hooks/exhaustive-deps | 81.893 | 3.5%
66
- react-hooks/rules-of-hooks | 58.807 | 2.5%
67
- no-misleading-character-class | 53.302 | 2.3%
68
- @typescript-eslint/ban-ts-comment | 49.364 | 2.1%
69
- no-unexpected-multiline | 36.564 | 1.6%
70
- no-fallthrough | 32.890 | 1.4%
71
- @typescript-eslint/triple-slash-reference | 30.004 | 1.3%
72
- no-regex-spaces | 26.454 | 1.1%
63
+ @typescript-eslint/no-unused-vars | 2022.725 | 59.1%
64
+ react-hooks/exhaustive-deps | 192.308 | 5.6%
65
+ no-global-assign | 140.412 | 4.1%
66
+ react-hooks/rules-of-hooks | 101.894 | 3.0%
67
+ @typescript-eslint/ban-ts-comment | 78.579 | 2.3%
68
+ no-misleading-character-class | 64.143 | 1.9%
69
+ no-unexpected-multiline | 47.419 | 1.4%
70
+ @typescript-eslint/triple-slash-reference | 40.424 | 1.2%
71
+ no-loss-of-precision | 39.686 | 1.2%
72
+ no-regex-spaces | 38.266 | 1.1%
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#975](https://github.com/utilitywarehouse/hearth/pull/975) [`102f04e`](https://github.com/utilitywarehouse/hearth/commit/102f04e0d560cf0faa21da5020c230e88e857251) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add a `background` option for Modal when used inside navigation modals
8
+
9
+ Modal now supports a `background` prop with `default` and `brand` values. When `background="brand"` is used in a navigation modal, the buttons and close icon invert for contrast, and the content area is scrollable.
10
+
11
+ **Components affected**:
12
+ - `Modal`
13
+
14
+ **Developer changes**:
15
+
16
+ No changes required. To opt in to the brand background:
17
+
18
+ ```tsx
19
+ <Modal background="brand" inNavModal>
20
+ ...
21
+ </Modal>
22
+ ```
23
+
3
24
  ## 0.22.1
4
25
 
5
26
  ### Patch Changes
@@ -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, inNavModal, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, stickyFooter, background, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { BottomSheetFooter, } from '@gorhom/bottom-sheet';
3
3
  import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
4
4
  import { useCallback, useEffect, useImperativeHandle, useRef } from 'react';
5
- import { AccessibilityInfo, Platform, View, findNodeHandle } from 'react-native';
5
+ import { AccessibilityInfo, Platform, ScrollView, View, findNodeHandle } from 'react-native';
6
6
  import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming, } from 'react-native-reanimated';
7
7
  import { StyleSheet } from 'react-native-unistyles';
8
8
  import { useTheme } from '../../hooks';
@@ -13,7 +13,7 @@ import { Button } from '../Button';
13
13
  import { Heading } from '../Heading';
14
14
  import { Spinner } from '../Spinner';
15
15
  import { UnstyledIconButton } from '../UnstyledIconButton';
16
- 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, inNavModal = false, stickyFooter = true, ...props }) => {
16
+ 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, inNavModal = false, stickyFooter = true, background = 'default', ...props }) => {
17
17
  const bottomSheetModalRef = useRef(null);
18
18
  const viewRef = useRef(null);
19
19
  const scrollViewRef = useRef(null);
@@ -105,9 +105,10 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
105
105
  noButtons,
106
106
  stickyFooter,
107
107
  showHandle: props.showHandle,
108
+ background: background === 'brand' ? 'brand' : 'primary',
108
109
  });
109
- const footer = (_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] }));
110
- 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, (!stickyFooter || inNavModal) && !noButtons ? footer : null] })) }));
110
+ const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: background === 'brand' && inNavModal, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: background === 'brand' && inNavModal, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
111
+ 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", inverted: background === 'brand' && inNavModal, ...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, inNavModal ? _jsx(ScrollView, { style: { flex: 1 }, children: children }) : children, (!stickyFooter || inNavModal) && !noButtons ? footer : null] })) }));
111
112
  const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
112
113
  onPressPrimaryButton,
113
114
  primaryButtonText,
@@ -116,7 +117,10 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
116
117
  primaryButtonProps,
117
118
  secondaryButtonProps,
118
119
  ]);
119
- return inNavModal ? (_jsxs(View, { style: { flex: 1, backgroundColor: theme.color.background.primary }, children: [Platform.OS === 'android' ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle], children: _jsx(View, { style: styles.inNavModalContent, children: content }) })] })) : (_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 })] }));
120
+ return inNavModal ? (_jsxs(View, { style: {
121
+ flex: 1,
122
+ backgroundColor: theme.color.background[background === 'brand' ? 'brand' : 'primary'],
123
+ }, children: [Platform.OS === 'android' ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle], children: _jsx(View, { style: styles.inNavModalContent, children: content }) })] })) : (_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 })] }));
120
124
  };
121
125
  const styles = StyleSheet.create((theme, rt) => ({
122
126
  modal: {
@@ -225,6 +229,14 @@ const styles = StyleSheet.create((theme, rt) => ({
225
229
  gap: theme.components.modal.gap,
226
230
  padding: theme.components.modal.padding,
227
231
  paddingBottom: theme.components.modal.padding + rt.insets.bottom,
232
+ variants: {
233
+ background: {
234
+ primary: {},
235
+ brand: {
236
+ backgroundColor: theme.color.background.brand,
237
+ },
238
+ },
239
+ },
228
240
  },
229
241
  androidContainer: {
230
242
  height: rt.insets.top + 18,
@@ -24,5 +24,6 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
24
24
  primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
25
25
  secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
26
26
  closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
27
+ background?: 'default' | 'brand';
27
28
  }
28
29
  export default ModalProps;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -56,8 +56,8 @@
56
56
  "vite-plugin-svgr": "^4.5.0",
57
57
  "vitest": "^3.2.4",
58
58
  "@utilitywarehouse/hearth-fonts": "^0.0.4",
59
- "@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
60
59
  "@utilitywarehouse/hearth-react-icons": "^0.8.0",
60
+ "@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
61
61
  "@utilitywarehouse/hearth-svg-assets": "^0.5.0",
62
62
  "@utilitywarehouse/hearth-tokens": "^0.2.3"
63
63
  },
@@ -109,6 +109,7 @@ The Modal component extends the `BottomSheetModal` component and accepts all of
109
109
  | `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
110
110
  | `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
111
111
  | `inNavModal` | `boolean` | Renders the modal correctly when used inside a navigation modal | `false` |
112
+ | `background` | `'default' \| 'brand'` | Sets the modal background. Only applies when `inNavModal` is `true` | `'default'` |
112
113
 
113
114
  \* use this to detect if the modal has been opened or closed, index 0 indicates open state and -1 indicates closed state
114
115
 
@@ -25,6 +25,7 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
25
25
  primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
26
26
  secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
27
27
  closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
28
+ background?: 'default' | 'brand';
28
29
  }
29
30
 
30
31
  export default ModalProps;
@@ -66,6 +66,7 @@ const meta = {
66
66
  onPressCloseButton: () => null,
67
67
  onPressPrimaryButton: () => null,
68
68
  onPressSecondaryButton: () => null,
69
+ background: 'brand',
69
70
  },
70
71
  } satisfies Meta<typeof Modal>;
71
72
 
@@ -7,7 +7,7 @@ import {
7
7
  import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
8
8
  import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
9
9
  import { useCallback, useEffect, useImperativeHandle, useRef } from 'react';
10
- import { AccessibilityInfo, Platform, View, findNodeHandle } from 'react-native';
10
+ import { AccessibilityInfo, Platform, ScrollView, View, findNodeHandle } from 'react-native';
11
11
  import Animated, {
12
12
  Easing,
13
13
  useAnimatedStyle,
@@ -50,6 +50,7 @@ const Modal = ({
50
50
  closeButtonProps,
51
51
  inNavModal = false,
52
52
  stickyFooter = true,
53
+ background = 'default',
53
54
  ...props
54
55
  }: ModalProps) => {
55
56
  const bottomSheetModalRef = useRef<BottomSheetModal>(null);
@@ -170,6 +171,7 @@ const Modal = ({
170
171
  noButtons,
171
172
  stickyFooter,
172
173
  showHandle: props.showHandle,
174
+ background: background === 'brand' ? 'brand' : 'primary',
173
175
  });
174
176
 
175
177
  const footer = (
@@ -178,6 +180,7 @@ const Modal = ({
178
180
  <Button
179
181
  onPress={handlePrimaryButtonPress}
180
182
  text={primaryButtonText}
183
+ inverted={background === 'brand' && inNavModal}
181
184
  {...primaryButtonProps}
182
185
  variant={(primaryButtonProps?.variant as 'solid') ?? 'solid'}
183
186
  colorScheme={(primaryButtonProps?.colorScheme as 'highlight') ?? 'highlight'}
@@ -187,6 +190,7 @@ const Modal = ({
187
190
  <Button
188
191
  onPress={handleSecondaryButtonPress}
189
192
  text={secondaryButtonText}
193
+ inverted={background === 'brand' && inNavModal}
190
194
  {...secondaryButtonProps}
191
195
  variant={(secondaryButtonProps?.variant as 'outline') ?? 'outline'}
192
196
  colorScheme={(secondaryButtonProps?.colorScheme as 'functional') ?? 'functional'}
@@ -232,6 +236,7 @@ const Modal = ({
232
236
  icon={CloseMediumIcon}
233
237
  onPress={handleCloseButtonPress}
234
238
  accessibilityLabel="Close modal"
239
+ inverted={background === 'brand' && inNavModal}
235
240
  {...closeButtonProps}
236
241
  />
237
242
  ) : null}
@@ -253,7 +258,7 @@ const Modal = ({
253
258
  </View>
254
259
  </View>
255
260
  ) : null}
256
- {children}
261
+ {inNavModal ? <ScrollView style={{ flex: 1 }}>{children}</ScrollView> : children}
257
262
  {(!stickyFooter || inNavModal) && !noButtons ? footer : null}
258
263
  </View>
259
264
  )}
@@ -277,7 +282,12 @@ const Modal = ({
277
282
  );
278
283
 
279
284
  return inNavModal ? (
280
- <View style={{ flex: 1, backgroundColor: theme.color.background.primary }}>
285
+ <View
286
+ style={{
287
+ flex: 1,
288
+ backgroundColor: theme.color.background[background === 'brand' ? 'brand' : 'primary'],
289
+ }}
290
+ >
281
291
  {Platform.OS === 'android' ? (
282
292
  <Animated.View style={[styles.androidContainer, animatedBackgroundStyle]}>
283
293
  <Animated.View style={[styles.pretendContent, animatedPretendContentStyle]} />
@@ -420,6 +430,14 @@ const styles = StyleSheet.create((theme, rt) => ({
420
430
  gap: theme.components.modal.gap,
421
431
  padding: theme.components.modal.padding,
422
432
  paddingBottom: theme.components.modal.padding + rt.insets.bottom,
433
+ variants: {
434
+ background: {
435
+ primary: {},
436
+ brand: {
437
+ backgroundColor: theme.color.background.brand,
438
+ },
439
+ },
440
+ },
423
441
  },
424
442
  androidContainer: {
425
443
  height: rt.insets.top + 18,