npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-wavy-slider

v0.4.6

Published

Expo native Android wrapper for the WavySlider Jetpack Compose component.

Readme

expo-wavy-slider

An Android Expo module that exposes ir.mahozad.multiplatform:wavy-slider as a React Native view.

Animated wavy Material Slider and progress/seek bar similar to the one used in Android 13 media controls.
It has curly, wobbly, squiggly, wiggly, jiggly, wriggly, dancing movements. Some users call it the sperm.

Key Features

  • High Performance: Using SharedValue and SharedObject smoothly driven on the UI thread, bypassing React re-renders.
  • Highly Customizable: Easily customize wave length, height, velocity, direction, thickness, and colors. (And you can animated most of them all in 60fps by using Reanimated!)
  • Custom Thumb Shapes: Support for circle, square, diamond, and standard Material 3 thumbs.
  • Media-Ready: Built-in buffered progress support, ideal for audio/video players.

Platform Support

[!WARNING] This package is only supported on Android.

Requirements

  • Expo
  • React Native New Architecture
  • react-native-worklets
  • react-native-reanimated

This package requires a development build. It does not run in Expo Go.

Installation

pnpm add expo-wavy-slider react-native-worklets react-native-reanimated

Basic Usage

import { WavySlider } from 'expo-wavy-slider'
import { useState } from 'react'
import { scheduleOnRN } from 'react-native-worklets'

export function Example() {
	const [value, setValue] = useState(0.5)

	return (
		<WavySlider
			style={{ width: '100%', height: 32 }}
			value={value}
			onValueChange={(nextValue) => {
				'worklet'
				scheduleOnRN(setValue, nextValue)
			}}
			waveLength={16}
			waveHeight={16}
			waveVelocity={15}
			waveDirection='head'
			waveThickness={4}
			trackThickness={4}
		/>
	)
}

BTW, Callback props must be worklet functions.

Reanimated Shared Values

progress, bufferedProgress, waveLength, waveHeight, waveVelocity, waveThickness, and trackThickness accept Reanimated shared values.

So you can add cool animations to your slider.

import { WavySlider } from 'expo-wavy-slider'
import {
	Easing,
	useAnimatedReaction,
	useDerivedValue,
	useSharedValue,
	withTiming,
} from 'react-native-reanimated'

export function AnimatedPlayerSlider({
	duration,
	position,
	isPlaying,
}: {
	duration: number
	position: number
	isPlaying: boolean
}) {
	const sharedPosition = useSharedValue(position)
	const playing = useSharedValue(isPlaying)
	const waveHeight = useSharedValue(isPlaying ? 8 : 0)
	const waveVelocity = useSharedValue(isPlaying ? 16 : 0)
	const trackThickness = useSharedValue(4)

	const progress = useDerivedValue(() => {
		const safeDuration = duration || 1
		return Math.min(Math.max(sharedPosition.value / safeDuration, 0), 1)
	})

	useAnimatedReaction(
		() => playing.value,
		(value) => {
			waveHeight.value = withTiming(value ? 8 : 0, {
				duration: 280,
				easing: Easing.out(Easing.cubic),
			})
			waveVelocity.value = withTiming(value ? 16 : 0, {
				duration: 120,
			})
		},
	)

	return (
		<WavySlider
			style={{ width: '100%', height: 32 }}
			progress={progress}
			waveHeight={waveHeight}
			waveVelocity={waveVelocity}
			trackThickness={trackThickness}
			onDragStateChange={(dragging) => {
				'worklet'
				trackThickness.value = withTiming(dragging ? 12 : 4, {
					duration: 180,
				})
			}}
		/>
	)
}

Native State

This concept is inspired by ExpoUI's useNativeState hook.

And basically, all these codes were copied from ExpoUI repo : )

useNativeState(initialValue) creates an Expo SharedObject. On Android it is read by Compose as MutableState, so assigning state.value from a worklet updates the native UI directly.

You usually do not need this hook when you already have a Reanimated shared value, because WavySlider bridges shared values internally.

import { WavySlider, useNativeState } from 'expo-wavy-slider'
import { useEffect } from 'react'

export function NativeStateExample() {
	const progress = useNativeState(0)

	useEffect(() => {
		progress.onChange = (value) => {
			'worklet'
			console.log(value)
			// Runs on the native UI runtime whenever progress.value changes.
		}

		return () => {
			progress.onChange = null
		}
	}, [progress])

	return (
		<WavySlider
			style={{ width: '100%', height: 32 }}
			progress={progress}
			onValueChange={(value) => {
				'worklet'
				progress.value = value
			}}
		/>
	)
}

Props

| Prop | Type | Default | Description | | ----------------------- | ---------------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | | value | number | 0 | Current slider value. Used as a fallback when progress is not provided. | | progress | SharedValue<number> \| ObservableState<number> | undefined | Native-driven current progress. Takes precedence over value. | | bufferedValue | number | 0 | Buffered or loaded progress value. | | bufferedProgress | SharedValue<number> \| ObservableState<number> | undefined | Native-driven buffered progress. Takes precedence over bufferedValue. | | min | number | 0 | Minimum selectable value. | | max | number | 1 | Maximum selectable value. | | lowerLimit | number | undefined | Lower bound for user interaction. | | upperLimit | number | undefined | Upper bound for user interaction. | | enabled | boolean | true | Whether user interaction is enabled. | | colors | WavySliderColors | undefined | Slider color configuration. | | waveLength | number \| SharedValue<number> \| ObservableState<number> | 16 | Wave length in dp. Set to 0 for a straight slider. | | waveHeight | number \| SharedValue<number> \| ObservableState<number> | 16 | Wave height in dp. Set to 0 for a straight slider. | | waveVelocity | number \| SharedValue<number> \| ObservableState<number> | 15 | Wave velocity in dp per second. Set to 0 to stop movement. | | waveDirection | 'left' \| 'right' \| 'tail' \| 'head' | 'head' | Wave movement direction. | | thumbShape | 'default' \| 'circle' \| 'square' \| 'diamond' | 'default' | Shape used for the slider thumb. | | waveThickness | number \| SharedValue<number> \| ObservableState<number> | 4 | Active wave stroke thickness in dp. | | trackThickness | number \| SharedValue<number> \| ObservableState<number> | 4 | Inactive and buffered track stroke thickness in dp. | | incremental | boolean | false | Whether wave height gradually increases toward the thumb. | | onValueChange | (value: number) => void | undefined | Worklet callback fired while the value changes. | | onValueChangeFinished | (value: number) => void | undefined | Worklet callback fired when dragging or tap-seeking finishes. | | onDragStateChange | (isDragged: boolean) => void | undefined | Worklet callback fired when the native drag state changes. |

Callback Rules

[!IMPORTANT] All callback props (onValueChange, onValueChangeFinished, and onDragStateChange) must be worklet functions because they are invoked directly from the UI thread runtime.