mirror of
https://github.com/Lumither/nocturne.git
synced 2026-10-17 10:26:21 +00:00
feat(nav, blog) new ThemeSwitcher, also several updates on ui
This commit is contained in:
8 files changed
+157
-98
No files matched your search
@@ -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` }>
|
||||||
|
|||||||
@@ -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' }>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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` }>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
};
|
||||||
@@ -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' }>
|
||||||
|
<Image
|
||||||
src={ 'https://oss.lumither.com/blog/pictures/avatar.webp' }
|
src={ 'https://oss.lumither.com/blog/pictures/avatar.webp' }
|
||||||
alt={ 'avatar' }
|
alt={ 'avatar' }
|
||||||
width={ 200 }
|
fill
|
||||||
height={ 200 }
|
className={ 'rounded-full object-cover' }
|
||||||
className={ 'rounded-full justify-self-center' }
|
loading={ 'eager' }
|
||||||
priority={ true }
|
></Image>
|
||||||
></NextImage>
|
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user