Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: move tooltips #5177

Draft
wants to merge 14 commits into
base: limit-ui-improvement-3
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const Wrapper = styled.div<{ hasSelectedItems: boolean }>`
align-items: center;
justify-content: space-between;
gap: 6px;
margin: 0 10px 0 ${({ hasSelectedItems }) => (hasSelectedItems ? '' : 'auto')};
margin: 0 0 0 ${({ hasSelectedItems }) => (hasSelectedItems ? '' : 'auto')};

${Media.upToSmall()} {
width: 100%;
Expand All @@ -38,7 +38,7 @@ const ActionButton = styled.button`
font-weight: 600;
text-decoration: none;
font-size: 13px;
padding: 10px 15px;
padding: 7px 12px;
margin: 0;
gap: 5px;
border: 0;
Expand All @@ -65,7 +65,7 @@ const TextButton = styled.button`
color: var(${UI.COLOR_TEXT_OPACITY_70});
font-size: 12px;
font-weight: 500;
padding: 5px 10px;
padding: 5px;
cursor: pointer;
background: none;
outline: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ import { parseOrdersTableUrl } from '../../utils/parseOrdersTableUrl'
import { MultipleCancellationMenu } from '../MultipleCancellationMenu'
import { OrdersReceiptModal } from '../OrdersReceiptModal'
import { useGetAlternativeOrderModalContextCallback, useSelectReceiptOrder } from '../OrdersReceiptModal/hooks'
import { UI } from '@cowprotocol/ui'

const SearchInputContainer = styled.div`
margin: 16px 0;
padding: 0 16px;
margin: 0;
padding: 0 0 0 16px;
position: relative;
`

Expand All @@ -50,25 +51,26 @@ const SearchIcon = styled(Search)`
left: 28px;
top: 50%;
transform: translateY(-50%);
color: ${({ theme }) => theme.textSecondary};
color: var(${UI.COLOR_TEXT_OPACITY_50});
width: 16px;
height: 16px;
`

const SearchInput = styled.input`
width: 100%;
padding: 8px 12px 8px 36px;
border: 1px solid ${({ theme }) => theme.grey};
border: 1px solid var(${UI.COLOR_PAPER_DARKER});
border-radius: 8px;
font-size: 14px;
font-size: 13px;
font-weight: 500;

&::placeholder {
color: ${({ theme }) => theme.textSecondary};
color: var(${UI.COLOR_TEXT_OPACITY_50});
}

