fix: add liquidity ux polish (#2024)
* zoom out on intiial * adjust initial zoom ranges * remove unneeded reactga event * adjust full range warning copy * update zoom * adjust zoom ranges and label around current price
This commit is contained in:
parent
2bc2a2c76e
commit
bf30013b6c
@ -40,6 +40,8 @@ const Tooltip = styled.text`
|
|||||||
// flips the handles draggers when close to the container edges
|
// flips the handles draggers when close to the container edges
|
||||||
const FLIP_HANDLE_THRESHOLD_PX = 20
|
const FLIP_HANDLE_THRESHOLD_PX = 20
|
||||||
|
|
||||||
|
const compare = (a1: [number, number], a2: [number, number]): boolean => a1[0] !== a2[0] || a1[1] !== a2[1]
|
||||||
|
|
||||||
export const Brush = ({
|
export const Brush = ({
|
||||||
id,
|
id,
|
||||||
xScale,
|
xScale,
|
||||||
@ -49,7 +51,8 @@ export const Brush = ({
|
|||||||
setBrushExtent,
|
setBrushExtent,
|
||||||
innerWidth,
|
innerWidth,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
colors,
|
westHandleColor,
|
||||||
|
eastHandleColor,
|
||||||
}: {
|
}: {
|
||||||
id: string
|
id: string
|
||||||
xScale: ScaleLinear<number, number>
|
xScale: ScaleLinear<number, number>
|
||||||
@ -59,10 +62,8 @@ export const Brush = ({
|
|||||||
setBrushExtent: (extent: [number, number]) => void
|
setBrushExtent: (extent: [number, number]) => void
|
||||||
innerWidth: number
|
innerWidth: number
|
||||||
innerHeight: number
|
innerHeight: number
|
||||||
colors: {
|
westHandleColor: string
|
||||||
west: string
|
eastHandleColor: string
|
||||||
east: string
|
|
||||||
}
|
|
||||||
}) => {
|
}) => {
|
||||||
const brushRef = useRef<SVGGElement | null>(null)
|
const brushRef = useRef<SVGGElement | null>(null)
|
||||||
const brushBehavior = useRef<BrushBehavior<SVGGElement> | null>(null)
|
const brushBehavior = useRef<BrushBehavior<SVGGElement> | null>(null)
|
||||||
@ -84,13 +85,13 @@ export const Brush = ({
|
|||||||
const scaled = (selection as [number, number]).map(xScale.invert) as [number, number]
|
const scaled = (selection as [number, number]).map(xScale.invert) as [number, number]
|
||||||
|
|
||||||
// avoid infinite render loop by checking for change
|
// avoid infinite render loop by checking for change
|
||||||
if (type === 'end' && (brushExtent[0] !== scaled[0] || brushExtent[1] !== scaled[1])) {
|
if (type === 'end' && compare(brushExtent, scaled)) {
|
||||||
setBrushExtent(scaled)
|
setBrushExtent(scaled)
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocalBrushExtent(scaled)
|
setLocalBrushExtent(scaled)
|
||||||
},
|
},
|
||||||
[xScale.invert, brushExtent, setBrushExtent]
|
[xScale, brushExtent, setBrushExtent]
|
||||||
)
|
)
|
||||||
|
|
||||||
// keep local and external brush extent in sync
|
// keep local and external brush extent in sync
|
||||||
@ -114,10 +115,7 @@ export const Brush = ({
|
|||||||
|
|
||||||
brushBehavior.current(select(brushRef.current))
|
brushBehavior.current(select(brushRef.current))
|
||||||
|
|
||||||
if (
|
if (previousBrushExtent && compare(brushExtent, previousBrushExtent)) {
|
||||||
previousBrushExtent &&
|
|
||||||
(brushExtent[0] !== previousBrushExtent[0] || brushExtent[1] !== previousBrushExtent[1])
|
|
||||||
) {
|
|
||||||
select(brushRef.current)
|
select(brushRef.current)
|
||||||
.transition()
|
.transition()
|
||||||
.call(brushBehavior.current.move as any, brushExtent.map(xScale))
|
.call(brushBehavior.current.move as any, brushExtent.map(xScale))
|
||||||
@ -136,9 +134,7 @@ export const Brush = ({
|
|||||||
if (!brushRef.current || !brushBehavior.current) return
|
if (!brushRef.current || !brushBehavior.current) return
|
||||||
|
|
||||||
brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any)
|
brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any)
|
||||||
// dependency on brushExtent would start an update loop
|
}, [brushExtent, xScale])
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [xScale])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowLabels(true)
|
setShowLabels(true)
|
||||||
@ -154,8 +150,8 @@ export const Brush = ({
|
|||||||
<>
|
<>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id={`${id}-gradient-selection`} x1="0%" y1="100%" x2="100%" y2="100%">
|
<linearGradient id={`${id}-gradient-selection`} x1="0%" y1="100%" x2="100%" y2="100%">
|
||||||
<stop stopColor={colors.west} />
|
<stop stopColor={westHandleColor} />
|
||||||
<stop stopColor={colors.east} offset="1" />
|
<stop stopColor={eastHandleColor} offset="1" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
|
||||||
{/* clips at exactly the svg area */}
|
{/* clips at exactly the svg area */}
|
||||||
@ -186,7 +182,7 @@ export const Brush = ({
|
|||||||
}, 1)`}
|
}, 1)`}
|
||||||
>
|
>
|
||||||
<g clipPath={`url(#${id}-handles-clip)`}>
|
<g clipPath={`url(#${id}-handles-clip)`}>
|
||||||
<Handle color={colors.west} d={brushHandlePath(innerHeight)} />
|
<Handle color={westHandleColor} d={brushHandlePath(innerHeight)} />
|
||||||
<HandleAccent d={brushHandleAccentPath()} />
|
<HandleAccent d={brushHandleAccentPath()} />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
@ -208,7 +204,7 @@ export const Brush = ({
|
|||||||
}, 1)`}
|
}, 1)`}
|
||||||
>
|
>
|
||||||
<g clipPath={`url(#${id}-handles-clip)`}>
|
<g clipPath={`url(#${id}-handles-clip)`}>
|
||||||
<Handle color={colors.east} d={brushHandlePath(innerHeight)} />
|
<Handle color={eastHandleColor} d={brushHandlePath(innerHeight)} />
|
||||||
<HandleAccent d={brushHandleAccentPath()} />
|
<HandleAccent d={brushHandleAccentPath()} />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
@ -228,8 +224,7 @@ export const Brush = ({
|
|||||||
),
|
),
|
||||||
[
|
[
|
||||||
brushLabelValue,
|
brushLabelValue,
|
||||||
colors.east,
|
eastHandleColor,
|
||||||
colors.west,
|
|
||||||
flipEastHandle,
|
flipEastHandle,
|
||||||
flipWestHandle,
|
flipWestHandle,
|
||||||
hovering,
|
hovering,
|
||||||
@ -238,6 +233,7 @@ export const Brush = ({
|
|||||||
innerWidth,
|
innerWidth,
|
||||||
localBrushExtent,
|
localBrushExtent,
|
||||||
showLabels,
|
showLabels,
|
||||||
|
westHandleColor,
|
||||||
xScale,
|
xScale,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -24,7 +24,7 @@ export function Chart({
|
|||||||
}: LiquidityChartRangeInputProps) {
|
}: LiquidityChartRangeInputProps) {
|
||||||
const svgRef = useRef<SVGSVGElement | null>(null)
|
const svgRef = useRef<SVGSVGElement | null>(null)
|
||||||
|
|
||||||
const [zoom, setZoom] = useState<ZoomTransform>()
|
const [zoom, setZoom] = useState<ZoomTransform | null>(null)
|
||||||
|
|
||||||
const [innerHeight, innerWidth] = useMemo(
|
const [innerHeight, innerWidth] = useMemo(
|
||||||
() => [height - margins.top - margins.bottom, width - margins.left - margins.right],
|
() => [height - margins.top - margins.bottom, width - margins.left - margins.right],
|
||||||
@ -34,7 +34,7 @@ export function Chart({
|
|||||||
const { xScale, yScale } = useMemo(() => {
|
const { xScale, yScale } = useMemo(() => {
|
||||||
const scales = {
|
const scales = {
|
||||||
xScale: scaleLinear()
|
xScale: scaleLinear()
|
||||||
.domain([(1 - zoomLevels.initial) * current, (1 + zoomLevels.initial) * current] as number[])
|
.domain([current * zoomLevels.initialMin, current * zoomLevels.initialMax] as number[])
|
||||||
.range([0, innerWidth]),
|
.range([0, innerWidth]),
|
||||||
yScale: scaleLinear()
|
yScale: scaleLinear()
|
||||||
.domain([0, max(series, yAccessor)] as number[])
|
.domain([0, max(series, yAccessor)] as number[])
|
||||||
@ -47,7 +47,12 @@ export function Chart({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return scales
|
return scales
|
||||||
}, [zoomLevels.initial, current, innerWidth, series, innerHeight, zoom])
|
}, [current, zoomLevels.initialMin, zoomLevels.initialMax, innerWidth, series, innerHeight, zoom])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// reset zoom as necessary
|
||||||
|
setZoom(null)
|
||||||
|
}, [zoomLevels])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!brushDomain) {
|
if (!brushDomain) {
|
||||||
@ -121,10 +126,8 @@ export function Chart({
|
|||||||
innerWidth={innerWidth}
|
innerWidth={innerWidth}
|
||||||
innerHeight={innerHeight}
|
innerHeight={innerHeight}
|
||||||
setBrushExtent={onBrushDomainChange}
|
setBrushExtent={onBrushDomainChange}
|
||||||
colors={{
|
westHandleColor={styles.brush.handle.west}
|
||||||
west: styles.brush.handle.west,
|
eastHandleColor={styles.brush.handle.east}
|
||||||
east: styles.brush.handle.east,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -45,7 +45,7 @@ export default function Zoom({
|
|||||||
}) {
|
}) {
|
||||||
const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>()
|
const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>()
|
||||||
|
|
||||||
const [zoomIn, zoomOut, reset] = useMemo(
|
const [zoomIn, zoomOut, reset, initial] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
() =>
|
() =>
|
||||||
svg &&
|
svg &&
|
||||||
@ -65,6 +65,12 @@ export default function Zoom({
|
|||||||
select(svg as Element)
|
select(svg as Element)
|
||||||
.transition()
|
.transition()
|
||||||
.call(zoomBehavior.current.scaleTo, 1),
|
.call(zoomBehavior.current.scaleTo, 1),
|
||||||
|
() =>
|
||||||
|
svg &&
|
||||||
|
zoomBehavior.current &&
|
||||||
|
select(svg as Element)
|
||||||
|
.transition()
|
||||||
|
.call(zoomBehavior.current.scaleTo, 0.5),
|
||||||
],
|
],
|
||||||
[svg, zoomBehavior]
|
[svg, zoomBehavior]
|
||||||
)
|
)
|
||||||
@ -72,7 +78,6 @@ export default function Zoom({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!svg) return
|
if (!svg) return
|
||||||
|
|
||||||
// zoom
|
|
||||||
zoomBehavior.current = zoom()
|
zoomBehavior.current = zoom()
|
||||||
.scaleExtent([zoomLevels.min, zoomLevels.max])
|
.scaleExtent([zoomLevels.min, zoomLevels.max])
|
||||||
.translateExtent([
|
.translateExtent([
|
||||||
@ -88,7 +93,12 @@ export default function Zoom({
|
|||||||
select(svg as Element)
|
select(svg as Element)
|
||||||
.call(zoomBehavior.current)
|
.call(zoomBehavior.current)
|
||||||
.on('mousedown.zoom', null)
|
.on('mousedown.zoom', null)
|
||||||
}, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior, zoomLevels.max, zoomLevels.min])
|
}, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior, zoomLevels, zoomLevels.max, zoomLevels.min])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// reset zoom to initial on zoomLevel chang
|
||||||
|
initial()
|
||||||
|
}, [initial, zoomLevels])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper count={showClear ? 3 : 2}>
|
<Wrapper count={showClear ? 3 : 2}>
|
||||||
|
@ -20,17 +20,20 @@ import { ZoomLevels } from './types'
|
|||||||
|
|
||||||
const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = {
|
const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = {
|
||||||
[FeeAmount.LOW]: {
|
[FeeAmount.LOW]: {
|
||||||
initial: 0.002,
|
initialMin: 0.999,
|
||||||
|
initialMax: 1.001,
|
||||||
min: 0.001,
|
min: 0.001,
|
||||||
max: 2,
|
max: 1.5,
|
||||||
},
|
},
|
||||||
[FeeAmount.MEDIUM]: {
|
[FeeAmount.MEDIUM]: {
|
||||||
initial: 0.3,
|
initialMin: 0.5,
|
||||||
|
initialMax: 2,
|
||||||
min: 0.01,
|
min: 0.01,
|
||||||
max: 20,
|
max: 20,
|
||||||
},
|
},
|
||||||
[FeeAmount.HIGH]: {
|
[FeeAmount.HIGH]: {
|
||||||
initial: 0.3,
|
initialMin: 0.5,
|
||||||
|
initialMax: 2,
|
||||||
min: 0.01,
|
min: 0.01,
|
||||||
max: 20,
|
max: 20,
|
||||||
},
|
},
|
||||||
@ -95,11 +98,6 @@ export default function LiquidityChartRangeInput({
|
|||||||
let leftRangeValue = Number(domain[0])
|
let leftRangeValue = Number(domain[0])
|
||||||
const rightRangeValue = Number(domain[1])
|
const rightRangeValue = Number(domain[1])
|
||||||
|
|
||||||
ReactGA.event({
|
|
||||||
category: 'Liquidity',
|
|
||||||
action: 'Chart brushed',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (leftRangeValue <= 0) {
|
if (leftRangeValue <= 0) {
|
||||||
leftRangeValue = 1 / 10 ** 6
|
leftRangeValue = 1 / 10 ** 6
|
||||||
}
|
}
|
||||||
@ -133,7 +131,9 @@ export default function LiquidityChartRangeInput({
|
|||||||
if (d === 'w' && ticksAtLimit[Bound.LOWER]) return '0'
|
if (d === 'w' && ticksAtLimit[Bound.LOWER]) return '0'
|
||||||
if (d === 'e' && ticksAtLimit[Bound.UPPER]) return '∞'
|
if (d === 'e' && ticksAtLimit[Bound.UPPER]) return '∞'
|
||||||
|
|
||||||
const percent = (((x < price ? -1 : 1) * (Math.max(x, price) - Math.min(x, price))) / Math.min(x, price)) * 100
|
//const percent = (((x < price ? -1 : 1) * (Math.max(x, price) - Math.min(x, price))) / Math.min(x, price)) * 100
|
||||||
|
|
||||||
|
const percent = (x < price ? -1 : 1) * ((Math.max(x, price) - Math.min(x, price)) / price) * 100
|
||||||
|
|
||||||
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
|
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,8 @@ export interface Margins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoomLevels {
|
export interface ZoomLevels {
|
||||||
initial: number
|
initialMin: number
|
||||||
|
initialMax: number
|
||||||
min: number
|
min: number
|
||||||
max: number
|
max: number
|
||||||
}
|
}
|
||||||
|
@ -842,8 +842,7 @@ export default function AddLiquidity({
|
|||||||
<RowFixed>
|
<RowFixed>
|
||||||
<TYPE.yellow ml="12px" fontSize="13px" margin={0} fontWeight={400}>
|
<TYPE.yellow ml="12px" fontSize="13px" margin={0} fontWeight={400}>
|
||||||
<Trans>
|
<Trans>
|
||||||
On Uniswap V3, setting a range across all prices like V2 is less capital efficient
|
Full range positions may earn less fees than concentrated positions. Learn more{' '}
|
||||||
than a concentrated one. Learn more{' '}
|
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
style={{ color: theme.yellow3, textDecoration: 'underline' }}
|
style={{ color: theme.yellow3, textDecoration: 'underline' }}
|
||||||
href={''}
|
href={''}
|
||||||
|
Loading…
Reference in New Issue
Block a user