Show loading indicator when adding uploads
This commit is contained in:
parent
b431e5de5c
commit
afd0e23a67
@ -1,23 +1,17 @@
|
||||
import Link from 'next/link';
|
||||
import { BiImageAdd } from 'react-icons/bi';
|
||||
import PathLoaderButton from '@/components/PathLoaderButton';
|
||||
|
||||
export default function AddButton ({
|
||||
href,
|
||||
label = 'Add',
|
||||
export default function AddButton({
|
||||
path,
|
||||
}: {
|
||||
href: string,
|
||||
label?: string,
|
||||
path: string,
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
title={label}
|
||||
href={href}
|
||||
className="button"
|
||||
<PathLoaderButton
|
||||
path={path}
|
||||
icon={<BiImageAdd size={18} className="translate-x-[1px]" />}
|
||||
>
|
||||
<BiImageAdd size={18} className="translate-y-[1px]" />
|
||||
<span className="hidden sm:inline-block">
|
||||
{label}
|
||||
</span>
|
||||
</Link>
|
||||
Add
|
||||
</PathLoaderButton>
|
||||
);
|
||||
}
|
||||
|
||||
@ -91,7 +91,9 @@ export default function AdminPhotosTable({
|
||||
>
|
||||
<input type="hidden" name="id" value={photo.id} />
|
||||
<SubmitButtonWithStatus
|
||||
icon={<IconGrSync className="translate-y-[-0.5px]" />}
|
||||
icon={<IconGrSync
|
||||
className="translate-x-[1px] translate-y-[0.5px]"
|
||||
/>}
|
||||
onFormSubmitToastMessage={`
|
||||
"${titleForPhoto(photo)}" EXIF data synced
|
||||
`}
|
||||
|
||||
@ -49,7 +49,7 @@ export default function AdminUploadsTable({
|
||||
'flex flex-nowrap',
|
||||
'gap-2 sm:gap-3 items-center',
|
||||
)}>
|
||||
<AddButton href={addUploadPath} />
|
||||
<AddButton path={addUploadPath} />
|
||||
<FormWithConfirm
|
||||
action={deleteBlobPhotoAction}
|
||||
confirmText="Are you sure you want to delete this upload?"
|
||||
|
||||
@ -11,7 +11,7 @@ export default function ClearCacheButton() {
|
||||
return (
|
||||
<form action={syncCacheAction}>
|
||||
<SubmitButtonWithStatus
|
||||
icon={<BiTrash />}
|
||||
icon={<BiTrash size={16} />}
|
||||
onFormSubmit={invalidateSwr}
|
||||
>
|
||||
Clear Cache
|
||||
|
||||
@ -14,6 +14,7 @@ export default function DeleteButton (
|
||||
const {
|
||||
onFormSubmit: onFormSubmitProps,
|
||||
clearLocalState,
|
||||
className,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@ -30,11 +31,13 @@ export default function DeleteButton (
|
||||
return <SubmitButtonWithStatus
|
||||
{...rest}
|
||||
title="Delete"
|
||||
icon={<BiTrash size={16} className="translate-y-[-1.5px]" />}
|
||||
icon={<BiTrash size={16} />}
|
||||
spinnerColor="text"
|
||||
className={clsx(
|
||||
'text-red-500 dark:text-red-600',
|
||||
className,
|
||||
'!text-red-500 dark:!text-red-600',
|
||||
'active:!bg-red-100/50 active:dark:!bg-red-950/50',
|
||||
'disabled:!bg-red-100/50 disabled:dark:!bg-red-950/50',
|
||||
'!border-red-200 hover:!border-red-300',
|
||||
'dark:!border-red-900/75 dark:hover:!border-red-900',
|
||||
)}
|
||||
|
||||
54
src/admin/LoaderButton.tsx
Normal file
54
src/admin/LoaderButton.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import Spinner, { SpinnerColor } from '@/components/Spinner';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||
|
||||
export default function LoaderButton(props: {
|
||||
children?: ReactNode
|
||||
isLoading?: boolean
|
||||
icon?: JSX.Element
|
||||
spinnerColor?: SpinnerColor
|
||||
styleAsLink?: boolean
|
||||
} & ButtonHTMLAttributes<HTMLButtonElement>) {
|
||||
const {
|
||||
children,
|
||||
isLoading,
|
||||
icon,
|
||||
spinnerColor,
|
||||
styleAsLink,
|
||||
type = 'button',
|
||||
disabled,
|
||||
className,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<button
|
||||
{...rest}
|
||||
type={type}
|
||||
className={clsx(
|
||||
className,
|
||||
styleAsLink ? 'link h-4' : 'h-9',
|
||||
'inline-flex items-center gap-2 self-start',
|
||||
)}
|
||||
disabled={isLoading || disabled}
|
||||
>
|
||||
{(icon || isLoading) &&
|
||||
<span className={clsx(
|
||||
'min-w-[1.25rem] h-4 translate-y-[-0.5px]',
|
||||
'inline-flex justify-center',
|
||||
)}>
|
||||
{isLoading
|
||||
? <Spinner
|
||||
size={14}
|
||||
color={spinnerColor}
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
: icon}
|
||||
</span>}
|
||||
{children && <span className={clsx(
|
||||
icon !== undefined && 'hidden sm:inline-block',
|
||||
)}>
|
||||
{children}
|
||||
</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
66
src/components/PathLoaderButton.tsx
Normal file
66
src/components/PathLoaderButton.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ReactNode, useEffect, useState, useTransition } from 'react';
|
||||
import { SpinnerColor } from './Spinner';
|
||||
import LoaderButton from '@/admin/LoaderButton';
|
||||
|
||||
export default function PathLoaderButton({
|
||||
path,
|
||||
icon,
|
||||
prefetch,
|
||||
loaderDelay = 100,
|
||||
shouldScroll = true,
|
||||
shouldReplace,
|
||||
spinnerColor,
|
||||
children,
|
||||
}: {
|
||||
path: string
|
||||
icon?: JSX.Element
|
||||
prefetch?: boolean
|
||||
loaderDelay?: number
|
||||
shouldScroll?: boolean
|
||||
shouldReplace?: boolean
|
||||
spinnerColor?: SpinnerColor
|
||||
children?: ReactNode
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const [shouldShowLoader, setShouldShowLoader] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPending) {
|
||||
const timeout = setTimeout(() => {
|
||||
setShouldShowLoader(true);
|
||||
}, loaderDelay);
|
||||
return () => clearTimeout(timeout);
|
||||
} else {
|
||||
setShouldShowLoader(false);
|
||||
}
|
||||
}, [isPending, loaderDelay]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prefetch) {
|
||||
router.prefetch(path);
|
||||
}
|
||||
}, [prefetch, router, path]);
|
||||
|
||||
return (
|
||||
<LoaderButton
|
||||
icon={icon}
|
||||
onClick={() => startTransition(() => {
|
||||
if (shouldReplace) {
|
||||
router.replace(path, { scroll: shouldScroll });
|
||||
} else {
|
||||
router.push(path, { scroll: shouldScroll });
|
||||
}
|
||||
})}
|
||||
isLoading={shouldShowLoader}
|
||||
spinnerColor={spinnerColor}
|
||||
>
|
||||
{children}
|
||||
</LoaderButton>
|
||||
);
|
||||
}
|
||||
@ -2,9 +2,10 @@
|
||||
|
||||
import { HTMLProps, useEffect, useRef } from 'react';
|
||||
import { useFormStatus } from 'react-dom';
|
||||
import Spinner, { SpinnerColor } from './Spinner';
|
||||
import { SpinnerColor } from './Spinner';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { toastSuccess } from '@/toast';
|
||||
import LoaderButton from '@/admin/LoaderButton';
|
||||
|
||||
interface Props extends HTMLProps<HTMLButtonElement> {
|
||||
icon?: JSX.Element
|
||||
@ -49,34 +50,21 @@ export default function SubmitButtonWithStatus({
|
||||
}, [onFormStatusChange, pending]);
|
||||
|
||||
return (
|
||||
<button
|
||||
<LoaderButton
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
className={clsx(
|
||||
className,
|
||||
'inline-flex items-center gap-2',
|
||||
primary && 'primary',
|
||||
styleAsLink && 'link',
|
||||
)}
|
||||
icon={icon}
|
||||
spinnerColor={spinnerColor}
|
||||
styleAsLink={styleAsLink}
|
||||
isLoading={pending}
|
||||
{...buttonProps}
|
||||
>
|
||||
{(icon || pending) &&
|
||||
<span className={clsx(
|
||||
'h-4',
|
||||
'min-w-[1rem]',
|
||||
'inline-flex justify-center sm:justify-normal',
|
||||
'-mx-0.5',
|
||||
'translate-y-[1px]',
|
||||
)}>
|
||||
{pending
|
||||
? <Spinner size={14} color={spinnerColor} />
|
||||
: icon}
|
||||
</span>}
|
||||
{children && <span className={clsx(
|
||||
icon !== undefined && 'hidden sm:inline-block',
|
||||
)}>
|
||||
{children}
|
||||
</span>}
|
||||
</button>
|
||||
{children}
|
||||
</LoaderButton>
|
||||
);
|
||||
};
|
||||
|
||||
@ -77,12 +77,13 @@
|
||||
cursor-pointer
|
||||
hover:no-underline
|
||||
inline-flex gap-2 items-center
|
||||
px-4
|
||||
px-3
|
||||
text-base
|
||||
shadow-sm
|
||||
active:bg-gray-100 dark:active:bg-gray-900
|
||||
hover:border-gray-300 dark:hover:border-gray-600
|
||||
disabled:cursor-not-allowed
|
||||
disabled:text-dim
|
||||
disabled:bg-gray-100 dark:disabled:bg-gray-900
|
||||
disabled:border-gray-200 disabled:dark:border-gray-700
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user