&:focus {
outline: none;
border-color: ${({ theme }) => theme.blue};
border-color: var(${UI.COLOR_TEXT});
}
`

Expand Down Expand Up @@ -209,37 +211,59 @@ export function OrdersTableWidget({

const searchTermLower = searchTerm.toLowerCase().trim()

// First try exact symbol matches (case-insensitive)
const exactMatches = orders.filter((order) => {
const parsedOrder = getParsedOrderFromTableItem(order)
const inputToken = parsedOrder.inputToken
const outputToken = parsedOrder.outputToken

return [inputToken.symbol, outputToken.symbol].some((symbol) => {
return symbol?.toLowerCase() === searchTermLower
})
})

// If we have exact matches, return those
if (exactMatches.length > 0) {
return exactMatches
}

// Otherwise, fall back to partial matches and address search
return orders.filter((order) => {
const parsedOrder = getParsedOrderFromTableItem(order)
const inputToken = parsedOrder.inputToken
const outputToken = parsedOrder.outputToken

// Check symbols (case-insensitive)
if (
inputToken.symbol?.toLowerCase().includes(searchTermLower) ||
outputToken.symbol?.toLowerCase().includes(searchTermLower)
) {
return true
}
// Check for partial symbol matches (case-insensitive)
const symbolMatch = [inputToken.symbol, outputToken.symbol].some((symbol) => {
return symbol?.toLowerCase().includes(searchTermLower)
})

if (symbolMatch) return true

// Clean up the search term but preserve '0x' prefix
// If not a symbol match, check for address matches
// Clean up the search term but preserve '0x' prefix if present
const hasPrefix = searchTermLower.startsWith('0x')
const cleanedSearch = searchTermLower.replace(/[^0-9a-fx]/g, '') // Allow 'x' for '0x'
const cleanedSearch = searchTermLower.replace(/[^0-9a-fx]/g, '')

// For exact address matches (40 or 42 chars), do strict comparison
if (cleanedSearch.length === 40 || cleanedSearch.length === 42) {
const searchTermNormalized = cleanedSearch.startsWith('0x') ? cleanedSearch : `0x${cleanedSearch}`
const searchTermNormalized = hasPrefix ? cleanedSearch : `0x${cleanedSearch}`
return [inputToken.address, outputToken.address].some(
(address) => address.toLowerCase() === searchTermNormalized.toLowerCase(),
)
}

// For partial address matches, allow includes
// For partial address matches
const searchWithoutPrefix = hasPrefix ? cleanedSearch.slice(2) : cleanedSearch
return [inputToken.address, outputToken.address].some((address) => {
const addressWithoutPrefix = address.slice(2).toLowerCase()
return addressWithoutPrefix.includes(searchWithoutPrefix.toLowerCase())
})
if (searchWithoutPrefix.length >= 2) {
// Only search if we have at least 2 characters
return [inputToken.address, outputToken.address].some((address) => {
const addressWithoutPrefix = address.slice(2).toLowerCase()
return addressWithoutPrefix.includes(searchWithoutPrefix.toLowerCase())
})
}

return false
})
}, [orders, searchTerm])

Expand All @@ -265,16 +289,16 @@ export function OrdersTableWidget({
pendingActivities={pendingActivity}
ordersPermitStatus={ordersPermitStatus}
injectedWidgetParams={injectedWidgetParams}
searchTerm={searchTerm}
>
{currentTabId === OPEN_TAB.id && orders.length > 0 && (
<MultipleCancellationMenu pendingOrders={tableItemsToOrders(orders)} />
)}
{(currentTabId === OPEN_TAB.id || currentTabId === 'all' || currentTabId === 'unfillable') &&
orders.length > 0 && <MultipleCancellationMenu pendingOrders={tableItemsToOrders(orders)} />}

<SearchInputContainer>
<SearchIcon />
<SearchInput
type="text"
placeholder="Token symbol, address"
placeholder="token symbol, address"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,49 @@ const Wrapper = styled.div<{
left: 0;
top: 0;
background: var(--statusBackground);
/* opacity: 0.14; */
z-index: 1;
border-radius: ${({ withWarning }) => (withWarning ? '9px 0 0 9px' : '9px')};
}
`

const StatusContent = styled.div`
display: flex;
align-items: center;
gap: 4px;
position: relative;
z-index: 2;
`

type OrderStatusBoxProps = {
order: ParsedOrder
widthAuto?: boolean
withWarning?: boolean
onClick?: Command
WarningTooltip?: React.ComponentType<{ children: React.ReactNode }>
}

