Streamline primitive components

This commit is contained in:
Sam Becker 2024-05-07 00:03:28 -05:00
parent 547def1721
commit 2e4208e7e1
14 changed files with 55 additions and 151 deletions

View File

@ -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,

View File

@ -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={

View File

@ -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={

View File

@ -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

View File

@ -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>
);
}

View File

@ -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'}
/>
);
}

View File

@ -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
/>
);
}

View File

@ -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

View File

@ -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}

View File

@ -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>

View File

@ -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,

View File

@ -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',

View File

@ -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>

View File

@ -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