Fix switch & button loading

This commit is contained in:
Stan Wohlwend 2024-05-18 17:51:18 +02:00
parent fa1f2a73a9
commit be7dc37a02
4 changed files with 52 additions and 48 deletions

View File

@ -1,8 +1,9 @@
"use client";;
"use client";
import { useAdminApp } from "../use-admin-app";
import { PageLayout } from "../page-layout";
import { SettingCard, SettingSwitch } from "@/components/settings";
import Typography from "@/components/ui/typography";
import { Button } from "@/components/ui/button";
export default function PageClient() {
const stackAdminApp = useAdminApp();

View File

@ -1,10 +1,11 @@
import * as React from "react";
import { ReloadIcon } from "@radix-ui/react-icons";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { Spinner } from "./spinner";
import { useAsyncCallback, useAsyncCallbackWithLoggedError } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
@ -63,28 +64,20 @@ interface ButtonProps extends OriginalButtonProps {
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ onClick, loading: loadingProp, children, ...props }, ref) => {
const [isLoading, setLoading] = React.useState(false);
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
if (onClick) {
setLoading(true);
try {
await onClick(e);
} finally {
setLoading(false);
}
}
};
const [handleClick, isLoading] = useAsyncCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
await onClick?.(e);
}, [onClick]);
const loading = loadingProp ?? isLoading;
const loading = loadingProp || isLoading;
return (
<OriginalButton
ref={ref}
onClick={(e) => runAsynchronously(handleClick(e))}
disabled={loading}
{...props}
ref={ref}
disabled={props.disabled || loading}
onClick={(e) => runAsynchronously(handleClick(e))}
>
{loading && <ReloadIcon className="mr-2 h-4 w-4 animate-spin" />}
{loading && <Spinner className="mr-2" />}
{children}
</OriginalButton>
);

View File

@ -0,0 +1,14 @@
import { ReloadIcon } from "@radix-ui/react-icons";
import React from "react";
export const Spinner = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<'span'>
>((props, ref) => {
return (
<span ref={ref} {...props}>
<ReloadIcon className="animate-spin" />
</span>
);
});
Spinner.displayName = "Spinner";

View File

@ -5,6 +5,8 @@ import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
import { Spinner } from "./spinner";
interface OriginalSwitchProps extends React.ComponentProps<typeof SwitchPrimitives.Root> {}
@ -37,40 +39,34 @@ interface AsyncSwitchProps extends OriginalSwitchProps {
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
AsyncSwitchProps
>(({ loading: loadingProp, onClick, ...props }, ref) => {
const [isLoading, setLoading] = React.useState(false);
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
if (onClick) {
setLoading(true);
try {
await onClick(e);
} finally {
setLoading(false);
}
}
};
>(({ loading: loadingProp, onClick, onCheckedChange, ...props }, ref) => {
const [handleClick, isLoadingClick] = useAsyncCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
await onClick?.(e);
}, [onClick]);
const handleCheckedChange = async (checked: boolean) => {
if (props.onCheckedChange) {
setLoading(true);
try {
await props.onCheckedChange(checked);
} finally {
setLoading(false);
}
}
};
const [handleCheckedChange, isLoadingCheckedChange] = useAsyncCallback(async (checked: boolean) => {
await onCheckedChange?.(checked);
}, [onCheckedChange]);
const loading = loadingProp ?? isLoading;
const loading = loadingProp || isLoadingClick || isLoadingCheckedChange;
return (
<OriginalSwitch
onClick={(e) => runAsynchronously(handleClick(e))}
onCheckedChange={(checked) => runAsynchronously(handleCheckedChange(checked))}
disabled={loading}
ref={ref}
{...props}
/>
<span className="relative leading-[0]">
<OriginalSwitch
{...props}
ref={ref}
onClick={(e) => runAsynchronously(handleClick(e))}
onCheckedChange={(checked) => runAsynchronously(handleCheckedChange(checked))}
disabled={props.disabled || loading}
style={{
visibility: loading ? "hidden" : "visible",
...props.style,
}}
/>
<span className={cn("absolute inset-0 flex items-center justify-center", !loading && "hidden")}>
<Spinner />
</span>
</span>
);
});
Switch.displayName = "Switch";