feat: Establish core application layout, global styles, and theme mode management.
This commit is contained in:
parent
a2ce1d79a2
commit
0e095b35c5
4 changed files with 79 additions and 30 deletions
|
|
@ -1113,3 +1113,33 @@ ul {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
/* =========================
|
||||||
|
Discovery page: Quasar dark-mode overrides (scoped)
|
||||||
|
ไม่กระทบหน้าอื่น เพราะล็อคด้วย .discovery-page
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
/* Dark mode: input background + border */
|
||||||
|
html.dark .discovery-page .input-dark-override .q-field__control {
|
||||||
|
background-color: #0f172a !important; /* slate-900 */
|
||||||
|
border-color: rgba(255, 255, 255, 0.10) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode: input text + icons */
|
||||||
|
html.dark .discovery-page .input-dark-override .q-field__native,
|
||||||
|
html.dark .discovery-page .input-dark-override .q-field__input,
|
||||||
|
html.dark .discovery-page .input-dark-override .q-field__prefix,
|
||||||
|
html.dark .discovery-page .input-dark-override .q-field__suffix,
|
||||||
|
html.dark .discovery-page .input-dark-override .q-select__dropdown-icon,
|
||||||
|
html.dark .discovery-page .input-dark-override .q-field__append .q-icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode: placeholder */
|
||||||
|
html.dark .discovery-page .input-dark-override .q-placeholder {
|
||||||
|
color: #94a3b8 !important; /* slate-400 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode: category label ให้ชัดขึ้น (แก้กรณี class ไม่ติด) */
|
||||||
|
html.dark .discovery-page .category-item .q-item__label {
|
||||||
|
color: #e2e8f0 !important; /* slate-200 */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,36 +12,13 @@ const { currentUser, logout } = useAuth()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
|
|
||||||
const isDarkMode = ref(false)
|
// Use centralized theme management
|
||||||
|
const { isDark, set } = useThemeMode()
|
||||||
|
|
||||||
const isHydrated = ref(false)
|
const isHydrated = ref(false)
|
||||||
|
|
||||||
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(() => {
|
onMounted(() => {
|
||||||
isHydrated.value = true
|
isHydrated.value = true
|
||||||
const savedTheme = localStorage.getItem('theme')
|
|
||||||
const preferredDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false
|
|
||||||
|
|
||||||
isDarkMode.value = savedTheme
|
|
||||||
? savedTheme === 'dark'
|
|
||||||
: preferredDark
|
|
||||||
|
|
||||||
applyTheme(isDarkMode.value)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// User Initials
|
// User Initials
|
||||||
|
|
@ -88,19 +65,19 @@ const handleLogout = async () => {
|
||||||
class="hover:bg-slate-100 dark:hover:bg-white/10 transition-colors"
|
class="hover:bg-slate-100 dark:hover:bg-white/10 transition-colors"
|
||||||
>
|
>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label class="font-bold text-sm">{{ item.label }}</q-item-label>
|
<q-item-label class="font-bold text-sm text-slate-800 dark:text-white">{{ item.label }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
<q-item class="hover:bg-slate-100 dark: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-section>
|
||||||
<q-item-label class="font-bold text-sm">{{ $t('userMenu.darkMode') }}</q-item-label>
|
<q-item-label class="font-bold text-sm text-slate-800 dark:text-white">{{ $t('userMenu.darkMode') }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
v-if="isHydrated"
|
v-if="isHydrated"
|
||||||
:model-value="isDarkMode"
|
:model-value="isDark"
|
||||||
@update:model-value="toggleDarkMode"
|
@update:model-value="set"
|
||||||
color="blue"
|
color="blue"
|
||||||
keep-color
|
keep-color
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
39
Frontend-Learner/composables/useThemeMode.ts
Normal file
39
Frontend-Learner/composables/useThemeMode.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
|
||||||
|
export const useThemeMode = () => {
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
|
// deterministic on SSR: default = light
|
||||||
|
const isDark = useState<boolean>('theme:isDark', () => false)
|
||||||
|
|
||||||
|
const applyTheme = (value: boolean) => {
|
||||||
|
if (!process.client) return
|
||||||
|
document.documentElement.classList.toggle('dark', value)
|
||||||
|
$q.dark.set(value)
|
||||||
|
localStorage.setItem('theme', value ? 'dark' : 'light')
|
||||||
|
}
|
||||||
|
|
||||||
|
const initTheme = () => {
|
||||||
|
if (!process.client) return
|
||||||
|
const saved = localStorage.getItem('theme') // 'dark' | 'light' | null
|
||||||
|
const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false
|
||||||
|
|
||||||
|
const value = saved ? saved === 'dark' : prefersDark
|
||||||
|
isDark.value = value
|
||||||
|
applyTheme(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(initTheme)
|
||||||
|
|
||||||
|
watch(isDark, (v) => applyTheme(v))
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
isDark.value = !isDark.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const set = (v: boolean) => {
|
||||||
|
isDark.value = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isDark, toggle, set }
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,9 @@ const leftDrawerOpen = ref(false)
|
||||||
const toggleLeftDrawer = () => {
|
const toggleLeftDrawer = () => {
|
||||||
leftDrawerOpen.value = !leftDrawerOpen.value
|
leftDrawerOpen.value = !leftDrawerOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize global theme management
|
||||||
|
useThemeMode()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue