@utilitywarehouse/hearth-react-native 0.23.0-test-list → 0.24.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 (61) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/.turbo/turbo-lint.log +72 -0
  3. package/CHANGELOG.md +36 -0
  4. package/build/components/List/List.js +2 -2
  5. package/build/components/Modal/Modal.js +5 -4
  6. package/build/components/Modal/Modal.props.d.ts +10 -4
  7. package/build/components/index.d.ts +1 -0
  8. package/build/components/index.js +1 -0
  9. package/docs/components/AllComponents.web.tsx +6 -0
  10. package/package.json +2 -2
  11. package/src/components/List/List.tsx +4 -5
  12. package/src/components/Modal/Modal.props.ts +13 -4
  13. package/src/components/Modal/Modal.stories.tsx +1 -1
  14. package/src/components/Modal/Modal.tsx +28 -11
  15. package/src/components/ProgressBar/ProgressBar.docs.mdx +90 -0
  16. package/src/components/ProgressBar/ProgressBar.figma.tsx +79 -0
  17. package/src/components/ProgressBar/ProgressBar.props.ts +60 -0
  18. package/src/components/ProgressBar/ProgressBar.stories.tsx +117 -0
  19. package/src/components/ProgressBar/ProgressBar.tsx +74 -0
  20. package/src/components/ProgressBar/ProgressBarCircular.tsx +181 -0
  21. package/src/components/ProgressBar/ProgressBarLinear.tsx +127 -0
  22. package/src/components/ProgressBar/index.ts +7 -0
  23. package/src/components/index.ts +1 -0
  24. package/build/components/SegmentedControl/SegmentedControl.context.d.ts +0 -14
  25. package/build/components/SegmentedControl/SegmentedControl.context.js +0 -9
  26. package/build/components/SegmentedControl/SegmentedControl.d.ts +0 -6
  27. package/build/components/SegmentedControl/SegmentedControl.js +0 -199
  28. package/build/components/SegmentedControl/SegmentedControl.props.d.ts +0 -18
  29. package/build/components/SegmentedControl/SegmentedControl.props.js +0 -1
  30. package/build/components/SegmentedControl/SegmentedControlOption.d.ts +0 -18
  31. package/build/components/SegmentedControl/SegmentedControlOption.js +0 -144
  32. package/build/components/SegmentedControl/SegmentedControlOption.props.d.ts +0 -14
  33. package/build/components/SegmentedControl/SegmentedControlOption.props.js +0 -1
  34. package/build/components/SegmentedControl/index.d.ts +0 -4
  35. package/build/components/SegmentedControl/index.js +0 -2
  36. package/build/components/TimePicker/TimePicker.d.ts +0 -6
  37. package/build/components/TimePicker/TimePicker.js +0 -78
  38. package/build/components/TimePicker/TimePicker.props.d.ts +0 -45
  39. package/build/components/TimePicker/TimePicker.props.js +0 -1
  40. package/build/components/TimePicker/TimePickerView.d.ts +0 -12
  41. package/build/components/TimePicker/TimePickerView.js +0 -130
  42. package/build/components/TimePicker/TimePickerWheel.d.ts +0 -8
  43. package/build/components/TimePicker/TimePickerWheel.js +0 -86
  44. package/build/components/TimePicker/TimePickerWheel.web.d.ts +0 -8
  45. package/build/components/TimePicker/TimePickerWheel.web.js +0 -122
  46. package/build/components/TimePicker/index.d.ts +0 -6
  47. package/build/components/TimePicker/index.js +0 -3
  48. package/build/components/TimePickerInput/TimePickerInput.d.ts +0 -6
  49. package/build/components/TimePickerInput/TimePickerInput.js +0 -127
  50. package/build/components/TimePickerInput/TimePickerInput.props.d.ts +0 -52
  51. package/build/components/TimePickerInput/TimePickerInput.props.js +0 -1
  52. package/build/components/TimePickerInput/TimePickerInputDoneButton.d.ts +0 -8
  53. package/build/components/TimePickerInput/TimePickerInputDoneButton.js +0 -19
  54. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.d.ts +0 -5
  55. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.js +0 -5
  56. package/build/components/TimePickerInput/index.d.ts +0 -2
  57. package/build/components/TimePickerInput/index.js +0 -1
  58. package/build/components/VerificationInput/VerificationInput.utils.d.ts +0 -8
  59. package/build/components/VerificationInput/VerificationInput.utils.js +0 -17
  60. package/build/components/VerificationInput/VerificationInput.utils.test.d.ts +0 -1
  61. package/build/components/VerificationInput/VerificationInput.utils.test.js +0 -36
