mirror of
https://github.com/saicaca/fuwari.git
synced 2026-01-11 23:02:53 +01:00
feat: option to follow the system-wide light/dark mode (#71)
This commit is contained in:
@@ -195,8 +195,8 @@ color_set({
|
||||
.float-panel {
|
||||
@apply top-[5.25rem] rounded-[var(--radius-large)] overflow-hidden bg-[var(--float-panel-bg)] transition shadow-xl dark:shadow-none
|
||||
}
|
||||
.float-panel.closed {
|
||||
@apply top-[4.75rem] opacity-0 pointer-events-none
|
||||
.float-panel-closed {
|
||||
@apply -translate-y-1 opacity-0 pointer-events-none
|
||||
}
|
||||
.search-panel mark {
|
||||
@apply bg-transparent text-[var(--primary)]
|
||||
|
||||
89
src/components/LightDarkSwitch.svelte
Normal file
89
src/components/LightDarkSwitch.svelte
Normal file
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
|
||||
import Icon from "@iconify/svelte"
|
||||
import {i18n} from '@i18n/translation'
|
||||
import I18nKey from '@i18n/i18nKey'
|
||||
import {LIGHT_MODE, DARK_MODE, AUTO_MODE, setTheme, getStoredTheme} from '../utils/setting-utils.ts'
|
||||
import {onMount} from "svelte";
|
||||
|
||||
const seq = [LIGHT_MODE, DARK_MODE, AUTO_MODE]
|
||||
let mode = AUTO_MODE
|
||||
|
||||
onMount(() => {
|
||||
mode = getStoredTheme()
|
||||
})
|
||||
|
||||
function switchScheme(newMode: string) {
|
||||
mode = newMode
|
||||
setTheme(newMode)
|
||||
}
|
||||
|
||||
function toggleScheme() {
|
||||
let i = 0
|
||||
for (; i < seq.length; i++) {
|
||||
if (seq[i] === mode) {
|
||||
break
|
||||
}
|
||||
}
|
||||
switchScheme(seq[(i + 1) % seq.length])
|
||||
}
|
||||
|
||||
function showPanel() {
|
||||
const panel = document.querySelector('#light-dark-panel')
|
||||
panel.classList.remove('float-panel-closed')
|
||||
}
|
||||
|
||||
function hidePanel() {
|
||||
const panel = document.querySelector('#light-dark-panel')
|
||||
panel.classList.add('float-panel-closed')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- z-50 make the panel higher than other float panels -->
|
||||
<div class="relative z-50" role="menu" tabindex="-1" on:mouseleave={hidePanel}>
|
||||
<button aria-label="Light/Dark Mode" role="menuitem" class="relative btn-plain h-11 w-11 rounded-lg active:scale-90" id="scheme-switch" on:click={toggleScheme} on:mouseenter={showPanel}>
|
||||
<div class="absolute" class:opacity-0={mode !== LIGHT_MODE}>
|
||||
<Icon icon="material-symbols:wb-sunny-outline-rounded" class="text-[1.25rem]"></Icon>
|
||||
</div>
|
||||
<div class="absolute" class:opacity-0={mode !== DARK_MODE}>
|
||||
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem]"></Icon>
|
||||
</div>
|
||||
<div class="absolute" class:opacity-0={mode !== AUTO_MODE}>
|
||||
<Icon icon="material-symbols:radio-button-partial-outline" class="text-[1.25rem]"></Icon>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div id="light-dark-panel" class="hidden lg:block absolute transition float-panel-closed top-11 -right-2 pt-5" >
|
||||
<div class="card-base float-panel p-2">
|
||||
<button class="flex transition whitespace-nowrap items-center justify-start w-full btn-plain h-9 rounded-lg px-3 font-medium active:scale-95 mb-0.5"
|
||||
class:current-setting={mode === LIGHT_MODE}
|
||||
on:click={() => switchScheme(LIGHT_MODE)}
|
||||
>
|
||||
<Icon icon="material-symbols:wb-sunny-outline-rounded" class="text-[1.25rem] mr-3"></Icon>
|
||||
{i18n(I18nKey.lightMode)}
|
||||
</button>
|
||||
<button class="flex transition whitespace-nowrap items-center justify-start w-full btn-plain h-9 rounded-lg px-3 font-medium active:scale-95 mb-0.5"
|
||||
class:current-setting={mode === DARK_MODE}
|
||||
on:click={() => switchScheme(DARK_MODE)}
|
||||
>
|
||||
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem] mr-3"></Icon>
|
||||
{i18n(I18nKey.darkMode)}
|
||||
</button>
|
||||
<button class="flex transition whitespace-nowrap items-center justify-start w-full btn-plain h-9 rounded-lg px-3 font-medium active:scale-95"
|
||||
class:current-setting={mode === AUTO_MODE}
|
||||
on:click={() => switchScheme(AUTO_MODE)}
|
||||
>
|
||||
<Icon icon="material-symbols:radio-button-partial-outline" class="text-[1.25rem] mr-3"></Icon>
|
||||
{i18n(I18nKey.systemMode)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="css">
|
||||
.current-setting {
|
||||
background: var(--btn-plain-bg-hover);
|
||||
color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
@@ -6,6 +6,7 @@ import {navBarConfig, siteConfig} from "../config";
|
||||
import NavMenuPanel from "./widget/NavMenuPanel.astro";
|
||||
import Search from "./Search.svelte";
|
||||
import {LinkPresets} from "../constants/link-presets";
|
||||
import LightDarkSwitch from "./LightDarkSwitch.svelte";
|
||||
const className = Astro.props.class;
|
||||
|
||||
let links: NavBarLink[] = navBarConfig.links.map((item: NavBarLink | LinkPreset): NavBarLink => {
|
||||
@@ -48,10 +49,7 @@ let links: NavBarLink[] = navBarConfig.links.map((item: NavBarLink | LinkPreset)
|
||||
<button aria-label="Display Settings" class="btn-plain h-11 w-11 rounded-lg active:scale-90" id="display-settings-switch">
|
||||
<Icon name="material-symbols:palette-outline" size={"1.25rem"}></Icon>
|
||||
</button>
|
||||
<button aria-label="Light/Dark Mode" class="btn-plain h-11 w-11 rounded-lg active:scale-90" id="scheme-switch">
|
||||
<Icon name="material-symbols:wb-sunny-outline-rounded" size={"1.25rem"} class="absolute opacity-[var(--display-light-icon)]"></Icon>
|
||||
<Icon name="material-symbols:dark-mode-outline-rounded" size={"1.25rem"} class="absolute opacity-[var(--display-dark-icon)]"></Icon>
|
||||
</button>
|
||||
<LightDarkSwitch client:load></LightDarkSwitch>
|
||||
<button aria-label="Menu" name="Nav Menu" class="btn-plain w-11 h-11 rounded-lg active:scale-90 md:hidden" id="nav-menu-switch">
|
||||
<Icon name="material-symbols:menu-rounded" size={"1.25rem"}></Icon>
|
||||
</button>
|
||||
@@ -86,13 +84,13 @@ function loadButtonScript() {
|
||||
let settingBtn = document.getElementById("display-settings-switch");
|
||||
settingBtn.addEventListener("click", function () {
|
||||
let settingPanel = document.getElementById("display-setting");
|
||||
settingPanel.classList.toggle("closed");
|
||||
settingPanel.classList.toggle("float-panel-closed");
|
||||
});
|
||||
|
||||
let menuBtn = document.getElementById("nav-menu-switch");
|
||||
menuBtn.addEventListener("click", function () {
|
||||
let menuPanel = document.getElementById("nav-menu-panel");
|
||||
menuPanel.classList.toggle("closed");
|
||||
menuPanel.classList.toggle("float-panel-closed");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ onMount(() => {
|
||||
return
|
||||
|
||||
if (!keyword && isDesktop) {
|
||||
panel.classList.add("closed")
|
||||
panel.classList.add("float-panel-closed")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ onMount(() => {
|
||||
}
|
||||
|
||||
if (!arr.length && isDesktop) {
|
||||
panel.classList.add("closed")
|
||||
panel.classList.add("float-panel-closed")
|
||||
return
|
||||
}
|
||||
|
||||
if (isDesktop) {
|
||||
panel.classList.remove("closed")
|
||||
panel.classList.remove("float-panel-closed")
|
||||
}
|
||||
result = arr
|
||||
}
|
||||
@@ -56,7 +56,7 @@ onMount(() => {
|
||||
|
||||
const togglePanel = () => {
|
||||
let panel = document.getElementById('search-panel')
|
||||
panel?.classList.toggle("closed")
|
||||
panel?.classList.toggle("float-panel-closed")
|
||||
}
|
||||
|
||||
$: search(keywordDesktop, true)
|
||||
@@ -82,7 +82,7 @@ $: search(keywordMobile, false)
|
||||
</button>
|
||||
|
||||
<!-- search panel -->
|
||||
<div id="search-panel" class="float-panel closed search-panel absolute md:w-[30rem]
|
||||
<div id="search-panel" class="float-panel float-panel-closed search-panel absolute md:w-[30rem]
|
||||
top-20 left-4 md:left-[unset] right-4 shadow-2xl rounded-2xl p-2">
|
||||
|
||||
<!-- search bar inside panel for phone/tablet -->
|
||||
|
||||
@@ -15,7 +15,7 @@ $: if (hue || hue === 0) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="display-setting" class="float-panel closed absolute transition-all w-80 fixed right-4 px-4 py-4">
|
||||
<div id="display-setting" class="float-panel float-panel-closed absolute transition-all w-80 right-4 px-4 py-4">
|
||||
<div class="flex flex-row gap-2 mb-3 items-center justify-between">
|
||||
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
|
||||
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
|
||||
const links = Astro.props.links;
|
||||
---
|
||||
<div id="nav-menu-panel" class:list={["float-panel closed absolute transition-all fixed right-4 px-2 py-2"]}>
|
||||
<div id="nav-menu-panel" class:list={["float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2"]}>
|
||||
{links.map((link) => (
|
||||
<a href={link.url} class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8
|
||||
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
|
||||
|
||||
@@ -22,6 +22,10 @@ enum I18nKey {
|
||||
|
||||
themeColor = 'themeColor',
|
||||
|
||||
lightMode = 'lightMode',
|
||||
darkMode = 'darkMode',
|
||||
systemMode = 'systemMode',
|
||||
|
||||
more = 'more',
|
||||
|
||||
author = 'author',
|
||||
|
||||
@@ -25,6 +25,10 @@ export const en: Translation = {
|
||||
|
||||
[Key.themeColor]: 'Theme Color',
|
||||
|
||||
[Key.lightMode]: 'Light',
|
||||
[Key.darkMode]: 'Dark',
|
||||
[Key.systemMode]: 'System',
|
||||
|
||||
[Key.more]: 'More',
|
||||
|
||||
[Key.author]: 'Author',
|
||||
|
||||
@@ -25,6 +25,10 @@ export const ja: Translation = {
|
||||
|
||||
[Key.themeColor]: 'テーマカラー',
|
||||
|
||||
[Key.lightMode]: 'ライト',
|
||||
[Key.darkMode]: 'ダーク',
|
||||
[Key.systemMode]: 'システム',
|
||||
|
||||
[Key.more]: 'もっと',
|
||||
|
||||
[Key.author]: '作者',
|
||||
|
||||
@@ -25,6 +25,10 @@ export const zh_CN: Translation = {
|
||||
|
||||
[Key.themeColor]: '主题色',
|
||||
|
||||
[Key.lightMode]: '亮色',
|
||||
[Key.darkMode]: '暗色',
|
||||
[Key.systemMode]: '跟随系统',
|
||||
|
||||
[Key.more]: '更多',
|
||||
|
||||
[Key.author]: '作者',
|
||||
|
||||
@@ -25,6 +25,10 @@ export const zh_TW: Translation = {
|
||||
|
||||
[Key.themeColor]: '主題色',
|
||||
|
||||
[Key.lightMode]: '亮色',
|
||||
[Key.darkMode]: '暗色',
|
||||
[Key.systemMode]: '跟隨系統',
|
||||
|
||||
[Key.more]: '更多',
|
||||
|
||||
[Key.author]: '作者',
|
||||
|
||||
@@ -69,7 +69,7 @@ const favicons: Favicon[] = siteConfig.favicon.length > 0 ? siteConfig.favicon :
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" isHome={isHomePage} pathname={testPathName} class="bg-[var(--page-bg)] transition text-[14px] md:text-[16px]">
|
||||
<html lang="en" isHome={isHomePage} pathname={testPathName} class="bg-[var(--page-bg)] transition dark text-[14px] md:text-[16px]">
|
||||
<head>
|
||||
|
||||
<title>{pageTitle}</title>
|
||||
@@ -148,7 +148,7 @@ import {
|
||||
SizeObserverPlugin,
|
||||
ClickScrollPlugin
|
||||
} from 'overlayscrollbars';
|
||||
import {getHue, setHue} from "../utils/setting-utils";
|
||||
import {getHue, getStoredTheme, setHue, setTheme} from "../utils/setting-utils";
|
||||
|
||||
/* Preload fonts */
|
||||
// (async function() {
|
||||
@@ -202,7 +202,7 @@ function setClickOutsideToClose(panel: string, ignores: string[]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
panelDom.classList.add("closed");
|
||||
panelDom.classList.add("float-panel-closed");
|
||||
});
|
||||
}
|
||||
setClickOutsideToClose("display-setting", ["display-setting", "display-settings-switch"])
|
||||
@@ -211,14 +211,8 @@ setClickOutsideToClose("search-panel", ["search-panel", "search-bar", "search-sw
|
||||
|
||||
|
||||
function loadTheme() {
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.theme = 'dark';
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.theme = 'light';
|
||||
}
|
||||
const theme = getStoredTheme()
|
||||
setTheme(theme)
|
||||
}
|
||||
|
||||
function loadHue() {
|
||||
|
||||
@@ -17,3 +17,28 @@ export function setHue(hue: number): void {
|
||||
}
|
||||
r.style.setProperty('--hue', hue)
|
||||
}
|
||||
|
||||
export const LIGHT_MODE = 'light', DARK_MODE = 'dark', AUTO_MODE = 'auto'
|
||||
|
||||
export function setTheme(theme: string): void {
|
||||
localStorage.setItem('theme', theme)
|
||||
switch (theme) {
|
||||
case LIGHT_MODE:
|
||||
document.documentElement.classList.remove('dark');
|
||||
break
|
||||
case DARK_MODE:
|
||||
document.documentElement.classList.add('dark');
|
||||
break
|
||||
case AUTO_MODE:
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
export function getStoredTheme(): string {
|
||||
return localStorage.getItem('theme') || AUTO_MODE
|
||||
}
|
||||
Reference in New Issue
Block a user