Skip to content

Commit 87fd0c2

Browse files
committed
chore: get initial version of spinner update in place
1 parent 2f356bc commit 87fd0c2

File tree

1 file changed

+47
-8
lines changed

1 file changed

+47
-8
lines changed

site/src/components/Spinner/Spinner.tsx

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import isChromatic from "chromatic/isChromatic";
88
import { type VariantProps, cva } from "class-variance-authority";
9-
import { type FC, useEffect, useState } from "react";
9+
import { type CSSProperties, type FC, useEffect, useState } from "react";
1010
import { cn } from "utils/cn";
1111

1212
const SPINNER_LEAF_COUNT = 8;
@@ -27,12 +27,44 @@ type SpinnerProps = Readonly<
2727
React.SVGProps<SVGSVGElement> &
2828
VariantProps<typeof spinnerVariants> & {
2929
loading: boolean;
30+
31+
/**
32+
* Indicates whether the children prop should be unmounted during
33+
* a loading state. Defaults to false - unmounting HTML elements
34+
* like form controls can lead to invalid HTML, so this prop should
35+
* be used with care and only if it prevents render performance
36+
* issues.
37+
*/
3038
unmountedWhileLoading?: boolean;
39+
40+
/**
41+
* Specifies whether there should be a delay before the spinner
42+
* appears on screen. If not specified, the spinner always appears
43+
* immediately.
44+
*
45+
* Can help avoid page flickering issues. (e.g., You have a modal
46+
* that takes a moment to close, and it has Spinner content inside
47+
* it. The user triggers a loading transition, and you want to show
48+
* the spinner at some point if a transition takes long enough, but
49+
* if the spinner mounting and modal closing happen in too quick of
50+
* a succession, the UI looks janky. So even though you might flip
51+
* the loading state immediately, you want to wait a second to show
52+
* it in case the modal can close quickly enough. It's lying to the
53+
* user in a way that makes the UI feel more polished.)
54+
*/
3155
spinnerStartDelayMs?: number;
3256
}
3357
>;
3458

3559
const leavesIterable = Array.from({ length: SPINNER_LEAF_COUNT }, (_, i) => i);
60+
const animationSettings: CSSProperties = isChromatic()
61+
? {}
62+
: {
63+
transitionDuration: `${0.1 * SPINNER_LEAF_COUNT}s`,
64+
transitionTimingFunction: "ease-in-out",
65+
animationIterationCount: "infinite",
66+
};
67+
3668
export const Spinner: FC<SpinnerProps> = ({
3769
className,
3870
size,
@@ -42,6 +74,11 @@ export const Spinner: FC<SpinnerProps> = ({
4274
unmountedWhileLoading = false,
4375
...delegatedProps
4476
}) => {
77+
/**
78+
* @todo Figure out if this conditional logic causes a component to lose
79+
* state. I would hope not, since the children prop is the same in both
80+
* cases, but I need to test this out
81+
*/
4582
const showSpinner = useShowSpinner(loading, spinnerStartDelayMs);
4683
if (!showSpinner) {
4784
return children;
@@ -67,26 +104,28 @@ export const Spinner: FC<SpinnerProps> = ({
67104
width="2"
68105
height="5.5"
69106
rx="1"
70-
className={
71-
// 0.8 is hard-coded because of Tailwind; the value
72-
// should always be (0.1 * SPINNER_LEAF_COUNT)
73-
isChromatic() ? "" : "animate-[loading_0.8s_ease-in-out_infinite]"
74-
}
75107
style={{
108+
...animationSettings,
76109
transform: `rotate(${leafIndex * (360 / SPINNER_LEAF_COUNT)}deg)`,
77110
transformOrigin: "center",
78111
animationDelay: `${-leafIndex * 0.1}s`,
79112
}}
80113
/>
81114
))}
82115
</svg>
83-
{!unmountedWhileLoading && <div className="sr-only">{children}</div>}
116+
117+
{!unmountedWhileLoading && (
118+
<div className="sr-only">
119+
This content is loading:
120+
{children}
121+
</div>
122+
)}
84123
</>
85124
);
86125
};
87126

88127
// Not a big fan of one-time custom hooks, but it helps insulate the main
89-
// component from the chaos of handling all these states, when the ultimate
128+
// component from the chaos of handling all these state syncs, when the ultimate
90129
// result is a simple boolean. V8 will be able to inline the function definition
91130
// in some cases anyway
92131
function useShowSpinner(

0 commit comments

Comments
 (0)