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 <jaayden.halko@gmail.com>
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

Lines changed: 1 addition & 1 deletion
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> {
Lines changed: 20 additions & 0 deletions
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+
};
Lines changed: 74 additions & 19 deletions
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+
}
Lines changed: 24 additions & 0 deletions
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

Lines changed: 9 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy