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

react-native-rotary-timer

v0.2.3

Published

A highly customizable, circular draggable timer component for React Native with smooth 60 FPS animations, gesture interactions, snapping, haptic feedback, and full render injection support. Built with react-native-reanimated, react-native-gesture-handler,

Readme

react-native-rotary-timer

A highly customizable, circular draggable timer component for React Native. Built with react-native-reanimated, react-native-gesture-handler, and react-native-svg for smooth 60 FPS animations and gesture interactions.

Features

  • 🎯 Draggable circular timer with snapping support
  • 🎨 Fully customizable ring, ticks, marker, label, hint, and background
  • 🔄 Clockwise / counter-clockwise fill
  • 📳 Haptic-ready feedback callbacks
  • 🏗️ Replaceable sub-components via render injection
  • ⏱️ Built-in countdown hook
  • 🧩 Imperative ref API for programmatic control

Demo

Try it in Expo Snack!

darkThemeTimer.gif

Installation

npm install react-native-rotary-timer
# or
yarn add react-native-rotary-timer

Peer Dependencies

Make sure the following packages are installed in your project:

npm install react-native-gesture-handler react-native-reanimated react-native-svg

[!NOTE] Follow the official installation guides for react-native-gesture-handler, react-native-reanimated, and react-native-svg if you haven't set them up already.

Quick Start

import RotaryTimer from 'react-native-rotary-timer';

export default function App() {
  return (
    <RotaryTimer
      size={250}
      ringActiveColor="#4CAF50"
      ringInactiveColor="#E0E0E0"
      onChange={(ms) => console.log('Time in ms:', ms)}
    />
  );
}

Props

All props are optional. The component works out of the box with sensible defaults.


Common

| Prop | Type | Default | Description | |------|------|---------|-------------| | ref | React.Ref<IRotaryTimerRef> | — | Imperative handle for programmatic control | | size | number | 200 | Diameter of the timer in pixels | | isEditable | boolean | true | Whether the user can drag the timer | | initialRotation | number | 0 | Initial rotation angle in radians | | minRotation | number | −Infinity | Minimum allowed rotation (radians) | | maxRotation | number | +Infinity | Maximum allowed rotation (radians) | | rotationSharedValue | SharedValue<number> | — | External Reanimated shared value to sync with | | onChange | (ms: number) => void | — | Called when the rotation changes, with time in ms | | onTouchStart | (rad: number) => void | — | Called when the user starts dragging | | onTouchEnd | (rad: number) => void | — | Called when the user stops dragging |


Ring

| Prop | Type | Default | Description | |------|------|---------|-------------| | ringWidth | number | 20 | Width of the ring stroke | | ringActiveColor | string | '#f00' | Color of the filled (active) portion | | ringInactiveColor | string | '#ccc' | Color of the unfilled (inactive) portion |


Ticks

| Prop | Type | Default | Description | |------|------|---------|-------------| | ticksCount | number | 60 | Number of tick marks around the circle | | tickAngle | number | — | Explicit angle between ticks (radians). Overrides ticksCount | | tickOffsetAngle | number | — | Angular offset for the first tick (radians) | | tickHeight | number | 10 | Height of each tick mark | | tickWidth | number | 1 | Width of each tick mark | | tickColor | string | '#000' | Color of the tick marks | | tickSpaceFromRing | number | — | Gap between the ring and the ticks | | tickRounding | number | — | Border radius of the tick marks |


Snap

Snapping makes the rotation "stick" to the nearest increment.

| Prop | Type | Default | Description | |------|------|---------|-------------| | snapTicksCount | number | ticksCount | Number of snap points around the circle | | snapAngle | number | — | Explicit angle between snap points (radians). Overrides snapTicksCount | | snapOffsetAngle | number | — | Angular offset for the snap positions (radians) |


Feedback

Feedback fires a callback at certain angular intervals (e.g. for haptics).

| Prop | Type | Default | Description | |------|------|---------|-------------| | feedbackTicksCount | number | ticksCount | Number of feedback points around the circle | | feedbackAngle | number | — | Explicit angle between feedback points (radians). Overrides feedbackTicksCount | | feedbackOffsetAngle | number | — | Angular offset for the feedback positions (radians) | | onFeedback | () => void | — | Called each time the user drags past a feedback point |


Marker

The marker indicates the current drag position on the ring.

| Prop | Type | Default | Description | |------|------|---------|-------------| | markerSize | number | ringWidth | Diameter of the marker circle | | markerColor | string | — | Color of the marker |


Label

The label displays the current time value in the center of the timer.

