feat(nav, blog) new ThemeSwitcher, also several updates on ui

This commit is contained in:
lumither committed 2024-08-19 01:22:27 +08:00
1 parent 2ca7e8bc14
commit 3c1fc34c2a
8 files changed
+159 -100

No files matched your search

+12 -14
View File
@@ -33,19 +33,23 @@ function BlogPostCard({ post }: Props) {
<Image <Image
alt={ 'cover picture' } alt={ 'cover picture' }
src={ header_img } src={ header_img }
className={ `w-full object-cover rounded-b-none` } className={ `w-full object-cover rounded-b-none z-10` }
style={ { loading={ 'eager' }
'maskImage': 'linear-gradient(black 95%, transparent)', fill
'backdropFilter': 'blur(20px)' ></Image>
} } <Image
alt={ 'cover picture shadow' }
src={ header_img }
loading={ 'eager' }
className={ `w-full object-cover rounded-b-none scale-y-110 blur-2xl` }
fill fill
></Image> ></Image>
</div> } </div> }
<CardBody className={ `overflow-visible` }> <CardBody className={ `overflow-visible` }>
<div className={ `flex flex-row justify-between` }> <div className={ `flex flex-row justify-between` }>
<div className={ `mx-2 ${ header_img !== '' ? '-mt-8' : 'mt-2' }` }> <div className={ `mx-2 ${ header_img !== '' ? '-mt-6' : 'mt-2' }` }>
<Chip className={ `mb-2 z-30` } radius={ `sm` } color={ `secondary` } <Chip className={ `mb-2 z-30 text-lg` } radius={ `sm` } size={ 'lg' } color={ `secondary` }
variant={ `solid` }>{ category }</Chip> variant={ `solid` }>{ category }</Chip>
<p className={ `text-4xl font-bold` }> { title }</p> <p className={ `text-4xl font-bold` }> { title }</p>
<p className={ `text-xl` }> { sub_title }</p> <p className={ `text-xl` }> { sub_title }</p>
@@ -54,13 +58,7 @@ function BlogPostCard({ post }: Props) {
</div> </div>
</CardBody> </CardBody>
{/* <CardBody className={ `transition-all duration-300 hidden group-hover:block` }> <CardFooter className={ 'pt-0' }>
<div className={ `mx-2` }>
<p>{ summary }</p>
</div>
</CardBody>*/ }
<CardFooter>
<div className={ `flex flex-row flex-wrap justify-between w-full` }> <div className={ `flex flex-row flex-wrap justify-between w-full` }>
<div> <div>
<ul className={ `flex flex-row flex-wrap` }> <ul className={ `flex flex-row flex-wrap` }>
+3 -18
View File
@@ -1,8 +1,9 @@
'use client'; 'use client';
import React, { useEffect, useState } from 'react'; import React from 'react';
import { Pagination as NextUIPagination } from '@nextui-org/react'; import { Pagination as NextUIPagination } from '@nextui-org/react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { useScreenSizeTrigger } from '@/app/public/screenSizeTrigger';
const Pagination = ({ total }: { const Pagination = ({ total }: {
total: number, total: number,
@@ -12,23 +13,7 @@ const Pagination = ({ total }: {
const router = useRouter(); const router = useRouter();
const maxMobileWidth: number = 768; const isMobile = useScreenSizeTrigger('md');
const [ isMobile, setIsMobile ] = useState(false);
useEffect(() => {
const updateMobileWidth = () => {
if (window.innerWidth <= maxMobileWidth) {
setIsMobile(true);
} else {
setIsMobile(false);
}
};
updateMobileWidth();
window.addEventListener('resize', updateMobileWidth);
return () => {
window.removeEventListener('resize', updateMobileWidth);
};
}, []);
return ( return (
<div className={ 'w-full p-4 pt-6 flex justify-center' }> <div className={ 'w-full p-4 pt-6 flex justify-center' }>
+8 -3
View File
@@ -25,7 +25,14 @@ const Footer = (props: Props) => {
<div className={ `w-full` }> <div className={ `w-full` }>
<div className={ 'flex transition bg-zinc-300/80 dark:bg-gray-600/25' }> <div className={ 'flex transition bg-zinc-300/80 dark:bg-gray-600/25' }>
<div className={ `p-8 w-full` }> <div className={ `p-8 w-full overflow-hidden` }>
<div className={ 'relative' }>
<FaCreativeCommons
size={ '160px' }
className={ 'absolute right-0 top-0 -mt-[4.8rem] -mr-16 opacity-30 brightness-50 ' }
/>
</div>
<div className={ 'flex flex-col space-y-4 relative' }> <div className={ 'flex flex-col space-y-4 relative' }>
<div className={ 'z-[2]' }> <div className={ 'z-[2]' }>
<p className={ 'text-xl font-bold' }>{ post['meta']['title'] }</p> <p className={ 'text-xl font-bold' }>{ post['meta']['title'] }</p>
@@ -63,8 +70,6 @@ const Footer = (props: Props) => {
</Link> </Link>
</div> </div>
</div> </div>
<FaCreativeCommons size={ '80px' }
className={ 'absolute inset-y-0 right-0 opacity-50 brightness-75 z-0' } />
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -42,7 +42,7 @@ const Headers = (props: Props) => {
'mx-8 -mt-16 backdrop-blur transition bg-zinc-300/80 dark:bg-gray-600/25 rounded-2xl z-30' 'mx-8 -mt-16 backdrop-blur transition bg-zinc-300/80 dark:bg-gray-600/25 rounded-2xl z-30'
: 'mx-6 mt-6' : 'mx-6 mt-6'
}> }>
<div className={ `p-4` }> <div className={ header_img !== '' ? `p-4` : 'p-4 pt-2' }>
<Chip className={ `mb-2` } radius={ `sm` } color={ `secondary` } <Chip className={ `mb-2` } radius={ `sm` } color={ `secondary` }
variant={ `solid` }>{ category }</Chip> variant={ `solid` }>{ category }</Chip>
<p className={ `text-4xl font-bold` }> { title }</p> <p className={ `text-4xl font-bold` }> { title }</p>
+3 -3
View File
@@ -7,8 +7,8 @@ import Footer from '@/components/Footer';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Lumither\'s site', title: 'Lumitherの酒馆',
description: 'a small corner on internet' description: '一间位于时间夹缝中的旅馆,静候下一位旅者的到来...'
}; };
export default function RootLayout({ export default function RootLayout({
@@ -19,7 +19,7 @@ export default function RootLayout({
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className={ `transition duration-300 dark:bg-[#282830] min-h-screen` }> <body className={ `transition duration-300 dark:bg-[#282830] min-h-screen` }>
<NextThemesProvider attribute="class" defaultTheme="dark"> <NextThemesProvider attribute="class" defaultTheme="system">
<div className={ `justify-center flex flex-row w-full max-w-full` }> <div className={ `justify-center flex flex-row w-full max-w-full` }>
<Navbar /> <Navbar />
<div className={ `flex flex-col flex-1 max-w-[1024px] min-w-0` }> <div className={ `flex flex-col flex-1 max-w-[1024px] min-w-0` }>
+35
View File
@@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';
const screenSize = new Map<string, number>(
[
[ 'sm', 640 ],
[ 'md', 768 ],
[ 'lg', 1024 ],
[ 'xl', 1280 ],
[ '2xl', 2536 ]
]
);
export const useScreenSizeTrigger = (size: number | string) => {
const criticalSize = (typeof size === 'string') ? (screenSize.get(size) ?? 768) : size;
const [ isCritical, setIsCritical ] = useState(false);
useEffect(() => {
const updateMobileWidth = () => {
if (window.innerWidth < criticalSize) {
setIsCritical(true);
} else {
setIsCritical(false);
}
};
updateMobileWidth();
window.addEventListener('resize', updateMobileWidth);
return () => {
window.removeEventListener('resize', updateMobileWidth);
};
});
return isCritical;
};
+28 -43
View File
@@ -2,11 +2,12 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button } from '@nextui-org/react'; import { Button } from '@nextui-org/react';
import NextImage from 'next/image'; import Image from 'next/image';
import { IoHome, IoInformationCircle, IoLink, IoLogoGithub, IoMail, IoPencil, IoSearch } from 'react-icons/io5'; import { IoHome, IoInformationCircle, IoLink, IoLogoGithub, IoMail, IoPencil, IoSearch } from 'react-icons/io5';
import Link from 'next/link'; import Link from 'next/link';
import ThemeSwitcher from '@/components/ThemeSwitcher'; import ThemeSwitcher from '@/components/ThemeSwitcher';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { useScreenSizeTrigger } from '@/app/public/screenSizeTrigger';
const entries: { display_name: string; href: string; icon: React.ReactNode }[] = [ const entries: { display_name: string; href: string; icon: React.ReactNode }[] = [
{ {
@@ -50,17 +51,17 @@ const connections: { label: string, href: string, icon: React.ReactNode }[] = [
]; ];
function Navbar() { function Navbar() {
const [ scrolled, setScrolled ] = useState(false); // const [ scrolled, setScrolled ] = useState(false);
useEffect(() => { // useEffect(() => {
const handleScroll = () => { // const handleScroll = () => {
const isScrolled = window.scrollY > 0; // const isScrolled = window.scrollY > 0;
setScrolled(isScrolled); // setScrolled(isScrolled);
}; // };
window.addEventListener('scroll', handleScroll); // window.addEventListener('scroll', handleScroll);
return () => { // return () => {
window.removeEventListener('scroll', handleScroll); // window.removeEventListener('scroll', handleScroll);
}; // };
}, []); // }, []);
const path = usePathname(); const path = usePathname();
@@ -74,41 +75,24 @@ function Navbar() {
} }
}, [ path ]); }, [ path ]);
const isMobile = useScreenSizeTrigger('lg');
const maxMobileWidth: number = 1024;
const [ isMobile, setIsMobile ] = useState(false);
useEffect(() => {
const updateMobileWidth = () => {
if (window.innerWidth <= maxMobileWidth) {
setIsMobile(true);
} else {
setIsMobile(false);
}
};
updateMobileWidth();
window.addEventListener('resize', updateMobileWidth);
return () => {
window.removeEventListener('resize', updateMobileWidth);
};
}, []);
return ( return (
<> <>
<div <div
className={ `flex flex-col min-w-[50px] lg:min-w-[256px] mr-0 lg:mr-2 ml-2 lg:ml-8 min-h-dvh pt-8 sticky` }> className={ `flex flex-col min-w-[50px] lg:min-w-[200px] ml-2 mr-0 lg:ml-8 lg:mr-6 min-h-screen pt-8 sticky` }>
<div className={ `fixed min-h-screen h-full` }> <div className={ `fixed min-h-screen h-full` }>
<div className={ `hidden lg:block` }> <div className={ `hidden lg:block` }>
<div className={ `flex justify-center items-center my-7` }> <div className={ `flex justify-center items-center my-7 h-[200px] w-[200px]` }>
<NextImage <div className={ 'relative w-full h-full' }>
src={ 'https://oss.lumither.com/blog/pictures/avatar.webp' } <Image
alt={ 'avatar' } src={ 'https://oss.lumither.com/blog/pictures/avatar.webp' }
width={ 200 } alt={ 'avatar' }
height={ 200 } fill
className={ 'rounded-full justify-self-center' } className={ 'rounded-full object-cover' }
priority={ true } loading={ 'eager' }
></NextImage> ></Image>
</div>
</div> </div>
<div> <div>
@@ -153,7 +137,8 @@ function Navbar() {
isIconOnly={ isMobile } isIconOnly={ isMobile }
className={ 'flex lg:justify-start w-fit lg:w-full' } className={ 'flex lg:justify-start w-fit lg:w-full' }
aria-label={ `navbar: ${ meta.display_name }` } aria-label={ `navbar: ${ meta.display_name }` }
href={ meta.href }> href={ meta.href }
>
{ meta.icon } { meta.icon }
<p className={ `font-bold hidden lg:block` }>{ meta.display_name }</p> <p className={ `font-bold hidden lg:block` }>{ meta.display_name }</p>
</Button> </Button>
@@ -170,7 +155,7 @@ function Navbar() {
</div> </div>
<div <div
className={ `absolute bottom-8 mb-8` } className={ `absolute bottom-16` }
> >
<ThemeSwitcher /> <ThemeSwitcher />
</div> </div>
+69 -18
View File
@@ -2,12 +2,34 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTheme } from 'next-themes'; import { useTheme } from 'next-themes';
import { Switch } from '@nextui-org/react'; import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from '@nextui-org/react';
import { MdOutlineDarkMode, MdOutlineLightMode } from 'react-icons/md'; import { MdOutlineDarkMode, MdOutlineDesktopWindows, MdOutlineLightMode } from 'react-icons/md';
import { useScreenSizeTrigger } from '@/app/public/screenSizeTrigger';
const switcherElements = new Map(
[
[ 'dark', {
'display_name': 'Dark Mode',
'icon': <MdOutlineDarkMode />
} ],
[ 'light', {
'display_name': 'Light Mode',
'icon': <MdOutlineLightMode />
} ],
[ 'system', {
'display_name': 'Follow System',
'icon': <MdOutlineDesktopWindows />
} ]
]
);
function ThemeSwitcher() { function ThemeSwitcher() {
const [ mounted, setMounted ] = useState(false); const [ mounted, setMounted ] = useState(false);
const { setTheme } = useTheme(); const { theme, setTheme } = useTheme();
const [ currTheme, setCurrTheme ] = useState(new Set([ theme as string ]));
const isMobile = useScreenSizeTrigger('lg');
useEffect(() => { useEffect(() => {
setMounted(true); setMounted(true);
@@ -19,22 +41,51 @@ function ThemeSwitcher() {
return ( return (
<div className={ `` }> <div className={ `` }>
<div className={ `flex flex-row items-center` }> <Dropdown backdrop={ 'blur' }>
<Switch <DropdownTrigger>
color={ 'default' } <Button
aria-label={ `theme switch` } variant={ 'flat' }
startContent={ <MdOutlineLightMode /> } aria-label={ 'Theme Trigger' }
endContent={ <MdOutlineDarkMode /> } fullWidth
onValueChange={ (isSelected) => { isIconOnly={ isMobile }
if (isSelected) { >
setTheme('light'); { switcherElements.get(currTheme.values().next().value)?.icon }
} else { <p className={ 'hidden lg:block' }>
setTheme('dark'); { switcherElements.get(currTheme.values().next().value)?.display_name }
} </p>
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label={ 'Toggle Theme' }
variant={ 'flat' }
selectionMode={ 'single' }
selectedKeys={ currTheme }
onSelectionChange={ ({ anchorKey }) => {
setCurrTheme(new Set([ anchorKey as string ]));
setTheme(anchorKey as string);
} } } }
></Switch>
<p className={ `font-bold hidden lg:block` }>{ `Toggle Theme` }</p> >
</div> <DropdownItem
key={ 'light' }
startContent={ <MdOutlineLightMode /> }>
Light Mode
</DropdownItem>
<DropdownItem
key={ 'dark' }
startContent={ <MdOutlineDarkMode /> }
>
Dark Mode
</DropdownItem>
<DropdownItem
key={ 'system' }
startContent={ <MdOutlineDesktopWindows /> }
>
Follow System
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div> </div>
); );
} }