Skip to content

Commit

Permalink
fix(ssr): fix ssr for overlay type components (jd-solanki#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
jd-solanki committed Nov 29, 2022
1 parent 5253c6a commit d5f2e78
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 89 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
"cSpell.words": [
"composables",
"Vite",
"vitepress"
"vitepress",
"Vuetify's"
],
// Extension: UnoCSS
"unocss.root": "packages/documentation",
Expand Down
2 changes: 1 addition & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# a base has not been set. This sample publishes the directory
# located at the absolute path "root/project/build-output"

publish = "packages/documentation/docs/.vitepress/dist"
publish = "docs/.vitepress/dist"
command = "npx pnpm i --store=node_modules/.pnpm-store && npm run docs:build"

# Default build command.
Expand Down
2 changes: 1 addition & 1 deletion packages/anu-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
},
"peerDependencies": {
"@unocss/reset": "^0.41.1",
"@vueuse/core": "^8.7.5",
"@vueuse/core": "^9.6.0",
"vue-router": "4"
}
}
48 changes: 27 additions & 21 deletions packages/anu-vue/src/components/dialog/ADialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { onClickOutside } from '@vueuse/core'
import { onClickOutside, useMounted } from '@vueuse/core'
import { Teleport, Transition, defineComponent, ref, toRef } from 'vue'
import { ACard, useCardProps } from '@/components/card'
import { useDOMScrollLock } from '@/composables/useDOMScrollLock'
import { useTeleport } from '@/composables/useTeleport'

