Skip to content

Commit

Permalink
refactor: portal
Browse files Browse the repository at this point in the history
  • Loading branch information
productdevbook committed Aug 16, 2023
1 parent 7146a8f commit b291126
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 114 deletions.
51 changes: 22 additions & 29 deletions packages/components/portal/src/Portal.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
import { Primitive, PrimitiveProps } from '@oku-ui/primitive'
import { Primitive, primitiveProps } from '@oku-ui/primitive'
import type {
ComponentPublicInstanceRef,
ElementType,
IPrimitiveProps,
MergeProps,
PrimitiveProps,

} from '@oku-ui/primitive'
import { useComposedRefs, useForwardRef } from '@oku-ui/use-composable'
import { defineComponent, h, ref, render, toRefs } from 'vue'
import { Teleport, defineComponent, h, ref, toRefs } from 'vue'

const PORTAL_NAME = 'OkuPortal'

type PortalElement = ElementType<'div'>
export type _PortalEl = HTMLDivElement
export type PortalElementIntrinsicElement = ElementType<'div'>
export type PortalElement = HTMLDivElement

interface PortalProps extends IPrimitiveProps {
interface PortalProps extends PrimitiveProps {
/**
* An optional container where the portaled content should be appended.
*/
container?: HTMLElement | null
}

const Portal = defineComponent({
const portal = defineComponent({
name: PORTAL_NAME,
inheritAttrs: false,
props: {
container: {
type: Object as () => HTMLElement | null | undefined,
default: () => globalThis.document.body,
},
...PrimitiveProps,
...primitiveProps,
},
setup(props, { slots, attrs }) {
const { asChild, container } = toRefs(props)
Expand All @@ -40,30 +40,23 @@ const Portal = defineComponent({
const forwardedRef = useForwardRef()
const composedRefs = useComposedRefs(portalRef, forwardedRef)

return () => {
if (container.value && slots.default) {
const content = h(
return () => container.value && slots.default
? h(Teleport, { to: container.value }, [
h(
Primitive.div,
{ ref: composedRefs, asChild: asChild.value, ...portalAttrs },
slots.default(),
)

const divElement = document.createElement('div')

render(content, divElement)

container.value.appendChild(divElement)
}

return null
}
{
default: () => slots.default?.(),
},
),
])
: null
},
})

type _Portal = MergeProps<PortalProps, PortalElement>

const OkuPortal = Portal as typeof Portal & (new () => { $props: _Portal })

export { OkuPortal }
export const OkuPortal = portal as typeof portal &
(new () => {
$props: Partial<PortalElement>
})

export type { PortalProps }
10 changes: 8 additions & 2 deletions packages/components/portal/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export { OkuPortal } from './Portal'
export type { PortalProps } from './Portal'
export {
OkuPortal,
} from './Portal'
export type {
PortalProps,
PortalElement,
PortalElementIntrinsicElement,
} from './Portal'
14 changes: 11 additions & 3 deletions packages/components/portal/src/portal.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import type { Component } from 'vue'
import { h } from 'vue'
import { OkuPortal } from './Portal'

const component = {
setup(props, { attrs, slots }) {
return () => h(OkuPortal, { ...attrs }, slots)
},
} as Component

describe('OkuPortal', () => {
it('teleports content to the specified container', async () => {
const container = document.createElement('div')
document.body.appendChild(container)

const wrapper = mount(OkuPortal, {
const wrapper = mount(component, {
props: {
container,
},
Expand All @@ -22,7 +30,7 @@ describe('OkuPortal', () => {
})

it('uses document body as the default container', async () => {
const wrapper = mount(OkuPortal, {
const wrapper = mount(component, {
slots: {
default: 'Portal Content',
},
Expand All @@ -34,7 +42,7 @@ describe('OkuPortal', () => {
})

it('renders content as a child when asChild prop is provided', async () => {
const wrapper = mount(OkuPortal, {
const wrapper = mount(component, {
props: {
asChild: true,
},
Expand Down
2 changes: 0 additions & 2 deletions packages/components/slot/src/slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ const OkuSlot = defineComponent({
})
}
else if (defaultSlot) {
if (defaultSlot.length > 1)
console.warn(`[OkuSlot] ${NAME} can only have one child`)
const [child] = defaultSlot
const slot = cloneVNode(child, { ...mergeProps(attrs, props), ref: composedRefs }, true)

Expand Down
73 changes: 0 additions & 73 deletions packages/core/primitive/src/primitive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,77 +368,4 @@ describe('Primitive', () => {
})
expect(wrapper.html()).toBe('<div id="test" class="text-red-500" disabled="true">Oku</div>')
})

test('asChild with 2 children', () => {
const wrapper = () => mount(componentDiv, {
props: {
asChild: true,
},
slots: {
default: `
<div>Oku</div>
<div>Oku</div>
`,
},
})

expect(() => wrapper()).toThrowError(/can only have one child/)
})

test('asChild with 2 children and attrs', () => {
const wrapper = () => mount(componentDiv, {
props: {
asChild: true,
disabled: true,
},
attrs: {
id: 'test',
class: 'text-red-500',
},
slots: {
default: `
<div>Oku</div>
<div>Oku</div>
`,
},
})

expect(() => wrapper()).toThrowError(/can only have one child/)
})

test('asChild with default 3 children', () => {
const wrapper = () => mount(componentDiv, {
props: {
asChild: true,
disabled: true,
disabled2: true,
},
slots: {
default: `
<div>Oku</div>
<Hello>Oku</Hello>
<Another>Oku</Another>
`,
},
})
expect(() => wrapper()).toThrowError(/can only have one child/)
})

test('asChild with default 3 children', () => {
const wrapper = () => mount(componentDiv, {
props: {
asChild: true,
disabled: true,
disabled2: true,
},
slots: {
default: `
<div>Oku</div>
<Hello>Oku</Hello>
<Another>Oku</Another>
`,
},
})
expect(() => wrapper()).toThrowError(/can only have one child/)
})
})
5 changes: 0 additions & 5 deletions packages/core/primitive/src/primitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ const Primitive = NODES.reduce((primitive, node) => {

const Tag: any = asChild ? OkuSlot : node
return () => {
const defaultSlot = slots.default?.()

if (asChild && defaultSlot?.length && defaultSlot?.length > 1)
throw new Error(`The ${node} component can only have one child`)

const mergedProps = mergeProps(attrs, primitiveProps)
return createVNode(Tag, { ...mergedProps, ref: composedRefs }, {
default: () => slots.default?.(),
Expand Down

0 comments on commit b291126

Please sign in to comment.