aboutsummaryrefslogtreecommitdiff
path: root/apps/web/components/superloader.tsx
blob: 1e51df685079dbf41c60f806bd8ebb9035e3f605 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"use client"

import { motion, useReducedMotion, type Variants } from "motion/react"

type NovaPathLoaderProps = {
	size?: number // px
	colorClassName?: string
	label?: string
	className?: string
}

// full SVG path data from nova-2d-anim.svg
const PATH_RIGHT =
	"M3.03472 6.05861L6.8539 9.91021H1.96777V11.9737H8.3006V17.4781H10.3467V11.5057C10.3467 10.8713 10.0963 10.2621 9.65119 9.81327L4.48145 4.59961L3.03472 6.05861Z"

const PATH_LEFT =
	"M12.6994 9.02793V3.52344H10.6533V9.49591C10.6533 10.1302 10.9037 10.7395 11.3488 11.1883L16.5197 16.4032L17.9665 14.9441L14.1473 11.0926H19.0334V9.02914L12.6994 9.02793Z"

// animation for stroke draw
const strokeVariants: Variants = {
	hidden: { pathLength: 0, opacity: 0.2 },
	visible: (i: number) => ({
		pathLength: [0, 1],
		opacity: [0.2, 1],
		transition: {
			duration: 0.9,
			repeat: Number.POSITIVE_INFINITY,
			repeatType: "reverse",
			ease: "easeInOut",
			delay: i * 0.18,
		},
	}),
	static: { pathLength: 1, opacity: 0.7 },
}

export function SuperLoader({
	size = 42,
	colorClassName = "text-sky-400",
	label = "Loading...",
	className = "",
}: NovaPathLoaderProps) {
	const prefersReducedMotion = useReducedMotion()

	const animateVariant = prefersReducedMotion ? "static" : "visible"

	return (
		<div
			role="status"
			aria-label={label}
			className={`inline-flex flex-col items-center gap-2 ${className}`}
			style={{ width: size + 10 }}
		>
			<motion.svg
				xmlns="http://www.w3.org/2000/svg"
				viewBox="0 0 21 21"
				width={size}
				height={size}
				className={`shrink-0 ${colorClassName}`}
			>
				<title>Loading...</title>
				{/* Right path */}
				<motion.path
					d={PATH_RIGHT}
					fill="none"
					stroke="currentColor"
					strokeWidth={1.4}
					strokeLinecap="round"
					strokeLinejoin="round"
					initial="hidden"
					animate={animateVariant}
					variants={strokeVariants}
					custom={0}
				/>

				{/* Left path */}
				<motion.path
					d={PATH_LEFT}
					fill="none"
					stroke="currentColor"
					strokeWidth={1.4}
					strokeLinecap="round"
					strokeLinejoin="round"
					initial="hidden"
					animate={animateVariant}
					variants={strokeVariants}
					custom={1}
				/>
			</motion.svg>

			<span
				className="text-xs font-medium text-slate-500"
				style={{ fontSize: size * 0.25 }}
			>
				{label}
			</span>
		</div>
	)
}