Skip to content

Commit

Permalink
feat[close #34]: add dark mode support
Browse files Browse the repository at this point in the history
  • Loading branch information
mirkobrombin committed Feb 15, 2024
1 parent 9f77d9a commit 0dccff6
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 96 deletions.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<title>Chronos</title>
</head>

<body>
<body class="bg-white dark:bg-gray-900">
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
Expand Down
77 changes: 50 additions & 27 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,74 +1,81 @@
<template>
<div :class="{ 'sticky top-0 z-50 bg-white shadow': stickyTopbar }" class="bg-white">
<div :class="{ 'sticky top-0 z-50 shadow': stickyTopbar }" class="bg-white dark:bg-gray-900">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<nav class="flex justify-between items-center py-4" aria-label="main navigation">
<router-link to="/" class="flex items-center">
<img :src="chronosConfig.logoUrl" class="w-7 min-w-7 h-7 min-h-7" alt="Logo" />
<h1 class="text-lg font-semibold ml-2 hidden sm:block">{{ chronosConfig.logoTitle }}</h1>
<h1 class="text-lg font-semibold ml-2 hidden sm:block text-gray-900 dark:text-gray-100">{{
chronosConfig.logoTitle }}</h1>
</router-link>

<div class="flex-1 mx-4 relative" v-if="collectionShortName">
<div
class="flex items-center border bg-gray-100 rounded-lg overflow-hidden focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-opacity-50">
<input class="flex-1 px-4 py-2 text-gray-600 bg-transparent focus:outline-none w-full" type="text"
placeholder="Search for articles.." v-model="search" @input="searchArticles">
<i class="material-icons p-2 text-gray-400">search</i>
<div v-if="collectionShortName" class="relative border-l">
<button @click="showLangs = !showLangs" class="flex items-center p-2 text-gray-600 hover:text-gray-900">
<div class="flex-1 mx-4 relative">
<div v-if="collectionShortName"
class="flex items-center border bg-gray-100 dark:bg-gray-700 rounded-lg overflow-hidden focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-opacity-50 border-gray-200 dark:border-gray-600">
<input class="flex-1 px-4 py-2 text-gray-600 dark:text-gray-300 bg-transparent focus:outline-none w-full"
type="text" placeholder="Search for articles.." v-model="search" @input="searchArticles">
<i class="material-icons p-2 text-gray-400 dark:text-gray-500">search</i>

<div v-if="collectionShortName" class="relative border-l border-gray-200 dark:border-gray-600">
<button @click="showLangs = !showLangs"
class="flex items-center p-2 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white">
<span>{{ chronosStore.prefLang }}</span>
<i class="material-icons">arrow_drop_down</i>
</button>
</div>
</div>
<div v-show="showLangs" class="absolute right-0 mt-2 w-48 bg-white shadow-lg rounded-md z-50">

<div v-show="showLangs" class="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 shadow-lg rounded-md z-50">
<div class="py-1">
<a v-for="(lang, index) in langs" :key="index" @click="setLang(lang)"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer">
class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">
{{ lang }}
</a>
</div>
</div>

<div v-if="searchResponse.length > 0"
class="absolute w-full mt-1 z-50 max-h-80 overflow-auto bg-white rounded-md shadow-lg m-2">
<span class="block p-4 hover:bg-gray-50 cursor-pointer" v-for="(result, index) in searchResponse" :key="index"
@click="goToArticle(result.Slug)">
class="absolute w-full mt-1 z-50 max-h-80 overflow-auto bg-white dark:bg-gray-800 rounded-md shadow-lg m-2">
<span class="block p-4 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
v-for="(result, index) in searchResponse" :key="index" @click="goToArticle(result.Slug)">
<div class="flex items-center space-x-2">
<i class="mdi material-icons text-gray-500">book</i>
<i class="mdi material-icons text-gray-500 dark:text-gray-400">book</i>
<div class="flex-1">
<p class="font-semibold">{{ result.Title }}</p>
<p class="text-sm text-gray-500">{{ result.Description }}</p>
<p class="font-semibold text-gray-900 dark:text-gray-100">{{ result.Title }}</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ result.Description }}</p>
</div>
</div>
</span>
</div>
</div>

<button @click="toggleDarkMode"
class="flex items-center p-2 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white">
<i class="material-icons" v-if="isDarkMode">light_mode</i>
<i class="material-icons" v-else>dark_mode</i>
</button>

<div class="hidden sm:flex items-center space-x-4">
<a class="text-base text-gray-600 hover:text-gray-900" :href="link.url" target="_blank"
v-for="(link, index) in chronosConfig.extraLinks" :key="index">
<a class="text-base text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white" :href="link.url"
target="_blank" v-for="(link, index) in chronosConfig.extraLinks" :key="index">
{{ link.name }}
</a>
</div>
</nav>
</div>


