mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 19:51:07 +02:00
feat: add theme toggle for light/dark modes
This commit is contained in:
parent
d148649454
commit
d599b3e26d
9 changed files with 125 additions and 28 deletions
1
public/image/moon.svg
Normal file
1
public/image/moon.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g fill="#3d3d3d"><path d="m11.64 12.36c-3.6-3.6-4.14-8.89-2.8-10.66-2.39 1.75-1.88 7.86 1.86 11.6s9.86 4.28 11.6 1.86c-1.77 1.34-7.06.8-10.66-2.8"/><path d="m5.22 4.01a10 10 0 0 1 3.54-2.28c-2.02.47-3.24 1.19-4.54 2.49-4.3 4.3-4.3 11.26 0 15.55 4.29 4.29 11.26 4.3 15.55 0 1.3-1.3 2.27-2.68 2.51-4.6a9.8 9.8 0 0 1 -2.3 3.6c-3.96 3.96-10.48 3.86-14.56-.21-4.08-4.08-4.18-10.6-.21-14.56z"/></g></svg>
|
After Width: | Height: | Size: 459 B |
1
public/image/sun.svg
Normal file
1
public/image/sun.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m4.22 4.22c-4.3 4.3-4.3 11.26 0 15.56s11.26 4.3 15.56 0 4.3-11.26 0-15.56-11.26-4.3-15.56 0m15.17 15.17c-3.63 3.63-9.88 3.26-13.96-.82s-4.45-10.33-.82-13.96 9.88-3.26 13.96.82 4.45 10.33.82 13.96" fill="#3d3d3d"/></svg>
|
After Width: | Height: | Size: 288 B |
|
@ -9,7 +9,7 @@ interface Props {
|
||||||
|
|
||||||
const { postTitle, postDescription, postImage } = Astro.props
|
const { postTitle, postDescription, postImage } = Astro.props
|
||||||
const { title, subtitle, description, author, url, favicon } = themeConfig.site
|
const { title, subtitle, description, author, url, favicon } = themeConfig.site
|
||||||
const { light: { backgroundTop: lightMode }, dark: { backgroundTop: darkMode } } = themeConfig.color
|
const { light: { background: lightMode }, dark: { background: darkMode } } = themeConfig.color
|
||||||
const { locale, moreLocale } = themeConfig.global
|
const { locale, moreLocale } = themeConfig.global
|
||||||
const { verification = {}, twitterID = '', facebookID = '', facebookLink = '', googleAnalyticsID = '', umamiAnalyticsID = '', siteScreenshot = '' } = themeConfig.seo ?? {}
|
const { verification = {}, twitterID = '', facebookID = '', facebookLink = '', googleAnalyticsID = '', umamiAnalyticsID = '', siteScreenshot = '' } = themeConfig.seo ?? {}
|
||||||
const { google = '', bing = '', yandex = '', baidu = '' } = verification
|
const { google = '', bing = '', yandex = '', baidu = '' } = verification
|
||||||
|
@ -23,7 +23,7 @@ const { cdn, commentURL = '', imageHostURL = '', customGoogleAnalyticsURL = '',
|
||||||
{favicon.toLowerCase().endsWith('.svg') && <link rel="icon" type="image/svg+xml" href={favicon} />}
|
{favicon.toLowerCase().endsWith('.svg') && <link rel="icon" type="image/svg+xml" href={favicon} />}
|
||||||
{favicon.toLowerCase().endsWith('.png') && <link rel="icon" type="image/png" href={favicon} />}
|
{favicon.toLowerCase().endsWith('.png') && <link rel="icon" type="image/png" href={favicon} />}
|
||||||
|
|
||||||
<title>{postTitle ? `${postTitle} - ${title}` : `${title} - ${subtitle}`}</title>
|
<title>{postTitle ? `${postTitle} | ${title}` : `${title} - ${subtitle}`}</title>
|
||||||
<meta name="description" content={postDescription || description} />
|
<meta name="description" content={postDescription || description} />
|
||||||
<meta name="author" content={author} />
|
<meta name="author" content={author} />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
62
src/components/ThemeToggle.astro
Normal file
62
src/components/ThemeToggle.astro
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<button
|
||||||
|
aria-pressed="false"
|
||||||
|
aria-label="Theme Toggle Button"
|
||||||
|
class="fixed right-10 top-10 z-999 aspect-square w-8 text-secondary"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="moon-icon" fill="currentColor">
|
||||||
|
<g>
|
||||||
|
<path d="m11.64 12.36c-3.6-3.6-4.14-8.89-2.8-10.66-2.39 1.75-1.88 7.86 1.86 11.6s9.86 4.28 11.6 1.86c-1.77 1.34-7.06.8-10.66-2.8" />
|
||||||
|
<path d="m5.22 4.01a10 10 0 0 1 3.54-2.28c-2.02.47-3.24 1.19-4.54 2.49-4.3 4.3-4.3 11.26 0 15.55 4.29 4.29 11.26 4.3 15.55 0 1.3-1.3 2.27-2.68 2.51-4.6a9.8 9.8 0 0 1 -2.3 3.6c-3.96 3.96-10.48 3.86-14.56-.21-4.08-4.08-4.18-10.6-.21-14.56z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="sun-icon" fill="currentColor">
|
||||||
|
<path d="m4.22 4.22c-4.3 4.3-4.3 11.26 0 15.56s11.26 4.3 15.56 0 4.3-11.26 0-15.56-11.26-4.3-15.56 0m15.17 15.17c-3.63 3.63-9.88 3.26-13.96-.82s-4.45-10.33-.82-13.96 9.88-3.26 13.96.82 4.45 10.33.82 13.96" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
declare global {
|
||||||
|
interface Document {
|
||||||
|
startViewTransition: (callback: () => void) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOGGLE = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
||||||
|
if (!TOGGLE)
|
||||||
|
throw new Error('Theme toggle button not found')
|
||||||
|
|
||||||
|
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
const savedTheme = localStorage.getItem('theme')
|
||||||
|
const initialTheme = savedTheme || (defaultDark ? 'dark' : 'light')
|
||||||
|
|
||||||
|
TOGGLE.setAttribute('aria-pressed', initialTheme === 'dark' ? 'true' : 'false')
|
||||||
|
document.documentElement.classList.toggle('dark', initialTheme === 'dark')
|
||||||
|
|
||||||
|
function SWITCH() {
|
||||||
|
const isDark = !TOGGLE.matches('[aria-pressed=true]')
|
||||||
|
TOGGLE.setAttribute('aria-pressed', String(isDark))
|
||||||
|
const newTheme = isDark ? 'dark' : 'light'
|
||||||
|
document.documentElement.classList.toggle('dark', isDark)
|
||||||
|
localStorage.setItem('theme', newTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TOGGLE_THEME() {
|
||||||
|
if (!document.startViewTransition) {
|
||||||
|
SWITCH()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
document.startViewTransition(SWITCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
TOGGLE.addEventListener('click', TOGGLE_THEME)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
[aria-pressed='true'] .moon-icon,
|
||||||
|
.sun-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
[aria-pressed='true'] .sun-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,27 +3,27 @@ import type { ThemeConfig } from '@/types'
|
||||||
export const themeConfig: ThemeConfig = {
|
export const themeConfig: ThemeConfig = {
|
||||||
// SITE INFORMATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START
|
// SITE INFORMATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START
|
||||||
site: {
|
site: {
|
||||||
title: 'retypeset',
|
title: 'Retypeset',
|
||||||
subtitle: '再现版式之美',
|
subtitle: '再现版式之美',
|
||||||
description: '一个优美的博客主题',
|
description: '一个优美的博客主题',
|
||||||
author: 'radishzz',
|
author: 'radishzz',
|
||||||
url: 'https://retypeset.netlify.app',
|
url: 'https://retypeset.netlify.app',
|
||||||
favicon: '/image/logo.svg', // or https://image.example.com/logo.svg support only webp, svg, png
|
favicon: '/image/logo.svg', // or https://image.example.com/logo.svg, support only webp, svg, png
|
||||||
},
|
},
|
||||||
// SITE INFORMATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
// SITE INFORMATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
||||||
|
|
||||||
// COLOR SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START
|
// COLOR SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START
|
||||||
color: {
|
color: {
|
||||||
mode: 'light',
|
mode: 'dark', // light, dark, DEFAULT to match system theme
|
||||||
light: {
|
light: {
|
||||||
text: '#232323',
|
primary: '#505050', // title text color in light mode
|
||||||
backgroundTop: '#f8f8f8',
|
secondary: '#17191A', // posts text color in light mode
|
||||||
backgroundEnd: '#FDE9EB',
|
background: '#F7EEEC', // background color in light mode
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
text: '#000000',
|
primary: '#A0A09F', // title text color in dark mode
|
||||||
backgroundTop: '#ffffff',
|
secondary: '#BEBEBE', // posts text color in dark mode
|
||||||
backgroundEnd: '#000000',
|
background: '#161616', // background color in dark mode
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// COLOR SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
// COLOR SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
---
|
---
|
||||||
import Head from '@/components/Head.astro'
|
import Head from '@/components/Head.astro'
|
||||||
|
import ThemeToggle from '@/components/ThemeToggle.astro'
|
||||||
import themeConfig from '@/config'
|
import themeConfig from '@/config'
|
||||||
import '@/styles/global.css'
|
import '@/styles/global.css'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
postTitle?: string
|
||||||
|
postDescription?: string
|
||||||
|
postImage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const { postTitle, postDescription, postImage } = Astro.props
|
||||||
|
|
||||||
const fontStyle = `font-${themeConfig.global.font}`
|
const fontStyle = `font-${themeConfig.global.font}`
|
||||||
|
const colorMode = themeConfig.color.mode
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang={Astro.currentLocale || 'en-US'} class={fontStyle}>
|
<html lang={Astro.currentLocale || 'en-US'} class={`${fontStyle} ${colorMode}`}>
|
||||||
<head>
|
<head>
|
||||||
<Head />
|
<Head {postTitle} {postDescription} {postImage} />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<ThemeToggle />
|
||||||
<main class="lg:p-9rem_8rem mx-a h-screen max-w-123rem min-w-32rem gap-x-[clamp(11rem,8.05%+8.102rem,18rem)] px-[clamp(2.4rem,9.94%-1.1784rem,9rem)] py-[clamp(4.4rem,5.42%+2.4488rem,8rem)] grid-[15rem_1.5rem_5.4rem_1fr_4.8rem_5.25rem]-[1fr_min(22rem,27%)]">
|
<main class="lg:p-9rem_8rem mx-a h-screen max-w-123rem min-w-32rem gap-x-[clamp(11rem,8.05%+8.102rem,18rem)] px-[clamp(2.4rem,9.94%-1.1784rem,9rem)] py-[clamp(4.4rem,5.42%+2.4488rem,8rem)] grid-[15rem_1.5rem_5.4rem_1fr_4.8rem_5.25rem]-[1fr_min(22rem,27%)]">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -14,7 +14,11 @@ const { post } = Astro.props
|
||||||
const { Content } = await post.render()
|
const { Content } = await post.render()
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout
|
||||||
|
postTitle={post.data.title}
|
||||||
|
postDescription={post.data.description}
|
||||||
|
postImage={post.data.image}
|
||||||
|
>
|
||||||
<article>
|
<article>
|
||||||
<h1>{post.data.title}</h1>
|
<h1>{post.data.title}</h1>
|
||||||
<time>{post.data.published.toISOString().split('T')[0]}</time>
|
<time>{post.data.published.toISOString().split('T')[0]}</time>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
:root {
|
:root {
|
||||||
--uno-colors-text: theme('colors.text');
|
--uno-colors-primary: theme('colors.primary');
|
||||||
--uno-colors-backgroundTop: theme('colors.backgroundTop');
|
--uno-colors-secondary: theme('colors.secondary');
|
||||||
--uno-colors-backgroundEnd: theme('colors.backgroundEnd');
|
--uno-colors-background: theme('colors.background');
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
--at-apply: 'antialiased scroll-smooth text-62.5%';
|
--at-apply: 'antialiased scroll-smooth text-62.5%';
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
--at-apply: 'min-h-screen min-h-dvh bg-gradient-to-b from-backgroundTop to-backgroundEnd bg-fixed overscroll-none text-1.6rem c-text';
|
--at-apply: 'bg-background min-h-dvh c-primary text-1.6rem';
|
||||||
}
|
}
|
||||||
h1, h2, h3 {
|
h1, h2, h3 {
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
@ -30,6 +30,24 @@ h5 {
|
||||||
h6 {
|
h6 {
|
||||||
--at-apply: 'text-[1.6rem]';
|
--at-apply: 'text-[1.6rem]';
|
||||||
}
|
}
|
||||||
/* :where(p) {
|
/* Horizontal reveal animation on theme toggle */
|
||||||
--at-apply: 'text-text/85';
|
@keyframes reveal {
|
||||||
} */
|
from {
|
||||||
|
clip-path: inset(var(--from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html.dark {
|
||||||
|
--from: 0 0 100% 0;
|
||||||
|
}
|
||||||
|
html:not(.dark) {
|
||||||
|
--from: 100% 0 0 0;
|
||||||
|
}
|
||||||
|
::view-transition-new(root) {
|
||||||
|
animation: reveal 1s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
clip-path: inset(0 0 0 0);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
::view-transition-old(root) {
|
||||||
|
z-index: -1;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
14
src/types/index.d.ts
vendored
14
src/types/index.d.ts
vendored
|
@ -14,21 +14,21 @@ export interface ThemeConfig {
|
||||||
color: {
|
color: {
|
||||||
mode: 'light' | 'dark'
|
mode: 'light' | 'dark'
|
||||||
light: {
|
light: {
|
||||||
text: string
|
primary: string
|
||||||
backgroundTop: string
|
secondary: string
|
||||||
backgroundEnd: string
|
background: string
|
||||||
}
|
}
|
||||||
dark: {
|
dark: {
|
||||||
text: string
|
primary: string
|
||||||
backgroundTop: string
|
secondary: string
|
||||||
backgroundEnd: string
|
background: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
global: {
|
global: {
|
||||||
locale: typeof langPath[number]
|
locale: typeof langPath[number]
|
||||||
moreLocale: typeof langPath[number][]
|
moreLocale: typeof langPath[number][]
|
||||||
font: string
|
font: 'sans' | 'serif' | 'italic'
|
||||||
}
|
}
|
||||||
|
|
||||||
comment?: {
|
comment?: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue