Skip to content

Commit

Permalink
feat: add network selection
Browse files Browse the repository at this point in the history
  • Loading branch information
marthendalnunes committed Dec 12, 2023
1 parent b380069 commit f76cbe7
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 2 deletions.
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]

0 comments on commit f76cbe7

Please sign in to comment.