Side menu logic with blog title

This commit is contained in:
Vince 2021-02-09 15:31:56 +11:00
parent b1dd5b5070
commit 8bed8fa3ac
12 changed files with 83 additions and 85 deletions

View file

@ -1,9 +1,5 @@
import React, { ReactNode, useContext } from 'react';
import { useSelector } from 'react-redux';
import React, { ReactNode } from 'react';
import { UI } from '../../constants';
import { ScreenContext } from '../../contexts/screen';
import { PageType } from '../../state/navigation';
import { IState } from '../../state/reducers';
import { Header } from '../navigation/Header';
import { SideMenu } from '../navigation/SideMenu';
@ -12,16 +8,6 @@ interface Props {
}
export default function Layout({ children }: Props) {
const { isTablet, isDesktop } = useContext(ScreenContext);
const { pageType, sideMenuExpanded } = useSelector(
(state: IState) => state.navigation,
);
const marginLeft =
(pageType !== PageType.NORMAL && isDesktop) || isTablet
? UI.SIDE_MENU_SIDE_BAR_WIDTH_PX
: 0;
return (
<div
style={{ height: '100vh', width: '100%' }}
@ -36,12 +22,7 @@ export default function Layout({ children }: Props) {
className="flex w-full h-full"
>
<SideMenu />
<div
style={{
marginLeft: `${marginLeft}px`,
}}
className="w-full py-6 overflow-y-auto duration-300 bg-alt"
>
<div className="w-full py-6 overflow-y-auto duration-300 bg-alt">
{children}
</div>
</div>

View file

@ -1,6 +1,7 @@
import classNames from 'classnames';
import Link from 'next/link';
import React, { useRef } from 'react';
import { v4 as uuid } from 'uuid';
import OxenLogoSVG from '../../assets/svgs/brand.svg';
import { NAVIGATION, UI } from '../../constants';
@ -45,15 +46,15 @@ export function DesktopHeader() {
);
return (
<>
<div key={uuid()}>
{item.external ? (
link
) : (
<Link key={item.label} href={item.href} as={item.href}>
<Link href={item.href} as={item.href}>
{link}
</Link>
)}
</>
</div>
);
})}
</div>

View file

@ -4,6 +4,6 @@ import { DesktopHeader } from './DesktopHeader';
import { MobileHeader } from './MobileHeader';
export function Header() {
const { isDesktop } = useContext(ScreenContext);
return <>{isDesktop ? <DesktopHeader /> : <MobileHeader />}</>;
const { isHuge } = useContext(ScreenContext);
return <>{isHuge ? <DesktopHeader /> : <MobileHeader />}</>;
}

View file

