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: add network selection component #7

Merged
merged 1 commit into from
Dec 13, 2023
Merged
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
21 changes: 21 additions & 0 deletions apps/www/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ export const Index: Record<string, any> = {
component: React.lazy(() => import("@/registry/default/buidl/error-message")),
files: ["registry/default/buidl/error-message.tsx"],
},
"network-selection": {
name: "network-selection",
type: "components:buidl",
registryDependencies: ["dropdown-menu"],
component: React.lazy(() => import("@/registry/default/buidl/network-selection")),
files: ["registry/default/buidl/network-selection.tsx"],
},
"button": {
name: "button",
type: "components:ui",
Expand Down Expand Up @@ -285,6 +292,13 @@ export const Index: Record<string, any> = {
component: React.lazy(() => import("@/registry/default/ui/skeleton")),
files: ["registry/default/ui/skeleton.tsx"],
},
"dropdown-menu": {
name: "dropdown-menu",
type: "components:ui",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/ui/dropdown-menu")),
files: ["registry/default/ui/dropdown-menu.tsx"],
},
"nonce-demo": {
name: "nonce-demo",
type: "components:example",
Expand Down Expand Up @@ -516,5 +530,12 @@ export const Index: Record<string, any> = {
component: React.lazy(() => import("@/registry/default/example/card-with-form")),
files: ["registry/default/example/card-with-form.tsx"],
},
"network-selection-demo": {
name: "network-selection-demo",
type: "components:example",
registryDependencies: ["network-selection"],
component: React.lazy(() => import("@/registry/default/example/network-selection-demo")),
files: ["registry/default/example/network-selection-demo.tsx"],
},
},
}
5 changes: 5 additions & 0 deletions apps/www/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/is-wallet-disconnected",
items: [],
},
{
title: "Network Selection",
href: "/docs/components/network-selection",
items: [],
},
],
},
{
Expand Down
58 changes: 58 additions & 0 deletions apps/www/content/docs/components/network-selection.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Network Selection
description: Select a network to connect to.
component: true
wagmi:
link: https://wagmi.sh/react/hooks/useSwitchNetwork
---

<ComponentPreview
name="network-selection-demo"
className="[&_.preview>[data-orientation=vertical]]:sm:max-w-[70%]"
/>

## Installation

<Tabs defaultValue="cli">

<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>

<TabsContent value="cli">

```bash
npx buidl-cli@latest add network-selection
```

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Install the following shadcn/ui components:</Step>

- [Button](https://ui.shadcn.com/docs/components/button)
- [DropdownMenu](https://ui.shadcn.com/docs/components/dropdown-menu)

<Step>Copy and paste the following code into your project.</Step>

<ComponentSource name="network-selection" />

</Steps>

</TabsContent>

</Tabs>

## Usage

```tsx
import { NetworkSelection } from "@/registry/default/buidl/network-selection"
```

```tsx
<NetworkSelection />
```
20 changes: 20 additions & 0 deletions apps/www/public/registry/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,19 @@
],
"type": "components:buidl"
},
{
"name": "network-selection",
"dependencies": [
"wagmi"
],
"registryDependencies": [
"dropdown-menu"
],
"files": [
"buidl/network-selection.tsx"
],
"type": "components:buidl"
},
{
"name": "button",
"dependencies": [
Expand Down Expand Up @@ -504,5 +517,12 @@
"ui/skeleton.tsx"
],
"type": "components:ui"
},
{
"name": "dropdown-menu",
"files": [
"ui/dropdown-menu.tsx"
],
"type": "components:ui"
}
]
3 changes: 1 addition & 2 deletions apps/www/public/registry/styles/default/dropdown-menu.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"name": "dropdown-menu",
"dependencies": ["@radix-ui/react-dropdown-menu"],
"files": [
{
"name": "dropdown-menu.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst DropdownMenu = DropdownMenuPrimitive.Root\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent\",\n inset && \"pl-8\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronRight className=\"ml-auto h-4 w-4\" />\n </DropdownMenuPrimitive.SubTrigger>\n))\nDropdownMenuSubTrigger.displayName =\n DropdownMenuPrimitive.SubTrigger.displayName\n\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n className={cn(\n \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n className\n )}\n {...props}\n />\n))\nDropdownMenuSubContent.displayName =\n DropdownMenuPrimitive.SubContent.displayName\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n))\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n inset && \"pl-8\",\n className\n )}\n {...props}\n />\n))\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Check className=\"h-4 w-4\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n))\nDropdownMenuCheckboxItem.displayName =\n DropdownMenuPrimitive.CheckboxItem.displayName\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Circle className=\"h-2 w-2 fill-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n))\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\n \"px-2 py-1.5 text-sm font-semibold\",\n inset && \"pl-8\",\n className\n )}\n {...props}\n />\n))\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\nconst DropdownMenuShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n return (\n <span\n className={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)}\n {...props}\n />\n )\n}\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\"\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuGroup,\n DropdownMenuPortal,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuRadioGroup,\n}\n"
}
],
"type": "components:ui"
}
}
16 changes: 16 additions & 0 deletions apps/www/public/registry/styles/default/network-selection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "network-selection",
"dependencies": [
"wagmi"
],
"registryDependencies": [
"dropdown-menu"
],
"files": [
{
"name": "network-selection.tsx",
"content": "import { useState, type HTMLAttributes } from \"react\"\nimport { ChevronDown } from \"lucide-react\"\nimport { Chain, useNetwork, useSwitchNetwork } from \"wagmi\"\nimport {\n arbitrum,\n base,\n gnosis,\n mainnet,\n optimism,\n polygon,\n} from \"wagmi/chains\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\n\nconst defaultChains = [arbitrum, base, gnosis, mainnet, optimism, polygon]\n\ninterface NetworkSelectionProps extends HTMLAttributes<HTMLElement> {\n chainId?: number\n useCurrentNetwork?: boolean\n initialChainId?: number\n selectNetworkLabel?: string\n chains?: Chain[]\n onValueChange?: (chainId: number) => void\n}\n\nexport const NetworkSelection = ({\n className,\n selectNetworkLabel = \"Select Network\",\n useCurrentNetwork = true,\n chains = defaultChains,\n initialChainId,\n onValueChange,\n ...props\n}: NetworkSelectionProps) => {\n const [selectedChain, setSelectedChain] = useState<Chain>(\n chains.find((chain) => chain.id === initialChainId) || chains[0]\n )\n const { chain } = useNetwork()\n const { switchNetwork } = useSwitchNetwork()\n\n const handleSwitchNetwork = (chain: Chain) => {\n if (useCurrentNetwork) {\n switchNetwork?.(chain.id)\n } else {\n setSelectedChain(chain)\n }\n onValueChange?.(chain.id)\n }\n\n return (\n <div className={cn(className)} {...props}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button className=\"gap-x-2\">\n {useCurrentNetwork ? (\n chain ? (\n <span className=\"text-sm font-semibold\">{chain?.name}</span>\n ) : (\n <> {selectNetworkLabel}</>\n )\n ) : selectedChain ? (\n <span className=\"text-sm font-semibold\">\n {selectedChain?.name}\n </span>\n ) : (\n <>{selectNetworkLabel}</>\n )}\n <ChevronDown className=\"text-gray-400\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuLabel>{selectNetworkLabel}</DropdownMenuLabel>\n <DropdownMenuSeparator className=\"bg-neutral-200\" />\n {chains.length > 0 &&\n chains.map((chain) => (\n <DropdownMenuItem\n key={chain.id}\n className=\"flex gap-x-2 focus:bg-neutral-300/80 dark:focus:bg-neutral-800\"\n onClick={() => handleSwitchNetwork(chain)}\n >\n <span>\n {chain.name} (<span className=\"text-xs\">{chain.id}</span>)\n </span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n )\n}\n"
}
],
"type": "components:buidl"
}
99 changes: 99 additions & 0 deletions apps/www/registry/default/buidl/network-selection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState, type HTMLAttributes } from "react"
import { ChevronDown } from "lucide-react"
import { Chain, useNetwork, useSwitchNetwork } from "wagmi"
import {
arbitrum,
base,
gnosis,
mainnet,
optimism,
polygon,
} from "wagmi/chains"

