@utilitywarehouse/hearth-react-native 0.22.0 → 0.23.0-test-list

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 (70) hide show
  1. package/.turbo/turbo-build.log +5 -4
  2. package/CHANGELOG.md +36 -0
  3. package/build/components/List/List.js +2 -2
  4. package/build/components/Modal/Modal.d.ts +1 -1
  5. package/build/components/Modal/Modal.js +17 -5
  6. package/build/components/Modal/Modal.props.d.ts +1 -0
  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/SegmentedControl/SegmentedControl.context.d.ts +14 -0
  18. package/build/components/SegmentedControl/SegmentedControl.context.js +9 -0
  19. package/build/components/SegmentedControl/SegmentedControl.d.ts +6 -0
  20. package/build/components/SegmentedControl/SegmentedControl.js +199 -0
  21. package/build/components/SegmentedControl/SegmentedControl.props.d.ts +18 -0
  22. package/build/components/SegmentedControl/SegmentedControl.props.js +1 -0
  23. package/build/components/SegmentedControl/SegmentedControlOption.d.ts +18 -0
  24. package/build/components/SegmentedControl/SegmentedControlOption.js +144 -0
  25. package/build/components/SegmentedControl/SegmentedControlOption.props.d.ts +14 -0
  26. package/build/components/SegmentedControl/SegmentedControlOption.props.js +1 -0
  27. package/build/components/SegmentedControl/index.d.ts +4 -0
  28. package/build/components/SegmentedControl/index.js +2 -0
  29. package/build/components/TimePicker/TimePicker.d.ts +6 -0
  30. package/build/components/TimePicker/TimePicker.js +78 -0
  31. package/build/components/TimePicker/TimePicker.props.d.ts +45 -0
  32. package/build/components/TimePicker/TimePicker.props.js +1 -0
  33. package/build/components/TimePicker/TimePickerView.d.ts +12 -0
  34. package/build/components/TimePicker/TimePickerView.js +130 -0
  35. package/build/components/TimePicker/TimePickerWheel.d.ts +8 -0
  36. package/build/components/TimePicker/TimePickerWheel.js +86 -0
  37. package/build/components/TimePicker/TimePickerWheel.web.d.ts +8 -0
  38. package/build/components/TimePicker/TimePickerWheel.web.js +122 -0
  39. package/build/components/TimePicker/index.d.ts +6 -0
  40. package/build/components/TimePicker/index.js +3 -0
  41. package/build/components/TimePickerInput/TimePickerInput.d.ts +6 -0
  42. package/build/components/TimePickerInput/TimePickerInput.js +127 -0
  43. package/build/components/TimePickerInput/TimePickerInput.props.d.ts +52 -0
  44. package/build/components/TimePickerInput/TimePickerInput.props.js +1 -0
  45. package/build/components/TimePickerInput/TimePickerInputDoneButton.d.ts +8 -0
  46. package/build/components/TimePickerInput/TimePickerInputDoneButton.js +19 -0
  47. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.d.ts +5 -0
  48. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.js +5 -0
  49. package/build/components/TimePickerInput/index.d.ts +2 -0
  50. package/build/components/TimePickerInput/index.js +1 -0
  51. package/build/components/VerificationInput/VerificationInput.js +182 -20
  52. package/build/components/VerificationInput/VerificationInput.utils.d.ts +8 -0
  53. package/build/components/VerificationInput/VerificationInput.utils.js +17 -0
  54. package/build/components/VerificationInput/VerificationInput.utils.test.d.ts +1 -0
  55. package/build/components/VerificationInput/VerificationInput.utils.test.js +36 -0
  56. package/build/components/VerificationInput/VerificationInputSlot.d.ts +7 -3
  57. package/build/components/VerificationInput/VerificationInputSlot.js +45 -7
  58. package/docs/changelog.mdx +249 -0
  59. package/package.json +3 -3
  60. package/src/components/List/List.tsx +5 -4
  61. package/src/components/Modal/Modal.docs.mdx +1 -0
  62. package/src/components/Modal/Modal.props.ts +1 -0
  63. package/src/components/Modal/Modal.stories.tsx +1 -0
  64. package/src/components/Modal/Modal.tsx +21 -3
  65. package/src/components/VerificationInput/VerificationInput.tsx +218 -29
  66. package/src/components/VerificationInput/VerificationInputSlot.tsx +90 -14
  67. package/.turbo/turbo-lint.log +0 -72
  68. package/build/components/VerificationInput/useVerificationInput.d.ts +0 -15
  69. package/build/components/VerificationInput/useVerificationInput.js +0 -73
  70. package/src/components/VerificationInput/useVerificationInput.ts +0 -88
