Skip to content

Commit 2cc1ff3

Browse files
Time picker enhancements to input + tab and onEnter to next input
1 parent 780f2c9 commit 2cc1ff3

File tree

5 files changed

+302
-150
lines changed

5 files changed

+302
-150
lines changed

src/Date/CalendarEdit.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { CalendarDate, ModeType } from './Calendar'
1010
import { LocalState } from './DatePickerModal'
1111
import TextInputWithMask from '../TextInputMask'
12+
import { useTheme } from 'react-native-paper'
1213

1314
function CalendarEdit({
1415
mode,
@@ -55,6 +56,20 @@ function CalendarEdit({
5556
}
5657
}, [mode, startInput, endInput, dateInput, collapsed])
5758

59+
const onSubmitStartInput = React.useCallback(() => {
60+
if (endInput.current) {
61+
endInput.current.focus()
62+
}
63+
}, [endInput])
64+
65+
const onSubmitEndInput = React.useCallback(() => {
66+
// TODO: close modal and persist range
67+
}, [])
68+
69+
const onSubmitInput = React.useCallback(() => {
70+
// TODO: close modal and persist range
71+
}, [])
72+
5873
return (
5974
<View style={styles.root}>
6075
<View style={styles.inner}>
@@ -64,6 +79,7 @@ function CalendarEdit({
6479
label={label}
6580
value={state.date}
6681
onChange={(date) => onChange({ ...state, date })}
82+
onSubmitEditing={onSubmitInput}
6783
/>
6884
) : null}
6985
{mode === 'range' ? (
@@ -73,6 +89,8 @@ function CalendarEdit({
7389
label={startLabel}
7490
value={state.startDate}
7591
onChange={(startDate) => onChange({ ...state, startDate })}
92+
returnKeyType={'next'}
93+
onSubmitEditing={onSubmitStartInput}
7694
/>
7795
<View style={styles.separator} />
7896
<CalendarInput
@@ -81,6 +99,7 @@ function CalendarEdit({
8199
value={state.endDate}
82100
onChange={(endDate) => onChange({ ...state, endDate })}
83101
isEndDate
102+
onSubmitEditing={onSubmitEndInput}
84103
/>
85104
</>
86105
) : null}
@@ -95,14 +114,19 @@ function CalendarInputPure(
95114
value,
96115
onChange,
97116
isEndDate,
117+
returnKeyType,
118+
onSubmitEditing,
98119
}: {
99120
label: string
100121
value: CalendarDate
101122
onChange: (d: Date | undefined) => any
102123
isEndDate?: boolean
124+
returnKeyType?: string
125+
onSubmitEditing?: () => any
103126
},
104127
ref: any
105128
) {
129+
const theme = useTheme()
106130
const formatter = React.useMemo(() => {
107131
return new Intl.DateTimeFormat(undefined, {
108132
month: '2-digit',
@@ -141,10 +165,13 @@ function CalendarInputPure(
141165
value={formattedValue}
142166
style={styles.input}
143167
label={`${label} (${inputFormat})`}
144-
keyboardType={'numeric'}
168+
keyboardType={'number-pad'}
145169
placeholder={inputFormat}
146170
mask={inputFormat}
147171
onChangeText={onChangeText}
172+
returnKeyType={returnKeyType}
173+
onSubmitEditing={onSubmitEditing}
174+
keyboardAppearance={theme.dark ? 'dark' : 'default'}
148175
/>
149176
)
150177
}

src/Time/TimeInput.tsx

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,76 @@ interface TimeInputProps
1010
extends Omit<Omit<TextInputProps, 'value'>, 'onFocus'> {
1111
value: number
1212
clockType: PossibleClockTypes
13-
onFocus: (type: PossibleClockTypes) => any
14-
focused: boolean
15-
13+
onPress?: (type: PossibleClockTypes) => any
14+
pressed?: boolean
15+
onChanged: (n: number) => any
1616
inputType: PossibleInputTypes
1717
}
1818

19-
export default function TimeInput({
20-
value,
21-
clockType,
22-
focused,
23-
onFocus,
19+
function TimeInput(
20+
{
21+
value,
22+
clockType,
23+
pressed,
24+
onPress,
25+
onChanged,
26+
inputType,
27+
...rest
28+
}: TimeInputProps,
29+
ref: any
30+
) {
31+
const [controlledValue, setControlledValue] = React.useState<string>(
32+
`${value}`
33+
)
2434

25-
inputType,
26-
...rest
27-
}: TimeInputProps) {
28-
const theme = useTheme()
35+
const onInnerChange = (text: string) => {
36+
setControlledValue(text)
37+
if (text !== '' && text !== '0') {
38+
onChanged(Number(text))
39+
}
40+
}
2941

30-
// 2-digit does not work on all devices..
42+
React.useEffect(() => {
43+
setControlledValue(`${value}`)
44+
}, [value])
3145

32-
const formattedValue = `${value}`.length === 1 ? `0${value}` : `${value}`
33-
const onInnerFocus = () => {
34-
onFocus(clockType)
35-
}
46+
const theme = useTheme()
47+
const [inputFocused, setInputFocused] = React.useState<boolean>(false)
3648

49+
const highlighted = inputType === inputTypes.picker ? pressed : inputFocused
3750
const backgroundColor = useMemo<string>(() => {
3851
if (theme.dark) {
39-
if (focused) {
52+
if (highlighted) {
4053
return Color(theme.colors.primary).hex()
4154
}
4255
return Color(theme.colors.surface).lighten(1.2).hex()
4356
}
4457

45-
if (focused) {
58+
if (highlighted) {
4659
return Color(theme.colors.primary).lighten(1).hex()
4760
}
4861
return Color(theme.colors.surface).darken(0.1).hex()
49-
}, [focused, theme])
62+
}, [highlighted, theme])
5063

5164
const color = useMemo<string>(() => {
52-
if (focused && !theme.dark) {
65+
if (highlighted && !theme.dark) {
5366
return theme.colors.primary
5467
}
5568
return theme.colors.text
56-
}, [focused, theme])
69+
}, [highlighted, theme])
70+
71+
let formattedValue = controlledValue
72+
if (!inputFocused) {
73+
formattedValue =
74+
controlledValue.length === 1
75+
? `0${controlledValue}`
76+
: `${controlledValue}`
77+
}
5778

5879
return (
5980
<View style={styles.root}>
6081
<TextInput
82+
ref={ref}
6183
style={[
6284
styles.input,
6385
{
@@ -68,12 +90,14 @@ export default function TimeInput({
6890
]}
6991
value={formattedValue}
7092
maxLength={2}
71-
onFocus={onInnerFocus}
93+
onFocus={() => setInputFocused(true)}
94+
onBlur={() => setInputFocused(false)}
7295
keyboardAppearance={theme.dark ? 'dark' : 'default'}
7396
keyboardType="number-pad"
97+
onChangeText={onInnerChange}
7498
{...rest}
7599
/>
76-
{inputType === inputTypes.picker ? (
100+
{onPress && inputType === inputTypes.picker ? (
77101
<TouchableRipple
78102
style={[
79103
StyleSheet.absoluteFill,
@@ -84,7 +108,7 @@ export default function TimeInput({
84108
},
85109
]}
86110
rippleColor={Color(theme.colors.primary).fade(0.7).hex()}
87-
onPress={() => onFocus(clockType)}
111+
onPress={() => onPress(clockType)}
88112
>
89113
<View />
90114
</TouchableRipple>
@@ -104,3 +128,5 @@ const styles = StyleSheet.create({
104128
},
105129
buttonOverlay: { overflow: 'hidden' },
106130
})
131+
132+
export default React.forwardRef(TimeInput)

src/Time/TimeInputs.tsx

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// @typescript-eslint/no-unused-vars
2+
// WORK IN PROGRESS
3+
4+
import * as React from 'react'
5+
import {
6+
View,
7+
StyleSheet,
8+
useWindowDimensions,
9+
TextInput as TextInputNative,
10+
} from 'react-native'
11+
import { useTheme } from 'react-native-paper'
12+
13+
import {
14+
clockTypes,
15+
inputTypes,
16+
PossibleClockTypes,
17+
PossibleInputTypes,
18+
} from './timeUtils'
19+
import TimeInput from './TimeInput'
20+
import AmPmSwitcher from './AmPmSwitcher'
21+
import AnalogClock, { circleSize } from './AnalogClock'
22+
23+
function TimeInputs({
24+
hours,
25+
minutes,
26+
onFocusInput,
27+
focused,
28+
inputType,
29+
onChange,
30+
is24Hour,
31+
}: {
32+
inputType: PossibleInputTypes
33+
focused: PossibleClockTypes
34+
hours: number
35+
minutes: number
36+
onFocusInput: (type: PossibleClockTypes) => any
37+
onChange: ({
38+
hours,
39+
minutes,
40+
focused,
41+
}: {
42+
hours: number
43+
minutes: number
44+
focused?: undefined | PossibleClockTypes
45+
}) => any
46+
is24Hour: boolean
47+
}) {
48+
const startInput = React.useRef<TextInputNative | null>(null)
49+
const endInput = React.useRef<TextInputNative | null>(null)
50+
const dimensions = useWindowDimensions()
51+
const isLandscape = dimensions.width > dimensions.height
52+
const theme = useTheme()
53+
54+
const onSubmitStartInput = React.useCallback(() => {
55+
if (endInput.current) {
56+
endInput.current.focus()
57+
}
58+
}, [endInput])
59+
60+
const onSubmitEndInput = React.useCallback(() => {
61+
// TODO: close modal and persist time
62+
}, [])
63+
64+
return (
65+
<View
66+
style={[
67+
styles.inputContainer,
68+
isLandscape && styles.inputContainerLandscape,
69+
]}
70+
>
71+
<TimeInput
72+
ref={startInput}
73+
placeholder={'00'}
74+
value={hours}
75+
clockType={clockTypes.hours}
76+
pressed={focused === clockTypes.hours}
77+
onPress={onFocusInput}
78+
inputType={inputType}
79+
returnKeyType={'next'}
80+
onSubmitEditing={onSubmitStartInput}
81+
blurOnSubmit={false}
82+
onChanged={(newHours) =>
83+
onChange({
84+
hours: newHours,
85+
minutes,
86+
})
87+
}
88+
// onChangeText={onChangeStartInput}
89+
/>
90+
<View style={styles.hoursAndMinutesSeparator}>
91+
<View style={styles.spaceDot} />
92+
<View style={[styles.dot, { backgroundColor: theme.colors.text }]} />
93+
<View style={styles.betweenDot} />
94+
<View style={[styles.dot, { backgroundColor: theme.colors.text }]} />
95+
<View style={styles.spaceDot} />
96+
</View>
97+
<TimeInput
98+
ref={endInput}
99+
placeholder={'00'}
100+
value={minutes}
101+
clockType={clockTypes.minutes}
102+
pressed={focused === clockTypes.minutes}
103+
onPress={onFocusInput}
104+
inputType={inputType}
105+
onSubmitEditing={onSubmitEndInput}
106+
onChanged={(newMinutes) =>
107+
onChange({
108+
hours,
109+
minutes: newMinutes,
110+
})
111+
}
112+
/>
113+
{!is24Hour && (
114+
<>
115+
<View style={styles.spaceBetweenInputsAndSwitcher} />
116+
<AmPmSwitcher />
117+
</>
118+
)}
119+
</View>
120+
)
121+
}
122+
123+
const styles = StyleSheet.create({
124+
spaceBetweenInputsAndSwitcher: { width: 12 },
125+
inputContainer: {
126+
flexDirection: 'row',
127+
alignItems: 'center',
128+
},
129+
inputContainerLandscape: {
130+
flex: 1,
131+
},
132+
hoursAndMinutesSeparator: {
133+
fontSize: 65,
134+
width: 24,
135+
alignItems: 'center',
136+
},
137+
spaceDot: {
138+
flex: 1,
139+
},
140+
dot: {
141+
width: 7,
142+
height: 7,
143+
borderRadius: 7 / 2,
144+
},
145+
betweenDot: {
146+
height: 12,
147+
},
148+
})
149+
150+
export default React.memo(TimeInputs)

0 commit comments

Comments
 (0)