@utilitywarehouse/hearth-react-native 0.23.0 → 0.25.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 (107) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +13 -13
  3. package/CHANGELOG.md +77 -0
  4. package/build/components/DatePicker/DatePickerCalendar.js +4 -9
  5. package/build/components/Modal/Modal.js +5 -4
  6. package/build/components/Modal/Modal.props.d.ts +10 -4
  7. package/build/components/ProgressBar/ProgressBar.d.ts +6 -0
  8. package/build/components/ProgressBar/ProgressBar.js +35 -0
  9. package/build/components/ProgressBar/ProgressBar.props.d.ts +60 -0
  10. package/build/components/ProgressBar/ProgressBar.props.js +1 -0
  11. package/build/components/ProgressBar/ProgressBarCircular.d.ts +6 -0
  12. package/build/components/ProgressBar/ProgressBarCircular.js +115 -0
  13. package/build/components/ProgressBar/ProgressBarLinear.d.ts +6 -0
  14. package/build/components/ProgressBar/ProgressBarLinear.js +79 -0
  15. package/build/components/ProgressBar/index.d.ts +2 -0
  16. package/build/components/ProgressBar/index.js +1 -0
  17. package/build/components/TimePicker/TimePicker.d.ts +6 -0
  18. package/build/components/TimePicker/TimePicker.js +78 -0
  19. package/build/components/TimePicker/TimePicker.props.d.ts +45 -0
  20. package/build/components/TimePicker/TimePicker.props.js +1 -0
  21. package/build/components/TimePicker/TimePickerView.d.ts +12 -0
  22. package/build/components/TimePicker/TimePickerView.js +130 -0
  23. package/build/components/TimePicker/TimePickerWheel.d.ts +8 -0
  24. package/build/components/TimePicker/TimePickerWheel.js +78 -0
  25. package/build/components/{DatePicker/time-picker/wheel-web.d.ts → TimePicker/TimePickerWheel.web.d.ts} +4 -4
  26. package/build/components/TimePicker/TimePickerWheel.web.js +122 -0
  27. package/build/components/TimePicker/index.d.ts +6 -0
  28. package/build/components/TimePicker/index.js +3 -0
  29. package/build/components/TimePickerInput/TimePickerInput.d.ts +6 -0
  30. package/build/components/TimePickerInput/TimePickerInput.js +127 -0
  31. package/build/components/TimePickerInput/TimePickerInput.props.d.ts +52 -0
  32. package/build/components/TimePickerInput/TimePickerInput.props.js +1 -0
  33. package/build/components/TimePickerInput/TimePickerInputDoneButton.d.ts +8 -0
  34. package/build/components/TimePickerInput/TimePickerInputDoneButton.js +19 -0
  35. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.d.ts +5 -0
  36. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.js +5 -0
  37. package/build/components/TimePickerInput/index.d.ts +2 -0
  38. package/build/components/TimePickerInput/index.js +1 -0
  39. package/build/components/index.d.ts +3 -0
  40. package/build/components/index.js +3 -0
  41. package/docs/components/AllComponents.web.tsx +36 -0
  42. package/package.json +2 -1
  43. package/src/components/DatePicker/DatePickerCalendar.tsx +30 -13
  44. package/src/components/Modal/Modal.props.ts +13 -4
  45. package/src/components/Modal/Modal.stories.tsx +1 -1
  46. package/src/components/Modal/Modal.tsx +28 -11
  47. package/src/components/ProgressBar/ProgressBar.docs.mdx +90 -0
  48. package/src/components/ProgressBar/ProgressBar.figma.tsx +79 -0
  49. package/src/components/ProgressBar/ProgressBar.props.ts +60 -0
  50. package/src/components/ProgressBar/ProgressBar.stories.tsx +117 -0
  51. package/src/components/ProgressBar/ProgressBar.tsx +74 -0
  52. package/src/components/ProgressBar/ProgressBarCircular.tsx +181 -0
  53. package/src/components/ProgressBar/ProgressBarLinear.tsx +127 -0
  54. package/src/components/ProgressBar/index.ts +7 -0
  55. package/src/components/TimePicker/TimePicker.docs.mdx +84 -0
  56. package/src/components/TimePicker/TimePicker.figma.tsx +29 -0
  57. package/src/components/TimePicker/TimePicker.props.ts +45 -0
  58. package/src/components/TimePicker/TimePicker.stories.tsx +85 -0
  59. package/src/components/TimePicker/TimePicker.tsx +150 -0
  60. package/src/components/TimePicker/TimePickerView.tsx +216 -0
  61. package/src/components/TimePicker/TimePickerWheel.tsx +154 -0
  62. package/src/components/TimePicker/TimePickerWheel.web.tsx +217 -0
  63. package/src/components/TimePicker/index.ts +8 -0
  64. package/src/components/TimePickerInput/TimePickerInput.docs.mdx +135 -0
  65. package/src/components/TimePickerInput/TimePickerInput.figma.tsx +34 -0
  66. package/src/components/TimePickerInput/TimePickerInput.props.ts +55 -0
  67. package/src/components/TimePickerInput/TimePickerInput.stories.tsx +175 -0
  68. package/src/components/TimePickerInput/TimePickerInput.tsx +283 -0
  69. package/src/components/TimePickerInput/TimePickerInputDoneButton.tsx +42 -0
  70. package/src/components/TimePickerInput/TimePickerInputDoneButton.web.tsx +7 -0
  71. package/src/components/TimePickerInput/index.ts +2 -0
  72. package/src/components/index.ts +3 -0
  73. package/build/components/DatePicker/TimePicker.d.ts +0 -3
  74. package/build/components/DatePicker/TimePicker.js +0 -84
  75. package/build/components/DatePicker/time-picker/animated-math.d.ts +0 -4
  76. package/build/components/DatePicker/time-picker/animated-math.js +0 -19
  77. package/build/components/DatePicker/time-picker/period-native.d.ts +0 -6
  78. package/build/components/DatePicker/time-picker/period-native.js +0 -17
  79. package/build/components/DatePicker/time-picker/period-picker.d.ts +0 -6
  80. package/build/components/DatePicker/time-picker/period-picker.js +0 -10
  81. package/build/components/DatePicker/time-picker/period-web.d.ts +0 -6
  82. package/build/components/DatePicker/time-picker/period-web.js +0 -21
  83. package/build/components/DatePicker/time-picker/wheel-native.d.ts +0 -8
  84. package/build/components/DatePicker/time-picker/wheel-native.js +0 -19
  85. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +0 -2
  86. package/build/components/DatePicker/time-picker/wheel-picker/index.js +0 -2
  87. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +0 -16
  88. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +0 -97
  89. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +0 -21
  90. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +0 -88
  91. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +0 -23
  92. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +0 -21
  93. package/build/components/DatePicker/time-picker/wheel-web.js +0 -146
  94. package/build/components/DatePicker/time-picker/wheel.d.ts +0 -8
  95. package/build/components/DatePicker/time-picker/wheel.js +0 -10
  96. package/src/components/DatePicker/TimePicker.tsx +0 -141
  97. package/src/components/DatePicker/time-picker/animated-math.ts +0 -33
  98. package/src/components/DatePicker/time-picker/period-native.tsx +0 -34
  99. package/src/components/DatePicker/time-picker/period-picker.tsx +0 -16
  100. package/src/components/DatePicker/time-picker/period-web.tsx +0 -36
  101. package/src/components/DatePicker/time-picker/wheel-native.tsx +0 -37
  102. package/src/components/DatePicker/time-picker/wheel-picker/index.ts +0 -3
  103. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.tsx +0 -132
  104. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.ts +0 -22
  105. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.tsx +0 -200
  106. package/src/components/DatePicker/time-picker/wheel-web.tsx +0 -180
  107. package/src/components/DatePicker/time-picker/wheel.tsx +0 -18
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.23.0 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.25.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.23.0 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.25.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
- 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
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
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 | 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%
63
+ @typescript-eslint/no-unused-vars | 1535.077 | 58.6%
64
+ react-hooks/exhaustive-deps | 186.011 | 7.1%
65
+ no-global-assign | 92.645 | 3.5%
66
+ react-hooks/rules-of-hooks | 90.323 | 3.4%
67
+ no-misleading-character-class | 64.601 | 2.5%
68
+ no-unexpected-multiline | 47.391 | 1.8%
69
+ @typescript-eslint/ban-ts-comment | 39.233 | 1.5%
70
+ @typescript-eslint/triple-slash-reference | 36.020 | 1.4%
71
+ no-useless-escape | 31.051 | 1.2%
72
+ @typescript-eslint/no-unused-expressions | 28.768 | 1.1%
package/CHANGELOG.md CHANGED
@@ -1,5 +1,82 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.25.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#982](https://github.com/utilitywarehouse/hearth/pull/982) [`506b388`](https://github.com/utilitywarehouse/hearth/commit/506b388ae1ef1065f013024a14bd9e2599a6442d) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `TimePicker` and `TimePickerInput` components with 12/24-hour support and minute intervals.
8
+
9
+ Includes a shared time picker view, updated wheel behavior for native platforms, and polished visuals like gradient fades.
10
+
11
+ **Components affected**:
12
+ - `TimePicker`
13
+ - `TimePickerInput`
14
+
15
+ ```tsx
16
+ import { TimePicker, TimePickerInput } from '@utilitywarehouse/hearth-react-native';
17
+ import { useRef, useState } from 'react';
18
+ import type { DateType } from '@utilitywarehouse/hearth-react-native';
19
+
20
+ const Example = () => {
21
+ const [value, setValue] = useState<DateType>();
22
+ const pickerRef = useRef(null);
23
+
24
+ return (
25
+ <>
26
+ <TimePickerInput
27
+ value={value}
28
+ onChange={({ date }) => setValue(date ?? undefined)}
29
+ onClear={() => setValue(undefined)}
30
+ timePickerProps={{ use12Hours: true, minuteInterval: 5 }}
31
+ />
32
+ <TimePicker
33
+ ref={pickerRef}
34
+ date={value}
35
+ onChange={({ date }) => setValue(date)}
36
+ use12Hours
37
+ minuteInterval={5}
38
+ />
39
+ </>
40
+ );
41
+ };
42
+ ```
43
+
44
+ ## 0.24.0
45
+
46
+ ### Minor Changes
47
+
48
+ - [#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.
49
+
50
+ **Developer changes**:
51
+
52
+ Use `ProgressBar` with a default percentage label, or override the label to show a custom value:
53
+
54
+ ```tsx
55
+ import { ProgressBar } from '@utilitywarehouse/hearth-react-native';
56
+
57
+ <ProgressBar value={42} label="Uploading documents" />
58
+
59
+ <ProgressBar
60
+ value={68}
61
+ max={100}
62
+ label="Data allowance"
63
+ variant="circular"
64
+ formatValueText={(value, { max }) => `${max - value}GB remaining`}
65
+ />
66
+ ```
67
+
68
+ ### Patch Changes
69
+
70
+ - [#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
71
+
72
+ 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.
73
+
74
+ **Components affected**:
75
+ - `Modal`
76
+
77
+ **Developer changes**:
78
+ No changes required unless you were relying on invalid prop combinations.
79
+
3
80
  ## 0.23.0
4
81
 
5
82
  ### Minor Changes
@@ -1,22 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { View } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
+ import TimePickerView from '../TimePicker/TimePickerView';
4
5
  import { useDatePickerContext } from './DatePicker.context';
5
6
  import Days from './DatePickerDays';
6
7
  import Footer from './DatePickerFooter';
7
8
  import Header from './DatePickerHeader';
8
9
  import Months from './DatePickerMonths';
9
10
  import Years from './DatePickerYears';
10
- import TimePicker from './TimePicker';
11
- const CalendarView = {
12
- year: _jsx(Years, {}),
13
- month: _jsx(Months, {}),
14
- day: _jsx(Days, {}),
15
- time: _jsx(TimePicker, {}),
16
- };
17
11
  const Calendar = () => {
18
- const { hideHeader, hideFooter, calendarView, containerHeight, navigationPosition } = useDatePickerContext();
19
- return (_jsxs(View, { style: [styles.container], testID: "calendar", children: [!hideHeader ? _jsx(Header, { navigationPosition: navigationPosition }) : null, _jsx(View, { style: styles.containerInner(containerHeight), children: CalendarView[calendarView] }), !hideFooter ? _jsx(Footer, {}) : null] }));
12
+ const { hideHeader, hideFooter, calendarView, containerHeight, navigationPosition, currentDate, onSelectDate, timeZone, use12Hours, } = useDatePickerContext();
13
+ const calendarContent = calendarView === 'year' ? (_jsx(Years, {})) : calendarView === 'month' ? (_jsx(Months, {})) : calendarView === 'time' ? (_jsx(TimePickerView, { currentDate: currentDate, onSelectDate: onSelectDate, timeZone: timeZone, use12Hours: use12Hours, containerHeight: containerHeight })) : (_jsx(Days, {}));
14
+ return (_jsxs(View, { style: [styles.container], testID: "calendar", children: [!hideHeader ? _jsx(Header, { navigationPosition: navigationPosition }) : null, _jsx(View, { style: styles.containerInner(containerHeight), children: calendarContent }), !hideFooter ? _jsx(Footer, {}) : null] }));
20
15
  };
21
16
  const styles = StyleSheet.create(theme => ({
22
17
  container: {
@@ -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;
@@ -0,0 +1,6 @@
1
+ import type ProgressBarProps from './ProgressBar.props';
2
+ declare const ProgressBar: {
3
+ ({ variant, colorScheme, size, value, min, max, label, hideLabel, formatValueText, "aria-valuetext": ariaValueText, accessibilityLabel, ...rest }: ProgressBarProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default ProgressBar;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ import ProgressBarCircular from './ProgressBarCircular';
4
+ import ProgressBarLinear from './ProgressBarLinear';
5
+ const clampValue = (value, min, max) => {
6
+ if (max <= min)
7
+ return min;
8
+ return Math.min(Math.max(value, min), max);
9
+ };
10
+ const valueToPercent = (value, min, max) => {
11
+ const range = max - min;
12
+ if (range <= 0)
13
+ return 0;
14
+ return ((value - min) / range) * 100;
15
+ };
16
+ const ProgressBar = ({ variant = 'linear', colorScheme = 'default', size = 'md', value, min = 0, max = 100, label, hideLabel, formatValueText, 'aria-valuetext': ariaValueText, accessibilityLabel, ...rest }) => {
17
+ const effectiveValue = colorScheme === 'success' && !formatValueText ? max : clampValue(value, min, max);
18
+ const percentValue = valueToPercent(effectiveValue, min, max);
19
+ const clampedPercent = Math.max(0, Math.min(100, percentValue));
20
+ const valueText = formatValueText
21
+ ? formatValueText(effectiveValue, { min, max, percent: clampedPercent })
22
+ : `${Math.round(clampedPercent)}%`;
23
+ const valueTextForAria = ariaValueText ?? valueText;
24
+ const internalProps = {
25
+ percent: clampedPercent,
26
+ label,
27
+ valueText,
28
+ hideLabel,
29
+ colorScheme,
30
+ size,
31
+ };
32
+ return (_jsx(View, { ...rest, accessible: true, role: "progressbar", accessibilityRole: "progressbar", accessibilityLabel: accessibilityLabel ?? label, accessibilityValue: { min, max, now: effectiveValue, text: valueTextForAria }, "aria-valuenow": effectiveValue, "aria-valuemin": min, "aria-valuemax": max, "aria-valuetext": valueTextForAria, "data-colorscheme": colorScheme, children: variant === 'circular' ? (_jsx(ProgressBarCircular, { ...internalProps })) : (_jsx(ProgressBarLinear, { ...internalProps })) }));
33
+ };
34
+ ProgressBar.displayName = 'ProgressBar';
35
+ export default ProgressBar;
@@ -0,0 +1,60 @@
1
+ import { ViewProps } from 'react-native';
2
+ export type ProgressBarVariant = 'linear' | 'circular';
3
+ export type ProgressBarColorScheme = 'default' | 'success' | 'danger';
4
+ export type ProgressBarSize = 'sm' | 'md';
5
+ export interface ProgressBarProps extends ViewProps {
6
+ variant?: ProgressBarVariant;
7
+ /**
8
+ * Set the visual appearance.
9
+ * @default 'default'
10
+ */
11
+ colorScheme?: ProgressBarColorScheme;
12
+ /**
13
+ * Sets the circular variant size. Does not affect the appearance of the linear variant.
14
+ * @default 'md'
15
+ */
16
+ size?: ProgressBarSize;
17
+ /**
18
+ * The current progress value.
19
+ */
20
+ value: number;
21
+ /**
22
+ * The minimum value.
23
+ * @default 0
24
+ */
25
+ min?: number;
26
+ /**
27
+ * The maximum value.
28
+ * @default 100
29
+ */
30
+ max?: number;
31
+ /**
32
+ * Required text label for the progress bar.
33
+ */
34
+ label: string;
35
+ /**
36
+ * Visually hide the label and value text.
37
+ */
38
+ hideLabel?: boolean;
39
+ /**
40
+ * Override the default percentage value label formatting.
41
+ */
42
+ formatValueText?: (value: number, meta: {
43
+ min: number;
44
+ max: number;
45
+ percent: number;
46
+ }) => string;
47
+ /**
48
+ * A human-readable text alternative for the current value.
49
+ */
50
+ 'aria-valuetext'?: string;
51
+ }
52
+ export interface ProgressBarInternalProps {
53
+ percent: number;
54
+ label: string;
55
+ valueText: string;
56
+ hideLabel?: boolean;
57
+ colorScheme: ProgressBarColorScheme;
58
+ size: ProgressBarSize;
59
+ }
60
+ export default ProgressBarProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { ProgressBarInternalProps } from './ProgressBar.props';
2
+ declare const ProgressBarCircular: {
3
+ ({ percent, label, valueText, hideLabel, colorScheme, size, }: ProgressBarInternalProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default ProgressBarCircular;
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { Text, View } from 'react-native';
4
+ import Animated, { Easing, useAnimatedProps, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated';
5
+ import { Circle, G, Svg } from 'react-native-svg';
6
+ import { StyleSheet } from 'react-native-unistyles';
7
+ import useTheme from '../../hooks/useTheme';
8
+ import { BodyText } from '../BodyText';
9
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
10
+ const ProgressBarCircular = ({ percent, label, valueText, hideLabel, colorScheme, size, }) => {
11
+ const { components } = useTheme();
12
+ const isReducedMotion = useReducedMotion();
13
+ const progress = useSharedValue(0);
14
+ const hasMountedRef = useRef(false);
15
+ useEffect(() => {
16
+ const target = Math.max(0, Math.min(100, percent)) / 100;
17
+ if (isReducedMotion) {
18
+ progress.value = target;
19
+ hasMountedRef.current = true;
20
+ return;
21
+ }
22
+ if (!hasMountedRef.current) {
23
+ progress.value = target;
24
+ hasMountedRef.current = true;
25
+ return;
26
+ }
27
+ progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
28
+ }, [percent, isReducedMotion, progress]);
29
+ const circularTokens = components.progressBar.circular[size];
30
+ const barWidth = 'bar' in circularTokens ? circularTokens.bar.width : circularTokens.barWidth;
31
+ const diameter = circularTokens.height;
32
+ const radius = (diameter - barWidth) / 2;
33
+ const circumference = 2 * Math.PI * radius;
34
+ const animatedCircleProps = useAnimatedProps(() => ({
35
+ strokeDashoffset: circumference * (1 - progress.value),
36
+ }));
37
+ const indicatorColor = colorScheme === 'success'
38
+ ? components.progressBar.progress.successColor
39
+ : colorScheme === 'danger'
40
+ ? components.progressBar.progress.dangerColor
41
+ : components.progressBar.progress.defaultColor;
42
+ styles.useVariants({ size });
43
+ return (_jsxs(View, { style: styles.container, children: [_jsx(Svg, { width: diameter, height: diameter, viewBox: `0 0 ${diameter} ${diameter}`, style: styles.svg, children: _jsxs(G, { origin: `${diameter / 2}, ${diameter / 2}`, rotation: -90, children: [_jsx(Circle, { cx: "50%", cy: "50%", r: radius, stroke: components.progressBar.barColor, strokeWidth: barWidth, fill: "transparent" }), _jsx(AnimatedCircle, { cx: "50%", cy: "50%", r: radius, stroke: indicatorColor, strokeWidth: barWidth, fill: "transparent", strokeLinecap: "round", strokeDasharray: circumference, animatedProps: animatedCircleProps })] }) }), _jsxs(View, { style: styles.content, children: [_jsx(Text, { style: styles.valueText, children: valueText }), !hideLabel && size === 'md' ? (_jsx(BodyText, { style: styles.label, size: "md", weight: "semibold", children: label })) : null] })] }));
44
+ };
45
+ ProgressBarCircular.displayName = 'ProgressBarCircular';
46
+ const styles = StyleSheet.create(theme => ({
47
+ container: {
48
+ alignItems: 'center',
49
+ justifyContent: 'center',
50
+ position: 'relative',
51
+ variants: {
52
+ size: {
53
+ md: {
54
+ width: theme.components.progressBar.circular.md.height,
55
+ height: theme.components.progressBar.circular.md.height,
56
+ },
57
+ sm: {
58
+ width: theme.components.progressBar.circular.sm.height,
59
+ height: theme.components.progressBar.circular.sm.height,
60
+ },
61
+ },
62
+ },
63
+ },
64
+ svg: {
65
+ position: 'absolute',
66
+ top: 0,
67
+ left: 0,
68
+ },
69
+ content: {
70
+ alignItems: 'center',
71
+ justifyContent: 'center',
72
+ _web: {
73
+ position: 'absolute',
74
+ top: 0,
75
+ left: 0,
76
+ width: '100%',
77
+ height: '100%',
78
+ },
79
+ variants: {
80
+ size: {
81
+ md: {
82
+ gap: theme.components.progressBar.circular.md.gap,
83
+ },
84
+ sm: {
85
+ gap: 0,
86
+ },
87
+ },
88
+ },
89
+ },
90
+ valueText: {
91
+ color: theme.color.text.primary,
92
+ textAlign: 'center',
93
+ variants: {
94
+ size: {
95
+ md: {
96
+ fontFamily: theme.components.progressBar.circular.md.label.fontFamily,
97
+ fontSize: theme.components.progressBar.circular.md.label.fontSize,
98
+ lineHeight: theme.components.progressBar.circular.md.label.lineHeight,
99
+ fontWeight: theme.components.progressBar.circular.md.label.fontWeight,
100
+ },
101
+ sm: {
102
+ fontFamily: theme.typography.mobile.bodyText.fontFamily,
103
+ fontSize: theme.typography.mobile.bodyText.md.fontSize,
104
+ lineHeight: theme.typography.mobile.bodyText.md.lineHeight,
105
+ fontWeight: theme.fontWeight.semibold,
106
+ },
107
+ },
108
+ },
109
+ },
110
+ label: {
111
+ textAlign: 'center',
112
+ maxWidth: 90,
113
+ },
114
+ }));
115
+ export default ProgressBarCircular;
@@ -0,0 +1,6 @@
1
+ import type { ProgressBarInternalProps } from './ProgressBar.props';
2
+ declare const ProgressBarLinear: {
3
+ ({ percent, label, valueText, hideLabel, colorScheme, }: ProgressBarInternalProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default ProgressBarLinear;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { Platform, View } from 'react-native';
4
+ import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated';
5
+ import { StyleSheet } from 'react-native-unistyles';
6
+ import useTheme from '../../hooks/useTheme';
7
+ import { BodyText } from '../BodyText';
8
+ const ProgressBarLinear = ({ percent, label, valueText, hideLabel, colorScheme, }) => {
9
+ const { components } = useTheme();
10
+ const isReducedMotion = useReducedMotion();
11
+ const progress = useSharedValue(0);
12
+ const hasMountedRef = useRef(false);
13
+ const [trackWidth, setTrackWidth] = useState(0);
14
+ useEffect(() => {
15
+ const target = Math.max(0, Math.min(100, percent)) / 100;
16
+ if (isReducedMotion) {
17
+ progress.value = target;
18
+ hasMountedRef.current = true;
19
+ return;
20
+ }
21
+ if (!hasMountedRef.current) {
22
+ progress.value = target;
23
+ hasMountedRef.current = true;
24
+ return;
25
+ }
26
+ progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
27
+ }, [percent, isReducedMotion, progress]);
28
+ const animatedStyle = useAnimatedStyle(() => ({
29
+ width: trackWidth * progress.value,
30
+ }));
31
+ const indicatorColor = colorScheme === 'success'
32
+ ? components.progressBar.progress.successColor
33
+ : colorScheme === 'danger'
34
+ ? components.progressBar.progress.dangerColor
35
+ : components.progressBar.progress.defaultColor;
36
+ const handleTrackLayout = (event) => {
37
+ setTrackWidth(event.nativeEvent.layout.width);
38
+ };
39
+ return (_jsxs(View, { style: styles.container, children: [!hideLabel && (_jsxs(View, { style: styles.header, children: [_jsx(BodyText, { size: "md", weight: "semibold", style: styles.label, children: label }), _jsx(BodyText, { size: "md", style: styles.value, children: valueText })] })), _jsx(View, { style: styles.track, onLayout: handleTrackLayout, children: Platform.OS === 'web' ? (_jsx(View, { style: [
40
+ styles.indicator,
41
+ { width: `${Math.max(0, Math.min(100, percent))}%` },
42
+ { backgroundColor: indicatorColor },
43
+ ] })) : (_jsx(Animated.View, { style: [styles.indicator, animatedStyle, { backgroundColor: indicatorColor }] })) })] }));
44
+ };
45
+ ProgressBarLinear.displayName = 'ProgressBarLinear';
46
+ const styles = StyleSheet.create(theme => ({
47
+ container: {
48
+ width: '100%',
49
+ gap: theme.components.progressBar.linear.gap,
50
+ },
51
+ header: {
52
+ flexDirection: 'row',
53
+ alignItems: 'center',
54
+ justifyContent: 'space-between',
55
+ gap: theme.components.progressBar.linear.label.gap,
56
+ },
57
+ label: {
58
+ flex: 1,
59
+ },
60
+ value: {
61
+ flexShrink: 0,
62
+ textAlign: 'right',
63
+ },
64
+ track: {
65
+ width: '100%',
66
+ height: theme.components.progressBar.linear.bar.height,
67
+ backgroundColor: theme.components.progressBar.barColor,
68
+ borderRadius: theme.components.progressBar.linear.bar.borderRadius,
69
+ overflow: 'hidden',
70
+ },
71
+ indicator: {
72
+ height: '100%',
73
+ borderRadius: theme.components.progressBar.linear.bar.borderRadius,
74
+ _web: {
75
+ height: '100%',
76
+ },
77
+ },
78
+ }));
79
+ export default ProgressBarLinear;
@@ -0,0 +1,2 @@
1
+ export { default as ProgressBar } from './ProgressBar';
2
+ export type { ProgressBarColorScheme, ProgressBarProps, ProgressBarSize, ProgressBarVariant, } from './ProgressBar.props';
@@ -0,0 +1 @@
1
+ export { default as ProgressBar } from './ProgressBar';
@@ -0,0 +1,6 @@
1
+ import type { TimePickerProps } from './TimePicker.props';
2
+ declare const TimePicker: {
3
+ ({ timeZone, date, onChange, use12Hours, minuteInterval, hideFooter, style, ref, onCancel, }: TimePickerProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default TimePicker;
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import dayjs from 'dayjs';
3
+ import timezone from 'dayjs/plugin/timezone';
4
+ import utc from 'dayjs/plugin/utc';
5
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
6
+ import { AccessibilityInfo, findNodeHandle, Platform, View as RNView } from 'react-native';
7
+ import { StyleSheet } from 'react-native-unistyles';
8
+ import { BottomSheetModal, BottomSheetView } from '../BottomSheet';
9
+ import { Button } from '../Button';
10
+ import TimePickerView from './TimePickerView';
11
+ dayjs.extend(utc);
12
+ dayjs.extend(timezone);
13
+ const Footer = ({ onCancel, onConfirm }) => {
14
+ return (_jsxs(RNView, { style: styles.footer, testID: "footer", children: [_jsx(Button, { variant: "ghost", colorScheme: "functional", onPress: onCancel, children: "Cancel" }), _jsx(Button, { variant: "ghost", colorScheme: "functional", onPress: onConfirm, children: "Ok" })] }));
15
+ };
16
+ const TimePicker = ({ timeZone, date, onChange, use12Hours, minuteInterval, hideFooter, style, ref, onCancel = () => { }, }) => {
17
+ dayjs.tz.setDefault(timeZone);
18
+ dayjs.locale('en');
19
+ const modalRef = useRef(null);
20
+ const pickerViewRef = useRef(null);
21
+ useImperativeHandle(ref, () => modalRef.current);
22
+ const [currentDate, setCurrentDate] = useState(() => {
23
+ return date ? dayjs.tz(date, timeZone) : dayjs().tz(timeZone);
24
+ });
25
+ useEffect(() => {
26
+ const nextDate = date ? dayjs.tz(date, timeZone) : dayjs().tz(timeZone);
27
+ const isSameMinute = dayjs(currentDate).isSame(nextDate, 'minute');
28
+ if (!isSameMinute) {
29
+ setCurrentDate(nextDate);
30
+ }
31
+ }, [currentDate, date, timeZone]);
32
+ const closeTimePicker = useCallback(() => {
33
+ modalRef.current?.close();
34
+ }, []);
35
+ const handleSelectDate = useCallback((selectedDate) => {
36
+ const newDate = dayjs.tz(selectedDate ?? currentDate, timeZone);
37
+ if (!dayjs(currentDate).isSame(newDate, 'minute')) {
38
+ setCurrentDate(newDate);
39
+ }
40
+ onChange?.({ date: newDate ? dayjs(newDate).toDate() : newDate });
41
+ }, [currentDate, onChange, timeZone]);
42
+ const handleCancel = useCallback(() => {
43
+ onCancel?.();
44
+ closeTimePicker();
45
+ }, [closeTimePicker, onCancel]);
46
+ const handleConfirm = useCallback(() => {
47
+ closeTimePicker();
48
+ }, [closeTimePicker]);
49
+ const handleChange = useCallback((index) => {
50
+ if (index > -1) {
51
+ setTimeout(() => {
52
+ AccessibilityInfo.announceForAccessibility('Time picker opened.');
53
+ const targetRef = pickerViewRef.current;
54
+ if (targetRef) {
55
+ const nodeHandle = findNodeHandle(targetRef);
56
+ if (nodeHandle) {
57
+ AccessibilityInfo.setAccessibilityFocus(nodeHandle);
58
+ }
59
+ }
60
+ }, 50);
61
+ }
62
+ }, []);
63
+ const contentStyle = useMemo(() => [styles.container, style], [style]);
64
+ return (_jsx(BottomSheetModal, { ref: modalRef, onChange: handleChange, accessible: false, enableContentPanningGesture: false, children: _jsx(BottomSheetView, { children: _jsxs(RNView, { ref: pickerViewRef, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Time picker' : undefined, importantForAccessibility: Platform.OS === 'android' ? 'yes' : 'auto', style: contentStyle, children: [_jsx(TimePickerView, { currentDate: currentDate, onSelectDate: handleSelectDate, timeZone: timeZone, use12Hours: use12Hours, minuteInterval: minuteInterval }), !hideFooter ? _jsx(Footer, { onCancel: handleCancel, onConfirm: handleConfirm }) : null] }) }) }));
65
+ };
66
+ TimePicker.displayName = 'TimePicker';
67
+ const styles = StyleSheet.create(theme => ({
68
+ container: {
69
+ backgroundColor: theme.color.background.secondary,
70
+ gap: theme.components.datePicker.calendar.gap,
71
+ },
72
+ footer: {
73
+ flexDirection: 'row',
74
+ gap: theme.components.datePicker.calendar.footer.gap,
75
+ justifyContent: 'flex-end',
76
+ },
77
+ }));
78
+ export default TimePicker;