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

[Draft] Custom address input #1055

Draft
wants to merge 2 commits into
base: refactor-adress-input
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion packages/nextjs/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider enableSystem>
<ThemeProvider attribute="data-theme" enableSystem>
<ScaffoldEthAppWithProviders>{children}</ScaffoldEthAppWithProviders>
</ThemeProvider>
</body>
Expand Down
21 changes: 21 additions & 0 deletions packages/nextjs/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "styles/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "~~/components",
"utils": "~~/lib/utils",
"ui": "~~/components/ui",
"lib": "~~/lib",
"hooks": "~~/hooks"
},
"iconLibrary": "lucide"
}
14 changes: 14 additions & 0 deletions packages/nextjs/components/scaffold-eth/Faucet.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";

import { useEffect, useState } from "react";
import { AddressInput2 } from "./Input/AddressInput2";
import { CustomAddressInput } from "./Input/CustomAddressInput";
import { Address as AddressType, createWalletClient, http, parseEther } from "viem";
import { hardhat } from "viem/chains";
import { useAccount } from "wagmi";
Expand All @@ -23,6 +25,8 @@ const localWalletClient = createWalletClient({
export const Faucet = () => {
const [loading, setLoading] = useState(false);
const [inputAddress, setInputAddress] = useState<AddressType>();
const [inputAddress2, setInputAddress2] = useState<AddressType>();
const [inputAddress3, setInputAddress3] = useState<AddressType>();
const [faucetAddress, setFaucetAddress] = useState<AddressType>();
const [sendValue, setSendValue] = useState("");

Expand Down Expand Up @@ -111,6 +115,16 @@ export const Faucet = () => {
value={inputAddress ?? ""}
onChange={value => setInputAddress(value as AddressType)}
/>
<AddressInput2
placeholder="Destination Address"
value={inputAddress2 ?? ""}
onChange={value => setInputAddress2(value as AddressType)}
/>
<CustomAddressInput
placeholder="Destination Address"
value={inputAddress3 ?? ""}
onChange={value => setInputAddress3(value as AddressType)}
/>
<EtherInput placeholder="Amount to send" value={sendValue} onChange={value => setSendValue(value)} />
<button className="h-10 btn btn-primary btn-sm px-2 rounded-full" onClick={sendETH} disabled={loading}>
{!loading ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Address, GetEnsNameReturnType } from "viem";
import { GetEnsAvatarReturnType } from "wagmi/actions";

export const CustomAddressInputPrefix = ({
ensName,
ensAvatar,
isEnsAvatarLoading,
isEnsLoadingAddressOrName,
enteredEnsName,
ensAddress,
}: {
ensName?: GetEnsNameReturnType;
ensAvatar?: GetEnsAvatarReturnType;
ensAddress?: Address;
isEnsAvatarLoading: boolean;
isEnsLoadingAddressOrName: boolean;
enteredEnsName?: string;
}) => {
return ensName ? (
<div className="flex bg-blue-400 rounded-l-md items-center">
{isEnsAvatarLoading && <div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-md shrink-0"></div>}
{ensAvatar ? (
<span className="w-[35px]">
{
// eslint-disable-next-line
<img className="w-md rounded-md" src={ensAvatar} alt={`${ensAddress} avatar`} />
}
</span>
) : null}
<span className="px-2">{enteredEnsName ?? ensName}</span>
</div>
) : (
isEnsLoadingAddressOrName && (
<div className="flex bg-base-300 rounded-l-md items-center gap-2 pr-2">
<div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-md shrink-0"></div>
<div className="skeleton bg-base-200 h-3 w-20"></div>
</div>
)
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { blo } from "blo";
import { Address } from "viem";

export const CustomAddressInputSuffix = ({ value }: { value: Address }) => {
// Don't want to use nextJS Image here (and adding remote patterns for the URL)
// eslint-disable-next-line @next/next/no-img-element
return value && <img alt="" className="!rounded-md" src={blo(value as `0x${string}`)} width="35" height="35" />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ReactNode } from "react";
import { useInputBase } from "../../../AddressInput2/_components/InputBase2/_hooks/useInputBase";
import { CommonInputProps } from "~~/components/scaffold-eth";
import { Input } from "~~/components/ui/input";

type InputBaseProps<T> = CommonInputProps<T> & {
error?: boolean;
prefix?: ReactNode;
suffix?: ReactNode;
reFocus?: boolean;
};

export const CustomInputBase = <T extends { toString: () => string } | undefined = string>({
name,
value,
onChange,
placeholder,
error,
disabled,
prefix,
suffix,
reFocus,
}: InputBaseProps<T>) => {
const { handleChange, onFocus, inputRef } = useInputBase<T>({ onChange, reFocus });

let modifier = "border-blue-400";
if (error) {
modifier = "border-error";
} else if (disabled) {
modifier = "border-disabled bg-base-300";
}

return (
<div className={`flex border-2 bg-base-200 rounded-md ${modifier}`}>
{prefix}
<Input
className={`input input-ghost focus-within:border-transparent px-4 border w-full font-medium focus:!outline-none focus:!ring-0`}
placeholder={placeholder}
name={name}
value={value?.toString()}
onChange={handleChange}
disabled={disabled}
autoComplete="off"
ref={inputRef}
onFocus={onFocus}
/>
{suffix}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useAddressInput } from "../AddressInput2/_hooks/useAddressInput";
import { CustomAddressInputPrefix } from "./_components/CustomAddressInputPrefix";
import { CustomAddressInputSuffix } from "./_components/CustomAddressInputSuffix";
import { CustomInputBase } from "./_components/CustomInputBase";
import { Address } from "viem";
import { CommonInputProps } from "~~/components/scaffold-eth";

/**
* Address input with ENS name resolution
*/
export const CustomAddressInput = ({
value,
name,
placeholder,
onChange,
disabled,
}: CommonInputProps<Address | string>) => {
const { ensAddress, ensName, ensAvatar, isEnsLoadingAddressOrName, reFocus, isEnsAvatarLoading, enteredEnsName } =
useAddressInput({
value,
onChange,
});

return (
<CustomInputBase<Address>
name={name}
placeholder={placeholder}
error={ensAddress === null}
value={value as Address}
onChange={onChange}
disabled={isEnsLoadingAddressOrName || disabled}
reFocus={reFocus}
prefix={
<CustomAddressInputPrefix
ensName={ensName}
ensAvatar={ensAvatar}
isEnsAvatarLoading={isEnsAvatarLoading}
isEnsLoadingAddressOrName={isEnsLoadingAddressOrName}
enteredEnsName={enteredEnsName}
/>
}
suffix={<CustomAddressInputSuffix value={value as Address} />}
/>
);
};
21 changes: 21 additions & 0 deletions packages/nextjs/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from "react";
import { cn } from "~~/lib/utils";

const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";

export { Input };
6 changes: 6 additions & 0 deletions packages/nextjs/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
5 changes: 5 additions & 0 deletions packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
"@uniswap/v2-sdk": "^4.6.1",
"blo": "^1.2.0",
"burner-connector": "0.0.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"daisyui": "4.12.10",
"kubo-rpc-client": "^5.0.2",
"lucide-react": "^0.475.0",
"next": "^14.2.11",
"next-nprogress-bar": "^2.3.13",
"next-themes": "^0.3.0",
Expand All @@ -33,6 +36,8 @@
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.0",
"tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7",
"usehooks-ts": "^3.1.0",
"viem": "2.23.0",
"wagmi": "2.14.11",
Expand Down
71 changes: 68 additions & 3 deletions packages/nextjs/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@tailwind base;
@tailwind components;
@tailwind utilities;

:root,
[data-theme] {
Expand Down Expand Up @@ -30,3 +30,68 @@ p {
.btn.btn-ghost {
@apply shadow-none;
}

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
Loading