A Pen by Hyperplexed on CodePen.
Created
May 5, 2022 06:04
-
-
Save justaguywhocodes/3f17acb61f6fcafe427551f1516cd75d to your computer and use it in GitHub Desktop.
React | Crypto UI 2.0
This file contains hidden or 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
<div id="root"></div> |
This file contains hidden or 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
enum CoinGeckoApi { | |
AllCoins = "coins/markets?vs_currency=usd&page=1&per_page=30&sparkline=false", | |
Base = "https://api.coingecko.com/api/v3" | |
} | |
enum RequestStatus { | |
Error = "Error", | |
Idle = "Idle", | |
Loading = "Loading", | |
Success = "Success" | |
} | |
enum Color { | |
Green = "76, 175, 80", | |
Red = "198, 40, 40" | |
} | |
interface ICrypto { | |
change: number; | |
id: string; | |
image: string; | |
marketCap: number; | |
name: string; | |
price: number; | |
rank: number; | |
supply: number; | |
symbol: string; | |
volume: number; | |
} | |
interface IChartPoint { | |
price: number; | |
timestamp: number; | |
} | |
/* ---------- Crypto Utility ---------- */ | |
interface ICryptoUtility { | |
formatPercent: (value: number) => string; | |
formatUSD: (value: number) => string; | |
getByID: (id: string, cryptos: ICrypto[]) => ICrypto | null; | |
map: (data: any) => ICrypto; | |
mapAll: (data: any[]) => ICrypto[]; | |
} | |
const CryptoUtility: ICryptoUtility = { | |
formatPercent: (value: number): string => { | |
return (value / 100).toLocaleString("en-US", { style: "percent", minimumFractionDigits: 2 }); | |
}, | |
formatUSD: (value: number): string => { | |
return value.toLocaleString("en-US", { style: "currency", currency: "USD" }); | |
}, | |
getByID: (id: string, cryptos: ICrypto[]): ICrypto | null => { | |
const match: ICrypto = cryptos.find((crypto: ICrypto) => crypto.id === id); | |
return match || null; | |
}, | |
map: (data: any): ICrypto => { | |
return { | |
change: data.price_change_percentage_24h, | |
id: data.id, | |
image: data.image, | |
marketCap: CryptoUtility.formatUSD(data.market_cap), | |
name: data.name, | |
price: CryptoUtility.formatUSD(data.current_price), | |
rank: data.market_cap_rank, | |
supply: data.circulating_supply.toLocaleString(), | |
symbol: data.symbol, | |
volume: CryptoUtility.formatUSD(data.total_volume) | |
} | |
}, | |
mapAll: (data: any[]): ICrypto[] => { | |
return data.map((item: any) => CryptoUtility.map(item)); | |
} | |
} | |
/* ---------- Chart Utility ---------- */ | |
interface IChartUtility { | |
draw: (id: string, points: IChartPoint[], change: number) => Chart; | |
getDatasetOptions: (change: number) => Chart.ChartDataSets; | |
getOptions: (points: IChartPoint[]) => Chart.ChartOptions; | |
getUrl: (id: string) => string; | |
mapPoints: (data: any) => IChartPoint[]; | |
update: (chart: Chart, points: IChartPoint[], change: number) => void; | |
} | |
const ChartUtility: IChartUtility = { | |
draw: (id: string, points: IChartPoint[], change: number): Chart => { | |
const canvas: HTMLCanvasElement | null = document.getElementById(id) as HTMLCanvasElement | null; | |
if(canvas !== null) { | |
const context: CanvasRenderingContext2D | null = canvas.getContext("2d"); | |
const { | |
clientHeight: height, | |
clientWidth: width | |
} = context.canvas; | |
context.stroke(); | |
return new Chart(context, { | |
type: "line", | |
data: { | |
datasets: [{ | |
data: points.map((point: IChartPoint) => point.price), | |
...ChartUtility.getDatasetOptions(change) | |
}], | |
labels: points.map((point: IChartPoint) => point.timestamp) | |
}, | |
options: ChartUtility.getOptions(points) | |
}) | |
} | |
}, | |
getDatasetOptions: (change: number): Chart.ChartDataSets => { | |
const color: Color = change >= 0 ? Color.Green : Color.Red; | |
return { | |
backgroundColor: "rgba(" + color + ", 0.1)", | |
borderColor: "rgba(" + color + ", 0.5)", | |
fill: true, | |
tension: 0.2, | |
pointRadius: 0 | |
} | |
}, | |
getOptions: (points: IChartPoint[]): Chart.ChartOptions => { | |
const min: number = Math.min.apply(Math, points.map((point: IChartPoint) => point.price)), | |
max: number = Math.max.apply(Math, points.map((point: IChartPoint) => point.price)); | |
return { | |
maintainAspectRatio: false, | |
responsive: true, | |
scales: { | |
x: { | |
display: false, | |
gridLines: { | |
display: false | |
} | |
}, | |
y: { | |
display: false, | |
gridLines: { | |
display: false | |
}, | |
suggestedMin: min * 0.98, | |
suggestedMax: max * 1.02 | |
} | |
}, | |
plugins: { | |
legend: { | |
display: false | |
}, | |
title: { | |
display: false | |
} | |
} | |
} | |
}, | |
getUrl: (id: string): string => { | |
return `${CoinGeckoApi.Base}/coins/${id}/market_chart?vs_currency=usd&days=1`; | |
}, | |
mapPoints: (data: any): IChartPoint[] => { | |
return data.prices.map((price: number[]) => ({ | |
price: price[1], | |
timestamp: price[0] | |
})) | |
}, | |
update: (chart: Chart, points: IChartPoint[], change: number): void => { | |
chart.options = ChartUtility.getOptions(points); | |
const options: Chart.ChartDataSets = ChartUtility.getDatasetOptions(change); | |
chart.data.datasets[0].data = points.map((point: IChartPoint) => point.price); | |
chart.data.datasets[0].backgroundColor = options.backgroundColor; | |
chart.data.datasets[0].borderColor = options.borderColor; | |
chart.data.datasets[0].pointRadius = options.pointRadius; | |
chart.data.labels = points.map((point: IChartPoint) => point.timestamp); | |
chart.update(); | |
} | |
} | |
/* ---------- Loading Component ---------- */ | |
const LoadingSpinner: React.FC = () => { | |
return ( | |
<div className="loading-spinner-wrapper"> | |
<div className="loading-spinner"> | |
<i className="fa-regular fa-spinner-third" /> | |
</div> | |
</div> | |
); | |
} | |
/* ---------- Crypto List Component ---------- */ | |
const CryptoListToggle: React.FC = () => { | |
const { state, toggleList } = React.useContext(AppContext); | |
if(state.status === RequestStatus.Success && state.cryptos.length > 0) { | |
const classes: string = classNames( | |
"fa-regular", { | |
"fa-bars": !state.listToggled, | |
"fa-xmark": state.listToggled | |
}); | |
return ( | |
<button | |
id="crypto-list-toggle-button" | |
onClick={() => toggleList(!state.listToggled)} | |
> | |
<i className={classes} /> | |
</button> | |
) | |
} | |
return null; | |
} | |
interface CryptoListItemProps { | |
crypto: ICrypto; | |
} | |
const CryptoListItem: React.FC<CryptoListItemProps> = (props: CryptoListItemProps) => { | |
const { state, selectCrypto } = React.useContext(AppContext); | |
const { crypto } = props; | |
const getClasses = (): string => { | |
const selected: boolean = state.selectedCrypto && state.selectedCrypto.id === crypto.id; | |
return classNames( | |
"crypto-list-item", { | |
selected | |
}); | |
} | |
return ( | |
<button type="button" className={getClasses()} onClick={() => selectCrypto(crypto.id)}> | |
<div className="crypto-list-item-background"> | |
<h1 className="crypto-list-item-symbol">{crypto.symbol}</h1> | |
<img className="crypto-list-item-background-image" src={crypto.image} /> | |
</div> | |
<div className="crypto-list-item-content"> | |
<h1 className="crypto-list-item-rank">{crypto.rank}</h1> | |
<img className="crypto-list-item-image" src={crypto.image} /> | |
<div className="crypto-list-item-details"> | |
<h1 className="crypto-list-item-name">{crypto.name}</h1> | |
<h1 className="crypto-list-item-price">{crypto.price}</h1> | |
</div> | |
</div> | |
</button> | |
); | |
} | |
const CryptoList: React.FC = () => { | |
const { state } = React.useContext(AppContext); | |
if(state.status === RequestStatus.Success && state.cryptos.length > 0) { | |
const getItems = (): JSX.Element[] => { | |
return state.cryptos.map((crypto: ICrypto) => ( | |
<CryptoListItem key={crypto.id} crypto={crypto} /> | |
)); | |
} | |
return ( | |
<div id="crypto-list"> | |
{getItems()} | |
</div> | |
); | |
} | |
return null; | |
} | |
/* ---------- Crypto Price Chart Component ---------- */ | |
interface ICryptoPriceChartState { | |
chart: Chart; | |
points: IChartPoint[]; | |
status: RequestStatus; | |
} | |
const CryptoPriceChart: React.FC = () => { | |
const { selectedCrypto: crypto } = React.useContext(AppContext).state; | |
const id: string = "crypto-price-chart"; | |
const [state, setStateTo] = React.useState<ICryptoPriceChartState>({ | |
chart: null, | |
points: [], | |
status: RequestStatus.Loading | |
}); | |
const setStatusTo = (status: RequestStatus): void => { | |
setStateTo({ ...state, status }); | |
} | |
const setChartTo = (chart: Chart): void => { | |
setStateTo({ ...state, chart }); | |
} | |
React.useEffect(() => { | |
const fetch = async (): Promise<void> => { | |
try { | |
setStatusTo(RequestStatus.Loading); | |
const res: any = await axios.get(ChartUtility.getUrl(crypto.id)); | |
setStateTo({ | |
...state, | |
points: ChartUtility.mapPoints(res.data), | |
status: RequestStatus.Success | |
}); | |
} catch (err) { | |
console.error(err); | |
setStatusTo(RequestStatus.Error); | |
} | |
} | |
fetch(); | |
}, [crypto]); | |
React.useEffect(() => { | |
if(state.chart === null && state.status === RequestStatus.Success) { | |
setChartTo(ChartUtility.draw(id, state.points, crypto.change)); | |
} | |
}, [state.status]); | |
React.useEffect(() => { | |
if(state.chart !== null) { | |
const update = (): void => ChartUtility.update(state.chart, state.points, crypto.change); | |
update(); | |
} | |
}, [state.chart, state.points]); | |
const getLoadingSpinner = (): JSX.Element => { | |
if(state.status === RequestStatus.Loading) { | |
return ( | |
<div id="crypto-price-chart-loading-spinner"> | |
<LoadingSpinner /> | |
</div> | |
); | |
} | |
} | |
return ( | |
<div id="crypto-price-chart-wrapper"> | |
<canvas id={id} /> | |
{getLoadingSpinner()} | |
</div> | |
); | |
} | |
/* ---------- Crypto Details Component ---------- */ | |
interface CryptoFieldProps { | |
className?: string; | |
label: string; | |
value: string | number; | |
} | |
const CryptoField: React.FC<CryptoFieldProps> = (props: CryptoFieldProps) => { | |
return ( | |
<div className={classNames("crypto-field", props.className)}> | |
<h1 className="crypto-field-value">{props.value}</h1> | |
<h1 className="crypto-field-label">{props.label}</h1> | |
</div> | |
); | |
} | |
interface ICryptoDetailsState { | |
crypto: ICrypto; | |
transitioning: boolean; | |
} | |
const CryptoDetails: React.FC = () => { | |
const { selectedCrypto } = React.useContext(AppContext).state; | |
const [state, setStateTo] = React.useState<ICryptoDetailsState>({ | |
crypto: null, | |
transitioning: true | |
}); | |
const setTransitioningTo = (transitioning: boolean): void => { | |
setStateTo({ ...state, transitioning }); | |
} | |
const { crypto } = state; | |
React.useEffect(() => { | |
if(selectedCrypto !== null) { | |
setTransitioningTo(true); | |
const timeout: NodeJS.Timeout = setTimeout(() => { | |
setStateTo({ crypto: selectedCrypto, transitioning: false }); | |
}, 500); | |
return () => { | |
clearTimeout(timeout); | |
} | |
} | |
}, [selectedCrypto]); | |
if(crypto !== null) { | |
const sign: string = crypto.change >= 0 ? "positive" : "negative"; | |
return ( | |
<div id="crypto-details" className={classNames(sign, { transitioning: state.transitioning })}> | |
<div id="crypto-details-content"> | |
<div id="crypto-fields"> | |
<CryptoField label="Rank" value={crypto.rank} /> | |
<CryptoField label="Name" value={crypto.name} /> | |
<CryptoField label="Price" value={crypto.price} /> | |
<CryptoField label="Market Cap" value={crypto.marketCap} /> | |
<CryptoField label="24H Volume" value={crypto.volume} /> | |
<CryptoField label="Circulating Supply" value={crypto.supply} /> | |
<CryptoField | |
className={sign} | |
label="24H Change" | |
value={CryptoUtility.formatPercent(crypto.change)} | |
/> | |
</div> | |
<CryptoPriceChart /> | |
<h1 id="crypto-details-symbol">{crypto.symbol}</h1> | |
</div> | |
</div> | |
); | |
} | |
return null; | |
} | |
/* ---------- App Component ---------- */ | |
interface IAppState { | |
cryptos: ICrypto[]; | |
listToggled: boolean; | |
selectedCrypto: ICrypto; | |
status: RequestStatus; | |
} | |
interface IAppContext { | |
state: IAppState; | |
selectCrypto: (id: string) => void; | |
setStateTo: (state: IAppState) => void; | |
toggleList: (listToggled: boolean) => void; | |
} | |
const AppContext = React.createContext<IAppContext>(null); | |
const App: React.FC = () => { | |
const [state, setStateTo] = React.useState<IAppState>({ | |
cryptos: [], | |
listToggled: true, | |
selectedCrypto: null, | |
status: RequestStatus.Loading | |
}); | |
const setStatusTo = (status: RequestStatus): void => { | |
setStateTo({ ...state, status }); | |
} | |
const selectCrypto = (id: string): void => { | |
setStateTo({ | |
...state, | |
listToggled: window.innerWidth > 800, | |
selectedCrypto: CryptoUtility.getByID(id, state.cryptos) | |
}); | |
} | |
const toggleList = (listToggled: boolean): void => { | |
setStateTo({ ...state, listToggled }); | |
} | |
React.useEffect(() => { | |
const fetch = async (): Promise<void> => { | |
try { | |
setStatusTo(RequestStatus.Loading); | |
const res: any = await axios.get(`${CoinGeckoApi.Base}/${CoinGeckoApi.AllCoins}`); | |
setStateTo({ | |
...state, | |
cryptos: CryptoUtility.mapAll(res.data), | |
status: RequestStatus.Success | |
}); | |
} catch (err) { | |
console.error(err); | |
setStatusTo(RequestStatus.Error); | |
} | |
} | |
fetch(); | |
}, []); | |
React.useEffect(() => { | |
if(state.status === RequestStatus.Success && state.cryptos.length > 0) { | |
selectCrypto(state.cryptos[0].id); | |
} | |
}, [state.status]); | |
const getLoadingSpinner = (): JSX.Element => { | |
if(state.status === RequestStatus.Loading) { | |
return ( | |
<LoadingSpinner /> | |
); | |
} | |
} | |
return( | |
<AppContext.Provider value={{ state, selectCrypto, setStateTo, toggleList }}> | |
<div id="app" className={classNames({ "list-toggled": state.listToggled })}> | |
<CryptoList /> | |
<CryptoDetails /> | |
<CryptoListToggle /> | |
{getLoadingSpinner()} | |
</div> | |
</AppContext.Provider> | |
) | |
} | |
ReactDOM.render(<App/>, document.getElementById("root")); |
This file contains hidden or 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
<script src="https://unpkg.com/react@17/umd/react.development.js"></script> | |
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> | |
<script src="https://unpkg.com/browse/@types/[email protected]/index.d.ts"></script> | |
<script src="https://unpkg.com/browse/@types/[email protected]/index.d.ts"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.24.0/axios.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.3.1/index.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.2/chart.min.js"></script> | |
<script src="https://kit.fontawesome.com/86933bf68b.js"></script> |
This file contains hidden or 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
@function gray($color){ | |
@return rgb($color, $color, $color); | |
} | |
$shadow1: rgba(0, 0, 0, 0.12) 0px 1px 6px, rgba(0, 0, 0, 0.12) 0px 1px 4px; | |
$blue: rgb(30, 136, 229); | |
$green: rgb(76, 175, 80); | |
$red: rgb(198, 40, 40); | |
@keyframes fadeInFromLeft { | |
from { | |
opacity: 0; | |
transform: translateX(-20px); | |
} | |
50% { | |
opacity: 0; | |
} | |
to { | |
opacity: 1; | |
transform: translateX(0px); | |
} | |
} | |
@keyframes spin { | |
from { | |
transform: rotate(0deg); | |
} | |
50% { | |
transform: rotate(720deg); | |
} | |
to { | |
transform: rotate(1440deg); | |
} | |
} | |
body{ | |
margin: 0px; | |
overflow: hidden; | |
padding: 0px; | |
input, h1, a, button { | |
color: gray(90); | |
font-family: 'Rubik', sans-serif; | |
font-weight: 400; | |
margin: 0px; | |
padding: 0px; | |
} | |
} | |
#app { | |
background-color: gray(30); | |
height: 100vh; | |
overflow: hidden; | |
&.list-toggled { | |
#crypto-list { | |
transform: translateX(0px); | |
} | |
#crypto-details { | |
#crypto-details-content { | |
margin-left: 480px; | |
width: calc(100% - 480px); | |
} | |
} | |
} | |
.loading-spinner-wrapper { | |
align-items: center; | |
display: flex; | |
height: 100%; | |
justify-content: center; | |
left: 0px; | |
position: absolute; | |
top: 0px; | |
width: 100%; | |
.loading-spinner { | |
animation: spin 2s ease-in-out infinite; | |
pointer-events: none; | |
i { | |
color: white; | |
font-size: 3em; | |
} | |
} | |
} | |
#crypto-list { | |
animation: fadeInFromLeft 0.25s ease-in; | |
display: flex; | |
flex-direction: column; | |
gap: 10px; | |
height: calc(100% - 20px); | |
overflow: auto; | |
padding: 10px; | |
padding-left: 0px; | |
position: relative; | |
transform: translateX(-520px); | |
transition: all 0.25s; | |
width: 500px; | |
z-index: 2; | |
&::-webkit-scrollbar { | |
width: 0px; | |
} | |
.crypto-list-item { | |
background-color: gray(20); | |
border: none; | |
border-bottom-right-radius: 1000px; | |
border-top-right-radius: 1000px; | |
cursor: pointer; | |
display: flex; | |
outline: none; | |
padding: 10px; | |
padding-left: 0px; | |
position: relative; | |
transition: all 0.25s; | |
&:hover, | |
&:focus, | |
&.selected { | |
.crypto-list-item-content { | |
width: calc(100% - 120px); | |
} | |
} | |
&.selected { | |
.crypto-list-item-content { | |
background-color: rgba(white, 0.15); | |
} | |
} | |
.crypto-list-item-background { | |
align-items: center; | |
border-radius: 10px; | |
border-bottom-right-radius: 1000px; | |
border-top-right-radius: 1000px; | |
display: flex; | |
height: 100%; | |
left: 0px; | |
overflow: hidden; | |
position: absolute; | |
top: 0px; | |
width: 100%; | |
z-index: 1; | |
.crypto-list-item-symbol { | |
color: white; | |
font-size: 8em; | |
font-weight: 500; | |
max-width: calc(100% - 160px); | |
opacity: 0.1; | |
overflow: hidden; | |
position: relative; | |
text-transform: uppercase; | |
white-space: nowrap; | |
} | |
.crypto-list-item-background-image { | |
background-color: gray(20); | |
border-radius: 1000px; | |
height: 120px; | |
opacity: 0.25; | |
position: absolute; | |
right: 20px; | |
width: 120px; | |
z-index: 2; | |
} | |
} | |
.crypto-list-item-content { | |
align-items: center; | |
backdrop-filter: blur(5px); | |
background-color: rgba(white, 0.05); | |
border-bottom-right-radius: 1000px; | |
border-top-right-radius: 1000px; | |
box-shadow: $shadow1; | |
display: flex; | |
gap: 20px; | |
padding: 40px; | |
position: relative; | |
transition: all 0.25s; | |
white-space: nowrap; | |
width: calc(100% - 160px); | |
z-index: 2; | |
h1 { | |
color: white; | |
font-size: 1em; | |
} | |
.crypto-list-item-image { | |
border-radius: 1000px; | |
height: 60px; | |
width: 60px; | |
} | |
.crypto-list-item-rank { | |
font-size: 1.25em; | |
font-weight: 500; | |
text-align: right; | |
width: 40px; | |
} | |
.crypto-list-item-details { | |
display: flex; | |
flex-direction: column; | |
gap: 2px; | |
text-align: left; | |
width: 240px; | |
.crypto-list-item-name { | |
font-size: 1.75em; | |
} | |
} | |
} | |
} | |
} | |
#crypto-details { | |
animation: fadeInFromLeft 0.25s ease-in; | |
height: 100vh; | |
left: 0px; | |
position: fixed; | |
top: 0px; | |
width: 100vw; | |
z-index: 1; | |
&.transitioning { | |
#crypto-details-content { | |
#crypto-fields { | |
opacity: 0; | |
transform: translateX(-20px); | |
} | |
#crypto-details-background-image { | |
opacity: 0; | |
transform: translateY(-20px); | |
} | |
#crypto-details-symbol { | |
opacity: 0; | |
transform: translateY(20px); | |
} | |
} | |
} | |
#crypto-details-content { | |
border-left: 1px solid rgba(white, 0.05); | |
height: 100%; | |
margin-left: 0px; | |
min-width: 300px; | |
position: relative; | |
transition: all 0.25s; | |
width: calc(100% - 1px); | |
#crypto-fields { | |
display: inline-flex; | |
flex-direction: column; | |
gap: 10px; | |
padding: 10px; | |
padding-left: 40px; | |
padding-top: 20px; | |
position: relative; | |
transition: all 0.25s; | |
z-index: 3; | |
.crypto-field { | |
white-space: nowrap; | |
&.positive { | |
.crypto-field-value { | |
color: $green; | |
} | |
} | |
&.negative { | |
.crypto-field-value { | |
color: $red; | |
} | |
} | |
.crypto-field-value { | |
color: white; | |
font-size: 1.5em; | |
} | |
.crypto-field-label { | |
color: rgba(white, 0.5); | |
font-size: 0.8em; | |
font-weight: 500; | |
text-transform: uppercase; | |
} | |
} | |
} | |
#crypto-price-chart-wrapper { | |
height: 100%; | |
left: 0px; | |
position: absolute; | |
top: 0px; | |
width: 100%; | |
z-index: 2; | |
#crypto-price-chart-loading-spinner { | |
bottom: 0px; | |
height: 100px; | |
left: 0px; | |
position: absolute; | |
width: 100px; | |
z-index: 2; | |
.loading-spinner { | |
i { | |
font-size: 2em; | |
} | |
} | |
} | |
#crypto-price-chart { | |
height: 100%; | |
position: relative; | |
width: 100%; | |
} | |
} | |
#crypto-details-symbol { | |
bottom: 0px; | |
color: white; | |
filter: blur(5px); | |
font-size: 20em; | |
font-weight: 500; | |
left: 0px; | |
margin: 20px; | |
opacity: 0.15; | |
position: absolute; | |
text-transform: uppercase; | |
transition: all 0.25s; | |
z-index: 1; | |
} | |
} | |
} | |
#crypto-list-toggle-button { | |
background-color: gray(30); | |
border: none; | |
border-radius: 100px; | |
box-shadow: $shadow1; | |
bottom: 0px; | |
cursor: pointer; | |
margin: 20px; | |
padding: 20px; | |
position: absolute; | |
right: 0px; | |
z-index: 3; | |
&:hover, | |
&:focus { | |
i { | |
color: $blue; | |
} | |
} | |
i { | |
color: white; | |
font-size: 2em; | |
height: 30px; | |
line-height: 30px; | |
text-align: center; | |
width: 30px; | |
} | |
} | |
#youtube-link { | |
align-items: center; | |
border-radius: 6px; | |
display: flex; | |
gap: 10px; | |
margin: 10px; | |
padding: 10px; | |
position: absolute; | |
right: 0px; | |
text-decoration: none; | |
top: 0px; | |
z-index: 1; | |
&:hover, | |
&:focus { | |
backdrop-filter: blur(5px); | |
background-color: rgba(white, 0.1); | |
} | |
i { | |
color: $red; | |
font-size: 1.5em; | |
} | |
h1 { | |
color: white; | |
font-size: 1.25em; | |
} | |
} | |
} | |
@media (max-width: 800px) { | |
#app { | |
&.list-toggled { | |
#crypto-list { | |
width: calc(100% - 10px); | |
} | |
#crypto-details { | |
#crypto-details-content { | |
margin-left: 100%; | |
width: calc(100% - 1px); | |
} | |
} | |
} | |
#crypto-details { | |
#crypto-details-content { | |
#crypto-fields { | |
padding-left: 20px; | |
} | |
#crypto-details-symbol { | |
font-size: 10em; | |
} | |
} | |
} | |
} | |
} | |
@media (max-width: 500px) { | |
#app { | |
#crypto-list { | |
.crypto-list-item { | |
&:hover, | |
&:focus, | |
&.selected { | |
.crypto-list-item-content { | |
width: calc(100% - 80px); | |
} | |
} | |
.crypto-list-item-background { | |
.crypto-list-item-symbol { | |
font-size: 6em; | |
} | |
.crypto-list-item-background-image { | |
height: 80px; | |
width: 80px; | |
} | |
} | |
.crypto-list-item-content { | |
padding: 20px; | |
width: calc(100% - 120px); | |
.crypto-list-item-details { | |
.crypto-list-item-name { | |
font-size: 1.25em; | |
} | |
} | |
} | |
} | |
} | |
#youtube-link { | |
i { | |
font-size: 1.25em; | |
} | |
h1 { | |
font-size: 1em; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment