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"
: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) {

View File

@ -41,6 +41,7 @@
:data-form="dataForm"
:data-form-value="dataFormValue"
:theme="theme"
:dark-mode="darkMode"
:admin-preview="adminPreview"
/>
</template>
@ -51,7 +52,7 @@
<!-- Captcha -->
<template v-if="form.use_captcha && isLastPage">
<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" />
</div>
</template>
@ -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)
}
},

View File

@ -56,7 +56,6 @@
<script>
import { computed } from 'vue'
import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js"
import { darkModeEnabled } from '~/lib/forms/public-page.js'
import { default as _has } from 'lodash/has'
export default {
@ -83,6 +82,10 @@ export default {
type: Boolean,
default: false
},
darkMode: {
type: Boolean,
default: false
},
field: {
type: Object,
required: true
@ -177,9 +180,6 @@ export default {
},
fieldSideBarOpened() {
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,
maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : 2000,
showCharLimit: field.show_char_limit || false,
isDark: this.isDark
isDark: this.darkMode
}
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')}"
:creating="creating"
:form="form"
:dark-mode="darkMode"
:admin-preview="true"
@restarted="previewFormSubmitted=false"
@submitted="previewFormSubmitted=true"
@ -61,7 +62,7 @@
<script>
import VSwitch from '../../../../forms/components/VSwitch.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'
export default {
@ -69,8 +70,11 @@ export default {
props: {},
setup () {
const workingFormStore = useWorkingFormStore()
const parent = ref(null)
return {
workingFormStore
workingFormStore,
parent: parent,
darkMode: useDarkMode(parent)
}
},
data () {

View File

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

View File

@ -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

View File

@ -40,7 +40,7 @@
<loader class="h-6 w-6 text-nt-blue mx-auto"/>
</p>
</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"
/>
</template>
@ -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)