Switch more menus to proper links
This commit is contained in:
parent
c78c6fd7e0
commit
fc8cca3f7f
@ -81,7 +81,7 @@ export default function AdminAppMenu({
|
||||
annotation: `${uploadsCount}`,
|
||||
icon: <IconFolder
|
||||
size={16}
|
||||
className="translate-x-[1px] translate-y-[1px]"
|
||||
className="translate-x-[1px] translate-y-[0.5px]"
|
||||
/>,
|
||||
href: PATH_ADMIN_UPLOADS,
|
||||
});
|
||||
@ -114,7 +114,7 @@ export default function AdminAppMenu({
|
||||
},
|
||||
icon: <IconPhoto
|
||||
size={15}
|
||||
className="translate-x-[-0.5px] translate-y-[1px]"
|
||||
className="translate-x-[-0.5px] translate-y-[0.5px]"
|
||||
/>,
|
||||
href: PATH_ADMIN_PHOTOS,
|
||||
});
|
||||
@ -136,7 +136,7 @@ export default function AdminAppMenu({
|
||||
annotation: `${recipesCount}`,
|
||||
icon: <IconRecipe
|
||||
size={17}
|
||||
className="translate-x-[-0.5px] translate-y-[1px]"
|
||||
className="translate-x-[-0.5px]"
|
||||
/>,
|
||||
href: PATH_ADMIN_RECIPES,
|
||||
});
|
||||
@ -175,7 +175,7 @@ export default function AdminAppMenu({
|
||||
: appText.admin.appConfig,
|
||||
icon: <AdminAppInfoIcon
|
||||
size="small"
|
||||
className="translate-x-[-0.5px] translate-y-[0.5px]"
|
||||
className="translate-x-[-0.5px]"
|
||||
/>,
|
||||
href: showAppInsightsLink
|
||||
? PATH_ADMIN_INSIGHTS
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { ComponentProps, ReactNode, useState } from 'react';
|
||||
import { ComponentProps, ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import LinkWithStatusChild from './primitives/LinkWithStatusChild';
|
||||
import clsx from 'clsx/lite';
|
||||
@ -11,6 +11,8 @@ export default function LinkWithStatus({
|
||||
loadingClassName,
|
||||
isLoading: isLoadingProp = false,
|
||||
setIsLoading: setIsLoadingProp,
|
||||
onLoad,
|
||||
flickerThreshold,
|
||||
...props
|
||||
}: Omit<ComponentProps<typeof Link>, 'children'> & {
|
||||
children: ReactNode | ((props: { isLoading: boolean }) => ReactNode)
|
||||
@ -18,6 +20,8 @@ export default function LinkWithStatus({
|
||||
// For hoisting state to a parent component, e.g., <EntityLink />
|
||||
isLoading?: boolean
|
||||
setIsLoading?: (isLoading: boolean) => void
|
||||
onLoad?: () => void
|
||||
flickerThreshold?: number
|
||||
}) {
|
||||
const [_isLoading, _setIsLoading] = useState(false);
|
||||
const isLoading = isLoadingProp || _isLoading;
|
||||
@ -25,6 +29,16 @@ export default function LinkWithStatus({
|
||||
|
||||
const isControlled = typeof children === 'function';
|
||||
|
||||
const hasStartedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
hasStartedRef.current = true;
|
||||
} else if (hasStartedRef.current) {
|
||||
onLoad?.();
|
||||
hasStartedRef.current = false;
|
||||
}
|
||||
}, [isLoading, onLoad]);
|
||||
|
||||
return <Link
|
||||
{...props}
|
||||
className={clsx(
|
||||
@ -36,7 +50,7 @@ export default function LinkWithStatus({
|
||||
isLoading && loadingClassName,
|
||||
)}
|
||||
>
|
||||
<LinkWithStatusChild {...{ setIsLoading }}>
|
||||
<LinkWithStatusChild {...{ setIsLoading, flickerThreshold }}>
|
||||
{typeof children === 'function'
|
||||
? children({ isLoading })
|
||||
: children}
|
||||
|
||||
36
src/components/LoaderLink.tsx
Normal file
36
src/components/LoaderLink.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { ComponentProps, ReactNode } from 'react';
|
||||
import LinkWithStatus from './LinkWithStatus';
|
||||
import Spinner from './Spinner';
|
||||
import clsx from 'clsx/lite';
|
||||
|
||||
export default function LoaderLink({
|
||||
icon,
|
||||
classNameIcon,
|
||||
children,
|
||||
...props
|
||||
}: Omit<ComponentProps<typeof LinkWithStatus>, 'children'> & {
|
||||
icon: ReactNode
|
||||
classNameIcon?: string
|
||||
children?: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<LinkWithStatus {...props}>
|
||||
{({ isLoading }) =>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className={clsx(
|
||||
'inline-flex items-center justify-center',
|
||||
'min-w-[1.25rem] h-6',
|
||||
classNameIcon,
|
||||
)}>
|
||||
{isLoading
|
||||
? <Spinner />
|
||||
: icon}
|
||||
</span>
|
||||
{children &&
|
||||
<span>
|
||||
{children}
|
||||
</span>}
|
||||
</span>}
|
||||
</LinkWithStatus>
|
||||
);
|
||||
}
|
||||
@ -5,14 +5,12 @@ import { clsx } from 'clsx/lite';
|
||||
import {
|
||||
ComponentProps,
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useState,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
import LoaderButton from '../primitives/LoaderButton';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { downloadFileFromBrowser } from '@/utility/url';
|
||||
import KeyCommand from '../primitives/KeyCommand';
|
||||
import LoaderLink from '../LoaderLink';
|
||||
|
||||
export default function MoreMenuItem({
|
||||
label,
|
||||
@ -43,26 +41,8 @@ export default function MoreMenuItem({
|
||||
keyCommand?: string
|
||||
keyCommandModifier?: ComponentProps<typeof KeyCommand>['modifier']
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const [transitionDidStart, setTransitionDidStart] = useState(false);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (transitionDidStart && !isPending) {
|
||||
dismissMenu?.();
|
||||
setTransitionDidStart(false);
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
}, [isPending, dismissMenu, transitionDidStart]);
|
||||
|
||||
const getColorClasses = () => {
|
||||
switch (color) {
|
||||
case 'grey': return clsx(
|
||||
@ -76,6 +56,16 @@ export default function MoreMenuItem({
|
||||
}
|
||||
};
|
||||
|
||||
const buttonContent = <>
|
||||
<span>
|
||||
{labelComplex ?? label}
|
||||
</span>
|
||||
{annotation &&
|
||||
<span className="text-dim ml-3">
|
||||
{annotation}
|
||||
</span>}
|
||||
</>;
|
||||
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
disabled={isLoading}
|
||||
@ -112,41 +102,39 @@ export default function MoreMenuItem({
|
||||
dismissMenu?.();
|
||||
}
|
||||
}
|
||||
if (href) {
|
||||
if (href !== pathname) {
|
||||
if (hrefDownloadName) {
|
||||
setIsLoading(true);
|
||||
downloadFileFromBrowser(href, hrefDownloadName)
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
dismissMenu?.();
|
||||
});
|
||||
} else {
|
||||
setTransitionDidStart(true);
|
||||
startTransition(() => router.push(href));
|
||||
}
|
||||
} else {
|
||||
dismissMenu?.();
|
||||
}
|
||||
if (href && hrefDownloadName) {
|
||||
setIsLoading(true);
|
||||
downloadFileFromBrowser(href, hrefDownloadName)
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
dismissMenu?.();
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LoaderButton
|
||||
icon={icon}
|
||||
isLoading={isLoading || isPending}
|
||||
hideText="never"
|
||||
styleAs="link-without-hover"
|
||||
className="translate-y-[0.5px] text-sm grow"
|
||||
classNameIcon="translate-y-[-0.5px]!"
|
||||
>
|
||||
<span>
|
||||
{labelComplex ?? label}
|
||||
</span>
|
||||
{annotation &&
|
||||
<span className="text-dim ml-3">
|
||||
{annotation}
|
||||
</span>}
|
||||
</LoaderButton>
|
||||
{href && !hrefDownloadName
|
||||
? <LoaderLink
|
||||
icon={icon}
|
||||
href={href}
|
||||
className={clsx(
|
||||
'inline-flex items-center grow',
|
||||
'text-sm text-main hover:text-main',
|
||||
)}
|
||||
onLoad={dismissMenu}
|
||||
flickerThreshold={0}
|
||||
>
|
||||
{buttonContent}
|
||||
</LoaderLink>
|
||||
: <LoaderButton
|
||||
icon={icon}
|
||||
isLoading={isLoading}
|
||||
hideText="never"
|
||||
styleAs="link-without-hover"
|
||||
className="translate-y-[0.5px] text-sm grow"
|
||||
classNameIcon="translate-y-[-0.5px]!"
|
||||
>
|
||||
{buttonContent}
|
||||
</LoaderButton>}
|
||||
{keyCommand &&
|
||||
<KeyCommand
|
||||
modifier={keyCommandModifier}
|
||||
|
||||
@ -3,14 +3,16 @@
|
||||
import { ReactNode, useEffect, useRef } from 'react';
|
||||
import { useLinkStatus } from 'next/link';
|
||||
|
||||
const FLICKER_THRESHOLD = 400;
|
||||
const DEFAULT_FLICKER_THRESHOLD = 400;
|
||||
|
||||
export default function LinkWithStatusChild({
|
||||
children,
|
||||
setIsLoading,
|
||||
flickerThreshold = DEFAULT_FLICKER_THRESHOLD,
|
||||
}: {
|
||||
children: ReactNode
|
||||
setIsLoading: (isLoading: boolean) => void
|
||||
flickerThreshold?: number
|
||||
}) {
|
||||
const { pending } = useLinkStatus();
|
||||
|
||||
@ -26,7 +28,7 @@ export default function LinkWithStatusChild({
|
||||
startLoadingTimeout.current = setTimeout(() => {
|
||||
setIsLoading(true);
|
||||
isLoadingStartTime.current = Date.now();
|
||||
}, FLICKER_THRESHOLD);
|
||||
}, flickerThreshold);
|
||||
} else if (startLoadingTimeout.current) {
|
||||
clearTimeout(startLoadingTimeout.current);
|
||||
startLoadingTimeout.current = undefined;
|
||||
@ -34,9 +36,9 @@ export default function LinkWithStatusChild({
|
||||
stopLoadingTimeout.current = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
isLoadingStartTime.current = undefined;
|
||||
}, FLICKER_THRESHOLD - loadingDuration);
|
||||
}, Math.max(0, flickerThreshold - loadingDuration));
|
||||
}
|
||||
}, [pending, setIsLoading]);
|
||||
}, [pending, setIsLoading, flickerThreshold]);
|
||||
|
||||
useEffect(() => () => {
|
||||
clearTimeout(startLoadingTimeout.current);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user