Skip to content

Commit

Permalink
Shadcn rainbowkit button + dropdown (#937)
Browse files Browse the repository at this point in the history
* feat: shadcn rainbowkit button

* revert: ghost button colors

* chore: prevent qr modal autofocus

* fix: network options

* fix: redundant comment

* chore: ring-accent

* fix: open menu and qr modal on enter and close on esc

* revert: button changes

* revert: dropdown-menu changes

* fix: avoid passing classNames

* fix: return w-full justify-start

* fix: dropdow items was clickable during closing menu animation
  • Loading branch information
rin-st authored Sep 19, 2024
1 parent 7eefe29 commit f1e5c1b
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 142 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useRef, useState } from "react";
import { useState } from "react";
import { AddressQRCodeModal } from "./AddressQRCodeModal";
import { NetworkOptions } from "./NetworkOptions";
import CopyToClipboard from "react-copy-to-clipboard";
import { getAddress } from "viem";
Expand All @@ -14,7 +15,13 @@ import {
QrCodeIcon,
} from "@heroicons/react/24/outline";
import { BlockieAvatar, isENS } from "~~/components/scaffold-eth";
import { useOutsideClick } from "~~/hooks/scaffold-eth";
import { Button } from "~~/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "~~/components/ui/dropdown-menu";
import { getTargetNetworks } from "~~/utils/scaffold-eth";

