@reservoir0x/relay-kit-ui
Version:
Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.
143 lines • 8.08 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useRef } from 'react';
import { Dropdown } from '../primitives/Dropdown.js';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faGear, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { Button, Flex, Input, Text, Box } from '../primitives/index.js';
import Tooltip from '../primitives/Tooltip.js';
import { TabsRoot, TabsList, TabsTrigger, TabsContent } from '../primitives/Tabs.js';
import { getSlippageRating, ratingToColor } from '../../utils/slippage.js';
import { EventNames } from '../../constants/events.js';
import { useDebounceValue } from 'usehooks-ts';
export const SlippageToleranceConfig = ({ setSlippageTolerance: externalSetValue, onAnalyticEvent }) => {
const [displayValue, setDisplayValue] = useState(undefined);
const [debouncedDisplayValue] = useDebounceValue(displayValue, 500);
const bpsValue = debouncedDisplayValue
? Number((Number(debouncedDisplayValue) * 100).toFixed(2)).toString()
: undefined;
useEffect(() => {
externalSetValue(bpsValue);
}, [bpsValue, externalSetValue]);
const [mode, setMode] = useState('Auto');
const [open, setOpen] = useState(false);
const inputRef = useRef(null);
useEffect(() => {
if (open && mode === 'Custom') {
setTimeout(() => {
inputRef.current?.focus();
}, 0);
}
}, [open, mode]);
const slippageRating = displayValue
? getSlippageRating(displayValue)
: undefined;
const slippageRatingColor = slippageRating
? ratingToColor[slippageRating]
: undefined;
const handleInputChange = (value) => {
// Remove non-numeric characters except decimal point
const sanitizedValue = value.replace(/[^0-9.]/g, '');
// Handle empty input
if (sanitizedValue === '') {
setDisplayValue(undefined);
return;
}
// Handle single decimal point input
if (sanitizedValue === '.') {
setDisplayValue('0.');
return;
}
// Validate format (numbers with up to 2 decimal places)
if (!/^[0-9]*\.?[0-9]{0,2}$/.test(sanitizedValue)) {
return;
}
// Prevent multiple leading zeros unless followed by a decimal
if (sanitizedValue.startsWith('0') &&
sanitizedValue.length > 1 &&
sanitizedValue[1] !== '.') {
return;
}
const numValue = parseFloat(sanitizedValue);
if (!isNaN(numValue)) {
if (numValue > 100) {
setDisplayValue('100');
return;
}
}
setDisplayValue(sanitizedValue);
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
}
};
const handleClose = () => {
const value = parseFloat(displayValue ?? '0');
const isAuto = mode === 'Auto' ||
displayValue === undefined ||
isNaN(value) ||
value < 0.01;
if (isAuto) {
setDisplayValue(undefined);
}
onAnalyticEvent?.(EventNames.SWAP_SLIPPAGE_TOLERANCE_SET, {
value: isAuto ? 'auto' : displayValue
});
};
return (_jsx("div", { className: "relay-kit-reset", children: _jsx(Dropdown, { open: open, onOpenChange: (isOpen) => {
setOpen(isOpen);
if (!isOpen) {
handleClose();
}
}, trigger: _jsxs(Button, { "aria-label": "Slippage Tolerance Configuration", color: "ghost", size: "none", css: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '1',
bg: 'subtle-background-color',
color: slippageRatingColor ?? 'gray9',
p: '2',
borderRadius: 12,
border: 'widget-card-border',
height: '36px',
px: '10px'
}, children: [open === false && displayValue && (_jsxs(Text, { style: "subtitle2", css: { color: slippageRatingColor }, children: [displayValue, "%"] })), _jsx(FontAwesomeIcon, { icon: faGear })] }), contentProps: {
align: 'end',
sideOffset: 5,
css: { maxWidth: 188, mx: 0 },
avoidCollisions: false,
onCloseAutoFocus: (e) => {
e.preventDefault();
}
}, children: _jsxs(Flex, { direction: "column", css: { width: '100%', gap: '2', maxWidth: 188 }, children: [_jsxs(Flex, { direction: "row", css: { gap: '1', alignItems: 'center' }, children: [_jsx(Text, { style: "subtitle3", children: "Max Slippage" }), _jsx(Tooltip, { content: _jsx(Text, { style: "tiny", css: { display: 'inline-block', maxWidth: 190 }, children: "If the price exceeds the maximum slippage percentage, the transaction will revert." }), children: _jsx(Box, { css: { color: 'gray8' }, children: _jsx(FontAwesomeIcon, { icon: faInfoCircle, width: 14, height: 14 }) }) })] }), _jsxs(TabsRoot, { value: mode, onValueChange: (value) => {
setMode(value);
if (value === 'Auto') {
setDisplayValue(undefined);
}
}, css: {
display: 'flex',
flexDirection: 'column',
width: '100%',
gap: '2'
}, children: [_jsxs(TabsList, { css: { width: '100%' }, children: [_jsx(TabsTrigger, { value: "Auto", css: { width: '50%' }, children: "Auto" }), _jsx(TabsTrigger, { value: "Custom", css: { width: '50%' }, children: "Custom" })] }), _jsx(TabsContent, { value: "Auto", css: { width: '100%' }, children: _jsx(Text, { style: "body3", color: "subtle", css: { lineHeight: '14px' }, children: "We'll set the slippage automatically to minimize the failure rate." }) }), _jsxs(TabsContent, { value: "Custom", css: {
display: 'flex',
width: '100%',
overflow: 'hidden',
flexDirection: 'column',
gap: '1'
}, children: [_jsxs(Flex, { css: { display: 'flex', width: '100%', position: 'relative' }, children: [_jsx(Input, { ref: inputRef, value: displayValue || '', onChange: (e) => handleInputChange(e.target.value), onKeyDown: handleKeyDown, onBlur: handleClose, placeholder: "2", css: {
height: '36px',
pr: '28px !important',
border: 'none',
textAlign: 'right',
width: '100%',
color: slippageRatingColor
} }), _jsx(Box, { css: {
position: 'absolute',
right: 8,
top: '50%',
transform: 'translateY(-50%)',
color: slippageRatingColor
}, children: "%" })] }), slippageRating === 'very-high' ? (_jsx(Text, { style: "body3", css: { color: 'red11' }, children: "Very high slippage" })) : null, slippageRating === 'high' ? (_jsx(Text, { style: "body3", css: { color: 'amber11' }, children: "High slippage" })) : null] })] })] }) }) }));
};
//# sourceMappingURL=SlippageToleranceConfig.js.map