import { cn } from "@/lib/utils"
import { Button } from "@/registry/default/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/default/ui/dropdown-menu"

const defaultChains = [arbitrum, base, gnosis, mainnet, optimism, polygon]

interface NetworkSelectionProps extends HTMLAttributes<HTMLElement> {
chainId?: number
useCurrentNetwork?: boolean
initialChainId?: number
selectNetworkLabel?: string
chains?: Chain[]
onValueChange?: (chainId: number) => void
}

export const NetworkSelection = ({
className,
selectNetworkLabel = "Select Network",
useCurrentNetwork = true,
chains = defaultChains,
initialChainId,
onValueChange,
...props
}: NetworkSelectionProps) => {
const [selectedChain, setSelectedChain] = useState<Chain>(
chains.find((chain) => chain.id === initialChainId) || chains[0]
)
const { chain } = useNetwork()
const { switchNetwork } = useSwitchNetwork()

const handleSwitchNetwork = (chain: Chain) => {
if (useCurrentNetwork) {
switchNetwork?.(chain.id)
} else {
setSelectedChain(chain)
}
onValueChange?.(chain.id)
}

return (
<div className={cn(className)} {...props}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-x-2">
{useCurrentNetwork ? (
chain ? (
<span className="text-sm font-semibold">{chain?.name}</span>
) : (
<> {selectNetworkLabel}</>
)
) : selectedChain ? (
<span className="text-sm font-semibold">
{selectedChain?.name}
</span>
) : (
<>{selectNetworkLabel}</>
)}
<ChevronDown className="text-gray-400" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>{selectNetworkLabel}</DropdownMenuLabel>
<DropdownMenuSeparator className="bg-neutral-200" />
{chains.length > 0 &&
chains.map((chain) => (
<DropdownMenuItem
key={chain.id}
className="flex gap-x-2 focus:bg-neutral-300/80 dark:focus:bg-neutral-800"
onClick={() => handleSwitchNetwork(chain)}
>
<span>
{chain.name} (<span className="text-xs">{chain.id}</span>)
</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}
18 changes: 18 additions & 0 deletions apps/www/registry/default/example/network-selection-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IsWalletConnected } from "@/registry/default/buidl/is-wallet-connected"
import { IsWalletDisconnected } from "@/registry/default/buidl/is-wallet-disconnected"
import { WalletConnect } from "@/registry/default/buidl/wallet-connect"

import { NetworkSelection } from "../buidl/network-selection"

export default function NetworkSelectionDemo() {
return (
<div className="flex flex-col items-center gap-4 text-center">
<IsWalletConnected>
<NetworkSelection />
</IsWalletConnected>
<IsWalletDisconnected>
<WalletConnect />
</IsWalletDisconnected>
</div>
)
}
18 changes: 18 additions & 0 deletions apps/www/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ const ui: Registry = [
type: "components:ui",
files: ["ui/skeleton.tsx"],
},
{
name: "dropdown-menu",
type: "components:ui",
files: ["ui/dropdown-menu.tsx"],
},
]

const buidl: Registry = [
Expand Down Expand Up @@ -279,6 +284,13 @@ const buidl: Registry = [
dependencies: ["wagmi"],
files: ["buidl/error-message.tsx"],
},
{
name: "network-selection",
type: "components:buidl",
dependencies: ["wagmi"],
registryDependencies: ["dropdown-menu"],
files: ["buidl/network-selection.tsx"],
},
]

const example: Registry = [
Expand Down Expand Up @@ -480,6 +492,12 @@ const example: Registry = [
registryDependencies: ["button", "card", "input", "label", "select"],
files: ["example/card-with-form.tsx"],
},
{
name: "network-selection-demo",
type: "components:example",
registryDependencies: ["button", "network-selection"],
files: ["example/network-selection-demo.tsx"],
},
]

export const registry: Registry = [...buidl, ...ui, ...example]