@ -1,7 +1,9 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import { ScreenContext } from '../../contexts/screen';
import { PageType } from '../../state/navigation';
import { IState } from '../../state/reducers';
import { SideMenuFullscreen } from './SideMenuFullscreen';
import { SideMenuInner } from './SideMenuInner';
import { SideMenuSplit } from './SideMenuSplit';
export interface ISideMenuItem {
@ -12,7 +14,9 @@ export interface ISideMenuItem {
export function SideMenu() {
const { isMobile, isTablet } = useContext(ScreenContext);
const overlay = false;
const { sideMenuExpanded: expanded, pageType, postTitle } = useSelector(
(state: IState) => state.navigation,
);
// ON MOBILE: overlay with no sidebar
// ON TABLET: overlay with sidebar
@ -23,12 +27,11 @@ export function SideMenu() {
// Split screen overlay with sidebar: ONLY ON DESKTOP && /blog
// Just sidebar: /blog
return isMobile || isTablet ? (
const isBlog = pageType === PageType.BLOG;
const isPost = pageType === PageType.POST;
return (isMobile || isTablet) && !isBlog && !isPost ? (
<SideMenuFullscreen withSideBar={isTablet} />
) : overlay ? (
<div className="fixed inset-0">
<SideMenuInner />
</div>
) : (
<SideMenuSplit />
);

View file

@ -40,7 +40,7 @@ export function SideMenuInner() {
<Contained>
<div className="flex flex-col w-full space-y-4">
{!isDesktop && (
{!isHuge && (
<div
className={classNames(
'flex flex-col pt-8 pb-2 font-medium uppercase font-prompt',
@ -59,7 +59,7 @@ export function SideMenuInner() {
</div>
)}
<div className="flex pb-6 -mx-6 space-x-3">
<div className="flex pb-6 space-x-3 mobile:-mx-6">
<a
href="https://t.me/Oxen_Community"
target="_blank"

View file

@ -5,7 +5,11 @@ import { useDispatch, useSelector } from 'react-redux';
import TriangleSVG from '../../assets/svgs/triangle.svg';
import { NAVIGATION, UI } from '../../constants';
import { ScreenContext } from '../../contexts/screen';
import { collapseSideMenu, expandSideMenu } from '../../state/navigation';
import {
collapseSideMenu,
expandSideMenu,
PageType,
} from '../../state/navigation';
import { IState } from '../../state/reducers';
export enum SideBarMode {
@ -18,13 +22,11 @@ interface Props {
}
export function SideMenuSideBar({ mode }: Props) {
const { isMobile, isTablet, isDesktop, isHuge } = useContext(ScreenContext);
const { sideMenuExpanded: expanded, sideMenuSplit } = useSelector(
const { isMobile, isTablet, isHuge } = useContext(ScreenContext);
const { sideMenuExpanded: expanded, pageType, postTitle } = useSelector(
(state: IState) => state.navigation,
);
const isCollapsible = isTablet || isMobile;
const router = useRouter();
const dispatch = useDispatch();
@ -32,8 +34,10 @@ export function SideMenuSideBar({ mode }: Props) {
item => item.href === router.asPath,
)?.label;
const isBlog = !sideMenuSplit;
const label = isBlog ? 'Blog' : selectedSideMenuItem;
const isBlog = pageType === PageType.BLOG;
const isPost = pageType === PageType.POST;
const label = isPost ? postTitle : isBlog ? 'Blog' : selectedSideMenuItem;
const isCollapsible = (isTablet || isMobile) && !isPost && !isBlog;
const toggleSideMenu = () =>
dispatch(expanded ? collapseSideMenu() : expandSideMenu());
@ -49,21 +53,20 @@ export function SideMenuSideBar({ mode }: Props) {
mode === SideBarMode.LABEL && 'border-r border-b',
mode === SideBarMode.MENU && !expanded && 'border-r',
)}
onClick={() => isCollapsible && toggleSideMenu()}
>
<div>
{isCollapsible && (
<TriangleSVG
onClick={() => isCollapsible && toggleSideMenu()}
className={classNames(
'h-4 transform outline-none duration-300 cursor-pointer',
expanded ? 'rotate-180' : '-rotate-60',
)}
/>
)}
{isBlog && !isCollapsible && (
<a href="/blog">
{(isBlog || isPost) && !isCollapsible && (
<a href={isPost ? '/blog' : '/'}>
<TriangleSVG
// onClick={() => router.push('/blog', '/blog')}
className={classNames(
'h-4 transform outline-none duration-300 rotate-180 cursor-pointer',
)}

View file

@ -98,15 +98,11 @@ const SIDE_MENU_ITEMS = {
},
} as { [name: string]: ISideMenuItem };
// Pages in which the sidebar breaks out of the split view
const SIDEBAR_OVERLAY_PAGES = ['blog'];
const NAVIGATION = {
MENU_ITEMS,
SIDE_MENU_ITEMS,
OVERLAY_PAGE_REGEX: new RegExp(
`^/(${SIDEBAR_OVERLAY_PAGES.map(i => `(${i})`).join('|')})[/]?`,
),
BLOG_REGEX: /^\/(blog)[/]?$/,
POST_REGEX: /^\/(blog\/\[slug\])[/]?$/,
};
export default NAVIGATION;

View file

@ -11,7 +11,8 @@ import ScreenProvider from '../contexts/screen';
import { CmsApi } from '../services/cms';
import {
collapseSideMenu,
setSideMenuSplit,
PageType,
setPageType,
setSplitPagesContent,
} from '../state/navigation';
import { rootReducer } from '../state/reducers';
@ -24,14 +25,20 @@ function App({ Component, pageProps }: AppProps) {
const handleLocationChange = url => {
// Break out of split view
const split = !NAVIGATION.OVERLAY_PAGE_REGEX.test(url);
const onBlog = NAVIGATION.BLOG_REGEX.test(url);
const onPost = NAVIGATION.POST_REGEX.test(url);
console.log(
'_app ➡️ NAVIGATION.OVERLAY_PAGE_REGEX.test(url);:',
NAVIGATION.OVERLAY_PAGE_REGEX.test(url),
);
console.log('_app ➡️ url:', url);
console.log('_app ➡️ onBlog:', onBlog);
console.log('_app ➡️ NAVIGATION.BLOG_REGEX:', NAVIGATION.BLOG_REGEX);
store.dispatch(setSideMenuSplit(split));
const pageType = onBlog
? PageType.BLOG
: onPost
? PageType.POST
: PageType.NORMAL;
store.dispatch(setPageType(pageType));
store.dispatch(collapseSideMenu());
};
@ -41,7 +48,10 @@ function App({ Component, pageProps }: AppProps) {
store.dispatch(collapseSideMenu());
handleLocationChange(router.pathname);
console.log('NAVIGATION.OVERLAY_PAGE_REGEX', NAVIGATION.OVERLAY_PAGE_REGEX);
console.log(
'_app ➡️ store.getState().navigation.pageType:',
store.getState().navigation.pageType,
);
}, []);
// Close side menu on page changed

View file

@ -1,12 +1,15 @@
// [slug].js
import Head from 'next/head';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Article } from '../../components/article/Article';
import { CmsApi } from '../../services/cms';
import { setPostTitle } from '../../state/navigation';
import { IState } from '../../state/reducers';
import { IPost } from '../../types/cms';
import { generateTitle } from '../../utils/metadata';
export async function getStaticProps({ params }) {
export async function getServerSideProps({ params }) {
const api = new CmsApi();
const post = await api.fetchBlogBySlug(String(params.slug) ?? '');
@ -17,18 +20,18 @@ export async function getStaticProps({ params }) {
};
}
return { props: post, revalidate: 60 };
return { props: { post } };
}
function Post(post: IPost) {
// Scroll to top on load
useEffect(() => {
if (typeof window !== 'undefined') {
window.scrollTo(0, 0);
}
}, []);
function Post({ post }: { post: IPost }) {
const { pageType, postTitle } = useSelector(
(state: IState) => state.navigation,
);
const dispatch = useDispatch();
console.log('[slug] ➡️ posts:', post);
useEffect(() => {
dispatch(setPostTitle(postTitle));
}, []);
return (
<>

View file

@ -1,7 +1,6 @@
import { InferGetServerSidePropsType } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import React from 'react';
import { ArticleCard } from '../../components/cards/ArticleCard';
import { ArticleCardFeature } from '../../components/cards/ArticleCardFeature';
import { CardGrid } from '../../components/cards/CardGrid';
@ -17,19 +16,9 @@ export async function getServerSideProps(context) {
return { props: { posts } };
}
const Blog = (
props: InferGetServerSidePropsType<typeof getServerSideProps>,
) => {
const { posts } = props;
console.log('index ➡️ posts:', posts);
const router = useRouter();
useEffect(() => {
console.log('index ➡️ router.query;:', router.query);
}, []);
const Blog = ({
posts,
}: InferGetServerSidePropsType<typeof getServerSideProps>) => {
return (
<div>
<Head>

View file

@ -19,6 +19,7 @@ export interface INavigation {
headerCollapsed: boolean;
sideMenuExpanded: boolean;
pageType: PageType;
postTitle?: string;
pages?: TPages;
}
@ -32,6 +33,7 @@ export const initialNavigationState: INavigation = {
// Side menu expanded only toggles for mobile.
// On desktop it's always open (if it fits).
pageType: PageType.NORMAL,
postTitle: undefined,
headerCollapsed: true,
sideMenuExpanded: false,
};
@ -39,6 +41,7 @@ export const initialNavigationState: INavigation = {
export enum NavigationActions {
SET_HEADER_COLLAPSED = 'SET_HEADER_COLLAPSED',
SET_PAGE_TYPE = 'SET_PAGE_TYPE',
SET_POST_TITLE = 'SET_POST_TITLE',
EXPAND_SIDE_MENU = 'EXPAND_SIDE_MENU',
COLLAPSE_SIDE_MENU = 'COLLAPSE_SIDE_MENU',
SET_SPLIT_PAGES_CONTENT = 'SET_SPLIT_PAGES_CONTENT',
@ -57,6 +60,12 @@ export const setPageType = (type: PageType) => ({
payload: type,
});
// For sidebar title
export const setPostTitle = (title: string) => ({
type: NavigationActions.SET_POST_TITLE,
payload: title,
});
export const expandSideMenu = () => ({
type: NavigationActions.EXPAND_SIDE_MENU,
});

View file

@ -20,6 +20,9 @@ export const navigationReducer = (
case NavigationActions.SET_PAGE_TYPE: {
return { ...state, pageType: action.payload };
}
case NavigationActions.SET_POST_TITLE: {
return { ...state, postTitle: action.payload };
}
case NavigationActions.EXPAND_SIDE_MENU: {
return { ...state, sideMenuExpanded: true };
}