Skip to content

Commit 53c6508

Browse files
feat(Popover): add close method in slots (#5176)
Co-authored-by: Benjamin Canac <[email protected]>
1 parent 8099440 commit 53c6508

File tree

3 files changed

+23
-14
lines changed

3 files changed

+23
-14
lines changed

docs/content/docs/2.components/popover.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ name: 'popover-anchor-slot-example'
237237

238238
:component-slots
239239

240+
::note
241+
The `close` function is only available when `mode` is set to `click` because Reka UI exposes this for [`Popover`](https://reka-ui.com/docs/components/popover#close-using-slot-props) but not for [`HoverCard`](https://reka-ui.com/docs/components/hover-card).
242+
::
243+
240244
### Emits
241245

242246
:component-emits

playgrounds/nuxt/app/pages/components/popover.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ function send() {
2121
<UPopover arrow :content="{ side: 'top' }">
2222
<UButton label="Click me top" color="neutral" variant="outline" />
2323

24-
<template #content>
25-
<div class="w-48 h-16" />
24+
<template #content="{ close }">
25+
<div class="flex justify-center gap-2 p-4 w-48">
26+
<UButton label="Close" color="neutral" @click="close" />
27+
</div>
2628
</template>
2729
</UPopover>
2830

src/runtime/components/Popover.vue

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import type { EmitsToProps } from '../types/utils'
66
import type { ComponentConfig } from '../types/tv'
77
88
type Popover = ComponentConfig<typeof theme, AppConfig, 'popover'>
9+
type PopoverMode = 'click' | 'hover'
910
10-
export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps, 'openDelay' | 'closeDelay'> {
11+
export interface PopoverProps<M extends PopoverMode = PopoverMode> extends PopoverRootProps, Pick<HoverCardRootProps, 'openDelay' | 'closeDelay'> {
1112
/**
1213
* The display mode of the popover.
1314
* @defaultValue 'click'
1415
*/
15-
mode?: 'click' | 'hover'
16+
mode?: M
1617
/**
1718
* The content of the popover.
1819
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 }
@@ -47,14 +48,16 @@ export interface PopoverEmits extends PopoverRootEmits {
4748
'close:prevent': []
4849
}
4950
50-
export interface PopoverSlots {
51+
type SlotProps<M extends PopoverMode = PopoverMode> = [M] extends ['hover'] ? {} : { close: () => void }
52+
53+
export interface PopoverSlots<M extends PopoverMode = PopoverMode> {
5154
default(props: { open: boolean }): any
52-
content(props?: {}): any
53-
anchor(props?: {}): any
55+
content(props: SlotProps<M>): any
56+
anchor(props: SlotProps<M>): any
5457
}
5558
</script>
5659

57-
<script setup lang="ts">
60+
<script setup lang="ts" generic="M extends PopoverMode">
5861
import { computed, toRef } from 'vue'
5962
import { defu } from 'defu'
6063
import { useForwardPropsEmits } from 'reka-ui'
@@ -64,15 +67,15 @@ import { useAppConfig } from '#imports'
6467
import { usePortal } from '../composables/usePortal'
6568
import { tv } from '../utils/tv'
6669
67-
const props = withDefaults(defineProps<PopoverProps>(), {
70+
const props = withDefaults(defineProps<PopoverProps<M>>(), {
6871
portal: true,
69-
mode: 'click',
72+
mode: 'click' as never,
7073
openDelay: 0,
7174
closeDelay: 0,
7275
dismissible: true
7376
})
7477
const emits = defineEmits<PopoverEmits>()
75-
const slots = defineSlots<PopoverSlots>()
78+
const slots = defineSlots<PopoverSlots<M>>()
7679
7780
const appConfig = useAppConfig() as Popover['AppConfig']
7881
@@ -106,18 +109,18 @@ const Component = computed(() => props.mode === 'hover' ? HoverCard : Popover)
106109
</script>
107110

108111
<template>
109-
<Component.Root v-slot="{ open }" v-bind="rootProps">
112+
<Component.Root v-slot="{ open, close }: { open: boolean, close?: () => void }" v-bind="rootProps">
110113
<Component.Trigger v-if="!!slots.default || !!reference" as-child :reference="reference" :class="props.class">
111114
<slot :open="open" />
112115
</Component.Trigger>
113116

114117
<Component.Anchor v-if="'Anchor' in Component && !!slots.anchor" as-child>
115-
<slot name="anchor" />
118+
<slot name="anchor" v-bind="((close ? { close } : {}) as SlotProps<M>)" />
116119
</Component.Anchor>
117120

118121
<Component.Portal v-bind="portalProps">
119122
<Component.Content v-bind="contentProps" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-on="contentEvents">
120-
<slot name="content" />
123+
<slot name="content" v-bind="((close ? { close } : {}) as SlotProps<M>)" />
121124

122125
<Component.Arrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
123126
</Component.Content>

0 commit comments

Comments
 (0)