-
Notifications
You must be signed in to change notification settings - Fork 192
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: SwapAmountInput #504
Merged
Merged
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
45d1b07
add token amount input component
abcrane123 2c87bde
adjust style
abcrane123 a7b29b3
add component to docs
abcrane123 aa8eb46
update token amount input
abcrane123 546c3da
move component to swap directory
abcrane123 ecfb419
remove token amount input
abcrane123 5d99397
update types and remove export
abcrane123 28dd4d1
add amount input container
abcrane123 d1cfea8
update docs
abcrane123 4c82674
remove unused prop
abcrane123 f2aed07
make dropdown onToggle prop optional
abcrane123 9fb661b
update test id for swap input
abcrane123 1bc7182
add html for swap input
abcrane123 2e55bba
address optional param issue
abcrane123 771b405
add SwapAmountInput tests
abcrane123 4eefeb9
comment out imports
abcrane123 63fd99f
add description to type
abcrane123 9ce1605
format code
abcrane123 2e3b96d
add utils test
abcrane123 09862b2
remove unused prop
abcrane123 3ce8811
remove unused imports
abcrane123 1612e68
remove unnecessary code in test file
abcrane123 45df7e3
remove unused file
abcrane123 55e3c9e
remove unused type
abcrane123 2bd86a7
remove unused code
abcrane123 af68798
move max button functionality inside SwapAmountInput component
abcrane123 79f7aef
rename props
abcrane123 37cafa2
update token selector test
abcrane123 9fd7391
address pr comments - alphabetize and reorder imports
abcrane123 b9fe0ac
alphabetize
abcrane123 64fb0d8
remove import from docs
abcrane123 508b979
remove export
abcrane123 4047ea3
remove css classnames
abcrane123 fb559fd
remove token changes
abcrane123 743398f
comment out token selector
abcrane123 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { ReactElement, useState } from 'react'; | ||
import type { Token } from '@coinbase/onchainkit/token'; | ||
|
||
type SwapAmountInputContainer = { | ||
children: ( | ||
amount: string, | ||
setAmount: (a: string) => void, | ||
setToken: (t: Token) => void, | ||
token: Token, | ||
tokenBalance: string, | ||
) => ReactElement; | ||
}; | ||
|
||
const TOKEN_BALANCE_MAP: Record<string, string> = { | ||
ETH: '3.5', | ||
USDC: '2.77', | ||
DAI: '4.9', | ||
}; | ||
|
||
export default function SwapAmountInputContainer({ children }: SwapAmountInputContainer) { | ||
const [token, setToken] = useState<Token>(); | ||
const [amount, setAmount] = useState(''); | ||
|
||
const tokenBalance = TOKEN_BALANCE_MAP[token?.symbol]; | ||
|
||
return children(amount, setAmount, setToken, token, tokenBalance); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
{/* import { SwapAmountInput } from '../../../../src/swap'; */} | ||
import App from '../App'; | ||
import SwapAmountInputContainer from '../../components/SwapAmountInputContainer.tsx'; | ||
|
||
# `<SwapAmountInput />` | ||
|
||
The `SwapAmountInput` component is a stylized input field designed for users to specify the amount of a particular token they wish to swap. This component integrates the `TokenSelector` component to allow users to select different tokens. | ||
|
||
## Usage | ||
|
||
:::code-group | ||
|
||
```tsx [code] | ||
<SwapAmountInput | ||
amount={amount} | ||
label="Sell" | ||
setAmount={setAmount} | ||
setToken={setToken} | ||
swappableTokens={[ | ||
{ | ||
address: '0x1234', | ||
chainId: 1, | ||
decimals: 18, | ||
image: | ||
'https://dynamic-assets.coinbase.com/dbb4b4983bde81309ddab83eb598358eb44375b930b94687ebe38bc22e52c3b2125258ffb8477a5ef22e33d6bd72e32a506c391caa13af64c00e46613c3e5806/asset_icons/4113b082d21cc5fab17fc8f2d19fb996165bcce635e6900f7fc2d57c4ef33ae9.png', | ||
name: 'Ethereum', | ||
symbol: 'ETH', | ||
}, | ||
... | ||
]} | ||
token={token} | ||
tokenBalance={tokenBalance} | ||
/> | ||
``` | ||
|
||
```html [return html] | ||
<div data-testid="ockSwapAmountInput_Container" class="ock-swapamountinput-container"> | ||
<div class="ock-swapamountinput-row"> | ||
<label class="ock-swapamountinput-label">Sell</label> | ||
</div> | ||
<div class="ock-swapamountinput-row"> | ||
<div class="ock-tokenselector-container"> | ||
<button data-testid="ockTokenSelector_Button" class="ock-tokenselector-button"> | ||
<span class="ock-tokenselector-label">Select</span> | ||
<svg | ||
data-testid="ockTokenSelector_CaretDown" | ||
width="16" | ||
height="17" | ||
viewBox="0 0 16 17" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M12.95 4.85999L8.00001 9.80999L3.05001 4.85999L1.64001 6.27999L8.00001 12.64L14.36 6.27999L12.95 4.85999Z" | ||
fill="#0A0B0D" | ||
></path> | ||
</svg> | ||
</button> | ||
</div> | ||
<button class="ock-swapamountinput-maxbutton">Max</button> | ||
</div> | ||
<input | ||
class="ock-swapamountinput-input" | ||
placeholder="0" | ||
data-testid="ockSwapAmountInput_Input" | ||
value="" | ||
/> | ||
</div> | ||
``` | ||
|
||
::: | ||
|
||
## Props | ||
|
||
[`SwapAmountInputReact`](/swap/types#SwapAmountInputReact) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
import React from 'react'; | ||
import '@testing-library/jest-dom'; | ||
import { fireEvent, render, screen, within } from '@testing-library/react'; | ||
import { Address } from 'viem'; | ||
import { SwapAmountInput } from './SwapAmountInput'; | ||
import { Token } from '../../token'; | ||
|
||
const setAmountMock = jest.fn(); | ||
const selectTokenClickMock = jest.fn(); | ||
|
||
const token = { | ||
address: '0x123' as Address, | ||
chainId: 1, | ||
decimals: 2, | ||
image: 'imageURL', | ||
name: 'Ether', | ||
symbol: 'ETH', | ||
}; | ||
|
||
const swappableTokens: Token[] = [ | ||
{ | ||
name: 'Ethereum', | ||
address: '', | ||
symbol: 'ETH', | ||
decimals: 18, | ||
image: 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png', | ||
chainId: 8453, | ||
}, | ||
{ | ||
name: 'USDC', | ||
address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', | ||
symbol: 'USDC', | ||
decimals: 6, | ||
image: | ||
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2', | ||
chainId: 8453, | ||
}, | ||
{ | ||
name: 'Dai', | ||
address: '0x50c5725949a6f0c72e6c4a641f24049a917db0cb', | ||
symbol: 'DAI', | ||
decimals: 18, | ||
image: | ||
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/d0/d7/d0d7784975771dbbac9a22c8c0c12928cc6f658cbcf2bbbf7c909f0fa2426dec-NmU4ZWViMDItOTQyYy00Yjk5LTkzODUtNGJlZmJiMTUxOTgy', | ||
chainId: 8453, | ||
}, | ||
]; | ||
|
||
describe('SwapAmountInput Component', () => { | ||
it('should render', async () => { | ||
render( | ||
<SwapAmountInput | ||
token={token} | ||
swappableTokens={swappableTokens} | ||
label="Sell" | ||
setAmount={setAmountMock} | ||
setToken={selectTokenClickMock} | ||
amount="1" | ||
tokenBalance="100" | ||
/>, | ||
); | ||
|
||
const amountInput = screen.getByTestId('ockSwapAmountInput_Container'); | ||
expect(amountInput).toBeInTheDocument(); | ||
|
||
const labelElement = within(amountInput).getByText('Sell'); | ||
expect(labelElement).toBeInTheDocument(); | ||
|
||
const balanceElement = within(amountInput).getByText('Balance: 100'); | ||
expect(balanceElement).toBeInTheDocument(); | ||
}); | ||
|
||
it('should update the amount if a user enters a number', async () => { | ||
render( | ||
<SwapAmountInput | ||
token={token} | ||
swappableTokens={swappableTokens} | ||
label="Sell" | ||
setAmount={setAmountMock} | ||
setToken={selectTokenClickMock} | ||
/>, | ||
); | ||
|
||
const input = screen.getByTestId('ockSwapAmountInput_Input') as HTMLInputElement; | ||
fireEvent.change(input, { target: { value: '2' } }); | ||
expect(setAmountMock).toHaveBeenCalledWith('2'); | ||
expect(input.value).toBe('2'); | ||
}); | ||
|
||
it('should not update the amount if a user enters a non-number', async () => { | ||
render( | ||
<SwapAmountInput | ||
token={token} | ||
swappableTokens={swappableTokens} | ||
label="Sell" | ||
setAmount={setAmountMock} | ||
setToken={selectTokenClickMock} | ||
amount="1" | ||
/>, | ||
); | ||
|
||
const input = screen.getByTestId('ockSwapAmountInput_Input') as HTMLInputElement; | ||
fireEvent.change(input, { target: { value: 'a' } }); | ||
expect(setAmountMock).not.toHaveBeenCalledWith('a'); | ||
expect(input.value).toBe('1'); | ||
}); | ||
|
||
it('should call setAmount with tokenBalance when the max button is clicked', async () => { | ||
render( | ||
<SwapAmountInput | ||
token={token} | ||
swappableTokens={swappableTokens} | ||
label="Sell" | ||
amount="1" | ||
setAmount={setAmountMock} | ||
setToken={selectTokenClickMock} | ||
tokenBalance="100" | ||
/>, | ||
); | ||
|
||
const maxButton = screen.getByTestId('ockSwapAmountInput_MaxButton'); | ||
fireEvent.click(maxButton); | ||
expect(setAmountMock).toHaveBeenCalledWith('100'); | ||
}); | ||
|
||
it('should disable the input when disabled prop is true', async () => { | ||
render( | ||
<SwapAmountInput | ||
token={token} | ||
swappableTokens={swappableTokens} | ||
label="Sell" | ||
amount="1" | ||
setAmount={setAmountMock} | ||
setToken={selectTokenClickMock} | ||
disabled | ||
/>, | ||
); | ||
|
||
const input = screen.getByTestId('ockSwapAmountInput_Input') as HTMLInputElement; | ||
expect(input).toBeDisabled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { useCallback } from 'react'; | ||
|
||
import { isValidAmount } from '../utils'; | ||
import type { SwapAmountInputReact } from '../types'; | ||
|
||
export function SwapAmountInput({ | ||
amount, | ||
disabled = false, | ||
label, | ||
setAmount, | ||
tokenBalance, | ||
}: SwapAmountInputReact) { | ||
const handleAmountChange = useCallback( | ||
(event: React.ChangeEvent<HTMLInputElement>) => { | ||
if (isValidAmount(event.target.value)) { | ||
setAmount(event.target.value); | ||
} | ||
}, | ||
[setAmount], | ||
); | ||
|
||
const handleMaxButtonClick = useCallback(() => { | ||
if (tokenBalance && isValidAmount(tokenBalance)) { | ||
setAmount(tokenBalance); | ||
} | ||
}, [tokenBalance, setAmount]); | ||
|
||
return ( | ||
<div | ||
className="box-border flex w-fit flex-col items-start gap-[11px] bg-[#FFF] p-4" | ||
data-testid="ockSwapAmountInput_Container" | ||
> | ||
<div className="flex w-full items-center justify-between"> | ||
<label className="text-sm font-semibold text-[#030712]">{label}</label> | ||
{tokenBalance && ( | ||
<label className="text-sm font-normal text-gray-400">{`Balance: ${tokenBalance}`}</label> | ||
)} | ||
</div> | ||
<div className="flex w-full items-center justify-between"> | ||
{/* TODO: add back in when TokenSelector is complete */} | ||
{/* <TokenSelector setToken={setToken} token={token}> | ||
<TokenSelectorDropdown options={swappableTokens} setToken={setToken} /> | ||
</TokenSelector> */} | ||
<button | ||
className="flex h-8 w-[58px] max-w-[200px] items-center rounded-[40px] bg-gray-100 px-3 py-2 text-base font-medium not-italic leading-6 text-gray-500" | ||
data-testid="ockSwapAmountInput_MaxButton" | ||
disabled={tokenBalance === undefined} | ||
onClick={handleMaxButtonClick} | ||
> | ||
Max | ||
</button> | ||
</div> | ||
<input | ||
className="border-[none] bg-transparent text-5xl text-[black]" | ||
data-testid="ockSwapAmountInput_Input" | ||
disabled={disabled} | ||
onChange={handleAmountChange} | ||
placeholder="0" | ||
value={amount} | ||
/> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in general, always keep everything Props and Types alphabetical so it's easiser to code review and mantian.