Created
November 15, 2024 05:44
-
-
Save kangchihlun/107f3b4918323899724dfcefbaff7880 to your computer and use it in GitHub Desktop.
View Transition
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 />); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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