feat: trade range input implementation (#287)

This commit is contained in:
Yusuf Seyrek 2023-07-07 18:19:00 +03:00 committed by GitHub
parent 0123685f79
commit c0d62cd8f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 4 deletions

View File

@ -30,8 +30,8 @@ const mockedDepositedVault: DepositedVault = {
},
cap: {
denom: 'mock',
max: 10,
used: 1,
max: BN(10),
used: BN(1),
},
}

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 1 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0.5" y1="0.5" x2="0.5" y2="7.5" stroke="currentColor" stroke-linecap="round" stroke-dasharray="2 2"/>
</svg>

After

Width:  |  Height:  |  Size: 190 B

View File

@ -41,4 +41,5 @@ export { default as Subtract } from 'components/Icons/Subtract.svg'
export { default as SwapIcon } from 'components/Icons/SwapIcon.svg'
export { default as TrashBin } from 'components/Icons/TrashBin.svg'
export { default as Wallet } from 'components/Icons/Wallet.svg'
export { default as VerticalThreeLine } from 'components/Icons/VerticalThreeLine.svg'
// @endindex

View File

@ -0,0 +1,69 @@
import { VerticalThreeLine } from 'components/Icons'
interface Props {
value: number
marginThreshold?: number
max: number
}
function InputOverlay({ max, value, marginThreshold }: Props) {
// 33 is the thumb width
const thumbPosPercent = 100 / (max / value)
const thumbPadRight = (thumbPosPercent / 100) * 33
const markPosPercent = 100 / (max / (marginThreshold ?? 1))
const markPadRight = (markPosPercent / 100) * 33
const hasPastMarginThreshold = marginThreshold ? value >= marginThreshold : undefined
return (
<>
<div
className={className.marksWrapper}
style={{
backgroundImage:
'linear-gradient(270deg, rgba(255, 97, 141, 0.89) 0%, rgba(66, 58, 70, 0.05) 100%)',
backgroundSize: `${thumbPosPercent}%`,
}}
>
{Array.from(Array(9).keys()).map((i) => (
<div key={`mark-${i}`} className={className.mark} />
))}
{marginThreshold && (
<div
key='margin-mark'
className={className.marginMarkContainer}
style={{ left: `calc(${markPosPercent}% - ${markPadRight}px)` }}
>
<div className={className.marginMark} />
<div className={className.marginMarkOverlay}>
<VerticalThreeLine className='h-2 w-[1px]' />
<div className={!hasPastMarginThreshold ? 'opacity-50' : 'opacity-100'}>Margin</div>
</div>
</div>
)}
</div>
<div
className={className.inputThumbOverlay}
style={{ left: `calc(${thumbPosPercent}% - ${thumbPadRight}px)` }}
>
{value}
</div>
</>
)
}
const className = {
inputThumbOverlay: `
w-[33px] h-4 absolute text-[10px] top-[5px]
pointer-events-none text-center font-bold
bg-pink shadow-md border-b-0 border-1
border-solid rounded-sm backdrop-blur-sm border-white/10
`,
mark: `w-1 h-1 bg-black rounded-full bg-opacity-30`,
marginMark: `w-1 h-1 bg-white rounded-full`,
marginMarkContainer: 'absolute w-[33px] flex justify-center',
marginMarkOverlay: 'absolute top-2.5 flex-col text-xs w-[33px] items-center flex',
marksWrapper: `absolute flex-1 flex w-full justify-evenly bg-no-repeat
top-[8.5px] pointer-events-none pt-[2.5px] pb-[2.5px] rounded-lg`,
}
export default InputOverlay

View File

@ -0,0 +1,80 @@
import { ChangeEvent, useCallback } from 'react'
import classNames from 'classnames'
import InputOverlay from 'components/RangeInput/InputOverlay'
type Props = {
value: number
onChange: (value: number) => void
wrapperClassName?: string
disabled?: boolean
max: number
marginThreshold?: number
}
function RangeInput(props: Props) {
const { value, max, onChange, wrapperClassName, disabled, marginThreshold } = props
const handleOnChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
onChange(parseInt(event.target.value))
},
[onChange],
)
return (
<div
className={classNames(className.containerDefault, wrapperClassName, {
[className.disabled]: !disabled,
})}
>
<div className={className.inputWrapper}>
<input
className={className.input}
type='range'
value={value}
max={max}
onChange={handleOnChange}
/>
<InputOverlay max={max} marginThreshold={marginThreshold} value={value} />
</div>
<div className={className.legendWrapper}>
<span>0</span>
<span>{max}</span>
</div>
</div>
)
}
const className = {
containerDefault: 'relative min-h-3 w-full transition-opacity',
disabled: 'pointer-events-none opacity-50',
legendWrapper: 'flex w-full justify-between text-xs text-opacity-50 text-white font-bold',
inputWrapper: 'relative h-[30px]',
input: `
relative w-full appearance-none bg-transparent cursor-pointer
[&::-webkit-slider-runnable-track]:bg-white
[&::-webkit-slider-runnable-track]:bg-opacity-20
[&::-webkit-slider-runnable-track]:h-[9px]
[&::-webkit-slider-runnable-track]:rounded-lg
[&::-moz-range-track]:bg-white
[&::-moz-range-track]:bg-opacity-20
[&::-moz-range-track]:h-1
[&::-moz-range-track]:pb-[5px]
[&::-moz-range-track]:rounded-lg
[&::-webkit-slider-thumb]:appearance-none
[&::-webkit-slider-thumb]:-mt-1
[&::-webkit-slider-thumb]:w-[33px]
[&::-webkit-slider-thumb]:h-4
[&::-moz-range-thumb]:appearance-none
[&::-moz-range-thumb]:opacity-0
[&::-moz-range-thumb]:w-[33px]
[&::-moz-range-thumb]:h-4
`,
}
export default RangeInput

View File

@ -1,11 +1,12 @@
import { useState } from 'react'
import { useParams } from 'react-router-dom'
import classNames from 'classnames'
import Loading from 'components/Loading'
import Text from 'components/Text'
import Divider from 'components/Divider'
import AssetSelector from './AssetSelector/AssetSelector'
import RangeInput from 'components/RangeInput'
import AssetSelector from 'components/Trade/TradeModule/AssetSelector/AssetSelector'
function Content() {
const params = useParams()
@ -25,6 +26,8 @@ function Fallback() {
}
export default function TradeModule() {
const [value, setValue] = useState(0)
return (
<div
className={classNames(
@ -35,6 +38,10 @@ export default function TradeModule() {
>
<AssetSelector />
<Divider />
<div className='p-4'>
<RangeInput max={4000} marginThreshold={2222} value={value} onChange={setValue} />
</div>
</div>
)
}

View File

@ -102,6 +102,7 @@ module.exports = {
warning: '#F79009',
'warning-bg': '#FEC84B',
white: '#FFF',
pink: '#de587f',
},
fontFamily: {
sans: ['Inter', 'sans-serif'],