From 6eecb553f48074ae8d7bfbace7c85007f5d9fc2b Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 14 Jul 2024 18:31:35 -0500 Subject: [PATCH] Refine photo select/deselect, admin app menu --- src/admin/AdminAppMenu.tsx | 52 ++++++++++++++++++++++++++++ src/admin/AdminBatchEditPanel.tsx | 31 +++++++++++------ src/components/SelectTileOverlay.tsx | 18 +++++----- src/components/SiteGrid.tsx | 2 +- src/components/more/MoreMenu.tsx | 26 ++++---------- src/components/more/MoreMenuItem.tsx | 10 ++++-- src/photo/PhotoGrid.tsx | 14 ++++---- src/photo/PhotoGridPage.tsx | 5 ++- src/site/Nav.tsx | 12 +++++++ src/state/AppState.ts | 2 +- src/state/AppStateProvider.tsx | 2 +- 11 files changed, 120 insertions(+), 54 deletions(-) create mode 100644 src/admin/AdminAppMenu.tsx diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx new file mode 100644 index 00000000..c037ce3f --- /dev/null +++ b/src/admin/AdminAppMenu.tsx @@ -0,0 +1,52 @@ +'use client'; + +import MoreMenu from '@/components/more/MoreMenu'; +import { GRID_HOMEPAGE_ENABLED } from '@/site/config'; +import { PATH_ADMIN_CONFIGURATION, PATH_GRID, PATH_ROOT } from '@/site/paths'; +import { useAppState } from '@/state/AppState'; +import { BiCog } from 'react-icons/bi'; +import { FaTimes } from 'react-icons/fa'; +import { ImCheckboxUnchecked } from 'react-icons/im'; + +export default function AdminAppMenu() { + const { + selectedPhotoIds, + setSelectedPhotoIds, + } = useAppState(); + + const isSelecting = selectedPhotoIds !== undefined; + + return ( + , + href: PATH_ADMIN_CONFIGURATION, + }, { + label: isSelecting + ? 'Exit Select' + : 'Select Multiple', + icon: isSelecting + ? + : , + href: GRID_HOMEPAGE_ENABLED ? PATH_ROOT : PATH_GRID, + action: () => { + if (isSelecting) { + setSelectedPhotoIds?.(undefined); + } else { + setSelectedPhotoIds?.([]); + } + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }, + shouldPreventDefault: false, + }]} + ariaLabel="Admin Menu" + /> + ); +} diff --git a/src/admin/AdminBatchEditPanel.tsx b/src/admin/AdminBatchEditPanel.tsx index 71848781..23001d56 100644 --- a/src/admin/AdminBatchEditPanel.tsx +++ b/src/admin/AdminBatchEditPanel.tsx @@ -5,32 +5,41 @@ import LoaderButton from '@/components/primitives/LoaderButton'; import SiteGrid from '@/components/SiteGrid'; import { useAppState } from '@/state/AppState'; import { clsx } from 'clsx/lite'; +import { IoCloseSharp } from 'react-icons/io5'; +import DeleteButton from './DeleteButton'; export default function AdminBatchEditPanel() { const { isUserSignedIn, - selectedPhotoIds = [], + selectedPhotoIds, setSelectedPhotoIds, } = useAppState(); - return isUserSignedIn && selectedPhotoIds.length > 0 + return isUserSignedIn && selectedPhotoIds !== undefined ? setSelectedPhotoIds?.([])} - primary - > - Clear - } + cta={
+ + Tag ... + + + } + onClick={() => setSelectedPhotoIds?.(undefined)} + /> +
} > - {selectedPhotoIds.length} photos selected + {selectedPhotoIds.length} + {selectedPhotoIds.length === 1 ? ' photo' : ' photos'} + {' '} + selected } /> : null; } diff --git a/src/components/SelectTileOverlay.tsx b/src/components/SelectTileOverlay.tsx index d9b54adf..9c8e8102 100644 --- a/src/components/SelectTileOverlay.tsx +++ b/src/components/SelectTileOverlay.tsx @@ -9,18 +9,19 @@ export default function SelectTileOverlay({ onSelectChange: () => void }) { return ( - <> +
{/* Admin Select Border */} -
+
- +
); } diff --git a/src/components/SiteGrid.tsx b/src/components/SiteGrid.tsx index 2bcd2cd4..931fec17 100644 --- a/src/components/SiteGrid.tsx +++ b/src/components/SiteGrid.tsx @@ -20,12 +20,12 @@ export default function SiteGrid({
Promise | void -} - export default function MoreMenu({ items, className, buttonClassName, ariaLabel, }: { - items: MoreMenuItem[] + items: ComponentProps [] className?: string buttonClassName?: string ariaLabel: string @@ -44,23 +36,17 @@ export default function MoreMenu({ - {items.map(({ label, icon, href, hrefDownloadName, action }) => - + {items.map(props => + )} diff --git a/src/components/more/MoreMenuItem.tsx b/src/components/more/MoreMenuItem.tsx index 213f1601..ab864291 100644 --- a/src/components/more/MoreMenuItem.tsx +++ b/src/components/more/MoreMenuItem.tsx @@ -4,7 +4,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import { clsx } from 'clsx/lite'; import { ReactNode, useState, useTransition } from 'react'; import LoaderButton from '../primitives/LoaderButton'; -import { useRouter } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; export default function MoreMenuItem({ label, @@ -12,15 +12,19 @@ export default function MoreMenuItem({ href, hrefDownloadName, action, + shouldPreventDefault = true, }: { label: ReactNode icon?: ReactNode href?: string hrefDownloadName?: string action?: () => Promise | void + shouldPreventDefault?: boolean }) { const router = useRouter(); + const pathname = usePathname(); + const [isPending, startTransition] = useTransition(); const [isLoading, setIsLoading] = useState(false); @@ -39,8 +43,8 @@ export default function MoreMenuItem({ : 'cursor-pointer', )} onClick={e => { - e.preventDefault(); - if (href) { + if (shouldPreventDefault) { e.preventDefault(); } + if (href && href !== pathname) { if (Boolean(hrefDownloadName)) { window.open(href, '_blank'); } else { diff --git a/src/photo/PhotoGrid.tsx b/src/photo/PhotoGrid.tsx index b9a06a62..d0d998c0 100644 --- a/src/photo/PhotoGrid.tsx +++ b/src/photo/PhotoGrid.tsx @@ -49,7 +49,7 @@ export default function PhotoGrid({ }) { const { isUserSignedIn, - selectedPhotoIds = [], + selectedPhotoIds, setSelectedPhotoIds, } = useAppState(); @@ -73,7 +73,7 @@ export default function PhotoGrid({ staggerOnFirstLoadOnly={staggerOnFirstLoadOnly} onAnimationComplete={onAnimationComplete} items={photos.map((photo, index) =>{ - const isSelected = selectedPhotoIds.includes(photo.id); + const isSelected = selectedPhotoIds?.includes(photo.id) ?? false; return
0 && 'pointer-events-none', + // Prevent photo navigation when selecting + selectedPhotoIds?.length !== undefined && 'pointer-events-none', )} {...{ photo, @@ -105,12 +105,12 @@ export default function PhotoGrid({ : undefined, }} /> - {canSelect && isUserSignedIn && + {isUserSignedIn && canSelect && selectedPhotoIds !== undefined && setSelectedPhotoIds?.(isSelected - ? selectedPhotoIds.filter(id => id !== photo.id) - : selectedPhotoIds.concat(photo.id), + ? (selectedPhotoIds ?? []).filter(id => id !== photo.id) + : (selectedPhotoIds ?? []).concat(photo.id), )} />}
; diff --git a/src/photo/PhotoGridPage.tsx b/src/photo/PhotoGridPage.tsx index c6dbc609..79447493 100644 --- a/src/photo/PhotoGridPage.tsx +++ b/src/photo/PhotoGridPage.tsx @@ -25,7 +25,10 @@ export default function PhotoGridPage({ }) { const { setSelectedPhotoIds } = useAppState(); - useEffect(() => () => setSelectedPhotoIds?.([]), [setSelectedPhotoIds]); + useEffect( + () => () => setSelectedPhotoIds?.(undefined), + [setSelectedPhotoIds] + ); return ( } + contentSide={isUserSignedIn && !isPathAdmin(pathname) + ?
+ +
+ : undefined} + sideHiddenOnMobile /> ); }; diff --git a/src/state/AppState.ts b/src/state/AppState.ts index f92e0001..d5d4cc04 100644 --- a/src/state/AppState.ts +++ b/src/state/AppState.ts @@ -23,7 +23,7 @@ export interface AppStateContext { registerAdminUpdate?: () => void hiddenPhotosCount?: number selectedPhotoIds?: string[] - setSelectedPhotoIds?: Dispatch> + setSelectedPhotoIds?: Dispatch> // DEBUG arePhotosMatted?: boolean setArePhotosMatted?: Dispatch> diff --git a/src/state/AppStateProvider.tsx b/src/state/AppStateProvider.tsx index f999b8a8..58ad9c4f 100644 --- a/src/state/AppStateProvider.tsx +++ b/src/state/AppStateProvider.tsx @@ -35,7 +35,7 @@ export default function AppStateProvider({ const [hiddenPhotosCount, setHiddenPhotosCount] = useState(0); const [selectedPhotoIds, setSelectedPhotoIds] = - useState([]); + useState(); // DEBUG const [arePhotosMatted, setArePhotosMatted] = useState(MATTE_PHOTOS);