Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cee3656

Browse files
chore: add spinner component (#16014)
This component is necessary to move forward with the "[Define a global button style](#14978)" issue, as it requires buttons to support loading states with spinners. The `Spinner` component is heavily inspired by the [Radix UI Spinner component](https://www.radix-ui.com/themes/docs/components/spinner) and was developed with the help of [v0](https://v0.dev/). **Preview** ![Screen Recording 2025-01-02 at 14 37 18](https://github.com/user-attachments/assets/838f6bb2-2125-4a55-9bee-3b3a52852d40) --------- Co-authored-by: Jaayden Halko <[email protected]>
1 parent 21a45cf commit cee3656

File tree

5 files changed

+128
-20
lines changed

5 files changed

+128
-20
lines changed

site/src/components/Loader/Loader.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Interpolation, Theme } from "@emotion/react";
2-
import { Spinner } from "components/Spinner/Spinner";
2+
import { Spinner } from "components/deprecated/Spinner/Spinner";
33
import type { FC, HTMLAttributes } from "react";
44

55
interface LoaderProps extends HTMLAttributes<HTMLDivElement> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { PlusIcon } from "lucide-react";
3+
import { Spinner } from "./Spinner";
4+
5+
const meta: Meta<typeof Spinner> = {
6+
title: "components/Spinner",
7+
component: Spinner,
8+
args: {
9+
children: <PlusIcon className="size-icon-lg" />,
10+
},
11+
};
12+
13+
export default meta;
14+
type Story = StoryObj<typeof Spinner>;
15+
16+
export const Idle: Story = {};
17+
18+
export const Loading: Story = {
19+
args: { loading: true },
20+
};
+74-19
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,77 @@
1-
import CircularProgress, {
2-
type CircularProgressProps,
3-
} from "@mui/material/CircularProgress";
4-
import isChromatic from "chromatic/isChromatic";
5-
import type { FC } from "react";
6-
71
/**
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.
115
*/
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;
2041
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
/**
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.
11+
*
12+
* @deprecated prefer `components.Spinner`
13+
*/
14+
export const Spinner: FC<CircularProgressProps> = (props) => {
15+
/**
16+
* During Chromatic snapshots, we render the spinner as determinate to make it
17+
* static without animations, using a deterministic value (75%).
18+
*/
19+
if (isChromatic()) {
20+
props.variant = "determinate";
21+
props.value = 75;
22+
}
23+
return <CircularProgress {...props} />;
24+
};

site/tailwind.config.js

+9
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ module.exports = {
5858
5: "hsl(var(--chart-5))",
5959
},
6060
},
61+
keyframes: {
62+
loading: {
63+
"0%": { opacity: 0.85 },
64+
"25%": { opacity: 0.7 },
65+
"50%": { opacity: 0.4 },
66+
"75%": { opacity: 0.3 },
67+
"100%": { opacity: 0.2 },
68+
},
69+
},
6170
},
6271
},
6372
plugins: [require("tailwindcss-animate")],

0 commit comments

Comments
 (0)