react-native-molecules 0.5.0-beta.17 → 0.5.0-beta.19

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.
@@ -1,52 +1,19 @@
1
- import { Fragment, memo, useCallback, useEffect, useLayoutEffect, useRef } from 'react';
2
- import { AppState, Dimensions, Platform, Pressable, View } from 'react-native';
3
- import { ScopedTheme, UnistylesRuntime } from 'react-native-unistyles';
1
+ import { useCallback, useEffect, useLayoutEffect } from 'react';
2
+ import { AppState, Dimensions, Platform } from 'react-native';
4
3
 
5
- import { Portal } from '../Portal';
6
- import {
7
- DEFAULT_ARROW_SIZE,
8
- popoverDefaultStyles,
9
- type PopoverProps,
10
- useArrowStyles,
11
- usePopover,
12
- } from './common';
13
- import { popoverStyles } from './utils';
4
+ import { popoverDefaultStyles } from './common';
5
+ import type { UsePlatformMeasureArgs, UsePlatformMeasureResult } from './usePlatformMeasure';
14
6
 
15
- const Popover = ({
7
+ export const usePlatformMeasure = ({
16
8
  triggerRef,
17
- children,
18
9
  isOpen,
19
- onClose,
20
- position = 'bottom',
21
- align = 'center',
22
- style,
23
- showArrow = false,
24
- arrowSize = DEFAULT_ARROW_SIZE,
25
- inverted = false,
10
+ calculatedPosition,
11
+ calculateAndSetPosition,
12
+ targetLayoutRef,
26
13
  triggerDimensions,
27
- offset = 8,
28
- ...rest
29
- }: PopoverProps) => {
30
- const {
31
- popoverLayoutRef,
32
- targetLayoutRef,
33
- actualPositionRef,
34
- calculatedPosition,
35
- calculateAndSetPosition,
36
- handlePopoverLayout,
37
- } = usePopover({
38
- isOpen,
39
- position,
40
- align,
41
- showArrow,
42
- arrowSize,
43
- offset,
44
- });
45
-
46
- const popoverRef = useRef<View>(null);
47
-
14
+ }: UsePlatformMeasureArgs): UsePlatformMeasureResult => {
48
15
  const measureTarget = useCallback(() => {
49
- if (triggerRef.current) {
16
+ if (triggerRef?.current) {
50
17
  triggerRef.current.measure(
51
18
  (
52
19
  _fx: number,
@@ -117,48 +84,7 @@ const Popover = ({
117
84
  };
118
85
  }, [isOpen, measureTarget]);
119
86
 
120
- const arrowStyles = useArrowStyles({
121
- showArrow,
122
- arrowSize,
123
- style,
124
- calculatedPosition,
125
- targetLayoutRef,
126
- popoverLayoutRef,
127
- actualPositionRef,
128
- });
129
-
130
- const popoverStyle = calculatedPosition ?? popoverDefaultStyles;
131
- const Wrapper = inverted ? ScopedTheme : Fragment;
132
-
133
- if (!isOpen && popoverStyle.opacity === 0) {
134
- return null;
135
- }
136
-
137
- const handleOutsidePress = () => {
138
- if (isOpen && onClose) {
139
- onClose();
140
- }
87
+ return {
88
+ popoverStyle: (calculatedPosition ?? popoverDefaultStyles) as any,
141
89
  };
142
-
143
- return (
144
- <Portal>
145
- <Wrapper
146
- {...(inverted
147
- ? { name: UnistylesRuntime.themeName === 'dark' ? 'light' : 'dark' }
148
- : ({} as { name: 'light' }))}>
149
- <Pressable onPress={handleOutsidePress} style={popoverStyles.overlay} />
150
-
151
- <View
152
- ref={popoverRef}
153
- onLayout={handlePopoverLayout}
154
- style={[popoverStyles.popoverContainer, style, popoverStyle]}
155
- {...rest}>
156
- {children}
157
- {showArrow && popoverStyle.opacity === 1 && <View style={arrowStyles} />}
158
- </View>
159
- </Wrapper>
160
- </Portal>
161
- );
162
90
  };
163
-
164
- export default memo(Popover);
@@ -0,0 +1,118 @@
1
+ import { type RefObject, useCallback, useEffect, useLayoutEffect, useMemo } from 'react';
2
+ import type { LayoutRectangle, View, ViewStyle } from 'react-native';
3
+
4
+ import { popoverDefaultStyles } from './common';
5
+
6
+ export type UsePlatformMeasureArgs = {
7
+ triggerRef: RefObject<View | any> | undefined;
8
+ isOpen: boolean;
9
+ onClose?: () => void;
10
+ calculatedPosition: ViewStyle | null;
11
+ calculateAndSetPosition: () => void;
12
+ targetLayoutRef: RefObject<LayoutRectangle | null>;
13
+ popoverRef: RefObject<View | null>;
14
+ triggerDimensions?: { width: number; height: number } | null;
15
+ };
16
+
17
+ export type UsePlatformMeasureResult = {
18
+ /** Platform-adjusted popover position (includes scroll offset on web) */
19
+ popoverStyle: ViewStyle;
20
+ };
21
+
22
+ export const usePlatformMeasure = ({
23
+ triggerRef,
24
+ isOpen,
25
+ onClose,
26
+ calculatedPosition,
27
+ calculateAndSetPosition,
28
+ targetLayoutRef,
29
+ popoverRef,
30
+ triggerDimensions,
31
+ }: UsePlatformMeasureArgs): UsePlatformMeasureResult => {
32
+ const measureTarget = useCallback(() => {
33
+ if (triggerRef?.current) {
34
+ triggerRef.current.measureInWindow(
35
+ (x: number, y: number, width: number, height: number) => {
36
+ if (width !== 0 || height !== 0) {
37
+ const newLayout = { x, y, width, height };
38
+ const changed =
39
+ !targetLayoutRef.current ||
40
+ targetLayoutRef.current.x !== newLayout.x ||
41
+ targetLayoutRef.current.y !== newLayout.y ||
42
+ targetLayoutRef.current.width !== newLayout.width ||
43
+ targetLayoutRef.current.height !== newLayout.height;
44
+
45
+ if (changed) {
46
+ targetLayoutRef.current = newLayout;
47
+ calculateAndSetPosition();
48
+ }
49
+ } else {
50
+ targetLayoutRef.current = null;
51
+ calculateAndSetPosition();
52
+ }
53
+ },
54
+ );
55
+ } else {
56
+ targetLayoutRef.current = null;
57
+ calculateAndSetPosition();
58
+ }
59
+ }, [triggerRef, calculateAndSetPosition, targetLayoutRef]);
60
+
61
+ useLayoutEffect(() => {
62
+ if (isOpen) {
63
+ const timeoutId = setTimeout(measureTarget, 0);
64
+ return () => clearTimeout(timeoutId);
65
+ }
66
+ return;
67
+ }, [isOpen, measureTarget, triggerDimensions]);
68
+
69
+ useLayoutEffect(() => {
70
+ if (!isOpen) return;
71
+ const handleResize = () => {
72
+ if (triggerRef?.current && isOpen) {
73
+ window.requestAnimationFrame(measureTarget);
74
+ }
75
+ };
76
+ window.addEventListener('resize', handleResize);
77
+ window.addEventListener('scroll', handleResize, true);
78
+ return () => {
79
+ window.removeEventListener('resize', handleResize);
80
+ window.removeEventListener('scroll', handleResize, true);
81
+ };
82
+ }, [isOpen, measureTarget, triggerRef]);
83
+
84
+ useEffect(() => {
85
+ if (!isOpen || !onClose) return;
86
+ const handleClickOutside = (event: MouseEvent) => {
87
+ const popoverElement = popoverRef.current as any as HTMLElement;
88
+ const targetElement = triggerRef?.current as any as HTMLElement;
89
+ if (
90
+ popoverElement &&
91
+ !popoverElement.contains(event.target as Node) &&
92
+ targetElement &&
93
+ !targetElement.contains(event.target as Node)
94
+ ) {
95
+ onClose();
96
+ }
97
+ };
98
+ document.addEventListener('mousedown', handleClickOutside, { capture: true });
99
+ return () => {
100
+ document.removeEventListener('mousedown', handleClickOutside, { capture: true });
101
+ };
102
+ }, [isOpen, onClose, popoverRef, triggerRef]);
103
+
104
+ const popoverStyle = useMemo(() => {
105
+ if (!calculatedPosition) return popoverDefaultStyles;
106
+
107
+ const scrollX = window.scrollX ?? window.pageXOffset ?? 0;
108
+ const scrollY = window.scrollY ?? window.pageYOffset ?? 0;
109
+
110
+ return {
111
+ ...calculatedPosition,
112
+ left: (calculatedPosition.left as number) + scrollX,
113
+ top: (calculatedPosition.top as number) + scrollY,
114
+ };
115
+ }, [calculatedPosition]);
116
+
117
+ return { popoverStyle };
118
+ };
@@ -15,24 +15,17 @@ const popoverStylesDefault = StyleSheet.create(theme => ({
15
15
  elevation: 5,
16
16
  zIndex: 100,
17
17
  },
18
- backdrop: {
18
+ overlay: {
19
19
  position: 'absolute',
20
20
  top: 0,
21
21
  left: 0,
22
22
  right: 0,
23
23
  bottom: 0,
24
+ backgroundColor: 'transparent',
24
25
  _web: {
25
26
  cursor: 'default',
26
27
  },
27
28
  },
28
- overlay: {
29
- position: 'absolute',
30
- top: 0,
31
- bottom: 0,
32
- left: 0,
33
- right: 0,
34
- backgroundColor: 'transparent',
35
- },
36
29
  }));
37
30
 
38
31
  export const popoverStyles = getRegisteredComponentStylesWithFallback(
@@ -1,24 +1,28 @@
1
- import { forwardRef, memo, useCallback, useMemo, useState } from 'react';
2
- import { StyleSheet, View } from 'react-native';
1
+ import { forwardRef, memo, useCallback, useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ type NativeSyntheticEvent,
4
+ StyleSheet,
5
+ TextInput as NativeTextInput,
6
+ type TextInputProps as NativeTextInputProps,
7
+ type TextInputSelectionChangeEventData,
8
+ View,
9
+ } from 'react-native';
3
10
 
4
11
  import { useTheme } from '../../hooks';
5
12
  import { resolveStateVariant } from '../../utils';
6
- import { TextInput, type TextInputProps } from '../TextInput';
7
13
  import { TouchableRipple } from '../TouchableRipple';
8
14
  import { inputTypes, type PossibleClockTypes, type PossibleInputTypes } from './timeUtils';
9
15
  import { timePickerInputStyles } from './utils';
10
16
 
11
- interface TimeInputProps
12
- extends Omit<
13
- Omit<TextInputProps, 'value' | 'variant' | 'onChangeText' | 'onPress'>,
14
- 'onFocus'
15
- > {
17
+ interface TimeInputProps extends Omit<NativeTextInputProps, 'value' | 'onChangeText' | 'onPress'> {
16
18
  value: number;
17
19
  clockType: PossibleClockTypes;
18
20
  onPress?: (type: PossibleClockTypes) => any;
19
21
  pressed: boolean;
20
- onChanged: (n: number) => any;
22
+ onChanged: (n: number, text: string) => any;
21
23
  inputType: PossibleInputTypes;
24
+ error?: boolean;
25
+ inputStyle?: NativeTextInputProps['style'];
22
26
  }
23
27
 
24
28
  function TimeInput(
@@ -29,18 +33,22 @@ function TimeInput(
29
33
  onPress,
30
34
  onChanged,
31
35
  inputType,
36
+ error = false,
32
37
  inputStyle,
33
38
  style,
34
39
  ...rest
35
40
  }: TimeInputProps,
36
41
  ref: any,
37
42
  ) {
38
- const onInnerChange = (text: string) => {
39
- onChanged(Number(text));
40
- };
41
-
42
43
  const theme = useTheme();
43
44
  const [inputFocused, setInputFocused] = useState<boolean>(false);
45
+ const [rawText, setRawText] = useState<string | null>(null);
46
+ const [selection, setSelection] = useState<{ start: number; end: number } | undefined>();
47
+
48
+ const onInnerChange = (text: string) => {
49
+ setRawText(text);
50
+ onChanged(Number(text), text);
51
+ };
44
52
 
45
53
  const highlighted = inputType === inputTypes.picker ? pressed : inputFocused;
46
54
 
@@ -52,46 +60,88 @@ function TimeInput(
52
60
  });
53
61
 
54
62
  const formattedValue = useMemo(() => {
55
- let _formattedValue = `${value}`;
56
-
57
- if (!inputFocused) {
58
- _formattedValue = `${value}`.length === 1 ? `0${value}` : `${value}`;
59
- }
63
+ if (rawText !== null && (inputFocused || error)) return rawText;
60
64
 
61
- return _formattedValue;
62
- }, [value, inputFocused]);
65
+ const str = `${value}`;
66
+ return str.length === 1 ? `0${str}` : str;
67
+ }, [value, inputFocused, rawText, error]);
63
68
 
64
- const { rippleColor, containerStyle, textInputContainerStyle, textInputStyle, buttonStyle } =
65
- useMemo(() => {
66
- const { container, input, button } = timePickerInputStyles;
69
+ const { rippleColor, containerStyle, textInputStyle, buttonStyle } = useMemo(() => {
70
+ const {
71
+ container,
72
+ input,
73
+ keyboardInput,
74
+ keyboardInputHighlighted,
75
+ inputError,
76
+ keyboardInputError,
77
+ button,
78
+ } = timePickerInputStyles;
79
+ const isKeyboardInput = inputType === inputTypes.keyboard;
67
80
 
68
- return {
69
- rippleColor: timePickerInputStyles.root?._rippleColor,
70
- containerStyle: container,
71
- textInputContainerStyle: [{ paddingHorizontal: 0 }, style],
72
- textInputStyle: [input, inputStyle],
73
- buttonStyle: [StyleSheet.absoluteFill, button],
74
- };
75
- // eslint-disable-next-line react-hooks/exhaustive-deps
76
- }, [inputStyle, style, state]);
81
+ return {
82
+ rippleColor: timePickerInputStyles.root?._rippleColor,
83
+ containerStyle: container,
84
+ textInputStyle: [
85
+ input,
86
+ isKeyboardInput ? keyboardInput : null,
87
+ isKeyboardInput && highlighted ? keyboardInputHighlighted : null,
88
+ error ? inputError : null,
89
+ isKeyboardInput && error ? keyboardInputError : null,
90
+ style,
91
+ inputStyle,
92
+ ],
93
+ buttonStyle: [StyleSheet.absoluteFill, button],
94
+ };
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ }, [error, highlighted, inputStyle, inputType, style, state]);
77
97
 
78
98
  const onFocus = useCallback(() => setInputFocused(true), []);
79
- const onBlur = useCallback(() => setInputFocused(false), []);
99
+
100
+ const onBlur = useCallback(() => {
101
+ setInputFocused(false);
102
+ setSelection(undefined);
103
+ if (!error) {
104
+ setRawText(null);
105
+ }
106
+ }, [error]);
107
+
80
108
  const onPressInput = useCallback(() => onPress?.(clockType), [clockType, onPress]);
81
109
 
110
+ const onSelectionChange = useCallback(
111
+ (e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
112
+ if (selection) {
113
+ setSelection(undefined);
114
+ }
115
+
116
+ rest.onSelectionChange?.(e);
117
+ },
118
+ [rest, selection],
119
+ );
120
+
121
+ useEffect(() => {
122
+ if (error && inputFocused && rawText?.length) {
123
+ setSelection({ start: 0, end: rawText.length });
124
+ return;
125
+ }
126
+
127
+ setSelection(undefined);
128
+ }, [error, inputFocused, rawText]);
129
+
82
130
  return (
83
131
  <View style={containerStyle}>
84
- <TextInput
85
- variant="plain"
132
+ <NativeTextInput
86
133
  ref={ref}
87
- inputStyle={textInputStyle}
88
- style={textInputContainerStyle}
89
134
  onFocus={onFocus}
90
135
  onBlur={onBlur}
91
136
  keyboardAppearance={theme.dark ? 'dark' : 'default'}
92
137
  value={formattedValue}
93
138
  maxLength={2}
139
+ placeholderTextColor={theme.colors.onSurfaceVariant}
140
+ selectTextOnFocus={inputType === inputTypes.picker || error}
141
+ selection={selection}
142
+ onSelectionChange={onSelectionChange}
94
143
  onChangeText={onInnerChange}
144
+ style={textInputStyle}
95
145
  {...rest}
96
146
  />
97
147
  <>
@@ -1,11 +1,12 @@
1
1
  // @typescript-eslint/no-unused-vars
2
2
  // WORK IN PROGRESS
3
3
 
4
- import { memo, useCallback, useRef } from 'react';
4
+ import { memo, useCallback, useEffect, useRef, useState } from 'react';
5
5
  import { TextInput as TextInputNative, useWindowDimensions, View } from 'react-native';
6
6
 
7
7
  import { useLatest } from '../../hooks';
8
8
  import { resolveStateVariant } from '../../utils';
9
+ import { Text } from '../Text';
9
10
  import AmPmSwitcher from './AmPmSwitcher';
10
11
  import TimeInput from './TimeInput';
11
12
  import {
@@ -41,6 +42,8 @@ function TimeInputs({
41
42
  }: Props) {
42
43
  const dimensions = useWindowDimensions();
43
44
  const isLandscape = dimensions.width > dimensions.height;
45
+ const [hourError, setHourError] = useState(false);
46
+ const [minuteError, setMinuteError] = useState(false);
44
47
 
45
48
  timePickerInputsStyles.useVariants({
46
49
  state: resolveStateVariant({
@@ -50,6 +53,17 @@ function TimeInputs({
50
53
  const startInput = useRef<TextInputNative | null>(null);
51
54
  const endInput = useRef<TextInputNative | null>(null);
52
55
 
56
+ useEffect(() => {
57
+ if (inputType !== 'keyboard') {
58
+ setHourError(false);
59
+ setMinuteError(false);
60
+ return;
61
+ }
62
+
63
+ const id = setTimeout(() => startInput.current?.focus(), 0);
64
+ return () => clearTimeout(id);
65
+ }, [inputType]);
66
+
53
67
  const onSubmitStartInput = useCallback(() => {
54
68
  if (endInput.current) {
55
69
  endInput.current.focus();
@@ -61,6 +75,9 @@ function TimeInputs({
61
75
  }, []);
62
76
 
63
77
  const minutesRef = useLatest(minutes);
78
+ const isPm = hours >= 12;
79
+ const hourErrorText = is24Hour ? 'Hour must be 0-23' : 'Hour must be 1-12';
80
+ const minuteErrorText = 'Minute must be 0-59';
64
81
  const onChangeHours = useCallback(
65
82
  (newHours: number) => {
66
83
  onChange({
@@ -73,74 +90,139 @@ function TimeInputs({
73
90
  );
74
91
 
75
92
  const onHourChange = useCallback(
76
- (newHoursFromInput: number) => {
93
+ (newHoursFromInput: number, text: string) => {
94
+ if (text.length === 0) {
95
+ setHourError(false);
96
+ return;
97
+ }
98
+
99
+ const isNumeric = /^\d+$/.test(text);
100
+ const minHours = is24Hour ? 0 : 1;
101
+ const maxHours = is24Hour ? 23 : 12;
102
+ const isValid =
103
+ isNumeric && newHoursFromInput >= minHours && newHoursFromInput <= maxHours;
104
+
105
+ setHourError(!isValid);
106
+
107
+ if (!isValid) {
108
+ return;
109
+ }
110
+
77
111
  let newHours = newHoursFromInput;
78
- if (newHoursFromInput >= 24) {
79
- newHours = 0;
112
+ if (!is24Hour) {
113
+ if (isPm) {
114
+ newHours = newHoursFromInput === 12 ? 12 : newHoursFromInput + 12;
115
+ } else {
116
+ newHours = newHoursFromInput === 12 ? 0 : newHoursFromInput;
117
+ }
80
118
  }
119
+
81
120
  onChange({
82
121
  hours: newHours,
83
- minutes,
122
+ minutes: minutesRef.current,
84
123
  });
124
+
125
+ const maxStartDigit = is24Hour ? 2 : 1;
126
+ const shouldAdvance = text.length >= 2 || newHoursFromInput > maxStartDigit;
127
+ if (shouldAdvance) endInput.current?.focus();
85
128
  },
86
- [minutes, onChange],
129
+ [is24Hour, isPm, minutesRef, onChange],
87
130
  );
88
131
 
89
132
  const onMinuteChange = useCallback(
90
- (newMinutesFromInput: number) => {
91
- let newMinutes = newMinutesFromInput;
133
+ (newMinutesFromInput: number, text: string) => {
134
+ if (text.length === 0) {
135
+ setMinuteError(false);
136
+ return;
137
+ }
138
+
139
+ const isNumeric = /^\d+$/.test(text);
140
+ const isValid = isNumeric && newMinutesFromInput >= 0 && newMinutesFromInput <= 59;
92
141
 
93
- if (newMinutesFromInput > 59) {
94
- newMinutes = 0;
142
+ setMinuteError(!isValid);
143
+
144
+ if (!isValid) {
145
+ return;
95
146
  }
147
+
96
148
  onChange({
97
149
  hours: hours,
98
- minutes: newMinutes,
150
+ minutes: newMinutesFromInput,
99
151
  });
100
152
  },
101
153
  [hours, onChange],
102
154
  );
103
155
 
104
156
  return (
105
- <View style={timePickerInputsStyles.inputContainer}>
106
- <TimeInput
107
- ref={startInput}
108
- placeholder={'00'}
109
- value={toHourInputFormat(hours, is24Hour)}
110
- clockType={clockTypes.hours}
111
- pressed={focused === clockTypes.hours}
112
- onPress={onFocusInput}
113
- inputType={inputType}
114
- returnKeyType={'next'}
115
- onSubmitEditing={onSubmitStartInput}
116
- blurOnSubmit={false}
117
- onChanged={onHourChange}
118
- // onChangeText={onChangeStartInput}
119
- />
120
- <View style={timePickerInputsStyles.hoursAndMinutesSeparator}>
121
- <View style={timePickerInputsStyles.spaceDot} />
122
- <View style={timePickerInputsStyles.dot} />
123
- <View style={timePickerInputsStyles.betweenDot} />
124
- <View style={timePickerInputsStyles.dot} />
125
- <View style={timePickerInputsStyles.spaceDot} />
157
+ <View style={timePickerInputsStyles.wrapper}>
158
+ <View style={timePickerInputsStyles.inputContainer}>
159
+ <TimeInput
160
+ ref={startInput}
161
+ placeholder={''}
162
+ value={toHourInputFormat(hours, is24Hour)}
163
+ clockType={clockTypes.hours}
164
+ pressed={focused === clockTypes.hours}
165
+ onPress={onFocusInput}
166
+ inputType={inputType}
167
+ returnKeyType={'next'}
168
+ onSubmitEditing={onSubmitStartInput}
169
+ blurOnSubmit={false}
170
+ error={hourError}
171
+ onChanged={onHourChange}
172
+ // onChangeText={onChangeStartInput}
173
+ />
174
+ <View style={timePickerInputsStyles.hoursAndMinutesSeparator}>
175
+ <View style={timePickerInputsStyles.spaceDot} />
176
+ <View style={timePickerInputsStyles.dot} />
177
+ <View style={timePickerInputsStyles.betweenDot} />
178
+ <View style={timePickerInputsStyles.dot} />
179
+ <View style={timePickerInputsStyles.spaceDot} />
180
+ </View>
181
+ <TimeInput
182
+ ref={endInput}
183
+ placeholder={'00'}
184
+ value={minutes}
185
+ clockType={clockTypes.minutes}
186
+ pressed={focused === clockTypes.minutes}
187
+ onPress={onFocusInput}
188
+ inputType={inputType}
189
+ error={minuteError}
190
+ onSubmitEditing={onSubmitEndInput}
191
+ onChanged={onMinuteChange}
192
+ />
193
+ {!is24Hour && (
194
+ <>
195
+ <View style={timePickerInputsStyles.spaceBetweenInputsAndSwitcher} />
196
+ <AmPmSwitcher hours={hours} onChange={onChangeHours} />
197
+ </>
198
+ )}
126
199
  </View>
127
- <TimeInput
128
- ref={endInput}
129
- placeholder={'00'}
130
- value={minutes}
131
- clockType={clockTypes.minutes}
132
- pressed={focused === clockTypes.minutes}
133
- onPress={onFocusInput}
134
- inputType={inputType}
135
- onSubmitEditing={onSubmitEndInput}
136
- onChanged={onMinuteChange}
137
- />
138
- {!is24Hour && (
139
- <>
140
- <View style={timePickerInputsStyles.spaceBetweenInputsAndSwitcher} />
141
- <AmPmSwitcher hours={hours} onChange={onChangeHours} />
142
- </>
143
- )}
200
+ {inputType === 'keyboard' ? (
201
+ <View style={timePickerInputsStyles.supportingRow}>
202
+ <View style={timePickerInputsStyles.supportingSlot}>
203
+ <Text
204
+ style={[
205
+ timePickerInputsStyles.supportingText,
206
+ hourError ? timePickerInputsStyles.supportingTextError : null,
207
+ ]}>
208
+ {hourError ? hourErrorText : 'Hour'}
209
+ </Text>
210
+ </View>
211
+ <View style={timePickerInputsStyles.hoursAndMinutesSeparator} />
212
+ <View style={timePickerInputsStyles.supportingSlot}>
213
+ <Text
214
+ style={[
215
+ timePickerInputsStyles.supportingText,
216
+ minuteError ? timePickerInputsStyles.supportingTextError : null,
217
+ ]}>
218
+ {minuteError ? minuteErrorText : 'Minute'}
219
+ </Text>
220
+ </View>
221
+ {!is24Hour && (
222
+ <View style={timePickerInputsStyles.spaceBetweenInputsAndSwitcher} />
223
+ )}
224
+ </View>
225
+ ) : null}
144
226
  </View>
145
227
  );
146
228
  }