Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Create use-cookies composable
Browse files Browse the repository at this point in the history
  • Loading branch information
obulat committed Nov 4, 2022
1 parent d364848 commit fe40051
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 80 deletions.
39 changes: 14 additions & 25 deletions src/components/VHeaderOld/VHeaderFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ import {
import { Portal as VTeleport } from 'portal-vue'
import { NuxtAppOptions } from '@nuxt/types'
import { useBodyScrollLock } from '~/composables/use-body-scroll-lock'
import { useFocusFilters } from '~/composables/use-focus-filters'
import { Focus } from '~/utils/focus-management'
import { defineEvent } from '~/types/emits'
import { UiCookieSetter, useUiStore } from '~/stores/ui'
import { UiStateCookie, useUiStore } from '~/stores/ui'
import useCookies from '~/composables/use-cookies'
import VModalContent from '~/components/VModal/VModalContent.vue'
import VSearchGridFilter from '~/components/VFilters/VSearchGridFilter.vue'
Expand Down Expand Up @@ -87,17 +87,18 @@ export default defineComponent({
},
setup(props, { emit }) {
const nodeRef = ref<HTMLElement | null>(null)
const disabledRef = toRef(props, 'disabled')
const { app } = useContext()
const disabledRef = toRef(props, 'disabled')
const uiStore = useUiStore()
const uiCookies = useCookies<UiStateCookie>(app, 'ui')
const isDesktopLayout = computed(() => uiStore.isDesktopLayout)
const shouldShowFilters = computed(
() => !disabledRef.value && uiStore.isFilterVisible
() => !disabledRef.value && uiStore.shouldShowFilter
)
watch(shouldShowFilters, (visible) => {
emit(visible ? 'open' : 'close')
Expand All @@ -106,35 +107,23 @@ export default defineComponent({
onBeforeUnmount(() => unlock())
/**
* TODO: we do not need to set filters to dismissed if search type is disabled
*/
watch(disabledRef, (disabled) => {
if (disabled && uiStore.isFilterVisible) {
uiStore.setFiltersState(false, uiCookieSetter)
if (disabled && uiStore.shouldShowFilter) {
uiStore.setFiltersState(false, uiCookies.setCookie)
}
})
// Lock the scroll when the screen changes to below Md if filters are open.
watch(isDesktopLayout, (isDesktop) => {
if (!isDesktop && uiStore.isFilterVisible) {
if (!isDesktop && uiStore.shouldShowFilter) {
lock()
}
})
/**
* Returns a function that can set the app `ui` cookies.
* Used by the pinia ui store because the `app.$cookies.set` can only
* be called from within the `setup` function.
* @param {NuxtAppOptions} app
*/
const uiCookieSetter = (app: NuxtAppOptions): UiCookieSetter => {
return (cookie) => app.$cookies.set('ui', cookie, { sameSite: true })
}
const onTriggerClick = () => {
const uiCookie = uiStore.toggleFilters(uiCookieSetter)
if (uiCookie) {
app.$cookies.set('ui', uiCookie, { sameSite: true })
}
}
const onTriggerClick = () => uiStore.toggleFilters(uiCookies.setCookie)
const focusFilters = useFocusFilters()
/**
Expand Down
31 changes: 31 additions & 0 deletions src/composables/use-cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { NuxtAppOptions } from '@nuxt/types'

type CookieValue = string | Record<string, boolean | string | undefined>

export const useCookies = <T extends CookieValue>(
app: NuxtAppOptions,
name: string
) => {
/**
* Sets the cookie with the `value`, using the useCookie's `name`.
*
* @param value - if not a string, this value is converted to string using
* `JSON.stringify`.
*/
const setCookie = (value: T) => {
const cookieValue =
typeof value === 'string' ? value : JSON.stringify(value)

app.$cookies.set(name, cookieValue, { sameSite: true })
}

const getCookie = (): string | undefined => {
return app.$cookies.get(name)
}

return {
setCookie,
getCookie,
}
}
export default useCookies
31 changes: 11 additions & 20 deletions src/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<VGlobalAudioSection />
</div>
</template>
<script>
<script lang="ts">
import {
computed,
onMounted,
Expand All @@ -61,9 +61,10 @@ import {
} from '~/composables/use-match-routes'
import { isMinScreen } from '~/composables/use-media-query'
import { useBrowserIsMobile } from '~/composables/use-browser-detection'
import useCookies from '~/composables/use-cookies'
import { useFeatureFlagStore } from '~/stores/feature-flag'
import { useUiStore } from '~/stores/ui'
import { useUiStore, UiStateCookie } from '~/stores/ui'
import { IsHeaderScrolledKey, IsSidebarVisibleKey } from '~/types/provides'
Expand Down Expand Up @@ -99,6 +100,8 @@ const embeddedPage = {
const uiStore = useUiStore()
const featureFlagStore = useFeatureFlagStore()
const uiCookies = useCookies<UiStateCookie>(app, 'ui')
const isNewHeaderEnabled = computed(() =>
featureFlagStore.isOn('new_header')
)
Expand All @@ -109,19 +112,8 @@ const embeddedPage = {
const desktopBreakpoint = isNewHeaderEnabled.value ? 'lg' : 'md'
const isDesktop = isMinScreen(desktopBreakpoint, { shouldPassInSSR })
/**
* Returns a function that can set the app `ui` cookies.
* Used by the pinia ui store because the `app.$cookies.set` can only
* be called from within the `setup` function.
* @param {NuxtAppOptions} app
* @returns {UiCookieSetter}
*/
const uiCookieSetter = (app) => {
return (cookie) => app.$cookies.set('ui', cookie, { sameSite: true })
}
watch(isDesktop, (isDesktop) => {
uiStore.updateBreakpoint(isDesktop, isMobileUa, uiCookieSetter(app))
uiStore.updateBreakpoint(isDesktop, isMobileUa, uiCookies.setCookie)
})
/**
Expand All @@ -135,7 +127,7 @@ const embeddedPage = {
uiStore.updateBreakpoint(
isDesktop.value,
isMobileUa,
uiCookieSetter(app)
uiCookies.setCookie
)
}
})
Expand All @@ -147,11 +139,10 @@ const embeddedPage = {
)
const isDesktopLayout = computed(() => uiStore.isDesktopLayout)
const isFilterVisible = computed(() => uiStore.isFilterVisible)
const isSidebarVisible = computed(() =>
Boolean(
isSearchRoute.value && isFilterVisible.value && isDesktopLayout.value
)
const isSidebarVisible = computed(
() =>
isSearchRoute.value && uiStore.shouldShowFilter && isDesktopLayout.value
)
const isHeaderScrolled = ref(false)
Expand Down
21 changes: 6 additions & 15 deletions src/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,6 @@ import {
watch,
} from '@nuxtjs/composition-api'
import { NuxtAppOptions } from '@nuxt/types'
import {
ALL_MEDIA,
searchPath,
Expand All @@ -146,11 +144,12 @@ import {
import { useBrowserIsMobile } from '~/composables/use-browser-detection'
import { isMinScreen } from '~/composables/use-media-query'
import useCookies from '~/composables/use-cookies'
import { useFeatureFlagStore } from '~/stores/feature-flag'
import { useMediaStore } from '~/stores/media'
import { useSearchStore } from '~/stores/search'
import { useUiStore } from '~/stores/ui'
import { UiStateCookie, useUiStore } from '~/stores/ui'
import VLink from '~/components/VLink.vue'
import VLogoButtonOld from '~/components/VHeaderOld/VLogoButtonOld.vue'
Expand Down Expand Up @@ -191,19 +190,10 @@ export default defineComponent({
const isDesktop = isMinScreen(desktopBreakpoint, { shouldPassInSSR })
/**
* Returns a function that can set the app `ui` cookies.
* Used by the pinia ui store because the `app.$cookies.set` can only
* be called from within the `setup` function.
* @param {NuxtAppOptions} app
* @returns {function(bpValues: object): void}
*/
const uiCookieSetter = (app: NuxtAppOptions) => {
return (bpValues) => app.$cookies.set('ui', bpValues, { sameSite: true })
}
const uiCookies = useCookies<UiStateCookie>(app, 'ui')
watch(isDesktop, (isDesktop) => {
uiStore.updateBreakpoint(isDesktop, isMobileUa, uiCookieSetter(app))
uiStore.updateBreakpoint(isDesktop, isMobileUa, uiCookies.setCookie)
})
/**
Expand All @@ -212,11 +202,12 @@ export default defineComponent({
onMounted(() => {
searchStore.$reset()
mediaStore.$reset()
if (isDesktop.value !== uiStore.isDesktopLayout) {
uiStore.updateBreakpoint(
isDesktop.value,
isMobileUa,
uiCookieSetter(app)
uiCookies.setCookie
)
}
})
Expand Down
55 changes: 35 additions & 20 deletions src/stores/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export interface UiStateCookie {
isFilterDismissed?: boolean
}

export type UiCookieSetter = (cookie: UiStateCookie) => void

export interface UiState {
/**
* whether to show the instructions snackbar.
Expand All @@ -42,6 +40,8 @@ export interface UiState {
isMobileUa: boolean
}

type UiCookieSetter = (value: UiStateCookie) => void

export const useUiStore = defineStore('ui', {
state: (): UiState => ({
instructionsSnackbarState: NOT_SHOWN,
Expand All @@ -62,6 +62,26 @@ export const useUiStore = defineStore('ui', {
isFilterDismissed: state.isFilterDismissed,
}
},
/**
* On desktop, we only hide the filters sidebar if it was
* specifically dismissed on the desktop layout.
*
* The filters state could diverge if the layout changed from desktop to mobile:
* closing the filters on mobile sets `isFilterVisible` to false, but does not
* save the `dismissed` because it only affects the desktop layout.
*
* Here, we first correct the state if it diverged, and then return visibility state.
*/
shouldShowFilter(state): boolean {
if (
state.isDesktopLayout &&
!state.isFilterDismissed &&
!state.isFilterVisible
) {
state.isFilterVisible = true
}
return state.isFilterVisible
},
},

actions: {
Expand Down Expand Up @@ -93,16 +113,16 @@ export const useUiStore = defineStore('ui', {
},
/**
* Sets the breakpoint and user agent state, and saves it into app cookies.
*
* @param isDesktopLayout - whether the layout is desktop (`lg` with the `new_header`
* and `md` with the `old_header`).
* @param isMobileUa - whether the request's user agent is `mobile` or not.
* @param cookieSetter - sets the app cookie. Has to be a function because `app.$cookies.set`
* can only be called from inside the `setup` function.
* @param setCookieFn - sets the app cookie.
*/
updateBreakpoint(
isDesktopLayout: boolean,
isMobileUa: boolean,
cookieSetter: (cookie: UiStateCookie) => void
setCookieFn: UiCookieSetter
) {
if (
this.isDesktopLayout === isDesktopLayout &&
Expand All @@ -114,33 +134,28 @@ export const useUiStore = defineStore('ui', {
this.isDesktopLayout = isDesktopLayout
this.isMobileUa = isMobileUa

cookieSetter(this.uiCookie)
setCookieFn(this.uiCookie)
},
/**
* Sets the filter state based on the `visible` parameter,
* and saves the state in a cookie.
* Closing the filters sets the state to `dismissed` on desktop,
* but `shown_on_desktop` on mobile.
* Sets the filter state based on the `visible` parameter.
* If the filter state is changed on desktop, the `isFilterDismissed`
* 'ui' cookie value is set accordingly.
*
* @param visible - whether the filters should be visible.
* @param cookieSetter - the function that sets the app cookies
* @param setCookieFn - the function that sets the app cookies
*/
setFiltersState(visible: boolean, cookieSetter: UiCookieSetter) {
setFiltersState(visible: boolean, setCookieFn: UiCookieSetter) {
this.isFilterVisible = visible
if (this.isDesktopLayout) {
this.isFilterDismissed = !visible
cookieSetter(this.uiCookie)
setCookieFn(this.uiCookie)
}
},
/**
* Toggles filter state. If the desktop layout is used,
* returns a new cookies value to set in the calling function.
*
* The calling function must be inside the `setup` function and
* must set the cookies if the return value is not `null`.
* Toggles filter state and saves the new state in a cookie.
*/
toggleFilters(cookieSetter: UiCookieSetter) {
return this.setFiltersState(!this.isFilterVisible, cookieSetter)
toggleFilters(setCookieFn: UiCookieSetter) {
this.setFiltersState(!this.isFilterVisible, setCookieFn)
},
},
})

0 comments on commit fe40051

Please sign in to comment.