ref(global) update project structure

This commit is contained in:
lumither committed 2024-06-18 13:07:40 +08:00
1 parent 2fabd8d5c0
commit 6b75c17b05
32 files changed
+67 -42

No files matched your search

@@ -0,0 +1,13 @@
import React from 'react';
const ConstructionPlaceHolder = () => {
return (
<div className={ `flex h-full w-full justify-center items-center` }>
<p className={ `text-2xl font-bold` }>
正在施工中...
</p>
</div>
);
};
export default ConstructionPlaceHolder;
+25
View File
@@ -0,0 +1,25 @@
import React from 'react';
import Link from 'next/link';
import { IoLogoGithub } from 'react-icons/io5';
const Footer = () => {
return (
<div className={ `p-5 mb-4` }>
<div className={ `h-1 w-20 bg-zinc-500 dark:bg-zinc-400 mb-3` }></div>
<p>
© 2024-{ new Date().getFullYear() } <Link href={ `/about` }>Lumither Tao</Link>
</p>
<p>
Powered by Next.js and Rust, built with passion and love
</p>
<Link
href={ `https://github.com/Lumither/blog` }
aria-label={ `GitHub repo for this website` }
>
<IoLogoGithub className={ `mt-1` } />
</Link>
</div>
);
};
export default Footer;
+197
View File
@@ -0,0 +1,197 @@
'use client';
import React, { useEffect, useState } from 'react';
import { Button } from '@nextui-org/react';
import NextImage from 'next/image';
import {
IoFileTrayFull,
IoHome,
IoInformationCircle,
IoLogoGithub,
IoMail,
IoPencil,
IoPricetags,
IoSearch
} from 'react-icons/io5';
import Link from 'next/link';
import ThemeSwitcher from '@/components/ThemeSwitcher';
import { usePathname } from 'next/navigation';
const entries: { display_name: string; href: string; icon: React.ReactNode }[] = [
{
icon: <IoHome size={ `20px` }></IoHome>,
display_name: 'Home',
href: '/'
},
{
icon: <IoInformationCircle size={ `20px` }></IoInformationCircle>,
display_name: 'About',
href: '/about'
},
{
icon: <IoPencil size={ `20px` }></IoPencil>,
display_name: 'Blog',
href: '/blog'
},
{
icon: <IoPricetags size={ `20px` }></IoPricetags>,
display_name: 'Tags',
href: '/tags'
},
{
icon: <IoFileTrayFull size={ `20px` }></IoFileTrayFull>,
display_name: 'Archive',
href: '/archive'
},
{
icon: <IoSearch size={ `20px` }></IoSearch>,
display_name: 'Search',
href: '/search'
}
];
const connections: { label: string, href: string, icon: React.ReactNode }[] = [
{
label: 'Github',
href: 'https://github.com/Lumither',
icon: <IoLogoGithub size={ `30px` } />
},
{
label: 'email',
href: 'mailto:lumither@outlook.com',
icon: <IoMail size={ `30px` } />
}
];
function Navbar() {
const [ scrolled, setScrolled ] = useState(false);
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 0;
setScrolled(isScrolled);
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
const path = usePathname();
const [ shownPath, setShownPath ] = useState(path);
useEffect(() => {
let trimmed_path = path.split(`/`)[1];
if (trimmed_path === '') {
setShownPath('HOME');
} else {
setShownPath(trimmed_path.toUpperCase());
}
}, [ path ]);
const minMobileWidth: number = 768;
const [ isMobile, setIsMobile ] = useState(false);
useEffect(() => {
const updateMobileWidth = () => {
if (window.innerWidth <= minMobileWidth) {
setIsMobile(true);
} else {
setIsMobile(false);
}
};
updateMobileWidth();
window.addEventListener('resize', updateMobileWidth);
return () => {
window.removeEventListener('resize', updateMobileWidth);
};
}, []);
return (
<>
<div
className={ `flex flex-col w-auto md:w-full min-w-[50px] max-w-[256px] mr-0 md:mr-2 ml-2 md:ml-8 min-h-dvh pt-8 sticky` }>
<div className={ `fixed min-h-screen h-full` }>
<div className={ `hidden md:block` }>
<div className={ `flex justify-center items-center my-7` }>
<NextImage
src={ 'https://oss.lumither.com/blog/pictures/compr/1717153492846703409_avatar.webp' }
alt={ 'avatar' }
width={ 200 }
height={ 200 }
className={ 'rounded-full justify-self-center' }
priority={ true }
></NextImage>
</div>
<div>
<p className={ `font-bold text-2xl` }>Lumither Tao</p>
<p className={ `text-xl text-zinc-500 dark:text-zinc-400` }>Ad Astra</p>
</div>
</div>
<div>
<ul className={ `flex flex-col space-y-2 md:space-y-0 md:flex-row items-center my-4 md:space-x-2` }>
{
connections.map((conn, key) => (
<li key={ key }>
<Button as={ Link }
href={ conn.href }
aria-label={ conn.label }
variant={ `light` }
color={ `default` }
className={ `w-fit` }
isIconOnly
>
{ conn.icon }
</Button>
</li>
))
}
</ul>
</div>
<div
className={ `flex justify-center md:justify-start w-full mt-8 ml-0 md:-ml-3` }>
<ul className={ `flex flex-col space-y-2 w-full` }>
{
entries.map((meta, key) => (
<li key={ key }>
<Button
as={ Link }
variant={ `light` }
color={ `default` }
fullWidth
isIconOnly={ isMobile }
className={ 'flex md:justify-start w-fit md:w-full' }
aria-label={ `navbar: ${ meta.display_name }` }
href={ meta.href }>
{ meta.icon }
<p className={ `font-bold hidden md:block` }>{ meta.display_name }</p>
</Button>
</li>
))
}
</ul>
</div>
<div className={ `mt-8 flex md:hidden justify-center w-full` }>
<p className={ `text-center sideways-lr font-bold text-2xl text-zinc-500 dark:text-zinc-400` }>
{ shownPath }
</p>
</div>
<div
className={ `absolute bottom-8 mb-8` }
>
<ThemeSwitcher />
</div>
</div>
</div>
</>
);
}
export default Navbar;
+42
View File
@@ -0,0 +1,42 @@
'use client';
import React, { useEffect, useState } from 'react';
import { useTheme } from 'next-themes';
import { Switch } from '@nextui-org/react';
import { MdOutlineDarkMode, MdOutlineLightMode } from 'react-icons/md';
function ThemeSwitcher() {
const [ mounted, setMounted ] = useState(false);
const { setTheme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<div className={ `` }>
<div className={ `flex flex-row items-center` }>
<Switch
color={ 'default' }
aria-label={ `theme switch` }
startContent={ <MdOutlineLightMode /> }
endContent={ <MdOutlineDarkMode /> }
onValueChange={ (isSelected) => {
if (isSelected) {
setTheme('light');
} else {
setTheme('dark');
}
} }
></Switch>
<p className={ `font-bold hidden md:block` }>{ `Toggle Theme` }</p>
</div>
</div>
);
}
export default ThemeSwitcher;