@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +13 -13
- package/CHANGELOG.md +77 -0
- package/build/components/DatePicker/DatePickerCalendar.js +4 -9
- package/build/components/Modal/Modal.js +5 -4
- package/build/components/Modal/Modal.props.d.ts +10 -4
- package/build/components/ProgressBar/ProgressBar.d.ts +6 -0
- package/build/components/ProgressBar/ProgressBar.js +35 -0
- package/build/components/ProgressBar/ProgressBar.props.d.ts +60 -0
- package/build/components/ProgressBar/ProgressBar.props.js +1 -0
- package/build/components/ProgressBar/ProgressBarCircular.d.ts +6 -0
- package/build/components/ProgressBar/ProgressBarCircular.js +115 -0
- package/build/components/ProgressBar/ProgressBarLinear.d.ts +6 -0
- package/build/components/ProgressBar/ProgressBarLinear.js +79 -0
- package/build/components/ProgressBar/index.d.ts +2 -0
- package/build/components/ProgressBar/index.js +1 -0
- package/build/components/TimePicker/TimePicker.d.ts +6 -0
- package/build/components/TimePicker/TimePicker.js +78 -0
- package/build/components/TimePicker/TimePicker.props.d.ts +45 -0
- package/build/components/TimePicker/TimePicker.props.js +1 -0
- package/build/components/TimePicker/TimePickerView.d.ts +12 -0
- package/build/components/TimePicker/TimePickerView.js +130 -0
- package/build/components/TimePicker/TimePickerWheel.d.ts +8 -0
- package/build/components/TimePicker/TimePickerWheel.js +78 -0
- package/build/components/{DatePicker/time-picker/wheel-web.d.ts → TimePicker/TimePickerWheel.web.d.ts} +4 -4
- package/build/components/TimePicker/TimePickerWheel.web.js +122 -0
- package/build/components/TimePicker/index.d.ts +6 -0
- package/build/components/TimePicker/index.js +3 -0
- package/build/components/TimePickerInput/TimePickerInput.d.ts +6 -0
- package/build/components/TimePickerInput/TimePickerInput.js +127 -0
- package/build/components/TimePickerInput/TimePickerInput.props.d.ts +52 -0
- package/build/components/TimePickerInput/TimePickerInput.props.js +1 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.d.ts +8 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.js +19 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.web.d.ts +5 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.web.js +5 -0
- package/build/components/TimePickerInput/index.d.ts +2 -0
- package/build/components/TimePickerInput/index.js +1 -0
- package/build/components/index.d.ts +3 -0
- package/build/components/index.js +3 -0
- package/docs/components/AllComponents.web.tsx +36 -0
- package/package.json +2 -1
- package/src/components/DatePicker/DatePickerCalendar.tsx +30 -13
- package/src/components/Modal/Modal.props.ts +13 -4
- package/src/components/Modal/Modal.stories.tsx +1 -1
- package/src/components/Modal/Modal.tsx +28 -11
- package/src/components/ProgressBar/ProgressBar.docs.mdx +90 -0
- package/src/components/ProgressBar/ProgressBar.figma.tsx +79 -0
- package/src/components/ProgressBar/ProgressBar.props.ts +60 -0
- package/src/components/ProgressBar/ProgressBar.stories.tsx +117 -0
- package/src/components/ProgressBar/ProgressBar.tsx +74 -0
- package/src/components/ProgressBar/ProgressBarCircular.tsx +181 -0
- package/src/components/ProgressBar/ProgressBarLinear.tsx +127 -0
- package/src/components/ProgressBar/index.ts +7 -0
- package/src/components/TimePicker/TimePicker.docs.mdx +84 -0
- package/src/components/TimePicker/TimePicker.figma.tsx +29 -0
- package/src/components/TimePicker/TimePicker.props.ts +45 -0
- package/src/components/TimePicker/TimePicker.stories.tsx +85 -0
- package/src/components/TimePicker/TimePicker.tsx +150 -0
- package/src/components/TimePicker/TimePickerView.tsx +216 -0
- package/src/components/TimePicker/TimePickerWheel.tsx +154 -0
- package/src/components/TimePicker/TimePickerWheel.web.tsx +217 -0
- package/src/components/TimePicker/index.ts +8 -0
- package/src/components/TimePickerInput/TimePickerInput.docs.mdx +135 -0
- package/src/components/TimePickerInput/TimePickerInput.figma.tsx +34 -0
- package/src/components/TimePickerInput/TimePickerInput.props.ts +55 -0
- package/src/components/TimePickerInput/TimePickerInput.stories.tsx +175 -0
- package/src/components/TimePickerInput/TimePickerInput.tsx +283 -0
- package/src/components/TimePickerInput/TimePickerInputDoneButton.tsx +42 -0
- package/src/components/TimePickerInput/TimePickerInputDoneButton.web.tsx +7 -0
- package/src/components/TimePickerInput/index.ts +2 -0
- package/src/components/index.ts +3 -0
- package/build/components/DatePicker/TimePicker.d.ts +0 -3
- package/build/components/DatePicker/TimePicker.js +0 -84
- package/build/components/DatePicker/time-picker/animated-math.d.ts +0 -4
- package/build/components/DatePicker/time-picker/animated-math.js +0 -19
- package/build/components/DatePicker/time-picker/period-native.d.ts +0 -6
- package/build/components/DatePicker/time-picker/period-native.js +0 -17
- package/build/components/DatePicker/time-picker/period-picker.d.ts +0 -6
- package/build/components/DatePicker/time-picker/period-picker.js +0 -10
- package/build/components/DatePicker/time-picker/period-web.d.ts +0 -6
- package/build/components/DatePicker/time-picker/period-web.js +0 -21
- package/build/components/DatePicker/time-picker/wheel-native.d.ts +0 -8
- package/build/components/DatePicker/time-picker/wheel-native.js +0 -19
- package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +0 -2
- package/build/components/DatePicker/time-picker/wheel-picker/index.js +0 -2
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +0 -16
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +0 -97
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +0 -21
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +0 -88
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +0 -23
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +0 -21
- package/build/components/DatePicker/time-picker/wheel-web.js +0 -146
- package/build/components/DatePicker/time-picker/wheel.d.ts +0 -8
- package/build/components/DatePicker/time-picker/wheel.js +0 -10
- package/src/components/DatePicker/TimePicker.tsx +0 -141
- package/src/components/DatePicker/time-picker/animated-math.ts +0 -33
- package/src/components/DatePicker/time-picker/period-native.tsx +0 -34
- package/src/components/DatePicker/time-picker/period-picker.tsx +0 -16
- package/src/components/DatePicker/time-picker/period-web.tsx +0 -36
- package/src/components/DatePicker/time-picker/wheel-native.tsx +0 -37
- package/src/components/DatePicker/time-picker/wheel-picker/index.ts +0 -3
- package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.tsx +0 -132
- package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.ts +0 -22
- package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.tsx +0 -200
- package/src/components/DatePicker/time-picker/wheel-web.tsx +0 -180
- package/src/components/DatePicker/time-picker/wheel.tsx +0 -18
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @utilitywarehouse/hearth-react-native@0.
|
|
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
|
-
|
|
35
|
-
|
|
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 |
|
|
64
|
-
react-hooks/exhaustive-deps |
|
|
65
|
-
no-global-assign |
|
|
66
|
-
react-hooks/rules-of-hooks |
|
|
67
|
-
|
|
68
|
-
no-
|
|
69
|
-
|
|
70
|
-
@typescript-eslint/triple-slash-reference |
|
|
71
|
-
no-
|
|
72
|
-
no-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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[
|
|
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
|
|
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 @@
|
|
|
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;
|