Skip to content

Commit 2fc5748

Browse files
committed
chore: add Spinner component
1 parent 07f6e53 commit 2fc5748

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed
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 { Spinner } from "./Spinner";
3+
import { PlusIcon } from "lucide-react";
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: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* This component was inspired by
3+
* https://www.radix-ui.com/themes/docs/components/spinner and developed using
4+
* https://v0.dev/ help.
5+
*/
6+
7+
import { cva, type VariantProps } from "class-variance-authority";
8+
import type { ReactNode } from "react";
9+
import { cn } from "utils/cn";
10+
11+
const leaves = 8;
12+
13+
const spinnerVariants = cva("", {
14+
variants: {
15+
size: {
16+
lg: "size-icon-lg",
17+
sm: "size-icon-sm",
18+
},
19+
},
20+
defaultVariants: {
21+
size: "lg",
22+
},
23+
});
24+
25+
type SpinnerProps = React.SVGProps<SVGSVGElement> &
26+
VariantProps<typeof spinnerVariants> & {
27+
children?: ReactNode;
28+
loading?: boolean;
29+
};
30+
31+
export function Spinner({
32+
className,
33+
size,
34+
loading,
35+
children,
36+
...props
37+
}: SpinnerProps) {
38+
if (!loading) {
39+
return children;
40+
}
41+
42+
return (
43+
<svg
44+
viewBox="0 0 24 24"
45+
xmlns="http://www.w3.org/2000/svg"
46+
fill="currentColor"
47+
className={cn(spinnerVariants({ size, className }))}
48+
{...props}
49+
>
50+
<title>Loading spinner</title>
51+
{[...Array(leaves)].map((_, i) => {
52+
const rotation = i * (360 / leaves);
53+
54+
return (
55+
<rect
56+
// biome-ignore lint/suspicious/noArrayIndexKey: This is a static array
57+
key={i}
58+
x="11.5"
59+
y="3"
60+
width="1.5"
61+
height="4"
62+
rx="0.5"
63+
// 0.8 = leaves * 0.1
64+
className="animate-[loading_0.8s_ease-in-out_infinite]"
65+
style={{
66+
transform: `rotate(${rotation}deg)`,
67+
transformOrigin: "center",
68+
animationDelay: `${-i * 0.1}s`,
69+
}}
70+
/>
71+
);
72+
})}
73+
</svg>
74+
);
75+
}

site/tailwind.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ module.exports = {
5858
5: "hsl(var(--chart-5))",
5959
},
6060
},
61+
keyframes: {
62+
loading: {
63+
"0%": { opacity: 0.85 },
64+
"50%": { opacity: 0.25 },
65+
"100%": { opacity: 0.25 },
66+
},
67+
},
6168
},
6269
},
6370
plugins: [require("tailwindcss-animate")],

0 commit comments

Comments
 (0)