@@ -1,5 +1,4 @@
1
-
2
- 
3
- > @utilitywarehouse/hearth-react-native@0.23.0-test-list build /Users/filmondaniels/Projects/Work/hearth/packages/react-native
4
- > tsc
5
-
1
+
2
+ > @utilitywarehouse/hearth-react-native@0.24.0 build /home/runner/work/hearth/hearth/packages/react-native
3
+ > tsc
4
+
@@ -0,0 +1,72 @@
1
+
2
+ > @utilitywarehouse/hearth-react-native@0.24.0 lint /home/runner/work/hearth/hearth/packages/react-native
3
+ > TIMING=1 eslint .
4
+
5
+
6
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Carousel/Carousel.context.tsx
7
+ 6:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
8
+
9
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Carousel/Carousel.tsx
10
+ 146:6 warning React Hook useMemo has a missing dependency: 'hasCarouselControlsInTree'. Either include it or remove the dependency array react-hooks/exhaustive-deps
11
+
12
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/DatePicker/DatePicker.tsx
13
+ 109:6 warning React Hook useCallback has an unnecessary dependency: 'modalRef.current'. Either exclude it or remove the dependency array. Mutable values like 'modalRef.current' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
14
+ 259:6 warning React Hook useEffect has a missing dependency: 'initialState'. Either include it or remove the dependency array react-hooks/exhaustive-deps
15
+ 346:6 warning React Hook useEffect has a missing dependency: 'onChange'. Either include it or remove the dependency array react-hooks/exhaustive-deps
16
+ 468:5 warning React Hook useCallback has a missing dependency: 'onChange'. Either include it or remove the dependency array react-hooks/exhaustive-deps
17
+ 536:6 warning React Hook useEffect has a missing dependency: 'onSelectMonth'. Either include it or remove the dependency array react-hooks/exhaustive-deps
18
+ 542:6 warning React Hook useEffect has a missing dependency: 'onSelectYear'. Either include it or remove the dependency array react-hooks/exhaustive-deps
19
+
20
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/DatePicker/DatePickerDay.tsx
21
+ 76:6 warning React Hook useMemo has an unnecessary dependency: 'styles.rangeRoot'. Either exclude it or remove the dependency array. Outer scope values like 'styles.rangeRoot' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
22
+ 84:6 warning React Hook useMemo has a missing dependency: 'isSelected'. Either include it or remove the dependency array react-hooks/exhaustive-deps
23
+
24
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/DatePicker/DatePickerDays.tsx
25
+ 179:6 warning React Hook useMemo has unnecessary dependencies: 'month' and 'year'. Either exclude them or remove the dependency array react-hooks/exhaustive-deps
26
+
27
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/DatePicker/DatePickerYears.tsx
28
+ 52:6 warning React Hook useCallback has a missing dependency: 'containerHeight'. Either include it or remove the dependency array. Outer scope values like 'styles' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
29
+
30
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Input/Input.tsx
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
+
33
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.tsx
34
+ 75: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
+ 291:5 warning React Hook useCallback has a missing dependency: 'footer'. Either include it or remove the dependency array react-hooks/exhaustive-deps
36
+
37
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.web.tsx
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
39
+
40
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/PillGroup/PillGroup.tsx
41
+ 17:9 warning The 'normalizedValue' conditional could make the dependencies of useMemo Hook (at line 33) change on every render. Move it inside the useMemo callback. Alternatively, wrap the initialization of 'normalizedValue' in its own useMemo() Hook react-hooks/exhaustive-deps
42
+
43
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Tabs/Tabs.tsx
44
+ 53:6 warning React Hook useEffect has a missing dependency: 'tabValues'. Either include it or remove the dependency array react-hooks/exhaustive-deps
45
+ 53:7 warning React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
46
+ 104:5 warning React Hook useMemo has an unnecessary dependency: 'tabValues'. Either exclude it or remove the dependency array react-hooks/exhaustive-deps
47
+ 127:62 warning React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
48
+
49
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Textarea/Textarea.tsx
50
+ 45:6 warning React Hook useEffect has a missing dependency: 'formFieldContext'. Either include it or remove the dependency array react-hooks/exhaustive-deps
51
+
52
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/Toast/Toast.context.tsx
53
+ 14:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
54
+ 106:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
55
+
56
+ /home/runner/work/hearth/hearth/packages/react-native/src/components/VerificationInput/VerificationInput.tsx
57
+ 174:7 warning React Hook useImperativeHandle has a missing dependency: 'updateValue'. Either include it or remove the dependency array react-hooks/exhaustive-deps
58
+
59
+ ✖ 25 problems (0 errors, 25 warnings)
60
+
61
+ Rule | Time (ms) | Relative
62
+ :-----------------------------------------|----------:|--------:
63
+ @typescript-eslint/no-unused-vars | 1400.036 | 60.2%
64
+ react-hooks/exhaustive-deps | 100.065 | 4.3%
65
+ react-hooks/rules-of-hooks | 83.204 | 3.6%
66
+ no-global-assign | 71.807 | 3.1%
67
+ @typescript-eslint/ban-ts-comment | 53.514 | 2.3%
68
+ no-loss-of-precision | 35.722 | 1.5%
69
+ @typescript-eslint/triple-slash-reference | 35.355 | 1.5%
70
+ no-unexpected-multiline | 32.651 | 1.4%
71
+ no-useless-escape | 30.776 | 1.3%
72
+ no-misleading-character-class | 29.528 | 1.3%
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.24.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#977](https://github.com/utilitywarehouse/hearth/pull/977) [`9d2b534`](https://github.com/utilitywarehouse/hearth/commit/9d2b5348a5748cb613f537808069de2e86bd21d7) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `ProgressBar` component with linear and circular variants.
8
+
9
+ **Developer changes**:
10
+
11
+ Use `ProgressBar` with a default percentage label, or override the label to show a custom value:
12
+
13
+ ```tsx
14
+ import { ProgressBar } from '@utilitywarehouse/hearth-react-native';
15
+
16
+ <ProgressBar value={42} label="Uploading documents" />
17
+
18
+ <ProgressBar
19
+ value={68}
20
+ max={100}
21
+ label="Data allowance"
22
+ variant="circular"
23
+ formatValueText={(value, { max }) => `${max - value}GB remaining`}
24
+ />
25
+ ```
26
+
27
+ ### Patch Changes
28
+
29
+ - [#978](https://github.com/utilitywarehouse/hearth/pull/978) [`26a1173`](https://github.com/utilitywarehouse/hearth/commit/26a11731a493a8b92ac2a3a183516376ab54663b) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Tighten `Modal` prop types and fix brand background text styling
30
+
31
+ Improves TypeScript safety so `stickyFooter` is not allowed when `inNavModal` is true, and `background` can only be set when `inNavModal` is true. Also ensures headings, body text, and button content are correctly inverted when using the brand background.
32
+
33
+ **Components affected**:
34
+ - `Modal`
35
+
36
+ **Developer changes**:
37
+ No changes required unless you were relying on invalid prop combinations.
38
+
3
39
  ## 0.23.0
4
40
 
5
41
  ### Minor Changes
@@ -6,7 +6,7 @@ import { Card } from '../Card';
6
6
  import { SectionHeader } from '../SectionHeader';
7
7
  import { ListContext } from './List.context';
8
8
  const List = ({ children, heading, helperText, headerTrailingContent, invalidText, ...props }) => {
9
- const { loading, disabled, container = 'none', testID, style, ...rest } = props;
9
+ const { loading, disabled, container = 'none' } = props;
10
10
  const orderRef = useRef([]);
11
11
  const [firstItemId, setFirstItemId] = useState(undefined);
12
12
  const containerToCard = {
@@ -35,7 +35,7 @@ const List = ({ children, heading, helperText, headerTrailingContent, invalidTex
35
35
  registerItem,
36
36
  };
37
37
  styles.useVariants({ disabled });
38
- return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...rest, style: [styles.container, style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent, invalidText: invalidText })) : null, container === 'none' ? (_jsx(View, { testID: testID, children: children })) : (React.Children.count(children) > 0 && (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, testID: testID, children: _jsx(_Fragment, { children: children }) })))] }) }));
38
+ return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...props, style: [styles.container, props.style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent, invalidText: invalidText })) : null, container === 'none' ? (_jsx(View, { children: children })) : (React.Children.count(children) > 0 && (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, children: _jsx(_Fragment, { children: children }) })))] }) }));
39
39
  };
