import React, { useEffect, useState } from 'react'
import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native'
import { GestureDetector, Gesture } from 'react-native-gesture-handler'
import Animated, { useSharedValue, useAnimatedStyle, withSpring, runOnJS } from 'react-native-reanimated'
import { useLayout } from '@react-native-community/hooks'
const values = [30, 35, 40, 45, 50, 55, 60, 65, 70]
const lables = ['30', '|', '40', '|', '50', '|', '60', '|', '70']
interface CutHeightSliderProps {
value: number
onValueChanged?: (value: number) => void
style?: StyleProp<ViewStyle>
}
export default function CutHeightSlider(props: CutHeightSliderProps) {
const { value, onValueChanged, style } = props
const [selectedIndex, setSelectedIndex] = useState(values.indexOf(value))
const { width, onLayout } = useLayout()
const size = width / values.length
const offset = useSharedValue(0)
const w = useSharedValue(0)
const draggable = useSharedValue(false)
const lastIndex = useSharedValue(selectedIndex)
const [showTop, setShowTop] = useState(false)
useEffect(() => {
if (width !== 0) {
w.value = width
}
}, [width, w])
useEffect(() => {
const index = values.indexOf(value)
if (index !== -1) {
offset.value = index * size
lastIndex.value = index
setSelectedIndex(index)
}
}, [value, size, offset, lastIndex])
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: offset.value,
},
],
}
})
const onIndexChanged = (index: number) => {
lastIndex.value = index
setSelectedIndex(index)
const nextValue = values[index]
if (nextValue !== value) {
onValueChanged && onValueChanged(values[index])
}
}
const onIndexChangedStart = () => {
setShowTop(true)
}
const onIndexChangedEnd = () => {
setTimeout(() => {
setShowTop(false)
}, 500);
}
const onIndexChangedTap = (index: number) => {
setShowTop(true)
lastIndex.value = index
setSelectedIndex(index)
const nextValue = values[index]
if (nextValue !== value) {
onValueChanged && onValueChanged(values[index])
}
setTimeout(() => {
setShowTop(false)
}, 500);
}
const bubbleStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: size*selectedIndex,
},
],
}
})
const tap = Gesture.Tap().onEnd(e => {
const index = Math.floor(e.x / size)
offset.value = withSpring(
index * size,
{
stiffness: 500,
overshootClamping: true,
},
() => runOnJS(onIndexChangedTap)(Math.abs(index)),
)
})
const pan = Gesture.Pan()
.onChange(e => {
if (!draggable.value) {
return
}
offset.value = offset.value + e.changeX
if (offset.value < 0) {
offset.value = 0
}
if (offset.value > w.value - size) {
offset.value = w.value - size
}
const current = Math.round(offset.value / size)
if (lastIndex.value !== current) {
lastIndex.value = current
runOnJS(setSelectedIndex)(current)
}
})
.onStart(e => {
const target = Math.floor(e.x / size)
const current = Math.floor(offset.value / size)
draggable.value = target === current
runOnJS(onIndexChangedStart)()
})
.onEnd(() => {
draggable.value = false
const index = Math.round(offset.value / size)
offset.value = withSpring(
index * size,
{
stiffness: 500,
overshootClamping: true,
},
() => runOnJS(onIndexChanged)(Math.abs(index)),
)
runOnJS(onIndexChangedEnd)()
}
)
const composed = Gesture.Race(tap, pan)
return (
<GestureDetector gesture={composed}>
<View style={[styles.root, style]} onLayout={onLayout}>
<Animated.View style={[styles.thumb, { width: size, height: size }, animatedStyle]} />
<View style={styles.plate}>
{lables.map((label, index) => (
<Mark
key={values[index]}
style={{ width: size, height: size }}
text={index === selectedIndex ? String(values[index]) : label}
checked={index === selectedIndex}
/>
))}
</View>
{/* <Animated.View style={[styles.bubble,{width:size*(selectedIndex+1)},]} > */}
{showTop ?
<Animated.View style={[styles.bubble,{width:size},animatedStyle]} >
<View style={[styles.bubbleRect,{width:size}]}>
<Text style={styles.bubbleText}>{values[selectedIndex]}</Text>
</View>
<View style={[{width:size,},styles.bubbleTriangleView]}>
<View style={[styles.bubbleTriangle,]} />
</View>
</Animated.View>
:null}
</View>
</GestureDetector>
)
}
interface MarkProps {
text: string
checked?: boolean
style?: StyleProp<ViewStyle>
}
function Mark(props: MarkProps) {
const { text, checked, style } = props
return (
<View style={[styles.label, style]}>
<Text style={[styles.labelText, checked ? styles.labelTextSelected : undefined]}>{text}</Text>
</View>
)
}
const styles = StyleSheet.create({
root: {
height: 48,
backgroundColor: '#F7F8FF',
borderRadius: 12,
justifyContent: 'center',
},
plate: {
...StyleSheet.absoluteFillObject,
flexDirection: 'row',
alignItems: 'center',
},
label: {
alignItems: 'center',
justifyContent: 'center',
},
labelText: {
color: '#17174A',
fontSize: 14,
},
labelTextSelected: {
color: '#4D58FF',
fontWeight: 'bold',
},
thumb: {
backgroundColor: '#DBDEFF',
borderRadius: 8,
},
bubble: {
position: 'absolute',
height: 40,
// width: 30,
bottom: 55,
// paddingTop: 4,
alignItems: 'flex-end',
// backgroundColor:'blue',
// marginHorizontal: 12,
},
bubbleRect: {
height: 40,
// width: BUBBLE_WIDTH,
borderRadius: 4,
backgroundColor: 'rgba(223, 227, 229, 1)',
justifyContent: 'center',
alignItems: 'center',
},
bubbleTriangleView:{
position:'absolute',
bottom:0,
alignItems:'center',
backgroundColor:'rgba(223, 227, 229, 1)'
},
bubbleTriangle: {
position: 'absolute',
bottom: -8,
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 4,
borderRightWidth: 4,
borderBottomWidth: 4,
borderLeftWidth: 4,
borderTopColor: 'rgba(223, 227, 229, 1)',
borderBottomColor: 'transparent',
borderRightColor: 'transparent',
borderLeftColor: 'transparent',
zIndex: -10,
},
bubbleText: {
color: '#292F33',
fontSize: 12,
},
})