|
2 | 2 | * @module Scrim |
3 | 3 | * |
4 | 4 | * @remarks |
5 | | - * Scrim/backdrop component for overlays. Integrates with createStack |
6 | | - * to provide a shared backdrop for all active overlays. Automatically |
7 | | - * positions itself behind the topmost overlay and handles dismiss behavior. |
| 5 | + * Scrim/backdrop component for overlays. Integrates with useStack |
| 6 | + * to render one backdrop per active overlay. The cumulative visual |
| 7 | + * effect creates natural opacity stacking. |
| 8 | + * |
| 9 | + * Requires createStackPlugin to be installed at app level. |
8 | 10 | * |
9 | 11 | * @see https://0.vuetifyjs.com/components/providers/scrim |
10 | 12 | */ |
11 | 13 |
|
12 | 14 | <script lang="ts"> |
13 | 15 | // Types |
14 | 16 | import type { AtomProps } from '#v0/components/Atom' |
15 | | - import type { StackContext } from '#v0/composables/useStack' |
| 17 | + import type { StackTicket } from '#v0/composables/useStack' |
16 | 18 |
|
17 | 19 | export interface ScrimProps extends AtomProps { |
18 | | - /** |
19 | | - * Custom stack context to use instead of the global stack |
20 | | - * |
21 | | - * @remarks Useful when multiple independent overlay systems exist in an app. |
22 | | - */ |
23 | | - stack?: StackContext |
24 | 20 | /** |
25 | 21 | * Transition name for enter/leave animations |
26 | 22 | * |
|
42 | 38 | } |
43 | 39 |
|
44 | 40 | export interface ScrimSlotProps { |
45 | | - /** Whether any overlays are active */ |
46 | | - isActive: boolean |
47 | | - /** Whether the topmost overlay is blocking */ |
48 | | - isBlocking: boolean |
49 | | - /** Z-index for the scrim (one below top overlay) */ |
| 41 | + /** The ticket for this scrim layer */ |
| 42 | + ticket: StackTicket |
| 43 | + /** Z-index for this scrim layer (one below the overlay) */ |
50 | 44 | zIndex: number |
51 | | - /** Dismiss the topmost non-blocking overlay */ |
| 45 | + /** Whether this ticket's overlay blocks scrim dismissal */ |
| 46 | + isBlocking: boolean |
| 47 | + /** Dismiss this overlay */ |
52 | 48 | dismiss: () => void |
53 | 49 | } |
54 | 50 | </script> |
|
61 | 57 | import { useStack } from '#v0/composables/useStack' |
62 | 58 |
|
63 | 59 | // Utilities |
64 | | - import { toRef, useAttrs } from 'vue' |
| 60 | + import { computed, useAttrs } from 'vue' |
65 | 61 |
|
66 | 62 | defineOptions({ name: 'Scrim', inheritAttrs: false }) |
67 | 63 |
|
|
71 | 67 |
|
72 | 68 | const { |
73 | 69 | as = 'div', |
74 | | - stack = useStack(), |
75 | 70 | transition = 'fade', |
76 | 71 | teleport = true, |
77 | 72 | teleportTo = 'body', |
78 | 73 | } = defineProps<ScrimProps>() |
79 | 74 |
|
80 | 75 | const attrs = useAttrs() |
| 76 | + const stack = useStack() |
| 77 | +
|
| 78 | + const tickets = computed(() => Array.from(stack.selectedItems.value)) |
81 | 79 |
|
82 | | - function onClick () { |
83 | | - const top = stack.top.value |
84 | | - if (top && !stack.isBlocking.value) { |
85 | | - top.dismiss() |
| 80 | + function onDismiss (ticket: StackTicket) { |
| 81 | + if (!ticket.blocking) { |
| 82 | + ticket.dismiss() |
86 | 83 | } |
87 | 84 | } |
88 | 85 |
|
89 | | - const slotProps = toRef((): ScrimSlotProps => ({ |
90 | | - isActive: stack.isActive.value, |
91 | | - isBlocking: stack.isBlocking.value, |
92 | | - zIndex: stack.scrimZIndex.value, |
93 | | - dismiss: onClick, |
94 | | - })) |
| 86 | + function getSlotProps (ticket: StackTicket): ScrimSlotProps { |
| 87 | + return { |
| 88 | + ticket, |
| 89 | + zIndex: ticket.zIndex.value - 1, |
| 90 | + isBlocking: ticket.blocking, |
| 91 | + dismiss: () => onDismiss(ticket), |
| 92 | + } |
| 93 | + } |
95 | 94 |
|
96 | | - const style = toRef(() => ({ |
97 | | - zIndex: stack.scrimZIndex.value, |
98 | | - })) |
| 95 | + function getStyle (ticket: StackTicket) { |
| 96 | + return { zIndex: ticket.zIndex.value - 1 } |
| 97 | + } |
99 | 98 | </script> |
100 | 99 |
|
101 | 100 | <template> |
102 | 101 | <Teleport |
103 | 102 | v-if="teleport" |
104 | 103 | :to="teleportTo" |
105 | 104 | > |
106 | | - <Transition :name="transition"> |
| 105 | + <TransitionGroup :name="transition"> |
107 | 106 | <Atom |
108 | | - v-if="stack.isActive.value" |
| 107 | + v-for="ticket in tickets" |
| 108 | + :key="ticket.id" |
109 | 109 | :as |
110 | | - :style |
| 110 | + :style="getStyle(ticket)" |
111 | 111 | v-bind="attrs" |
112 | | - @click="onClick" |
| 112 | + @click="() => onDismiss(ticket)" |
113 | 113 | > |
114 | | - <slot v-bind="slotProps" /> |
| 114 | + <slot v-bind="getSlotProps(ticket)" /> |
115 | 115 | </Atom> |
116 | | - </Transition> |
| 116 | + </TransitionGroup> |
117 | 117 | </Teleport> |
118 | 118 |
|
119 | | - <Transition |
| 119 | + <TransitionGroup |
120 | 120 | v-else |
121 | 121 | :name="transition" |
122 | 122 | > |
123 | 123 | <Atom |
124 | | - v-if="stack.isActive.value" |
| 124 | + v-for="ticket in tickets" |
| 125 | + :key="ticket.id" |
125 | 126 | :as |
126 | | - :style |
| 127 | + :style="getStyle(ticket)" |
127 | 128 | v-bind="attrs" |
128 | | - @click="onClick" |
| 129 | + @click="() => onDismiss(ticket)" |
129 | 130 | > |
130 | | - <slot v-bind="slotProps" /> |
| 131 | + <slot v-bind="getSlotProps(ticket)" /> |
131 | 132 | </Atom> |
132 | | - </Transition> |
| 133 | + </TransitionGroup> |
133 | 134 | </template> |
0 commit comments