feat: Implement core application layout, global styling, dark mode, and essential UI components.

This commit is contained in:
supalerk-ar66 2026-01-26 12:59:19 +07:00
parent 84e4d478c7
commit a2ce1d79a2
7 changed files with 87 additions and 67 deletions

View file

@ -4,29 +4,44 @@
* @description User profile dropdown menu component using Quasar.
*/
import { ref, computed, onMounted, watch } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useAuth } from '~/composables/useAuth'
import { useQuasar } from 'quasar'
const { currentUser, logout } = useAuth()
const { t } = useI18n()
const $q = useQuasar()
const isDarkMode = ref(false)
const isHydrated = ref(false)
// Sync Dark Mode state
const applyTheme = (value: boolean) => {
// Tailwind dark mode
document.documentElement.classList.toggle('dark', value)
// Quasar dark mode
$q.dark.set(value)
// persist
localStorage.setItem('theme', value ? 'dark' : 'light')
}
const toggleDarkMode = (val: boolean) => {
isDarkMode.value = val
applyTheme(val)
}
// Sync Dark Mode state on mount
onMounted(() => {
if (document.documentElement.classList.contains('dark')) {
isDarkMode.value = true
}
})
isHydrated.value = true
const savedTheme = localStorage.getItem('theme')
const preferredDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false
// Watch for dark mode changes
watch(isDarkMode, (val) => {
if (val) {
document.documentElement.classList.add('dark')
localStorage.setItem('theme', 'dark')
} else {
document.documentElement.classList.remove('dark')
localStorage.setItem('theme', 'light')
}
isDarkMode.value = savedTheme
? savedTheme === 'dark'
: preferredDark
applyTheme(isDarkMode.value)
})
// User Initials
@ -37,11 +52,6 @@ const userInitials = computed(() => {
return f + l
})
const userName = computed(() => {
if (!currentUser.value) return 'User'
return `${currentUser.value.firstName} ${currentUser.value.lastName}`
})
const menuItems = computed(() => [
{ label: t('userMenu.home'), to: '/dashboard' },
{ label: t('userMenu.courseList'), to: '/browse/discovery' },
@ -64,42 +74,40 @@ const handleLogout = async () => {
<q-menu
anchor="bottom end"
self="top end"
class="rounded-2xl shadow-xl overflow-hidden border border-slate-100 dark:border-slate-700"
:offset="[0, 10]"
style="min-width: 240px; background-color: var(--bg-surface);"
content-class="bg-white dark:bg-slate-800 text-slate-900 dark:text-white rounded-2xl shadow-xl border border-slate-200/70 dark:border-white/10"
style="min-width: 240px;"
>
<div class="bg-[#1e293b] text-white">
<!-- Optional header if needed, but per design keeping it simple list -->
</div>
<q-list class="bg-[#1e293b] text-white py-2">
<q-list class="py-2">
<q-item
v-for="item in menuItems"
:key="item.label"
clickable
v-close-popup
@click="navigateTo(item.to)"
class="hover:bg-white/10 transition-colors"
class="hover:bg-slate-100 dark:hover:bg-white/10 transition-colors"
>
<q-item-section>
<q-item-label class="font-bold text-sm">{{ item.label }}</q-item-label>
</q-item-section>
</q-item>
<q-item class="hover:bg-white/10 transition-colors">
<q-item class="hover:bg-slate-100 dark:hover:bg-white/10 transition-colors">
<q-item-section>
<q-item-label class="font-bold text-sm">{{ $t('userMenu.darkMode') }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-toggle
v-model="isDarkMode"
v-if="isHydrated"
:model-value="isDarkMode"
@update:model-value="toggleDarkMode"
color="blue"
keep-color
/>
</q-item-section>
</q-item>
<q-separator class="bg-white/10 my-1" />
<q-separator class="bg-slate-100 dark:bg-white/10 my-1" />
<div class="p-4">
<q-btn
@ -116,7 +124,3 @@ const handleLogout = async () => {
</q-btn>
</div>
</template>
<style scoped>
/* Override Quasar generic styles if necessary */
</style>