Skip to content

Commit 27aa7da

Browse files
committed
fix: whilePress and whileHover are not working when using variants
1 parent c427a7a commit 27aa7da

File tree

2 files changed

+220
-79
lines changed

2 files changed

+220
-79
lines changed

packages/motion/src/features/animation/animation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export class AnimationFeature extends Feature {
228228
// If current node is a variant node, merge the control node's variant
229229
if (this.state.visualElement.isVariantNode) {
230230
const controlVariant = resolveVariant(this.state.context[name], variants, customValue)
231-
resolvedVariant = controlVariant ? Object.assign(controlVariant || {}, resolvedVariant) : variant
231+
resolvedVariant = controlVariant ? Object.assign(controlVariant || {}, resolvedVariant) : Object.assign(variant, resolvedVariant)
232232
}
233233
if (!resolvedVariant)
234234
return

playground/nuxt/pages/test.vue

Lines changed: 219 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,234 @@
11
<script setup lang="ts">
2-
import { ref } from 'vue'
3-
import { Motion } from 'motion-v'
4-
5-
// Tab data
6-
const tabs = [
7-
{ id: 'home', label: 'Home' },
8-
{ id: 'about', label: 'About' },
9-
{ id: 'services', label: 'Services' },
10-
{ id: 'contact', label: 'Contact' },
11-
]
12-
13-
// Current active tab
14-
const activeTab = ref(tabs[0].id)
15-
// Hovered tab
16-
const hoveredTab = ref<string | null>(null)
17-
18-
function setActiveTab(tabId: string) {
19-
activeTab.value = tabId
2+
import { onMounted, ref } from 'vue'
3+
import { type MotionProps, motion, useDomRef } from 'motion-v'
4+
5+
const isOpen = ref(false)
6+
const containerRef = useDomRef()
7+
const dimensions = ref({ width: 0, height: 0 })
8+
9+
onMounted(() => {
10+
if (containerRef.value) {
11+
dimensions.value.width = containerRef.value.offsetWidth
12+
dimensions.value.height = containerRef.value.offsetHeight
13+
}
14+
})
15+
16+
function toggle() {
17+
isOpen.value = !isOpen.value
18+
}
19+
20+
const navVariants: MotionProps['variants'] = {
21+
open: {
22+
transition: { staggerChildren: 0.07, delayChildren: 0.2 },
23+
},
24+
closed: {
25+
transition: { staggerChildren: 0.05, staggerDirection: -1 },
26+
},
27+
}
28+
29+
const itemVariants = {
30+
open: {
31+
y: 0,
32+
opacity: 1,
33+
transition: {
34+
y: { stiffness: 1000, velocity: -100 },
35+
},
36+
},
37+
closed: {
38+
y: 50,
39+
opacity: 0,
40+
transition: {
41+
y: { stiffness: 1000 },
42+
},
43+
},
2044
}
2145
22-
function setHoveredTab(tabId: string | null) {
23-
hoveredTab.value = tabId
46+
const sidebarVariants: MotionProps['variants'] = {
47+
open: (height: any = 1000) => ({
48+
clipPath: `circle(${height * 2 + 200}px at 40px 40px)`,
49+
transition: {
50+
type: 'spring',
51+
stiffness: 20,
52+
restDelta: 2,
53+
},
54+
}),
55+
closed: {
56+
clipPath: 'circle(30px at 40px 40px)',
57+
transition: {
58+
delay: 0.2,
59+
type: 'spring',
60+
stiffness: 400,
61+
damping: 40,
62+
},
63+
},
2464
}
65+
66+
const colors = ['#FF008C', '#D309E1', '#9C1AFF', '#7700FF', '#4400FF']
2567
</script>
2668

2769
<template>
28-
<div class="min-h-screen bg-background flex items-center justify-center p-8">
29-
<div class="w-full max-w-md">
30-
<!-- Tab Navigation -->
31-
<div class="relative p-1 bg-muted rounded-lg">
32-
<!-- Active Tab Background -->
33-
34-
<!-- Tab List -->
35-
<div
36-
class="relative flex"
37-
@mouseleave="setHoveredTab(null)"
70+
<div>
71+
<div class="container">
72+
<motion.nav
73+
ref="containerRef"
74+
:initial="false"
75+
:animate="isOpen ? 'open' : 'closed'"
76+
:custom="dimensions.height"
77+
class="nav"
78+
>
79+
<motion.div
80+
class="background"
81+
:variants="sidebarVariants"
82+
/>
83+
84+
<!-- Navigation -->
85+
<motion.ul
86+
class="list"
87+
:variants="navVariants"
3888
>
39-
<button
40-
v-for="tab in tabs"
41-
:key="tab.id"
42-
class="relative px-3 py-1.5 text-sm font-medium transition-colors z-10 flex-1"
43-
:class="[
44-
activeTab === tab.id
45-
? 'text-foreground'
46-
: 'text-muted-foreground hover:text-foreground',
47-
]"
48-
@mouseenter="setHoveredTab(tab.id)"
89+
<motion.li
90+
v-for="i in 5"
91+
:key="i - 1"
92+
class="list-item"
93+
:variants="itemVariants"
94+
:while-press="{ scale: 0.95 }"
95+
:while-hover="{ scale: 1.1 }"
4996
>
50-
{{ tab.label }}
51-
<AnimatePresence mode="sync">
52-
<Motion
53-
v-if="hoveredTab === tab.id"
54-
:data-id="tab.id"
55-
class="absolute bg-background bg-blue-500 z-[-1] w-full h-full top-0 left-0 rounded-md shadow-sm border"
56-
layout-id="hover-tab"
57-
:initial="{
58-
opacity: 0,
59-
}"
60-
:animate="{
61-
opacity: 1,
62-
}"
63-
:exit="{
64-
opacity: 0,
65-
}"
66-
:transition="{
67-
layout: {
68-
type: 'spring',
69-
stiffness: 250,
70-
damping: 27,
71-
mass: 1,
72-
},
73-
}"
74-
:style="{
75-
position: 'absolute',
76-
top: -1,
77-
left: 0,
78-
width: '100%',
79-
height: '26px',
80-
zIndex: 0,
81-
}"
82-
/>
83-
</AnimatePresence>
84-
</button>
85-
</div>
86-
</div>
97+
<div
98+
class="icon-placeholder"
99+
:style="{ border: `2px solid ${colors[i - 1]}` }"
100+
/>
101+
<div
102+
class="text-placeholder"
103+
:style="{ border: `2px solid ${colors[i - 1]}` }"
104+
/>
105+
</motion.li>
106+
</motion.ul>
107+
108+
<!-- Menu Toggle -->
109+
<button
110+
class="toggle-container"
111+
@click="toggle"
112+
>
113+
<svg
114+
width="23"
115+
height="23"
116+
viewBox="0 0 23 23"
117+
>
118+
<motion.path
119+
fill="transparent"
120+
stroke-width="3"
121+
stroke="hsl(0, 0%, 18%)"
122+
stroke-linecap="round"
123+
:variants="{
124+
closed: { d: 'M 2 2.5 L 20 2.5' },
125+
open: { d: 'M 3 16.5 L 17 2.5' },
126+
}"
127+
/>
128+
<motion.path
129+
fill="transparent"
130+
stroke-width="3"
131+
stroke="hsl(0, 0%, 18%)"
132+
stroke-linecap="round"
133+
d="M 2 9.423 L 20 9.423"
134+
:variants="{
135+
closed: { opacity: 1 },
136+
open: { opacity: 0 },
137+
}"
138+
:transition="{ duration: 0.1 }"
139+
/>
140+
<motion.path
141+
fill="transparent"
142+
stroke-width="3"
143+
stroke="hsl(0, 0%, 18%)"
144+
stroke-linecap="round"
145+
:variants="{
146+
closed: { d: 'M 2 16.346 L 20 16.346' },
147+
open: { d: 'M 3 2.5 L 17 16.346' },
148+
}"
149+
/>
150+
</svg>
151+
</button>
152+
</motion.nav>
87153
</div>
88154
</div>
89155
</template>
90156