@@ -0,0 +1,199 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Children, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { 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 { useStyleProps } from '../../hooks';
7
+ import { SegmentedControlContext } from './SegmentedControl.context';
8
+ const Indicator = Animated.createAnimatedComponent(View);
9
+ const GROUP_BORDER_WIDTH = 1;
10
+ const SegmentedControl = ({ value: controlledValue, defaultValue, onValueChange, size = 'sm', disabled = false, children, style, ...props }) => {
11
+ const { computedStyles, remainingProps } = useStyleProps(props);
12
+ const isReducedMotion = useReducedMotion();
13
+ const indicatorPositionOffset = GROUP_BORDER_WIDTH;
14
+ const optionValues = useMemo(() => {
15
+ const values = [];
16
+ const walk = (node) => {
17
+ Children.forEach(node, child => {
18
+ if (!isValidElement(child))
19
+ return;
20
+ const childType = child.type;
21
+ const childProps = child.props;
22
+ if (childType?.displayName === 'SegmentedControlOption' &&
23
+ typeof childProps?.value === 'string') {
24
+ values.push(childProps.value);
25
+ }
26
+ if (childProps?.children) {
27
+ walk(childProps.children);
28
+ }
29
+ });
30
+ };
31
+ walk(children);
32
+ return values;
33
+ }, [children]);
34
+ const optionValuesKey = useMemo(() => optionValues.join('|'), [optionValues]);
35
+ const optionValuesRef = useRef(optionValues);
36
+ useEffect(() => {
37
+ optionValuesRef.current = optionValues;
38
+ }, [optionValues]);
39
+ const getInitialValue = () => {
40
+ if (controlledValue !== undefined)
41
+ return controlledValue;
42
+ if (defaultValue !== undefined)
43
+ return defaultValue;
44
+ return optionValues[0];
45
+ };
46
+ const [uncontrolledValue, setUncontrolledValue] = useState(getInitialValue);
47
+ useEffect(() => {
48
+ if (controlledValue !== undefined) {
49
+ setUncontrolledValue(controlledValue);
50
+ }
51
+ }, [controlledValue]);
52
+ useEffect(() => {
53
+ const currentOptionValues = optionValuesRef.current;
54
+ setUncontrolledValue(prev => {
55
+ if (!prev)
56
+ return currentOptionValues[0];
57
+ if (!currentOptionValues.includes(prev))
58
+ return currentOptionValues[0];
59
+ return prev;
60
+ });
61
+ }, [optionValuesKey]);
62
+ const currentValue = controlledValue !== undefined ? controlledValue : uncontrolledValue;
63
+ const indicatorX = useSharedValue(0);
64
+ const indicatorWidth = useSharedValue(0);
65
+ const indicatorY = useSharedValue(0);
66
+ const indicatorHeight = useSharedValue(0);
67
+ const [hasIndicator, setHasIndicator] = useState(false);
68
+ const layoutsRef = useRef(new Map());
69
+ const prevValueRef = useRef(undefined);
70
+ const initialisedRef = useRef(false);
71
+ const select = useCallback((nextValue) => {
72
+ if (disabled)
73
+ return;
74
+ if (controlledValue === undefined) {
75
+ setUncontrolledValue(nextValue);
76
+ }
77
+ onValueChange?.(nextValue);
78
+ }, [controlledValue, disabled, onValueChange]);
79
+ const registerOptionLayout = useCallback((value, layout) => {
80
+ layoutsRef.current.set(value, layout);
81
+ const activeValue = controlledValue !== undefined ? controlledValue : uncontrolledValue;
82
+ if (!activeValue || activeValue !== value)
83
+ return;
84
+ if (!initialisedRef.current) {
85
+ indicatorX.value = Math.max(0, layout.x - indicatorPositionOffset);
86
+ indicatorWidth.value = layout.width;
87
+ indicatorY.value = Math.max(0, layout.y - indicatorPositionOffset);
88
+ indicatorHeight.value = layout.height;
89
+ prevValueRef.current = activeValue;
90
+ initialisedRef.current = true;
91
+ setHasIndicator(true);
92
+ return;
93
+ }
94
+ if (prevValueRef.current === activeValue)
95
+ return;
96
+ const config = {
97
+ delay: 200,
98
+ duration: isReducedMotion ? 0 : 220,
99
+ easing: Easing.out(Easing.cubic),
100
+ };
101
+ indicatorX.value = withTiming(Math.max(0, layout.x - indicatorPositionOffset), config);
102
+ indicatorWidth.value = withTiming(layout.width, config);
103
+ indicatorY.value = withTiming(Math.max(0, layout.y - indicatorPositionOffset), config);
104
+ indicatorHeight.value = withTiming(layout.height, config);
105
+ prevValueRef.current = activeValue;
106
+ }, [
107
+ controlledValue,
108
+ indicatorHeight,
109
+ indicatorWidth,
110
+ indicatorX,
111
+ indicatorY,
112
+ indicatorPositionOffset,
113
+ isReducedMotion,
114
+ uncontrolledValue,
115
+ ]);
116
+ useEffect(() => {
117
+ if (!currentValue || !initialisedRef.current)
118
+ return;
119
+ if (prevValueRef.current === undefined || prevValueRef.current === currentValue)
120
+ return;
121
+ const layout = layoutsRef.current.get(currentValue);
122
+ if (!layout)
123
+ return;
124
+ const config = {
125
+ duration: isReducedMotion ? 0 : 220,
126
+ easing: Easing.out(Easing.cubic),
127
+ };
128
+ indicatorX.value = withTiming(Math.max(0, layout.x - indicatorPositionOffset), config);
129
+ indicatorWidth.value = withTiming(layout.width, config);
130
+ indicatorY.value = withTiming(Math.max(0, layout.y - indicatorPositionOffset), config);
131
+ indicatorHeight.value = withTiming(layout.height, config);
132
+ prevValueRef.current = currentValue;
133
+ }, [
134
+ currentValue,
135
+ indicatorHeight,
136
+ indicatorWidth,
137
+ indicatorX,
138
+ indicatorY,
139
+ indicatorPositionOffset,
140
+ isReducedMotion,
141
+ optionValuesKey,
142
+ ]);
143
+ const indicatorStyle = useAnimatedStyle(() => ({
144
+ transform: [{ translateX: indicatorX.value }, { translateY: indicatorY.value }],
145
+ width: indicatorWidth.value,
146
+ height: indicatorHeight.value,
147
+ }));
148
+ styles.useVariants({ disabled, size });
149
+ const contextValue = useMemo(() => ({
150
+ value: currentValue,
151
+ select,
152
+ disabled,
153
+ size,
154
+ registerOptionLayout,
155
+ }), [currentValue, select, disabled, size, registerOptionLayout]);
156
+ return (_jsx(SegmentedControlContext.Provider, { value: contextValue, children: _jsxs(View, { accessibilityRole: "radiogroup", accessibilityState: { disabled }, style: [styles.container, computedStyles, style], ...remainingProps, children: [hasIndicator ? (_jsx(Indicator, { style: [styles.indicator, styles.pointerEventsNone, indicatorStyle] })) : null, children] }) }));
157
+ };
158
+ SegmentedControl.displayName = 'SegmentedControl';
159
+ const styles = StyleSheet.create(theme => ({
160
+ container: {
161
+ flexDirection: 'row',
162
+ alignItems: 'center',
163
+ alignSelf: 'flex-start',
164
+ gap: theme.components.segmentedControl.group.gap,
165
+ height: theme.components.segmentedControl.group.height,
166
+ borderRadius: theme.components.segmentedControl.group.borderRadius,
167
+ borderWidth: theme.components.segmentedControl.group.borderWidth,
168
+ backgroundColor: theme.color.surface.neutral.subtle,
169
+ borderColor: theme.color.border.strong,
170
+ variants: {
171
+ size: {
172
+ sm: {
173
+ height: 32,
174
+ padding: 2,
175
+ },
176
+ md: {
177
+ height: theme.components.segmentedControl.group.height,
178
+ padding: 2,
179
+ },
180
+ },
181
+ disabled: {
182
+ true: {
183
+ opacity: theme.opacity.disabled,
184
+ },
185
+ },
186
+ },
187
+ },
188
+ indicator: {
189
+ position: 'absolute',
190
+ left: 0,
191
+ top: 0,
192
+ borderRadius: theme.components.segmentedControl.borderRadius,
193
+ backgroundColor: theme.color.interactive.brand.surface.strong.default,
194
+ },
195
+ pointerEventsNone: {
196
+ pointerEvents: 'none',
197
+ },
198
+ }));
199
+ export default SegmentedControl;
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+ import type { FlexLayoutProps } from '../../types';
4
+ export interface SegmentedControlProps extends ViewProps, FlexLayoutProps {
5
+ /** Controlled selected option value. */
6
+ value?: string;
7
+ /** Initial selected option value for uncontrolled mode. */
8
+ defaultValue?: string;
9
+ /** Called when selected option changes. */
10
+ onValueChange?: (value: string) => void;
11
+ /** Size variant. */
12
+ size?: 'sm' | 'md';
13
+ /** Disables all options in the control. */
14
+ disabled?: boolean;
15
+ /** SegmentedControlOption children. */
16
+ children: ReactNode;
17
+ }
18
+ export default SegmentedControlProps;
@@ -0,0 +1,18 @@
1
+ import type SegmentedControlOptionProps from './SegmentedControlOption.props';
2
+ declare const SegmentedControlOption: import("react").ForwardRefExoticComponent<SegmentedControlOptionProps & {
3
+ states?: {
4
+ active?: boolean;
5
+ disabled?: boolean;
6
+ };
7
+ } & Omit<import("react-native").PressableProps, "children"> & {
8
+ tabIndex?: 0 | -1 | undefined;
9
+ } & {
10
+ children?: import("react").ReactNode | (({ hovered, pressed, focused, focusVisible, disabled, }: {
11
+ hovered?: boolean | undefined;
12
+ pressed?: boolean | undefined;
13
+ focused?: boolean | undefined;
14
+ focusVisible?: boolean | undefined;
15
+ disabled?: boolean | undefined;
16
+ }) => import("react").ReactNode);
17
+ } & import("react").RefAttributes<unknown>>;
18
+ export default SegmentedControlOption;
@@ -0,0 +1,144 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createPressable } from '@gluestack-ui/pressable';
3
+ import { useEffect } from 'react';
4
+ import { Platform, Pressable, View } from 'react-native';
5
+ import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated';
6
+ import { StyleSheet } from 'react-native-unistyles';
7
+ import { BodyText } from '../BodyText';
8
+ import { Icon } from '../Icon';
9
+ import { useSegmentedControlContext } from './SegmentedControl.context';
10
+ const AnimatedView = Animated.createAnimatedComponent(View);
11
+ const SegmentedControlOptionRoot = ({ value, children, icon, accessibilityLabel, disabled = false, style, states = {}, ...props }) => {
12
+ const { value: selectedValue, select, disabled: allDisabled, size, registerOptionLayout, } = useSegmentedControlContext();
13
+ const { active = false } = states;
14
+ const reducedMotion = useReducedMotion();
15
+ const selected = selectedValue === value;
16
+ const isDisabled = disabled || !!allDisabled;
17
+ const selectedProgress = useSharedValue(selected ? 1 : 0);
18
+ useEffect(() => {
19
+ selectedProgress.value = withTiming(selected ? 1 : 0, {
20
+ duration: reducedMotion ? 0 : 220,
21
+ easing: Easing.out(Easing.cubic),
22
+ });
23
+ }, [reducedMotion, selected, selectedProgress]);
24
+ const regularLabelStyle = useAnimatedStyle(() => ({
25
+ opacity: 1 - selectedProgress.value,
26
+ }));
27
+ const selectedLabelStyle = useAnimatedStyle(() => ({
28
+ opacity: selectedProgress.value,
29
+ }));
30
+ styles.useVariants({ selected, disabled: isDisabled, size, active });
31
+ const onPress = () => {
32
+ if (isDisabled)
33
+ return;
34
+ select(value);
35
+ };
36
+ const accessibleLabel = typeof children === 'string' || typeof children === 'number' ? String(children) : value;
37
+ return (_jsx(Pressable, { ...props, accessibilityRole: "radio", accessibilityState: { checked: selected, disabled: isDisabled }, accessibilityLabel: accessibilityLabel ?? accessibleLabel, onPress: onPress, onLayout: e => registerOptionLayout(value, e.nativeEvent.layout), disabled: isDisabled, style: [styles.option, style], ...(Platform.OS === 'web'
38
+ ? { 'aria-label': accessibilityLabel ?? accessibleLabel }
39
+ : null), children: _jsxs(View, { style: styles.contentWrap, accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: [icon ? _jsx(Icon, { as: icon, size: "sm", style: styles.icon }) : null, _jsxs(View, { style: styles.labelWrap, children: [_jsx(BodyText, { size: "md", weight: "semibold", style: styles.labelSizer, accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: children }), _jsx(AnimatedView, { style: [styles.textLayer, styles.pointerEventsNone, regularLabelStyle], accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: _jsx(BodyText, { size: "md", weight: "regular", style: styles.textRegular, children: children }) }), _jsx(AnimatedView, { style: [styles.textLayer, styles.pointerEventsNone, selectedLabelStyle], accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: _jsx(BodyText, { size: "md", weight: "semibold", style: styles.textSelected, children: children }) })] })] }) }));
40
+ };
41
+ const SegmentedControlOption = createPressable({ Root: SegmentedControlOptionRoot });
42
+ SegmentedControlOption.displayName = 'SegmentedControlOption';
43
+ const styles = StyleSheet.create(theme => ({
44
+ option: {
45
+ minWidth: theme.components.segmentedControl.minWidth,
46
+ height: theme.components.segmentedControl.height,
47
+ borderRadius: theme.components.segmentedControl.borderRadius,
48
+ paddingHorizontal: theme.components.segmentedControl.paddingHorizontal,
49
+ paddingVertical: theme.components.segmentedControl.paddingVertical,
50
+ justifyContent: 'center',
51
+ alignItems: 'center',
52
+ backgroundColor: 'transparent',
53
+ zIndex: 1,
54
+ variants: {
55
+ size: {
56
+ sm: {
57
+ height: 28,
58
+ paddingHorizontal: theme.space[150],
59
+ paddingVertical: 0,
60
+ },
61
+ md: {
62
+ height: 44,
63
+ paddingHorizontal: theme.components.segmentedControl.paddingHorizontal,
64
+ paddingVertical: theme.components.segmentedControl.paddingVertical,
65
+ },
66
+ },
67
+ selected: {
68
+ true: {
69
+ backgroundColor: 'transparent',
70
+ _web: {
71
+ _active: {
72
+ backgroundColor: theme.color.interactive.brand.surface.strong.active,
73
+ },
74
+ },
75
+ },
76
+ false: {
77
+ _web: {
78
+ _hover: {
79
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.hover,
80
+ },
81
+ _active: {
82
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
83
+ },
84
+ },
85
+ },
86
+ },
87
+ active: {
88
+ true: {
89
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
90
+ },
91
+ },
92
+ },
93
+ },
94
+ contentWrap: {
95
+ flexDirection: 'row',
96
+ alignItems: 'center',
97
+ justifyContent: 'center',
98
+ gap: theme.components.segmentedControl.gap,
99
+ },
100
+ labelWrap: {
101
+ position: 'relative',
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ },
105
+ labelSizer: {
106
+ opacity: 0,
107
+ },
108
+ textLayer: {
109
+ position: 'absolute',
110
+ left: 0,
111
+ right: 0,
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ },
115
+ pointerEventsNone: {
116
+ pointerEvents: 'none',
117
+ },
118
+ icon: {
119
+ variants: {
120
+ selected: {
121
+ true: {
122
+ color: theme.color.icon.inverted,
123
+ },
124
+ false: {
125
+ color: theme.color.icon.primary,
126
+ },
127
+ },
128
+ },
129
+ },
130
+ textRegular: {
131
+ color: theme.color.text.primary,
132
+ },
133
+ textSelected: {
134
+ color: theme.color.text.inverted,
135
+ variants: {
136
+ disabled: {
137
+ true: {
138
+ opacity: 1,
139
+ },
140
+ },
141
+ },
142
+ },
143
+ }));
144
+ export default SegmentedControlOption;
@@ -0,0 +1,14 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+ import type { PressableProps, ViewProps } from 'react-native';
3
+ export interface SegmentedControlOptionProps extends Omit<PressableProps, 'children'> {
4
+ /** Unique option value. */
5
+ value: string;
6
+ /** Option label/content. */
7
+ children: ReactNode;
8
+ /** Optional leading icon. */
9
+ icon?: ComponentType<any>;
10
+ /** Disables only this option. */
11
+ disabled?: boolean;
12
+ style?: ViewProps['style'];
13
+ }
14
+ export default SegmentedControlOptionProps;
@@ -0,0 +1,4 @@
1
+ export { default as SegmentedControl } from './SegmentedControl';
2
+ export type { SegmentedControlProps } from './SegmentedControl.props';
3
+ export { default as SegmentedControlOption } from './SegmentedControlOption';
4
+ export type { SegmentedControlOptionProps } from './SegmentedControlOption.props';
@@ -0,0 +1,2 @@
1
+ export { default as SegmentedControl } from './SegmentedControl';
2
+ export { default as SegmentedControlOption } from './SegmentedControlOption';
@@ -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;