40
40
  List.displayName = 'List';
41
41
  const styles = StyleSheet.create(theme => ({
@@ -20,6 +20,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
20
20
  const theme = useTheme();
21
21
  const backgroundOpacity = useSharedValue(0);
22
22
  const pretendContentTranslateY = useSharedValue(20);
23
+ const isBrandBackground = background === 'brand';
23
24
  const triggerCloseAnimation = useCallback(() => {
24
25
  if (Platform.OS === 'android' && inNavModal) {
25
26
  pretendContentTranslateY.value = withTiming(20, {
@@ -105,10 +106,10 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
105
106
  noButtons,
106
107
  stickyFooter,
107
108
  showHandle: props.showHandle,
108
- background: background === 'brand' ? 'brand' : 'primary',
109
+ background: isBrandBackground ? 'brand' : 'primary',
109
110
  });
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
+ const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: isBrandBackground && inNavModal, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: isBrandBackground && inNavModal, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
112
+ 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", color: isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground && inNavModal, 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, inverted: isBrandBackground && inNavModal, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground && 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, inverted: isBrandBackground && inNavModal, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] })] })) : null, inNavModal ? _jsx(ScrollView, { style: { flex: 1 }, children: children }) : children, (!stickyFooter || inNavModal) && !noButtons ? footer : null] })) }));
112
113
  const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
113
114
  onPressPrimaryButton,
114
115
  primaryButtonText,
@@ -119,7 +120,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
119
120
  ]);
120
121
  return inNavModal ? (_jsxs(View, { style: {
121
122
  flex: 1,
122
- backgroundColor: theme.color.background[background === 'brand' ? 'brand' : 'primary'],
123
+ backgroundColor: theme.color.background[isBrandBackground ? 'brand' : 'primary'],
123
124
  }, 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 })] }));
124
125
  };
