Skip to content

Instantly share code, notes, and snippets.

@kangchihlun
Created November 15, 2024 05:44
Show Gist options
  • Save kangchihlun/107f3b4918323899724dfcefbaff7880 to your computer and use it in GitHub Desktop.
Save kangchihlun/107f3b4918323899724dfcefbaff7880 to your computer and use it in GitHub Desktop.
View Transition
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Send Money App</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" data-type="module" src="index.js"></script>
</body>
</html>
import React, { useState } from "https://esm.sh/react@18";
import { createRoot, flushSync } from "https://esm.sh/react-dom@18";
const ITEMS = [
{
title: "Bank Transfer",
subtitle: "Transfer money to bank account",
icon: <i class="fa-solid fa-building-columns"></i>
},
{
title: "Debit/Credit Card",
subtitle: "Send money from your card",
icon: <i class="fa-solid fa-credit-card"></i>
},
{
title: "Wallet",
subtitle: "Transfer money from your wallet",
icon: <i class="fa-solid fa-wallet"></i>
}
];
const Card = ({ title, icon, onCancel, id, children }) => {
return (
<div className="card" id={id}>
<div className="card-title-row">
{icon && <div className="card-icon">{icon}</div>}
<div className="card-title">{title}</div>
{onCancel && (
<div className="close-btn" onClick={onCancel}>
<i class="fa-solid fa-x"></i>
</div>
)}
</div>
<div className="card-body">{children}</div>
</div>
);
};
const SelectionCard = ({ onSelect }) => {
return (
<Card title="Send Money">
<div className="selection">
{ITEMS.map((item) => (
<div className="item" key={item.title} onClick={() => onSelect(item)}>
<div className="item-icon">{item.icon}</div>
<div className="item-title">{item.title}</div>
<div className="item-subtitle">{item.subtitle}</div>
</div>
))}
</div>
</Card>
);
};
const BankTransferCard = ({ onCancel }) => {
const { title, icon } = ITEMS[0];
return (
<Card title={title} icon={icon} id="bank-transfer" onCancel={onCancel}>
<form>
<div className="form-item">
<label htmlFor="fullname">Full Name</label>
<input id="fullname" />
</div>
<div className="form-item">
<label htmlFor="account-number">Account Number</label>
<input id="account-number" />
</div>
<div className="form-item">
<label htmlFor="bank-code">Bank Code</label>
<input id="bank-code" />
</div>
<button className="btn">Proceed</button>
</form>
</Card>
);
};
const DebitCard = ({ onCancel }) => {
const { title, icon } = ITEMS[1];
return (
<Card title={title} icon={icon} id="debit-card" onCancel={onCancel}>
<div className="row">
<div className="avaliable-cards">Avaliable Cards</div>
<button className="add-card-btn">
<i class="fa-solid fa-plus"></i>
Add Card
</button>
</div>
<div className="debit-card-radio-group">
<div className="debit-card-radio">
<input
type="radio"
id="debit-card-1"
name="debit-card"
value="debit-card-1"
checked
/>
<label for="debit-card-1" className="row">
**** 6448
<i class="fa-brands fa-cc-visa"></i>
</label>
</div>
<div className="debit-card-radio">
<input
type="radio"
id="debit-card-2"
name="debit-card"
value="debit-card-2"
/>
<label for="debit-card-2" className="row">
**** 1234
<i class="fa-brands fa-cc-mastercard"></i>
</label>
</div>
</div>
<button className="btn">Proceed</button>
</Card>
);
};
const Wallet = ({ onCancel }) => {
const { title, icon } = ITEMS[2];
return (
<Card id="wallet-card" title={title} icon={icon} onCancel={onCancel}>
<form>
<div className="form-item">
<label>Wallet Balance</label>
<div className="wallet-balance">$123,213</div>
</div>
<div className="form-item">
<label htmlFor="amount">Amount</label>
<input id="amount" type="number" />
</div>
<button className="btn">Proceed</button>
</form>
</Card>
);
};
const App = () => {
const [selectedItem, setSelectedItem] = useState(null);
const handleSelectItem = (item) => {
document.startViewTransition(() => {
flushSync(() => {
setSelectedItem(item);
});
});
};
const handleUnselect = () => {
document.startViewTransition(() => {
flushSync(() => {
setSelectedItem(null);
});
});
};
let card;
switch (selectedItem?.title) {
case "Bank Transfer":
card = <BankTransferCard onCancel={handleUnselect} />;
break;
case "Debit/Credit Card":
card = <DebitCard onCancel={handleUnselect} />;
break;
case "Wallet":
card = <Wallet onCancel={handleUnselect} />;
break;
default:
card = <SelectionCard onSelect={handleSelectItem} />;
}
return <>{card}</>;
};
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<App />);
html,
body {
height: 100%;
margin: 0;
}
body {
display: grid;
place-items: center;
}
/* card */
.card {
border-radius: 20px;
border: 1px solid #f6f6f6;
box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,
rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;
width: 300px;
}
.card-icon {
color: #989898;
position: absolute;
}
.card-title {
color: #989898;
&:not(:first-of-type) {
margin-left: 24px;
}
}
.close-btn {
background: #f4eeeb;
width: 20px;
height: 20px;
border-radius: 50%;
display: grid;
place-items: center;
line-height: 1;
cursor: pointer;
position: absolute;
right: 20px;
top: 12px;
font-size: 10px;
i {
scale: 0.8;
}
}
.card-title-row {
padding: 12px 20px 0;
position: relative;
}
.card-body {
padding: 12px 20px 16px;
}
.btn {
padding: 12px 24px;
background-color: #2d2d2d;
border: none;
border-radius: 12px;
color: #fff;
font-size: 14px;
cursor: pointer;
margin-top: 16px;
}
.row {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
}
/* selection card */
.selection {
display: grid;
gap: 12px;
}
.item {
font-size: 14px;
cursor: pointer;
display: grid;
grid-template-columns: max-content 1fr;
grid-template-rows: repeat(2, max-content);
column-gap: 8px;
align-items: center;
}
.item-icon {
grid-row: 1 / -1;
font-size: 16px;
}
.item-title {
color: #282828;
}
.item-subtitle {
color: #ababab;
}
/* form */
.form-item {
display: flex;
flex-direction: column;
&:not(:last-of-type) {
margin-bottom: 12px;
}
label {
color: #818181;
font-size: 14px;
margin-bottom: 4px;
}
input {
border-radius: 8px;
height: 32px;
outline: none;
border: 2px solid #e9e9e9;
font-size: 16px;
&:focus {
border-color: #2c2c2c;
}
}
}
/* debit card */
.avaliable-cards {
color: #818181;
font-size: 14px;
}
.add-card-btn {
border-radius: 12px;
border: 1px solid #ededed;
padding: 4px 8px;
color: #818181;
background: #fff;
cursor: pointer;
display: flex;
gap: 4px;
align-items: center;
}
.debit-card-radio-group {
margin-top: 12px;
}
.debit-card-radio {
padding: 12px 8px;
background: #faf8f2;
border-radius: 8px;
display: flex;
align-items: center;
&:not(:last-of-type) {
margin-bottom: 8px;
}
input[type="radio"] {
accent-color: #2d2d2d;
}
}
/* view transition */
.card {
view-transition-name: card;
}
::view-transition-old(card),
::view-transition-new(card) {
height: 100%;
}
.item:nth-of-type(1) .item-title,
#bank-transfer .card-title {
view-transition-name: bank-transfer-title;
}
.item:nth-of-type(1) .item-icon,
#bank-transfer .card-icon {
view-transition-name: bank-transfer-icon;
}
.item:nth-of-type(2) .item-title,
#debit-card .card-title {
view-transition-name: debit-card-title;
}
.item:nth-of-type(2) .item-icon,
#debit-card .card-icon {
view-transition-name: debit-card-icon;
}
.item:nth-of-type(3) .item-title,
#wallet-card .card-title {
view-transition-name: wallet-card-title;
}
.item:nth-of-type(3) .item-icon,
#wallet-card .card-icon {
view-transition-name: wallet-card-icon;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment