Fix Dark mode (#376)

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Chirag Chhatrala 2024-04-15 18:49:37 +05:30 committed by GitHub
parent e1faba8239
commit ae18bcbb8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 188 additions and 55 deletions

View File

@ -69,6 +69,7 @@
:loading="loading" :loading="loading"
:fields="form.properties" :fields="form.properties"
:theme="theme" :theme="theme"
:dark-mode="darkMode"
:admin-preview="adminPreview" :admin-preview="adminPreview"
@submit="submitForm" @submit="submitForm"
> >
@ -127,7 +128,11 @@ export default {
form: { type: Object, required: true }, form: { type: Object, required: true },
creating: { type: Boolean, default: false }, // If true, fake form submit creating: { type: Boolean, default: false }, // If true, fake form submit
adminPreview: { type: Boolean, default: false }, // If used in FormEditorPreview adminPreview: { type: Boolean, default: false }, // If used in FormEditorPreview
submitButtonClass: { type: String, default: '' } submitButtonClass: { type: String, default: '' },
darkMode: {
type: Boolean,
default: false
}
}, },
setup(props) { setup(props) {

View File

@ -41,6 +41,7 @@
:data-form="dataForm" :data-form="dataForm"
:data-form-value="dataFormValue" :data-form-value="dataFormValue"
:theme="theme" :theme="theme"
:dark-mode="darkMode"
:admin-preview="adminPreview" :admin-preview="adminPreview"
/> />
</template> </template>
@ -51,7 +52,7 @@
<!-- Captcha --> <!-- Captcha -->
<template v-if="form.use_captcha && isLastPage"> <template v-if="form.use_captcha && isLastPage">
<div class="mb-3 px-2 mt-2 mx-auto w-max"> <div class="mb-3 px-2 mt-2 mx-auto w-max">
<vue-hcaptcha ref="hcaptcha" :sitekey="hCaptchaSiteKey" :theme="darkModeEnabled?'dark':'light'" /> <vue-hcaptcha ref="hcaptcha" :sitekey="hCaptchaSiteKey" :theme="darkMode?'dark':'light'" />
<has-error :form="dataForm" field="h-captcha-response" /> <has-error :form="dataForm" field="h-captcha-response" />
</div> </div>
</template> </template>
@ -85,7 +86,6 @@ import VueHcaptcha from "@hcaptcha/vue3-hcaptcha"
import OpenFormField from './OpenFormField.vue' import OpenFormField from './OpenFormField.vue'
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js" import {pendingSubmission} from "~/composables/forms/pendingSubmission.js"
import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js" import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js"
import {darkModeEnabled} from "~/lib/forms/public-page.js"
export default { export default {
name: 'OpenForm', name: 'OpenForm',
@ -112,7 +112,11 @@ export default {
required: true required: true
}, },
defaultDataForm:{}, 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) { setup (props) {
@ -124,7 +128,6 @@ export default {
dataForm, dataForm,
recordsStore, recordsStore,
workingFormStore, workingFormStore,
darkModeEnabled: darkModeEnabled(),
pendingSubmission: pendingSubmission(props.form) pendingSubmission: pendingSubmission(props.form)
} }
}, },

View File

@ -56,7 +56,6 @@
<script> <script>
import { computed } from 'vue' import { computed } from 'vue'
import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js" import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js"
import { darkModeEnabled } from '~/lib/forms/public-page.js'
import { default as _has } from 'lodash/has' import { default as _has } from 'lodash/has'
export default { export default {
@ -83,6 +82,10 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
darkMode: {
type: Boolean,
default: false
},
field: { field: {
type: Object, type: Object,
required: true required: true
@ -177,9 +180,6 @@ export default {
}, },
fieldSideBarOpened() { fieldSideBarOpened() {
return this.adminPreview && (this.form && this.selectedFieldIndex !== null) ? (this.form.properties[this.selectedFieldIndex] && this.showEditFieldSidebar) : false return this.adminPreview && (this.form && this.selectedFieldIndex !== null) ? (this.form.properties[this.selectedFieldIndex] && this.showEditFieldSidebar) : false
},
isDark () {
return this.form.dark_mode === 'dark' || this.form.dark_mode === 'auto' && darkModeEnabled()
} }
}, },
@ -250,7 +250,7 @@ export default {
theme: this.theme, theme: this.theme,
maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : 2000, maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : 2000,
showCharLimit: field.show_char_limit || false, showCharLimit: field.show_char_limit || false,
isDark: this.isDark isDark: this.darkMode
} }
if (['select', 'multi_select'].includes(field.type)) { if (['select', 'multi_select'].includes(field.type)) {

View File

@ -32,6 +32,7 @@
<open-complete-form ref="form-preview" class="w-full mx-auto py-5 px-3" :class="{'max-w-lg': form && (form.width === 'centered')}" <open-complete-form ref="form-preview" class="w-full mx-auto py-5 px-3" :class="{'max-w-lg': form && (form.width === 'centered')}"
:creating="creating" :creating="creating"
:form="form" :form="form"
:dark-mode="darkMode"
:admin-preview="true" :admin-preview="true"
@restarted="previewFormSubmitted=false" @restarted="previewFormSubmitted=false"
@submitted="previewFormSubmitted=true" @submitted="previewFormSubmitted=true"
@ -61,7 +62,7 @@
<script> <script>
import VSwitch from '../../../../forms/components/VSwitch.vue' import VSwitch from '../../../../forms/components/VSwitch.vue'
import OpenCompleteForm from '../../OpenCompleteForm.vue' import OpenCompleteForm from '../../OpenCompleteForm.vue'
import {handleDarkMode} from "~/lib/forms/public-page.js" import {handleDarkMode, useDarkMode} from "~/lib/forms/public-page.js"
import { default as _has } from 'lodash/has' import { default as _has } from 'lodash/has'
export default { export default {
@ -69,8 +70,11 @@ export default {
props: {}, props: {},
setup () { setup () {
const workingFormStore = useWorkingFormStore() const workingFormStore = useWorkingFormStore()
const parent = ref(null)
return { return {
workingFormStore workingFormStore,
parent: parent,
darkMode: useDarkMode(parent)
} }
}, },
data () { data () {

View File

@ -1,49 +1,63 @@
<template> <template>
<iframe v-if="!isDark" id="testimonialto-carousel-all-notionforms" <iframe
:id="iframeId"
title="NoteForms testimonial"
loading="lazy" loading="lazy"
src="https://embed.testimonial.to/carousel/all/notionforms?theme=light&autoplay=on&showmore=on&one-row=on&same-height=off" height="500px"
frameBorder="0" scrolling="no" width="100%" :src="'https://embed-v2.testimonial.to/w/notionforms?theme=light&card=base&loadMore=on&initialCount=8&tag=all'"
frameBorder="0"
scrolling="no"
width="100%"
/> />
<iframe v-else id="testimonialto-carousel-all-notionforms" src="https://embed.testimonial.to/carousel/all/notionforms?theme=dark&autoplay=on&showmore=on&one-row=on&same-height=off" frameborder="0" scrolling="no" width="100%" />
</template> </template>
<script> <script>
import {darkModeEnabled} from "~/lib/forms/public-page.js" import { useDarkMode } from '~/lib/forms/public-page.js'
export default { export default {
props: { props: {
featuresOnly: { featuresOnly: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
data: () => ({}),
setup () { data: () => ({
const isDark = darkModeEnabled() iframeId: 'testimonialto-carousel-all-notionforms'
return { }),
isDark
} computed: {},
},
mounted () { mounted () {
window.addEventListener('load', () => {
this.loadScript() this.loadScript()
})
}, },
methods: { methods: {
loadScript () { loadScript () {
if (import.meta.server) return if (import.meta.server)
return
const script = document.createElement('script') const script = document.createElement('script')
script.setAttribute('src', 'https://testimonial.to/js/iframeResizer.min.js') script.setAttribute(
'src',
'https://testimonial.to/js/iframeResizer.min.js'
)
script.setAttribute('defer', 'defer')
document.head.appendChild(script) document.head.appendChild(script)
script.addEventListener('load', function () { script.addEventListener('load', () => {
window.iFrameResize({ console.log('resizeing')
window.iFrameResize(
{
log: false, log: false,
checkOrigin: false checkOrigin: false
}, '#testimonialto-carousel-all-notionforms') },
'#' + this.iframeId
)
}) })
} }
} }
} }
</script> </script>

View File

@ -1,30 +1,121 @@
let darkModeNodeParent = import.meta.client ? document.body : null let darkModeNodeParent = import.meta.client ? document.body : null
/** /**
* Handle form public pages dark mode and transparent mode * Handle form public pages dark mode and transparent mode
*/ */
export function handleDarkMode (darkMode, elem = null) { export function handleDarkMode (darkMode, elem = null) {
if (import.meta.server) return if (import.meta.server)
return
darkModeNodeParent = elem ?? document.body darkModeNodeParent = elem ?? document.body
// Dark mode // Dark mode
if (['dark', 'light'].includes(darkMode)) { if (['dark', 'light'].includes(darkMode))
return handleDarkModeToggle(darkMode === 'dark') return handleDarkModeToggle(darkMode === 'dark')
}
// Case auto // Case auto
handleDarkModeToggle(window.matchMedia('(prefers-color-scheme: dark)').matches) handleDarkModeToggle(
window.matchMedia('(prefers-color-scheme: dark)').matches
)
// Create listener // Create listener
window.matchMedia('(prefers-color-scheme: dark)') window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', handleDarkModeToggle) .addEventListener('change', handleDarkModeToggle)
} }
export function darkModeEnabled() { export function useClassWatcher (elem, className) {
if (import.meta.server) return false const hasClass = ref(false)
return computed(() => document.body.classList.contains('dark'))
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) { function handleDarkModeToggle (enabled) {
@ -32,20 +123,27 @@ function handleDarkModeToggle (enabled) {
// if we received an event // if we received an event
enabled = enabled.matches enabled = enabled.matches
} }
enabled ? darkModeNodeParent.classList.add('dark') : darkModeNodeParent.classList.remove('dark') enabled
? darkModeNodeParent.classList.add('dark')
: darkModeNodeParent.classList.remove('dark')
} }
export function disableDarkMode () { export function disableDarkMode () {
if (import.meta.server) return if (import.meta.server)
return
const body = document.body const body = document.body
body.classList.remove('dark') body.classList.remove('dark')
// Remove event listener // 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) { export function handleTransparentMode (transparentModeEnabled) {
if (import.meta.server) return if (import.meta.server)
if (!useIsIframe() || !transparentModeEnabled) return return
if (!useIsIframe() || !transparentModeEnabled)
return
const app = document.getElementById('app') const app = document.getElementById('app')
app.classList.remove('bg-white') app.classList.remove('bg-white')
@ -54,8 +152,11 @@ export function handleTransparentMode (transparentModeEnabled) {
} }
export function focusOnFirstFormElement () { export function focusOnFirstFormElement () {
if (import.meta.server) return if (import.meta.server)
for (const ele of document.querySelectorAll('input,button,textarea,[role="button"]')) { return
for (const ele of document.querySelectorAll(
'input,button,textarea,[role="button"]'
)) {
if (ele.offsetWidth !== 0 || ele.offsetHeight !== 0) { if (ele.offsetWidth !== 0 || ele.offsetHeight !== 0) {
ele.focus() ele.focus()
break break

View File

@ -40,7 +40,7 @@
<loader class="h-6 w-6 text-nt-blue mx-auto"/> <loader class="h-6 w-6 text-nt-blue mx-auto"/>
</p> </p>
</div> </div>
<open-complete-form v-show="!recordLoading" ref="openCompleteForm" :form="form" class="mb-10" <open-complete-form v-show="!recordLoading" ref="openCompleteForm" :form="form" class="mb-10" :dark-mode="darkMode"
@password-entered="passwordEntered" @password-entered="passwordEntered"
/> />
</template> </template>
@ -53,12 +53,18 @@ import {computed} from 'vue'
import OpenCompleteForm from "~/components/open/forms/OpenCompleteForm.vue" import OpenCompleteForm from "~/components/open/forms/OpenCompleteForm.vue"
import sha256 from 'js-sha256' import sha256 from 'js-sha256'
import { onBeforeRouteLeave } from 'vue-router' import { onBeforeRouteLeave } from 'vue-router'
import {disableDarkMode, handleDarkMode, handleTransparentMode, focusOnFirstFormElement} from '~/lib/forms/public-page' import {
disableDarkMode,
handleDarkMode,
handleTransparentMode,
focusOnFirstFormElement,
useDarkMode
} from '~/lib/forms/public-page'
const crisp = useCrisp() const crisp = useCrisp()
const formsStore = useFormsStore() const formsStore = useFormsStore()
const recordsStore = useRecordsStore() const recordsStore = useRecordsStore()
const darkMode = useDarkMode()
const isIframe = useIsIframe() const isIframe = useIsIframe()
const formLoading = computed(() => formsStore.loading) const formLoading = computed(() => formsStore.loading)
const recordLoading = computed(() => recordsStore.loading) const recordLoading = computed(() => recordsStore.loading)