1- <script setup lang="tsx">
2- /** @jsxImportSource vue */
3- import { AnimateNumber } from ' motion-number-vue'
4- import { motion } from ' motion-v'
5- import { ref } from ' vue'
1+ <script setup lang="ts">
2+ import { animate , motion , useMotionValue , useTransform } from ' motion-v'
63
7- const isCompact = ref (true )
8- const isCurrency = ref (false )
4+ const progress = useMotionValue (0 )
95
10- function Switch({ isOn , toggle }: { isOn: boolean , toggle: () => void }) {
11- return (
12- <motion.button
13- class = " switch-container"
14- style = { {
15- justifyContent: ` flex-${isOn ? ' end' : ' start' } ` ,
16- }}
17- initial = { false }
18- animate = { {
19- backgroundColor: isOn
20- ? ' var(--hue-6-transparent)'
21- : ' #586d8c33' ,
22- }}
23- onClick = { toggle }
24- focus = { {
25- outline: ' 2px solid #4ff0b7' ,
26- }}
27- >
28- <motion.div
29- class = " switch-handle"
30- layout
31- data-is-on = { isOn }
32- transition = { {
33- type: ' spring' ,
34- visualDuration: 0.2 ,
35- bounce: 0.2 ,
36- }}
37- />
38- </motion.button >
39- )
6+ const circleStrokeWidth = useTransform (progress , [0 , 1 ], [0 , 20 ])
7+ const circleRotation = useTransform (progress , [0 , 1 ], [' -90deg' , ' -90deg' ])
8+ const circleColor = useTransform (progress , [0 , 1 ], [' #ffffff' , ' #8df0cc' ])
9+
10+ const buttonScale = useTransform (progress , [0 , 1 ], [1 , 0.85 ])
11+ const buttonProgressX = useTransform (progress , [0 , 1 ], [' -200%' , ' 0%' ])
12+
13+ function handlePointerDown() {
14+ progress .set (0 )
15+ animate (progress , 1 , {
16+ duration: 2 ,
17+ ease: ' easeOut' ,
18+ })
4019}
41- const value = ref (5385 )
42- function changeCurrency() {
43- // value.value = 58
44- value .value = Math .random () * 1000 * (Math .random () > 0.5 ? 1 : - 1 ) * 10 ** Math .floor (Math .random () * 3 )
20+
21+ function handlePointerUp() {
22+ animate (progress , 0 , { duration: 0.3 })
4523}
4624 </script >
4725
4826<template >
49- <div
50- class =" container"
51- @click =" changeCurrency"
52- >
53- <AnimateNumber
54- :format =" {
55- notation: isCompact ? 'compact' : undefined,
56- compactDisplay: isCompact ? 'short' : undefined,
57- roundingMode: isCompact ? 'trunc' : undefined,
58- style: isCurrency ? 'currency' : undefined,
59- currency: isCurrency ? 'USD' : undefined,
60- }"
61- locales =" en-US"
62- class =" number"
63- :transition =" {
64- visualDuration: 0.6,
65- type: 'spring',
66- bounce: 0.25,
67- opacity: { duration: 0.3, ease: 'linear' },
68- }"
69- :value =" value"
70- />
71- <div class =" controls" >
72- <div >
73- Currency:
74- <Switch
75- :is-on =" isCurrency"
76- :toggle =" () => isCurrency = !isCurrency"
27+ <div class =" container" >
28+ <div class =" button-wrapper" >
29+ <motion .button
30+ class =" button"
31+ :style =" {
32+ scale: buttonScale,
33+ }"
34+ @pointerdown =" handlePointerDown"
35+ @pointerup =" handlePointerUp"
36+ @pointerleave =" handlePointerUp"
37+ >
38+ <motion .div
39+ class =" button-background"
40+ :style =" {
41+ x: buttonProgressX,
42+ }"
7743 />
78- </div >
79- <div >
80- Compact:
81- <Switch
82- :is-on =" isCompact"
83- :toggle =" () => isCompact = !isCompact"
44+ Hold to confirm
45+ </motion .button >
46+
47+ <motion .svg
48+ class =" progress-ring"
49+ width =" 320"
50+ height =" 320"
51+ viewBox =" 0 0 320 320"
52+ >
53+ <motion .circle
54+ cx =" 160"
55+ cy =" 160"
56+ r =" 120"
57+ fill =" none"
58+ stroke =" var(--white-feint)"
59+ stroke-width =" 24"
60+ stroke-linecap =" round"
61+ :style =" {
62+ rotate: circleRotation,
63+ transformOrigin: 'center',
64+ opacity: progress,
65+ stroke: circleColor,
66+ strokeWidth: circleStrokeWidth,
67+ pathLength: progress,
68+ }"
8469 />
85- </div >
70+ </motion .svg >
8671 </div >
8772 </div >
8873</template >
@@ -91,40 +76,52 @@ function changeCurrency() {
9176.container {
9277 display : flex ;
9378 flex-direction : column ;
79+ gap : 16px ;
9480 align-items : center ;
95- gap : 20px ;
96- }
97-
98- .number {
99- font-size : 78px ;
81+ justify-content : center ;
82+ padding : 16px ;
83+ height : 80px ;
10084}
10185
102- .controls {
86+ .button-wrapper {
87+ position : relative ;
10388 display : flex ;
104- gap : 20 px ;
105- border-radius : 50 px ;
89+ align-items : center ;
90+ justify-content : center ;
10691}
10792
108- .controls > div {
109- display : flex ;
110- align-items : center ;
111- gap : 10px ;
112- font-size : 18px ;
93+ .progress-ring {
94+ position : absolute ;
95+ top : 50% ;
96+ left : 50% ;
97+ transform : translate (-50% , -50% );
98+ pointer-events : none ;
11399}
114100
115- .switch-container {
116- width : 40px ;
117- height : 20px ;
118- border-radius : 25px ;
119- cursor : pointer ;
120- display : flex ;
121- padding : 5px ;
101+ .button {
102+ color : var (--black );
103+ background-color : var (--white );
104+ border-radius : 999px ;
105+ padding : 12px 20px ;
106+ position : relative ;
107+ isolation : isolate ;
108+ overflow : hidden ;
109+ will-change : transform;
110+ user-select : none ;
111+ -webkit-user-select : none ;
112+ -webkit-touch-callout : none ;
122113}
123114
124- .switch-handle {
125- width : 20px ;
126- height : 20px ;
127- background-color : #4ff0b7 ;
128- border-radius : 50% ;
115+ .button-background {
116+ position : absolute ;
117+ top : 0 ;
118+ left : 0 ;
119+ width : 100% ;
120+ height : 100% ;
121+ background-color : var (--green );
122+ border-radius : 999px ;
123+ z-index : -1 ;
124+ filter : blur (20px );
125+ scale : 2 ;
129126}
130127 </style >
0 commit comments