Skip nav animation in empty state
This commit is contained in:
parent
6c73b14b85
commit
8c6e406904
@ -6,10 +6,8 @@ import {
|
|||||||
DEFAULT_THEME,
|
DEFAULT_THEME,
|
||||||
PRESERVE_ORIGINAL_UPLOADS,
|
PRESERVE_ORIGINAL_UPLOADS,
|
||||||
META_DESCRIPTION,
|
META_DESCRIPTION,
|
||||||
NAV_TITLE,
|
|
||||||
META_TITLE,
|
META_TITLE,
|
||||||
HTML_LANG,
|
HTML_LANG,
|
||||||
NAV_CAPTION,
|
|
||||||
SITE_FEEDS_ENABLED,
|
SITE_FEEDS_ENABLED,
|
||||||
ADMIN_DEBUG_TOOLS_ENABLED,
|
ADMIN_DEBUG_TOOLS_ENABLED,
|
||||||
} from '@/app/config';
|
} from '@/app/config';
|
||||||
@ -104,10 +102,7 @@ export default function RootLayout({
|
|||||||
'mx-3 mb-3',
|
'mx-3 mb-3',
|
||||||
'lg:mx-6 lg:mb-6',
|
'lg:mx-6 lg:mb-6',
|
||||||
)}>
|
)}>
|
||||||
<Nav
|
<Nav />
|
||||||
navTitle={NAV_TITLE}
|
|
||||||
navCaption={NAV_CAPTION}
|
|
||||||
/>
|
|
||||||
<main>
|
<main>
|
||||||
<ShareModals />
|
<ShareModals />
|
||||||
<RecipeModal />
|
<RecipeModal />
|
||||||
|
|||||||
@ -866,7 +866,7 @@ export default function AdminAppConfigurationClient({
|
|||||||
titleShort={(section as AdminConfigSection).titleShort}
|
titleShort={(section as AdminConfigSection).titleShort}
|
||||||
icon={section.icon}
|
icon={section.icon}
|
||||||
optional={!section.required}
|
optional={!section.required}
|
||||||
updateHashOnVisible={hasScrolled}
|
updateHashOnVisible={hasScrolled && !simplifiedView}
|
||||||
>
|
>
|
||||||
{renderGroupContent(section.title)}
|
{renderGroupContent(section.title)}
|
||||||
</ChecklistGroup>
|
</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';
|
export default async function Nav() {
|
||||||
import { usePathname } from 'next/navigation';
|
const photos = await getPhotosCached({ limit: 1 }).catch(() => []);
|
||||||
import Link from 'next/link';
|
return <NavClient
|
||||||
import AppGrid from '../components/AppGrid';
|
navTitle={NAV_TITLE}
|
||||||
import AppViewSwitcher, { SwitcherSelection } from '@/app/AppViewSwitcher';
|
navCaption={NAV_CAPTION}
|
||||||
import {
|
animate={photos.length > 0}
|
||||||
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>]
|
|
||||||
: []}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
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 SignInOrUploadClient from '@/admin/SignInOrUploadClient';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { PATH_ADMIN_CONFIGURATION } from '@/app/path';
|
import { PATH_ADMIN_CONFIGURATION } from '@/app/path';
|
||||||
import AnimateItems from '@/components/AnimateItems';
|
|
||||||
import { getAppText } from '@/i18n/state/server';
|
import { getAppText } from '@/i18n/state/server';
|
||||||
|
|
||||||
export default async function PhotosEmptyState() {
|
export default async function PhotosEmptyState() {
|
||||||
@ -20,50 +19,46 @@ export default async function PhotosEmptyState() {
|
|||||||
return (
|
return (
|
||||||
<AppGrid
|
<AppGrid
|
||||||
contentMain={
|
contentMain={
|
||||||
<AnimateItems
|
<Container
|
||||||
items={[
|
key="PhotosEmptyState"
|
||||||
<Container
|
className="min-h-[20rem] sm:min-h-[30rem] px-8"
|
||||||
key="PhotosEmptyState"
|
padding="loose"
|
||||||
className="min-h-[20rem] sm:min-h-[30rem] px-8"
|
>
|
||||||
padding="loose"
|
<HiOutlinePhotograph
|
||||||
>
|
className="text-medium"
|
||||||
<HiOutlinePhotograph
|
size={24}
|
||||||
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(
|
<div>
|
||||||
'font-bold text-2xl',
|
{appText.onboarding.setupConfig}
|
||||||
'text-gray-700 dark:text-gray-200',
|
{' '}
|
||||||
)}>
|
<Link
|
||||||
{!IS_SITE_READY
|
href={PATH_ADMIN_CONFIGURATION}
|
||||||
? appText.onboarding.setupIncomplete
|
className="text-main hover:underline"
|
||||||
: appText.onboarding.setupComplete}
|
>
|
||||||
|
/admin/configuration
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{!IS_SITE_READY
|
</div>}
|
||||||
? <AdminAppConfiguration simplifiedView />
|
</Container>
|
||||||
: <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>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user