125
126
  const styles = StyleSheet.create((theme, rt) => ({
@@ -3,16 +3,14 @@ import { ViewProps } from 'react-native';
3
3
  import { BottomSheetProps } from '../BottomSheet';
4
4
  import { ButtonWithoutChildrenProps } from '../Button/Button.props';
5
5
  import { UnstyledIconButtonProps } from '../UnstyledIconButton';
6
- interface ModalProps extends Omit<BottomSheetProps, 'children'> {
6
+ interface ModalPropsBase extends Omit<BottomSheetProps, 'children'> {
7
7
  loading?: boolean;
8
8
  image?: ReactNode;
9
9
  showCloseButton?: boolean;
10
10
  heading?: string;
11
11
  loadingHeading?: string;
12
12
  description?: string;
13
- inNavModal?: boolean;
14
13
  fullscreen?: boolean;
15
- stickyFooter?: boolean;
16
14
  children?: ViewProps['children'];
17
15
  onPressPrimaryButton?: () => void;
18
16
  primaryButtonText?: string;
@@ -24,6 +22,14 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
24
22
  primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
25
23
  secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
26
24
  closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
27
- background?: 'default' | 'brand';
28
25
  }
26
+ type ModalProps = (ModalPropsBase & {
27
+ inNavModal?: false | undefined;
28
+ stickyFooter?: boolean;
29
+ background?: never;
30
+ }) | (ModalPropsBase & {
31
+ inNavModal: true;
32
+ stickyFooter?: never;
33
+ background?: 'default' | 'brand';
34
+ });
29
35
  export default ModalProps;
@@ -40,6 +40,7 @@ export * from './List';
40
40
  export * from './Menu';
41
41
  export * from './Modal';
42
42
  export * from './PillGroup';
43
+ export * from './ProgressBar';
43
44
  export * from './ProgressStepper';
44
45
  export * from './Radio';
45
46
  export * from './RadioCard';
@@ -41,6 +41,7 @@ export * from './List';
41
41
  export * from './Menu';
42
42
  export * from './Modal';
43
43
  export * from './PillGroup';
44
+ export * from './ProgressBar';
44
45
  export * from './ProgressStepper';
45
46
  export * from './Radio';
46
47
  export * from './RadioCard';
@@ -74,6 +74,7 @@ import {
74
74
  OL,
75
75
  Pill,
76
76
  PillGroup,
77
+ ProgressBar,
77
78
  ProgressStep,
78
79
  ProgressStepper,
79
80
  Radio,
@@ -663,6 +664,11 @@ const AllComponents: React.FC = () => {
663
664
  </PillGroup>
664
665
  </Center>
665
666
  </ComponentWrapper>
667
+ <ComponentWrapper name="Progress Bar" link="/?path=/docs/components-progress-bar--docs">
668
+ <Center flex={1} px="300">
669
+ <ProgressBar value={58} label="Order progress" />
670
+ </Center>
671
+ </ComponentWrapper>
666
672
  <ComponentWrapper
667
673
  name="Progress Stepper"
668
674
  link="/?path=/docs/components-progress-stepper--docs"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.23.0-test-list",
3
+ "version": "0.24.0",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,9 +55,9 @@
55
55
  "vite": "^7.1.3",
56
56
  "vite-plugin-svgr": "^4.5.0",
57
57
  "vitest": "^3.2.4",
58
- "@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
59
58
  "@utilitywarehouse/hearth-fonts": "^0.0.4",
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
  },
@@ -14,8 +14,7 @@ const List = ({
14
14
  invalidText,
15
15
  ...props
16
16
  }: ListProps) => {
17
- const { loading, disabled, container = 'none', testID, style, ...rest } = props;
18
-
17
+ const { loading, disabled, container = 'none' } = props;
19
18
  const orderRef = useRef<string[]>([]);
20
19
  const [firstItemId, setFirstItemId] = useState<string | undefined>(undefined);
21
20
  const containerToCard: {
@@ -52,7 +51,7 @@ const List = ({
52
51
  styles.useVariants({ disabled });
53
52
  return (
54
53
  <ListContext.Provider value={value}>
55
- <View {...rest} style={[styles.container, style]}>
54
+ <View {...props} style={[styles.container, props.style]}>
56
55
  {heading ? (
57
56
  <SectionHeader
58
57
  heading={heading}
@@ -62,10 +61,10 @@ const List = ({
62
61
  />
63
62
  ) : null}
64
63
  {container === 'none' ? (
65
- <View testID={testID}>{children}</View>
64
+ <View>{children}</View>
66
65
  ) : (
67
66
  React.Children.count(children) > 0 && (
68
- <Card {...containerToCard} noPadding style={styles.card} testID={testID}>
67
+ <Card {...containerToCard} noPadding style={styles.card}>
69
68
  <>{children}</>
70
69
  </Card>
71
70
  )
@@ -4,16 +4,14 @@ import { BottomSheetProps } from '../BottomSheet';
4
4
  import { ButtonWithoutChildrenProps } from '../Button/Button.props';
5
5
  import { UnstyledIconButtonProps } from '../UnstyledIconButton';
6
6
 
7
- interface ModalProps extends Omit<BottomSheetProps, 'children'> {
7
+ interface ModalPropsBase extends Omit<BottomSheetProps, 'children'> {
8
8
  loading?: boolean;
9
9
  image?: ReactNode;
10
10
  showCloseButton?: boolean;
11
11
  heading?: string;
12
12
  loadingHeading?: string;
13
13
  description?: string;
14
- inNavModal?: boolean;
15
14
  fullscreen?: boolean;
16
- stickyFooter?: boolean;
17
15
  children?: ViewProps['children'];
18
16
  onPressPrimaryButton?: () => void;
19
17
  primaryButtonText?: string;
@@ -25,7 +23,18 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
25
23
  primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
26
24
  secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
27
25
  closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
28
- background?: 'default' | 'brand';
29
26
  }
30
27
 
28
+ type ModalProps =
29
+ | (ModalPropsBase & {
30
+ inNavModal?: false | undefined;
31
+ stickyFooter?: boolean;
32
+ background?: never;
33
+ })
34
+ | (ModalPropsBase & {
35
+ inNavModal: true;
36
+ stickyFooter?: never;
37
+ background?: 'default' | 'brand';
38
+ });
39
+
31
40
  export default ModalProps;
@@ -1,4 +1,4 @@
1
- import { Meta, StoryObj } from '@storybook/react-vite';
1
+ import { Meta, StoryObj } from '@storybook/react-native';
2
2
  import { useRef } from 'react';
3
3
  import { ImageSourcePropType, Platform, View } from 'react-native';
4
4
  import { Modal, ModalImage } from '.';
@@ -59,6 +59,7 @@ const Modal = ({
59
59
  const theme = useTheme();
60
60
  const backgroundOpacity = useSharedValue(0);
61
61
  const pretendContentTranslateY = useSharedValue(20);
62
+ const isBrandBackground = background === 'brand';
62
63
 
63
64
  const triggerCloseAnimation = useCallback(() => {
64
65
  if (Platform.OS === 'android' && inNavModal) {
@@ -171,7 +172,7 @@ const Modal = ({
171
172
  noButtons,
172
173
  stickyFooter,
173
174
  showHandle: props.showHandle,
174
- background: background === 'brand' ? 'brand' : 'primary',
175
+ background: isBrandBackground ? 'brand' : 'primary',
175
176
  });
176
177
 
177
178
  const footer = (
@@ -180,7 +181,7 @@ const Modal = ({
180
181
  <Button
181
182
  onPress={handlePrimaryButtonPress}
182
183
  text={primaryButtonText}
183
- inverted={background === 'brand' && inNavModal}
184
+ inverted={isBrandBackground && inNavModal}
184
185
  {...primaryButtonProps}
185
186
  variant={(primaryButtonProps?.variant as 'solid') ?? 'solid'}
186
187
  colorScheme={(primaryButtonProps?.colorScheme as 'highlight') ?? 'highlight'}
@@ -190,7 +191,7 @@ const Modal = ({
190
191
  <Button
191
192
  onPress={handleSecondaryButtonPress}
192
193
  text={secondaryButtonText}
193
- inverted={background === 'brand' && inNavModal}
194
+ inverted={isBrandBackground && inNavModal}
194
195
  {...secondaryButtonProps}
195
196
  variant={(secondaryButtonProps?.variant as 'outline') ?? 'outline'}
196
197
  colorScheme={(secondaryButtonProps?.colorScheme as 'functional') ?? 'functional'}
@@ -209,8 +210,11 @@ const Modal = ({
209
210
  screenReaderFocusable
210
211
  ref={viewRef}
211
212
  >
212
- <Spinner size="lg" />
213
- <Heading size="lg" textAlign="center">
213
+ <Spinner
214
+ size="lg"
215
+ color={isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined}
216
+ />
217
+ <Heading size="lg" textAlign="center" inverted={isBrandBackground && inNavModal}>
214
218
  {loadingHeading}
215
219
  </Heading>
216
220
  </View>
@@ -225,18 +229,22 @@ const Modal = ({
225
229
  <View style={styles.header}>
226
230
  <View style={styles.headerTextContent}>
227
231
  {heading && !image ? (
228
- <Heading size="lg" accessible>
232
+ <Heading size="lg" accessible inverted={isBrandBackground && inNavModal}>
229
233
  {heading}
230
234
  </Heading>
231
235
  ) : null}
232
- {description && !image ? <BodyText accessible>{description}</BodyText> : null}
236
+ {description && !image ? (
237
+ <BodyText accessible inverted={isBrandBackground && inNavModal}>
238
+ {description}
239
+ </BodyText>
240
+ ) : null}
233
241
  </View>
234
242
  {showCloseButton ? (
235
243
  <UnstyledIconButton
236
244
  icon={CloseMediumIcon}
237
245
  onPress={handleCloseButtonPress}
238
246
  accessibilityLabel="Close modal"
239
- inverted={background === 'brand' && inNavModal}
247
+ inverted={isBrandBackground && inNavModal}
240
248
  {...closeButtonProps}
241
249
  />
242
250
  ) : null}
@@ -246,12 +254,21 @@ const Modal = ({
246
254
  {image}
247
255
  <View style={styles.textContent}>
248
256
  {heading ? (
249
- <Heading size="lg" textAlign="center" accessible>
257
+ <Heading
258
+ size="lg"
259
+ textAlign="center"
260
+ accessible
261
+ inverted={isBrandBackground && inNavModal}
262
+ >
250
263
  {heading}
251
264
  </Heading>
252
265
  ) : null}
253
266
  {description ? (
254
- <BodyText textAlign="center" accessible>
267
+ <BodyText
268
+ textAlign="center"
269
+ accessible
270
+ inverted={isBrandBackground && inNavModal}
271
+ >
255
272
  {description}
256
273
  </BodyText>
257
274
  ) : null}
@@ -285,7 +302,7 @@ const Modal = ({
285
302
  <View
286
303
  style={{
287
304
  flex: 1,
288
- backgroundColor: theme.color.background[background === 'brand' ? 'brand' : 'primary'],
305
+ backgroundColor: theme.color.background[isBrandBackground ? 'brand' : 'primary'],
289
306
  }}
290
307
  >
291
308
  {Platform.OS === 'android' ? (
@@ -0,0 +1,90 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { Box, Center, ProgressBar } from '../..';
3
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
4
+ import * as Stories from './ProgressBar.stories';
5
+
6
+ <Meta title="Components / Progress Bar" />
7
+
8
+ <BackToTopButton />
9
+
10
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=7849-5704&t=Jg2fPJPQNzOyspmQ-4" />
11
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=7863-3977&t=Jg2fPJPQNzOyspmQ-4" />
12
+
13
+ # Progress Bar
14
+
15
+ Progress bars communicate task completion for linear flows and bounded operations. Use the linear variant for
16
+ inline layouts and the circular variant when space is tighter or when progress needs more emphasis.
17
+
18
+ - [Playground](#playground)
19
+ - [Usage](#usage)
20
+ - [Props](#props)
21
+ - [Variants](#variants)
22
+ - [Circular Sizes](#circular-sizes)
23
+ - [Examples](#examples)
24
+ - [Custom Value Labels](#custom-value-labels)
25
+
26
+ ## Playground
27
+
28
+ <Canvas of={Stories.Playground} />
29
+
30
+ <Controls of={Stories.Playground} />
31
+
32
+ ## Usage
33
+
34
+ <UsageWrap>
35
+ <Center>
36
+ <Box style={{ width: 260 }}>
37
+ <ProgressBar value={42} label="Uploading documents" />
38
+ </Box>
39
+ </Center>
40
+ </UsageWrap>
41
+
42
+ ```tsx
43
+ import { ProgressBar } from '@utilitywarehouse/hearth-react-native';
44
+
45
+ const MyComponent = () => <ProgressBar value={42} label="Uploading documents" />;
46
+ ```
47
+
48
+ ## Props
49
+
50
+ | Property | Type | Description | Default |
51
+ | ----------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------- |
52
+ | `variant` | `'linear' \| 'circular'` | The progress bar variant. | `linear` |
53
+ | `colorScheme` | `'default' \| 'success' \| 'danger'` | The color scheme for the progress indicator. | `default` |
54
+ | `size` | `'sm' \| 'md'` | Circular size. Only applies to the circular variant. | `md` |
55
+ | `value` | `number` | Current progress value. | |
56
+ | `min` | `number` | Minimum value. | `0` |
57
+ | `max` | `number` | Maximum value. | `100` |
58
+ | `label` | `string` | Accessible label for the progress bar. | |
59
+ | `hideLabel` | `boolean` | Visually hide the label and value text. | `false` |
60
+ | `formatValueText` | `(value: number, meta: { min: number; `<br />` max: number; percent: number }) => string` | Override the default percentage label. | |
61
+ | `aria-valuetext` | `string` | A human-readable text alternative for the current value. | |
62
+
63
+ ## Variants
64
+
65
+ <Canvas of={Stories.Variants} />
66
+
67
+ ## Circular Sizes
68
+
69
+ <Canvas of={Stories.CircularSizes} />
70
+
71
+ ## Examples
72
+
73
+ ### Custom Value Labels
74
+
75
+ Use `formatValueText` to show values that are not percentages, such as remaining data allowance.
76
+
77
+ <Canvas of={Stories.CustomValueLabels} />
78
+
79
+ ```tsx
80
+ import { ProgressBar } from '@utilitywarehouse/hearth-react-native';
81
+
82
+ const MyComponent = () => (
83
+ <ProgressBar
84
+ value={68}
85
+ max={100}
86
+ label="Data allowance"
87
+ formatValueText={(value, { max }) => `${max - value}GB remaining`}
88
+ />
89
+ );
90
+ ```
@@ -0,0 +1,79 @@
1
+ import figma from '@figma/code-connect';
2
+ import { ProgressBar } from '../';
3
+
4
+ figma.connect(
5
+ ProgressBar,
6
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=7849-5704',
7
+ {
8
+ props: {
9
+ value: figma.enum('Progress', {
10
+ '0%': 0,
11
+ '10%': 10,
12
+ '20%': 20,
13
+ '30%': 30,
14
+ '40%': 40,
15
+ '50%': 50,
16
+ '60%': 60,
17
+ '70%': 70,
18
+ '80%': 80,
19
+ '90%': 90,
20
+ '100%': 100,
21
+ }),
22
+ colorScheme: figma.enum('Color Scheme', {
23
+ Default: 'default',
24
+ Success: 'success',
25
+ Danger: 'danger',
26
+ }),
27
+ label: figma.boolean('Label?', { true: figma.string('Label') }),
28
+ },
29
+ example: props => (
30
+ <ProgressBar
31
+ variant="linear"
32
+ value={props.value}
33
+ colorScheme={props.colorScheme}
34
+ label={props.label}
35
+ />
36
+ ),
37
+ }
38
+ );
39
+
40
+ figma.connect(
41
+ ProgressBar,
42
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=7863-3977',
43
+ {
44
+ props: {
45
+ value: figma.enum('Progress', {
46
+ '0%': 0,
47
+ '10%': 10,
48
+ '20%': 20,
49
+ '30%': 30,
50
+ '40%': 40,
51
+ '50%': 50,
52
+ '60%': 60,
53
+ '70%': 70,
54
+ '80%': 80,
55
+ '90%': 90,
56
+ '100%': 100,
57
+ }),
58
+ colorScheme: figma.enum('Color Scheme', {
59
+ Default: 'default',
60
+ Success: 'success',
61
+ Danger: 'danger',
62
+ }),
63
+ size: figma.enum('Size', {
64
+ 'SM-80': 'sm',
65
+ 'MD-140': 'md',
66
+ }),
67
+ label: figma.boolean('Label?', { true: figma.string('Label') }),
68
+ },
69
+ example: props => (
70
+ <ProgressBar
71
+ variant="circular"
72
+ value={props.value}
73
+ colorScheme={props.colorScheme}
74
+ size={props.size}
75
+ label={props.label}
76
+ />
77
+ ),
78
+ }
79
+ );