91157
<style scoped>
92-
/* Additional custom styles if needed */
158+
.container {
159+
position: relative;
160+
display: flex;
161+
justify-content: flex-start;
162+
align-items: stretch;
163+
flex: 1;
164+
width: 500px;
165+
max-width: 100%;
166+
height: 400px;
167+
background-color: var(--accent);
168+
border-radius: 20px;
169+
overflow: hidden;
170+
}
171+
172+
.nav {
173+
width: 300px;
174+
}
175+
176+
.background {
177+
background-color: #f5f5f5;
178+
position: absolute;
179+
top: 0;
180+
left: 0;
181+
bottom: 0;
182+
width: 300px;
183+
}
184+
185+
.toggle-container {
186+
outline: none;
187+
border: none;
188+
-webkit-user-select: none;
189+
-moz-user-select: none;
190+
cursor: pointer;
191+
position: absolute;
192+
top: 18px;
193+
left: 15px;
194+
width: 50px;
195+
height: 50px;
196+
border-radius: 50%;
197+
background: transparent;
198+
}
199+
200+
.list {
201+
list-style: none;
202+
padding: 25px;
203+
margin: 0;
204+
position: absolute;
205+
top: 80px;
206+
width: 230px;
207+
}
208+
209+
.list-item {
210+
display: flex;
211+
align-items: center;
212+
justify-content: flex-start;
213+
padding: 0;
214+
margin: 0;
215+
list-style: none;
216+
margin-bottom: 20px;
217+
cursor: pointer;
218+
}
219+
220+
.icon-placeholder {
221+
width: 40px;
222+
height: 40px;
223+
border-radius: 50%;
224+
flex: 40px 0;
225+
margin-right: 20px;
226+
}
227+
228+
.text-placeholder {
229+
border-radius: 5px;
230+
width: 200px;
231+
height: 20px;
232+
flex: 1;
233+
}
93234
</style>

0 commit comments

Comments
 (0)