<router-view />

<footer class="bg-white mt-12">
<footer class="bg-white dark:bg-gray-900 mt-12">
<div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 text-center">
<p class="text-base text-gray-600">
<strong>Chronos</strong> by <a href="https://vanillaos.org" class="text-blue-600 hover:underline">Vanilla
OS</a>.
<p class="text-base text-gray-600 dark:text-gray-400">
<strong>Chronos</strong> by <a href="https://vanillaos.org"
class="text-blue-600 dark:text-blue-400 hover:underline">Vanilla OS</a>.
</p>
</div>
</footer>
</div>
</template>



<script lang="ts">
import { defineComponent } from "vue";
import { useChronosStore } from "@/core/store";
Expand All @@ -84,6 +91,7 @@ export default defineComponent({
search: "",
chronosConfig: {} as ChronosConfig,
collectionShortName: "",
isDarkMode: false,
};
},
computed: {
Expand Down Expand Up @@ -113,6 +121,14 @@ export default defineComponent({
this.chronosStore.$patch((state) => {
state.prefLang = "en";
});
// Dark mode
const userPrefersDark = localStorage.getItem('darkMode') === 'true' ||
(!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (userPrefersDark) {
document.documentElement.classList.add('dark');
this.isDarkMode = true;
}
},
methods: {
setLang(lang: string) {
Expand Down Expand Up @@ -146,6 +162,13 @@ export default defineComponent({
}
}
},
toggleDarkMode() {
document.documentElement.classList.toggle('dark');
const isDarkMode = document.documentElement.classList.contains('dark');
localStorage.setItem('darkMode', isDarkMode ? 'true' : 'false');
this.isDarkMode = isDarkMode;
},
},
});
</script>
24 changes: 13 additions & 11 deletions src/components/CustomSelect.vue
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
<template>
<div class="relative">
<div @click="toggleDropdown" class="form-input cursor-pointer">
<span v-if="!modelValue.length" class="text-blue-600">{{ defaultLabel }}</span>
<span v-else-if="modelValue.length === 1" class="text-blue-600">{{ modelValue[0] }}</span>
<span v-else class="text-blue-600">{{ modelValue.length }} Tags Selected</span>
<div @click="toggleDropdown" class="form-input cursor-pointer dark:text-gray-200">
<span v-if="!modelValue.length" class="text-blue-600 dark:text-blue-400">{{ defaultLabel }}</span>
<span v-else-if="modelValue.length === 1" class="text-blue-600 dark:text-blue-400">{{ modelValue[0] }}</span>
<span v-else class="text-blue-600 dark:text-blue-400">{{ modelValue.length }} Tags Selected</span>
</div>
<div v-show="openDropdown" class="absolute z-10 bg-white mt-1 rounded-md shadow-lg border w-content right-0"
<div v-show="openDropdown"
class="absolute z-10 bg-white dark:bg-gray-800 mt-1 rounded-md shadow-lg border dark:border-gray-700 w-content right-0"
style="min-width: 10rem;">
<ul class="max-h-60 overflow-auto">
<li v-for="option in filteredOptions" :key="option" @click="selectOption(option)"
class="cursor-pointer select-none p-2 hover:bg-blue-100">
class="cursor-pointer select-none p-2 hover:bg-blue-100 dark:hover:bg-gray-700 dark:text-gray-200">
{{ option }}
<span v-if="modelValue.includes(option)" class="material-icons text-sm">check</span>
<span v-if="modelValue.includes(option)" class="material-icons text-sm dark:text-gray-200">check</span>
</li>
</ul>
<div v-if="modelValue.length !== 0" class="p-2 flex flex-wrap gap-2 flex-col">
<div v-if="modelValue.length !== 0" class="p-2 flex flex-wrap gap-2 flex-col dark:bg-gray-600">
<span v-for="tag in modelValue" :key="tag"
class="bg-blue-100 text-blue-800 rounded-full px-3 py-1 text-xs font-medium flex items-center flex-1 mr-2">
class="bg-blue-100 dark:bg-gray-800 text-blue-800 dark:text-blue-200 rounded-full px-3 py-1 text-xs font-medium flex items-center flex-1 mr-2">
{{ tag }}
<i class="material-icons text-sm cursor-pointer ml-auto" @click.stop="removeTag(tag)">cancel</i>
<i class="material-icons text-sm cursor-pointer ml-auto dark:text-gray-200"
@click.stop="removeTag(tag)">cancel</i>
</span>
</div>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent, ref, watch, computed } from 'vue';
Expand Down
43 changes: 24 additions & 19 deletions src/views/ArticleView.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
<template>
<nav class="bg-gray-50 border-b border-gray-200 px-4 py-3" aria-label="breadcrumbs">
<nav class="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3"
aria-label="breadcrumbs">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ul class="flex space-x-4">
<li>
<router-link to="/" class="text-blue-600 hover:text-blue-800">Home</router-link>
<router-link to="/"
class="text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 dark:text-blue-400">Home</router-link>
</li>
<li>
<a class="cursor-pointer text-gray-600">{{ lang }}</a>
<a class="cursor-pointer text-gray-600 dark:text-gray-400">{{ lang }}</a>
</li>
<li class="text-gray-500" aria-current="page">
<li class="text-gray-500 dark:text-gray-300" aria-current="page">
{{ article.Title }}
</li>
</ul>
</div>
</nav>
<section class="bg-gray-100 text-black">
<section class="bg-gray-100 dark:bg-gray-800 text-black dark:text-gray-200">
<div class="container mx-auto py-8 px-4 text-center">
<h1 class="text-3xl font-bold">{{ article.Title }}</h1>
<p class="mt-4">{{ article.Description }}</p>
Expand All @@ -23,7 +25,7 @@
<img v-if="!imageError[author]" :src="`https://github.com/${author}.png?size=40`" :alt="author"
class="w-7 h-7 rounded-full object-cover " @error="imageError[author] = true" :title="author">
<span v-else
class="w-7 h-7 rounded-full overflow-hidden bg-blue-500 text-white font-bold inline-flex items-center justify-center -mr-3">
class="w-7 h-7 rounded-full overflow-hidden bg-blue-500 dark:bg-blue-700 text-white font-bold inline-flex items-center justify-center -mr-3">
{{ getInitials(author) }}
</span>
</div>
Expand All @@ -33,23 +35,25 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex py-8">
<aside class="hidden lg:block lg:w-1/4">
<div class="sticky top-4 z-1">
<div class="bg-gray-50 border border-gray-200 p-4 rounded-lg">
<p class="font-semibold mb-4">Navigation</p>
<div class="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-600 p-4 rounded-lg">
<p class="font-semibold mb-4 text-gray-900 dark:text-gray-200">Navigation</p>
<ul class="space-y-2">
<li v-for="(heading, index) in headings" :key="index" :class="`pl-${heading.level * 2}`">
<li v-for="(heading, index) in headings" :key="index"
:class="`pl-${heading.level * 2} text-gray-900 dark:text-gray-200`">
<a @click="scrollToHeading(heading.id)"
class="cursor-pointer text-blue-600 hover:text-blue-800">{{ heading.text }}</a>
class="cursor-pointer text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 dark:text-blue-400">{{
heading.text }}</a>
</li>
</ul>
<a v-if="editUrl != ''" :href="editUrl" target="_blank"
class="mt-4 inline-block w-full text-center bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
class="mt-4 inline-block w-full text-center bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-800 text-white font-bold py-2 px-4 rounded">
Edit
</a>
</div>
</div>
</aside>
<div class="w-full lg:w-3/4 lg:pl-4">
<div class="content prose article-content" v-html="article.Body"></div>
<div class="content prose dark:prose-invert article-content" v-html="article.Body"></div>
</div>
</div>

