@utilitywarehouse/hearth-react-native 0.24.0 → 0.26.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 +72 -0
- package/build/components/DatePicker/DatePickerCalendar.js +4 -9
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +30 -7
- package/build/components/Modal/Modal.props.d.ts +4 -2
- 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 +2 -0
- package/build/components/index.js +2 -0
- package/docs/components/AllComponents.web.tsx +30 -0
- package/package.json +3 -2
- package/src/components/DatePicker/DatePickerCalendar.tsx +30 -13
- package/src/components/Modal/Modal.docs.mdx +9 -3
- package/src/components/Modal/Modal.props.ts +4 -2
- package/src/components/Modal/Modal.tsx +44 -7
- 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 +2 -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.26.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
|
+
86: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
|
+
313: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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
no-
|
|
69
|
-
@typescript-eslint/triple-slash-reference |
|
|
70
|
-
|
|
71
|
-
no-
|
|
72
|
-
no-
|
|
63
|
+
@typescript-eslint/no-unused-vars | 1316.387 | 59.6%
|
|
64
|
+
react-hooks/exhaustive-deps | 96.698 | 4.4%
|
|
65
|
+
no-global-assign | 93.436 | 4.2%
|
|
66
|
+
react-hooks/rules-of-hooks | 81.760 | 3.7%
|
|
67
|
+
no-unexpected-multiline | 47.972 | 2.2%
|
|
68
|
+
no-misleading-character-class | 43.286 | 2.0%
|
|
69
|
+
@typescript-eslint/triple-slash-reference | 33.417 | 1.5%
|
|
70
|
+
@typescript-eslint/ban-ts-comment | 32.258 | 1.5%
|
|
71
|
+
no-regex-spaces | 29.418 | 1.3%
|
|
72
|
+
no-loss-of-precision | 25.945 | 1.2%
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# @utilitywarehouse/hearth-react-native
|
|
2
2
|
|
|
3
|
+
## 0.26.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#981](https://github.com/utilitywarehouse/hearth/pull/981) [`df56387`](https://github.com/utilitywarehouse/hearth/commit/df563872e6bf040d419f6c7fce2343ebe560edb9) Thanks [@declanelcocks](https://github.com/declanelcocks)! - 🌟 [ENHANCEMENT]: Improve `Modal` behaviour when used inside a React Navigation modal (`inNavModal`).
|
|
8
|
+
|
|
9
|
+
The following improvements have been made to the `Modal` component when used in a navigation context with `inNavModal={true}`:
|
|
10
|
+
- **`scrollable` prop**: Content is now rendered inside a `ScrollView` by default. Set `scrollable={false}` to opt out, for example when you need to centre content or use a custom layout.
|
|
11
|
+
- **`stickyFooter` support**: The `stickyFooter` prop now works correctly in `inNavModal` mode.
|
|
12
|
+
- **Auto full-screen detection**: When the modal fills the entire screen (e.g. with `presentation: 'fullScreenModal'`), the `fullscreen` style is applied automatically. The `fullscreen` prop is no longer available when `inNavModal` is `true`.
|
|
13
|
+
|
|
14
|
+
**Components affected**:
|
|
15
|
+
- `Modal`
|
|
16
|
+
|
|
17
|
+
**Developer changes**:
|
|
18
|
+
|
|
19
|
+
No changes are required for existing usage. If you were passing `fullscreen` alongside `inNavModal={true}`, remove the `fullscreen` prop — full-screen styling is now detected automatically:
|
|
20
|
+
|
|
21
|
+
```diff
|
|
22
|
+
- <Modal inNavModal fullscreen>
|
|
23
|
+
+ <Modal inNavModal>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To disable the default `ScrollView` wrapping in `inNavModal` mode:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<Modal inNavModal scrollable={false}>
|
|
30
|
+
{/* custom layout */}
|
|
31
|
+
</Modal>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 0.25.0
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
37
|
+
|
|
38
|
+
- [#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.
|
|
39
|
+
|
|
40
|
+
Includes a shared time picker view, updated wheel behavior for native platforms, and polished visuals like gradient fades.
|
|
41
|
+
|
|
42
|
+
**Components affected**:
|
|
43
|
+
- `TimePicker`
|
|
44
|
+
- `TimePickerInput`
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { TimePicker, TimePickerInput } from '@utilitywarehouse/hearth-react-native';
|
|
48
|
+
import { useRef, useState } from 'react';
|
|
49
|
+
import type { DateType } from '@utilitywarehouse/hearth-react-native';
|
|
50
|
+
|
|
51
|
+
const Example = () => {
|
|
52
|
+
const [value, setValue] = useState<DateType>();
|
|
53
|
+
const pickerRef = useRef(null);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<TimePickerInput
|
|
58
|
+
value={value}
|
|
59
|
+
onChange={({ date }) => setValue(date ?? undefined)}
|
|
60
|
+
onClear={() => setValue(undefined)}
|
|
61
|
+
timePickerProps={{ use12Hours: true, minuteInterval: 5 }}
|
|
62
|
+
/>
|
|
63
|
+
<TimePicker
|
|
64
|
+
ref={pickerRef}
|
|
65
|
+
date={value}
|
|
66
|
+
onChange={({ date }) => setValue(date)}
|
|
67
|
+
use12Hours
|
|
68
|
+
minuteInterval={5}
|
|
69
|
+
/>
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
3
75
|
## 0.24.0
|
|
4
76
|
|
|
5
77
|
### 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: {
|
|
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
|
|
|
3
3
|
type Modal<T = any> = BottomSheetModalMethods<T> & {
|
|
4
4
|
triggerCloseAnimation?: () => void;
|
|
5
5
|
};
|
|
6
|
-
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, stickyFooter, background, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, stickyFooter, background, scrollable, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export default Modal;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { BottomSheetFooter, } from '@gorhom/bottom-sheet';
|
|
3
3
|
import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
4
|
-
import { useCallback, useEffect, useImperativeHandle, useRef } from 'react';
|
|
5
|
-
import { AccessibilityInfo, Platform, ScrollView, View, findNodeHandle } from 'react-native';
|
|
4
|
+
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { AccessibilityInfo, Dimensions, Platform, ScrollView, View, findNodeHandle } from 'react-native';
|
|
6
6
|
import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming, } from 'react-native-reanimated';
|
|
7
7
|
import { StyleSheet } from 'react-native-unistyles';
|
|
8
8
|
import { useTheme } from '../../hooks';
|
|
@@ -13,7 +13,7 @@ import { Button } from '../Button';
|
|
|
13
13
|
import { Heading } from '../Heading';
|
|
14
14
|
import { Spinner } from '../Spinner';
|
|
15
15
|
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
16
|
-
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, stickyFooter = true, background = 'default', ...props }) => {
|
|
16
|
+
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, stickyFooter = true, background = 'default', scrollable = true, ...props }) => {
|
|
17
17
|
const bottomSheetModalRef = useRef(null);
|
|
18
18
|
const viewRef = useRef(null);
|
|
19
19
|
const scrollViewRef = useRef(null);
|
|
@@ -21,6 +21,13 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
21
21
|
const backgroundOpacity = useSharedValue(0);
|
|
22
22
|
const pretendContentTranslateY = useSharedValue(20);
|
|
23
23
|
const isBrandBackground = background === 'brand';
|
|
24
|
+
const [inNavModalHeight, setInNavModalHeight] = useState();
|
|
25
|
+
const isNavModalFullScreen = useMemo(() => {
|
|
26
|
+
if (!inNavModalHeight || !inNavModal)
|
|
27
|
+
return false;
|
|
28
|
+
const screenHeight = Dimensions.get('window').height;
|
|
29
|
+
return inNavModalHeight >= screenHeight;
|
|
30
|
+
}, [inNavModalHeight, inNavModal]);
|
|
24
31
|
const triggerCloseAnimation = useCallback(() => {
|
|
25
32
|
if (Platform.OS === 'android' && inNavModal) {
|
|
26
33
|
pretendContentTranslateY.value = withTiming(20, {
|
|
@@ -107,9 +114,13 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
107
114
|
stickyFooter,
|
|
108
115
|
showHandle: props.showHandle,
|
|
109
116
|
background: isBrandBackground ? 'brand' : 'primary',
|
|
117
|
+
...(inNavModal && {
|
|
118
|
+
fullscreen: isNavModalFullScreen,
|
|
119
|
+
}),
|
|
110
120
|
});
|
|
111
121
|
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
|
|
122
|
+
const InNavModalContainer = scrollable ? ScrollView : View;
|
|
123
|
+
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 && (_jsxs(InNavModalContainer, { style: { flexGrow: stickyFooter ? 1 : 0 }, children: [children, !stickyFooter ? _jsx(View, { style: styles.inNavModalFooterContainer, children: footer }) : null] })), !inNavModal && children, ((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons ? footer : null] })) }));
|
|
113
124
|
const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
|
|
114
125
|
onPressPrimaryButton,
|
|
115
126
|
primaryButtonText,
|
|
@@ -118,7 +129,9 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
118
129
|
primaryButtonProps,
|
|
119
130
|
secondaryButtonProps,
|
|
120
131
|
]);
|
|
121
|
-
return inNavModal ? (_jsxs(View, {
|
|
132
|
+
return inNavModal ? (_jsxs(View, { onLayout: (e) => {
|
|
133
|
+
setInNavModalHeight(e.nativeEvent.layout.height);
|
|
134
|
+
}, style: {
|
|
122
135
|
flex: 1,
|
|
123
136
|
backgroundColor: theme.color.background[isBrandBackground ? 'brand' : 'primary'],
|
|
124
137
|
}, 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 })] }));
|
|
@@ -227,8 +240,6 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
227
240
|
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
228
241
|
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
229
242
|
backgroundColor: theme.color.surface.neutral.strong,
|
|
230
|
-
gap: theme.components.modal.gap,
|
|
231
|
-
padding: theme.components.modal.padding,
|
|
232
243
|
paddingBottom: theme.components.modal.padding + rt.insets.bottom,
|
|
233
244
|
variants: {
|
|
234
245
|
background: {
|
|
@@ -237,8 +248,20 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
237
248
|
backgroundColor: theme.color.background.brand,
|
|
238
249
|
},
|
|
239
250
|
},
|
|
251
|
+
fullscreen: {
|
|
252
|
+
true: {
|
|
253
|
+
padding: theme.components.modal.padding,
|
|
254
|
+
paddingTop: rt.insets.top,
|
|
255
|
+
},
|
|
256
|
+
false: {
|
|
257
|
+
padding: theme.components.modal.padding,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
240
260
|
},
|
|
241
261
|
},
|
|
262
|
+
inNavModalFooterContainer: {
|
|
263
|
+
paddingTop: theme.components.modal.padding,
|
|
264
|
+
},
|
|
242
265
|
androidContainer: {
|
|
243
266
|
height: rt.insets.top + 18,
|
|
244
267
|
paddingLeft: theme.components.modal.padding,
|
|
@@ -11,6 +11,7 @@ interface ModalPropsBase extends Omit<BottomSheetProps, 'children'> {
|
|
|
11
11
|
loadingHeading?: string;
|
|
12
12
|
description?: string;
|
|
13
13
|
fullscreen?: boolean;
|
|
14
|
+
stickyFooter?: boolean;
|
|
14
15
|
children?: ViewProps['children'];
|
|
15
16
|
onPressPrimaryButton?: () => void;
|
|
16
17
|
primaryButtonText?: string;
|
|
@@ -25,11 +26,12 @@ interface ModalPropsBase extends Omit<BottomSheetProps, 'children'> {
|
|
|
25
26
|
}
|
|
26
27
|
type ModalProps = (ModalPropsBase & {
|
|
27
28
|
inNavModal?: false | undefined;
|
|
28
|
-
|
|
29
|
+
scrollable?: never;
|
|
29
30
|
background?: never;
|
|
30
31
|
}) | (ModalPropsBase & {
|
|
31
32
|
inNavModal: true;
|
|
32
|
-
|
|
33
|
+
fullscreen?: never;
|
|
34
|
+
scrollable?: boolean;
|
|
33
35
|
background?: 'default' | 'brand';
|
|
34
36
|
});
|
|
35
37
|
export default ModalProps;
|
|
@@ -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;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
|
|
2
|
+
import type { Ref } from 'react';
|
|
3
|
+
import type { ViewStyle } from 'react-native';
|
|
4
|
+
import type { DateType, PickerOption } from '../DatePicker/DatePicker.props';
|
|
5
|
+
export interface TimePickerProps {
|
|
6
|
+
/**
|
|
7
|
+
* IANA time zone identifier applied when normalising and comparing times.
|
|
8
|
+
*/
|
|
9
|
+
timeZone?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Controlled time value.
|
|
12
|
+
*/
|
|
13
|
+
date?: DateType;
|
|
14
|
+
/**
|
|
15
|
+
* Fired whenever a time is picked.
|
|
16
|
+
*/
|
|
17
|
+
onChange?: (params: {
|
|
18
|
+
date: DateType;
|
|
19
|
+
}) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Display a 12-hour clock with AM/PM selector.
|
|
22
|
+
*/
|
|
23
|
+
use12Hours?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Step interval for minutes shown in the picker.
|
|
26
|
+
*/
|
|
27
|
+
minuteInterval?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Hide the footer actions.
|
|
30
|
+
*/
|
|
31
|
+
hideFooter?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Custom container styling for the time picker surface.
|
|
34
|
+
*/
|
|
35
|
+
style?: ViewStyle;
|
|
36
|
+
/**
|
|
37
|
+
* Gives imperative access to the bottom sheet instance.
|
|
38
|
+
*/
|
|
39
|
+
ref?: Ref<BottomSheetModalMethods<any>>;
|
|
40
|
+
/**
|
|
41
|
+
* Fired when the cancel action is triggered.
|
|
42
|
+
*/
|
|
43
|
+
onCancel?: () => void;
|
|
44
|
+
}
|
|
45
|
+
export type { DateType, PickerOption };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DateType } from './TimePicker.props';
|
|
2
|
+
export type Period = 'AM' | 'PM';
|
|
3
|
+
type TimePickerViewProps = {
|
|
4
|
+
currentDate: DateType;
|
|
5
|
+
onSelectDate: (date: DateType) => void;
|
|
6
|
+
timeZone?: string;
|
|
7
|
+
use12Hours?: boolean;
|
|
8
|
+
minuteInterval?: number;
|
|
9
|
+
containerHeight?: number;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("react").MemoExoticComponent<({ currentDate, onSelectDate, timeZone, use12Hours, minuteInterval, }: TimePickerViewProps) => import("react/jsx-runtime").JSX.Element>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
6
|
+
import { BodyText } from '../BodyText';
|
|
7
|
+
import { formatNumber, getParsedDate } from '../DatePicker/utils';
|
|
8
|
+
import TimePickerWheel from './TimePickerWheel';
|
|
9
|
+
const createNumberList = (num, numerals, startFrom = 0) => {
|
|
10
|
+
return Array.from({ length: num }, (_, i) => ({
|
|
11
|
+
value: i + startFrom,
|
|
12
|
+
text: i + startFrom < 10
|
|
13
|
+
? `${formatNumber(0, numerals)}${formatNumber(i + startFrom, numerals)}`
|
|
14
|
+
: `${formatNumber(i + startFrom, numerals)}`,
|
|
15
|
+
}));
|
|
16
|
+
};
|
|
17
|
+
const createMinuteList = (interval, numerals) => {
|
|
18
|
+
const safeInterval = Math.min(59, Math.max(1, Math.floor(interval)));
|
|
19
|
+
const values = Array.from({ length: Math.ceil(60 / safeInterval) }, (_, index) => Math.min(index * safeInterval, 59)).filter((value, index, array) => array.indexOf(value) === index && value < 60);
|
|
20
|
+
return values.map(value => ({
|
|
21
|
+
value,
|
|
22
|
+
text: value < 10
|
|
23
|
+
? `${formatNumber(0, numerals)}${formatNumber(value, numerals)}`
|
|
24
|
+
: `${formatNumber(value, numerals)}`,
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
27
|
+
const getClosestMinute = (value, options) => {
|
|
28
|
+
if (!options.length)
|
|
29
|
+
return value;
|
|
30
|
+
const values = options.map(option => option.value);
|
|
31
|
+
if (values.includes(value))
|
|
32
|
+
return value;
|
|
33
|
+
let closest = values[0] ?? value;
|
|
34
|
+
let closestDiff = Math.abs(value - closest);
|
|
35
|
+
values.forEach(optionValue => {
|
|
36
|
+
const diff = Math.abs(value - optionValue);
|
|
37
|
+
if (diff < closestDiff) {
|
|
38
|
+
closestDiff = diff;
|
|
39
|
+
closest = optionValue;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return closest;
|
|
43
|
+
};
|
|
44
|
+
const TimePickerView = ({ currentDate, onSelectDate, timeZone, use12Hours, minuteInterval = 1, }) => {
|
|
45
|
+
const hours = useMemo(() => createNumberList(use12Hours ? 12 : 24, 'latn', use12Hours ? 1 : 0), [use12Hours]);
|
|
46
|
+
const minutes = useMemo(() => createMinuteList(minuteInterval, 'latn'), [minuteInterval]);
|
|
47
|
+
const periodOptions = useMemo(() => [
|
|
48
|
+
{ value: 'AM', text: 'AM' },
|
|
49
|
+
{ value: 'PM', text: 'PM' },
|
|
50
|
+
], []);
|
|
51
|
+
const baseDate = currentDate;
|
|
52
|
+
const { hour, hour12, minute, period } = getParsedDate(baseDate);
|
|
53
|
+
const minuteValue = useMemo(() => getClosestMinute(minute, minutes), [minute, minutes]);
|
|
54
|
+
const handleChangeHour = useCallback((value) => {
|
|
55
|
+
let hour24 = value;
|
|
56
|
+
if (use12Hours) {
|
|
57
|
+
if (period === 'AM' && value === 12) {
|
|
58
|
+
hour24 = 0;
|
|
59
|
+
}
|
|
60
|
+
else if (period === 'PM' && value < 12) {
|
|
61
|
+
hour24 = value + 12;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
hour24 = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const newDate = dayjs.tz(baseDate, timeZone).hour(hour24).minute(minuteValue);
|
|
68
|
+
onSelectDate(newDate);
|
|
69
|
+
}, [baseDate, onSelectDate, timeZone, use12Hours, period, minuteValue]);
|
|
70
|
+
const handleChangeMinute = useCallback((value) => {
|
|
71
|
+
const newDate = dayjs.tz(baseDate, timeZone).minute(value);
|
|
72
|
+
onSelectDate(newDate);
|
|
73
|
+
}, [baseDate, onSelectDate, timeZone]);
|
|
74
|
+
const handlePeriodChange = useCallback((newPeriod) => {
|
|
75
|
+
let newHour = hour12;
|
|
76
|
+
if (newPeriod === 'PM' && hour12 < 12) {
|
|
77
|
+
newHour = hour12 + 12;
|
|
78
|
+
}
|
|
79
|
+
else if (newPeriod === 'AM' && hour12 === 12) {
|
|
80
|
+
newHour = 0;
|
|
81
|
+
}
|
|
82
|
+
else if (newPeriod === 'AM' && hour >= 12) {
|
|
83
|
+
newHour = hour12;
|
|
84
|
+
}
|
|
85
|
+
const newDate = dayjs.tz(baseDate, timeZone).hour(newHour);
|
|
86
|
+
onSelectDate(newDate);
|
|
87
|
+
}, [baseDate, onSelectDate, timeZone, hour, hour12]);
|
|
88
|
+
return (_jsxs(View, { style: styles.container, testID: "time-selector", children: [_jsxs(View, { style: styles.timePickerContainer, children: [_jsx(View, { style: styles.wheelContainer, children: _jsx(TimePickerWheel, { value: use12Hours ? hour12 : hour, items: hours, setValue: handleChangeHour }) }), _jsx(BodyText, { style: styles.timeSeparator, size: "lg", children: ":" }), _jsx(View, { style: styles.wheelContainer, children: _jsx(TimePickerWheel, { value: minuteValue, items: minutes, setValue: handleChangeMinute }) })] }), use12Hours && period ? (_jsx(View, { style: styles.periodContainer, children: _jsx(TimePickerWheel, { value: period, items: periodOptions, setValue: handlePeriodChange }) })) : null] }));
|
|
89
|
+
};
|
|
90
|
+
const styles = StyleSheet.create({
|
|
91
|
+
container: {
|
|
92
|
+
flex: 1,
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
justifyContent: 'center',
|
|
95
|
+
flexDirection: 'row',
|
|
96
|
+
},
|
|
97
|
+
wheelContainer: {
|
|
98
|
+
flex: 1,
|
|
99
|
+
},
|
|
100
|
+
timePickerContainer: {
|
|
101
|
+
alignItems: 'center',
|
|
102
|
+
justifyContent: 'center',
|
|
103
|
+
width: 146,
|
|
104
|
+
height: 208,
|
|
105
|
+
flexDirection: 'row',
|
|
106
|
+
marginBottom: -30,
|
|
107
|
+
},
|
|
108
|
+
timeSeparator: {
|
|
109
|
+
marginHorizontal: 5,
|
|
110
|
+
},
|
|
111
|
+
periodContainer: {
|
|
112
|
+
marginLeft: 10,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const customComparator = (prev, next) => {
|
|
116
|
+
if (prev.onSelectDate !== next.onSelectDate) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (prev.timeZone !== next.timeZone) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (prev.use12Hours !== next.use12Hours) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (prev.minuteInterval !== next.minuteInterval) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return dayjs(prev.currentDate).isSame(next.currentDate, 'minute');
|
|
129
|
+
};
|
|
130
|
+
export default memo(TimePickerView, customComparator);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PickerOption } from './TimePicker.props';
|
|
2
|
+
type TimePickerWheelProps = {
|
|
3
|
+
value: number | string;
|
|
4
|
+
setValue?: (value: any) => void;
|
|
5
|
+
items: PickerOption[];
|
|
6
|
+
};
|
|
7
|
+
declare const TimePickerWheel: ({ value, setValue, items }: TimePickerWheelProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default TimePickerWheel;
|