Make batch edits more discoverable

This commit is contained in:
Sam Becker 2025-03-17 20:43:57 -05:00
parent ed0fe9cae9
commit 34a8f2f362
6 changed files with 51 additions and 72 deletions

View File

@ -11,13 +11,12 @@ import {
PATH_GRID_INFERRED,
} from '@/app/paths';
import { useAppState } from '@/state/AppState';
import { IoArrowDown, IoArrowUp } from 'react-icons/io5';
import { IoArrowDown, IoArrowUp, IoCloseSharp } from 'react-icons/io5';
import { clsx } from 'clsx/lite';
import AdminAppInfoIcon from './AdminAppInfoIcon';
import { signOutAction } from '@/auth/actions';
import { ComponentProps } from 'react';
import useIsKeyBeingPressed from '@/utility/useIsKeyBeingPressed';
import IconSelectMultiple from '@/components/icons/IconSelectMultiple';
import IconPhoto from '@/components/icons/IconPhoto';
import IconUpload from '@/components/icons/IconUpload';
import IconRecipe from '@/components/icons/IconRecipe';
@ -25,6 +24,7 @@ import IconTag from '@/components/icons/IconTag';
import IconFolder from '@/components/icons/IconFolder';
import IconSignOut from '@/components/icons/IconSignOut';
import IconLock from '@/components/icons/IconLock';
import { IoMdCheckboxOutline } from 'react-icons/io';
export default function AdminAppMenu({
active,
@ -121,9 +121,14 @@ export default function AdminAppMenu({
if (photosCountTotal) {
items.push({
label: isSelecting
? 'Exit Select'
: 'Edit Multiple',
icon: <IconSelectMultiple {...{ isSelecting }} />,
? 'Exit Batch Edit'
: 'Batch Edit ...',
icon: isSelecting
? <IoCloseSharp
size={18}
className="translate-x-[-1px] translate-y-[0.5px]"
/>
: <IoMdCheckboxOutline size={17} className="translate-x-[-0.5px]" />,
href: PATH_GRID_INFERRED,
action: () => {
if (isSelecting) {

View File

@ -51,6 +51,10 @@ export default function AdminBatchEditPanelClient({
false,
);
const isFormDisabled =
isPerformingSelectEdit ||
selectedPhotoIds?.length === 0;
const renderPhotoCTA = () => selectedPhotoIds?.length === 0
? <>
<FaArrowDown />
@ -105,40 +109,37 @@ export default function AdminBatchEditPanelClient({
</LoaderButton>
</>
: <>
{(selectedPhotoIds?.length ?? 0) > 0 &&
<>
<DeletePhotosButton
photoIds={selectedPhotoIds}
disabled={isPerformingSelectEdit}
onClick={() => setIsPerformingSelectEdit?.(true)}
onDelete={resetForm}
onFinish={() => setIsPerformingSelectEdit?.(false)}
/>
<LoaderButton
icon={<IconFavs />}
disabled={isPerformingSelectEdit}
confirmText={`Are you sure you want to favorite ${photosText}?`}
onClick={() => {
setIsPerformingSelectEdit?.(true);
tagMultiplePhotosAction(
TAG_FAVS,
selectedPhotoIds ?? [],
)
.then(() => {
toastSuccess(`${photosText} favorited`);
resetForm();
})
.finally(() => setIsPerformingSelectEdit?.(false));
}}
/>
<LoaderButton
onClick={() => setTags('')}
disabled={isPerformingSelectEdit}
icon={<IconTag size={15} className="translate-y-[1.5px]" />}
>
Tag ...
</LoaderButton>
</>}
<DeletePhotosButton
photoIds={selectedPhotoIds}
disabled={isFormDisabled}
onClick={() => setIsPerformingSelectEdit?.(true)}
onDelete={resetForm}
onFinish={() => setIsPerformingSelectEdit?.(false)}
/>
<LoaderButton
icon={<IconFavs />}
disabled={isFormDisabled}
confirmText={`Are you sure you want to favorite ${photosText}?`}
onClick={() => {
setIsPerformingSelectEdit?.(true);
tagMultiplePhotosAction(
TAG_FAVS,
selectedPhotoIds ?? [],
)
.then(() => {
toastSuccess(`${photosText} favorited`);
resetForm();
})
.finally(() => setIsPerformingSelectEdit?.(false));
}}
/>
<LoaderButton
onClick={() => setTags('')}
disabled={isFormDisabled}
icon={<IconTag size={15} className="translate-y-[1.5px]" />}
>
Tag ...
</LoaderButton>
<LoaderButton
icon={<IoCloseSharp size={19} />}
onClick={() => setSelectedPhotoIds?.(undefined)}

View File

@ -18,8 +18,9 @@ export default function DeleteButton({
'active:bg-red-100/50! dark:active:bg-red-950/50!',
'disabled:text-red-500/60! dark:disabled:text-red-500/60!',
'disabled:bg-red-100/50! dark:disabled:bg-red-950/50!',
'border-red-200! hover:border-red-300!',
'dark:border-red-900/75! dark:hover:border-red-900!',
'border-red-200! disabled:border-red-200! hover:border-red-300!',
// eslint-disable-next-line max-len
'dark:border-red-900/75! dark:disabled:border-red-900/75! dark:hover:border-red-900!',
className,
)}
/>

View File

@ -1,7 +1,7 @@
'use client';
import { clsx } from 'clsx/lite';
import Checkbox from './primitives/Checkbox';
import SimpleCheckbox from './primitives/SimpleCheckbox';
import { useAppState } from '@/state/AppState';
import Spinner from './Spinner';
@ -45,7 +45,7 @@ export default function SelectTileOverlay({
className="m-[1px]"
/>
: null
: <Checkbox
: <SimpleCheckbox
className={clsx(
'text-white',
// Required to prevent Safari jitter

View File

@ -1,28 +0,0 @@
import clsx from 'clsx/lite';
import { IconBaseProps } from 'react-icons';
import { ImCheckboxUnchecked } from 'react-icons/im';
import { IoCloseSharp } from 'react-icons/io5';
export default function IconSelectMultiple({
isSelecting,
className,
...props
}: IconBaseProps & { isSelecting: boolean }) {
return isSelecting
? <IoCloseSharp {...{
...props,
className: clsx(
'text-[18px] translate-x-[-1px] translate-y-[0.5px]',
className,
),
}} />
: <ImCheckboxUnchecked
{...{
...props,
className: clsx(
'translate-x-[-0.5px] translate-y-[0.5px] text-[0.75rem]',
className,
),
}}
/>;
}

View File

@ -4,7 +4,7 @@ import { ImCheckboxUnchecked, ImCheckboxChecked } from 'react-icons/im';
const ICON_CLASS_NAME = 'text-[1rem]';
export default function Checkbox(props: {
export default function SimpleCheckbox(props: {
children?: ReactNode
} & InputHTMLAttributes<HTMLInputElement>) {
const {