mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Fix switch & button loading
This commit is contained in:
parent
fa1f2a73a9
commit
be7dc37a02
@ -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();
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
14
packages/stack-server/src/components/ui/spinner.tsx
Normal file
14
packages/stack-server/src/components/ui/spinner.tsx
Normal 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";
|
||||
@ -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";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user