Refine uploads layout

This commit is contained in:
Sam Becker 2025-06-18 09:23:20 -05:00
parent 3bd6b20e76
commit 6ac797d5ac
8 changed files with 95 additions and 75 deletions

View File

@ -2,9 +2,10 @@ import { BiImageAdd } from 'react-icons/bi';
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
import { ComponentProps } from 'react';
export default function AddButton(
props: ComponentProps<typeof PathLoaderButton>,
) {
export default function AddButton({
children,
...props
}: ComponentProps<typeof PathLoaderButton>) {
return (
<PathLoaderButton
{...props}
@ -13,7 +14,7 @@ export default function AddButton(
className="translate-x-[1px] translate-y-[1px]"
/>}
>
Add
{children || 'Add'}
</PathLoaderButton>
);
}

View File

@ -149,6 +149,7 @@ export default function AdminBatchUploadActions({
onChange={setTags}
onError={setTagErrorMessage}
readOnly={isAdding}
className="relative z-10"
/>
<div className="flex gap-8">
<FieldsetFavs

View File

@ -1,5 +1,6 @@
'use client';
import { Dispatch, SetStateAction } from 'react';
import { UrlAddStatus } from './AdminUploadsClient';
import AdminUploadsTableRow from './AdminUploadsTableRow';
@ -12,23 +13,24 @@ export default function AdminUploadsTable({
}: {
isAdding?: boolean
urlAddStatuses: UrlAddStatus[]
setUrlAddStatuses?: (urlAddStatuses: UrlAddStatus[]) => void
setUrlAddStatuses?: Dispatch<SetStateAction<UrlAddStatus[]>>
isDeleting?: boolean
setIsDeleting?: (isDeleting: boolean) => void
setIsDeleting?: Dispatch<SetStateAction<boolean>>
}) {
const isComplete = urlAddStatuses.every(({ status }) => status === 'added');
return (
<div className="space-y-4">
{urlAddStatuses.map(status =>
{urlAddStatuses.map((status, index) =>
<AdminUploadsTableRow
key={status.url}
{...{
...status,
tabIndex: index + 1,
shouldRedirectToAdminPhotosOnDelete: urlAddStatuses.length <= 1,
isAdding,
isDeleting,
isComplete,
setIsDeleting,
urlAddStatuses,
setUrlAddStatuses,
}}
/>,

View File

@ -11,9 +11,10 @@ import { FaRegCircleCheck } from 'react-icons/fa6';
import AddButton from './AddButton';
import { pathForAdminUploadUrl } from '@/app/paths';
import DeleteBlobButton from './DeleteUploadButton';
import { useEffect, useRef } from 'react';
import { Dispatch, SetStateAction, useEffect, useRef } from 'react';
import { isElementEntirelyInViewport } from '@/utility/dom';
import FieldSetWithStatus from '@/components/FieldSetWithStatus';
import EditButton from './EditButton';
export default function AdminUploadsTableRow({
url,
@ -22,19 +23,21 @@ export default function AdminUploadsTableRow({
draftTitle = '',
uploadedAt,
size,
tabIndex,
shouldRedirectToAdminPhotosOnDelete,
isAdding,
isDeleting,
isComplete,
setIsDeleting,
urlAddStatuses,
setUrlAddStatuses,
}: UrlAddStatus & {
tabIndex: number
shouldRedirectToAdminPhotosOnDelete: boolean
isAdding?: boolean
isDeleting?: boolean
isComplete?: boolean
setIsDeleting?: (isDeleting: boolean) => void
urlAddStatuses: UrlAddStatus[]
setUrlAddStatuses?: (urlAddStatuses: UrlAddStatus[]) => void
setIsDeleting?: Dispatch<SetStateAction<boolean>>
setUrlAddStatuses?: Dispatch<SetStateAction<UrlAddStatus[]>>
}) {
const ref = useRef<HTMLDivElement>(null);
@ -75,11 +78,67 @@ export default function AdminUploadsTableRow({
/>
</div>
<div className={clsx(
'flex flex-col w-full self-start min-w-0',
'flex self-stretch w-full min-w-0',
'gap-2 sm:gap-3',
'p-2 sm:p-3',
)}>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-3 w-full">
<div className="flex flex-col grow gap-3">
<FieldSetWithStatus
label="Title"
value={draftTitle}
onChange={titleUpdated => {
setUrlAddStatuses?.(statuses => statuses.map(status => ({
...status,
draftTitle: status.url === url
? titleUpdated
: status.draftTitle,
})));
}}
placeholder="Title (optional)"
tabIndex={tabIndex}
readOnly={isAdding || isDeleting || isComplete || Boolean(status)}
hideLabel
/>
<div className="flex items-center gap-2">
{isAdding || isComplete
? <>
{status === 'added'
? <FaRegCircleCheck size={18} />
: status === 'adding' &&
<Spinner
size={19}
className="translate-y-[2px]"
/>}
</>
: <>
<AddButton
path={pathForAdminUploadUrl(url)}
disabled={isDeleting}
hideTextOnMobile={false}
tooltip="Add directly"
/>
<EditButton
path={pathForAdminUploadUrl(url)}
tooltip="Review photo details"
hideText
/>
<DeleteBlobButton
urls={[url]}
shouldRedirectToAdminPhotos={
shouldRedirectToAdminPhotosOnDelete}
onDeleteStart={() => setIsDeleting?.(true)}
onDelete={() => {
setIsDeleting?.(false);
setUrlAddStatuses?.(statuses => statuses
.filter(({ url: urlToRemove }) => urlToRemove !== url));
}}
isLoading={isDeleting}
tooltip="Delete upload"
/>
</>}
</div>
</div>
<div className={clsx(
'flex gap-2 sm:gap-3',
'ml-0.5',
@ -92,7 +151,10 @@ export default function AdminUploadsTableRow({
: 'Waiting'
: <>
{uploadedAt
? <ResponsiveDate date={uploadedAt} length="medium" />
? <ResponsiveDate
date={uploadedAt}
titleLabel="UPLOADED AT"
/>
: '—'}
<div className="max-sm:hidden text-dim truncate">
{size
@ -101,55 +163,7 @@ export default function AdminUploadsTableRow({
</div>
</>}
</div>
<FieldSetWithStatus
label="Title"
className="[&_input]:min-h-9 [&_input]:px-2 [&_input]:py-0"
value={draftTitle}
onChange={titleUpdated => {
setUrlAddStatuses?.(urlAddStatuses.map(status => ({
...status,
draftTitle: status.url === url
? titleUpdated
: status.draftTitle,
})));
}}
placeholder="Title (optional)"
tabIndex={urlAddStatuses
.findIndex(status => status.url === url) + 1}
hideLabel
/>
</div>
<span className="flex items-center gap-2">
{isAdding || isComplete
? <>
{status === 'added'
? <FaRegCircleCheck size={18} />
: status === 'adding' &&
<Spinner
size={19}
className="translate-y-[2px]"
/>}
</>
: <>
<AddButton
path={pathForAdminUploadUrl(url)}
disabled={isDeleting}
hideTextOnMobile={false}
/>
<DeleteBlobButton
urls={[url]}
shouldRedirectToAdminPhotos={urlAddStatuses.length <= 1}
onDeleteStart={() => setIsDeleting?.(true)}
onDelete={() => {
setIsDeleting?.(false);
setUrlAddStatuses?.(urlAddStatuses
.filter(({ url: urlToRemove }) =>
urlToRemove !== url));
}}
isLoading={isDeleting}
/>
</>}
</span>
</div>
</div>
);

View File

@ -12,7 +12,6 @@ export default function DeleteUploadButton({
shouldRedirectToAdminPhotos,
onDeleteStart,
onDelete,
hideTextOnMobile,
children,
isLoading,
...props
@ -50,7 +49,6 @@ export default function DeleteUploadButton({
});
}}
isLoading={isLoading ?? isDeleting}
hideTextOnMobile={hideTextOnMobile}
>
{children}
</DeleteButton>

View File

@ -1,17 +1,19 @@
import IconEdit from '@/components/icons/IconEdit';
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
import { ComponentProps } from 'react';
export default function EditButton ({
path,
children,
...props
}: {
path: string,
}) {
hideText?: boolean
} & ComponentProps<typeof PathLoaderButton>) {
return (
<PathLoaderButton
path={path}
{...props}
icon={<IconEdit size={15} className="translate-y-[0.5px]" />}
>
Edit
{children || 'Edit'}
</PathLoaderButton>
);
}

View File

@ -69,7 +69,7 @@ export default function LoaderButton({
),
styleAs === 'link' && 'hover:text-dim',
styleAs === 'link-without-hover' && 'hover:text-main',
'inline-flex items-center gap-2 self-start whitespace-nowrap',
'inline-flex items-center gap-1.5 self-start whitespace-nowrap',
primary && 'primary',
hideFocusOutline && 'focus:outline-hidden',
className,
@ -88,7 +88,7 @@ export default function LoaderButton({
size={14}
color={spinnerColor}
className={clsx(
'translate-y-[1px]',
'translate-y-[0.5px]',
spinnerClassName,
)}
/>
@ -96,7 +96,7 @@ export default function LoaderButton({
</span>}
{children && <span className={clsx(
styleAs !== 'button' && isLoading && 'text-dim',
hideTextOnMobile && icon !== undefined && 'hidden sm:inline-block',
hideTextOnMobile && icon !== undefined && 'max-sm:hidden',
)}>
{children}
</span>}

View File

@ -11,6 +11,7 @@ export default function PathLoaderButton({
shouldScroll = true,
shouldReplace,
isLoading,
onClick,
children,
...props
}: {
@ -46,7 +47,8 @@ export default function PathLoaderButton({
return (
<LoaderButton
{...props}
onClick={() => {
onClick={e => {
onClick?.(e);
startTransition(() => {
if (shouldReplace) {
router.replace(path, { scroll: shouldScroll });