Pull to Refresh
beui/pull-to-refreshFreeComponent
Native-feeling pull-to-refresh container with drag resistance, threshold feedback and async refresh handling.
Install it
npx shadcn@latest add https://beui.dev/r/pull-to-refresh.jsonSource
1"use client";2// beui.dev/components/motion/pull-to-refresh34import {5 AnimatePresence,6 animate,7 type MotionValue,8 motion,9 useMotionValue,10 useReducedMotion,11 useTransform,12} from "motion/react";13import {14 type ReactNode,15 type PointerEvent as ReactPointerEvent,16 useCallback,17 useEffect,18 useRef,19 useState,20} from "react";21import {22 EASE_IN_OUT,23 EASE_OUT,24 SPRING_PANEL,25 SPRING_SWAP,26} from "@/lib/ease";27import { cn } from "@/lib/utils";2829export type PullToRefreshStatus =30 | "idle"31 | "pulling"32 | "ready"33 | "refreshing";3435export interface PullToRefreshProps {36 /** Runs after the user pulls beyond the threshold and releases. */37 onRefresh: () => void | Promise<void>;38 children: ReactNode;39 /** Keeps the indicator active while an externally managed refresh runs. */40 refreshing?: boolean;41 disabled?: boolean;42 /** Resisted pull distance in pixels required to refresh. */43 threshold?: number;44 /** Maximum resisted pull distance in pixels. */45 maxPull?: number;46 /** Content offset in pixels while refreshing. */47 holdDistance?: number;48 pullingLabel?: ReactNode;49 releaseLabel?: ReactNode;50 refreshingLabel?: ReactNode;51 ariaLabel?: string;52 className?: string;53 contentClassName?: string;54 indicatorClassName?: string;55}5657type Gesture = {58 active: boolean;59 startX: number;60 startY: number;61 pointerId: number | null;62};6364const EMPTY_GESTURE: Gesture = {65 active: false,66 startX: 0,67 startY: 0,68 pointerId: null,69};7071// This character needs a compact repeating rhythm rather than a settling72// spring: a small orbit and blink that read as activity without feeling busy.73const CHARACTER_LOOP = {74 duration: 0.9,75 ease: EASE_IN_OUT,76 repeat: Number.POSITIVE_INFINITY,77} as const;78const CALM_PULSE = {79 duration: 1.2,80 ease: EASE_IN_OUT,81 repeat: Number.POSITIVE_INFINITY,82} as const;83const LABEL_SWAP = { duration: 0.16, ease: EASE_OUT } as const;8485function resistedDistance(distance: number, maxPull: number) {86 return maxPull * (1 - Math.exp(-Math.max(0, distance) / maxPull));87}8889function RefreshBuddy({90 progress,91 status,92 reduce,93}: {94 progress: MotionValue<number>;95 status: PullToRefreshStatus;96 reduce: boolean;97}) {98 const lift = useTransform(progress, [0, 1], [-7, 0]);99 const tilt = useTransform(progress, [0, 1], [-10, 0]);100 const stretch = useTransform(progress, [0, 0.55, 1], [0.68, 1.1, 0.92]);101 const ready = status === "ready";102 const refreshing = status === "refreshing";103104 return (105 <motion.span106// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Action Swap Bluraction-swap-blur | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Cascadeaction-swap-cascade | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Rollaction-swap-roll | beui | Components | Free | 3 deps · 4 files | |
| Agent Activityagent-activity | beui | Components | Free | 4 deps · 9 files | |
| Agent Loading States Agent Progressagent-progress | beui | Components | Free | 3 deps · 3 files | |
| AI Sidebarai-sidebar | beui | Components | Free | 4 deps · 5 files | |
| Animated Badgeanimated-badge | beui | Components | Free | 4 deps · 3 files | |
| Number Animation Animated Numberanimated-number | beui | Components | Free | 3 deps · 3 files |
