import * as stylex from "@stylexjs/stylex"; import { memo } from "react"; import { interaction } from "./mixins"; import { size } from "./tokens.stylex"; export interface RefreshSpinnerProps { size: "small" | "medium"; status: RefreshSpinnerStatus; pullPosition?: number; style?: stylex.StyleXStyles<{ zIndex?: number; }>; } export type RefreshSpinnerStatus = "idle" | "pulling" | "refreshing" | "done"; const spin = stylex.keyframes({ from: { rotate: "0deg", }, to: { rotate: "360deg", }, }); const throb = stylex.keyframes({ from: { opacity: 0.1, }, to: { opacity: 0.5, }, }); const styles = stylex.create({ svg: { fill: "currentColor", opacity: 0.5, }, idle: { animationName: throb, animationDuration: "1s", animationTimingFunction: "ease-in-out", animationIterationCount: "infinite", animationDirection: "alternate", }, pulling: (progressDegree) => ({ maskImage: `conic-gradient(from ${progressDegree}deg, #fff0, #ffff)`, }), refreshing: { animationName: spin, animationDuration: "3s", animationTimingFunction: "linear", animationIterationCount: "infinite", }, done: { // We're setting the class instantly and don't remember the previous state // This means that the icon may be spinning before // we cannot retain its position, so it will jump back to the initial rotation. This would be visible if we'd animate the opacity. // transition: `opacity ${duration.slow}`, visibility: "hidden", }, }); const sizes = stylex.create({ small: { width: size.px4, height: size.px4, maxWidth: size.px4, maxHeight: size.px4, }, medium: { width: size.px8, height: size.px8, maxWidth: size.px8, maxHeight: size.px8, }, }); export default memo(function RefreshSpinner({ size, status, pullPosition, style, }: RefreshSpinnerProps) { return ( // biome-ignore lint/a11y/noSvgWithoutTitle: it's a spinner, it doesn't need a title ); });