add styles

This commit is contained in:
UserX 2023-08-09 15:32:19 +03:00
parent 3fc789bb02
commit e693462f0f
10 changed files with 309 additions and 238 deletions

View File

@ -1,7 +1,7 @@
{
"name": "cryptocurrency-converter",
"version": "0.1.0",
"homepage": "https://dmitry220.github.io/crypto-converter",
"homepage": "https://vbatischev1.github.io/crypto-converter",
"private": true,
"dependencies": {
"@tanstack/react-query": "^5.0.0-beta.9",

View File

@ -1,8 +1,16 @@
body{
background: #e5e5e5;
}
.converter{
max-width: 812px;
max-width: 610px;
margin: 50px auto;
padding: 0 15px;
background: #fff;
padding: 15px;
border-radius: 10px;
&__head{
font-weight: 700;
@ -26,6 +34,7 @@
}
&__row{
border-radius: 10px;
height: 100%;
font-size: 24px;
display: flex;
@ -39,10 +48,11 @@
font-size: inherit;
}
&_border{
border: 3px solid gray;
}
}
&__crypto-name{
height: 100%;
vertical-align: middle;
@ -50,42 +60,52 @@
cursor: pointer;
display: flex;
align-items: center;
font-size: 12px;
line-height: 15px;
justify-content: center;
padding: 0 15px;
border-left: 2px solid rgba(7, 28, 71, 0.12);
font-weight: 700;
border: 2px solid rgba(7, 28, 71, 0.12);
border-left: none;
border-radius: 0 10px 10px 0;
&-title{
display: flex;
align-items: center;
flex-direction: column;
gap: 3px;
}
img{
width: 50px;
width: 40px;
height: 20px;
}
}
&__arrow{
width: 50px;
height: 35px;
object-fit: contain;
cursor: pointer;
@media (max-width:768px) {
height: 50px;
width: 50px;
}
@media (max-width:576px) {
height: 25px;
width: 25px;
}
width: 20px;
background: #999;
-webkit-mask: url(//yastatic.net/s3/web4static/_/v2/static/media/Swap_16.c0236c02.svg) no-repeat center;
mask: url(//yastatic.net/s3/web4static/_/v2/static/media/Swap_16.c0236c02.svg) no-repeat center;
}
&__input{
border: 2px solid rgba(7, 28, 71, 0.12);
border-radius: 10px 0 0 10px;
height: 100%;
font-family: HelveticaNeue-Light,"Helvetica Neue Light",Helvetica,Arial,sans-serif;
font-size: inherit;
text-align: right;
width: 201px;
color: #000;
border-width: 0 5px 0px 0px;
border-color: rgb(107 107 107);
position: relative;
top: 0;
padding: 0 10px 0 0;
outline: none;
@media (max-width:768px) {
border-width: 0;
}
@ -95,33 +115,3 @@
font-size: 18px;
}
}
.popup-converter{
position: absolute;
z-index: 2;
inset: 75px auto auto 0;
width: 95%;
box-shadow: 0 4px 24px rgba(0,0,0,.25);
background: #fff;
display: flex;
flex-direction: column;
padding: 10px;
gap: 12px;
border-radius: 5px;
&__button{
background: transparent;
transition: 0.3s all ease;
padding: 10px;
font-size: 18px;
cursor: pointer;
border: none;
&_active{
background: green;
color: #fff;
}
&:hover{
background: green;
color: #fff;
}
}
}

View File

@ -1,12 +1,10 @@
import React, {ChangeEvent, useEffect, useRef, useState} from 'react';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import './App.scss';
import arrowSwap from '../src/assets/images/arrow-right-left.svg'
import btc from '../src/assets/images/btc.svg'
import usdt from '../src/assets/images/usdt.svg'
import eth from '../src/assets/images/eth.svg'
import useOnClickOutside from './hooks/useOnClickOutside';
import {SecondValuationItem} from './Components/SecondValutionItem/SecondValutionItem';
import {FirstValuationItem} from './Components/FirstValutionItem/FirstValutionItem';
import { PopupConverter } from './Components/popup-converter/Popup-converter';
import { convert } from '.';
import { IValutions } from './models/IValutions';
@ -15,37 +13,64 @@ function App() {
const valuations: IValutions[] = [
{
title: 'BTC',
img: btc
img: btc,
alt: 'Bitcoin'
},
{
title: 'USDT',
img: usdt
img: usdt,
alt: 'Tether'
},
{
title: 'ETH',
img: eth
img: eth,
alt: 'Ethereum'
},
]
const ref = useRef<HTMLDivElement>(null);
const [firstValue, setFirstValue] = useState<number>(0)
const [secondValue, setSecondValue] = useState<number>(0)
const [firstValue, setFirstValue] = useState<string>('1')
const [secondValue, setSecondValue] = useState<string>('1')
const [firstValuation, setFirstValuation] = useState<IValutions>(valuations[0])
const [secondValuation, setSecondValuation] = useState<IValutions>(valuations[1])
const [showFirstValuation, setShowFirstValuation] = useState<boolean>()
const [showSecondValuation, setShowSecondValuation] = useState<boolean>()
const [isSwap, setSwap] = useState(false)
const [isSwap, setSwap] = useState(true)
const regexInput = (e: ChangeEvent<HTMLInputElement>) => {
let [_, sign, integer, decimals]: any = e.target.value.replace(/[^\d\.\-]/g, "")
.replace(/(\..*?)\./g, "$1")
.replace(/(.+)-/g, "$1")
.match(/^(-?)(.*?)((?:\.\d*)?)$/);
let pos: number = Number(e.target.selectionStart) - 1;
if (!integer && decimals) pos += 2;
if (integer || decimals) {
integer = +integer;
}
const formatted = sign + integer + decimals;
if (formatted !== e.target.value) {
e.target.value = formatted;
e.target.setSelectionRange(pos, pos);
}
}
const handlerInputOne = (e: ChangeEvent<HTMLInputElement>) => {
setFirstValue(+e.target.value)
getSecondValuation(+e.target.value)
regexInput(e)
setFirstValue(e.target.value)
getSecondValuation(+e.target.value || 0)
}
const handlerInputTwo = (e: ChangeEvent<HTMLInputElement>) => {
setSecondValue(+e.target.value)
getFirstValuation(+e.target.value)
regexInput(e)
setSecondValue(e.target.value)
getFirstValuation(+e.target.value || 0)
}
const openFirstListValuation = () => setShowFirstValuation(true)
@ -54,12 +79,12 @@ function App() {
const getSecondValuation = async (value: number) => {
await convert.ready()
setSecondValue(convert[firstValuation.title][secondValuation.title](value))
setSecondValue(convert[firstValuation.title][secondValuation.title](value) || 0)
}
const getFirstValuation = async (value: number) => {
await convert.ready()
setFirstValue(convert[secondValuation.title][firstValuation.title](value))
setFirstValue(convert[secondValuation.title][firstValuation.title](value) || 0)
}
const swap = () => {
@ -74,7 +99,7 @@ function App() {
});
useEffect(() => {
getSecondValuation(firstValue)
getSecondValuation(+firstValue)
}, [firstValuation])
useEffect(() => {
@ -82,63 +107,77 @@ function App() {
setSwap(false)
return
}
getFirstValuation(secondValue)
getFirstValuation(+secondValue)
}, [secondValuation])
useEffect(() => {
const interval = setInterval(() => {
getSecondValuation(+firstValue)
}, 25000)
return () => clearInterval(interval)
}, [])
return (
<div className="converter">
<div className="converter__head">
Тестовое задание. React. Typescript
</div>
<div className="converter__body">
{ }
<div className="converter__row converter__row_border">
<input className='converter__input' min={0} type="number" value={firstValue} onChange={handlerInputOne} />
<input className='converter__input' type="text" value={firstValue}
onChange={handlerInputOne} />
<span className='converter__crypto-name' onClick={openFirstListValuation}>
<div className={'converter__crypto-name-title'}>
<img src={firstValuation.img} alt="" />
{firstValuation.title}
</div>
<div>
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="8" height="14"><path
d="M4 0l4 6H0l4-6zm0 14l4-6H0l4 6z" /></svg>
</div>
</span>
{showFirstValuation && <div className='converter__popup popup-converter' ref={ref}>
{valuations.map((item, index) =>
<FirstValuationItem
<PopupConverter
key={index}
item={item}
firstValuation={firstValuation.title}
setSecondValuation={setSecondValuation}
secondValuation={secondValuation.title}
setFirstValuation={setFirstValuation}
setValuation={setFirstValuation}
valuation={firstValuation.title}
/>)}
</div>
}
</div>
<div className="converter__row">
<img className='converter__arrow' src={arrowSwap} alt="" onClick={swap} />
<div className={'converter__arrow'} onClick={swap} />
</div>
<div className="converter__row converter__row_border">
<input className='converter__input' min={0} type="number" value={secondValue} onChange={handlerInputTwo} />
<input className='converter__input' min={0} type="text" maxLength={20} value={secondValue}
onChange={handlerInputTwo} />
<span className='converter__crypto-name' onClick={openSecondListValuation}>
<div className={'converter__crypto-name-title'}>
<img src={secondValuation.img} alt="" />
{secondValuation.title}
</div>
<div>
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="8" height="14"><path
d="M4 0l4 6H0l4-6zm0 14l4-6H0l4 6z" /></svg>
</div>
</span>
{showSecondValuation && <div className='converter__popup popup-converter' ref={ref}>
{valuations.map((item, index) =>
<SecondValuationItem
<PopupConverter
key={index}
item={item}
firstValuation={firstValuation.title}
setSecondValuation={setSecondValuation}
secondValuation={secondValuation.title}
setFirstValuation={setFirstValuation}
valuation={secondValuation.title}
setValuation={setSecondValuation}
/>)}
</div>
}
</div>
</div>
<footer className="converter__footer">
<b>{(firstValue || 0) + ' ' + firstValuation.title + ' = ' + (secondValue || 0) + ' ' + secondValuation.title}</b> <br />
<b>{(firstValue || 0) + ' ' + firstValuation.title + ' = ' + (secondValue || 0) + ' ' + secondValuation.title}</b>
<br />
Данные носят ознакомительный характер {'\t'}
<b>
{

View File

@ -1,28 +0,0 @@
import { Dispatch, FC, MouseEvent } from "react"
import { IListValuationsName } from "../../models/IListValutionsName"
export const FirstValuationItem: FC<IListValuationsName> = (
{
item,
firstValuation,
secondValuation,
setFirstValuation,
setSecondValuation
}
) => {
return (
<button type='button'
className={firstValuation != item.title ? 'popup-converter__button' : 'popup-converter__button popup-converter__button_active'}
value={item.title}
onClick={(e: MouseEvent<HTMLButtonElement>) => {
setFirstValuation({
img: item.img,
title: e.currentTarget.value
})
}}>{item.title}</button>
)
}

View File

@ -1,25 +0,0 @@
import { Dispatch, FC, MouseEvent } from "react"
import { IListValuationsName } from "../../models/IListValutionsName"
export const SecondValuationItem: FC<IListValuationsName> = ({
item,
firstValuation,
secondValuation,
setFirstValuation,
setSecondValuation
}
) => {
return (
<button type='button'
className={secondValuation != item.title ? 'popup-converter__button' : 'popup-converter__button popup-converter__button_active'}
value={item.title} onClick={(e: MouseEvent<HTMLButtonElement>) => {
setSecondValuation({
img: item.img,
title: e.currentTarget.value
})
}}>{item.title}</button>
)
}

View File

@ -0,0 +1,54 @@
.popup-converter{
position: absolute;
z-index: 2;
inset: 75px auto auto 0;
width: 100%;
box-shadow: 0 4px 24px rgba(0,0,0,.25);
background: #fff;
display: flex;
flex-direction: column;
padding: 10px 0;
border-radius: 5px;
&__container{
padding: 4px 12px;
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
&:hover{
background: #ffeca1;
}
}
&__check{
display: flex;
flex: 0 0 16px;
}
&__body{
display: flex;
align-items: center;
}
&__img{
flex: 0 0 20px;
height: 20px;
img{
width: 100%;
height: 100%;
object-fit: cover;
}
}
&__button{
background: transparent;
transition: 0.3s all ease;
// padding: 10px;
font-size: 16px;
font-weight: 700;
cursor: pointer;
border: none;
span{
margin: 0 4px;
font-size: 16px;
line-height: 24px;
color: rgba(84,96,122,.68);
}
}
}

View File

@ -0,0 +1,42 @@
import {Dispatch, FC, MouseEvent} from "react"
import {IListValuationsName} from "../../models/IListValutionsName"
import './Popup-converter.scss'
export const PopupConverter: FC<IListValuationsName> = (
{
item,
valuation,
setValuation,
}
) => {
const setActiveValuation = (e: MouseEvent<HTMLDivElement>) => {
setValuation({
img: item.img,
title: item.title
})
}
return (
<div className={'popup-converter__container'} onClick={setActiveValuation}>
<div className={'popup-converter__check'}>
{valuation === item.title &&
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="16" height="10">
<path d="M7.207 7.506L3.629 3.81 2.343 4.939l4.841 5.002 8.462-8.428L14.382.362z"/>
</svg>}
</div>
<div className={'popup-converter__body'}>
<img className={'popup-converter__img'} src={item.img} alt="icon"/>
<button type='button'
className={'popup-converter__button'}
value={item.title}
>{item.title} <span>{item.alt}</span></button>
</div>
</div>
)
}

View File

@ -8,7 +8,7 @@ import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
const CryptoConvert = require("crypto-convert").default;
export const convert = new CryptoConvert({
cryptoInterval: 600000, //Crypto prices update interval in ms (default 5 seconds on Node.js & 15 seconds on Browsers)
cryptoInterval: 20000, //Crypto prices update interval in ms (default 5 seconds on Node.js & 15 seconds on Browsers)
fiatInterval: (60 * 1e3 * 60), //Fiat prices update interval (default every 1 hour)
calculateAverage: true, //Calculate the average crypto price from exchanges
binance: true, //Use binance rates

View File

@ -3,8 +3,6 @@ import { IValutions } from "./IValutions";
export interface IListValuationsName {
item: IValutions,
setFirstValuation: Dispatch<IValutions>,
setSecondValuation: Dispatch<IValutions>,
secondValuation: string,
firstValuation: string
setValuation: Dispatch<IValutions>,
valuation: string
}

View File

@ -1,4 +1,5 @@
export interface IValutions {
title: string,
img: string
img: string,
alt?: string
}