Skip to content

Commit a5ccc4f

Browse files
Cleanup and organize time picker components
1 parent 5ceb813 commit a5ccc4f

File tree

9 files changed

+395
-282
lines changed

9 files changed

+395
-282
lines changed

example/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ function App({
4242

4343
return (
4444
<ScrollView
45+
scrollEnabled={false}
4546
style={{
4647
backgroundColor: theme.colors.background,
4748
flex: 1,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"<rootDir>/lib/"
8484
]
8585
},
86+
8687
"commitlint": {
8788
"extends": [
8889
"@commitlint/config-conventional"

src/Time/AmPmSwitcher.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as React from 'react'
2+
import { View, StyleSheet } from 'react-native'
3+
import { Text, TouchableRipple, useTheme } from 'react-native-paper'
4+
5+
export default function AmPmSwitcher() {
6+
const theme = useTheme()
7+
return (
8+
<View
9+
style={[
10+
styles.root,
11+
{
12+
borderColor: theme.colors.backdrop,
13+
borderRadius: theme.roundness,
14+
},
15+
]}
16+
>
17+
<SwitchButton label="AM" onPress={() => {}} />
18+
<View style={styles.switchSeparator} />
19+
<SwitchButton label="PM" onPress={() => {}} />
20+
</View>
21+
)
22+
}
23+
24+
function SwitchButton({
25+
label,
26+
onPress,
27+
}: {
28+
label: string
29+
onPress: () => any
30+
}) {
31+
const theme = useTheme()
32+
return (
33+
<TouchableRipple onPress={onPress} style={styles.switchButton}>
34+
<Text selectable={false} style={{ ...theme.fonts.medium }}>
35+
{label}
36+
</Text>
37+
</TouchableRipple>
38+
)
39+
}
40+
41+
const styles = StyleSheet.create({
42+
root: {
43+
width: 41,
44+
height: 65,
45+
borderWidth: 1,
46+
},
47+
switchSeparator: {
48+
height: 1,
49+
width: 41,
50+
backgroundColor: '#ccc',
51+
},
52+
switchButton: {
53+
flex: 1,
54+
alignItems: 'center',
55+
justifyContent: 'center',
56+
},
57+
})

src/Time/AnalogClock.tsx

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import Color from 'color'
2+
import {
3+
GestureResponderEvent,
4+
LayoutChangeEvent,
5+
PanResponder,
6+
StyleSheet,
7+
View,
8+
} from 'react-native'
9+
import { useTheme } from 'react-native-paper'
10+
import {
11+
clockTypes,
12+
getAngle,
13+
getHours,
14+
isPM,
15+
PossibleClockTypes,
16+
} from './timeUtils'
17+
import * as React from 'react'
18+
import { useCallback } from 'react'
19+
import { useLatest } from '../utils'
20+
import AnalogClockHours from './AnalogClockHours'
21+
22+
export const circleSize = 215
23+
24+
export default function AnalogClock({
25+
hours,
26+
minutes,
27+
focused,
28+
is24Hour,
29+
}: {
30+
hours: number
31+
minutes: number
32+
focused: PossibleClockTypes
33+
is24Hour: boolean
34+
}) {
35+
const theme = useTheme()
36+
const pointerNumber = focused === clockTypes.hours ? hours : minutes
37+
const clockRef = React.useRef<View | null>(null)
38+
const elementX = React.useRef<number>(0)
39+
const elementY = React.useRef<number>(0)
40+
41+
// We need the latest values
42+
const hoursRef = useLatest(hours)
43+
const focusedRef = useLatest(focused)
44+
const is24HourRef = useLatest(is24Hour)
45+
const onPointerMove = React.useCallback(
46+
(e: GestureResponderEvent) => {
47+
let x = e.nativeEvent.pageX - elementX.current
48+
let y = e.nativeEvent.pageY - elementY.current
49+
50+
let angle = getAngle(x, y, circleSize)
51+
if (focusedRef.current === clockTypes.hours) {
52+
let pickedHours = getHours(angle)
53+
54+
if (is24HourRef.current && isPM(x, y, circleSize)) {
55+
pickedHours += 12
56+
}
57+
if (hoursRef.current !== pickedHours) {
58+
// TODO: add onChange
59+
// setHours(pickedHours)
60+
}
61+
}
62+
},
63+
[hoursRef, focusedRef, is24HourRef]
64+
)
65+
66+
const panResponder = React.useRef(
67+
PanResponder.create({
68+
onPanResponderGrant: onPointerMove,
69+
onPanResponderMove: onPointerMove,
70+
onPanResponderRelease: onPointerMove,
71+
72+
onStartShouldSetPanResponder: returnTrue,
73+
onStartShouldSetPanResponderCapture: returnTrue,
74+
onMoveShouldSetPanResponder: returnTrue,
75+
onMoveShouldSetPanResponderCapture: returnTrue,
76+
onPanResponderTerminationRequest: returnTrue,
77+
onShouldBlockNativeResponder: returnTrue,
78+
})
79+
).current
80+
81+
const onLayout = useCallback(
82+
(_: LayoutChangeEvent) => {
83+
console.log('onLayout')
84+
if (!clockRef.current) {
85+
return
86+
}
87+
clockRef.current.measureInWindow((x, y) => {
88+
elementX.current = x
89+
elementY.current = y
90+
})
91+
},
92+
[elementX, elementY]
93+
)
94+
return (
95+
<View
96+
ref={clockRef}
97+
onLayout={onLayout}
98+
{...panResponder.panHandlers}
99+
style={[
100+
styles.clock,
101+
{
102+
backgroundColor: theme.dark
103+
? Color(theme.colors.surface).lighten(1.2).hex()
104+
: Color(theme.colors.surface).darken(0.1).hex(),
105+
},
106+
]}
107+
//@ts-ignore -> https://github.com/necolas/react-native-web/issues/506
108+
cursor={'pointer'}
109+
>
110+
<View
111+
style={{
112+
position: 'absolute',
113+
width: circleSize / 2 - 4 - (hours > 12 ? 33 : 0),
114+
marginBottom: -1,
115+
height: 2,
116+
borderRadius: 4,
117+
backgroundColor: theme.colors.primary,
118+
transform: [
119+
{ rotate: -90 + pointerNumber * 30 + 'deg' },
120+
{ translateX: circleSize / 4 - 4 - (hours > 12 ? 33 / 2 : 0) },
121+
],
122+
}}
123+
pointerEvents="none"
124+
>
125+
<View
126+
style={{
127+
borderRadius: 15,
128+
height: 30,
129+
width: 30,
130+
position: 'absolute',
131+
backgroundColor: theme.colors.primary,
132+
right: 0,
133+
bottom: -14,
134+
}}
135+
/>
136+
</View>
137+
<View
138+
style={[StyleSheet.absoluteFill, styles.center]}
139+
pointerEvents="none"
140+
>
141+
<View
142+
style={[
143+
styles.middlePoint,
144+
{
145+
backgroundColor: theme.colors.primary,
146+
},
147+
]}
148+
/>
149+
</View>
150+
<AnalogClockHours is24Hour={is24Hour} hours={hours} />
151+
</View>
152+
)
153+
}
154+
155+
const styles = StyleSheet.create({
156+
clock: {
157+
height: circleSize,
158+
width: circleSize,
159+
position: 'relative',
160+
justifyContent: 'center',
161+
alignItems: 'center',
162+
borderRadius: circleSize / 2,
163+
},
164+
middlePoint: {
165+
borderRadius: 4,
166+
height: 8,
167+
width: 8,
168+
},
169+
center: { justifyContent: 'center', alignItems: 'center' },
170+
})
171+
172+
function returnTrue() {
173+
return true
174+
}

src/Time/AnalogClockHours.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import * as React from 'react'
2+
import { View, StyleSheet } from 'react-native'
3+
import { Text } from 'react-native-paper'
4+
import { getNumbers } from './timeUtils'
5+
import { circleSize } from './AnalogClock'
6+
7+
const outerRange = getNumbers(false, circleSize, 12)
8+
const innerRange = getNumbers(true, circleSize, 12)
9+
10+
export default function AnalogClockHours({
11+
is24Hour,
12+
hours,
13+
}: {
14+
is24Hour: boolean
15+
hours: number
16+
}) {
17+
return (
18+
<>
19+
{outerRange.map((a, i) => (
20+
<View
21+
key={i}
22+
pointerEvents="none"
23+
style={[
24+
styles.outerHourRoot,
25+
{
26+
top: a[1],
27+
left: a[0],
28+
},
29+
]}
30+
>
31+
<View style={styles.outerHourInner}>
32+
<Text
33+
style={hours === i + 1 ? { color: '#fff' } : null}
34+
selectable={false}
35+
>
36+
{i + 1}
37+
</Text>
38+
</View>
39+
</View>
40+
))}
41+
{is24Hour
42+
? innerRange.map((a, i) => (
43+
<View
44+
key={i}
45+
pointerEvents="none"
46+
style={[
47+
styles.innerHourRoot,
48+
{
49+
top: a[1],
50+
left: a[0],
51+
},
52+
]}
53+
>
54+
<View style={styles.innerHourInner}>
55+
<Text
56+
selectable={false}
57+
style={[
58+
{ fontSize: 13 },
59+
i + 13 === hours ? { color: '#fff' } : null,
60+
]}
61+
>
62+
{i + 13 === 24 ? '00' : i + 13}
63+
</Text>
64+
</View>
65+
</View>
66+
))
67+
: null}
68+
</>
69+
)
70+
}
71+
72+
const styles = StyleSheet.create({
73+
outerHourRoot: {
74+
position: 'absolute',
75+
justifyContent: 'center',
76+
alignItems: 'center',
77+
zIndex: 20,
78+
width: 50,
79+
height: 50,
80+
marginLeft: -25,
81+
marginTop: -25,
82+
83+
borderRadius: 25,
84+
},
85+
outerHourInner: { borderRadius: 25 },
86+
innerHourRoot: {
87+
position: 'absolute',
88+
zIndex: 20,
89+
justifyContent: 'center',
90+
alignItems: 'center',
91+
width: 40,
92+
height: 40,
93+
marginLeft: -20,
94+
marginTop: -20,
95+
borderRadius: 20,
96+
},
97+
innerHourInner: { borderRadius: 20 },
98+
})
File renamed without changes.

0 commit comments

Comments
 (0)