feat: trade range input implementation (#287)
This commit is contained in:
parent
0123685f79
commit
c0d62cd8f0
@ -30,8 +30,8 @@ const mockedDepositedVault: DepositedVault = {
|
||||
},
|
||||
cap: {
|
||||
denom: 'mock',
|
||||
max: 10,
|
||||
used: 1,
|
||||
max: BN(10),
|
||||
used: BN(1),
|
||||
},
|
||||
}
|
||||
|
||||
|
3
src/components/Icons/VerticalThreeLine.svg
Normal file
3
src/components/Icons/VerticalThreeLine.svg
Normal 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 |
@ -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
|
||||
|
69
src/components/RangeInput/InputOverlay.tsx
Normal file
69
src/components/RangeInput/InputOverlay.tsx
Normal 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
|
80
src/components/RangeInput/index.tsx
Normal file
80
src/components/RangeInput/index.tsx
Normal 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
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ module.exports = {
|
||||
warning: '#F79009',
|
||||
'warning-bg': '#FEC84B',
|
||||
white: '#FFF',
|
||||
pink: '#de587f',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
|
Loading…
Reference in New Issue
Block a user