Add sections to admin menus

This commit is contained in:
Sam Becker 2025-04-21 17:11:06 -05:00
parent db14acabdf
commit d32fa39aab
4 changed files with 58 additions and 32 deletions

View File

@ -29,6 +29,7 @@ import { IoMdCheckboxOutline } from 'react-icons/io';
import Spinner from '@/components/Spinner'; import Spinner from '@/components/Spinner';
import IconBroom from '@/components/icons/IconBroom'; import IconBroom from '@/components/icons/IconBroom';
import InsightsIndicatorDot from './insights/InsightsIndicatorDot'; import InsightsIndicatorDot from './insights/InsightsIndicatorDot';
import MoreMenuItem from '@/components/more/MoreMenuItem';
export default function AdminAppMenu({ export default function AdminAppMenu({
active, active,
@ -60,17 +61,21 @@ export default function AdminAppMenu({
const showAppInsightsLink = photosCountTotal > 0 && !isAltPressed; const showAppInsightsLink = photosCountTotal > 0 && !isAltPressed;
const items: ComponentProps<typeof MoreMenu>['items'] = [{ const sectionUpload: ComponentProps<typeof MoreMenuItem>[] = [];
const sectionMain: ComponentProps<typeof MoreMenuItem>[] = [];
const sectionSignOut: ComponentProps<typeof MoreMenuItem>[] = [];
sectionUpload.push({
label: 'Upload Photos', label: 'Upload Photos',
icon: <IconUpload icon: <IconUpload
size={15} size={15}
className="translate-x-[0.5px] translate-y-[0.5px]" className="translate-x-[0.5px] translate-y-[0.5px]"
/>, />,
action: startUpload, action: startUpload,
}]; });
if (uploadsCount) { if (uploadsCount) {
items.push({ sectionMain.push({
label: 'Uploads', label: 'Uploads',
annotation: `${uploadsCount}`, annotation: `${uploadsCount}`,
icon: <IconFolder icon: <IconFolder
@ -81,7 +86,7 @@ export default function AdminAppMenu({
}); });
} }
if (photosCountNeedSync) { if (photosCountNeedSync) {
items.push({ sectionMain.push({
label: 'Updates', label: 'Updates',
annotation: <> annotation: <>
<span className="mr-3"> <span className="mr-3">
@ -100,7 +105,7 @@ export default function AdminAppMenu({
}); });
} }
if (photosCountTotal) { if (photosCountTotal) {
items.push({ sectionMain.push({
label: 'Manage Photos', label: 'Manage Photos',
...photosCountTotal && { ...photosCountTotal && {
annotation: `${photosCountTotal}`, annotation: `${photosCountTotal}`,
@ -113,7 +118,7 @@ export default function AdminAppMenu({
}); });
} }
if (tagsCount) { if (tagsCount) {
items.push({ sectionMain.push({
label: 'Manage Tags', label: 'Manage Tags',
annotation: `${tagsCount}`, annotation: `${tagsCount}`,
icon: <IconTag icon: <IconTag
@ -124,7 +129,7 @@ export default function AdminAppMenu({
}); });
} }
if (recipesCount) { if (recipesCount) {
items.push({ sectionMain.push({
label: 'Manage Recipes', label: 'Manage Recipes',
annotation: `${recipesCount}`, annotation: `${recipesCount}`,
icon: <IconRecipe icon: <IconRecipe
@ -135,7 +140,7 @@ export default function AdminAppMenu({
}); });
} }
if (photosCountTotal) { if (photosCountTotal) {
items.push({ sectionMain.push({
label: isSelecting label: isSelecting
? 'Exit Batch Edit' ? 'Exit Batch Edit'
: 'Batch Edit ...', : 'Batch Edit ...',
@ -163,7 +168,7 @@ export default function AdminAppMenu({
}); });
} }
items.push({ sectionMain.push({
label: showAppInsightsLink label: showAppInsightsLink
? 'App Insights' ? 'App Insights'
: 'App Configuration', : 'App Configuration',
@ -174,7 +179,9 @@ export default function AdminAppMenu({
href: showAppInsightsLink href: showAppInsightsLink
? PATH_ADMIN_INSIGHTS ? PATH_ADMIN_INSIGHTS
: PATH_ADMIN_CONFIGURATION, : PATH_ADMIN_CONFIGURATION,
}, { });
sectionSignOut.push({
label: 'Sign Out', label: 'Sign Out',
icon: <IconSignOut size={15} />, icon: <IconSignOut size={15} />,
action: () => signOutAction().then(clearAuthStateAndRedirectIfNecessary), action: () => signOutAction().then(clearAuthStateAndRedirectIfNecessary),
@ -235,7 +242,11 @@ export default function AdminAppMenu({
'[&>*>*]:translate-y-[6px]', '[&>*>*]:translate-y-[6px]',
!animateMenuClose && '[&>*>*]:duration-300', !animateMenuClose && '[&>*>*]:duration-300',
)} )}
items={items} sections={[
sectionUpload,
sectionMain,
sectionSignOut,
]}
ariaLabel="Admin Menu" ariaLabel="Admin Menu"
/> />
); );

View File

@ -31,7 +31,7 @@ export default function AdminPhotoMenu({
revalidatePhoto, revalidatePhoto,
includeFavorite = true, includeFavorite = true,
...props ...props
}: Omit<ComponentProps<typeof MoreMenu>, 'items'> & { }: Omit<ComponentProps<typeof MoreMenu>, 'sections'> & {
photo: Photo photo: Photo
revalidatePhoto?: RevalidatePhoto revalidatePhoto?: RevalidatePhoto
includeFavorite?: boolean includeFavorite?: boolean
@ -43,8 +43,8 @@ export default function AdminPhotoMenu({
const shouldRedirectFav = isPathFavs(path) && isFav; const shouldRedirectFav = isPathFavs(path) && isFav;
const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path; const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path;
const items = useMemo(() => { const sections = useMemo(() => {
const items: ComponentProps<typeof MoreMenuItem>[] = [{ const sectionMain: ComponentProps<typeof MoreMenuItem>[] = [{
label: 'Edit', label: 'Edit',
icon: <IconEdit icon: <IconEdit
size={15} size={15}
@ -53,7 +53,7 @@ export default function AdminPhotoMenu({
href: pathForAdminPhotoEdit(photo.id), href: pathForAdminPhotoEdit(photo.id),
}]; }];
if (includeFavorite) { if (includeFavorite) {
items.push({ sectionMain.push({
label: isFav ? 'Unfavorite' : 'Favorite', label: isFav ? 'Unfavorite' : 'Favorite',
icon: <IconFavs icon: <IconFavs
size={14} size={14}
@ -66,7 +66,7 @@ export default function AdminPhotoMenu({
).then(() => revalidatePhoto?.(photo.id)), ).then(() => revalidatePhoto?.(photo.id)),
}); });
} }
items.push({ sectionMain.push({
label: 'Download', label: 'Download',
icon: <MdOutlineFileDownload icon: <MdOutlineFileDownload
size={17} size={17}
@ -75,7 +75,7 @@ export default function AdminPhotoMenu({
href: photo.url, href: photo.url,
hrefDownloadName: downloadFileNameForPhoto(photo), hrefDownloadName: downloadFileNameForPhoto(photo),
}); });
items.push({ sectionMain.push({
label: 'Sync', label: 'Sync',
labelComplex: <span className="inline-flex items-center gap-2"> labelComplex: <span className="inline-flex items-center gap-2">
<span>Sync</span> <span>Sync</span>
@ -90,7 +90,7 @@ export default function AdminPhotoMenu({
action: () => syncPhotoAction(photo.id) action: () => syncPhotoAction(photo.id)
.then(() => revalidatePhoto?.(photo.id)), .then(() => revalidatePhoto?.(photo.id)),
}); });
items.push({ const sectionDelete = [{
label: 'Delete', label: 'Delete',
icon: <BiTrash icon: <BiTrash
size={15} size={15}
@ -109,8 +109,8 @@ export default function AdminPhotoMenu({
}); });
} }
}, },
}); }];
return items; return [sectionMain, sectionDelete];
}, [ }, [
photo, photo,
includeFavorite, includeFavorite,
@ -124,7 +124,7 @@ export default function AdminPhotoMenu({
return ( return (
isUserSignedIn isUserSignedIn
? <MoreMenu {...{ ? <MoreMenu {...{
items, sections,
...props, ...props,
}}/> }}/>
: null : null

View File

@ -11,7 +11,7 @@ import { FiMoreHorizontal } from 'react-icons/fi';
import MoreMenuItem from './MoreMenuItem'; import MoreMenuItem from './MoreMenuItem';
export default function MoreMenu({ export default function MoreMenu({
items, sections,
icon, icon,
header, header,
className, className,
@ -24,7 +24,7 @@ export default function MoreMenu({
onOpen, onOpen,
...props ...props
}: { }: {
items: ComponentProps<typeof MoreMenuItem>[] sections: ComponentProps<typeof MoreMenuItem>[][]
icon?: ReactNode icon?: ReactNode
header?: ReactNode header?: ReactNode
className?: string className?: string
@ -71,7 +71,7 @@ export default function MoreMenu({
'z-10', 'z-10',
'min-w-[8rem]', 'min-w-[8rem]',
'component-surface', 'component-surface',
'p-1', 'py-1',
'shadow-lg', 'shadow-lg',
'data-[side=top]:dark:shadow-[0_0px_40px_rgba(0,0,0,0.6)]', 'data-[side=top]:dark:shadow-[0_0px_40px_rgba(0,0,0,0.6)]',
'data-[side=bottom]:dark:shadow-[0_10px_40px_rgba(0,0,0,0.6)]', 'data-[side=bottom]:dark:shadow-[0_10px_40px_rgba(0,0,0,0.6)]',
@ -86,13 +86,26 @@ export default function MoreMenu({
)}> )}>
{header} {header}
</div>} </div>}
{items.map(props => <div className="divide-y divide-medium">
<MoreMenuItem {sections.map((section, index) =>
key={`${props.label}`} <div
{...props} key={index}
dismissMenu={dismissMenu} className={clsx(
/>, '[&:not(:first-child)]:pt-1',
)} '[&:not(:last-child)]:pb-1',
)}
>
{section.map(props =>
<div key={props.label} className="px-1">
<MoreMenuItem
dismissMenu={dismissMenu}
{...props}
/>
</div>,
)}
</div>,
)}
</div>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Portal> </DropdownMenu.Portal>
</DropdownMenu.Root> </DropdownMenu.Root>

View File

@ -110,7 +110,9 @@ export default function MoreMenuItem({
styleAs="link-without-hover" styleAs="link-without-hover"
className="translate-y-[1px]" className="translate-y-[1px]"
> >
{labelComplex ?? label} <span>
{labelComplex ?? label}
</span>
{annotation && {annotation &&
<span className="text-dim ml-3"> <span className="text-dim ml-3">
{annotation} {annotation}