const allowedNetworks = getTargetNetworks();
Expand All @@ -34,104 +41,112 @@ export const AddressInfoDropdown = ({
}: AddressInfoDropdownProps) => {
const { disconnect } = useDisconnect();
const checkSumAddress = getAddress(address);
const [open, setOpen] = useState(false);

const [addressCopied, setAddressCopied] = useState(false);

const [selectingNetwork, setSelectingNetwork] = useState(false);
const dropdownRef = useRef<HTMLDetailsElement>(null);
const closeDropdown = () => {
setSelectingNetwork(false);
dropdownRef.current?.removeAttribute("open");
setOpen(false);
};
useOutsideClick(dropdownRef, closeDropdown);

return (
<>
<details ref={dropdownRef} className="dropdown dropdown-end leading-3">
<summary tabIndex={0} className="btn btn-secondary btn-sm pl-0 pr-2 shadow-md dropdown-toggle gap-0 !h-auto">
<DropdownMenu open={open} onOpenChange={open ? undefined : setOpen}>
<DropdownMenuTrigger asChild onClick={() => setOpen(open => !open)}>
<Button size="sm" variant="secondary">
<BlockieAvatar address={checkSumAddress} size={30} ensImage={ensAvatar} />
<span className="ml-2 mr-1">
{isENS(displayName) ? displayName : checkSumAddress?.slice(0, 6) + "..." + checkSumAddress?.slice(-4)}
</span>
<ChevronDownIcon className="h-6 w-4 ml-2 sm:ml-0" />
</summary>
<ul
tabIndex={0}
className="dropdown-content menu z-[2] p-2 mt-2 shadow-center shadow-accent bg-base-200 rounded-box gap-1"
>
<NetworkOptions hidden={!selectingNetwork} />
<li className={selectingNetwork ? "hidden" : ""}>
{addressCopied ? (
<div className="btn-sm !rounded-xl flex gap-3 py-3">
<CheckCircleIcon
className="text-xl font-normal h-6 w-4 cursor-pointer ml-2 sm:ml-0"
aria-hidden="true"
/>
<span className=" whitespace-nowrap">Copy address</span>
</div>
) : (
<CopyToClipboard
text={checkSumAddress}
onCopy={() => {
setAddressCopied(true);
setTimeout(() => {
setAddressCopied(false);
}, 800);
}}
>
<div className="btn-sm !rounded-xl flex gap-3 py-3">
<DocumentDuplicateIcon
className="text-xl font-normal h-6 w-4 cursor-pointer ml-2 sm:ml-0"
aria-hidden="true"
/>
<span className=" whitespace-nowrap">Copy address</span>
</div>
</CopyToClipboard>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
onInteractOutside={closeDropdown}
onEscapeKeyDown={closeDropdown}
className={open ? "" : "!pointer-events-none"}
>
<NetworkOptions hidden={!selectingNetwork} />
{!selectingNetwork && (
<>
<DropdownMenuItem asChild>
<Button variant="ghost" size="sm" className="w-full justify-start">
{addressCopied ? (
<div className="flex items-center">
<CheckCircleIcon
className="text-xl font-normal h-6 w-4 cursor-pointer mr-2 ml-2 sm:ml-0"
aria-hidden="true"
/>
<span className="whitespace-nowrap">Copied!</span>
</div>
) : (
<CopyToClipboard
text={checkSumAddress}
onCopy={() => {
setAddressCopied(true);
setTimeout(() => {
setAddressCopied(false);
}, 800);
}}
>
<div className="flex items-center w-full">
<DocumentDuplicateIcon className="text-xl font-normal h-6 w-4 mr-2" aria-hidden="true" />
<span className="whitespace-nowrap">Copy address</span>
</div>
</CopyToClipboard>
)}
</Button>
</DropdownMenuItem>
<AddressQRCodeModal address={address}>
<DropdownMenuItem asChild>
<Button variant="ghost" size="sm" className="w-full">
<div className="flex items-center w-full">
<QrCodeIcon className="mr-2 h-4 w-4" />
<span>View QR Code</span>
</div>
</Button>
</DropdownMenuItem>
</AddressQRCodeModal>
<DropdownMenuItem asChild>
<Button variant="ghost" size="sm" className="w-full" asChild>
<a target="_blank" href={blockExplorerAddressLink} rel="noopener noreferrer">
<ArrowTopRightOnSquareIcon className="mr-2 h-4 w-4" />
View on Block Explorer
</a>
</Button>
</DropdownMenuItem>
{allowedNetworks.length > 1 && (
<DropdownMenuItem asChild>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={() => setSelectingNetwork(true)}
>
<ArrowsRightLeftIcon className="mr-2 h-4 w-4" />
<span>Switch Network</span>
</Button>
</DropdownMenuItem>
)}
</li>
<li className={selectingNetwork ? "hidden" : ""}>
<label htmlFor="qrcode-modal" className="btn-sm !rounded-xl flex gap-3 py-3">
<QrCodeIcon className="h-6 w-4 ml-2 sm:ml-0" />
<span className="whitespace-nowrap">View QR Code</span>
</label>
</li>
<li className={selectingNetwork ? "hidden" : ""}>
<button className="menu-item btn-sm !rounded-xl flex gap-3 py-3" type="button">
<ArrowTopRightOnSquareIcon className="h-6 w-4 ml-2 sm:ml-0" />
<a
target="_blank"
href={blockExplorerAddressLink}
rel="noopener noreferrer"
className="whitespace-nowrap"
>
View on Block Explorer
</a>
</button>
</li>
{allowedNetworks.length > 1 ? (
<li className={selectingNetwork ? "hidden" : ""}>
<button
className="btn-sm !rounded-xl flex gap-3 py-3"
type="button"
<DropdownMenuItem asChild>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={() => {
setSelectingNetwork(true);
disconnect();
closeDropdown();
}}
>
<ArrowsRightLeftIcon className="h-6 w-4 ml-2 sm:ml-0" /> <span>Switch Network</span>
</button>
</li>
) : null}
<li className={selectingNetwork ? "hidden" : ""}>
<button
className="menu-item text-error btn-sm !rounded-xl flex gap-3 py-3"
type="button"
onClick={() => disconnect()}
>
<ArrowLeftOnRectangleIcon className="h-6 w-4 ml-2 sm:ml-0" /> <span>Disconnect</span>
</button>
</li>
</ul>
</details>
</>
<ArrowLeftOnRectangleIcon className="mr-2 h-4 w-4" />
<span>Disconnect</span>
</Button>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
import { QRCodeSVG } from "qrcode.react";
import { Address as AddressType } from "viem";
import { Address } from "~~/components/scaffold-eth";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "~~/components/ui/dialog";

type AddressQRCodeModalProps = {
address: AddressType;
modalId: string;
children: React.ReactNode;
};

export const AddressQRCodeModal = ({ address, modalId }: AddressQRCodeModalProps) => {
export const AddressQRCodeModal = ({ address, children }: AddressQRCodeModalProps) => {
return (
<>
<div>
<input type="checkbox" id={`${modalId}`} className="modal-toggle" />
<label htmlFor={`${modalId}`} className="modal cursor-pointer">
<label className="modal-box relative">
{/* dummy input to capture event onclick on modal box */}
<input className="h-0 w-0 absolute top-0 left-0" />
<label htmlFor={`${modalId}`} className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3">
</label>
<div className="space-y-3 py-6">
<div className="flex flex-col items-center gap-6">
<QRCodeSVG value={address} size={256} />
<Address address={address} format="long" disableAddressLink />
</div>
</div>
</label>
</label>
</div>
</>
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent onOpenAutoFocus={e => e.preventDefault()}>
<DialogHeader>
<DialogTitle>Address QR Code</DialogTitle>
</DialogHeader>
<div className="flex flex-col items-center gap-6 py-6 break-all">
<QRCodeSVG value={address} size={256} />
<Address address={address} format="long" disableAddressLink />
</div>
</DialogContent>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useTheme } from "next-themes";
import { useAccount, useSwitchChain } from "wagmi";
import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid";
import { Button } from "~~/components/ui/button";
import { DropdownMenuItem } from "~~/components/ui/dropdown-menu";
import { getNetworkColor } from "~~/hooks/scaffold-eth";
import { getTargetNetworks } from "~~/utils/scaffold-eth";

Expand All @@ -21,15 +23,16 @@ export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => {
{allowedNetworks
.filter(allowedNetwork => allowedNetwork.id !== chain?.id)
.map(allowedNetwork => (
<li key={allowedNetwork.id} className={hidden ? "hidden" : ""}>
<button
className="menu-item btn-sm !rounded-xl flex gap-3 py-3 whitespace-nowrap"
type="button"
<DropdownMenuItem asChild key={allowedNetwork.id} className={hidden ? "hidden" : ""}>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={() => {
switchChain?.({ chainId: allowedNetwork.id });
}}
>
<ArrowsRightLeftIcon className="h-6 w-4 ml-2 sm:ml-0" />
<ArrowsRightLeftIcon className="h-4 w-4 mr-2" />
<span>
Switch to{" "}
<span
Expand All @@ -40,8 +43,8 @@ export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => {
{allowedNetwork.name}
</span>
</span>
</button>
</li>
</Button>
</DropdownMenuItem>
))}
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import { NetworkOptions } from "./NetworkOptions";
import { ChevronDown } from "lucide-react";
import { useDisconnect } from "wagmi";
import { ArrowLeftOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
import { ArrowLeftOnRectangleIcon } from "@heroicons/react/24/outline";
import { Button } from "~~/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "~~/components/ui/dropdown-menu";

export const WrongNetworkDropdown = () => {
const { disconnect } = useDisconnect();

return (
<div className="dropdown dropdown-end mr-2">
<label tabIndex={0} className="btn btn-error btn-sm dropdown-toggle gap-1">
<span>Wrong network</span>
<ChevronDownIcon className="h-6 w-4 ml-2 sm:ml-0" />
</label>
<ul
tabIndex={0}
className="dropdown-content menu p-2 mt-1 shadow-center shadow-accent bg-base-200 rounded-box gap-1"
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="destructive" size="sm">
Wrong network
<ChevronDown className="h-4 w-4 ml-2" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<NetworkOptions />
<li>
<button
className="menu-item text-error btn-sm !rounded-xl flex gap-3 py-3"
type="button"
onClick={() => disconnect()}
>
<ArrowLeftOnRectangleIcon className="h-6 w-4 ml-2 sm:ml-0" />
<DropdownMenuItem asChild>
<Button variant="ghost" size="sm" className="w-full justify-start" onClick={() => disconnect()}>
<ArrowLeftOnRectangleIcon className="mr-2 h-4 w-4" />
<span>Disconnect</span>
</button>
</li>
</ul>
</div>
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AddressQRCodeModal } from "./AddressQRCodeModal";
import { WrongNetworkDropdown } from "./WrongNetworkDropdown";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { Address } from "viem";
import { Button } from "~~/components/ui/button";
import { useNetworkColor } from "~~/hooks/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth";
Expand All @@ -31,9 +32,9 @@ export const RainbowKitCustomConnectButton = () => {
{(() => {
if (!connected) {
return (
<button className="btn btn-primary btn-sm" onClick={openConnectModal} type="button">
<Button variant="default" size="sm" onClick={openConnectModal}>
Connect Wallet
</button>
</Button>
);
}

Expand All @@ -55,7 +56,7 @@ export const RainbowKitCustomConnectButton = () => {
ensAvatar={account.ensAvatar}
blockExplorerAddressLink={blockExplorerAddressLink}
/>
<AddressQRCodeModal address={account.address as Address} modalId="qrcode-modal" />
<AddressQRCodeModal address={account.address as Address}>QR</AddressQRCodeModal>
</>
);
})()}
Expand Down
Loading

0 comments on commit f1e5c1b

Please sign in to comment.