diff --git a/src/admin/AddButton.tsx b/src/admin/AddButton.tsx
index b1bdede6..f776b6d7 100644
--- a/src/admin/AddButton.tsx
+++ b/src/admin/AddButton.tsx
@@ -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 (
- }
>
-
-
- {label}
-
-
+ Add
+
);
}
diff --git a/src/admin/AdminPhotosTable.tsx b/src/admin/AdminPhotosTable.tsx
index a0802161..e501c963 100644
--- a/src/admin/AdminPhotosTable.tsx
+++ b/src/admin/AdminPhotosTable.tsx
@@ -91,7 +91,9 @@ export default function AdminPhotosTable({
>
}
+ icon={}
onFormSubmitToastMessage={`
"${titleForPhoto(photo)}" EXIF data synced
`}
diff --git a/src/admin/AdminUploadsTable.tsx b/src/admin/AdminUploadsTable.tsx
index 316f7222..1427f594 100644
--- a/src/admin/AdminUploadsTable.tsx
+++ b/src/admin/AdminUploadsTable.tsx
@@ -49,7 +49,7 @@ export default function AdminUploadsTable({
'flex flex-nowrap',
'gap-2 sm:gap-3 items-center',
)}>
-
+
}
+ icon={}
onFormSubmit={invalidateSwr}
>
Clear Cache
diff --git a/src/admin/DeleteButton.tsx b/src/admin/DeleteButton.tsx
index 7c570903..7c048beb 100644
--- a/src/admin/DeleteButton.tsx
+++ b/src/admin/DeleteButton.tsx
@@ -14,6 +14,7 @@ export default function DeleteButton (
const {
onFormSubmit: onFormSubmitProps,
clearLocalState,
+ className,
...rest
} = props;
@@ -30,11 +31,13 @@ export default function DeleteButton (
return }
+ icon={}
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',
)}
diff --git a/src/admin/LoaderButton.tsx b/src/admin/LoaderButton.tsx
new file mode 100644
index 00000000..b6c55329
--- /dev/null
+++ b/src/admin/LoaderButton.tsx
@@ -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) {
+ const {
+ children,
+ isLoading,
+ icon,
+ spinnerColor,
+ styleAsLink,
+ type = 'button',
+ disabled,
+ className,
+ ...rest
+ } = props;
+ return (
+
+ );
+}
diff --git a/src/components/PathLoaderButton.tsx b/src/components/PathLoaderButton.tsx
new file mode 100644
index 00000000..ba121f4d
--- /dev/null
+++ b/src/components/PathLoaderButton.tsx
@@ -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 (
+ startTransition(() => {
+ if (shouldReplace) {
+ router.replace(path, { scroll: shouldScroll });
+ } else {
+ router.push(path, { scroll: shouldScroll });
+ }
+ })}
+ isLoading={shouldShowLoader}
+ spinnerColor={spinnerColor}
+ >
+ {children}
+
+ );
+}
diff --git a/src/components/SubmitButtonWithStatus.tsx b/src/components/SubmitButtonWithStatus.tsx
index 514b138d..3cd6a248 100644
--- a/src/components/SubmitButtonWithStatus.tsx
+++ b/src/components/SubmitButtonWithStatus.tsx
@@ -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 {
icon?: JSX.Element
@@ -49,34 +50,21 @@ export default function SubmitButtonWithStatus({
}, [onFormStatusChange, pending]);
return (
-
+ {children}
+
);
};
diff --git a/src/site/globals.css b/src/site/globals.css
index 5052b267..f0fdf1a1 100644
--- a/src/site/globals.css
+++ b/src/site/globals.css
@@ -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
}