diff --git a/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx b/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx
index 9020f7c004..da4ffec05b 100644
--- a/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx
+++ b/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/CurrencyInputPanel.tsx
@@ -163,6 +163,15 @@ export function CurrencyInputPanel(props: CurrencyInputPanelProps) {
{topContent}
+
+ {inputTooltip ? (
+
+ {numericalInput}
+
+ ) : (
+ numericalInput
+ )}
+
-
- {inputTooltip ? (
-
- {numericalInput}
-
- ) : (
- numericalInput
- )}
-
+
+ {amount && (
+
+
+
+ )}
+
{balance && !disabled && (
- Balance:
+
{showSetMax && balance.greaterThan(0) && (
Max
)}
)}
-
- {amount && (
-
-
-
- )}
-
diff --git a/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/styled.tsx b/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/styled.tsx
index 81d5492220..cd619d4475 100644
--- a/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/styled.tsx
+++ b/apps/cowswap-frontend/src/common/pure/CurrencyInputPanel/styled.tsx
@@ -81,6 +81,7 @@ export const NumericalInput = styled(Input)<{ $loading: boolean }>`
font-size: 28px;
font-weight: 500;
color: inherit;
+ text-align: left;
&::placeholder {
opacity: 0.7;
@@ -150,7 +151,9 @@ export const SetMaxBtn = styled.button`
border-radius: 6px;
padding: 3px 4px;
text-transform: uppercase;
- transition: background var(${UI.ANIMATION_DURATION}) ease-in-out, color var(${UI.ANIMATION_DURATION}) ease-in-out;
+ transition:
+ background var(${UI.ANIMATION_DURATION}) ease-in-out,
+ color var(${UI.ANIMATION_DURATION}) ease-in-out;
&:hover {
background: var(${UI.COLOR_PRIMARY});
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx
index a2420948e3..5b6cf6ad4f 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/index.tsx
@@ -91,7 +91,7 @@ export function LimitOrdersWidget() {
const inputCurrencyInfo: CurrencyInfo = {
field: Field.INPUT,
- label: isSell ? 'Sell amount' : 'You sell at most',
+ label: isSell ? 'Sell' : 'You sell at most',
currency: inputCurrency,
amount: inputCurrencyAmount,
isIndependent: isSell,
@@ -176,7 +176,7 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {
handleUnlock={() => updateLimitOrdersState({ isUnlocked: true })}
/>
),
- middleContent: (
+ topContent: (
<>
{!isWrapOrUnwrap &&
ClosableBanner(ZERO_BANNER_STORAGE_KEY, (onClose) => (
@@ -194,17 +194,33 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {
))}
-
-
-
-
+ {props.settingsState.limitPricePosition === 'top' && (
+
+
+
+ )}
+ >
+ ),
+ middleContent: (
+ <>
+ {props.settingsState.limitPricePosition === 'between' && (
+
+
+
+ )}
>
),
bottomContent(warnings) {
return (
<>
+ {props.settingsState.limitPricePosition === 'bottom' && (
+
+
+
+ )}
-
+
+
@@ -220,7 +236,7 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {
}
const params = {
- compactView: false,
+ compactView: true,
recipient,
showRecipient,
isTradePriceUpdating: isRateLoading,
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/styled.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/styled.tsx
index c23762aa09..1c0c34d7df 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/styled.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWidget/styled.tsx
@@ -1,9 +1,7 @@
-import { Media } from '@cowprotocol/ui'
+import { Media, UI } from '@cowprotocol/ui'
import styled from 'styled-components/macro'
-import { NumericalInput } from 'modules/limitOrders/containers/RateInput/styled'
-
export const TradeButtonBox = styled.div`
margin: 10px 0 0;
display: flex;
@@ -20,20 +18,18 @@ export const FooterBox = styled.div`
`
export const RateWrapper = styled.div`
- display: grid;
- grid-template-columns: auto 151px;
- grid-template-rows: max-content;
+ display: flex;
+ flex-flow: column wrap;
+ width: 100%;
max-width: 100%;
gap: 6px;
text-align: right;
color: inherit;
+ background: var(${UI.COLOR_PAPER_DARKER});
+ border-radius: 16px;
${Media.upToSmall()} {
display: flex;
flex-flow: column wrap;
}
-
- ${NumericalInput} {
- font-size: 21px;
- }
`
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/index.tsx
index e1d21cab06..58f3bb461f 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/index.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/index.tsx
@@ -1,23 +1,28 @@
import { useAtomValue, useSetAtom } from 'jotai'
import { useCallback, useEffect, useMemo, useState } from 'react'
+import LockedIcon from '@cowprotocol/assets/images/icon-locked.svg'
+import UnlockedIcon from '@cowprotocol/assets/images/icon-unlocked.svg'
+import UsdIcon from '@cowprotocol/assets/images/icon-USD.svg'
import { formatInputAmount, getAddress, isFractionFalsy } from '@cowprotocol/common-utils'
-import { HelpTooltip, Loader, TokenSymbol } from '@cowprotocol/ui'
+import { TokenLogo } from '@cowprotocol/tokens'
+import { TokenSymbol, HoverTooltip, HelpTooltip } from '@cowprotocol/ui'
import { useWalletInfo } from '@cowprotocol/wallet'
-import { RefreshCw } from 'react-feather'
-
+import SVG from 'react-inlinesvg'
import { useLimitOrdersDerivedState } from 'modules/limitOrders/hooks/useLimitOrdersDerivedState'
import { useRateImpact } from 'modules/limitOrders/hooks/useRateImpact'
import { useUpdateActiveRate } from 'modules/limitOrders/hooks/useUpdateActiveRate'
-import { ExecutionPriceTooltip } from 'modules/limitOrders/pure/ExecutionPriceTooltip'
import { HeadingText } from 'modules/limitOrders/pure/RateInput/HeadingText'
import { executionPriceAtom } from 'modules/limitOrders/state/executionPriceAtom'
+import {
+ limitOrdersSettingsAtom,
+ updateLimitOrdersSettingsAtom,
+} from 'modules/limitOrders/state/limitOrdersSettingsAtom'
import { limitRateAtom, updateLimitRateAtom } from 'modules/limitOrders/state/limitRateAtom'
import { toFraction } from 'modules/limitOrders/utils/toFraction'
-import { ordersTableFeatures } from 'common/constants/featureFlags'
import { ExecutionPrice } from 'common/pure/ExecutionPrice'
import { getQuoteCurrency, getQuoteCurrencyByStableCoin } from 'common/services/getQuoteCurrency'
@@ -26,21 +31,15 @@ import * as styledEl from './styled'
export function RateInput() {
const { chainId } = useWalletInfo()
// Rate state
- const {
- isInverted,
- activeRate,
- isLoading,
- marketRate,
- feeAmount,
- isLoadingMarketRate,
- typedValue,
- isTypedValue,
- initialRate,
- } = useAtomValue(limitRateAtom)
+ const { isInverted, activeRate, isLoading, marketRate, isLoadingMarketRate, typedValue, isTypedValue, initialRate } =
+ useAtomValue(limitRateAtom)
const updateRate = useUpdateActiveRate()
const updateLimitRateState = useSetAtom(updateLimitRateAtom)
const executionPrice = useAtomValue(executionPriceAtom)
const [isQuoteCurrencySet, setIsQuoteCurrencySet] = useState(false)
+ const [isUsdMode, setIsUsdMode] = useState(false)
+ const { limitPriceLocked } = useAtomValue(limitOrdersSettingsAtom)
+ const updateLimitOrdersSettings = useSetAtom(updateLimitOrdersSettingsAtom)
// Limit order state
const { inputCurrency, outputCurrency, inputCurrencyAmount, outputCurrencyAmount } = useLimitOrdersDerivedState()
@@ -84,13 +83,33 @@ export function RateInput() {
isAlternativeOrderRate: false,
})
},
- [isInverted, updateRate, updateLimitRateState]
+ [isInverted, updateRate, updateLimitRateState],
)
+ // Handle toggle USD mode
+ const handleToggleUsdMode = useCallback(() => {
+ setIsUsdMode(!isUsdMode)
+ }, [isUsdMode])
+
// Handle toggle primary field
const handleToggle = useCallback(() => {
- updateLimitRateState({ isInverted: !isInverted, isTypedValue: false })
- }, [isInverted, updateLimitRateState])
+ if (isUsdMode) {
+ // When in USD mode, just switch to token mode without toggling tokens
+ setIsUsdMode(false)
+ } else {
+ // When already in token mode, toggle between tokens
+ updateLimitRateState({ isInverted: !isInverted, isTypedValue: false })
+ }
+ }, [isInverted, updateLimitRateState, isUsdMode])
+
+ // Handle toggle price lock
+ const handleTogglePriceLock = useCallback(
+ (event: React.MouseEvent) => {
+ event.stopPropagation()
+ updateLimitOrdersSettings({ limitPriceLocked: !limitPriceLocked })
+ },
+ [limitPriceLocked, updateLimitOrdersSettings],
+ )
const isDisabledMPrice = useMemo(() => {
if (isLoadingMarketRate) return true
@@ -144,13 +163,39 @@ export function RateInput() {
<>
-
-
-
- Set to market
-
+
+
+
+
+
+ }
+ onToggle={handleToggle}
+ />
+
+ {areBothCurrencies && (
+
+ Market:{' '}
+
+ {isLoadingMarketRate ? (
+
+ ) : marketRate && !marketRate.equalTo(0) ? (
+ formatInputAmount(isInverted ? marketRate.invert() : marketRate)
+ ) : (
+ ''
+ )}
+
+
+ )}
-
{isLoading && areBothCurrencies ? (
@@ -163,42 +208,36 @@ export function RateInput() {
/>
)}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
- {ordersTableFeatures.DISPLAY_EST_EXECUTION_PRICE && (
-
-
- Order executes at{' '}
- {isLoadingMarketRate ? (
-
- ) : executionPrice ? (
-
- }
- />
- ) : null}
-
- {!isLoadingMarketRate && executionPrice && (
-
+
+
+ {isLoadingMarketRate ? (
+
+ ) : executionPrice ? (
+
+ ) : (
+ '-'
)}
-
- )}
+
+
+ Estimated fill price
+
+
+
>
)
}
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/styled.ts b/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/styled.ts
index 08d19b62ea..c56abdaaa8 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/styled.ts
+++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/RateInput/styled.ts
@@ -1,4 +1,4 @@
-import { Loader, Media } from '@cowprotocol/ui'
+import { Loader, Media, TokenSymbol } from '@cowprotocol/ui'
import { UI } from '@cowprotocol/ui'
import styled from 'styled-components/macro'
@@ -6,12 +6,9 @@ import styled from 'styled-components/macro'
import Input from 'legacy/components/NumericalInput'
export const Wrapper = styled.div`
- background: var(${UI.COLOR_PAPER_DARKER});
- border-radius: 16px;
- padding: 10px 16px;
- flex: 1 1 70%;
- min-height: 80px;
- justify-content: space-between;
+ padding: 16px 16px 0;
+ width: 100%;
+ max-width: 100%;
display: flex;
flex-flow: row wrap;
color: inherit;
@@ -29,28 +26,53 @@ export const Header = styled.div`
font-weight: 500;
width: 100%;
color: inherit;
+
+ > span > i {
+ font-style: normal;
+ color: var(${UI.COLOR_TEXT});
+ }
+`
+
+export const MarketRateWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 12px;
+ font-weight: 400;
+
+ > i {
+ font-style: normal;
+ color: var(${UI.COLOR_TEXT_OPACITY_70});
+ }
`
export const MarketPriceButton = styled.button`
- background: var(${UI.COLOR_PAPER});
color: inherit;
white-space: nowrap;
border: none;
font-weight: 500;
cursor: pointer;
- border-radius: 9px;
- padding: 5px 8px;
- font-size: 11px;
- transition: background var(${UI.ANIMATION_DURATION}) ease-in-out, color var(${UI.ANIMATION_DURATION}) ease-in-out;
+ font-size: inherit;
+ background: transparent;
+ padding: 0;
+ color: var(${UI.COLOR_TEXT});
+ transition:
+ background var(${UI.ANIMATION_DURATION}) ease-in-out,
+ color var(${UI.ANIMATION_DURATION}) ease-in-out;
+ text-decoration: underline;
+ text-decoration-style: dashed;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 2px;
+ text-decoration-color: var(${UI.COLOR_TEXT_OPACITY_70});
- &:disabled {
- cursor: default;
- opacity: 0.6;
+ > svg {
+ margin: 0 0 -2px 7px;
}
- &:not(:disabled):hover {
- background: var(${UI.COLOR_PRIMARY});
- color: var(${UI.COLOR_BUTTON_TEXT});
+ &:disabled {
+ cursor: default;
+ opacity: 0.7;
+ text-decoration: none;
}
`
@@ -59,7 +81,9 @@ export const Body = styled.div`
align-items: center;
justify-content: space-between;
width: 100%;
+ max-width: 100%;
gap: 8px;
+ padding: 12px 0 4px;
color: inherit;
`
@@ -68,9 +92,11 @@ export const NumericalInput = styled(Input)<{ $loading: boolean }>`
align-items: center;
background: none;
border: none;
- width: 100%;
text-align: left;
color: inherit;
+ font-size: 32px;
+ letter-spacing: -1.5px;
+ flex: 1 1 auto;
&::placeholder {
opacity: 0.7;
@@ -78,32 +104,126 @@ export const NumericalInput = styled(Input)<{ $loading: boolean }>`
}
`
-export const ActiveCurrency = styled.button`
+export const CurrencyToggleGroup = styled.div`
+ display: flex;
+ align-items: center;
+ background: transparent;
+ overflow: hidden;
+`
+
+export const ActiveCurrency = styled.button<{ $active?: boolean }>`
+ --height: 25px;
+ --skew-width: 6px;
+ --skew-offset: -3px;
+ --skew-angle: -10deg;
+ --padding: 10px;
+ --gap: 6px;
+ --font-size: 13px;
+ --border-radius: 8px;
+
display: flex;
align-items: center;
- justify-content: flex-end;
+ gap: var(--gap);
+ font-size: var(--font-size);
+ font-weight: var(${UI.FONT_WEIGHT_MEDIUM});
border: none;
- background: none;
- padding: 0;
- margin: 0 0 0 auto;
- gap: 8px;
- max-width: 130px;
- width: auto;
- color: inherit;
cursor: pointer;
+ position: relative;
+ height: var(--height);
+ border-radius: var(--border-radius);
+ transition: all 0.2s ease-in-out;
+ background: ${({ $active }) => ($active ? 'var(' + UI.COLOR_PAPER + ')' : 'var(' + UI.COLOR_PAPER_DARKEST + ')')};
+ color: ${({ $active }) => ($active ? 'var(' + UI.COLOR_TEXT + ')' : 'var(' + UI.COLOR_TEXT_OPACITY_50 + ')')};
+ padding: 0 10px;
+
+ &:first-child {
+ padding-right: var(--padding);
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+
+ ${({ $active }) =>
+ $active &&
+ `
+ &::after {
+ content: '';
+ position: absolute;
+ right: var(--skew-offset);
+ top: 0;
+ bottom: 0;
+ width: var(--skew-width);
+ background: var(${UI.COLOR_PAPER_DARKER});
+ transform: skew(var(--skew-angle));
+ z-index: 5;
+ }
+ `}
+ }
+
+ &:last-child {
+ padding-left: var(--padding);
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+
+ ${({ $active }) =>
+ $active &&
+ `
+ &::before {
+ content: '';
+ position: absolute;
+ left: var(--skew-offset);
+ top: 0;
+ bottom: 0;
+ width: var(--skew-width);
+ background: var(${UI.COLOR_PAPER_DARKER});
+ transform: skew(var(--skew-angle));
+ z-index: 5;
+ }
+ `}
+ }
+
+ &:hover {
+ color: var(${UI.COLOR_TEXT});
+ }
`
-export const ActiveSymbol = styled.span`
+export const UsdButton = styled(ActiveCurrency)`
+ font-weight: var(${UI.FONT_WEIGHT_BOLD});
+ min-width: 40px;
+ justify-content: center;
+
+ > svg {
+ width: 10px;
+ height: 16px;
+ color: inherit;
+ }
+`
+
+export const ActiveSymbol = styled.span<{ $active?: boolean }>`
color: inherit;
font-size: 13px;
font-weight: 500;
text-align: right;
padding: 10px 0;
+ display: flex;
+ gap: 4px;
+
+ ${({ $active }) =>
+ !$active &&
+ `
+ > div {
+ background: transparent;
+ }
+ > div > img {
+ opacity: 0.5;
+ }
+
+ > ${TokenSymbol} {
+ color: var(${UI.COLOR_TEXT_OPACITY_50});
+ }
+ `}
`
export const ActiveIcon = styled.div`
- --size: 20px;
- background-color: var(${UI.COLOR_PAPER});
+ --size: 19px;
color: inherit;
width: var(--size);
min-width: var(--size);
@@ -113,6 +233,19 @@ export const ActiveIcon = styled.div`
display: flex;
align-items: center;
justify-content: center;
+ cursor: pointer;
+ margin: 0 2px 0 0;
+ transition:
+ color var(${UI.ANIMATION_DURATION}) ease-in-out,
+ background var(${UI.ANIMATION_DURATION}) ease-in-out;
+ background: transparent;
+ border: 1px solid var(${UI.COLOR_PAPER_DARKEST});
+
+ &:hover {
+ color: var(${UI.COLOR_TEXT});
+ background: var(${UI.COLOR_PAPER});
+ border-color: var(${UI.COLOR_PAPER});
+ }
`
export const RateLoader = styled(Loader)`
@@ -123,33 +256,29 @@ export const EstimatedRate = styled.div`
display: flex;
width: 100%;
justify-content: space-between;
- min-height: 42px;
+ min-height: 36px;
margin: 0;
padding: 12px 10px 14px;
- font-size: 13px;
+ font-size: 16px;
border-radius: 0 0 16px 16px;
- font-weight: 400;
+ font-weight: 500;
+ color: var(${UI.COLOR_TEXT});
background: var(${UI.COLOR_PAPER});
border: 2px solid var(${UI.COLOR_PAPER_DARKER});
- background: red;
> b {
display: flex;
flex-flow: row nowrap;
- font-weight: normal;
+ font-weight: inherit;
text-align: left;
- opacity: 0.7;
transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out;
-
- &:hover {
- opacity: 1;
- }
+ color: inherit;
}
- // TODO: Make the question helper icon transparent through a prop instead
> b svg {
opacity: 0.7;
transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out;
+ color: inherit;
&:hover {
opacity: 1;
@@ -161,4 +290,36 @@ export const EstimatedRate = styled.div`
color: inherit;
opacity: 0.7;
}
+
+ > span {
+ display: flex;
+ align-items: center;
+ font-size: 13px;
+ font-weight: 400;
+ color: var(${UI.COLOR_TEXT_OPACITY_70});
+ }
+`
+
+export const LockIcon = styled.span`
+ --size: 19px;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ color: inherit;
+ border-radius: var(--size);
+ width: var(--size);
+ min-width: var(--size);
+ height: var(--size);
+ border: 1px solid var(${UI.COLOR_PAPER_DARKEST});
+ cursor: pointer;
+ transition:
+ border-color var(${UI.ANIMATION_DURATION}) ease-in-out,
+ background var(${UI.ANIMATION_DURATION}) ease-in-out;
+
+ &:hover {
+ border-color: var(${UI.COLOR_PAPER});
+ background: var(${UI.COLOR_PAPER});
+ }
`
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/SettingsWidget/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/SettingsWidget/index.tsx
index 00267f9fb2..5750c21f07 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/containers/SettingsWidget/index.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/SettingsWidget/index.tsx
@@ -1,9 +1,11 @@
import { useAtomValue, useSetAtom } from 'jotai'
-import React from 'react'
+
+import UsdIcon from '@cowprotocol/assets/images/icon-USD.svg'
import { Menu, MenuItem } from '@reach/menu-button'
+import SVG from 'react-inlinesvg'
-import { MenuContent, SettingsButton, SettingsIcon } from 'modules/trade/pure/Settings'
+import { ButtonsContainer, MenuContent, SettingsButton, SettingsIcon, UsdButton } from 'modules/trade/pure/Settings'
import { Settings } from '../../pure/Settings'
import { limitOrdersSettingsAtom, updateLimitOrdersSettingsAtom } from '../../state/limitOrdersSettingsAtom'
@@ -13,10 +15,15 @@ export function SettingsWidget() {
const updateSettingsState = useSetAtom(updateLimitOrdersSettingsAtom)
return (
- <>
+
+ {/* TODO: add active state */}
+
+
+
- >
+
)
}
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/TradeRateDetails/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/TradeRateDetails/index.tsx
index 1508275f31..db516ce54d 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/containers/TradeRateDetails/index.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/TradeRateDetails/index.tsx
@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react'
import { useInjectedWidgetParams } from 'modules/injectedWidget'
import { TradeTotalCostsDetails, PartnerFeeRow } from 'modules/trade'
+import { StyledRateInfo } from 'modules/trade/containers/TradeTotalCostsDetails/styled'
+import { Box } from 'modules/trade/containers/TradeTotalCostsDetails/styled'
import { useUsdAmount } from 'modules/usdAmount'
import { useVolumeFee, useVolumeFeeTooltip } from 'modules/volumeFee'
@@ -11,10 +13,11 @@ import { useLimitOrderPartnerFeeAmount } from '../../hooks/useLimitOrderPartnerF
interface TradeRateDetailsProps {
rateInfoParams?: RateInfoParams
+ alwaysExpanded?: boolean
}
-export function TradeRateDetails({ rateInfoParams }: TradeRateDetailsProps) {
- const [isFeeDetailsOpen, setFeeDetailsOpen] = useState(false)
+export function TradeRateDetails({ rateInfoParams, alwaysExpanded = false }: TradeRateDetailsProps) {
+ const [isFeeDetailsOpen, setFeeDetailsOpen] = useState(alwaysExpanded)
const widgetParams = useInjectedWidgetParams()
const volumeFee = useVolumeFee()
const partnerFeeAmount = useLimitOrderPartnerFeeAmount()
@@ -23,8 +26,9 @@ export function TradeRateDetails({ rateInfoParams }: TradeRateDetailsProps) {
const partnerFeeBps = volumeFee?.bps
const toggleAccordion = useCallback(() => {
+ if (alwaysExpanded) return
setFeeDetailsOpen((prev) => !prev)
- }, [])
+ }, [alwaysExpanded])
const partnerFeeRow = (
+
+ {partnerFeeRow}
+ >
+ )
+ }
+
return (
{
const { activeRate, amount, orderKind } = params
const field = isSellOrder(orderKind) ? Field.INPUT : Field.OUTPUT
+ const isBuyAmountChange = field === Field.OUTPUT
+ if (isBuyAmountChange) {
+ // When changing BUY amount
+ if (limitPriceLocked) {
+ // If price is locked, only update the output amount
+ const update: Partial> = {
+ orderKind,
+ outputCurrencyAmount: FractionUtils.serializeFractionToJSON(amount),
+ }
+ updateLimitOrdersState(update)
+ } else {
+ // If price is unlocked, update the rate based on the new amounts
+ const update: Partial> = {
+ orderKind,
+ outputCurrencyAmount: FractionUtils.serializeFractionToJSON(amount),
+ }
+ updateLimitOrdersState(update)
+
+ // Calculate and update the new rate
+ if (amount && currentInputAmount) {
+ const newRate = new Price(
+ currentInputAmount.currency,
+ amount.currency,
+ currentInputAmount.quotient,
+ amount.quotient,
+ )
+ updateLimitRateState({
+ activeRate: FractionUtils.fractionLikeToFraction(newRate),
+ isTypedValue: false,
+ isRateFromUrl: false,
+ isAlternativeOrderRate: false,
+ })
+ }
+ }
+ return
+ }
+
+ // Normal flow for SELL amount changes
const calculatedAmount = calculateAmountForRate({
activeRate,
amount,
@@ -37,21 +80,21 @@ export function useUpdateCurrencyAmount() {
outputCurrency,
})
- const inputCurrencyAmount = FractionUtils.serializeFractionToJSON(
- field === Field.INPUT ? amount : calculatedAmount
- )
- const outputCurrencyAmount = FractionUtils.serializeFractionToJSON(
- field === Field.OUTPUT ? amount : calculatedAmount
- )
+ const newInputAmount = (field as Field) === Field.INPUT ? amount : calculatedAmount
+ const newOutputAmount = (field as Field) === Field.OUTPUT ? amount : calculatedAmount
const update: Partial> = {
orderKind,
- ...(inputCurrencyAmount ? { inputCurrencyAmount } : undefined),
- ...(outputCurrencyAmount ? { outputCurrencyAmount } : undefined),
+ ...(newInputAmount
+ ? { inputCurrencyAmount: FractionUtils.serializeFractionToJSON(newInputAmount) }
+ : undefined),
+ ...(newOutputAmount
+ ? { outputCurrencyAmount: FractionUtils.serializeFractionToJSON(newOutputAmount) }
+ : undefined),
}
updateLimitOrdersState(update)
},
- [inputCurrency, outputCurrency, updateLimitOrdersState]
+ [inputCurrency, outputCurrency, updateLimitOrdersState, limitPriceLocked, updateLimitRateState, currentInputAmount],
)
}
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/deadlines.ts b/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/deadlines.ts
index b0a04d3be0..7364eae9f3 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/deadlines.ts
+++ b/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/deadlines.ts
@@ -11,18 +11,35 @@ export interface LimitOrderDeadline {
export const MIN_CUSTOM_DEADLINE = ms`30min`
export const MAX_CUSTOM_DEADLINE = MAX_ORDER_DEADLINE
-export const defaultLimitOrderDeadline: LimitOrderDeadline = { title: '7 Days', value: ms`7d` }
-
-export const LIMIT_ORDERS_DEADLINES: LimitOrderDeadline[] = [
- { title: '5 Minutes', value: ms`5m` },
- { title: '30 Minutes', value: ms`30m` },
- { title: '1 Hour', value: ms`1 hour` },
- { title: '1 Day', value: ms`1d` },
- { title: '3 Days', value: ms`3d` },
- defaultLimitOrderDeadline,
- { title: '1 Month', value: ms`30d` },
- { title: '6 Months (max)', value: MAX_CUSTOM_DEADLINE },
-]
+export enum LimitOrderDeadlinePreset {
+ FIVE_MINUTES = '5 Minutes',
+ THIRTY_MINUTES = '30 Minutes',
+ ONE_HOUR = '1 Hour',
+ ONE_DAY = '1 Day',
+ THREE_DAYS = '3 Days',
+ ONE_MONTH = '1 Month',
+ SIX_MONTHS = '6 Months (max)',
+}
+
+const DEADLINE_VALUES: Record = {
+ [LimitOrderDeadlinePreset.FIVE_MINUTES]: ms`5m`,
+ [LimitOrderDeadlinePreset.THIRTY_MINUTES]: ms`30m`,
+ [LimitOrderDeadlinePreset.ONE_HOUR]: ms`1 hour`,
+ [LimitOrderDeadlinePreset.ONE_DAY]: ms`1d`,
+ [LimitOrderDeadlinePreset.THREE_DAYS]: ms`3d`,
+ [LimitOrderDeadlinePreset.ONE_MONTH]: ms`30d`,
+ [LimitOrderDeadlinePreset.SIX_MONTHS]: MAX_CUSTOM_DEADLINE,
+}
+
+export const defaultLimitOrderDeadline: LimitOrderDeadline = {
+ title: LimitOrderDeadlinePreset.SIX_MONTHS,
+ value: DEADLINE_VALUES[LimitOrderDeadlinePreset.SIX_MONTHS],
+}
+
+export const LIMIT_ORDERS_DEADLINES: LimitOrderDeadline[] = Object.entries(DEADLINE_VALUES).map(([title, value]) => ({
+ title,
+ value,
+}))
/**
* Get limit order deadlines and optionally adds
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/index.tsx
index 247d86c01f..32d1fdb777 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/index.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/index.tsx
@@ -126,7 +126,7 @@ export function DeadlineSelector(props: DeadlineSelectorProps) {
return (
- Expiry
+ Order expires in
{isDeadlineDisabled ? (
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/styled.tsx b/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/styled.tsx
index 8f07cfb6cc..73272e0ef7 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/styled.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/pure/DeadlineSelector/styled.tsx
@@ -5,52 +5,32 @@ import { transparentize } from 'color2k'
import { X } from 'react-feather'
import styled from 'styled-components/macro'
-export const Wrapper = styled.div<{ inline?: boolean; minHeight?: string }>`
- background: var(${UI.COLOR_PAPER_DARKER});
+export const Wrapper = styled.div`
color: inherit;
- border-radius: 16px;
- padding: 10px 16px;
- min-height: ${({ minHeight }) => minHeight || '80px'};
+ padding: 0;
justify-content: space-between;
display: flex;
flex-flow: row wrap;
-
- ${({ inline }) =>
- inline &&
- `
- justify-content: space-between;
-
- > span {
- flex: 1;
- }
-
- > button {
- width: auto;
- }
- `}
+ width: 100%;
+ font-size: 13px;
+ font-weight: inherit;
+ min-height: 24px;
`
export const Label = styled.span`
display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 13px;
- font-weight: 500;
- width: 100%;
+ font-size: inherit;
+ font-weight: inherit;
color: inherit;
- opacity: 0.7;
- transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out;
-
- &:hover {
- opacity: 1;
- }
+ align-self: center;
+ justify-self: center;
`
export const Current = styled(MenuButton)<{ $custom?: boolean }>`
color: inherit;
font-size: ${({ $custom }) => ($custom ? '12px' : '100%')};
letter-spacing: ${({ $custom }) => ($custom ? '-0.3px' : '0')};
- font-weight: 500;
+ font-weight: inherit;
display: flex;
align-items: center;
justify-content: space-between;
@@ -61,7 +41,6 @@ export const Current = styled(MenuButton)<{ $custom?: boolean }>`
padding: 0;
white-space: nowrap;
cursor: pointer;
- width: 100%;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/pure/EstimatedFillPrice/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/pure/EstimatedFillPrice/index.tsx
new file mode 100644
index 0000000000..5c9b4e47c9
--- /dev/null
+++ b/apps/cowswap-frontend/src/modules/limitOrders/pure/EstimatedFillPrice/index.tsx
@@ -0,0 +1,82 @@
+import { TokenAmount, HoverTooltip } from '@cowprotocol/ui'
+import { UI } from '@cowprotocol/ui'
+import { FractionLike, Nullish } from '@cowprotocol/ui'
+import { Currency, Price, CurrencyAmount, Fraction } from '@uniswap/sdk-core'
+
+import styled from 'styled-components/macro'
+
+import { ExecutionPriceTooltip } from '../ExecutionPriceTooltip'
+
+const EstimatedFillPriceBox = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding: 10px;
+ border-radius: 0 0 16px 16px;
+ font-size: 14px;
+ font-weight: 600;
+ background: var(${UI.COLOR_PAPER});
+ border: 1px solid var(${UI.COLOR_PAPER_DARKER});
+`
+
+const Label = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ font-weight: 500;
+`
+
+const Value = styled.div`
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+`
+
+const QuestionWrapper = styled.div`
+ display: flex;
+ align-items: center;
+`
+
+export interface EstimatedFillPriceProps {
+ currency: Currency
+ estimatedFillPrice: Nullish
+ executionPrice: Price
+ isInverted: boolean
+ feeAmount: CurrencyAmount | null
+ marketRate: Fraction | null
+}
+
+export function EstimatedFillPrice({
+ currency,
+ estimatedFillPrice,
+ executionPrice,
+ isInverted,
+ feeAmount,
+ marketRate,
+}: EstimatedFillPriceProps) {
+ return (
+
+
+
+ ≈
+
+
+ )
+}
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/pure/RateInput/HeadingText.tsx b/apps/cowswap-frontend/src/modules/limitOrders/pure/RateInput/HeadingText.tsx
index 822e847b32..88ed3dee08 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/pure/RateInput/HeadingText.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/pure/RateInput/HeadingText.tsx
@@ -1,4 +1,5 @@
-import { UI } from '@cowprotocol/ui'
+import { TokenLogo } from '@cowprotocol/tokens'
+import { TokenSymbol, UI } from '@cowprotocol/ui'
import { Currency } from '@uniswap/sdk-core'
import styled from 'styled-components/macro'
@@ -9,33 +10,65 @@ type Props = {
currency: Currency | null
inputCurrency: Currency | null
rateImpact: number
+ toggleIcon?: React.ReactNode
+ onToggle?: () => void
}
const Wrapper = styled.span`
display: flex;
flex-flow: row wrap;
- align-items: flex-start;
+ align-items: center;
justify-content: flex-start;
text-align: left;
gap: 0 3px;
- opacity: 0.7;
+ font-size: 13px;
+ font-weight: 400;
+ margin: auto 0;
+ color: var(${UI.COLOR_TEXT_OPACITY_70});
+`
+
+const TokenWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-weight: 600;
+ color: var(${UI.COLOR_TEXT});
+`
+
+const TextWrapper = styled.span<{ clickable: boolean }>`
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ cursor: ${({ clickable }) => (clickable ? 'pointer' : 'default')};
transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out;
&:hover {
- opacity: 1;
+ text-decoration: underline;
+ text-decoration-style: dashed;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 2px;
+ text-decoration-color: var(${UI.COLOR_TEXT_OPACITY_70});
}
`
-export function HeadingText({ inputCurrency, currency, rateImpact }: Props) {
+export function HeadingText({ inputCurrency, currency, rateImpact, toggleIcon, onToggle }: Props) {
if (!currency) {
return Select input and output
}
return (
- {/* Price of */}
- Limit price
- {}
+ {toggleIcon}
+
+ When
+
+ 1
+
+
+
+ is worth
+ {}
+
)
}
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/pure/Settings/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/pure/Settings/index.tsx
index a12ec03174..94f49afc4b 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/pure/Settings/index.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/pure/Settings/index.tsx
@@ -1,18 +1,135 @@
+import { useCallback, useState } from 'react'
+
+import { UI } from '@cowprotocol/ui'
+import { HelpTooltip } from '@cowprotocol/ui'
+
+import styled from 'styled-components/macro'
+
import { SettingsBox, SettingsContainer, SettingsTitle } from 'modules/trade/pure/Settings'
import { LimitOrdersSettingsState } from '../../state/limitOrdersSettingsAtom'
+const DropdownButton = styled.button`
+ background: var(${UI.COLOR_PAPER_DARKER});
+ color: inherit;
+ border: 1px solid var(${UI.COLOR_BORDER});
+ border-radius: 12px;
+ padding: 10px 34px 10px 12px;
+ min-width: 140px;
+ cursor: pointer;
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+ transition: all 0.2s ease-in-out;
+
+ &::after {
+ content: '▼';
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 10px;
+ transition: transform 0.2s ease-in-out;
+ color: var(${UI.COLOR_PRIMARY_OPACITY_50});
+ }
+
+ &:hover {
+ border-color: var(${UI.COLOR_PRIMARY_OPACITY_25});
+ background: var(${UI.COLOR_PRIMARY_OPACITY_10});
+ }
+
+ &:focus {
+ outline: none;
+ }
+`
+
+const DropdownList = styled.div<{ isOpen: boolean }>`
+ display: ${({ isOpen }) => (isOpen ? 'block' : 'none')};
+ position: absolute;
+ top: calc(100% + 8px);
+ right: 0;
+ background: var(${UI.COLOR_PAPER});
+ border: 1px solid var(${UI.COLOR_BORDER});
+ border-radius: 12px;
+ padding: 6px;
+ min-width: 140px;
+ z-index: 100;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+`
+
+const DropdownItem = styled.div`
+ padding: 10px 12px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 14px;
+ transition: all 0.15s ease-in-out;
+ color: inherit;
+
+ &:hover {
+ background: var(${UI.COLOR_PRIMARY_OPACITY_10});
+ transform: translateX(2px);
+ }
+
+ &:active {
+ transform: translateX(2px) scale(0.98);
+ }
+`
+
+const DropdownContainer = styled.div`
+ position: relative;
+ user-select: none;
+`
+
+const SettingsRow = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 15px;
+ font-weight: 400;
+ color: inherit;
+ font-size: 14px;
+`
+
+const SettingsLabel = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 5px;
+`
+
export interface SettingsProps {
state: LimitOrdersSettingsState
onStateChanged: (state: Partial) => void
}
+const POSITION_LABELS = {
+ top: 'Top',
+ between: 'Between currencies',
+ bottom: 'Bottom',
+}
+
export function Settings({ state, onStateChanged }: SettingsProps) {
- const { showRecipient, partialFillsEnabled } = state
+ const { showRecipient, partialFillsEnabled, limitPricePosition, limitPriceLocked } = state
+ const [isOpen, setIsOpen] = useState(false)
+
+ const handleSelect = useCallback(
+ (value: LimitOrdersSettingsState['limitPricePosition']) => (e: React.MouseEvent) => {
+ e.stopPropagation()
+ onStateChanged({ limitPricePosition: value })
+ setIsOpen(false)
+ },
+ [onStateChanged],
+ )
+
+ const toggleDropdown = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ setIsOpen(!isOpen)
+ }
return (
- Interface Settings
+ Limit Order Settings
onStateChanged({ partialFillsEnabled: !partialFillsEnabled })}
/>
+
+ onStateChanged({ limitPriceLocked: !limitPriceLocked })}
+ />
+
+
+
+ Limit Price Position
+
+
+
+ {POSITION_LABELS[limitPricePosition]}
+
+ {Object.entries(POSITION_LABELS).map(([value, label]) => (
+
+ {label}
+
+ ))}
+
+
+
)
}
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersSettingsAtom.ts b/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersSettingsAtom.ts
index b4df5c2ef4..ee14f8fc04 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersSettingsAtom.ts
+++ b/apps/cowswap-frontend/src/modules/limitOrders/state/limitOrdersSettingsAtom.ts
@@ -17,6 +17,8 @@ export interface LimitOrdersSettingsState {
readonly partialFillsEnabled: boolean
readonly deadlineMilliseconds: Milliseconds
readonly customDeadlineTimestamp: Timestamp | null
+ readonly limitPricePosition: 'top' | 'between' | 'bottom'
+ readonly limitPriceLocked: boolean
}
export const defaultLimitOrdersSettings: LimitOrdersSettingsState = {
@@ -24,40 +26,42 @@ export const defaultLimitOrdersSettings: LimitOrdersSettingsState = {
partialFillsEnabled: true,
deadlineMilliseconds: defaultLimitOrderDeadline.value,
customDeadlineTimestamp: null,
+ limitPricePosition: 'top',
+ limitPriceLocked: true,
}
// regular
const regularLimitOrdersSettingsAtom = atomWithStorage(
'limit-orders-settings-atom:v2',
defaultLimitOrdersSettings,
- getJotaiIsolatedStorage()
+ getJotaiIsolatedStorage(),
)
const regularUpdateLimitOrdersSettingsAtom = atom(
null,
- partialFillsOverrideSetterFactory(regularLimitOrdersSettingsAtom)
+ partialFillsOverrideSetterFactory(regularLimitOrdersSettingsAtom),
)
// alternative
const alternativeLimitOrdersSettingsAtom = atom(defaultLimitOrdersSettings)
const alternativeUpdateLimitOrdersSettingsAtom = atom(
null,
- partialFillsOverrideSetterFactory(alternativeLimitOrdersSettingsAtom)
+ partialFillsOverrideSetterFactory(alternativeLimitOrdersSettingsAtom),
)
// export
export const limitOrdersSettingsAtom = alternativeOrderReadWriteAtomFactory(
regularLimitOrdersSettingsAtom,
- alternativeLimitOrdersSettingsAtom
+ alternativeLimitOrdersSettingsAtom,
)
export const updateLimitOrdersSettingsAtom = atom(
null,
- alternativeOrderAtomSetterFactory(regularUpdateLimitOrdersSettingsAtom, alternativeUpdateLimitOrdersSettingsAtom)
+ alternativeOrderAtomSetterFactory(regularUpdateLimitOrdersSettingsAtom, alternativeUpdateLimitOrdersSettingsAtom),
)
// utils
function partialFillsOverrideSetterFactory(
- atomToUpdate: typeof regularLimitOrdersSettingsAtom | typeof alternativeLimitOrdersSettingsAtom
+ atomToUpdate: typeof regularLimitOrdersSettingsAtom | typeof alternativeLimitOrdersSettingsAtom,
) {
return (get: Getter, set: Setter, nextState: Partial) => {
set(atomToUpdate, () => {
diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx
index 6866a903ba..a2f3738b46 100644
--- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx
+++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx
@@ -193,6 +193,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) {
/>
{!isWrapOrUnwrap && middleContent}
+
+ {slots.limitPriceInput}
+
{withRecipient && }
{isWrapOrUnwrap ? (
diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts
index 0cf4606b84..f9fe79e55b 100644
--- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts
+++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts
@@ -38,6 +38,7 @@ export interface TradeWidgetSlots {
lockScreen?: ReactNode
topContent?: ReactNode
middleContent?: ReactNode
+ limitPriceInput?: ReactNode
bottomContent?(warnings: ReactNode | null): ReactNode
outerContent?: ReactNode
updaters?: ReactNode
diff --git a/apps/cowswap-frontend/src/modules/trade/pure/Settings/SettingsIcon.tsx b/apps/cowswap-frontend/src/modules/trade/pure/Settings/SettingsIcon.tsx
index a3a5694823..9f145ac95c 100644
--- a/apps/cowswap-frontend/src/modules/trade/pure/Settings/SettingsIcon.tsx
+++ b/apps/cowswap-frontend/src/modules/trade/pure/Settings/SettingsIcon.tsx
@@ -1,23 +1,13 @@
import IMAGE_ICON_SETTINGS_ALT from '@cowprotocol/assets/images/icon-settings-alt.svg'
-import { UI } from '@cowprotocol/ui'
import SVG from 'react-inlinesvg'
-import styled from 'styled-components/macro'
-const StyledMenuIcon = styled.span`
- height: var(${UI.ICON_SIZE_NORMAL});
- width: var(${UI.ICON_SIZE_NORMAL});
- opacity: 0.6;
+import { SettingsButtonIcon } from './styled'
- &:hover {
- opacity: 1;
- }
-`
-
-export function SettingsIcon() {
+export function SettingsIcon({ active }: { active?: boolean }) {
return (
-
+
-
+
)
}
diff --git a/apps/cowswap-frontend/src/modules/trade/pure/Settings/styled.ts b/apps/cowswap-frontend/src/modules/trade/pure/Settings/styled.ts
index 8ae70bc993..0570b448da 100644
--- a/apps/cowswap-frontend/src/modules/trade/pure/Settings/styled.ts
+++ b/apps/cowswap-frontend/src/modules/trade/pure/Settings/styled.ts
@@ -2,13 +2,15 @@ import { UI } from '@cowprotocol/ui'
import { MenuButton, MenuList } from '@reach/menu-button'
import { transparentize } from 'color2k'
-import styled from 'styled-components/macro'
+import styled, { css } from 'styled-components/macro'
export const SettingsTitle = styled.h3`
font-weight: 600;
- font-size: 14px;
+ font-size: 18px;
color: inherit;
- margin: 0 0 12px 0;
+ margin: 0 auto 21px;
+ width: 100%;
+ text-align: center;
`
export const SettingsContainer = styled.div`
@@ -56,8 +58,56 @@ export const SettingsButton = styled(MenuButton)`
cursor: pointer;
`
+const iconButtonStyles = css<{ active?: boolean; iconSize?: string }>`
+ --maxSize: 28px;
+ --iconSize: ${({ iconSize }) => iconSize || `var(${UI.ICON_SIZE_NORMAL})`};
+ background: ${({ active }) => (active ? `var(${UI.COLOR_PAPER_DARKER})` : 'none')};
+ border: none;
+ padding: 4px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(${UI.COLOR_TEXT});
+ opacity: ${({ active }) => (active ? '1' : '0.6')};
+ transition: all var(${UI.ANIMATION_DURATION}) ease-in-out;
+ border-radius: 8px;
+ max-width: var(--maxSize);
+ max-height: var(--maxSize);
+ width: var(--maxSize);
+ height: var(--maxSize);
+
+ &:hover {
+ opacity: 1;
+ background: var(${UI.COLOR_PAPER_DARKER});
+ }
+
+ > svg {
+ width: var(--iconSize);
+ height: var(--iconSize);
+ color: inherit;
+ object-fit: contain;
+ }
+`
+
+export const SettingsButtonIcon = styled.span<{ active?: boolean; iconSize?: string }>`
+ ${iconButtonStyles}
+ --iconSize: 18px;
+ margin: auto;
+`
+
export const MenuContent = styled(MenuList)`
position: relative;
z-index: 2;
color: var(${UI.COLOR_TEXT_PAPER});
`
+
+export const ButtonsContainer = styled.div`
+ display: flex;
+ gap: 4px;
+`
+
+export const UsdButton = styled.button<{ active?: boolean }>`
+ ${iconButtonStyles}
+ --iconSize: 20px;
+`
diff --git a/libs/assets/src/images/icon-USD.svg b/libs/assets/src/images/icon-USD.svg
new file mode 100644
index 0000000000..eaaacd1af3
--- /dev/null
+++ b/libs/assets/src/images/icon-USD.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/libs/assets/src/images/icon-locked.svg b/libs/assets/src/images/icon-locked.svg
new file mode 100644
index 0000000000..debfee8ade
--- /dev/null
+++ b/libs/assets/src/images/icon-locked.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/libs/assets/src/images/icon-switch-arrows.svg b/libs/assets/src/images/icon-switch-arrows.svg
new file mode 100644
index 0000000000..98b6786c9b
--- /dev/null
+++ b/libs/assets/src/images/icon-switch-arrows.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/libs/assets/src/images/icon-unlocked.svg b/libs/assets/src/images/icon-unlocked.svg
new file mode 100644
index 0000000000..7ccd1d2701
--- /dev/null
+++ b/libs/assets/src/images/icon-unlocked.svg
@@ -0,0 +1 @@
+
\ No newline at end of file