UNPKG

@reservoir0x/relay-kit-ui

Version:

Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.

143 lines 8.08 kB
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