Skip nav animation in empty state
This commit is contained in:
parent
6c73b14b85
commit
8c6e406904
@ -6,10 +6,8 @@ import {
|
||||
DEFAULT_THEME,
|
||||
PRESERVE_ORIGINAL_UPLOADS,
|
||||
META_DESCRIPTION,
|
||||
NAV_TITLE,
|
||||
META_TITLE,
|
||||
HTML_LANG,
|
||||
NAV_CAPTION,
|
||||
SITE_FEEDS_ENABLED,
|
||||
ADMIN_DEBUG_TOOLS_ENABLED,
|
||||
} from '@/app/config';
|
||||
@ -104,10 +102,7 @@ export default function RootLayout({
|
||||
'mx-3 mb-3',
|
||||
'lg:mx-6 lg:mb-6',
|
||||
)}>
|
||||
<Nav
|
||||
navTitle={NAV_TITLE}
|
||||
navCaption={NAV_CAPTION}
|
||||
/>
|
||||
<Nav />
|
||||
<main>
|
||||
<ShareModals />
|
||||
<RecipeModal />
|
||||
|
||||
@ -866,7 +866,7 @@ export default function AdminAppConfigurationClient({
|
||||
titleShort={(section as AdminConfigSection).titleShort}
|
||||
icon={section.icon}
|
||||
optional={!section.required}
|
||||
updateHashOnVisible={hasScrolled}
|
||||
updateHashOnVisible={hasScrolled && !simplifiedView}
|
||||
>
|
||||
{renderGroupContent(section.title)}
|
||||
</ChecklistGroup>
|
||||
|
||||
127
src/app/Nav.tsx
127
src/app/Nav.tsx
@ -1,117 +1,12 @@
|
||||
'use client';
|
||||
import { getPhotosCached } from '@/photo/cache';
|
||||
import NavClient from './NavClient';
|
||||
import { NAV_CAPTION, NAV_TITLE } from './config';
|
||||
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import AppGrid from '../components/AppGrid';
|
||||
import AppViewSwitcher, { SwitcherSelection } from '@/app/AppViewSwitcher';
|
||||
import {
|
||||
PATH_ROOT,
|
||||
isPathAdmin,
|
||||
isPathFull,
|
||||
isPathGrid,
|
||||
isPathProtected,
|
||||
isPathSignIn,
|
||||
} from '@/app/path';
|
||||
import AnimateItems from '../components/AnimateItems';
|
||||
import {
|
||||
GRID_HOMEPAGE_ENABLED,
|
||||
NAV_CAPTION,
|
||||
} from './config';
|
||||
import { useRef } from 'react';
|
||||
import useStickyNav from './useStickyNav';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
|
||||
const NAV_HEIGHT_CLASS = NAV_CAPTION
|
||||
? 'min-h-[4rem] sm:min-h-[5rem]'
|
||||
: 'min-h-[4rem]';
|
||||
|
||||
export default function Nav({
|
||||
navTitle,
|
||||
navCaption,
|
||||
}: {
|
||||
navTitle: string
|
||||
navCaption?: string
|
||||
}) {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
const pathname = usePathname();
|
||||
const showNav = !isPathSignIn(pathname);
|
||||
|
||||
const {
|
||||
hasLoadedWithAnimations,
|
||||
} = useAppState();
|
||||
|
||||
const {
|
||||
classNameStickyContainer,
|
||||
classNameStickyNav,
|
||||
isNavVisible,
|
||||
} = useStickyNav(ref, !isPathAdmin(pathname));
|
||||
|
||||
const renderLink = (
|
||||
text: string,
|
||||
linkOrAction: string | (() => void),
|
||||
) =>
|
||||
typeof linkOrAction === 'string'
|
||||
? <Link href={linkOrAction}>{text}</Link>
|
||||
: <button onClick={linkOrAction} type="button">{text}</button>;
|
||||
|
||||
const switcherSelectionForPath = (): SwitcherSelection | undefined => {
|
||||
if (pathname === PATH_ROOT) {
|
||||
return GRID_HOMEPAGE_ENABLED ? 'grid' : 'full';
|
||||
} else if (isPathGrid(pathname)) {
|
||||
return 'grid';
|
||||
} else if (isPathFull(pathname)) {
|
||||
return 'full';
|
||||
} else if (isPathProtected(pathname)) {
|
||||
return 'admin';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppGrid
|
||||
className={classNameStickyContainer}
|
||||
classNameMain='pointer-events-auto'
|
||||
contentMain={
|
||||
<AnimateItems
|
||||
animateOnFirstLoadOnly
|
||||
type={!isPathAdmin(pathname) ? 'bottom' : 'none'}
|
||||
distanceOffset={10}
|
||||
items={showNav
|
||||
? [<nav
|
||||
key="nav"
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'w-full flex items-center bg-main',
|
||||
NAV_HEIGHT_CLASS,
|
||||
// Enlarge nav to ensure it fully masks underlying content
|
||||
'md:w-[calc(100%+8px)] md:translate-x-[-4px] md:px-[4px]',
|
||||
classNameStickyNav,
|
||||
)}>
|
||||
<AppViewSwitcher
|
||||
currentSelection={switcherSelectionForPath()}
|
||||
className="translate-x-[-1px]"
|
||||
animate={hasLoadedWithAnimations && isNavVisible}
|
||||
/>
|
||||
<div className={clsx(
|
||||
'grow text-right min-w-0',
|
||||
'translate-y-[-1px]',
|
||||
)}>
|
||||
<div className="truncate overflow-hidden select-none">
|
||||
{renderLink(navTitle, PATH_ROOT)}
|
||||
</div>
|
||||
{navCaption &&
|
||||
<div className={clsx(
|
||||
'hidden sm:block truncate overflow-hidden',
|
||||
'leading-tight text-dim',
|
||||
)}>
|
||||
{navCaption}
|
||||
</div>}
|
||||
</div>
|
||||
</nav>]
|
||||
: []}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default async function Nav() {
|
||||
const photos = await getPhotosCached({ limit: 1 }).catch(() => []);
|
||||
return <NavClient
|
||||
navTitle={NAV_TITLE}
|
||||
navCaption={NAV_CAPTION}
|
||||
animate={photos.length > 0}
|
||||
/>;
|
||||
}
|
||||
|
||||
119
src/app/NavClient.tsx
Normal file
119
src/app/NavClient.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
'use client';
|
||||
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import AppGrid from '../components/AppGrid';
|
||||
import AppViewSwitcher, { SwitcherSelection } from '@/app/AppViewSwitcher';
|
||||
import {
|
||||
PATH_ROOT,
|
||||
isPathAdmin,
|
||||
isPathFull,
|
||||
isPathGrid,
|
||||
isPathProtected,
|
||||
isPathSignIn,
|
||||
} from '@/app/path';
|
||||
import AnimateItems from '../components/AnimateItems';
|
||||
import {
|
||||
GRID_HOMEPAGE_ENABLED,
|
||||
NAV_CAPTION,
|
||||
} from './config';
|
||||
import { useRef } from 'react';
|
||||
import useStickyNav from './useStickyNav';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
|
||||
const NAV_HEIGHT_CLASS = NAV_CAPTION
|
||||
? 'min-h-[4rem] sm:min-h-[5rem]'
|
||||
: 'min-h-[4rem]';
|
||||
|
||||
export default function NavClient({
|
||||
navTitle,
|
||||
navCaption,
|
||||
animate,
|
||||
}: {
|
||||
navTitle: string
|
||||
navCaption?: string
|
||||
animate: boolean
|
||||
}) {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
const pathname = usePathname();
|
||||
const showNav = !isPathSignIn(pathname);
|
||||
|
||||
const {
|
||||
hasLoadedWithAnimations,
|
||||
} = useAppState();
|
||||
|
||||
const {
|
||||
classNameStickyContainer,
|
||||
classNameStickyNav,
|
||||
isNavVisible,
|
||||
} = useStickyNav(ref, !isPathAdmin(pathname));
|
||||
|
||||
const renderLink = (
|
||||
text: string,
|
||||
linkOrAction: string | (() => void),
|
||||
) =>
|
||||
typeof linkOrAction === 'string'
|
||||
? <Link href={linkOrAction}>{text}</Link>
|
||||
: <button onClick={linkOrAction} type="button">{text}</button>;
|
||||
|
||||
const switcherSelectionForPath = (): SwitcherSelection | undefined => {
|
||||
if (pathname === PATH_ROOT) {
|
||||
return GRID_HOMEPAGE_ENABLED ? 'grid' : 'full';
|
||||
} else if (isPathGrid(pathname)) {
|
||||
return 'grid';
|
||||
} else if (isPathFull(pathname)) {
|
||||
return 'full';
|
||||
} else if (isPathProtected(pathname)) {
|
||||
return 'admin';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppGrid
|
||||
className={classNameStickyContainer}
|
||||
classNameMain='pointer-events-auto'
|
||||
contentMain={
|
||||
<AnimateItems
|
||||
animateOnFirstLoadOnly
|
||||
type={animate && !isPathAdmin(pathname) ? 'bottom' : 'none'}
|
||||
distanceOffset={10}
|
||||
items={showNav
|
||||
? [<nav
|
||||
key="nav"
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'w-full flex items-center bg-main',
|
||||
NAV_HEIGHT_CLASS,
|
||||
// Enlarge nav to ensure it fully masks underlying content
|
||||
'md:w-[calc(100%+8px)] md:translate-x-[-4px] md:px-[4px]',
|
||||
classNameStickyNav,
|
||||
)}>
|
||||
<AppViewSwitcher
|
||||
currentSelection={switcherSelectionForPath()}
|
||||
className="translate-x-[-1px]"
|
||||
animate={hasLoadedWithAnimations && isNavVisible}
|
||||
/>
|
||||
<div className={clsx(
|
||||
'grow text-right min-w-0',
|
||||
'translate-y-[-1px]',
|
||||
)}>
|
||||
<div className="truncate overflow-hidden select-none">
|
||||
{renderLink(navTitle, PATH_ROOT)}
|
||||
</div>
|
||||
{navCaption &&
|
||||
<div className={clsx(
|
||||
'hidden sm:block truncate overflow-hidden',
|
||||
'leading-tight text-dim',
|
||||
)}>
|
||||
{navCaption}
|
||||
</div>}
|
||||
</div>
|
||||
</nav>]
|
||||
: []}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -11,7 +11,6 @@ import { revalidatePath } from 'next/cache';
|
||||
import SignInOrUploadClient from '@/admin/SignInOrUploadClient';
|
||||
import Link from 'next/link';
|
||||
import { PATH_ADMIN_CONFIGURATION } from '@/app/path';
|
||||
import AnimateItems from '@/components/AnimateItems';
|
||||
import { getAppText } from '@/i18n/state/server';
|
||||
|
||||
export default async function PhotosEmptyState() {
|
||||
@ -20,50 +19,46 @@ export default async function PhotosEmptyState() {
|
||||
return (
|
||||
<AppGrid
|
||||
contentMain={
|
||||
<AnimateItems
|
||||
items={[
|
||||
<Container
|
||||
key="PhotosEmptyState"
|
||||
className="min-h-[20rem] sm:min-h-[30rem] px-8"
|
||||
padding="loose"
|
||||
>
|
||||
<HiOutlinePhotograph
|
||||
className="text-medium"
|
||||
size={24}
|
||||
<Container
|
||||
key="PhotosEmptyState"
|
||||
className="min-h-[20rem] sm:min-h-[30rem] px-8"
|
||||
padding="loose"
|
||||
>
|
||||
<HiOutlinePhotograph
|
||||
className="text-medium"
|
||||
size={24}
|
||||
/>
|
||||
<div className={clsx(
|
||||
'font-bold text-2xl',
|
||||
'text-gray-700 dark:text-gray-200',
|
||||
)}>
|
||||
{!IS_SITE_READY
|
||||
? appText.onboarding.setupIncomplete
|
||||
: appText.onboarding.setupComplete}
|
||||
</div>
|
||||
{!IS_SITE_READY
|
||||
? <AdminAppConfiguration simplifiedView />
|
||||
: <div className="max-w-md text-center space-y-6">
|
||||
<SignInOrUploadClient
|
||||
shouldResize={!PRESERVE_ORIGINAL_UPLOADS}
|
||||
onLastUpload={async () => {
|
||||
'use server';
|
||||
// Update upload count in admin nav
|
||||
revalidatePath('/admin', 'layout');
|
||||
}}
|
||||
/>
|
||||
<div className={clsx(
|
||||
'font-bold text-2xl',
|
||||
'text-gray-700 dark:text-gray-200',
|
||||
)}>
|
||||
{!IS_SITE_READY
|
||||
? appText.onboarding.setupIncomplete
|
||||
: appText.onboarding.setupComplete}
|
||||
<div>
|
||||
{appText.onboarding.setupConfig}
|
||||
{' '}
|
||||
<Link
|
||||
href={PATH_ADMIN_CONFIGURATION}
|
||||
className="text-main hover:underline"
|
||||
>
|
||||
/admin/configuration
|
||||
</Link>
|
||||
</div>
|
||||
{!IS_SITE_READY
|
||||
? <AdminAppConfiguration simplifiedView />
|
||||
: <div className="max-w-md text-center space-y-6">
|
||||
<SignInOrUploadClient
|
||||
shouldResize={!PRESERVE_ORIGINAL_UPLOADS}
|
||||
onLastUpload={async () => {
|
||||
'use server';
|
||||
// Update upload count in admin nav
|
||||
revalidatePath('/admin', 'layout');
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
{appText.onboarding.setupConfig}
|
||||
{' '}
|
||||
<Link
|
||||
href={PATH_ADMIN_CONFIGURATION}
|
||||
className="text-main hover:underline"
|
||||
>
|
||||
/admin/configuration
|
||||
</Link>
|
||||
</div>
|
||||
</div>}
|
||||
</Container>,
|
||||
]}
|
||||
/>
|
||||
</div>}
|
||||
</Container>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user