JavaScript est désactivé. Le code ci-dessous est l’exemple complet.
App.jsx import { useState } from 'react' ;
import {
MotionHold ,
MotionList ,
MotionNumber ,
MotionPresence ,
} from '@kkwaenggwari/animated-chorus' ;
import './styles.css' ;
const rates = { USD : 1 , EUR : 0.92 , KRW : 1380 };
const catalog = [
{ id : 'mug' , name : 'Stoneware mug' , price : 18 },
{ id : 'tote' , name : 'Canvas tote' , price : 24 },
{ id : 'lamp' , name : 'Desk lamp' , price : 89 },
{ id : 'plant' , name : 'Small fern' , price : 32 },
];
export default function App ({ currency = 'USD' , holdTime = 1200 } ) {
const [cart, setCart] = useState ([
{ ...catalog[0 ], quantity : 2 },
{ ...catalog[2 ], quantity : 1 },
]);
const [coupon, setCoupon] = useState (false );
const [paid, setPaid] = useState (false );
const money = (amount ) => ({
value : Math .round (amount * rates[currency] * 100 ) / 100 ,
intl : true ,
locale : 'en-US' ,
format : { style : 'currency' , currency },
});
const change = (id, delta ) =>
setCart (
cart
.map ((item ) => (item.id === id ? { ...item, quantity : item.quantity + delta } : item))
.filter ((item ) => item.quantity > 0 ),
);
const addNext = ( ) => {
const next = catalog.find ((product ) => !cart.some ((item ) => item.id === product.id ));
if (next) setCart ([...cart, { ...next, quantity : 1 }]);
};
const subtotal = cart.reduce ((sum, item ) => sum + item.price * item.quantity , 0 );
const discount = coupon ? subtotal * 0.1 : 0 ;
const total = subtotal - discount;
return (
<div className ="checkout" >
<MotionList
as ="ul"
className ="cart"
items ={cart}
effect ="slide"
renderItem ={(item) => (
<div className ="line" >
<span className ="name" > {item.name}</span >
<span className ="quantity" >
<button onClick ={() => change(item.id, -1)}>−</button >
<MotionNumber value ={item.quantity} />
<button onClick ={() => change(item.id, 1)}>+</button >
</span >
<MotionNumber className ="amount" {...money (item.price * item.quantity )} />
</div >
)}
/>
<div className ="actions" >
<button onClick ={addNext} > Add an item</button >
<button onClick ={() => setCoupon(!coupon)}>{coupon ? 'Remove' : 'Apply'} SAVE10</button >
</div >
<MotionPresence present ={coupon} className ="coupon" >
SAVE10 applied: 10% off
</MotionPresence >
<p className ="total" >
<span className ="label" > Total</span >
<MotionNumber {...money (total )} mode ="roll" />
</p >
{paid ? (
<button className ="done" onClick ={() => setPaid(false)}>
Paid. Start over
</button >
) : (
<MotionHold
className ="pay"
fillClassName ="pay-fill"
duration ={holdTime}
onConfirm ={() => setPaid(true)}
>
Hold to pay
</MotionHold >
)}
</div >
);
}styles.css .checkout {
display : grid;
gap : 12px ;
max-width : 520px ;
margin : 0 auto;
padding : 24px 20px ;
}
.cart {
display : grid;
gap : 8px ;
margin : 0 ;
padding : 0 ;
list-style : none;
}
.line {
display : grid;
grid-template-columns : minmax (0 , 1 fr) auto auto;
align-items : center;
gap : 12px ;
padding : 10px 14px ;
border : 1px solid #cfe8dc ;
border-radius : 12px ;
background : #fff ;
}
.name {
color : #0e2e24 ;
font-weight : 600 ;
}
.quantity {
display : inline-flex;
align-items : center;
gap : 8px ;
font-variant-numeric : tabular-nums;
}
.quantity button {
width : 32px ;
height : 32px ;
border : 1px solid #cfe8dc ;
border-radius : 50% ;
background : #fff ;
font-size : 16px ;
cursor : pointer;
}
.amount {
min-width : 84px ;
text-align : end;
font-variant-numeric : tabular-nums;
}
.actions {
display : flex;
flex-wrap : wrap;
gap : 8px ;
}
.actions button {
min-height : 40px ;
padding : 0 16px ;
border : 1px solid #cfe8dc ;
border-radius : 999px ;
background : #fff ;
font :
600 14px system-ui,
sans-serif;
cursor : pointer;
}
.coupon {
padding : 10px 14px ;
border-radius : 12px ;
background : #eaf6f0 ;
color : #176b52 ;
font-weight : 600 ;
}
.total {
display : flex;
align-items : baseline;
justify-content : space-between;
margin : 6px 0 0 ;
padding-top : 12px ;
border-top : 1px solid #cfe8dc ;
font :
800 30px system-ui,
sans-serif;
font-variant-numeric : tabular-nums;
color : #0e2e24 ;
}
.total .label {
font-size : 16px ;
font-weight : 600 ;
color : #5b616b ;
}
.pay ,
.done {
position : relative;
min-height : 52px ;
overflow : hidden;
isolation : isolate;
border : 0 ;
border-radius : 999px ;
background : #176b52 ;
color : #fff ;
font :
700 16px system-ui,
sans-serif;
cursor : pointer;
user-select : none;
touch-action : none;
}
.pay-fill {
position : absolute;
inset : 0 ;
z-index : -1 ;
background : #0e2e24 ;
}
.done {
background : #0e2e24 ;
}