Skip to content

Commit

Permalink
Prevent layout shift by removing <ClientOnly> on the theme selector (
Browse files Browse the repository at this point in the history
…#5116)

Co-authored-by: Olga Bulat <[email protected]>
  • Loading branch information
dhruvkb and obulat authored Oct 31, 2024
1 parent 403596c commit 040906e
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 24 deletions.
4 changes: 1 addition & 3 deletions frontend/src/components/VFooter/VFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ const linkColumnHeight = computed(() => ({
v-bind="languageProps"
class="language max-w-full border-secondary"
/>
<ClientOnly>
<VThemeSelect class="border-secondary" />
</ClientOnly>
<VThemeSelect class="border-secondary" />
</div>
</template>
<template v-else>
Expand Down
49 changes: 28 additions & 21 deletions frontend/src/components/VThemeSelect/VThemeSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ light, dark and system.
<script setup lang="ts">
import { useI18n, useDarkMode } from "#imports"

import { computed, type ComputedRef } from "vue"
import { computed, ref, onMounted, watch, type Ref } from "vue"

import { useUiStore } from "~/stores/ui"
import { useUiStore, type ColorMode } from "~/stores/ui"

import VIcon from "~/components/VIcon/VIcon.vue"
import VSelectField, {
Expand All @@ -26,14 +26,13 @@ const THEME_ICON_NAME = Object.freeze({
const THEME_TEXT = {
light: i18n.t(`theme.choices.light`),
dark: i18n.t(`theme.choices.dark`),
system: i18n.t(`theme.choices.system`),
}

const colorMode = computed({
get: () => uiStore.colorMode,
set: (value) => {
uiStore.setColorMode(value)
},
})
const colorMode: Ref<ColorMode> = ref(uiStore.colorMode)
const handleUpdateModelValue = (value: string) => {
uiStore.setColorMode(value as ColorMode)
}

const isDarkModeSeen = computed(() => uiStore.isDarkModeSeen)
const setIsDarkModeSeen = () => {
Expand All @@ -46,38 +45,46 @@ const darkMode = useDarkMode()
* The icon always reflects the actual theme applied to the site.
* Therefore, it must be based on the value of `effectiveColorMode`.
*/
const currentThemeIcon = computed(
() => THEME_ICON_NAME[darkMode.effectiveColorMode.value]
)
const currentThemeIcon: Ref<"sun" | "moon" | undefined> = ref(undefined)

/**
* The choices are computed because the text for the color mode choice
* "system" is dynamic and reflects the user's preferred color scheme at
* the OS-level.
*/
const choices: ComputedRef<Choice[]> = computed(() => {
const systemText = `${i18n.t(`theme.choices.system`)} (${THEME_TEXT[darkMode.osColorMode.value]})`
return [
{ key: "light", text: THEME_TEXT.light },
{ key: "dark", text: THEME_TEXT.dark },
{ key: "system", text: systemText },
]
})
const choices: Ref<Choice[]> = ref([
{ key: "light", text: THEME_TEXT.light },
{ key: "dark", text: THEME_TEXT.dark },
{ key: "system", text: THEME_TEXT.system },
])

const updateRefs = () => {
colorMode.value = uiStore.colorMode
currentThemeIcon.value = THEME_ICON_NAME[darkMode.effectiveColorMode.value]
choices.value[2].text = `${THEME_TEXT.system} (${THEME_TEXT[darkMode.osColorMode.value]})`
}

onMounted(updateRefs)
watch(
() => [darkMode.effectiveColorMode.value, darkMode.osColorMode.value],
updateRefs
)
</script>

<template>
<VSelectField
v-model="colorMode"
:model-value="colorMode"
field-id="theme"
:choices="choices"
:blank-text="$t('theme.theme')"
:label-text="$t('theme.theme')"
:show-selected="false"
:show-new-highlight="!isDarkModeSeen"
@click="setIsDarkModeSeen"
@update:model-value="handleUpdateModelValue"
>
<template #start>
<VIcon :name="currentThemeIcon" />
<VIcon v-if="currentThemeIcon" :name="currentThemeIcon" />
</template>
</VSelectField>
</template>

0 comments on commit 040906e

Please sign in to comment.