Skip to content

Commit

Permalink
Add light/dark mode toggle
Browse files Browse the repository at this point in the history
Co-authored-by: lyqht <[email protected]>
  • Loading branch information
ssrahul96 and lyqht authored Oct 29, 2024
1 parent fbd2372 commit 8844c19
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A customizable QR code generator to create beautiful and unique QR codes.
- 🎨 Customizable colors and styles
- 🖼️ Export to SVG and PNG
- 📋 Copy to clipboard
- 🌓 Light/dark mode based on system settings
- 🌓 Light/dark/system-preference mode toggle
- 🎲 Randomize style button
- 🌐 Available in 29+ languages thanks to [deepl-translate-github-action](https://github.com/lyqht/deepl-translate-github-action)
- 💾 Save & Load QR Code config
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<meta name="twitter:image" content="https://github.com/lyqht/mini-qr-code-generator/blob/main/public/og_image.png?raw=true">
<meta name="twitter:site" content="@estee_tey">
<meta name="license" content="GNU General Public License v3.0">
<meta name="color-scheme" content="dark light">
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
Expand Down
71 changes: 68 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { createRandomColor, getRandomItemInArray } from './utils/color'
import { getNumericCSSValue } from './utils/formatting'
import { sortedLocales } from './utils/language'
import { allPresets, type Preset } from './utils/presets'
import useDarkModePreference from './utils/useDarkModePreference'
//#region /** locale */
const isLocaleSelectOpen = ref(false)
const { t, locale } = useI18n()
Expand Down Expand Up @@ -184,6 +184,10 @@ watch(
},
{ immediate: true }
)
//#region /** dark mode */
const { isDarkMode, isDarkModePreferenceSetBySystem, toggleDarkModePreference } =
useDarkModePreference()
//#endregion
//#region /* error correction level */
Expand Down Expand Up @@ -542,7 +546,7 @@ async function generateBatchQRCodes(format: 'png' | 'svg') {

<template>
<main>
<div class="relative grid place-items-center bg-white p-8 md:px-6 dark:bg-zinc-900">
<div class="relative grid place-items-center bg-white p-8 dark:bg-zinc-900 md:px-6">
<div
class="mb-8 flex w-full flex-row flex-wrap justify-between gap-4 md:mb-4 md:w-5/6 md:ps-4"
>
Expand All @@ -565,7 +569,68 @@ async function generateBatchQRCodes(format: 'png' | 'svg') {
</svg>
</a>
<div class="vertical-border"></div>
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24">
<button
class="icon-button"
@click="toggleDarkModePreference"
:aria-label="t('Toggle dark mode')"
>
<span v-if="isDarkModePreferenceSetBySystem">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24">
<g fill="#abcabc">
<path d="M12 16a4 4 0 0 0 0-8z" />
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10s10-4.477 10-10S17.523 2 12 2m0 2v4a4 4 0 1 0 0 8v4a8 8 0 1 0 0-16"
clip-rule="evenodd"
/>
</g>
</svg>
</span>

<span v-else-if="isDarkMode">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon"
fill="none"
viewBox="0 0 24 24"
stroke="#abcbca"
stroke-width="2"
width="36"
height="36"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
</span>
<span v-else>
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
width="36"
height="36"
>
<path
fill="#abcbca"
stroke-linecap="round"
stroke-linejoin="round"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
</span>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="36"
height="36"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="#abcbca"
Expand Down
6 changes: 6 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
transition-duration: 300ms;
transition-property: background-color, color;

@media screen and (prefers-reduced-motion: reduce), (update: slow) {
transition-duration: 0s;
}
}

a {
Expand Down
89 changes: 89 additions & 0 deletions src/utils/useDarkModePreference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'

export type DarkModePreference = 'light' | 'dark' | 'system'
const colorSchemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')

function getLocalStoragePreference(): DarkModePreference {
return localStorage.getItem('dark-mode-preference') as DarkModePreference
}

function getMediaPreference(): DarkModePreference {
const hasDarkPreference = colorSchemeMediaQuery.matches
return hasDarkPreference ? 'dark' : 'light'
}

function getDarkModePreference(): DarkModePreference {
return getLocalStoragePreference() ?? 'system'
}

function getIsDarkMode(): boolean {
const darkModePreference = getDarkModePreference()
return (
darkModePreference === 'dark' ||
(darkModePreference === 'system' && getMediaPreference() === 'dark')
)
}

const useDarkModePreference = () => {
const darkModePreference = ref<DarkModePreference>(getDarkModePreference())
const isDarkModePreferenceSetBySystem = computed(() => darkModePreference.value === 'system')
const isDarkMode = ref<boolean>(getIsDarkMode())

const updateUiBasedOnDarkMode = () => {
const isDark = getIsDarkMode()
isDarkMode.value = isDark
if (isDark && !document.documentElement.classList.contains('dark')) {
document.documentElement.classList.add('dark')
} else if (!isDark && document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark')
}
}

watch(darkModePreference, updateUiBasedOnDarkMode, { immediate: true })

function setDarkModePreference(theme: DarkModePreference) {
localStorage.setItem('dark-mode-preference', theme)
darkModePreference.value = theme
}

const preferences: DarkModePreference[] = ['light', 'dark', 'system']
function toggleDarkModePreference(): void {
const updatedPreference =
preferences[(preferences.indexOf(darkModePreference.value) + 1) % preferences.length]
setDarkModePreference(updatedPreference)
}

function updateDarkModePreferenceIfSystemPreferenceChanges() {
console.log(
'updateDarkModePreferenceIfSystemPreferenceChanges',
isDarkModePreferenceSetBySystem.value
)
if (isDarkModePreferenceSetBySystem.value) {
updateUiBasedOnDarkMode()
}
}

onMounted(() => {
colorSchemeMediaQuery.addEventListener(
'change',
updateDarkModePreferenceIfSystemPreferenceChanges
)
})

onBeforeUnmount(() => {
colorSchemeMediaQuery.removeEventListener(
'change',
updateDarkModePreferenceIfSystemPreferenceChanges
)
})

return {
isDarkMode,
darkModePreference,
isDarkModePreferenceSetBySystem,
setDarkModePreference,
toggleDarkModePreference
}
}

export default useDarkModePreference
1 change: 1 addition & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const content = [
'./app/**/*.{ts,tsx,vue}',
'./src/**/*.{ts,tsx,vue}'
]
export const darkMode = 'class'
export const theme = {
container: {
center: true,
Expand Down

0 comments on commit 8844c19

Please sign in to comment.