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: Native Send Component #1874

Draft
wants to merge 82 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
17d6b57
send scaffolding
brendan-defi Jan 22, 2025
29ce8e2
remove ethbalance from provider props
brendan-defi Jan 22, 2025
d5f399f
update utils
brendan-defi Jan 22, 2025
cb64639
refactor to remove sendprovider dep
brendan-defi Jan 22, 2025
971dc6a
starting address selection
brendan-defi Jan 22, 2025
05eb416
update getAddress to support basename reverse res
brendan-defi Jan 23, 2025
a89983c
utils + tests
brendan-defi Jan 23, 2025
033c4b8
early send flows
brendan-defi Jan 23, 2025
6ede224
move send into wallet
brendan-defi Jan 23, 2025
b9bba55
send in walletisland
brendan-defi Jan 23, 2025
c50f9a8
state drives active screen
brendan-defi Jan 23, 2025
ebe1a68
use amount input in send
brendan-defi Jan 23, 2025
b0a97f5
send amount input
brendan-defi Jan 24, 2025
7cb8665
remove comments, unused imports
brendan-defi Jan 24, 2025
f029ffb
fix imports
brendan-defi Jan 24, 2025
c49fe08
fix imports, tests
brendan-defi Jan 24, 2025
8964803
removed duplicate hook
brendan-defi Jan 24, 2025
9e63f2d
add close button
brendan-defi Jan 24, 2025
57f92da
address input reset
brendan-defi Jan 25, 2025
b56268e
send updates
brendan-defi Jan 25, 2025
c936a62
token balance component
brendan-defi Jan 25, 2025
f379001
enable actions on token balance
brendan-defi Jan 25, 2025
f3854da
token selection
brendan-defi Jan 26, 2025
b1df7ac
improved token reselection
brendan-defi Jan 27, 2025
da50b49
improved asset type switch
brendan-defi Jan 27, 2025
393fc34
update send button
brendan-defi Jan 27, 2025
d029daf
header back button
brendan-defi Jan 27, 2025
bfbb95a
fix imports
brendan-defi Jan 27, 2025
ada978b
refactor provider for clarity
brendan-defi Jan 27, 2025
58d9600
update filename
brendan-defi Jan 27, 2025
69e5ab9
fix subtitle
brendan-defi Jan 28, 2025
ad5af09
add fundcard when no eth balance
brendan-defi Jan 28, 2025
35efd87
minor refactor
brendan-defi Jan 28, 2025
620554b
design review
brendan-defi Jan 29, 2025
c3d3e84
fix ethBalance race condition
brendan-defi Jan 29, 2025
77771aa
fix lints
brendan-defi Jan 29, 2025
320f46d
use common files
brendan-defi Jan 29, 2025
8999246
subcomponents use props
brendan-defi Jan 29, 2025
1ed61e6
override classnames
brendan-defi Jan 29, 2025
26aff3a
input field uses names
brendan-defi Jan 29, 2025
671505e
update send fund wallet
brendan-defi Jan 29, 2025
d542d7e
use common files
brendan-defi Jan 29, 2025
a44357a
remove send exportsc
brendan-defi Jan 29, 2025
f15c9b6
update token balance display
brendan-defi Jan 29, 2025
430e3d2
remove max width from token name
brendan-defi Jan 29, 2025
588a8a4
fix use max
brendan-defi Jan 29, 2025
67e9726
hardcode input autocomplete props
brendan-defi Jan 29, 2025
7a25392
send on base and mainnet
brendan-defi Jan 29, 2025
a9cc65b
fix button-within-button bug
brendan-defi Jan 29, 2025
d838f28
implement succcessOverride
brendan-defi Jan 29, 2025
3ad97f7
fix lints
brendan-defi Jan 29, 2025
02aed37
implement lifecycle tracking
brendan-defi Jan 30, 2025
930add1
update types to conform to standard useLifecycleStatus non-null expec…
brendan-defi Jan 30, 2025
66cd248
fix lints
brendan-defi Jan 30, 2025
c1061d0
fix playground package.json
brendan-defi Jan 30, 2025
adbbe2e
remove error export, improve number transforms
brendan-defi Jan 30, 2025
240a3f7
improve number transforms
brendan-defi Jan 30, 2025
84d73d1
improved number transforms
brendan-defi Jan 30, 2025
640615a
improved number transforms
brendan-defi Jan 30, 2025
4f5452e
utils for readability and reuse
brendan-defi Jan 30, 2025
6be3572
remove identity wrapper
brendan-defi Jan 30, 2025
4358eaa
utility for resolving the displayed address/name
brendan-defi Jan 30, 2025
ca305b2
use utility
brendan-defi Jan 30, 2025
be0826e
revert type change
brendan-defi Jan 30, 2025
1bc72ff
update lifecycle types
brendan-defi Jan 30, 2025
00f920d
update and create utils
brendan-defi Jan 30, 2025
441795b
button label
brendan-defi Jan 30, 2025
fe0de1c
update provider and types
brendan-defi Jan 30, 2025
266afbf
clean provider
brendan-defi Jan 30, 2025
4be81d1
handle null args
brendan-defi Jan 30, 2025
d5a9b15
refactor to better leverage TextInput
brendan-defi Jan 30, 2025
b61afce
consolidate address selection
brendan-defi Jan 30, 2025
5e1d699
refactor to avoid rerenders
brendan-defi Jan 30, 2025
b1d9011
handle status updates
brendan-defi Jan 30, 2025
4f88bb0
header handles address reset
brendan-defi Jan 30, 2025
9438101
fix lints
brendan-defi Jan 30, 2025
51947c3
fix spacing
brendan-defi Jan 31, 2025
a89d3f1
fix input resetting
brendan-defi Jan 31, 2025
d0fe8e8
add debounce to value inputs
brendan-defi Jan 31, 2025
98c1207
refactor resolve address input
brendan-defi Jan 31, 2025
1a8c48a
fix walletHasEth logic
brendan-defi Feb 1, 2025
353a8e8
fix design feedback
brendan-defi Feb 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions playground/nextjs-app-router/onchainkit/package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "@coinbase/onchainkit",
"version": "0.36.8",
"version": "0.36.9",
"type": "module",
"repository": "https://github.com/coinbase/onchainkit.git",
"license": "MIT",
"scripts": {
"build": "packemon build --addEngines --addFiles --loadConfigs --declaration && npx packemon validate --no-license --no-people --no-repo && tailwindcss -i ./src/styles/index.css -o ./src/tailwind.css --minify && tailwindcss -i ./src/styles/index-with-tailwind.css -o ./src/styles.css --minify",
"build": "packemon build --addEngines --addFiles --loadConfigs --declaration && tscpaths -p tsconfig.esm.json -s ./src -o ./esm && npx packemon validate --no-license --no-people --no-repo && tailwindcss -i ./src/styles/index.css -o ./src/tailwind.css --minify && tailwindcss -i ./src/styles/index-with-tailwind.css -o ./src/styles.css --minify",
"check": "biome check --write .",
"check:unsafe": "biome check . --fix --unsafe",
"ci:check": "biome ci --formatter-enabled=false --linter-enabled=false",
Expand Down Expand Up @@ -77,6 +77,7 @@
"rimraf": "^5.0.5",
"storybook": "^8.2.9",
"tailwindcss": "^3.4.3",
"tscpaths": "^0.0.9",
"tsup": "^8.3.5",
"typescript": "~5.3.3",
"vite": "^5.3.3",
Expand Down
10 changes: 2 additions & 8 deletions src/internal/components/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ type TextInputReact = {
setValue?: (s: string) => void;
value: string;
inputValidator?: (s: string) => boolean;
/** autocomplete attribute handles browser autocomplete, defaults to 'off' */
autoComplete?: string;
/** data-1p-ignore attribute handles password manager autocomplete, defaults to true */
'data-1p-ignore'?: boolean;
};

export const TextInput = forwardRef<HTMLInputElement, TextInputReact>(
Expand All @@ -41,8 +37,6 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputReact>(
inputMode,
value,
inputValidator = () => true,
autoComplete = 'off',
'data-1p-ignore': data1pIgnore = true,
},
ref,
) => {
Expand Down Expand Up @@ -80,8 +74,8 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputReact>(
onChange={handleChange}
onFocus={onFocus}
disabled={disabled}
autoComplete={autoComplete}
data-1p-ignore={data1pIgnore}
autoComplete="off" // autocomplete attribute handles browser autocomplete
data-1p-ignore={true} // data-1p-ignore attribute handles password manager autocomplete
/>
);
},
Expand Down
134 changes: 134 additions & 0 deletions src/internal/components/TokenBalance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import type { PortfolioTokenWithFiatValue } from '@/api/types';
import { formatFiatAmount } from '@/internal/utils/formatFiatAmount';
import { truncateDecimalPlaces } from '@/internal/utils/truncateDecimalPlaces';
import { border, cn, color, text } from '@/styles/theme';
import { TokenImage } from '@/token';
import { useMemo } from 'react';
import { formatUnits } from 'viem';

type TokenBalanceProps = {
token: PortfolioTokenWithFiatValue;
subtitle: string;
showImage?: boolean;
onClick?: (token: PortfolioTokenWithFiatValue) => void;
className?: string;
} & (
| { showAction?: true; actionText?: string; onActionPress?: () => void }
| { showAction?: false; actionText?: never; onActionPress?: never }
);

export function TokenBalance({
token,
subtitle,
showImage = true,
onClick,
showAction = false,
actionText = 'Use max',
onActionPress,
className,
}: TokenBalanceProps) {
const formattedFiatValue = formatFiatAmount({
amount: token.fiatBalance,
currency: 'USD',
});

const formattedCryptoValue = truncateDecimalPlaces(
formatUnits(BigInt(token.cryptoBalance), token.decimals),
3,
);

const tokenContent = useMemo(() => {
return (
<div className="grid w-full grid-cols-[2.5rem_1fr_auto] items-center gap-3">
<div className="h-10 w-10">
{showImage && <TokenImage token={token} size={40} />}
</div>
<div className="flex min-w-0 flex-col text-left">
<span
className={cn(
text.headline,
color.foreground,
'overflow-hidden text-ellipsis whitespace-nowrap',
)}
>
{token.name?.trim()}
</span>
<span className={cn(text.label2, color.foregroundMuted)}>
{`${formattedCryptoValue} ${token.symbol} ${subtitle}`}
</span>
</div>
<div className="text-right">
{showAction ? (
<span
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why <span > here? Probably should be a button if it's clickable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't have a button within a button, and sometimes the whole container is a button

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be better to style the second button so it appears over the top using position relative to avoid having an interactive descendent of a button. Even with an onClick, a span won't be tabbable without a tabindex or role, both of which would make this invalid.

aria-label={actionText}
onClick={(e) => {
e.stopPropagation();
onActionPress?.();
}}
onKeyDown={(e) => {
e.stopPropagation();
onActionPress?.();
}}
className={cn(
text.label2,
color.primary,
border.radius,
'ml-auto p-0.5 font-bold',
'border border-transparent hover:border-[--ock-line-primary]',
)}
>
{actionText}
</span>
) : (
<span
className={cn(
text.label2,
color.foregroundMuted,
'whitespace-nowrap',
)}
>
{formattedFiatValue}
</span>
)}
</div>
</div>
);
}, [
showImage,
token,
subtitle,
showAction,
actionText,
onActionPress,
formattedFiatValue,
formattedCryptoValue,
]);

if (onClick) {
return (
<button
type="button"
onClick={() => onClick(token)}
className={cn(
'flex w-full items-center justify-start gap-4 px-2 py-1',
className,
)}
data-testid="ockTokenBalanceButton"
>
{tokenContent}
</button>
);
}

return (
<div
className={cn(
'flex w-full items-center justify-start gap-4 px-2 py-1',
className,
)}
data-testid="ockTokenBalanceDiv"
>
{tokenContent}
</div>
);
}
28 changes: 20 additions & 8 deletions src/internal/components/amount-input/AmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ type AmountInputProps = {
setFiatAmount: (value: string) => void;
setCryptoAmount: (value: string) => void;
exchangeRate: string;
delayMs?: number;
className?: string;
textClassName?: string;
};

export function AmountInput({
Expand All @@ -24,10 +26,12 @@ export function AmountInput({
asset,
selectedInputType,
currency,
className,
setFiatAmount,
setCryptoAmount,
exchangeRate,
delayMs,
className,
textClassName,
}: AmountInputProps) {
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -94,28 +98,35 @@ export function AmountInput({
>
<div className="flex h-14">
<TextInput
ref={inputRef}
value={value}
onChange={handleAmountChange}
inputValidator={isValidAmount}
delayMs={delayMs}
placeholder="0"
inputMode="decimal"
className={cn(
text.body,
'border-none bg-transparent',
'text-6xl leading-none outline-none',
'[appearance:textfield]',
'[&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none',
'[&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:appearance-none',
textClassName,
)}
value={value}
onChange={handleAmountChange}
inputValidator={isValidAmount}
ref={inputRef}
inputMode="decimal"
placeholder="0"
/>

<CurrencyLabel ref={currencySpanRef} label={currencyOrAsset} />
<CurrencyLabel
ref={currencySpanRef}
label={currencyOrAsset}
className={textClassName}
/>
</div>

{/* Hidden span for measuring text width
Without this span the input field would not adjust its width based on the text width and would look like this:
[0.12--------Empty Space-------][ETH] - As you can see the currency symbol is far away from the inputed value

With this span we can measure the width of the text in the input field and set the width of the input field to match the text width
[0.12][ETH] - Now the currency symbol is displayed next to the input field
*/}
Expand All @@ -128,6 +139,7 @@ export function AmountInput({
'text-6xl leading-none outline-none',
'pointer-events-none absolute whitespace-nowrap opacity-0',
'left-[-9999px]', // Hide the span from the DOM
textClassName,
)}
>
{value ? `${value}.` : '0.'}
Expand Down
4 changes: 3 additions & 1 deletion src/internal/components/amount-input/CurrencyLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { forwardRef } from 'react';

type CurrencyLabelProps = {
label: string;
className?: string;
};

export const CurrencyLabel = forwardRef<HTMLSpanElement, CurrencyLabelProps>(
({ label }, ref) => {
({ label, className }, ref) => {
return (
<span
ref={ref}
Expand All @@ -15,6 +16,7 @@ export const CurrencyLabel = forwardRef<HTMLSpanElement, CurrencyLabelProps>(
color.disabled,
'flex items-center justify-center bg-transparent',
'text-6xl leading-none outline-none',
className,
)}
data-testid="ockCurrencySpan"
>
Expand Down
9 changes: 8 additions & 1 deletion src/token/components/TokenRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ export const TokenRow = memo(function TokenRow({
<span className="flex items-center gap-3">
{!hideImage && <TokenImage token={token} size={28} />}
<span className="flex flex-col items-start">
<span className={cn(text.headline)}>{token.name}</span>
<span
className={cn(
text.headline,
'overflow-hidden text-ellipsis whitespace-nowrap text-left',
)}
>
{token.name.trim()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious what the need for trim() is here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some tokens have whitespace at the front / end for style /s. we don't want that

</span>
{!hideSymbol && (
<span className={cn(text.body, color.foregroundMuted)}>
{token.symbol}
Expand Down
Loading
Loading