Crypto Orbit
A gesture-driven crypto pay button with an orbiting arc of coins. Press and hold to reveal a fan of crypto coin bubbles — drag to hover over any coin, release to select. Each bubble springs out with a staggered reveal, glows and scales when hovered, and the whole interaction runs entirely on the UI thread via a single worklet-side distance check.
Gesture-driven coin reveal
Press-and-hold morphs the button into an orbiting arc with a staggered spring reveal, no pre-baked animation.
Radial hit-testing
Drag anywhere on screen; the nearest coin within hitRadius highlights live as your finger moves, powered by a worklet-side distance check.
Active-coin glow & scale
The hovered coin scales up and glows via its own glowColor, driven entirely on the UI thread.
Headless hook included
useCoinGesture exposes the raw gesture and shared values separately for teams building a fully custom coin-picker UI.
Installation
Copy the command directly into your project.
Install the needed dependencies
Use in your app
Built to match the preview exactly — for the complete implementation, check the repo on GitHub.
import { StyleSheet } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context'
import CryptoPaySelector from '../../components/crypto-orbit/components/CryptoPaySelector'
import { Coin } from '../../components/crypto-orbit/constants/types'
export default function WalletScreen() {
const handleSelectCoin = (coin: Coin) => {
}
return (
<GestureHandlerRootView style={styles.root}>
<SafeAreaView style={styles.safe}>
<CryptoPaySelector onSelect={handleSelectCoin} />
</SafeAreaView>
</GestureHandlerRootView>
)
}
const styles = StyleSheet.create({
root: {
flex: 1,
},
safe: {
flex: 1,
backgroundColor: '#000000',
},
})API
<CryptoPaySelector>
| Prop | Type | Default | Description |
|---|---|---|---|
| coins | readonly Coin[] | built-in 5-coin set (SOL, BTC, ETH, ADA, BNB) | Coins revealed around the button on press-and-hold. |
| idleLabel | string | 'Pay' | Label shown on the button while idle. |
| hitRadius | number | verticalScale(80) | Touch radius in points used to detect which coin is under the finger. |
| hapticsEnabled | boolean | true | Enables impact haptics on hover and selection. |
| showBackdrop | boolean | true | Dims the screen behind the coins while the selector is active. |
| backdropColor | string | '#000000' | Color of the dimming backdrop. |
| bottomOffset | number | 0 | Extra bottom spacing for the button, e.g. to sit above a tab bar. |
| onActiveChange | (isActive: boolean) => void | — | Fires whenever the selector opens or closes. |
| onSelect | (coin: Coin) => void | — | Required. Fires when the user releases their finger over a coin. |
| containerStyle | StyleProp<ViewStyle> | — | Style override for the outer container. |
| buttonStyle | StyleProp<ViewStyle> | — | Style override for the button. |
| labelStyle | StyleProp<TextStyle> | — | Style override for the button label. |
useCoinGesture(params)
Headless hook powering CryptoPaySelector. Use it directly if you want to render the coin arc yourself with a completely custom visual layer.
Params
| Prop | Type | Default | Description |
|---|---|---|---|
| coins | readonly Coin[] | — | Required. Coins to hit-test against. |
| hitRadius | number | verticalScale(80) | Touch radius in points. |
| hapticsEnabled | boolean | true | Enables impact haptics on hover and selection. |
| onSelect | (coin: Coin) => void | — | Required. Fires on release over a coin. |
| onActiveChange | (isActive: boolean) => void | — | Fires on gesture begin/end. |
Returns
| Field | Type | Description |
|---|---|---|
| gesture | GestureType | Pass to <GestureDetector gesture={gesture} >. |
| activeIndex | SharedValue<number> | Index of the currently hovered coin, -1 if none. |
| activeCoinId | string | null | JS-thread mirror of activeIndex, for use outside worklets. |
| revealProgress | SharedValue<number[]> | Per-coin reveal progress, 0–1. |
| backdropOpacity | SharedValue<number> | Backdrop fade progress, 0–1. |
| buttonLayout | SharedValue<CoinPosition> | Measured screen position of the button, used as the hit-test origin. |
Types
interface Coin {
readonly id: string;
readonly name: string;
readonly symbol: string;
readonly glowColor: string;
readonly icon: ImageSourcePropType;
readonly position: CoinPosition;
}
interface CoinPosition {
readonly x: number;
readonly y: number;
}