Progress Indicator

Progress indicators let people know that your app isn't stalled while it loads content or performs lengthy operations.

Apple
Apple
import { Icon } from '@roninoss/icons';
import { Stack } from 'expo-router';
import * as React from 'react';
import { View, Text, ScrollView, Pressable } from 'react-native';

import { ProgressIndicator } from '~/components/nativewindui/ProgressIndicator';
import { useColorScheme } from '~/lib/useColorScheme';

export default function ProgressIndicatorScreen() {
  const [progress, setProgress] = React.useState(13);
  let id: ReturnType<typeof setInterval> | null = null;
  React.useEffect(() => {
    if (!id) {
      id = setInterval(() => {
        setProgress((prev) => (prev >= 99 ? 0 : prev + 5));
      }, 1000);
    }
    return () => {
      if (id) clearInterval(id);
    };
  }, []);
  const { colors } = useColorScheme();
  return (
    <>
      <Stack.Screen
        options={{
          title: 'NativeWindUI',
          headerSearchBarOptions: {
            hideWhenScrolling: false,
          },
          headerLargeTitle: true,
          headerRight() {
            return (
              <Pressable className="opacity-80 active:opacity-40">
                <View className="opacity-90">
                  <Icon name="cog-outline" color={colors.foreground} />
                </View>
              </Pressable>
            );
          },
        }}
      />
      <ScrollView contentInsetAdjustmentBehavior="automatic" className="p-4">
        <View className="border-border bg-card gap-4 rounded-xl border p-4 pb-6 shadow-sm shadow-black/10 dark:shadow-none">
          <Text className="text-foreground text-center text-sm font-medium tracking-wider opacity-60">
            Progress Indicator
          </Text>
          <View className="p-4">
            <ProgressIndicator value={progress} />
          </View>
        </View>
      </ScrollView>
    </>
  );
}

Installation

1

Run the following command:

npx nwui-cli@latest add progress-indicator
🚀
Ship.
1

Copy/paste the following code to the specified file path:

~/components/nativewindui/ProgressIndicator.tsx
import * as React from 'react';
import { View } from 'react-native';
import Animated, {
  Extrapolation,
  interpolate,
  useAnimatedStyle,
  useDerivedValue,
  withSpring,
} from 'react-native-reanimated';

import { cn } from '~/lib/cn';

const DEFAULT_MAX = 100;

const ProgressIndicator = React.forwardRef<
  React.ElementRef<typeof View>,
  React.ComponentPropsWithoutRef<typeof View> & {
    value?: number;
    max?: number;
    getValueLabel?: (value: number, max: number) => string;
  }
>(
  (
    {
      value: valueProp,
      max: maxProp,
      getValueLabel = defaultGetValueLabel,
      className,
      children,
      ...props
    },
    ref
  ) => {
    const max = maxProp ?? DEFAULT_MAX;
    const value = isValidValueNumber(valueProp, max) ? valueProp : 0;
    const progress = useDerivedValue(() => value ?? 0);

    const indicator = useAnimatedStyle(() => {
      return {
        width: withSpring(
          `${interpolate(progress.value, [0, 100], [1, 100], Extrapolation.CLAMP)}%`,
          { overshootClamping: true }
        ),
      };
    });

    return (
      <View
        role="progressbar"
        ref={ref}
        aria-valuemax={max}
        aria-valuemin={0}
        aria-valuenow={value}
        aria-valuetext={getValueLabel(value, max)}
        accessibilityValue={{
          min: 0,
          max,
          now: value,
          text: getValueLabel(value, max),
        }}
        className={cn('relative h-1 w-full overflow-hidden rounded-full', className)}
        {...props}>
        <View className="bg-muted absolute bottom-0 left-0 right-0 top-0 opacity-20" />
        <Animated.View role="presentation" style={indicator} className={cn('bg-primary h-full')} />
      </View>
    );
  }
);

ProgressIndicator.displayName = 'ProgressIndicator';

export { ProgressIndicator };

function defaultGetValueLabel(value: number, max: number) {
  return `${Math.round((value / max) * 100)}%`;
}

function isValidValueNumber(value: any, max: number): value is number {
  return typeof value === 'number' && !isNaN(value) && value <= max && value >= 0;
}
🛸
Ship.

Usage

import { ProgressIndicator } from '~/components/nativewindui/ProgressIndicator';
<ProgressIndicator value={50} />

Props

ProgressIndicator

Inherits all the props from React Native's View component.

PropTypeDescription
valuenumberThe current value to be represented.
maxnumberThe maximum value for the range.
getValueLabel(value: number, max: number) => stringA function to format the value label based on the current value and maximum.
© Ronin Technologies LLC 2024