export function OrderStatusBox({ order, widthAuto, withWarning, onClick }: OrderStatusBoxProps) {
export function OrderStatusBox({ order, widthAuto, withWarning, onClick, WarningTooltip }: OrderStatusBoxProps) {
const { title, color, background } = getOrderStatusTitleAndColor(order)

const content = <StatusContent>{title}</StatusContent>

return (
<Wrapper
color={color}
background={background}
widthAuto={widthAuto}
withWarning={withWarning}
clickable={!!onClick}
onClick={onClick}
>
{/* Status overrides for special cases */}
{title}
</Wrapper>
<>
<Wrapper
color={color}
background={background}
widthAuto={widthAuto}
withWarning={withWarning}
clickable={!!onClick}
onClick={onClick}
>
{content}
</Wrapper>
{withWarning && WarningTooltip && (
<WarningTooltip>
<></>
</WarningTooltip>
)}
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AlertTriangle from '@cowprotocol/assets/cow-swap/alert.svg'
import { ZERO_FRACTION } from '@cowprotocol/common-const'
import { Command } from '@cowprotocol/types'
import { UI } from '@cowprotocol/ui'
import { SymbolElement, TokenAmount, TokenAmountProps } from '@cowprotocol/ui'
import { HoverTooltip } from '@cowprotocol/ui'
Expand Down Expand Up @@ -27,19 +28,6 @@ export const EstimatedExecutionPriceWrapper = styled.span<{ hasWarning: boolean;
color: inherit;
}

// Triangle warning icon override
${styledEl.WarningIndicator} {
padding: 0 0 0 3px;

svg {
--size: 18px;
width: var(--size);
height: var(--size);
min-width: var(--size);
min-height: var(--size);
}
}

// Popover container override
> div > div,
> span {
Expand All @@ -49,21 +37,36 @@ export const EstimatedExecutionPriceWrapper = styled.span<{ hasWarning: boolean;
`

const UnfillableLabel = styled.span`
width: 100%;
max-width: 90px;
background: var(${UI.COLOR_DANGER_BG});
color: var(${UI.COLOR_DANGER_TEXT});
width: auto;
color: var(${UI.COLOR_DANGER});
position: relative;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
padding: 6px 2px;
margin: 0 4px 0 0;
letter-spacing: 0.2px;
text-transform: uppercase;
font-size: inherit;
font-weight: 500;
line-height: 1.1;
flex-flow: row wrap;
align-items: center;
justify-content: flex-start;
gap: 3px;
`

const ApprovalLink = styled.button`
background: none;
border: none;
padding: 0;
margin: 0;
cursor: pointer;
font-size: inherit;
color: inherit;
text-decoration: underline;
color: var(${UI.COLOR_PRIMARY});
font-weight: 500;

&:hover {
opacity: 1;
}
`

export type EstimatedExecutionPriceProps = TokenAmountProps & {
Expand All @@ -74,18 +77,25 @@ export type EstimatedExecutionPriceProps = TokenAmountProps & {
amountDifference?: CurrencyAmount<Currency>
percentageFee?: Percent
amountFee?: CurrencyAmount<Currency>
warningText?: string
WarningTooltip?: React.FC<{ children: React.ReactNode; showIcon: boolean }>
onApprove?: Command
}

export function EstimatedExecutionPrice(props: EstimatedExecutionPriceProps) {
const {
amount,
tokenSymbol,
isInverted,
isUnfillable,
canShowWarning,
percentageDifference,
amountDifference,
percentageFee,
amountFee,
amount,
warningText,
WarningTooltip,
onApprove,
...rest
} = props

Expand All @@ -104,22 +114,34 @@ export function EstimatedExecutionPrice(props: EstimatedExecutionPriceProps) {

const content = (
<>
<TokenAmount amount={amount} {...rest} />
<TokenAmount amount={amount} tokenSymbol={tokenSymbol} {...rest} />
</>
)

const unfillableLabel = (
<UnfillableLabel>
{warningText}
{warningText === 'Insufficient allowance' && onApprove && (
<ApprovalLink onClick={onApprove}>Set approval</ApprovalLink>
)}
{WarningTooltip && <WarningTooltip showIcon>{null}</WarningTooltip>}
</UnfillableLabel>
)

return (
<EstimatedExecutionPriceWrapper hasWarning={!!feeWarning} showPointerCursor={!isUnfillable}>
{isUnfillable ? (
<UnfillableLabel>UNFILLABLE</UnfillableLabel>
<div>{unfillableLabel}</div>
) : !absoluteDifferenceAmount ? (
<span>{content}</span>
) : (
<HoverTooltip
wrapInContainer={true}
content={
<styledEl.ExecuteInformationTooltip>
{!isNegativeDifference ? (
{isNegativeDifference && Math.abs(Number(percentageDifferenceInverted?.toFixed(4) ?? 0)) <= 0.01 ? (
<>Will execute soon!</>
) : (
<>
Market price needs to go {marketPriceNeedsToGoDown ? 'down 📉' : 'up 📈'} by&nbsp;
<b>
Expand All @@ -131,8 +153,6 @@ export function EstimatedExecutionPrice(props: EstimatedExecutionPriceProps) {
</span>
&nbsp;to execute your order.
</>
) : (
<>Will execute soon!</>
)}
</styledEl.ExecuteInformationTooltip>
}
Expand Down
Loading
Loading