export const ADialog = defineComponent({
name: 'ADialog',
Expand All @@ -27,6 +28,9 @@ export const ADialog = defineComponent({
},
emits: ['update:modelValue'],
setup(props, { slots, emit, attrs }) {
const { teleportTarget } = useTeleport()
const isMounted = useMounted()

const refCard = ref()
if (!props.persistent) {
onClickOutside(refCard, () => {
Expand All @@ -41,26 +45,28 @@ export const ADialog = defineComponent({
// Lock DOM scroll when modelValue is `true`
useDOMScrollLock(toRef(props, 'modelValue'))

return () => <Teleport to="body">
<Transition name="bg">
<div
class={['a-dialog-wrapper grid uno-layer-base-place-items-center fixed uno-layer-base-inset-0 bg-[hsla(var(--a-overlay-color),var(--a-overlay-opacity))]']}
v-show={props.modelValue}
>
<Transition name="scale">
<ACard
{...attrs}
class="a-dialog backface-hidden transform translate-z-0 max-w-[calc(100vw-2rem)]"
ref={refCard}
v-show={props.modelValue}
{...props}
>
{{ ...slots }}
</ACard>
</Transition>
</div>
</Transition>
</Teleport>
return () => isMounted.value
? <Teleport to={teleportTarget.value}>
<Transition name="bg">
<div
class={['a-dialog-wrapper grid uno-layer-base-place-items-center fixed uno-layer-base-inset-0 bg-[hsla(var(--a-overlay-color),var(--a-overlay-opacity))]']}
v-show={props.modelValue}
>
<Transition name="scale">
<ACard
{...attrs}
class="a-dialog backface-hidden transform translate-z-0 max-w-[calc(100vw-2rem)]"
ref={refCard}
v-show={props.modelValue}
{...props}
>
{{ ...slots }}
</ACard>
</Transition>
</div>
</Transition>
</Teleport>
: null
},
})

Expand Down
60 changes: 33 additions & 27 deletions packages/anu-vue/src/components/drawer/ADrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { onClickOutside } from '@vueuse/core'
import { onClickOutside, useMounted } from '@vueuse/core'
import type { PropType } from 'vue'
import { Teleport, Transition, defineComponent, ref, toRef } from 'vue'
import { useDOMScrollLock } from '@/composables/useDOMScrollLock'
import { ACard, useCardProps } from '@/components/card'
import { useDOMScrollLock } from '@/composables/useDOMScrollLock'
import { useTeleport } from '@/composables/useTeleport'

export const ADrawer = defineComponent({
name: 'ADrawer',
Expand Down Expand Up @@ -35,6 +36,9 @@ export const ADrawer = defineComponent({
},
emits: ['update:modelValue'],
setup(props, { slots, emit, attrs }) {
const { teleportTarget } = useTeleport()
const isMounted = useMounted()

const refCard = ref()
if (!props.persistent) {
onClickOutside(refCard, () => {
Expand All @@ -49,38 +53,40 @@ export const ADrawer = defineComponent({
// Lock DOM scroll when modelValue is `true`
useDOMScrollLock(toRef(props, 'modelValue'))

return () => <Teleport to="body">
<Transition name="bg">
<div
class={[
'a-drawer-wrapper flex fixed uno-layer-base-inset-0 bg-[hsla(var(--a-overlay-color),var(--a-overlay-opacity))]',
return () => isMounted.value
? <Teleport to={teleportTarget.value}>
<Transition name="bg">
<div
class={[
'a-drawer-wrapper flex fixed uno-layer-base-inset-0 bg-[hsla(var(--a-overlay-color),var(--a-overlay-opacity))]',
`a-drawer-anchor-${props.anchor}`,

// `flex-col` set full width for top & bottom anchored drawer
['top', 'bottom'].includes(props.anchor) && 'flex-col',

// set drawer to end of flex container of anchor is right or bottom
['right', 'bottom'].includes(props.anchor) && 'justify-end',
]}
v-show={props.modelValue}
>
<Transition name={`slide-${props.anchor === 'bottom' ? 'up' : props.anchor === 'top' ? 'down' : props.anchor}`}>
<ACard
{...attrs}
{...props}
class={[
'a-drawer backface-hidden transform translate-z-0',
props.anchor === 'bottom' && '[--a-transition-slide-up-transform:100%]',
]}
ref={refCard}
v-show={props.modelValue}
>
{{ ...slots }}
</ACard>
</Transition>
</div>
</Transition>
</Teleport>
]}
v-show={props.modelValue}
>
<Transition name={`slide-${props.anchor === 'bottom' ? 'up' : props.anchor === 'top' ? 'down' : props.anchor}`}>
<ACard
{...attrs}
{...props}
class={[
'a-drawer backface-hidden transform translate-z-0',
props.anchor === 'bottom' && '[--a-transition-slide-up-transform:100%]',
]}
ref={refCard}
v-show={props.modelValue}
>
{{ ...slots }}
</ACard>
</Transition>
</div>
</Transition>
</Teleport>
: null
},
})

Expand Down
51 changes: 32 additions & 19 deletions packages/anu-vue/src/components/menu/AMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Middleware, Placement, Strategy } from '@floating-ui/dom'
import { autoUpdate, computePosition, flip, shift } from '@floating-ui/dom'
import { onClickOutside, useEventListener } from '@vueuse/core'
import { onClickOutside, useEventListener, useMounted } from '@vueuse/core'
import type { PropType } from 'vue'
import { Teleport, Transition, defineComponent, getCurrentInstance, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { Teleport, Transition, defineComponent, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { sameWidth as sameWidthMiddleware } from './middlewares'
import { useTeleport } from '@/composables/useTeleport'
import { ACard } from '@/components'

export const AMenu = defineComponent({
Expand Down Expand Up @@ -71,6 +72,9 @@ export const AMenu = defineComponent({
},
},
setup(props, { slots }) {
const { teleportTarget } = useTeleport()
const isMounted = useMounted()

const isMenuVisible = ref(props.modelValue ?? false)

// Template refs
Expand Down Expand Up @@ -110,15 +114,22 @@ export const AMenu = defineComponent({
}

let floatingUiCleanup: Function

onMounted(() => {
const vm = getCurrentInstance()
refReference.value = vm?.proxy?.$el?.parentNode

// Recalculate position if placement changes at runtime
watch(() => props.placement, () => {
floatingUiCleanup = autoUpdate(refReference.value, refFloating.value.$el, calculateFloatingPosition)
}, { immediate: true })
})

// Recalculate position if placement changes at runtime
watch(
[isMounted, () => props.placement],
() => {
console.log('watch triggered')
nextTick(() => {
floatingUiCleanup = autoUpdate(refReference.value, refFloating.value.$el, calculateFloatingPosition)
})
},
)
onBeforeUnmount(() => floatingUiCleanup())

// 馃憠 Event listeners
Expand Down Expand Up @@ -155,18 +166,20 @@ export const AMenu = defineComponent({
}
}

return () => <Teleport to="body">
{/* 鈩癸笍 Transition component don't accept null as value of name prop so we need `props.transition || undefined` */}
<Transition name={props.transition || undefined}>
<ACard
class={['a-menu', props.strategy === 'fixed' ? 'fixed' : 'absolute']}
ref={refFloating}
v-show={props.modelValue ?? isMenuVisible.value}
>
{slots.default?.()}
</ACard>
</Transition>
</Teleport>
return () => isMounted.value
? <Teleport to={teleportTarget.value}>
{/* 鈩癸笍 Transition component don't accept null as value of name prop so we need `props.transition || undefined` */}
<Transition name={props.transition || undefined}>
<ACard
class={['a-menu', props.strategy === 'fixed' ? 'fixed' : 'absolute']}
ref={refFloating}
v-show={props.modelValue ?? isMenuVisible.value}
>
{slots.default?.()}
</ACard>
</Transition>
</Teleport>
: null
},
})

Expand Down
44 changes: 27 additions & 17 deletions packages/anu-vue/src/components/select/ASelect.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'
import { onClickOutside } from '@vueuse/core'
import { onClickOutside, useMounted } from '@vueuse/core'
import type { PropType } from 'vue'
import { Teleport, computed, defineComponent, onBeforeUnmount, onMounted, ref } from 'vue'
import { Teleport, computed, defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { isObject } from '@/utils/helpers'
import { useTeleport } from '@/composables/useTeleport'
import { ABaseInput, useBaseInputProp } from '@/components/base-input'

interface ObjectOption { label: string; value: string | number }
Expand Down Expand Up @@ -51,6 +52,9 @@ export const ASelect = defineComponent({
},
emits: ['input', 'update:modelValue'],
setup(props, { slots, emit, attrs }) {
const { teleportTarget } = useTeleport()
const isMounted = useMounted()

// SECTION Floating
// Template refs
const refReference = ref()
Expand Down Expand Up @@ -87,7 +91,9 @@ export const ASelect = defineComponent({

let floatingUiCleanup: Function = () => { }
onMounted(() => {
floatingUiCleanup = autoUpdate(refReference.value.refInputContainer, refFloating.value, calculateFloatingPosition)
nextTick(() => {
floatingUiCleanup = autoUpdate(refReference.value.refInputContainer, refFloating.value, calculateFloatingPosition)
})
})
onBeforeUnmount(() => floatingUiCleanup())

Expand Down Expand Up @@ -157,19 +163,21 @@ export const ASelect = defineComponent({
/>,
}}
</ABaseInput>
<Teleport to="body">
<ul
class={[
'a-select-options-container absolute bg-[hsl(var(--a-layer))]',
props.optionsWrapperClasses,
]}
onClick={closeOptions}
ref={refFloating}
v-show={isOptionsVisible.value}
>
{
{
isMounted.value
? <Teleport to={teleportTarget.value}>
<ul
class={[
'a-select-options-container absolute bg-[hsl(var(--a-layer))]',
props.optionsWrapperClasses,
]}
onClick={closeOptions}
ref={refFloating}
v-show={isOptionsVisible.value}
>
{
slots.default
? slots.default?.({
? slots.default({
attrs: {
class: optionClasses,
},
Expand All @@ -183,8 +191,10 @@ export const ASelect = defineComponent({
</li>
))
}
</ul>
</Teleport>
</ul>
</Teleport>
: null
}
</>
},
})
Expand Down
38 changes: 38 additions & 0 deletions packages/anu-vue/src/composables/useTeleport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 鈩癸笍 Inspired from Vuetify's useTeleport composable

import type { Ref } from 'vue'
import { computed, unref } from 'vue'

export function useTeleport(target?: Ref<string | Element>) {
const teleportTarget = computed(() => {
const _target = unref(target)

if (typeof window === 'undefined')
return undefined

const targetElement
= _target === undefined
? document.body
: typeof _target === 'string'
? document.querySelector(_target)
: _target

if (targetElement == null) {
console.warn(`Unable to locate target ${_target}`)

return undefined
}

if (!useTeleport.cache.has(targetElement)) {
const el = document.createElement('div')
el.id = 'a-teleport-target'
targetElement.appendChild(el)
useTeleport.cache.set(targetElement, el)
}

return useTeleport.cache.get(targetElement)
})

return { teleportTarget }
}
useTeleport.cache = new WeakMap<Element, Element>()
Loading

0 comments on commit d5f2e78

Please sign in to comment.