JavaScript đang tắt. Đoạn mã bên dưới là ví dụ đầy đủ.
App.jsximport { 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, 1fr) 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;
}