From ae18bcbb8db2d3d055d55fed0f3cc2fc8f1f39f9 Mon Sep 17 00:00:00 2001
From: Chirag Chhatrala <60499540+chiragchhatrala@users.noreply.github.com>
Date: Mon, 15 Apr 2024 18:49:37 +0530
Subject: [PATCH] Fix Dark mode (#376)
Co-authored-by: Julien Nahum
---
.../open/forms/OpenCompleteForm.vue | 7 +-
client/components/open/forms/OpenForm.vue | 11 +-
.../components/open/forms/OpenFormField.vue | 10 +-
.../form-components/FormEditorPreview.vue | 8 +-
.../components/pages/welcome/Testimonials.vue | 58 +++++---
client/lib/forms/public-page.js | 135 +++++++++++++++---
client/pages/forms/[slug]/index.vue | 14 +-
7 files changed, 188 insertions(+), 55 deletions(-)
diff --git a/client/components/open/forms/OpenCompleteForm.vue b/client/components/open/forms/OpenCompleteForm.vue
index 4300d63a..efecd633 100644
--- a/client/components/open/forms/OpenCompleteForm.vue
+++ b/client/components/open/forms/OpenCompleteForm.vue
@@ -69,6 +69,7 @@
:loading="loading"
:fields="form.properties"
:theme="theme"
+ :dark-mode="darkMode"
:admin-preview="adminPreview"
@submit="submitForm"
>
@@ -127,7 +128,11 @@ export default {
form: { type: Object, required: true },
creating: { type: Boolean, default: false }, // If true, fake form submit
adminPreview: { type: Boolean, default: false }, // If used in FormEditorPreview
- submitButtonClass: { type: String, default: '' }
+ submitButtonClass: { type: String, default: '' },
+ darkMode: {
+ type: Boolean,
+ default: false
+ }
},
setup(props) {
diff --git a/client/components/open/forms/OpenForm.vue b/client/components/open/forms/OpenForm.vue
index 016ea34c..f96e635a 100644
--- a/client/components/open/forms/OpenForm.vue
+++ b/client/components/open/forms/OpenForm.vue
@@ -41,6 +41,7 @@
:data-form="dataForm"
:data-form-value="dataFormValue"
:theme="theme"
+ :dark-mode="darkMode"
:admin-preview="adminPreview"
/>
@@ -51,7 +52,7 @@
-
+
@@ -85,7 +86,6 @@ import VueHcaptcha from "@hcaptcha/vue3-hcaptcha"
import OpenFormField from './OpenFormField.vue'
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js"
import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js"
-import {darkModeEnabled} from "~/lib/forms/public-page.js"
export default {
name: 'OpenForm',
@@ -112,7 +112,11 @@ export default {
required: true
},
defaultDataForm:{},
- adminPreview: { type: Boolean, default: false } // If used in FormEditorPreview
+ adminPreview: { type: Boolean, default: false }, // If used in FormEditorPreview
+ darkMode: {
+ type: Boolean,
+ default: false
+ }
},
setup (props) {
@@ -124,7 +128,6 @@ export default {
dataForm,
recordsStore,
workingFormStore,
- darkModeEnabled: darkModeEnabled(),
pendingSubmission: pendingSubmission(props.form)
}
},
diff --git a/client/components/open/forms/OpenFormField.vue b/client/components/open/forms/OpenFormField.vue
index bca19e47..9edb6ff2 100644
--- a/client/components/open/forms/OpenFormField.vue
+++ b/client/components/open/forms/OpenFormField.vue
@@ -56,7 +56,6 @@
+
+
diff --git a/client/lib/forms/public-page.js b/client/lib/forms/public-page.js
index 93932467..9e6af7f1 100644
--- a/client/lib/forms/public-page.js
+++ b/client/lib/forms/public-page.js
@@ -1,30 +1,121 @@
-
let darkModeNodeParent = import.meta.client ? document.body : null
/**
* Handle form public pages dark mode and transparent mode
*/
export function handleDarkMode (darkMode, elem = null) {
- if (import.meta.server) return
+ if (import.meta.server)
+ return
darkModeNodeParent = elem ?? document.body
// Dark mode
- if (['dark', 'light'].includes(darkMode)) {
+ if (['dark', 'light'].includes(darkMode))
return handleDarkModeToggle(darkMode === 'dark')
- }
// Case auto
- handleDarkModeToggle(window.matchMedia('(prefers-color-scheme: dark)').matches)
+ handleDarkModeToggle(
+ window.matchMedia('(prefers-color-scheme: dark)').matches
+ )
// Create listener
- window.matchMedia('(prefers-color-scheme: dark)')
+ window
+ .matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', handleDarkModeToggle)
}
-export function darkModeEnabled() {
- if (import.meta.server) return false
- return computed(() => document.body.classList.contains('dark'))
+export function useClassWatcher (elem, className) {
+ const hasClass = ref(false)
+
+ const updateClassPresence = () => {
+ hasClass.value = elem.value?.classList.contains(className) ?? false
+ }
+
+ let observer = null
+
+ const startObserving = () => {
+ if (elem.value) {
+ updateClassPresence()
+ observer = new MutationObserver(updateClassPresence)
+ observer.observe(elem.value, { attributes: true, attributeFilter: ['class'] })
+ }
+ }
+
+ const stopObserving = () => {
+ if (observer) {
+ observer.disconnect()
+ observer = null
+ }
+ }
+
+ onMounted(() => {
+ watch(elem, (newElem, oldElem, onCleanup) => {
+ stopObserving()
+ startObserving()
+ onCleanup(stopObserving)
+ }, { immediate: true })
+ })
+
+ onUnmounted(() => {
+ stopObserving()
+ })
+
+ return computed(() => hasClass.value)
+}
+
+export function useDarkMode (elem = ref(null)) {
+ // Define a computed property to handle the element reference reactively
+ const effectiveElem = computed(() => {
+ return elem.value || (process.client ? document.body : null)
+ })
+
+ // Pass the computed property to useClassWatcher
+ return useClassWatcher(effectiveElem, 'dark')
+}
+
+export function darkModeEnabled (elem = ref(null)) {
+ if (import.meta.server)
+ return ref(false)
+
+ // TODO: replace this with a function that returns a watcher for the given of dark class element
+ // Simplify this
+ const isDark = ref(false)
+
+ // Update isDark based on the current class
+ const updateIsDark = () => {
+ console.log(elem.value, 'test')
+ const finalElement = elem.value ?? document.body
+ isDark.value = finalElement.classList.contains('dark')
+ }
+
+ // MutationObserver callback to react to class changes
+ let observer = new MutationObserver(updateIsDark)
+
+ // Initialize and clean up the observer
+ const initObserver = (element) => {
+ if (observer) {
+ observer.disconnect()
+ }
+ if (element) {
+ observer = new MutationObserver(updateIsDark)
+ observer.observe(element, { attributes: true, attributeFilter: ['class'] })
+ }
+ }
+
+ onMounted(() => {
+ if (!import.meta.server) {
+ initObserver(elem)
+ }
+ })
+
+ onUnmounted(() => {
+ if (observer) {
+ observer.disconnect()
+ }
+ })
+
+ // Return a computed ref that depends on isDark
+ return computed(() => isDark.value)
}
function handleDarkModeToggle (enabled) {
@@ -32,20 +123,27 @@ function handleDarkModeToggle (enabled) {
// if we received an event
enabled = enabled.matches
}
- enabled ? darkModeNodeParent.classList.add('dark') : darkModeNodeParent.classList.remove('dark')
+ enabled
+ ? darkModeNodeParent.classList.add('dark')
+ : darkModeNodeParent.classList.remove('dark')
}
export function disableDarkMode () {
- if (import.meta.server) return
+ if (import.meta.server)
+ return
const body = document.body
body.classList.remove('dark')
// Remove event listener
- window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', handleDarkModeToggle)
+ window
+ .matchMedia('(prefers-color-scheme: dark)')
+ .removeEventListener('change', handleDarkModeToggle)
}
export function handleTransparentMode (transparentModeEnabled) {
- if (import.meta.server) return
- if (!useIsIframe() || !transparentModeEnabled) return
+ if (import.meta.server)
+ return
+ if (!useIsIframe() || !transparentModeEnabled)
+ return
const app = document.getElementById('app')
app.classList.remove('bg-white')
@@ -53,9 +151,12 @@ export function handleTransparentMode (transparentModeEnabled) {
app.classList.add('bg-transparent')
}
-export function focusOnFirstFormElement() {
- if (import.meta.server) return
- for (const ele of document.querySelectorAll('input,button,textarea,[role="button"]')) {
+export function focusOnFirstFormElement () {
+ if (import.meta.server)
+ return
+ for (const ele of document.querySelectorAll(
+ 'input,button,textarea,[role="button"]'
+ )) {
if (ele.offsetWidth !== 0 || ele.offsetHeight !== 0) {
ele.focus()
break
diff --git a/client/pages/forms/[slug]/index.vue b/client/pages/forms/[slug]/index.vue
index 0abff8ad..907b9d4c 100644
--- a/client/pages/forms/[slug]/index.vue
+++ b/client/pages/forms/[slug]/index.vue
@@ -40,7 +40,7 @@
-
@@ -52,13 +52,19 @@
import {computed} from 'vue'
import OpenCompleteForm from "~/components/open/forms/OpenCompleteForm.vue"
import sha256 from 'js-sha256'
-import {onBeforeRouteLeave} from 'vue-router'
-import {disableDarkMode, handleDarkMode, handleTransparentMode, focusOnFirstFormElement} from '~/lib/forms/public-page'
+import { onBeforeRouteLeave } from 'vue-router'
+import {
+ disableDarkMode,
+ handleDarkMode,
+ handleTransparentMode,
+ focusOnFirstFormElement,
+ useDarkMode
+} from '~/lib/forms/public-page'
const crisp = useCrisp()
const formsStore = useFormsStore()
const recordsStore = useRecordsStore()
-
+const darkMode = useDarkMode()
const isIframe = useIsIframe()
const formLoading = computed(() => formsStore.loading)
const recordLoading = computed(() => recordsStore.loading)