Show loading state on individual selected tiles

This commit is contained in:
Sam Becker 2024-07-21 10:15:06 -05:00
parent 50024bd115
commit ac10e97533
4 changed files with 50 additions and 29 deletions

View File

@ -14,6 +14,7 @@ import PhotoTagFieldset from './PhotoTagFieldset';
import { tagMultiplePhotosAction } from '@/photo/actions';
import { toastSuccess } from '@/toast';
import DeletePhotosButton from './DeletePhotosButton';
import { photoQuantityText } from '@/photo';
export default function AdminBatchEditPanelClient({
uniqueTags,
@ -26,13 +27,13 @@ export default function AdminBatchEditPanelClient({
isUserSignedIn,
selectedPhotoIds,
setSelectedPhotoIds,
isPerformingSelectEdit,
setIsPerformingSelectEdit,
} = useAppState();
const [isLoading, setIsLoading] = useState(false);
const [tags, setTags] = useState<string>();
const [tagErrorMessage, setTagErrorMessage] = useState('');
const isTagging = tags !== undefined;
const isInTagMode = tags !== undefined;
const resetForm = () => {
setSelectedPhotoIds?.(undefined);
@ -40,13 +41,13 @@ export default function AdminBatchEditPanelClient({
setTagErrorMessage('');
};
const photosPlural = selectedPhotoIds?.length === 1 ? 'photo' : 'photos';
const photosText = photoQuantityText(selectedPhotoIds?.length ?? 0, false);
const renderPhotoText = () => selectedPhotoIds?.length === 0
? 'Select photos below'
: `${selectedPhotoIds?.length ?? 0} ${photosPlural} selected`;
: `${photosText} selected`;
const renderActions = () => isTagging
const renderActions = () => isInTagMode
? <>
<LoaderButton
className="min-h-[2.5rem]"
@ -54,33 +55,33 @@ export default function AdminBatchEditPanelClient({
setTags(undefined);
setTagErrorMessage('');
}}
disabled={isLoading}
disabled={isPerformingSelectEdit}
>
Cancel
</LoaderButton>
<LoaderButton
className="min-h-[2.5rem]"
// eslint-disable-next-line max-len
confirmText={`Are you sure you want to apply tags to ${selectedPhotoIds?.length} ${photosPlural}? This action cannot be undone.`}
confirmText={`Are you sure you want to apply tags to ${photosText}? This action cannot be undone.`}
onClick={() => {
setIsLoading(true);
setIsPerformingSelectEdit?.(true);
tagMultiplePhotosAction(
tags,
selectedPhotoIds ?? [],
)
.then(() => {
toastSuccess(
`Tags applied to ${selectedPhotoIds?.length} ${photosPlural}`
`Tags applied to ${photosText}`
);
resetForm();
})
.finally(() => setIsLoading(false));
.finally(() => setIsPerformingSelectEdit?.(false));
}}
disabled={
!tags ||
Boolean(tagErrorMessage) ||
(selectedPhotoIds?.length ?? 0) === 0 ||
isLoading
isPerformingSelectEdit
}
primary
>
@ -92,18 +93,18 @@ export default function AdminBatchEditPanelClient({
<>
<LoaderButton
onClick={() => setTags('')}
isLoading={isLoading}
disabled={isPerformingSelectEdit}
>
Tag ...
</LoaderButton>
<DeletePhotosButton
photoIds={selectedPhotoIds}
disabled={isLoading}
disabled={isPerformingSelectEdit}
onDelete={resetForm}
/>
</>}
<LoaderButton
icon={<IoCloseSharp size={20} className="translate-y-[-1.5px]" />}
icon={<IoCloseSharp size={20} className="translate-y-[0.5px]" />}
onClick={() => setSelectedPhotoIds?.(undefined)}
/>
</>;
@ -124,22 +125,21 @@ export default function AdminBatchEditPanelClient({
'!text-gray-900 dark:!text-gray-100',
'!bg-gray-100/90 dark:!bg-gray-900/70',
)}
padding={isTagging ? 'tight-cta-right-left' : 'tight-cta-right'}
padding={isInTagMode ? 'tight-cta-right-left' : 'tight-cta-right'}
cta={<div className="flex items-center gap-2.5">
{renderActions()}
</div>}
spaceChildren={false}
hideIcon
>
{isTagging
{isInTagMode
? <PhotoTagFieldset
tags={tags}
tagOptions={uniqueTags}
placeholder={
`Tag ${selectedPhotoIds?.length} ${photosPlural} ...`
}
placeholder={`Tag ${photosText} ...`}
onChange={setTags}
onError={setTagErrorMessage}
readOnly={isPerformingSelectEdit}
openOnLoad
hideLabel
/>

View File

@ -1,5 +1,9 @@
'use client';
import { clsx } from 'clsx/lite';
import Checkbox from './primitives/Checkbox';
import { useAppState } from '@/state/AppState';
import Spinner from './Spinner';
export default function SelectTileOverlay({
isSelected,
@ -8,10 +12,13 @@ export default function SelectTileOverlay({
isSelected: boolean
onSelectChange: () => void
}) {
const { isPerformingSelectEdit } = useAppState();
return (
<div className={clsx(
'absolute w-full h-full cursor-pointer',
'active:bg-gray-950/40 active:dark:bg-gray-950/60',
isPerformingSelectEdit && 'pointer-events-none',
)}>
{/* Admin Select Border */}
<div
@ -30,15 +37,23 @@ export default function SelectTileOverlay({
</div>
{/* Admin Select Action */}
<div className="absolute top-0 right-0 p-2">
<Checkbox
className={clsx(
'text-white',
// Required to prevent Safari jitter
'translate-x-[0.1px]',
)}
checked={isSelected}
onChange={onSelectChange}
/>
{isPerformingSelectEdit
? isSelected
? <Spinner
size={16}
color="text"
className="m-[1px]"
/>
: null
: <Checkbox
className={clsx(
'text-white',
// Required to prevent Safari jitter
'translate-x-[0.1px]',
)}
checked={isSelected}
onChange={onSelectChange}
/>}
</div>
</div>
);

View File

@ -24,6 +24,8 @@ export interface AppStateContext {
hiddenPhotosCount?: number
selectedPhotoIds?: string[]
setSelectedPhotoIds?: Dispatch<SetStateAction<string[] | undefined>>
isPerformingSelectEdit?: boolean
setIsPerformingSelectEdit?: Dispatch<SetStateAction<boolean>>
// DEBUG
arePhotosMatted?: boolean
setArePhotosMatted?: Dispatch<SetStateAction<boolean>>

View File

@ -36,6 +36,8 @@ export default function AppStateProvider({
useState(0);
const [selectedPhotoIds, setSelectedPhotoIds] =
useState<string[] | undefined>();
const [isPerformingSelectEdit, setIsPerformingSelectEdit] =
useState(false);
// DEBUG
const [arePhotosMatted, setArePhotosMatted] =
useState(MATTE_PHOTOS);
@ -96,6 +98,8 @@ export default function AppStateProvider({
hiddenPhotosCount,
selectedPhotoIds,
setSelectedPhotoIds,
isPerformingSelectEdit,
setIsPerformingSelectEdit,
// DEBUG
arePhotosMatted,
setArePhotosMatted,