| Prop | Type | Default | Description | |------|------|------------|-------------| | labelTextStyle | AnimatedStyle<TextStyle> | — | Animated text style for the label | | labelHideWhenZero | boolean | true | Hide the label when the rotation is zero | | renderLabel | (rad: number) => string | HH:MM:SS | Worklet function that converts radians to a display string |


Hint

The hint is a visual indicator (arrows) shown when the timer is at zero.

| Prop | Type | Default | Description | |------|------|---------|-------------| | hintSize | number | 120 | Size of the hint element | | hintColor | string | '#000' | Color of the hint | | hintHideWhenNotZero | boolean | true | Hide the hint when rotation is non-zero | | hintEnabledRotation | boolean | true | Whether the hint rotates around the center of the timer |


Background

| Prop | Type | Default | Description | |------|------|---------|-------------| | backgroundColor | string | — | Background color of the timer area | | backgroundSize | number | size | Size of the background circle | | backgroundStyle | AnimatedStyle<ViewStyle> | — | Animated style for the background |


Custom Components

You can replace any sub-component with your own implementation.

If you want to replace only the display, use ComponentView props; if you also want to change the component's behavior, use Component props.

| Prop | Type | Description | |------|------|-------------| | BackgroundComponent | React.ComponentType<IBackgroundProps> | Replaces the background | | BackgroundViewComponent | React.ComponentType<IBackgroundViewProps> | Replaces the background view | | RingComponent | React.ComponentType<IRingProps> | Replaces the ring | | RingViewComponent | React.ComponentType<IRingViewProps> | Replaces the ring view | | TicksComponent | React.ComponentType<ITicksProps> | Replaces the ticks container | | TickItemComponent | React.ComponentType<ITickItemProps> | Replaces a single tick | | TickItemViewComponent | React.ComponentType<ITickItemViewProps> | Replaces a single tick view | | MarkerComponent | React.ComponentType<IMarkerProps> | Replaces the marker | | MarkerViewComponent | React.ComponentType<IMarkerViewProps> | Replaces the marker view | | LabelComponent | React.ComponentType<ILabelProps> | Replaces the label | | LabelViewComponent | React.ComponentType<ILabelViewProps> | Replaces the label view | | HintComponent | React.ComponentType<IHintProps> | Replaces the hint | | HintViewComponent | React.ComponentType<IHintViewProps> | Replaces the hint view |

Ref Methods

Use a ref to control the timer programmatically:

import { useRef } from 'react';
import RotaryTimer, { type IRotaryTimerRef } from 'react-native-rotary-timer';

function App() {
  const timerRef = useRef<IRotaryTimerRef>(null);

  return (
    <>
      <RotaryTimer ref={timerRef} />

      <Button title="Add 5 min" onPress={() => timerRef.current?.increaseRotation(Math.PI / 6)} />
      <Button title="Remove 5 min" onPress={() => timerRef.current?.reduceRotation(Math.PI / 6)} />
      <Button title="Set 30 min" onPress={() => timerRef.current?.setRotation(Math.PI)} />
      <Button title="Reset" onPress={() => timerRef.current?.reset()} />
    </>
  );
}

| Method | Signature | Description | |--------|-----------|-------------| | increaseRotation | (rotation: number) => void | Increase rotation by the given radians (animated) | | reduceRotation | (rotation: number) => void | Decrease rotation by the given radians (animated) | | setRotation | (rotation: number) => void | Set rotation to an absolute value (animated) | | reset | () => void | Reset to initialRotation (animated) |

Hooks

useCountdown

Manages a countdown interval that pauses while the user is dragging:

import { useCountdown } from 'react-native-rotary-timer';

const { onTouchStart, onTouchEnd } = useCountdown(() => {
  // Return a setInterval that decrements rotation
  return setInterval(() => {
    // increment logic (+ 1 second)
    timerRef.current?.increaseRotation(convertMillisecondsToRadians(1000))
  }, 1000);
}, isActive);

Helpers

The library exports worklet-compatible helper functions:

import {
  convertRadiansToMilliseconds,
  convertMillisecondsToRadians,
  convertMillisecondsToTime,
  renderLabel,
} from 'react-native-rotary-timer';

// Convert radians to milliseconds (default: 60 min per full circle)
const ms = convertRadiansToMilliseconds(Math.PI); // → 1,800,000 ms (30 min)

// Convert milliseconds to radians
const rad = convertMillisecondsToRadians(1800000); // → Math.PI

// Break milliseconds into time components. Use it to create your own `renderLabel` function
const time = convertMillisecondsToTime(3661000);
// → { days: 0, hours: 1, totalHours: 1, minutes: 1, totalMinutes: 61, seconds: 1, totalSeconds: 3661, milliseconds: 0 }

Contributing

License

MIT