mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
feat(Popover): add anchor slot (#4119)
Co-authored-by: Jakub <jakub.michalek@freelo.io> Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const open = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UPopover
|
||||||
|
v-model:open="open"
|
||||||
|
:dismissible="false"
|
||||||
|
:ui="{ content: 'w-(--reka-popper-anchor-width) p-4' }"
|
||||||
|
>
|
||||||
|
<template #anchor>
|
||||||
|
<UInput placeholder="Focus to open" @focus="open = true" @blur="open = false" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<Placeholder class="w-full aspect-square" />
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
</template>
|
||||||
@@ -202,6 +202,21 @@ name: 'popover-command-palette-example'
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With anchor slot
|
||||||
|
|
||||||
|
You can use the `#anchor` slot to position the Popover against a custom element.
|
||||||
|
|
||||||
|
::warning
|
||||||
|
This slot only works when `mode` is `click`.
|
||||||
|
::
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
collapse: true
|
||||||
|
name: 'popover-anchor-slot-example'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
|
const openCustomAnchor = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
function send() {
|
function send() {
|
||||||
@@ -51,6 +52,21 @@ function send() {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UPopover>
|
</UPopover>
|
||||||
|
|
||||||
|
<div class="mt-8 relative">
|
||||||
|
<UPopover
|
||||||
|
v-model:open="openCustomAnchor"
|
||||||
|
:dismissible="false"
|
||||||
|
>
|
||||||
|
<template #anchor>
|
||||||
|
<UInput placeholder="Search" class="w-56" @focus="openCustomAnchor = true" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<Placeholder class="size-48 m-4 inline-flex" />
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-24">
|
<div class="mt-24">
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface PopoverEmits extends PopoverRootEmits {
|
|||||||
export interface PopoverSlots {
|
export interface PopoverSlots {
|
||||||
default(props: { open: boolean }): any
|
default(props: { open: boolean }): any
|
||||||
content(props?: {}): any
|
content(props?: {}): any
|
||||||
|
anchor(props?: {}): any
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -103,6 +104,10 @@ const Component = computed(() => props.mode === 'hover' ? HoverCard : Popover)
|
|||||||
<slot :open="open" />
|
<slot :open="open" />
|
||||||
</Component.Trigger>
|
</Component.Trigger>
|
||||||
|
|
||||||
|
<Component.Anchor v-if="'Anchor' in Component && !!slots.anchor" as-child>
|
||||||
|
<slot name="anchor" />
|
||||||
|
</Component.Anchor>
|
||||||
|
|
||||||
<Component.Portal v-bind="portalProps">
|
<Component.Portal v-bind="portalProps">
|
||||||
<Component.Content v-bind="contentProps" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-on="contentEvents">
|
<Component.Content v-bind="contentProps" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-on="contentEvents">
|
||||||
<slot name="content" />
|
<slot name="content" />
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ describe('Popover', () => {
|
|||||||
['with ui', { props: { ...props, ui: { content: 'shadow-xl' } } }],
|
['with ui', { props: { ...props, ui: { content: 'shadow-xl' } } }],
|
||||||
// Slots
|
// Slots
|
||||||
['with default slot', { props, slots: { default: () => 'Default slot' } }],
|
['with default slot', { props, slots: { default: () => 'Default slot' } }],
|
||||||
['with content slot', { props, slots: { content: () => 'Content slot' } }]
|
['with content slot', { props, slots: { content: () => 'Content slot' } }],
|
||||||
|
['with anchor slot', { props, slots: { anchor: () => 'Anchor slot' } }]
|
||||||
])('renders %s correctly', async (nameOrHtml: string, options: { props?: PopoverProps, slots?: Partial<PopoverSlots> }) => {
|
])('renders %s correctly', async (nameOrHtml: string, options: { props?: PopoverProps, slots?: Partial<PopoverSlots> }) => {
|
||||||
const html = await ComponentRender(nameOrHtml, options, Popover)
|
const html = await ComponentRender(nameOrHtml, options, Popover)
|
||||||
expect(html).toMatchSnapshot()
|
expect(html).toMatchSnapshot()
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`Popover > renders with anchor slot correctly 1`] = `
|
||||||
|
"<!--v-if-->
|
||||||
|
Anchor slot
|
||||||
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
|
<div data-reka-popper-content-wrapper="" style="position: fixed; left: 0px; top: 0px; transform: translate(0, -200%); min-width: max-content;">
|
||||||
|
<div id="reka-popover-content-v-0" data-state="open" aria-labelledby="" style="--reka-popover-content-transform-origin: var(--reka-popper-transform-origin); --reka-popover-content-available-width: var(--reka-popper-available-width); --reka-popover-content-available-height: var(--reka-popper-available-height); --reka-popover-trigger-width: var(--reka-popper-anchor-width); --reka-popover-trigger-height: var(--reka-popper-anchor-height); animation: none;" role="dialog" data-dismissable-layer="" tabindex="-1" class="bg-default shadow-lg rounded-md ring ring-default data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-popover-content-transform-origin) focus:outline-none pointer-events-auto" data-side="bottom" data-align="center"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--teleport end-->"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Popover > renders with arrow correctly 1`] = `
|
exports[`Popover > renders with arrow correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -15,6 +30,7 @@ exports[`Popover > renders with arrow correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with class correctly 1`] = `
|
exports[`Popover > renders with class correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +44,7 @@ exports[`Popover > renders with class correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with content slot correctly 1`] = `
|
exports[`Popover > renders with content slot correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +60,7 @@ exports[`Popover > renders with content slot correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with default slot correctly 1`] = `
|
exports[`Popover > renders with default slot correctly 1`] = `
|
||||||
"Default slot
|
"Default slot
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -56,6 +74,7 @@ exports[`Popover > renders with default slot correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with open correctly 1`] = `
|
exports[`Popover > renders with open correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -69,6 +88,7 @@ exports[`Popover > renders with open correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with ui correctly 1`] = `
|
exports[`Popover > renders with ui correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`Popover > renders with anchor slot correctly 1`] = `
|
||||||
|
"<!--v-if-->
|
||||||
|
Anchor slot
|
||||||
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
|
<div data-reka-popper-content-wrapper="" style="position: fixed; left: 0px; top: 0px; transform: translate(0, -200%); min-width: max-content;">
|
||||||
|
<div id="reka-popover-content-v-0-0-0" data-state="open" aria-labelledby="" style="--reka-popover-content-transform-origin: var(--reka-popper-transform-origin); --reka-popover-content-available-width: var(--reka-popper-available-width); --reka-popover-content-available-height: var(--reka-popper-available-height); --reka-popover-trigger-width: var(--reka-popper-anchor-width); --reka-popover-trigger-height: var(--reka-popper-anchor-height); animation: none;" role="dialog" data-dismissable-layer="" tabindex="-1" class="bg-default shadow-lg rounded-md ring ring-default data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-popover-content-transform-origin) focus:outline-none pointer-events-auto" data-side="bottom" data-align="center"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--teleport end-->"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Popover > renders with arrow correctly 1`] = `
|
exports[`Popover > renders with arrow correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -15,6 +30,7 @@ exports[`Popover > renders with arrow correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with class correctly 1`] = `
|
exports[`Popover > renders with class correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +44,7 @@ exports[`Popover > renders with class correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with content slot correctly 1`] = `
|
exports[`Popover > renders with content slot correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +60,7 @@ exports[`Popover > renders with content slot correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with default slot correctly 1`] = `
|
exports[`Popover > renders with default slot correctly 1`] = `
|
||||||
"Default slot
|
"Default slot
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -56,6 +74,7 @@ exports[`Popover > renders with default slot correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with open correctly 1`] = `
|
exports[`Popover > renders with open correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
@@ -69,6 +88,7 @@ exports[`Popover > renders with open correctly 1`] = `
|
|||||||
|
|
||||||
exports[`Popover > renders with ui correctly 1`] = `
|
exports[`Popover > renders with ui correctly 1`] = `
|
||||||
"<!--v-if-->
|
"<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user