Expand All @@ -60,25 +64,26 @@

<div v-if="isSidebarVisible" @click.self="isSidebarVisible = false"
class="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden bottom-0 flex flex-col items-end justify-end">
<aside class="relative bg-white p-4 shadow-lg overflow-auto w-full rounded-t-lg pb-16">
<aside class="relative bg-white dark:bg-gray-700 p-4 shadow-lg overflow-auto w-full rounded-t-lg pb-16">
<div class="p-2">
<p class="font-semibold mb-4">Navigation</p>
<p class="font-semibold mb-4 text-gray-900 dark:text-gray-200">Navigation</p>
<ul class="space-y-2">
<li v-for="(heading, index) in headings" :key="index" :class="`pl-${heading.level * 2}`">
<a @click="scrollToHeading(heading.id)" class="cursor-pointer text-blue-600 hover:text-blue-800">{{
heading.text }}</a>
<li v-for="(heading, index) in headings" :key="index"
:class="`pl-${heading.level * 2} text-gray-900 dark:text-gray-200`">
<a @click="scrollToHeading(heading.id)"
class="cursor-pointer text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 dark:text-blue-400">{{
heading.text }}</a>
</li>
</ul>
<a v-if="editUrl != ''" :href="editUrl" target="_blank"
class="mt-4 inline-block w-full text-center bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
class="mt-4 inline-block w-full text-center bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-800 text-white font-bold py-2 px-4 rounded">
Edit
</a>
</div>
</aside>
</div>
</template>


<script lang="ts">
import { defineComponent, reactive, watch } from "vue";
import type { Article, ChronosCollection, ChronosConfig } from "@/core/models";
Expand Down
Loading

0 comments on commit 0dccff6

Please sign in to comment.