Skip to content

Commit 84867f5

Browse files
fix(Portal): add close event, disabled passthrough, tune examples (#188)
Co-authored-by: John Leider <john@vuetifyjs.com>
1 parent e16830e commit 84867f5

File tree

7 files changed

+54
-110
lines changed

7 files changed

+54
-110
lines changed

apps/docs/src/examples/components/portal/basic.vue

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,39 @@
1111
<div class="flex items-center gap-4">
1212
<button
1313
class="rounded bg-primary px-4 py-2 text-on-primary"
14-
@click="show = !show"
14+
@click="show = true"
1515
>
16-
{{ show ? 'Hide' : 'Show' }} Portal
16+
Show overlay
1717
</button>
1818

1919
<span class="text-sm text-on-surface-variant">
20-
{{ mobile ? 'Mobile — renders inline below' : 'Desktop — teleported to body (bottom-right)' }}
20+
{{ mobile ? 'Mobile — renders inline below' : 'Desktop — teleported to body' }}
2121
</span>
2222
</div>
2323

24-
<Portal v-if="show" :disabled="mobile">
25-
<template #default="{ zIndex }">
24+
<Portal v-if="show" :disabled="mobile" @close="show = false">
25+
<template #default="{ zIndex, close }">
2626
<div
2727
class="rounded-lg bg-surface-variant p-4 shadow-lg"
2828
:class="mobile ? '' : 'fixed bottom-4 right-4'"
2929
:style="mobile ? {} : { zIndex }"
3030
>
31-
<p class="text-sm font-medium">Portal Content</p>
32-
<p class="mt-1 text-xs text-on-surface-variant">
33-
z-index: {{ zIndex }} · {{ mobile ? 'inline' : 'teleported to body' }}
34-
</p>
31+
<div class="flex items-start justify-between gap-4">
32+
<div>
33+
<p class="text-sm font-medium">Portal Content</p>
34+
35+
<p class="mt-1 text-xs text-on-surface-variant">
36+
z-index: {{ zIndex }} · {{ mobile ? 'inline' : 'teleported' }}
37+
</p>
38+
</div>
39+
40+
<button
41+
class="rounded px-2 py-1 text-xs text-on-surface-variant hover:bg-surface hover:text-on-surface"
42+
@click="close"
43+
>
44+
close
45+
</button>
46+
</div>
3547
</div>
3648
</template>
3749
</Portal>

apps/docs/src/examples/components/portal/custom-target.vue

Lines changed: 0 additions & 85 deletions
This file was deleted.

apps/docs/src/examples/components/portal/stacking.vue

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
layers.value.push(next++)
1010
}
1111
12-
function onRemove (index: number) {
12+
function onClose (index: number) {
1313
layers.value.splice(index, 1)
1414
}
1515
</script>
@@ -29,8 +29,12 @@
2929
</span>
3030
</div>
3131

32-
<Portal v-for="(id, index) in layers" :key="id">
33-
<template #default="{ zIndex }">
32+
<Portal
33+
v-for="(id, index) in layers"
34+
:key="id"
35+
@close="onClose(index)"
36+
>
37+
<template #default="{ zIndex, close }">
3438
<div
3539
class="fixed rounded-lg border border-divider bg-surface p-4 shadow-xl"
3640
:style="{
@@ -48,11 +52,18 @@
4852

4953
<button
5054
class="rounded px-2 py-1 text-xs text-on-surface-variant hover:bg-surface-variant hover:text-on-surface"
51-
@click="onRemove(index)"
55+
@click="close"
5256
>
5357
close
5458
</button>
5559
</div>
60+
61+
<button
62+
class="mt-3 w-full rounded bg-primary/10 px-3 py-1.5 text-xs text-primary hover:bg-primary/20"
63+
@click="onAdd"
64+
>
65+
Add another layer
66+
</button>
5667
</div>
5768
</template>
5869
</Portal>

apps/docs/src/examples/components/snackbar/queue.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116

117117
<Snackbar.Portal
118118
class="fixed bottom-4 right-4 w-72"
119+
:teleport="false"
119120
@mouseenter="onEnter"
120121
@mouseleave="onLeave"
121122
>

apps/docs/src/examples/composables/use-notifications/NotificationProvider.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@
206206
</div>
207207

208208
<!-- Toasts — rendered from notifications.queue via Snackbar -->
209-
<Snackbar.Portal class="fixed bottom-4 right-4 flex flex-col gap-2">
209+
<Snackbar.Portal class="fixed bottom-4 right-4 flex flex-col gap-2" :teleport="false">
210210
<TransitionGroup
211211
enter-active-class="transition duration-200 ease-out"
212212
enter-from-class="opacity-0 translate-y-2"

apps/docs/src/pages/components/primitives/portal.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,7 @@ The `disabled` prop controls teleporting — when `true`, content renders inline
7676

7777
### Stacking
7878

79-
Each Portal registers its own stack ticket. Add multiple layers to see how `useStack` assigns incrementing `zIndex` values — each new Portal layers above the previous one. The `zIndex` slot prop updates reactively as layers are added and removed.
80-
81-
:::
82-
83-
::: example
84-
/components/portal/custom-target
85-
86-
### Custom Target
87-
88-
Teleport content into a specific container using the `to` prop with a CSS selector. Items added in the input panel are rendered inside the target panel via Portal — demonstrating how content can originate in one part of the tree and appear in another.
79+
Each Portal registers its own stack ticket. Add multiple layers to see how `useStack` assigns incrementing `zIndex` values — each new Portal layers above the previous one. Layers can be dismissed via the close button or programmatically. The `zIndex` slot prop updates reactively as layers are added and removed.
8980

9081
:::
9182

packages/0/src/components/Portal/Portal.vue

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
to?: string | HTMLElement
2424
/** Render inline instead of teleporting. @default false */
2525
disabled?: boolean
26+
/** Block scrim close. @default false */
27+
blocking?: boolean
2628
}
2729
2830
export interface PortalSlotProps {
2931
/** Calculated z-index from useStack */
3032
zIndex: number
33+
/** Close this portal (unselects from stack) */
34+
close: () => void
3135
}
3236
</script>
3337

@@ -38,17 +42,27 @@
3842
default: (props: PortalSlotProps) => any
3943
}>()
4044
45+
const emit = defineEmits<{
46+
close: []
47+
}>()
48+
4149
const {
4250
to = 'body',
4351
disabled = false,
52+
blocking = false,
4453
} = defineProps<PortalProps>()
4554
4655
const stack = useStack()
47-
const ticket = stack.register()
56+
const ticket = stack.register({
57+
disabled,
58+
blocking,
59+
onDismiss: () => emit('close'),
60+
})
4861
ticket.select()
4962
5063
const slotProps = toRef((): PortalSlotProps => ({
5164
zIndex: ticket.zIndex.value,
65+
close: ticket.dismiss,
5266
}))
5367
</script>
5468

0 commit comments

Comments
 (0)