feat: complete header and footer components and dark/light mode switching function

This commit is contained in:
Guoqi Sun 2024-11-26 22:17:48 +08:00
parent 51e76a3b99
commit e5b8bd7ab0
16 changed files with 234 additions and 19 deletions

View file

@ -1,4 +1,8 @@
{
"recommendations": ["astro-build.astro-vscode"],
"recommendations": [
"astro-build.astro-vscode",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
],
"unwantedRecommendations": []
}

View file

@ -1,4 +1,9 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[astro]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "always"

View file

@ -17,6 +17,7 @@
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"astro": "^5.0.0-beta.10",
"lucide-react": "^0.461.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^3.4.15",

12
pnpm-lock.yaml generated
View file

@ -26,6 +26,9 @@ importers:
astro:
specifier: ^5.0.0-beta.10
version: 5.0.0-beta.10(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1)
lucide-react:
specifier: ^0.461.0
version: 0.461.0(react@18.3.1)
react:
specifier: ^18.3.1
version: 18.3.1
@ -1991,6 +1994,11 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react@0.461.0:
resolution: {integrity: sha512-Scpw3D/dV1bgVRC5Kh774RCm99z0iZpPv75M6kg7QL1lLvkQ1rmI1Sjjic1aGp1ULBwd7FokV6ry0g+d6pMB+w==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
magic-string@0.30.14:
resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==}
@ -5297,6 +5305,10 @@ snapshots:
dependencies:
yallist: 3.1.1
lucide-react@0.461.0(react@18.3.1):
dependencies:
react: 18.3.1
magic-string@0.30.14:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0

View file

@ -0,0 +1,22 @@
---
---
<div class="my-10 flex flex-col md:flex-row">
<p>
&copy; {new Date().getFullYear()}
</p>
<p class="mx-1 hidden md:block">|</p>
<p class="mr-1">Designed By</p>
<a href="https://sunguoqi.com" class="text-blue-500 hover:underline">
Guoqi Sun
</a>
<p class="mx-1 hidden md:block">|</p>
<p class="mr-1">Powered By</p>
<a
href="https://github.com/sun0225SUN/astro-air"
class="text-blue-500 hover:underline"
>
Astro Air
</a>
</div>

View file

@ -0,0 +1,17 @@
---
import { Rss } from "lucide-react"
import ThemeToggle from "~/components/astro/theme-toggle.astro"
import { site } from "~/config"
---
<header class="flex h-20 w-full items-center justify-between">
<a href="/">
<div class="text-xl font-semibold">{site.name}</div>
</a>
<div class="flex items-center gap-4">
<a href="/atom.xml" target="_blank">
<Rss />
</a>
<ThemeToggle />
</div>
</header>

View file

@ -0,0 +1,57 @@
---
import { Moon, Sun } from "lucide-react"
---
<button id="themeToggle">
<Sun className="dark:hidden" />
<Moon className="hidden dark:block" />
</button>
<script>
// check current theme
const theme = (() => {
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
return localStorage.getItem("theme")
}
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark"
}
return "light"
})()
// set theme
if (theme === "light") {
document.documentElement.classList.remove("dark")
} else {
document.documentElement.classList.add("dark")
}
// save theme
window.localStorage.setItem("theme", theme!)
// update theme
const updateTheme = () => {
const isDark = document.documentElement.classList.contains("dark")
localStorage.setItem("theme", isDark ? "dark" : "light")
}
// toggle theme
const handleToggleClick = () => {
const element = document.documentElement
if (!document.startViewTransition) {
element.classList.toggle("dark")
updateTheme()
return
}
document.startViewTransition(() => {
element.classList.toggle("dark")
updateTheme()
})
}
document
.getElementById("themeToggle")!
.addEventListener("click", handleToggleClick)
</script>

28
src/config.ts Normal file
View file

@ -0,0 +1,28 @@
export const site = {
name: "小孙同学",
favicon: "/avatar.png",
title: "小孙同学",
url: "https://blog.sunguoqi.com",
slogan: "一个浪漫的理性主义者",
description:
"🖥️ 前端小学生(React)|📸 摄影爱好者(Nikon Zfc)|🛸 旅行探索家(体验派)|🚴 骑行蹭风选手( Java 鱼雷 6-top )|🍎 科技产品发烧友(苹果&小米)<br/><br/> ✨ 路虽远行则将至,事虽难做则必成。热爱可抵岁月漫长。山高路远,独善其身,看世界,也找自己。希望能成为一个有趣的人~",
}
export const tabs = [
// {
// label: "归档",
// link: "./archive",
// },
// {
// label: "标签",
// link: "./tags",
// },
{
label: "影集",
link: "https://camlife.cn",
},
{
label: "关于",
link: "https://sunguoqi.com",
},
]

View file

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>Astro Basics</title>
</head>
<body>
<slot />
</body>
</html>

42
src/layouts/base.astro Normal file
View file

@ -0,0 +1,42 @@
---
import "~/styles/globals.css"
import { site } from "~/config"
---
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href={site.favicon} />
<meta name="generator" content={Astro.generator} />
<title>{site.title} - {site.slogan}</title>
<meta name="description" content={site.description} />
<script is:inline>
// set theme before page load
const theme = (() => {
if (
typeof localStorage !== "undefined" &&
localStorage.getItem("theme")
) {
return localStorage.getItem("theme")
}
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark"
}
return "light"
})()
if (theme === "dark") {
document.documentElement.classList.add("dark")
}
</script>
</head>
<body>
<div
class="flex min-h-screen w-full justify-center dark:bg-[#121212] dark:text-white"
>
<slot />
</div>
</body>
</html>

9
src/layouts/home.astro Normal file
View file

@ -0,0 +1,9 @@
---
import BaseLayout from "./base.astro"
---
<BaseLayout>
<main class="max-auto w-full max-w-3xl">
<slot />
</main>
</BaseLayout>

View file

@ -1,8 +1,12 @@
---
import Layout from "../layouts/layout.astro"
import { Test } from "../components/test"
import Footer from "~/components/astro/footer.astro"
import Header from "~/components/astro/header.astro"
import { Test } from "../components/react/test"
import HomeLayout from "../layouts/home.astro"
---
<Layout>
<HomeLayout>
<Header />
<Test />
</Layout>
<Footer />
</HomeLayout>

27
src/styles/globals.css Normal file
View file

@ -0,0 +1,27 @@
/* Theme toggle effect */
/* https://theme-toggle.rdsx.dev/ */
/* https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API */
::view-transition-group(root) {
animation-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
}
::view-transition-new(root) {
mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><filter id="blur"><feGaussianBlur stdDeviation="2"/></filter></defs><circle cx="0" cy="0" r="18" fill="white" filter="url(%23blur)"/></svg>')
top left / 0 no-repeat;
mask-origin: content-box;
animation: scale 1s;
transform-origin: top left;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 1s;
transform-origin: top left;
z-index: -1;
}
@keyframes scale {
to {
mask-size: 350vmax;
}
}

View file

@ -1,6 +1,7 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
darkMode: ["class"],
theme: {
extend: {},
},

View file

@ -5,7 +5,6 @@
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
/* Path Aliases */
"baseUrl": ".",
"paths": {