Created
November 22, 2019 10:15
-
-
Save vilindberg/66f7483744cf99007475d560886116ab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { forwardRef, useEffect, useRef, useState } from 'react' | |
import { Animated, View } from 'react-native' | |
import styled from 'styled-components/native' | |
import { almostBlack, offWhite } from '../../colors' | |
import { baseText } from '../../styles' | |
import { measure } from '../../utils/view.util' | |
import { Touchable } from '../touchable' | |
type ItemProps = { | |
text: string | |
onSelect: (text: string) => void | |
} | |
const MultiToggleItem = forwardRef<View, ItemProps>(({ onSelect, text }: ItemProps, itemRef) => { | |
return ( | |
<Touchable style={{ zIndex: 1, justifyContent: 'center' }} onPress={() => onSelect(text)}> | |
<ItemWrapper ref={itemRef}> | |
<Label>{text}</Label> | |
</ItemWrapper> | |
</Touchable> | |
) | |
}) | |
type Props = { | |
style?: object | |
} | |
const items = ['3 mån', 'YTD', 'Start'] | |
export const MultiToggle = ({ ...props }: Props) => { | |
const [selected, setSelected] = useState('3 mån') | |
const [startedMounting, setStartedMounting] = useState(false) | |
const [isMounted, setIsMounted] = useState(false) | |
const widthRef = useRef(new Animated.Value(0)) | |
const translateRef = useRef(new Animated.Value(4)) | |
const opacityRef = useRef(new Animated.Value(0)) | |
const references = useRef<React.RefObject<View>>([] as any) | |
useEffect(() => { | |
setTimeout(() => { | |
setStartedMounting(true) | |
}, 300) | |
setTimeout(() => { | |
setIsMounted(true) | |
}, 400) | |
}, []) | |
useEffect(() => { | |
if (!startedMounting) return | |
Animated.timing(opacityRef.current, { | |
toValue: 1, | |
duration: 100 | |
}).start() | |
}, [startedMounting]) | |
useEffect(() => { | |
if (!isMounted) return | |
const asyncFn = async () => { | |
const selectedIndex = items.findIndex(i => i === selected) | |
const itemsBefore = items.slice(0, selectedIndex) | |
const selectedItemRef = references.current[selectedIndex] | |
const selectedItemMeasurements = await measure(selectedItemRef.current) | |
const selectedItemWidth = selectedItemMeasurements.width | |
const startPosition = await itemsBefore.reduce(async (tutti, _, idx) => { | |
const itemMeasurements = await measure(references.current[idx].current) | |
const itemWidth = itemMeasurements.width | |
const previous = await tutti | |
return Promise.resolve(previous + itemWidth + 4) | |
}, Promise.resolve(4)) | |
Animated.timing(widthRef.current, { | |
toValue: selectedItemWidth - 4, | |
duration: 200 | |
}).start() | |
Animated.timing(translateRef.current, { | |
toValue: startPosition, | |
duration: 200 | |
}).start() | |
} | |
asyncFn() | |
}, [selected, isMounted]) | |
function getOrCreateRef(id: number): React.RefObject<View> { | |
if (!references.current.hasOwnProperty(id)) { | |
references.current[id] = React.createRef<View>() | |
} | |
return references.current[id] | |
} | |
return ( | |
<Wrapper style={{ opacity: opacityRef.current }} {...props}> | |
{items.map((item, index) => ( | |
<MultiToggleItem ref={getOrCreateRef(index)} onSelect={() => setSelected(item)} text={item} /> | |
))} | |
<Overlay style={{ width: widthRef.current, transform: [{ translateX: translateRef.current }] }} /> | |
</Wrapper> | |
) | |
} | |
const Overlay = styled(Animated.View)` | |
position: absolute; | |
background-color: ${offWhite}; | |
border-radius: 4px; | |
height: 24px; | |
width: 40px; | |
top: 4px; | |
shadow-opacity: 0.25; | |
shadow-radius: 3.84px; | |
shadow-color: ${almostBlack}; | |
shadow-offset: 0px 2px; | |
elevation: 5; | |
` | |
const Wrapper = styled(Animated.View)` | |
background-color: rgba(17, 21, 28, 0.05); | |
height: 32px; | |
flex-direction: row; | |
border-radius: 4px; | |
` | |
const ItemWrapper = styled.View` | |
border-radius: 4px; | |
padding: 4px 8px; | |
margin: 0 2px; | |
` | |
const Label = styled.Text` | |
${baseText}; | |
text-align: center; | |
` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment