|
1 |
| -import CircularProgress, { |
2 |
| - type CircularProgressProps, |
3 |
| -} from "@mui/material/CircularProgress"; |
4 |
| -import isChromatic from "chromatic/isChromatic"; |
5 |
| -import type { FC } from "react"; |
6 |
| - |
7 | 1 | /**
|
8 |
| - * Spinner component used to indicate loading states. This component abstracts |
9 |
| - * the MUI CircularProgress to provide better control over its rendering, |
10 |
| - * especially in snapshot tests with Chromatic. |
| 2 | + * This component was inspired by |
| 3 | + * https://www.radix-ui.com/themes/docs/components/spinner and developed using |
| 4 | + * https://v0.dev/ help. |
11 | 5 | */
|
12 |
| -export const Spinner: FC<CircularProgressProps> = (props) => { |
13 |
| - /** |
14 |
| - * During Chromatic snapshots, we render the spinner as determinate to make it |
15 |
| - * static without animations, using a deterministic value (75%). |
16 |
| - */ |
17 |
| - if (isChromatic()) { |
18 |
| - props.variant = "determinate"; |
19 |
| - props.value = 75; |
| 6 | + |
| 7 | +import isChromatic from "chromatic/isChromatic"; |
| 8 | +import { type VariantProps, cva } from "class-variance-authority"; |
| 9 | +import type { ReactNode } from "react"; |
| 10 | +import { cn } from "utils/cn"; |
| 11 | + |
| 12 | +const leaves = 8; |
| 13 | + |
| 14 | +const spinnerVariants = cva("", { |
| 15 | + variants: { |
| 16 | + size: { |
| 17 | + lg: "size-icon-lg", |
| 18 | + sm: "size-icon-sm", |
| 19 | + }, |
| 20 | + }, |
| 21 | + defaultVariants: { |
| 22 | + size: "lg", |
| 23 | + }, |
| 24 | +}); |
| 25 | + |
| 26 | +type SpinnerProps = React.SVGProps<SVGSVGElement> & |
| 27 | + VariantProps<typeof spinnerVariants> & { |
| 28 | + children?: ReactNode; |
| 29 | + loading?: boolean; |
| 30 | + }; |
| 31 | + |
| 32 | +export function Spinner({ |
| 33 | + className, |
| 34 | + size, |
| 35 | + loading, |
| 36 | + children, |
| 37 | + ...props |
| 38 | +}: SpinnerProps) { |
| 39 | + if (!loading) { |
| 40 | + return children; |
20 | 41 | }
|
21 |
| - return <CircularProgress {...props} />; |
22 |
| -}; |
| 42 | + |
| 43 | + return ( |
| 44 | + <svg |
| 45 | + viewBox="0 0 24 24" |
| 46 | + xmlns="http://www.w3.org/2000/svg" |
| 47 | + fill="currentColor" |
| 48 | + className={cn(spinnerVariants({ size, className }))} |
| 49 | + {...props} |
| 50 | + > |
| 51 | + <title>Loading spinner</title> |
| 52 | + {[...Array(leaves)].map((_, i) => { |
| 53 | + const rotation = i * (360 / leaves); |
| 54 | + |
| 55 | + return ( |
| 56 | + <rect |
| 57 | + key={i} |
| 58 | + x="10.9" |
| 59 | + y="2" |
| 60 | + width="2" |
| 61 | + height="5.5" |
| 62 | + rx="1" |
| 63 | + // 0.8 = leaves * 0.1 |
| 64 | + className={ |
| 65 | + isChromatic() ? "" : "animate-[loading_0.8s_ease-in-out_infinite]" |
| 66 | + } |
| 67 | + style={{ |
| 68 | + transform: `rotate(${rotation}deg)`, |
| 69 | + transformOrigin: "center", |
| 70 | + animationDelay: `${-i * 0.1}s`, |
| 71 | + }} |
| 72 | + /> |
| 73 | + ); |
| 74 | + })} |
| 75 | + </svg> |
| 76 | + ); |
| 77 | +} |
0 commit comments