Streamline primitive components
This commit is contained in:
parent
547def1721
commit
2e4208e7e1
@ -1,5 +1,5 @@
|
||||
import { BiImageAdd } from 'react-icons/bi';
|
||||
import PathLoaderButton from '@/components/PathLoaderButton';
|
||||
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
|
||||
|
||||
export default function AddButton({
|
||||
path,
|
||||
|
||||
@ -80,7 +80,7 @@ export default function AdminPhotosTable({
|
||||
'flex flex-nowrap',
|
||||
'gap-2 sm:gap-3 items-center',
|
||||
)}>
|
||||
<EditButton href={pathForAdminPhotoEdit(photo)} />
|
||||
<EditButton path={pathForAdminPhotoEdit(photo)} />
|
||||
<FormWithConfirm
|
||||
action={syncPhotoExifDataAction}
|
||||
confirmText={
|
||||
|
||||
@ -26,7 +26,7 @@ export default function AdminTagTable({
|
||||
'flex flex-nowrap',
|
||||
'gap-2 sm:gap-3 items-center',
|
||||
)}>
|
||||
<EditButton href={pathForAdminTagEdit(tag)} />
|
||||
<EditButton path={pathForAdminTagEdit(tag)} />
|
||||
<FormWithConfirm
|
||||
action={deletePhotoTagGloballyAction}
|
||||
confirmText={
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import PathLoaderButton from '@/components/PathLoaderButton';
|
||||
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
|
||||
import { FaRegEdit } from 'react-icons/fa';
|
||||
|
||||
export default function EditButton ({
|
||||
href,
|
||||
path,
|
||||
}: {
|
||||
href: string,
|
||||
path: string,
|
||||
}) {
|
||||
return (
|
||||
<PathLoaderButton
|
||||
path={href}
|
||||
path={path}
|
||||
icon={<FaRegEdit size={15} className="translate-y-[0.5px]" />}
|
||||
>
|
||||
Edit
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { clsx } from 'clsx/lite';
|
||||
import Spinner, { SpinnerColor } from './Spinner';
|
||||
|
||||
export default function IconButton({
|
||||
icon,
|
||||
onClick,
|
||||
isLoading,
|
||||
className,
|
||||
spinnerColor,
|
||||
spinnerSize,
|
||||
}: {
|
||||
icon: JSX.Element
|
||||
onClick?: () => void
|
||||
isLoading?: boolean
|
||||
className?: string
|
||||
spinnerColor?: SpinnerColor
|
||||
spinnerSize?: number
|
||||
}) {
|
||||
return (
|
||||
<span className={clsx(
|
||||
className,
|
||||
'relative inline-flex items-center',
|
||||
'w-[1rem] h-[1.1rem]',
|
||||
)}>
|
||||
{!isLoading
|
||||
? <button
|
||||
onClick={onClick}
|
||||
className={clsx(
|
||||
'inline-flex items-center justify-center',
|
||||
'p-0 border-none shadow-none',
|
||||
'active:bg-transparent bg-transparent dark:bg-transparent',
|
||||
'translate-x-[-1px]',
|
||||
onClick !== undefined && 'cursor-pointer',
|
||||
'active:opacity-50',
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
: <span className={clsx(
|
||||
'inline-flex items-center justify-center',
|
||||
'h-full w-full',
|
||||
)}>
|
||||
<Spinner
|
||||
color={spinnerColor}
|
||||
size={spinnerSize}
|
||||
/>
|
||||
</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import IconButton from './IconButton';
|
||||
import { useEffect, useState, useTransition } from 'react';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { SpinnerColor } from './Spinner';
|
||||
|
||||
export default function IconPathButton({
|
||||
icon,
|
||||
path,
|
||||
prefetch,
|
||||
loaderDelay = 250,
|
||||
shouldScroll = true,
|
||||
shouldReplace,
|
||||
spinnerColor,
|
||||
}: {
|
||||
icon: JSX.Element
|
||||
path: string
|
||||
prefetch?: boolean
|
||||
loaderDelay?: number
|
||||
shouldScroll?: boolean
|
||||
shouldReplace?: boolean
|
||||
spinnerColor?: SpinnerColor
|
||||
}) {
|
||||
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 (
|
||||
<IconButton
|
||||
icon={icon}
|
||||
onClick={() => startTransition(() => {
|
||||
if (shouldReplace) {
|
||||
router.replace(path, { scroll: shouldScroll });
|
||||
} else {
|
||||
router.push(path, { scroll: shouldScroll });
|
||||
}
|
||||
})}
|
||||
isLoading={shouldShowLoader}
|
||||
className={clsx(
|
||||
'translate-y-[-0.5px]',
|
||||
'active:translate-y-[1px]',
|
||||
'text-medium',
|
||||
'active:text-gray-600 dark:active:text-gray-300',
|
||||
)}
|
||||
spinnerColor={spinnerColor ?? 'text'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,27 +1,33 @@
|
||||
import { TbPhotoShare } from 'react-icons/tb';
|
||||
import IconPathButton from '@/components/IconPathButton';
|
||||
import PathLoaderButton from './primitives/PathLoaderButton';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export default function ShareButton({
|
||||
path,
|
||||
prefetch,
|
||||
shouldScroll,
|
||||
dim,
|
||||
className,
|
||||
}: {
|
||||
path: string
|
||||
prefetch?: boolean
|
||||
shouldScroll?: boolean
|
||||
dim?: boolean
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<IconPathButton {...{
|
||||
path,
|
||||
icon: <TbPhotoShare size={17} className={dim
|
||||
? 'text-dim'
|
||||
: undefined} />,
|
||||
prefetch,
|
||||
shouldScroll,
|
||||
shouldReplace: true,
|
||||
spinnerColor: 'dim',
|
||||
}} />
|
||||
<PathLoaderButton
|
||||
path={path}
|
||||
className={clsx(
|
||||
className,
|
||||
dim ? 'text-dim' : 'text-medium',
|
||||
)}
|
||||
icon={<TbPhotoShare size={17} />}
|
||||
spinnerColor="dim"
|
||||
prefetch={prefetch}
|
||||
shouldScroll={shouldScroll}
|
||||
shouldReplace
|
||||
styleAsLink
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { useFormStatus } from 'react-dom';
|
||||
import { SpinnerColor } from './Spinner';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { toastSuccess } from '@/toast';
|
||||
import LoaderButton from '@/admin/LoaderButton';
|
||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||
|
||||
interface Props extends HTMLProps<HTMLButtonElement> {
|
||||
icon?: JSX.Element
|
||||
|
||||
@ -26,7 +26,9 @@ export default function LoaderButton(props: {
|
||||
type={type}
|
||||
className={clsx(
|
||||
className,
|
||||
styleAsLink ? 'link h-4' : 'h-9',
|
||||
styleAsLink
|
||||
? 'link h-4 hover:text-dim active:text-medium'
|
||||
: 'h-9',
|
||||
'inline-flex items-center gap-2 self-start',
|
||||
)}
|
||||
disabled={isLoading || disabled}
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ReactNode, useEffect, useState, useTransition } from 'react';
|
||||
import { SpinnerColor } from './Spinner';
|
||||
import LoaderButton from '@/admin/LoaderButton';
|
||||
import { SpinnerColor } from '../Spinner';
|
||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||
|
||||
export default function PathLoaderButton({
|
||||
path,
|
||||
@ -13,6 +13,8 @@ export default function PathLoaderButton({
|
||||
shouldScroll = true,
|
||||
shouldReplace,
|
||||
spinnerColor,
|
||||
styleAsLink,
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
path: string
|
||||
@ -22,6 +24,8 @@ export default function PathLoaderButton({
|
||||
shouldScroll?: boolean
|
||||
shouldReplace?: boolean
|
||||
spinnerColor?: SpinnerColor
|
||||
styleAsLink?: boolean
|
||||
className?: string
|
||||
children?: ReactNode
|
||||
}) {
|
||||
const router = useRouter();
|
||||
@ -50,6 +54,7 @@ export default function PathLoaderButton({
|
||||
return (
|
||||
<LoaderButton
|
||||
icon={icon}
|
||||
className={className}
|
||||
onClick={() => startTransition(() => {
|
||||
if (shouldReplace) {
|
||||
router.replace(path, { scroll: shouldScroll });
|
||||
@ -59,6 +64,7 @@ export default function PathLoaderButton({
|
||||
})}
|
||||
isLoading={shouldShowLoader}
|
||||
spinnerColor={spinnerColor}
|
||||
styleAsLink={styleAsLink}
|
||||
>
|
||||
{children}
|
||||
</LoaderButton>
|
||||
@ -170,6 +170,10 @@ export default function PhotoLarge({
|
||||
{photo.takenAtNaiveFormatted}
|
||||
</div>
|
||||
<ShareButton
|
||||
className={clsx(
|
||||
'md:translate-x-[-2.5px]',
|
||||
'translate-y-[1.5px] md:translate-y-0',
|
||||
)}
|
||||
path={pathForPhotoShare(
|
||||
photo,
|
||||
shouldShareTag ? primaryTag : undefined,
|
||||
|
||||
@ -62,9 +62,11 @@ export default function PhotoSetHeader({
|
||||
? `${entityVerb} ${selectedPhotoIndex + 1} of ${count ?? photos.length}`
|
||||
: entityDescription}
|
||||
{selectedPhotoIndex === undefined &&
|
||||
<span className="translate-y-[1px]">
|
||||
<ShareButton path={sharePath} dim />
|
||||
</span>}
|
||||
<ShareButton
|
||||
className="translate-y-[1.5px]"
|
||||
path={sharePath}
|
||||
dim
|
||||
/>}
|
||||
</span>
|
||||
<span className={clsx(
|
||||
'hidden sm:inline-block',
|
||||
|
||||
@ -13,7 +13,6 @@ import {
|
||||
BiPencil,
|
||||
BiRefresh,
|
||||
} from 'react-icons/bi';
|
||||
import IconButton from '@/components/IconButton';
|
||||
import InfoBlock from '@/components/InfoBlock';
|
||||
import Checklist from '@/components/Checklist';
|
||||
import { toastSuccess } from '@/toast';
|
||||
@ -21,6 +20,7 @@ import { ConfigChecklistStatus } from './config';
|
||||
import StatusIcon from '@/components/StatusIcon';
|
||||
import { labelForStorage } from '@/services/storage';
|
||||
import { HiSparkles } from 'react-icons/hi';
|
||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||
|
||||
export default function SiteChecklistClient({
|
||||
hasDatabase,
|
||||
@ -93,13 +93,17 @@ export default function SiteChecklistClient({
|
||||
</>;
|
||||
|
||||
const renderCopyButton = (label: string, text: string, subtle?: boolean) =>
|
||||
<IconButton
|
||||
<LoaderButton
|
||||
icon={<BiCopy size={15} />}
|
||||
className={clsx(subtle && 'text-gray-300 dark:text-gray-700')}
|
||||
className={clsx(
|
||||
'translate-y-[2px]',
|
||||
subtle && 'text-gray-300 dark:text-gray-700',
|
||||
)}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(text);
|
||||
toastSuccess(`${label} copied to clipboard`);
|
||||
}}
|
||||
styleAsLink
|
||||
/>;
|
||||
|
||||
const renderEnvVar = (
|
||||
@ -235,11 +239,12 @@ export default function SiteChecklistClient({
|
||||
<span>{secret}</span>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{renderCopyButton('Secret', secret)}
|
||||
<IconButton
|
||||
<LoaderButton
|
||||
icon={<BiRefresh size={18} />}
|
||||
onClick={refreshSecret}
|
||||
isLoading={isPendingSecret}
|
||||
spinnerColor="text"
|
||||
styleAsLink
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -112,7 +112,7 @@
|
||||
button.link {
|
||||
@apply
|
||||
p-0 min-h-0
|
||||
border-none bg-transparent active:bg-transparent shadow-none
|
||||
border-none bg-transparent active:bg-transparent shadow-none rounded-none
|
||||
}
|
||||
a, .link {
|
||||
@apply
|
||||
|
||||
Loading…
Reference in New Issue
Block a user