Playground: Revamp (#7768)

This commit is contained in:
Manan Tank
2025-07-31 15:37:13 -07:00
committed by GitHub
parent 58787a28f3
commit 2df891f380
99 changed files with 4048 additions and 1639 deletions
+21
View File
@@ -0,0 +1,21 @@
export function TokenIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
role="presentation"
className={props.className}
>
<path
d="M15.9377 15.9377C19.3603 15.4795 22 12.548 22 9C22 5.13401 18.866 2 15 2C11.452 2 8.52049 4.63967 8.06227 8.06227M16 15C16 18.866 12.866 22 9 22C5.13401 22 2 18.866 2 15C2 11.134 5.13401 8 9 8C12.866 8 16 11.134 16 15Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
export function WalletProductIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
role="presentation"
className={props.className}
>
<path
d="M16 7.99995V4.5006C16 3.66887 16 3.253 15.8248 2.99742C15.6717 2.77413 15.4346 2.62245 15.1678 2.57703C14.8623 2.52505 14.4847 2.69932 13.7295 3.04787L4.85901 7.14194C4.18551 7.45279 3.84875 7.60822 3.60211 7.84927C3.38406 8.06237 3.21762 8.3225 3.1155 8.60978C3 8.93474 3 9.30563 3 10.0474V14.9999M16.5 14.4999H16.51M3 11.2L3 17.7999C3 18.9201 3 19.4801 3.21799 19.9079C3.40973 20.2843 3.71569 20.5902 4.09202 20.782C4.51984 20.9999 5.07989 20.9999 6.2 20.9999H17.8C18.9201 20.9999 19.4802 21 19.908 20.782C20.2843 20.5902 20.5903 20.2843 20.782 19.9079C21 19.4801 21 18.9201 21 17.7999V11.2C21 10.0798 21 9.51979 20.782 9.09197C20.5903 8.71565 20.2843 8.40969 19.908 8.21794C19.4802 7.99995 18.9201 7.99995 17.8 7.99995L6.2 7.99995C5.0799 7.99995 4.51984 7.99995 4.09202 8.21794C3.7157 8.40968 3.40973 8.71564 3.21799 9.09197C3 9.51979 3 10.0798 3 11.2ZM17 14.4999C17 14.7761 16.7761 14.9999 16.5 14.9999C16.2239 14.9999 16 14.7761 16 14.4999C16 14.2238 16.2239 13.9999 16.5 13.9999C16.7761 13.9999 17 14.2238 17 14.4999Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
@@ -1,5 +1,6 @@
"use client";
import {
AtomIcon,
BookTextIcon,
BoxIcon,
ChartNoAxesColumnIcon,
@@ -51,7 +52,7 @@ export function TeamSidebarLayout(props: {
},
],
subMenu: {
icon: WalletCardsIcon,
icon: AtomIcon,
label: "Ecosystems",
},
},
@@ -4,12 +4,10 @@ import {
BellIcon,
BookTextIcon,
BoxIcon,
CoinsIcon,
HomeIcon,
LockIcon,
RssIcon,
SettingsIcon,
WalletIcon,
} from "lucide-react";
import { FullWidthSidebarLayout } from "@/components/blocks/full-width-sidebar-layout";
import { Badge } from "@/components/ui/badge";
@@ -17,6 +15,8 @@ import { ContractIcon } from "@/icons/ContractIcon";
import { InsightIcon } from "@/icons/InsightIcon";
import { PayIcon } from "@/icons/PayIcon";
import { SmartAccountIcon } from "@/icons/SmartAccountIcon";
import { TokenIcon } from "@/icons/TokenIcon";
import { WalletProductIcon } from "@/icons/WalletProductIcon";
export function ProjectSidebarLayout(props: {
layoutPath: string;
@@ -48,7 +48,7 @@ export function ProjectSidebarLayout(props: {
links: [
{
href: `${layoutPath}/wallets`,
icon: WalletIcon,
icon: WalletProductIcon,
label: "Wallets",
},
{
@@ -85,7 +85,7 @@ export function ProjectSidebarLayout(props: {
},
{
href: `${layoutPath}/tokens`,
icon: CoinsIcon,
icon: TokenIcon,
label: (
<span className="flex items-center gap-2">
Tokens <Badge>New</Badge>
+23 -3
View File
@@ -113,17 +113,37 @@ const nextConfig = {
},
{
source: "/wallets/account-abstraction/sponsor",
destination: "/wallets/account-abstraction/eip-4337",
destination: "/account-abstraction/eip-4337",
permanent: false,
},
{
source: "/wallets/account-abstraction/7702",
destination: "/wallets/account-abstraction/eip-7702",
destination: "/account-abstraction/eip-7702",
permanent: false,
},
{
source: "/wallets/account-abstraction/5792",
destination: "/wallets/account-abstraction/eip-5792",
destination: "/account-abstraction/eip-5792",
permanent: false,
},
{
source: "/wallets/account-abstraction/native-aa",
destination: "/account-abstraction/native-aa",
permanent: false,
},
{
source: "/wallets/headless/token-components",
destination: "/tokens/token-components",
permanent: false,
},
{
source: "/wallets/headless/nft-components",
destination: "/tokens/nft-components",
permanent: false,
},
{
source: "/wallets/in-app-wallet/ecosystem",
destination: "/wallets/ecosystem-wallet",
permanent: false,
},
];
+2
View File
@@ -3,6 +3,8 @@
"@abstract-foundation/agw-react": "^1.6.4",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "1.1.14",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-progress": "^1.1.7",
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,96 @@
<svg width="510" height="510" viewBox="0 0 510 510" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M423.472 360.235L356.633 387.064V308.174L423.472 281.345V360.235Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M422.767 280.725L356.214 307.438L289.662 280.725L356.214 254.011L422.767 280.725Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M355.795 308.173V386.238L289.585 281.597L355.795 308.173Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M355.166 386.812L288.957 360.235V282.171L355.166 386.812Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M423.891 280.725V360.52L356.214 387.685L288.538 360.52V280.725L356.214 253.56L423.891 280.725Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M423.891 280.726L356.214 307.891" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M356.214 387.685V307.891" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M356.214 307.891L288.538 280.726" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M423.472 253.272L356.633 280.101V201.211L423.472 174.382V253.272Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M422.767 173.762L356.214 200.475L289.662 173.762L356.214 147.048L422.767 173.762Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M355.795 201.21V279.275L289.585 174.634L355.795 201.21Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M355.166 279.849L288.957 253.272V175.208L355.166 279.849Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M423.891 173.762V253.557L356.214 280.722L288.538 253.557V173.762L356.214 146.597L423.891 173.762Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M423.891 173.762L356.214 200.928" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M356.214 280.722V200.928" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M356.214 200.928L288.538 173.762" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M423.472 146.314L356.633 173.143V94.2525L423.472 67.4234V146.314Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M422.767 66.8043L356.214 93.5172L289.662 66.8043L356.214 40.0905L422.767 66.8043Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M355.795 94.2516V172.317L289.585 67.6754L355.795 94.2516Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M355.166 172.891L288.957 146.314V68.2496L355.166 172.891Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M423.891 66.8044V146.599L356.214 173.764L288.538 146.599V66.8044L356.214 39.6393L423.891 66.8044Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M423.891 66.8043L356.214 93.9694" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M356.214 173.764V93.9694" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M356.214 93.9694L288.538 66.8043" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M321.958 400.132L255.119 426.961V348.07L321.958 321.241V400.132Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M321.252 320.622L254.7 347.335L188.148 320.622L254.7 293.908L321.252 320.622Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M254.281 348.069V426.135L188.071 321.493L254.281 348.069Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M253.652 426.709L187.443 400.132V322.067L253.652 426.709Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M322.377 320.622V400.417L254.7 427.582L187.024 400.417V320.622L254.7 293.457L322.377 320.622Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M322.377 320.622L254.7 347.787" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M254.7 427.583V347.788" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M254.7 347.787L187.024 320.622" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M321.958 294.259L255.119 321.088V242.198L321.958 215.368V294.259Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M321.252 214.749L254.7 241.462L188.148 214.749L254.7 188.035L321.252 214.749Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M254.281 242.197V320.262L188.071 215.62L254.281 242.197Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M253.652 320.836L187.443 294.259V216.195L253.652 320.836Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M322.377 214.749V294.544L254.7 321.709L187.024 294.544V214.749L254.7 187.584L322.377 214.749Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M322.377 214.749L254.7 241.914" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M254.7 321.709V241.914" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M254.7 241.914L187.024 214.749" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M324.131 187.298L257.292 214.127V135.236L324.131 108.407V187.298Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M323.425 107.788L256.873 134.501L190.321 107.788L256.873 81.074L323.425 107.788Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M256.454 135.235V213.301L190.244 108.659L256.454 135.235Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M255.825 213.875L189.616 187.298V109.233L255.825 213.875Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M324.55 107.788V187.582L256.873 214.747L189.197 187.582V107.788L256.873 80.6228L324.55 107.788Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M324.55 107.788L256.873 134.953" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M256.873 214.748V134.953" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M256.873 134.953L189.197 107.788" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M220.507 442.377L153.668 469.206V390.315L220.507 363.486V442.377Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M219.802 362.867L153.25 389.58L86.6978 362.867L153.25 336.153L219.802 362.867Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M152.831 390.314V468.38L86.6206 363.738L152.831 390.314Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M152.202 468.954L85.9927 442.377V364.312L152.202 468.954Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M220.927 362.867V442.661L153.25 469.827L85.5737 442.661V362.867L153.25 335.702L220.927 362.867Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M220.926 362.867L153.25 390.032" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M153.25 469.827V390.032" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M153.25 390.032L85.5737 362.867" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M220.508 335.415L153.669 362.244V283.354L220.508 256.525V335.415Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M219.802 255.906L153.25 282.618L86.6978 255.906L153.25 229.192L219.802 255.906Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M152.831 283.353V361.418L86.6206 256.777L152.831 283.353Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M152.202 361.992L85.9927 335.415V257.351L152.202 361.992Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M220.927 255.906V335.7L153.25 362.865L85.5737 335.7V255.906L153.25 228.74L220.927 255.906Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M220.927 255.906L153.25 283.071" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M153.25 362.865V283.071" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M153.25 283.071L85.5737 255.906" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M220.508 228.456L153.669 255.285V176.394L220.508 149.565V228.456Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M219.802 148.946L153.25 175.659L86.6978 148.946L153.25 122.232L219.802 148.946Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M152.831 176.393V254.458L86.6206 149.817L152.831 176.393Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M152.202 255.033L85.9927 228.456V150.391L152.202 255.033Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937"/>
<path d="M220.927 148.946V228.74L153.25 255.905L85.5737 228.74V148.946L153.25 121.781L220.927 148.946Z" fill="black" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M220.927 148.946L153.25 176.111" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M153.25 255.906V176.111" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M153.25 176.111L85.5737 148.946" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<g style="mix-blend-mode:plus-lighter" opacity="0.8">
<ellipse cx="190.193" cy="188.694" rx="69.2605" ry="106.046" transform="rotate(8.23956 190.193 188.694)" fill="#490631"/>
</g>
<g clip-path="url(#clip0_5423_1241)">
<path d="M315.687 364.883L261.307 276.347C259.839 273.965 257.136 272.824 254.376 273.168L245.796 259.199L235.001 272.382L241.326 282.668L241.048 282.877C240.281 284.976 240.393 287.32 241.581 289.247L295.96 377.783C299.09 382.877 306.045 384.129 311.5 380.57C316.944 377.01 318.828 369.989 315.699 364.883H315.687Z" fill="black"/>
<rect width="43.6018" height="4.6553" transform="matrix(0.955689 -0.387444 -0.0218247 0.993867 166.765 287.5)" fill="#F213A4"/>
<path d="M206.796 82.6179C203.403 82.6179 200.067 82.9493 196.795 83.5262C193.701 82.9493 190.522 82.6179 187.241 82.6179C144.356 82.6179 110.562 130.096 105.356 188.67C100.149 247.244 120.629 294.722 168.387 294.722C171.57 294.722 174.693 294.427 177.773 293.924C181.021 294.439 184.401 294.722 187.954 294.722C230.765 294.722 264.632 247.244 269.839 188.67C275.046 130.096 248.225 82.6179 206.796 82.6179ZM192.082 289.64C190.853 289.726 189.603 289.775 188.357 289.775C143.88 289.775 124.907 244.507 129.869 188.682C134.832 132.857 166.394 87.5768 206.33 87.5768C207.391 87.5768 208.435 87.6259 209.479 87.6872C237.822 101.386 254.472 141.413 250.272 188.67C246.071 235.927 223.233 275.917 192.082 289.64Z" fill="black"/>
<path d="M254.376 273.168C257.124 272.824 259.839 273.965 261.307 276.347L315.687 364.883C318.816 369.977 316.944 377.01 311.488 380.57C306.045 384.129 299.077 382.889 295.948 377.783L241.569 289.247C240.381 287.32 240.267 284.988 241.036 282.877" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M245.796 259.199L255.013 274.199C257.149 277.685 255.87 282.484 252.145 284.914C248.421 287.345 243.666 286.498 241.53 283.012L235.001 272.382" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M269.805 188.67C264.598 247.244 230.693 294.722 187.826 294.722C140.006 294.722 119.495 247.244 124.701 188.67C129.908 130.096 163.752 82.6179 206.68 82.6179C249.608 82.6179 275.012 130.096 269.805 188.67Z" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M168.246 294.722C120.426 294.722 99.9144 247.244 105.121 188.67C110.328 130.096 144.159 82.6179 187.1 82.6179" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M209.381 87.6755C237.761 101.386 254.426 141.413 250.225 188.67C246.024 235.927 223.028 276.163 191.713 289.775" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M264.789 188.67C259.826 244.507 228.156 289.763 188.242 289.763C143.708 289.763 124.705 244.494 129.667 188.67C134.629 132.845 166.226 87.5768 206.214 87.5768C246.202 87.5768 269.751 132.845 264.789 188.67Z" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M168.246 294.722H189.58" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M187.1 82.6179H208.434" stroke="#A3A3A3" stroke-width="0.837937" stroke-miterlimit="10"/>
<path d="M183.882 227.367L160.043 237.032L160.145 232.404L183.983 222.74L183.882 227.367ZM208.333 206.919L160.043 226.496L160.145 221.869L208.435 202.292L208.333 206.919ZM208.334 196.137L160.042 215.715L160.144 211.088L208.435 191.511L208.334 196.137ZM192.454 191.882L160.043 205.021L160.145 200.394L192.556 187.254L192.454 191.882ZM208.333 174.907L160.043 194.485L160.145 189.858L208.435 170.281L208.333 174.907Z" fill="#F213A4"/>
</g>
<defs>
<clipPath id="clip0_5423_1241">
<rect width="229.503" height="302.392" fill="white" transform="matrix(1 0 -0.0885398 0.996073 113.984 82.0041)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 46 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 380 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 77 KiB

+34 -41
View File
@@ -1,45 +1,38 @@
import Image from "next/image";
import Link from "next/link";
import thirdwebIconSrc from "@/../public/thirdweb.svg";
import { Sidebar, type SidebarLink } from "@/components/ui/sidebar";
import { ScrollShadow } from "../components/ui/ScrollShadow/ScrollShadow";
import { otherLinks } from "./otherLinks";
"use client";
import { BookTextIcon, GithubIcon } from "lucide-react";
import { SidebarProvider } from "@/components/ui/sidebar";
import { FullWidthSidebarLayout } from "../components/blocks/full-width-sidebar-layout";
import { DashboardIcon } from "../icons/DashboardIcon";
import { sidebarLinks } from "./navLinks";
export function AppSidebar(props: { links: SidebarLink[] }) {
export function AppSidebarLayout(props: { children: React.ReactNode }) {
return (
<div className="z-10 hidden h-dvh w-[300px] flex-col border-border/50 border-r-2 xl:flex">
<div className="border-b px-6 py-5">
<div className="flex items-center gap-2">
<Image alt="thirdweb" className="size-6" src={thirdwebIconSrc} />
<span className="font-bold text-lg leading-none tracking-tight">
Playground
</span>
</div>
</div>
<div className="relative flex max-h-full flex-1 flex-col overflow-hidden">
<ScrollShadow
className="grow pr-4 pl-6"
scrollableClassName="max-h-full pt-6"
>
<Sidebar links={props.links} />
</ScrollShadow>
</div>
<div className="mt-auto flex flex-col gap-4 border-t px-6 py-6">
{otherLinks.map((link) => {
return (
<Link
className="flex items-center gap-2 text-muted-foreground hover:text-foreground "
href={link.href}
key={link.href}
target="_blank"
>
{link.name}
</Link>
);
})}
</div>
</div>
<SidebarProvider>
<FullWidthSidebarLayout
contentSidebarLinks={sidebarLinks}
footerSidebarLinks={[
{
separator: true,
},
{
href: "https://thirdweb.com/team?utm_source=playground",
label: "Dashboard",
icon: DashboardIcon,
},
{
href: "https://portal.thirdweb.com?utm_source=playground",
label: "Documentation",
icon: BookTextIcon,
},
{
href: "https://github.com/thirdweb-dev",
label: "Github",
icon: GithubIcon,
},
]}
>
{props.children}
</FullWidthSidebarLayout>
</SidebarProvider>
);
}
@@ -1,93 +0,0 @@
"use client";
import { MenuIcon, XIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
import thirdwebIconSrc from "@/../public/thirdweb.svg";
import { Button } from "../components/ui/button";
import { ScrollShadow } from "../components/ui/ScrollShadow/ScrollShadow";
import { Sidebar, type SidebarLink } from "../components/ui/sidebar";
import { otherLinks } from "./otherLinks";
export function MobileHeader(props: { links: SidebarLink[] }) {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "auto";
}
return () => {
document.body.style.overflow = "auto";
};
}, [isOpen]);
return (
<>
<header className="sticky top-0 z-10 flex justify-between gap-4 border-b bg-background px-4 py-4 xl:hidden">
<Link
aria-label="thirdweb Docs"
className="flex items-center gap-3"
href="/"
title="thirdweb Docs"
>
<Image alt="" className="size-7" src={thirdwebIconSrc} />
<span className="font-bold text-xl leading-none tracking-tight">
Playground
</span>
</Link>
<Button
className="!h-auto p-2"
onClick={() => {
setIsOpen((v) => !v);
}}
variant="outline"
>
{!isOpen ? (
<MenuIcon className="size-6" />
) : (
<XIcon className="size-6" />
)}
</Button>
</header>
{isOpen && (
<div
className="fade-in-0 slide-in-from-top-5 fixed top-[75px] right-0 bottom-0 left-0 z-50 flex animate-in flex-col bg-background duration-200"
onClickCapture={(e) => {
if (e.target instanceof HTMLElement && e.target.closest("a")) {
setIsOpen(false);
}
}}
>
<div className="relative flex max-h-full flex-1 flex-col overflow-hidden">
<ScrollShadow
className="grow px-6"
scrollableClassName="max-h-full pt-6"
>
<Sidebar links={props.links} />
</ScrollShadow>
</div>
<div className="mt-auto flex flex-col gap-4 border-t px-6 py-6">
{otherLinks.map((link) => {
return (
<Link
className="flex items-center gap-2 text-muted-foreground hover:text-foreground "
href={link.href}
key={link.href}
target="_blank"
>
{link.name}
</Link>
);
})}
</div>
</div>
)}
</>
);
}
@@ -1,27 +1,33 @@
import type { Metadata } from "next";
import { ShieldIcon } from "lucide-react";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { ConnectSmartAccountCustomPreview } from "../../../../components/account-abstraction/connect-smart-account";
import { SponsoredTxPreview } from "../../../../components/account-abstraction/sponsored-tx";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { CodeExample } from "../../../../components/code/code-example";
import { SponsoredInAppTxPreview } from "../../../../components/in-app-wallet/sponsored-tx";
import { createMetadata } from "@/lib/metadata";
import { ConnectSmartAccountCustomPreview } from "../../../components/account-abstraction/connect-smart-account";
import { SponsoredTxPreview } from "../../../components/account-abstraction/sponsored-tx";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { CodeExample } from "../../../components/code/code-example";
import { SponsoredInAppTxPreview } from "../../../components/in-app-wallet/sponsored-tx";
export const metadata: Metadata = {
description: "Turn any EOA into a smart contract wallet with EIP-4337.",
metadataBase,
title: "EIP-4337 Smart Contract Wallets | thirdweb Connect",
};
const title = "Account Abstraction EIP-4337";
const description =
"Enable account abstraction with EIP-4337. Unlock gasless transactions, session keys, paymaster support, and smart wallet programmability";
export const metadata = createMetadata({
description,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>Turn any EOA into a smart contract wallet with EIP-4337.</>
}
icon={ShieldIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="EIP-4337 Smart Contract Wallets"
>
<div className="flex flex-col gap-14">
<SponsoredTx />
@@ -1,30 +1,32 @@
import type { Metadata } from "next";
import { ShieldIcon } from "lucide-react";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { Eip5792GetCapabilitiesPreview } from "../../../../components/account-abstraction/5792-get-capabilities";
import { Eip5792SendCallsPreview } from "../../../../components/account-abstraction/5792-send-calls";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { CodeExample } from "../../../../components/code/code-example";
import { Eip5792GetCapabilitiesPreview } from "../../../components/account-abstraction/5792-get-capabilities";
import { Eip5792SendCallsPreview } from "../../../components/account-abstraction/5792-send-calls";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { CodeExample } from "../../../components/code/code-example";
import { createMetadata } from "../../../lib/metadata";
export const metadata: Metadata = {
description:
"EIP-5792 capabilities allow you to view the capabilities of the connected wallet",
metadataBase,
title: "EIP-5792 Wallet Capabilities | thirdweb Connect",
};
const title = "Account Abstraction EIP-5792";
const description =
"EIP-5792 brings a standard for abstracted actions—combine transaction batching, bundling, and paymasters with a unified smart account interface";
export const metadata = createMetadata({
description,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
EIP-5792 capabilities allow you to view the capabilities of the
connected wallet.
</>
}
icon={ShieldIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="EIP-5792 Wallet Capabilities"
>
<div className="flex flex-col gap-14">
<Eip5792GetCapabilities />
@@ -1,29 +1,33 @@
import type { Metadata } from "next";
import { ShieldIcon } from "lucide-react";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { Eip7702SmartAccountPreview } from "../../../../components/account-abstraction/7702-smart-account";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { CodeExample } from "../../../../components/code/code-example";
import { Eip7702SmartAccountPreview } from "../../../components/account-abstraction/7702-smart-account";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { CodeExample } from "../../../components/code/code-example";
import { createMetadata } from "../../../lib/metadata";
export const metadata: Metadata = {
description:
"EIP-7702 smart accounts allow you to turn your EOA into a smart account with no code changes",
metadataBase,
title: "EIP-7702 Smart Accounts | thirdweb Connect",
};
const title = "Account Abstraction EIP-7702";
const description =
"Enable account abstraction with EIP-7702. Unlock gasless transactions, session keys, paymaster support, and smart wallet programmability";
const ogDescription =
"Use EIP-7702 to upgrade EOAs into temporary smart accounts. Bring native account abstraction to any wallet, enabling gasless transactions and advanced logic.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
EIP-7702 smart accounts allow you to turn your EOA into a smart
account with no code changes.
</>
}
icon={ShieldIcon}
description={description}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="EIP-7702 Smart Accounts"
title={title}
>
<Eip7702SmartAccount />
</PageLayout>
@@ -1,29 +1,31 @@
import type { Metadata } from "next";
import { ShieldIcon } from "lucide-react";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { SponsoredTxZksyncPreview } from "../../../../components/account-abstraction/sponsored-tx-zksync";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { CodeExample } from "../../../../components/code/code-example";
import { createMetadata } from "@/lib/metadata";
import { SponsoredTxZksyncPreview } from "../../../components/account-abstraction/sponsored-tx-zksync";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { CodeExample } from "../../../components/code/code-example";
export const metadata: Metadata = {
description:
"On zkSync chains, you can take advantage of native account abstraction with no code changes",
metadataBase,
title: "Native Account Abstraction",
};
const title = "Native Account Abstraction Through zkSync";
const description =
"Leverage native account abstraction on zkSync. Use smart accounts out of the box for gasless transactions, batching, and flexible signing logic";
export const metadata = createMetadata({
description,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
On zkSync chains, you can take advantage of native account
abstraction with no code changes.
</>
}
icon={ShieldIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="Native Account Abstraction"
>
<SponsoredZksyncTx />
</PageLayout>
Binary file not shown.
@@ -0,0 +1,108 @@
/* eslint-disable @next/next/no-img-element */
import { ImageResponse } from "next/og";
import { getBaseUrl } from "@/lib/env";
export const runtime = "edge";
const BASE_URL = getBaseUrl();
const width = 1200;
const height = 630;
const iconSize = 400;
const inter600 = fetch(new URL("./inter/700.ttf", import.meta.url)).then(
(res) => res.arrayBuffer(),
);
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const icon = searchParams.get("icon");
const title = searchParams.get("title");
if (!icon || !title) {
return new Response("Failed to generate the image", {
status: 500,
});
}
const iconUrl = `${BASE_URL}/og/icons/${icon}.svg`;
return new ImageResponse(
// ImageResponse JSX element
<div
style={{
alignItems: "center",
background: "black",
display: "flex",
height: "100%",
width: "100%",
}}
>
<img
alt=""
height={height}
src={`${BASE_URL}/og/background-1.jpg`}
style={{
bottom: 0,
left: 0,
position: "absolute",
right: 0,
top: 0,
}}
width={width}
/>
{/* Left */}
<div
style={{
color: "white",
display: "flex",
flexDirection: "column",
padding: "80px 60px",
width: "50%",
}}
>
<div
style={{
display: "flex",
fontFamily: "Inter",
fontSize: "60px",
fontWeight: 600,
marginBottom: "40px",
wordBreak: title.includes(" ") ? "break-word" : "break-all",
}}
>
{title}
</div>
</div>
<img
alt=""
height={iconSize}
src={iconUrl}
style={{
display: "flex",
position: "absolute",
right: width / 20,
top: height / 2 - iconSize / 1.5,
}}
width={iconSize}
/>
</div>,
// ImageResponse options
{
fonts: [
{
data: await inter600,
name: "Inter",
style: "normal",
weight: 600,
},
],
height: height,
width: width,
},
);
}
@@ -0,0 +1,71 @@
import { RssIcon } from "lucide-react";
import { WatchEventPreview } from "@/components/blockchain-api/watch-event-preview";
import { CodeExample } from "@/components/code/code-example";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "../../../lib/metadata";
const title = "Listen Contract Events";
const description =
"Subscribe to any contract event with auto-polling hooks and type-safe event handlers. Supports all common standards out of the box";
const ogDescription =
"Listen to blockchain contract events using auto-polling hooks and type-safe functions. Supports ERC20, ERC721, and other common Web3 standards with minimal setup.";
export const metadata = createMetadata({
title,
description: ogDescription,
image: {
icon: "contract",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
icon={RssIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/typescript/v5?utm_source=playground"
>
<WatchEvent />
</PageLayout>
</ThirdwebProvider>
);
}
function WatchEvent() {
return (
<CodeExample
code={`import { useContractEvents } from "thirdweb/react";
import { getContract } from "thirdweb";
import { base } from "thirdweb/chains";
import { transferEvent } from "thirdweb/extensions/erc20";
const usdcContractOnBase = getContract({
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
chain: base,
client,
});
function App() {
// Listen to USDC transfers on Base
const contractEvents = useContractEvents({
contract: usdcContractOnBase,
events: [transferEvent()],
blockRange: 100,
});
(contractEvents.data || []).forEach((item) => {
const { from, to, value } = item.args;
console.log("{from}...{value} USDC...{to}");
});
}
`}
lang="tsx"
preview={<WatchEventPreview />}
/>
);
}
@@ -0,0 +1,117 @@
import { BlocksIcon } from "lucide-react";
import { ReadContractExtensionPreview } from "@/components/blockchain-api/read-contract-extension";
import { WriteContractExtensionPreview } from "@/components/blockchain-api/write-contract-extension";
import { CodeExample } from "@/components/code/code-example";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "../../../lib/metadata";
const title = "Pre-Built Extensions";
const description =
"High-level ready and write functions with built-in pre/post-processing for common standards.";
const ogDescription =
"Simplify smart contract reads and writes with prebuilt extensions. Handle approvals, formatting, and standards with built-in pre/post-processing.";
export const metadata = createMetadata({
title,
description: ogDescription,
image: {
icon: "contract",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
icon={BlocksIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/typescript/v5?utm_source=playground"
>
<div className="flex flex-col gap-14">
<ReadContractExtension />
<WriteContractExtension />
</div>
</PageLayout>
</ThirdwebProvider>
);
}
function ReadContractExtension() {
return (
<CodeExample
code={`import { getContract } from "thirdweb";
import { ethereum } from "thirdweb/chains";
import { MediaRenderer, useReadContract } from "thirdweb/react";
const nftContract = getContract({
address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d",
chain: ethereum,
client: THIRDWEB_CLIENT,
});
function App() {
const { data } = useReadContract(getNFT, {
contract: nftContract,
tokenId: 458n,
});
return (
<MediaRenderer
client={THIRDWEB_CLIENT}
src={data?.metadata.image}
/>
);
}
`}
header={{
description:
"Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.",
title: "Prebuilt read extensions",
}}
lang="tsx"
preview={<ReadContractExtensionPreview />}
/>
);
}
function WriteContractExtension() {
return (
<CodeExample
code={`import { getContract } from "thirdweb";
import { sepolia } from "thirdweb/chains";
import { claimTo } from "thirdweb/extensions/erc20";
const tw_coin = getContract({
address: "0xACf072b740a23D48ECd302C9052fbeb3813b60a6",
chain: sepolia,
client: THIRDWEB_CLIENT,
});
function App() {
return <TransactionButton
transaction={() =>
claimTo({
contract: twCoinContract,
to: account.address,
quantity: "10",
})
}
>
Claim
</TransactionButton>
}
`}
header={{
description:
"Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.",
title: "Prebuilt write extensions",
}}
lang="tsx"
preview={<WriteContractExtensionPreview />}
/>
);
}
@@ -0,0 +1,73 @@
import { ScanTextIcon } from "lucide-react";
import { ReadContractRawPreview } from "@/components/blockchain-api/read-contract-raw";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { createMetadata } from "@/lib/metadata";
const title = "Read Contract Data";
const description =
"Read data from any contract on EVM with Type safe functions and hooks without needing contract ABIs";
const ogDescription =
"Query smart contracts and wallet data using type-safe functions and React hooks—no ABI required. Read blockchain data with full type safety";
export const metadata = createMetadata({
title,
description: ogDescription,
image: {
icon: "contract",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
icon={ScanTextIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/typescript/v5?utm_source=playground"
>
<div className="flex flex-col gap-14">
<ReadContractRaw />
</div>
</PageLayout>
</ThirdwebProvider>
);
}
function ReadContractRaw() {
return (
<CodeExample
code={`import { getContract } from "thirdweb";
import { ethereum } from "thirdweb/chains";
import { MediaRenderer, useReadContract } from "thirdweb/react";
const onChainCryptoPunks = getContract({
address: "0x16F5A35647D6F03D5D3da7b35409D65ba03aF3B2",
chain: ethereum,
client: THIRDWEB_CLIENT,
});
function App() {
// Read the image of the tokenId #1
const { data } = useReadContract({
contract: onChainCryptoPunks,
method: "function punkImageSvg(uint16 index) view returns (string svg)",
params: [1],
});
return (
<MediaRenderer
client={THIRDWEB_CLIENT}
src={data}
/>
);
}
`}
lang="tsx"
preview={<ReadContractRawPreview />}
/>
);
}
@@ -0,0 +1,76 @@
import { PencilIcon } from "lucide-react";
import { WriteContractRawPreview } from "@/components/blockchain-api/write-contract-raw";
import { CodeExample } from "@/components/code/code-example";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "../../../lib/metadata";
const title = "Write Contract";
const description =
"Send transactions from the connected wallet using type-safe functions and hooks for contract calls or raw transactions";
const ogDescription =
"Send blockchain transactions with the connected wallet using type-safe functions and React hooks for contract calls or raw transactions";
export const metadata = createMetadata({
title,
description: ogDescription,
image: {
icon: "contract",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
icon={PencilIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/typescript/v5?utm_source=playground"
>
<div className="flex flex-col gap-14">
<WriteContractRaw />
</div>
</PageLayout>
</ThirdwebProvider>
);
}
function WriteContractRaw() {
return (
<CodeExample
code={`import { getContract, prepareContractCall, toUnits } from "thirdweb";
import { sepolia } from "thirdweb/chains";
const tw_coin = getContract({
address: "0xACf072b740a23D48ECd302C9052fbeb3813b60a6",
chain: sepolia,
client: THIRDWEB_CLIENT,
});
function App() {
return <TransactionButton
transaction={() =>
prepareContractCall({
contract: tw_coin,
method:
"function transfer(address to, uint256 value) returns (bool)",
params: [
"0x...",
toUnits("5", 18),
],
})
}
>
Send
</TransactionButton>
}
`}
lang="tsx"
preview={<WriteContractRawPreview />}
/>
);
}
+10 -5
View File
@@ -108,6 +108,11 @@
}
}
.shiki,
.shiki span {
background-color: transparent !important;
}
.dark .shiki,
.dark .shiki span {
color: var(--shiki-dark) !important;
@@ -117,11 +122,6 @@
text-decoration: var(--shiki-dark-text-decoration) !important;
}
.shiki,
.shiki span {
background-color: transparent !important;
}
/* Fix colors on auto-filled inputs */
input:-webkit-autofill,
input:-webkit-autofill:hover,
@@ -149,3 +149,8 @@ input:-webkit-autofill:active {
scrollbar-width: none; /* Firefox */
}
}
* {
scrollbar-width: thin;
scrollbar-color: hsl(var(--muted)) transparent;
}
@@ -54,7 +54,7 @@ export default async function Page(props: {
return (
<div>
<Breadcrumbs />
<h1 className="mt-3 mb-6 font-semibold text-2xl tracking-tight lg:text-3xl">
<h1 className="mt-3 mb-6 font-semibold text-2xl tracking-tight">
<div className="flex items-center gap-2">
<span
className={cn(
+18 -4
View File
@@ -1,15 +1,29 @@
import type React from "react";
import { createMetadata } from "@/lib/metadata";
import { PageLayout } from "../../components/blocks/APIHeader";
import { InsightIcon } from "../../icons/InsightIcon";
const title = "Insight";
const description =
"Simple & customizable endpoints for querying rich blockchain data";
export const metadata = createMetadata({
title,
description,
image: {
icon: "insight",
title,
},
});
export default function Layout(props: { children: React.ReactNode }) {
return (
<PageLayout
icon={InsightIcon}
containerClassName="pb-16"
description={
<>Simple & customizable endpoints for querying rich blockchain data</>
}
description={description}
docsLink="https://portal.thirdweb.com/insight?utm_source=playground"
title="Insight"
title={title}
>
{props.children}
</PageLayout>
+18 -17
View File
@@ -2,12 +2,11 @@ import type { Metadata } from "next";
import { Fira_Code, Inter } from "next/font/google";
import { metadataBase } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { AppSidebar } from "./AppSidebar";
import { AppSidebarLayout } from "./AppSidebar";
import { Providers } from "./providers";
import "./globals.css";
import { ThemeProvider } from "next-themes";
import NextTopLoader from "nextjs-toploader";
import { MobileHeader } from "./MobileHeader";
import { getSidebarLinks } from "./navLinks";
const sansFont = Inter({
subsets: ["latin"],
@@ -32,7 +31,6 @@ export default async function RootLayout({
}: {
children: React.ReactNode;
}) {
const sidebarLinks = getSidebarLinks();
return (
<html lang="en" suppressHydrationWarning>
<body
@@ -42,21 +40,24 @@ export default async function RootLayout({
monoFont.variable,
)}
>
<NextTopLoader
color="hsl(var(--foreground))"
height={2}
shadow={false}
showSpinner={false}
/>
<MobileHeader links={sidebarLinks} />
<div className="flex flex-col lg:h-dvh lg:flex-row">
<AppSidebar links={sidebarLinks} />
<div className="flex grow flex-col lg:overflow-auto">
<div className="relative grow">
<ThemeProvider
attribute="class"
defaultTheme="dark"
disableTransitionOnChange
enableSystem={false}
>
<NextTopLoader
color="hsl(var(--foreground))"
height={2}
shadow={false}
showSpinner={false}
/>
<div className="flex flex-col lg:h-dvh lg:flex-row">
<AppSidebarLayout>
<Providers>{children}</Providers>
</div>
</AppSidebarLayout>
</div>
</div>
</ThemeProvider>
</body>
</html>
);
@@ -1,95 +0,0 @@
// TODO: clean all of this up a heck of a lot!
const LOGIN_URL = "https://login.thirdweb.com";
const CLIENT_ID = "demo";
export async function triggerLogin() {
const { codeChallenge, codeVerifier } = await generateCodeChallenge();
const state = generateState();
storeCodeVerifier(codeVerifier);
storeState(state);
const redirectUri = window.location.href;
window.location.href = `${LOGIN_URL}/authorize?client_id=${CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
}
// do code exchange
export async function handleLogin(code: string) {
const codeVerifier = getCodeVerifier();
const state = getState();
if (!codeVerifier || !state) {
console.error("No code verifier or state found");
return;
}
// clear the code verifier and state -> we don't need them anymore
clearCodeVerifier();
clearState();
const redirectUri = window.location.href;
fetch(`${LOGIN_URL}/api/token`, {
body: JSON.stringify({
client_id: CLIENT_ID,
code,
code_verifier: codeVerifier,
grant_type: "authorization_code",
redirect_uri: redirectUri,
state,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
})
.then((res) => res.json())
.then((data) => {
console.log("data", data);
})
.catch((err) => {
console.error("error", err);
});
}
// oauth2 PKCE code challenge generator (code challenge has to be url safe)
async function generateCodeChallenge() {
const codeVerifier = base64UrlEncode(
window.crypto.getRandomValues(new Uint8Array(64)),
);
const codeChallenge = await window.crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(codeVerifier),
);
return {
codeChallenge: base64UrlEncode(codeChallenge),
codeVerifier: codeVerifier,
};
}
function generateState() {
const random = window.crypto.getRandomValues(new Uint8Array(12));
return base64UrlEncode(random);
}
function base64UrlEncode(array: Uint8Array | ArrayBuffer) {
return btoa(String.fromCharCode(...new Uint8Array(array)))
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
}
function storeCodeVerifier(codeVerifier: string) {
localStorage.setItem("codeVerifier", codeVerifier);
}
function getCodeVerifier() {
return localStorage.getItem("codeVerifier");
}
function clearCodeVerifier() {
localStorage.removeItem("codeVerifier");
}
function storeState(state: string) {
localStorage.setItem("state", state);
}
function getState() {
return localStorage.getItem("state");
}
function clearState() {
localStorage.removeItem("state");
}
@@ -1,9 +0,0 @@
import { Suspense } from "react";
export default function LoginLayout({
children,
}: {
children: React.ReactNode;
}) {
return <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>;
}
@@ -1,32 +0,0 @@
"use client";
import { useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { handleLogin, triggerLogin } from "./_sdk_";
export default function LoginPage() {
const searchParams = useSearchParams();
const code = searchParams.get("code");
if (code) {
handleLogin(code);
}
return (
<div className="flex min-h-screen items-center justify-center">
<Card>
<CardHeader>
<CardTitle>Login Test</CardTitle>
</CardHeader>
<CardContent>
<Button
onClick={() => {
triggerLogin();
}}
>
Login
</Button>
</CardContent>
</Card>
</div>
);
}
+187 -125
View File
@@ -1,163 +1,225 @@
import type { SidebarLink } from "../components/ui/sidebar";
"use client";
import { ArrowLeftRightIcon } from "lucide-react";
import type { ShadcnSidebarLink } from "@/components/blocks/full-width-sidebar-layout";
import { ContractIcon } from "../icons/ContractIcon";
import { InsightIcon } from "../icons/InsightIcon";
import { PayIcon } from "../icons/PayIcon";
import { SmartAccountIcon } from "../icons/SmartAccountIcon";
import { TokenIcon } from "../icons/TokenIcon";
import { WalletProductIcon } from "../icons/WalletProductIcon";
import { insightBlueprints } from "./insight/insightBlueprints";
const staticSidebarLinks: SidebarLink[] = [
{
isCollapsible: false,
links: [
{
href: "/wallets/sign-in/button",
name: "ConnectButton",
},
{
href: "/wallets/sign-in/embed",
name: "ConnectEmbed",
},
{
href: "/wallets/sign-in/headless",
name: "Headless Connect",
},
{
href: "/wallets/in-app-wallet",
name: "In-App Wallets",
},
{
href: "/wallets/in-app-wallet/ecosystem",
name: "Ecosystem Wallets",
},
{
href: "/wallets/account-abstraction/eip-4337",
name: "EIP-4337",
},
{
href: "/wallets/account-abstraction/eip-7702",
name: "EIP-7702",
},
{
href: "/wallets/account-abstraction/eip-5792",
name: "EIP-5792",
},
{
href: "/wallets/account-abstraction/native-aa",
name: "Native AA (zkSync)",
},
{
href: "/wallets/auth",
name: "Auth",
},
{
href: "/wallets/social",
name: "Social",
},
{
href: "/wallets/blockchain-api",
name: "Blockchain API",
},
{
expanded: false,
links: [
{
href: "/wallets/headless/account-components",
name: "Account",
},
{
href: "/wallets/headless/nft-components",
name: "NFT",
},
{
href: "/wallets/headless/token-components",
name: "Token",
},
{
href: "/wallets/headless/chain-components",
name: "Chain",
},
{
href: "/wallets/headless/wallet-components",
name: "Wallet",
},
],
name: "Headless Components",
},
],
name: "Wallets",
const wallets: ShadcnSidebarLink = {
subMenu: {
label: "Wallets",
icon: WalletProductIcon,
},
];
links: [
{
href: "/wallets/sign-in/button",
label: "Connect Button",
},
{
href: "/wallets/sign-in/embed",
label: "Connect Embed",
},
{
href: "/wallets/sign-in/headless",
label: "Headless Connect",
},
{
href: "/wallets/in-app-wallet",
label: "In-App Wallets",
},
{
href: "/wallets/ecosystem-wallet",
label: "Ecosystem Wallets",
},
{
href: "/wallets/auth",
label: "Authentication (SIWE)",
},
{
href: "/wallets/social",
label: "Social Profiles",
},
const universalBridgeSidebarLinks: SidebarLink = {
expanded: false,
isCollapsible: false,
{
subMenu: {
label: "Headless Components",
},
links: [
{
href: "/wallets/headless/account-components",
label: "Account Components",
},
{
href: "/wallets/headless/chain-components",
label: "Chain Components",
},
{
href: "/wallets/headless/wallet-components",
label: "Wallet Components",
},
],
},
],
};
const contracts: ShadcnSidebarLink = {
subMenu: {
label: "Contracts",
icon: ContractIcon,
},
links: [
{
href: "/contracts/read",
label: "Read Contract",
},
{
href: "/contracts/write",
label: "Write Contract",
},
{
href: "/contracts/extensions",
label: "Pre-built Extensions",
},
{
href: "/contracts/events",
label: "Listen Contract Events",
},
],
};
const tokens: ShadcnSidebarLink = {
subMenu: {
label: "Tokens",
icon: TokenIcon,
},
links: [
{
href: "/tokens/token-components",
label: "Token Components",
},
{
href: "/tokens/nft-components",
label: "NFT Components",
},
],
};
const accountAbstractions: ShadcnSidebarLink = {
subMenu: {
label: "Account Abstraction",
icon: SmartAccountIcon,
},
links: [
{
href: "/account-abstraction/eip-4337",
label: "EIP-4337",
},
{
href: "/account-abstraction/eip-7702",
label: "EIP-7702",
},
{
href: "/account-abstraction/eip-5792",
label: "EIP-5792",
},
{
href: "/account-abstraction/native-aa",
label: "Native AA (zkSync)",
},
],
};
const payments: ShadcnSidebarLink = {
subMenu: {
label: "Payments",
icon: PayIcon,
},
links: [
{
href: "/payments/ui-components",
name: "UI Component",
label: "UI Components",
},
{
href: "/payments/fund-wallet",
name: "Buy Crypto",
label: "Buy Crypto",
},
{
href: "/payments/commerce",
name: "Checkout",
label: "Checkout",
},
{
href: "/payments/transactions",
name: "Transactions",
label: "Onchain Transaction",
},
{
href: "/payments/backend",
name: "Backend API",
label: "Payments API",
},
],
name: "Payments",
};
const engineSidebarLinks: SidebarLink = {
expanded: false,
isCollapsible: false,
const transactions: ShadcnSidebarLink = {
subMenu: {
label: "Transactions",
icon: ArrowLeftRightIcon,
},
links: [
{
href: "/transactions/airdrop-tokens",
name: "Airdrop",
label: "Airdrop Tokens",
},
{
href: "/transactions/mint-tokens",
name: "Mint NFTs",
label: "Mint NFTs",
},
{
href: "/transactions/webhooks",
name: "Webhooks",
label: "Webhooks",
},
],
name: "Transactions",
};
export function getSidebarLinks() {
const insightLinks: SidebarLink[] = insightBlueprints.map((blueprint) => {
return {
expanded: false,
links: blueprint.paths.map((pathInfo) => {
return {
crossedOut: pathInfo.deprecated,
href: `/insight/${blueprint.id}?path=${pathInfo.path}`,
name: pathInfo.name,
};
}),
name: blueprint.name,
};
});
const sidebarLinks: SidebarLink[] = [
...staticSidebarLinks,
universalBridgeSidebarLinks,
engineSidebarLinks,
{
expanded: false,
isCollapsible: false,
links: insightLinks,
name: "Insight",
const insightLinks: ShadcnSidebarLink[] = insightBlueprints.map((blueprint) => {
return {
links: blueprint.paths.map((pathInfo) => {
return {
href: `/insight/${blueprint.id}?path=${pathInfo.path}`,
label: pathInfo.name,
exactMatch: true,
};
}),
subMenu: {
label: blueprint.name,
},
];
};
});
return sidebarLinks;
}
const insight: ShadcnSidebarLink = {
links: [
{
href: "/insight",
label: "Overview",
exactMatch: true,
},
...insightLinks,
],
subMenu: {
label: "Insight",
icon: InsightIcon,
},
};
export const sidebarLinks: ShadcnSidebarLink[] = [
wallets,
transactions,
contracts,
payments,
tokens,
insight,
accountAbstractions,
];
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

-14
View File
@@ -1,14 +0,0 @@
export const otherLinks: { name: string; href: string }[] = [
{
href: "https://thirdweb.com/team?utm_source=playground",
name: "Dashboard",
},
{
href: "https://portal.thirdweb.com?utm_source=playground",
name: "Docs",
},
{
href: "https://github.com/thirdweb-dev",
name: "Github",
},
];
+291 -2
View File
@@ -1,5 +1,294 @@
import { redirect } from "next/navigation";
import {
ArrowLeftRightIcon,
BlocksIcon,
BoxIcon,
BracesIcon,
CreditCardIcon,
GlobeIcon,
LockIcon,
PanelTopIcon,
PencilIcon,
PlaneIcon,
RectangleHorizontalIcon,
RssIcon,
ScanTextIcon,
ShieldIcon,
ShoppingBagIcon,
SquircleDashedIcon,
StampIcon,
UserIcon,
} from "lucide-react";
import Link from "next/link";
import { InsightIcon } from "../icons/InsightIcon";
import { ThirdwebIcon } from "../icons/ThirdwebMiniLogo";
export default function Page() {
redirect("/wallets/sign-in/button");
return (
<main className="pb-10">
{/* Hero Section */}
<section className="py-16">
<div className="container max-w-5xl">
<div className="flex mb-6">
<div className="rounded-full p-3 border bg-card">
<ThirdwebIcon
isMonoChrome
className="size-9 text-muted-foreground"
/>
</div>
</div>
<h1 className="mb-3 font-bold text-5xl tracking-tighter">
thirdweb Playground
</h1>
<p className="max-w-4xl text-base md:text-lg text-muted-foreground leading-normal">
Interactive UI components and endpoints to test, tweak, and ship
faster with thirdweb.
</p>
</div>
</section>
<div className="container max-w-5xl space-y-12">
{/* Wallets Section */}
<section>
<SectionTitle label="Wallets" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<FeatureCard
icon={RectangleHorizontalIcon}
title="Connect Button"
href="/wallets/sign-in/button"
description="Wallet connection component for EOA or email, mobile, social, and passkey logins"
/>
<FeatureCard
icon={PanelTopIcon}
title="Connect Embed"
href="/wallets/sign-in/embed"
description="Embedded component to view balance, get funds, and more"
/>
<FeatureCard
icon={SquircleDashedIcon}
title="Headless Connect"
href="/wallets/sign-in/headless"
description="Customizable wallet connection components using React hooks"
/>
<FeatureCard
icon={UserIcon}
title="In-App Wallets"
href="/wallets/in-app-wallet"
description="Add social login, passkey, phone, or email sign-in to your app"
/>
<FeatureCard
icon={LockIcon}
title="Authentication (SIWE)"
href="/wallets/auth"
description="Authenticate users to your backend using their wallet"
/>
<FeatureCard
icon={GlobeIcon}
title="Social Profiles"
href="/wallets/social"
description="Get user profiles across apps like ENS, Lens, Farcaster, and more"
/>
</div>
</section>
{/* Transactions Section */}
<section>
<SectionTitle label="Transactions" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<FeatureCard
icon={PlaneIcon}
title="Airdrop Tokens"
href="/transactions/airdrop-tokens"
description="Airdrop any token with a few lines of code with gas sponsorship, optional."
/>
<FeatureCard
icon={StampIcon}
title="Mint NFTs"
href="/transactions/mint-tokens"
description="Gasless and efficient token minting with just a wallet address"
/>
<FeatureCard
icon={RssIcon}
href="/transactions/webhooks"
title="Webhooks"
description="Receive real-time notifications for transactions and wallet events."
/>
</div>
</section>
{/* Contracts Section */}
<section>
<SectionTitle label="Contracts" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<FeatureCard
icon={ScanTextIcon}
title="Read Contract"
href="/contracts/read"
description="Read data from any contract on any EVM"
/>
<FeatureCard
icon={PencilIcon}
title="Write Contract"
href="/contracts/write"
description="Send transactions from the connected wallet"
/>
<FeatureCard
icon={BlocksIcon}
title="Pre-Built Extensions"
href="/contracts/extensions"
description="High-level read and write functions"
/>
<FeatureCard
icon={RssIcon}
title="Listen Contract Events"
href="/contracts/events"
description="Subscribe to any contract event"
/>
</div>
</section>
{/* Payments Section */}
<section>
<SectionTitle label="Payments" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<FeatureCard
icon={BoxIcon}
title="Payments UI Components"
description="Onramp, swap, and bridge over 1,000+ crypto tokens"
href="/payments/ui-components"
/>
<FeatureCard
icon={ShoppingBagIcon}
title="Buy Crypto"
description="Buy any token with ability to customize theme, amounts, and more"
href="/payments/fund-wallet"
/>
<FeatureCard
icon={CreditCardIcon}
title="Checkout"
description="Enable crypto payments for services and get notified on each sale"
href="/payments/commerce"
/>
<FeatureCard
icon={ArrowLeftRightIcon}
title="Transactions"
description="Enable users to pay for onchain transactions with fiat or crypto"
href="/payments/transactions"
/>
<FeatureCard
icon={BracesIcon}
title="Payments API"
description="Create customizable UIs or backend flows using the HTTP API"
href="/payments/backend"
/>
</div>
</section>
{/* Insight Section */}
<section>
<SectionTitle label="Insight" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<FeatureCard
icon={InsightIcon}
title="Events"
href="/insight"
description="Query contract events on any supported EVM chain"
/>
<FeatureCard
icon={InsightIcon}
title="Transactions"
href="/insight"
description="Query transactions to and from specified recipients"
/>
<FeatureCard
icon={InsightIcon}
title="Tokens"
href="/insight"
description="Query token owners, transfers, prices, and more"
/>
<FeatureCard
icon={InsightIcon}
title="NFTs"
href="/insight"
description="Query NFT balances, collections, transfers, metadata, and more."
/>
<FeatureCard
icon={InsightIcon}
title="Wallets"
href="/insight"
description="Query transactions to and from specific wallets"
/>
<FeatureCard
icon={InsightIcon}
title="Contracts"
href="/insight"
description="Query a contract's ABI or metadata"
/>
</div>
</section>
{/* Account Abstraction Section */}
<section>
<SectionTitle label="Account Abstraction" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<FeatureCard
icon={ShieldIcon}
title="EIP-4337"
href="/account-abstraction/eip-4337"
description="Implement via a higher-layer mempool using objects and bundlers"
/>
<FeatureCard
icon={ShieldIcon}
title="EIP-7702"
href="/account-abstraction/eip-7702"
description="Allow EOAs to temporarily behave like smart contracts during txs"
/>
<FeatureCard
icon={ShieldIcon}
title="EIP-5792"
href="/account-abstraction/eip-5792"
description="Define a standard RPC interface for smart account interactions"
/>
<FeatureCard
icon={ShieldIcon}
title="Native (zkSync)"
href="/account-abstraction/native-aa"
description="Native account abstraction available for zkSync chains"
/>
</div>
</section>
</div>
</main>
);
}
function SectionTitle(props: { label: string }) {
return (
<h2 className="font-semibold text-xl text-foreground tracking-tight mb-3">
{props.label}
</h2>
);
}
function FeatureCard(props: {
title: string;
description: string;
icon: React.FC<{ className?: string }>;
href: string; // todo make this required
}) {
return (
<div className="rounded-lg border bg-card p-4 hover:border-active-border relative">
<div className="flex mb-4">
<div className="p-2 rounded-full border bg-background">
<props.icon className="size-4 text-muted-foreground" />
</div>
</div>
<h3 className="font-medium mb-0.5 text-lg tracking-tight">
<Link className="before:absolute before:inset-0" href={props.href}>
{props.title}
</Link>
</h3>
<p className="text-sm text-muted-foreground">{props.description}</p>
</div>
);
}
@@ -1,15 +1,32 @@
import { BracesIcon } from "lucide-react";
import type React from "react";
import { PageHeader } from "../../../components/blocks/APIHeader";
import { PageHeader } from "@/components/blocks/APIHeader";
import { createMetadata } from "@/lib/metadata";
const title = "Payments API";
const description =
"Create customizable components or backend flows with an HTTP API to onramp, swap, and bridge to and from different cryptocurrencies";
const ogDescription =
"Bridge, swap, and onramp any currency with thirdweb’s Payments REST API. Use simple HTTP requests to integrate from your backend.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "payments",
title,
},
});
export default function Layout(props: { children: React.ReactNode }) {
return (
<div>
<PageHeader
description={
<>HTTP API to bridge, swap and onramp to and from any currency</>
}
description={description}
docsLink="https://portal.thirdweb.com/payments?utm_source=playground"
title="Payments API"
icon={BracesIcon}
title={title}
containerClassName="max-w-[1400px]"
/>
{props.children}
@@ -1,3 +1,4 @@
import { ArrowUpRightIcon } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
@@ -7,10 +8,10 @@ export default async function Page() {
try {
const paths = await getBridgePaths();
return (
<div className="container pb-20">
<div className="container max-w-[1400px] pb-20">
<div className="flex flex-col justify-between md:flex-row md:gap-8">
<div>
<h2 className="mb-1 font-semibold text-2xl tracking-tight">
<h2 className="mb-0.5 font-semibold text-xl tracking-tight">
thirdweb Payments REST API
</h2>
<p className="mb-5 text-muted-foreground">
@@ -20,8 +21,13 @@ export default async function Page() {
</div>
<Link href="https://bridge.thirdweb.com/reference" target="_blank">
<Button className="max-md:w-full" variant="outline">
<Button
className="max-md:w-full rounded-full gap-2"
variant="outline"
size="sm"
>
View all endpoints
<ArrowUpRightIcon className="size-4" />
</Button>
</Link>
</div>
@@ -36,7 +36,7 @@ export default async function Page(props: {
const title = pathMetadata.summary || "";
return (
<div className="container pb-8">
<div className="container max-w-[1400px] pb-8">
<Breadcrumbs />
<div className="h-3" />
{title && (
@@ -1,28 +1,33 @@
import type { Metadata } from "next";
import { CreditCardIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import { BuyMerchPreview } from "@/components/pay/direct-payment";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"The easiest way for users to transact in your app. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.",
metadataBase,
title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Payments",
};
const title = "Checkout Component";
const description =
"Enable purchase of any service or goods with fiat or cryptocurrency and setup notifications on every sale to ship goods, activate services, and more";
const ogDescription =
"Accept fiat or crypto payments on any chain—direct to your wallet. Instant checkout, webhook support, and full control over post-sale actions.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "payments",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Let your users pay for any service with fiat or crypto on any chain.
</>
}
icon={CreditCardIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/payments?utm_source=playground"
title="Commerce payments with fiat or crypto"
>
<BuyMerch />
</PageLayout>
@@ -1,29 +1,33 @@
import type { Metadata } from "next";
import { ShoppingBagIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { StyledBuyWidgetPreview } from "@/components/universal-bridge/buy";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"The easiest way for users to fund their wallets. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.",
metadataBase,
title: "Buy Crypto | thirdweb Payments",
};
const title = "Buy Crypto Component";
const description =
"Embeddable component for users to purchase any cryptocurrency for top-ups and more with fiat or crypto-to-crypto swaps";
const ogDescription =
"Configure a component to buy cryptocurrency with specified amounts, customization, and more. This interactive playground shows how to customize the component.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "payments",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Onramp users with credit card &amp; cross-chain crypto payments —
and generate revenue for each user transaction.
</>
}
icon={ShoppingBagIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="The easiest way for users to fund their wallets"
>
<StyledPayWidget />
</PageLayout>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 KiB

@@ -1,4 +1,4 @@
import type { Metadata } from "next";
import { ArrowLeftRightIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import {
@@ -6,28 +6,32 @@ import {
PayTransactionPreview,
} from "@/components/pay/transaction-button";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"The easiest way for users to transact in your app. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.",
metadataBase,
title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Pay",
};
const title = "Onchain Transaction Component";
const description =
"Enable seamless onchain transactions for any contract with fiat or crypto with amounts calculated and automatic execution after funds are confirmed.";
const ogDescription =
"Power onchain transactions with fiat or crypto payments. Automatically calculate costs and run the transaction post onramp or token swap.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "payments",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
icon={ArrowLeftRightIcon}
containerClassName="space-y-12"
description={
<>
Let your users pay for onchain transactions with fiat or crypto on
any chain.
</>
}
description={description}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="Onchain transactions with fiat or crypto"
title={title}
>
<BuyOnchainAsset />
<NoFundsPopup />
@@ -1,15 +1,23 @@
import type { Metadata } from "next";
import { BoxIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "@/lib/metadata";
import PayEmbedPlayground from "../embed/page";
export const metadata: Metadata = {
description:
"The easiest way for users to transact in your app. Onramp users, pay with any token and generate revenue for each user transaction. Integrate for free.",
metadataBase,
title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Payments",
};
const title = "Crypto Payments UI Components";
const description =
"Onramp, swap, & bridge over 1,000+ tokens to enable seamless crypto payments, checkouts, and transactions";
const ogDescription =
"Onramp, swap, and bridge cryptocurrency with easy to implement components for purchasing crypto, checking out physical or digital goods and services, and executing onchain transactions.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "payments",
title,
},
});
export default function Page(props: {
searchParams: Promise<{ tab: string }>;
@@ -17,14 +25,10 @@ export default function Page(props: {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Onramp users with credit card &amp; cross-chain crypto payments —
and generate revenue for each user transaction.
</>
}
icon={BoxIcon}
description={description}
docsLink="https://portal.thirdweb.com/payments?utm_source=playground"
title="Payments UI component"
title={title}
>
<PayEmbedPlayground searchParams={props.searchParams} />
</PageLayout>
@@ -1,87 +0,0 @@
"use client";
import { useState } from "react";
import { arbitrum, base, ethereum } from "thirdweb/chains";
import { PageLayout } from "@/components/blocks/APIHeader";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { TokenSelector } from "@/components/ui/TokenSelector";
import { THIRDWEB_CLIENT } from "@/lib/client";
import type { TokenMetadata } from "@/lib/types";
export default function TokenSelectorDemo() {
const [selectedToken, setSelectedToken] = useState<
{ chainId: number; address: string } | undefined
>(undefined);
const [selectedChain, setSelectedChain] = useState<number>(ethereum.id);
const chains = [
{ id: ethereum.id, name: "Ethereum" },
{ id: base.id, name: "Base" },
{ id: arbitrum.id, name: "Arbitrum" },
];
return (
<ThirdwebProvider>
<PageLayout
description="Demo of the TokenSelector component ported from dashboard"
docsLink="https://portal.thirdweb.com/react/v5/components/onchain?utm_source=playground"
title="Token Selector Demo"
>
<div className="space-y-8">
<div className="space-y-4">
<h2 className="text-xl font-semibold">Select a Chain</h2>
<select
className="rounded border border-border bg-background p-2"
onChange={(e) => {
setSelectedChain(Number(e.target.value));
setSelectedToken(undefined); // Reset token when chain changes
}}
value={selectedChain}
>
{chains.map((chain) => (
<option key={chain.id} value={chain.id}>
{chain.name}
</option>
))}
</select>
</div>
<div className="space-y-4">
<h2 className="text-xl font-semibold">Select a Token</h2>
<div className="max-w-md">
<TokenSelector
addNativeTokenIfMissing={true}
chainId={selectedChain}
client={THIRDWEB_CLIENT}
enabled={true}
onChange={(token: TokenMetadata) => {
setSelectedToken({
address: token.address,
chainId: token.chainId,
});
}}
placeholder="Select a token"
selectedToken={selectedToken}
/>
</div>
</div>
{selectedToken && (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Selected Token</h2>
<div className="rounded border border-border bg-muted p-4">
<p>
<strong>Chain ID:</strong> {selectedToken.chainId}
</p>
<p>
<strong>Address:</strong> {selectedToken.address}
</p>
</div>
</div>
)}
</div>
</PageLayout>
</ThirdwebProvider>
);
}
@@ -1,4 +1,4 @@
import type { Metadata } from "next";
import { ImageIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import {
NftCardExample,
@@ -7,24 +7,29 @@ import {
NftNameExample,
} from "@/components/headless-ui/nft-examples";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"Elevate your NFT marketplace with our React headless UI components, engineered for seamless digital asset transactions. These customizable, zero-styling components simplify NFT interactions while giving developers complete freedom to craft their perfect user interface.",
metadataBase,
title: "NFT Components",
};
const title = "NFT Components";
const description =
"Headless UI components for rendering NFT Media and metadata";
export const metadata = createMetadata({
title,
description,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>Headless UI components for rendering NFT Media and metadata</>
}
icon={ImageIcon}
description={description}
docsLink="https://portal.thirdweb.com/react/v5/components/onchain#nfts?utm_source=playground"
title="NFT Components"
title={title}
>
<div className="space-y-14">
<NftMediaExample />
@@ -1,4 +1,4 @@
import type { Metadata } from "next";
import { DollarSignIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import {
TokenImageBasic,
@@ -6,27 +6,30 @@ import {
TokenSymbolBasic,
} from "@/components/headless-ui/token-examples";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"Headless UI components for rendering token image, name, and symbol",
metadataBase,
title: "Token Components",
};
const title = "Token Components";
const description =
"Headless UI components for rendering token image, name, and symbol";
export const metadata = createMetadata({
title,
description,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
containerClassName="space-y-12"
description={
<>
Headless UI components for rendering token image, name, and symbol
</>
}
icon={DollarSignIcon}
description={description}
docsLink="https://portal.thirdweb.com/react/v5/components/onchain#tokens?utm_source=playground"
title="Token Components"
title={title}
>
<TokenImageBasic />
<TokenNameBasic />
@@ -1,20 +1,33 @@
import { PlaneIcon } from "lucide-react";
import { EngineAirdropPreview } from "@/app/transactions/airdrop-tokens/_components/airdrop-preview";
import { PageLayout } from "@/components/blocks/APIHeader";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "@/lib/metadata";
import { AirdropCode } from "./_components/airdrop-code";
const title = "Airdrop Tokens";
const description =
"Airdrop ERC-20, ERC-721, or ERC-1155 tokens at scale to multiple addresses. Support gas sponsorship and receive real-time status updates for each transaction.";
const ogDescription =
"Airdrop any collection of ERC20, ERC721, or ERC1155 tokens with a few lines of code. Try the flow, inspect the smart contract, and copy code directly into your app.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "transactions",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Engine makes it effortless for any developer to airdrop tokens at
scale. You sponsor the gas so your users only need a wallet address!
</>
}
icon={PlaneIcon}
description={description}
docsLink="https://thirdweb-engine.apidocumentation.com/reference#tag/erc20/POST/contract/{chain}/{contractAddress}/erc20/mint-batch-to?utm_source=playground"
title="Airdrop"
title={title}
>
<EngineAirdropPreview />
<div className="h-10" />
@@ -1,20 +1,33 @@
import { StampIcon } from "lucide-react";
import { EngineMintPreview } from "@/app/transactions/mint-tokens/_components/mint-preview";
import { PageLayout } from "@/components/blocks/APIHeader";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "@/lib/metadata";
import { MintCode } from "./_components/mint-code";
const title = "Mint NFTs";
const description =
"Enable users to mint new tokens into any smart contract. Gas fees are sponsored, so users only need to provide a wallet address";
const ogDescription =
"Interactive demo for gasless token minting. Mint tokens to any contract with just a wallet address. Sponsor gas and streamline UX.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "transactions",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Allow your users to mint new tokens into any given contract. You
sponsor the gas so your users only need a wallet address!
</>
}
icon={StampIcon}
description={description}
docsLink="https://thirdweb-engine.apidocumentation.com/reference#tag/erc1155/POST/contract/{chain}/{contractAddress}/erc1155/mint-to?utm_source=playground"
title="Mint Dynamic NFTs"
title={title}
>
<EngineMintPreview />
<div className="h-10" />
@@ -1,19 +1,32 @@
import { RssIcon } from "lucide-react";
import { EngineWebhooksPreview } from "@/app/transactions/webhooks/_components/webhooks-preview";
import { PageLayout } from "@/components/blocks/APIHeader";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "@/lib/metadata";
const title = "Transaction Webhooks";
const description =
"Configure webhooks to notify your backend of transaction events or activity from your server wallet";
const ogDescription =
"Set up webhooks using Transactions to receive real-time notifications for transactions and wallet events. Test the flow and see example payloads in action.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "transactions",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Configure webhooks in Engine to notify your backend server of
transaction or backend wallet events.
</>
}
icon={RssIcon}
description={description}
docsLink="https://portal.thirdweb.com/engine/v2/features/webhooks?utm_source=playground"
title="Webhooks"
title={title}
>
<EngineWebhooksPreview />
</PageLayout>
@@ -1,91 +0,0 @@
import type { Metadata } from "next";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import {
ConnectSmartAccountCustomPreview,
ConnectSmartAccountPreview,
} from "../../../../components/account-abstraction/connect-smart-account";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { CodeExample } from "../../../../components/code/code-example";
export const metadata: Metadata = {
description:
"Let users sign up with their email, phone number, social media accounts or directly with a wallet. Seamlessly integrate account abstraction and SIWE auth.",
metadataBase,
title: "Account Abstraction | thirdweb Connect",
};
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Let users connect to their smart accounts with any wallet and unlock
gas sponsorship, batched transactions, session keys and full wallet
programmability.
</>
}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="Connect smart accounts"
>
<ConnectSmartAccount />
</PageLayout>
</ThirdwebProvider>
);
}
function ConnectSmartAccount() {
return (
<>
<CodeExample
code={`\
import { ConnectButton } from "thirdweb/react";
function App(){
return (<>
<ConnectButton client={client}
// account abstraction options
accountAbstraction={{ chain, sponsorGas: true }} />
</>);
};`}
header={{
description:
"Use the prebuilt UI components to connect to smart accounts",
title: "Using prebuilt UI component",
}}
lang="tsx"
preview={<ConnectSmartAccountPreview />}
/>
<div className="h-14" />
<CodeExample
code={`\
import { useConnect } from "thirdweb/react";
import { createWallet } from "thirdweb/wallets";
function App(){
const { connect } = useConnect({ client,
// account abstraction options
accountAbstraction: { chain, sponsorGas: true }});
return (<>
<button onClick={() => connect(async () => {
// any wallet connected here will be
// converted to a smart account
const adminWallet = inAppWallet();
await adminWallet.connect({ client, strategy: "google" });
return adminWallet;
})}>Connect with Google</button>
</>);
};`}
header={{
description: "Build your own UI to connect to smart accounts",
title: "Build custom UI",
}}
lang="tsx"
preview={<ConnectSmartAccountCustomPreview />}
/>
</>
);
}
@@ -1,33 +1,34 @@
import type { Metadata } from "next";
import { LockIcon } from "lucide-react";
import { BasicAuthPreview } from "@/components/auth/basic-auth";
import { GatedContentPreview } from "@/components/auth/gated-content";
import { SmartAccountAuthPreview } from "@/components/auth/smart-account-auth";
import { CodeExample } from "@/components/code/code-example";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { BasicAuthHookPreview } from "../../../components/auth/basic-auth-hook";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "../../../lib/metadata";
export const metadata: Metadata = {
description:
"Authenticate users to your backend using only their wallet. This is a secure and easy way to authenticate users without requiring them to create an additional account.",
metadataBase,
title: "Auth | thirdweb Connect",
};
const title = "Authentication (SIWE)";
const description =
"Add secure wallet authentication to your app using SIWE and JWT. Authenticate users without passwords via Sign-In with Ethereum and backend token validation";
export const metadata = createMetadata({
description,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Authenticate users to your backend using only their wallet. This is
a secure and easy way to authenticate users without requiring them
to create an additional account.
</>
}
icon={LockIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/typescript/v5/auth?utm_source=playground"
title="Auth"
>
<div className="flex flex-col gap-14">
<BasicAuth />
@@ -1,241 +0,0 @@
import type { Metadata } from "next";
import { ReadContractExtensionPreview } from "@/components/blockchain-api/read-contract-extension";
import { ReadContractRawPreview } from "@/components/blockchain-api/read-contract-raw";
import { WatchEventPreview } from "@/components/blockchain-api/watch-event-preview";
import { WriteContractExtensionPreview } from "@/components/blockchain-api/write-contract-extension";
import { WriteContractRawPreview } from "@/components/blockchain-api/write-contract-raw";
import { CodeExample } from "@/components/code/code-example";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { PageLayout } from "../../../components/blocks/APIHeader";
export const metadata: Metadata = {
description:
"Interact with EVM blockchains using thirdweb SDK. Create seamless NFT minting experience. Airdrop tokens to millions of users",
metadataBase,
title: "Blockchain API | thirdweb Connect",
};
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Performant, reliable and type safe API to read write to any contract
on any EVM chain through our RPC Edge endpoints.
</>
}
docsLink="https://portal.thirdweb.com/typescript/v5?utm_source=playground"
title="Blockchain API"
>
<div className="flex flex-col gap-14">
<ReadContractRaw />
<ReadContractExtension />
<WriteContractExtension />
<WriteContractRaw />
<WatchEvent />
</div>
</PageLayout>
</ThirdwebProvider>
);
}
function ReadContractRaw() {
return (
<CodeExample
code={`import { getContract } from "thirdweb";
import { ethereum } from "thirdweb/chains";
import { MediaRenderer, useReadContract } from "thirdweb/react";
const onChainCryptoPunks = getContract({
address: "0x16F5A35647D6F03D5D3da7b35409D65ba03aF3B2",
chain: ethereum,
client: THIRDWEB_CLIENT,
});
function App() {
// Read the image of the tokenId #1
const { data } = useReadContract({
contract: onChainCryptoPunks,
method: "function punkImageSvg(uint16 index) view returns (string svg)",
params: [1],
});
return (
<MediaRenderer
client={THIRDWEB_CLIENT}
src={data}
/>
);
}
`}
header={{
description:
"Read data from any contract or wallet. Type safe functions and hooks without needing full ABIs.",
title: "Query blockchain data",
}}
lang="tsx"
preview={<ReadContractRawPreview />}
/>
);
}
function ReadContractExtension() {
return (
<CodeExample
code={`import { getContract } from "thirdweb";
import { ethereum } from "thirdweb/chains";
import { MediaRenderer, useReadContract } from "thirdweb/react";
const nftContract = getContract({
address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d",
chain: ethereum,
client: THIRDWEB_CLIENT,
});
function App() {
const { data } = useReadContract(getNFT, {
contract: nftContract,
tokenId: 458n,
});
return (
<MediaRenderer
client={THIRDWEB_CLIENT}
src={data?.metadata.image}
/>
);
}
`}
header={{
description:
"Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.",
title: "Prebuilt read extensions",
}}
lang="tsx"
preview={<ReadContractExtensionPreview />}
/>
);
}
function WriteContractExtension() {
return (
<CodeExample
code={`import { getContract } from "thirdweb";
import { sepolia } from "thirdweb/chains";
import { claimTo } from "thirdweb/extensions/erc20";
const tw_coin = getContract({
address: "0xACf072b740a23D48ECd302C9052fbeb3813b60a6",
chain: sepolia,
client: THIRDWEB_CLIENT,
});
function App() {
return <TransactionButton
transaction={() =>
claimTo({
contract: twCoinContract,
to: account.address,
quantity: "10",
})
}
>
Claim
</TransactionButton>
}
`}
header={{
description:
"Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.",
title: "Prebuilt write extensions",
}}
lang="tsx"
preview={<WriteContractExtensionPreview />}
/>
);
}
function WriteContractRaw() {
return (
<CodeExample
code={`import { getContract, prepareContractCall, toUnits } from "thirdweb";
import { sepolia } from "thirdweb/chains";
const tw_coin = getContract({
address: "0xACf072b740a23D48ECd302C9052fbeb3813b60a6",
chain: sepolia,
client: THIRDWEB_CLIENT,
});
// Claim some test tokens from the above example!
function App() {
return <TransactionButton
transaction={() =>
prepareContractCall({
contract: tw_coin,
method:
"function transfer(address to, uint256 value) returns (bool)",
params: [
"0x...",
toUnits("5", 18),
],
})
}
>
Send
</TransactionButton>
}
`}
header={{
description:
"Send transactions with the connected wallet. Type safe functions and hooks to send contracts call or raw transaction.",
title: "Write data to blockchain",
}}
lang="tsx"
preview={<WriteContractRawPreview />}
/>
);
}
function WatchEvent() {
return (
<CodeExample
code={`import { useContractEvents } from "thirdweb/react";
import { getContract } from "thirdweb";
import { base } from "thirdweb/chains";
import { transferEvent } from "thirdweb/extensions/erc20";
const usdcContractOnBase = getContract({
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
chain: base,
client,
});
function App() {
// Listen to USDC transfers on Base
const contractEvents = useContractEvents({
contract: usdcContractOnBase,
events: [transferEvent()],
blockRange: 100,
});
(contractEvents.data || []).forEach((item) => {
const { from, to, value } = item.args;
console.log("{from}...{value} USDC...{to}");
});
}
`}
header={{
description:
"Subscribe to any contract event. Auto polling hooks and functions with type safe event extensions for all common standards.",
title: "Listen to blockchain events",
}}
lang="tsx"
preview={<WatchEventPreview />}
/>
);
}
@@ -1,30 +1,32 @@
import type { Metadata } from "next";
import { AtomIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { EcosystemConnectEmbed } from "../../../../components/in-app-wallet/ecosystem";
import { Profiles } from "../../../../components/in-app-wallet/profile-sections";
import ThirdwebProvider from "../../../../components/thirdweb-provider";
import { metadataBase } from "../../../../lib/constants";
import { EcosystemConnectEmbed } from "@/components/in-app-wallet/ecosystem";
import { Profiles } from "@/components/in-app-wallet/profile-sections";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"Build a public or permissioned ecosystem by allowing third party apps and games to connect to the same accounts.",
metadataBase,
title: "Build your own Ecosystem | thirdweb",
};
const title = "Ecosystem Wallets";
const description =
"Enable global wallet-based identity across apps and games. Create public or permissioned ecosystems where users keep one account everywhere.";
export const metadata = createMetadata({
description: description,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Build a public or permissioned ecosystem by allowing third party
apps and games to connect to the same accounts.
</>
}
icon={AtomIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets/ecosystem/set-up?utm_source=playground"
title="Build your own Ecosystem"
>
<ConnectEmbedExample />
<div className="h-14" />
@@ -1,4 +1,4 @@
import type { Metadata } from "next";
import { CircleUserIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import {
AccountAvatarExample,
@@ -7,27 +7,29 @@ import {
AccountNameExample,
} from "@/components/headless-ui/account-examples";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"Headless components for rendering account information like ENS name, ENS avatar, account balance and more",
metadataBase,
title: "Account Components",
};
const title = "Account Components";
const description =
"Headless components for rendering account information like ENS name, ENS avatar, account balance and more";
export const metadata = createMetadata({
title,
description,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Headless components for rendering account information like ENS name,
ENS avatar, account balance and more
</>
}
icon={CircleUserIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/react/v5/components/account?utm_source=playground"
title="Account Components"
>
<div className="space-y-16">
<AccountNameExample />
@@ -1,28 +1,33 @@
import type { Metadata } from "next";
import { LinkIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import {
ChainIconExample,
ChainNameExample,
} from "@/components/headless-ui/chain-examples";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description: "Headless UI components for rendering chain name and icon",
metadataBase,
title: "Chain Components",
};
const title = "Chain Components";
const description = "Headless UI components for rendering chain name and icon";
export const metadata = createMetadata({
title,
description,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
icon={LinkIcon}
containerClassName="space-y-12"
description={
<>Headless UI components for rendering chain name and icon</>
}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/react/v5/components/onchain#chains?utm_source=playground"
title="Chain Components"
>
<ChainIconExample />
<ChainNameExample />
@@ -1,28 +1,33 @@
import type { Metadata } from "next";
import { WalletCardsIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import {
WalletIconExample,
WalletNameExample,
} from "@/components/headless-ui/wallet-examples";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description: "Headless UI components for rendering wallet name and icon",
metadataBase,
title: "Wallet Components",
};
const title = "Wallet Components";
const description = "Headless UI components for rendering wallet name and icon";
export const metadata = createMetadata({
title,
description,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
icon={WalletCardsIcon}
containerClassName="space-y-12"
description={
<>Headless UI components for rendering wallet name and icon</>
}
description={description}
docsLink="https://portal.thirdweb.com/react/v5/connecting-wallets/ui-components?utm_source=playground"
title="Wallet Components"
title={title}
>
<WalletIconExample />
<WalletNameExample />
@@ -1,33 +1,33 @@
import type { Metadata } from "next";
import { UserIcon } from "lucide-react";
import { CodeExample } from "@/components/code/code-example";
import { CustomLoginForm } from "@/components/in-app-wallet/custom-login-form";
import { createMetadata } from "@/lib/metadata";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { InAppConnectEmbed } from "../../../components/in-app-wallet/connect-button";
import { Profiles } from "../../../components/in-app-wallet/profile-sections";
import ThirdwebProvider from "../../../components/thirdweb-provider";
import { metadataBase } from "../../../lib/constants";
export const metadata: Metadata = {
description:
"Let users sign up with their email, phone number, social media accounts or directly with a wallet",
metadataBase,
title: "Any Auth | thirdweb in-app wallet",
};
const title = "In-App Wallets";
const description =
"Add social login, passkey, phone, or email sign-in to your app. Use built-in auth or connect your own backend with custom endpoints.";
export const metadata = createMetadata({
description,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Use any of the built-in auth methods or bring your own.
<br />
Supports custom auth endpoints to integrate with your existing user
base.
</>
}
description={description}
docsLink="https://portal.thirdweb.com/wallets/users?utm_source=playground"
title="Onboard users to web3 with any auth method"
title={title}
icon={UserIcon}
>
<UIIntegration />
<div className="h-14" />
@@ -1,86 +0,0 @@
import type { Metadata } from "next";
import { CodeExample } from "@/components/code/code-example";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { SponsoredInAppTxPreview } from "../../../../components/in-app-wallet/sponsored-tx";
import ThirdwebProvider from "../../../../components/thirdweb-provider";
import { metadataBase } from "../../../../lib/constants";
export const metadata: Metadata = {
description:
"With in-app wallets, users don't need to confirm every transaction. Combine it with smart account flag to cover gas costs for the best UX",
metadataBase,
title: "Signless Sponsored Transactions | thirdweb in-app wallet",
};
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
With in-app wallets, users {"don't"} need to confirm every
transaction.
<br />
Combine it with smart account flag to cover gas costs for the best
UX.
</>
}
docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
title="Signless Sponsored Transactions"
>
<SponsoredInAppTx />
</PageLayout>
</ThirdwebProvider>
);
}
function SponsoredInAppTx() {
return (
<CodeExample
code={`
import { inAppWallet } from "thirdweb/wallets";
import { claimTo } from "thirdweb/extensions/erc1155";
import {
ConnectButton,
TransactionButton,
} from "thirdweb/react";
import { baseSepolia } from "thirdweb/chains";
const wallets = [
inAppWallet(
{
// turn on gas sponsorship for in-app wallets
// Can use EIP4337 or EIP7702 on supported chains
executionMode: {
mode: "EIP4337",
smartAccount: { chain: baseSepolia, sponsorGas: true },
},
}),
];
function App() {
return (
<>
<ConnectButton client={client} wallets={wallets} />
{/* signless, sponsored transactions */}
<TransactionButton
transaction={() =>
claimTo({
contract,
to: "0x123...",
tokenId: 0n,
quantity: 1n,
})
}
>
Mint
</TransactionButton>
</>
);
}`}
lang="tsx"
preview={<SponsoredInAppTxPreview />}
/>
);
}
@@ -151,7 +151,7 @@ export function RightSection(props: {
);
return (
<div className="flex shrink-0 flex-col gap-4 xl:sticky xl:top-0 xl:max-h-[100vh] xl:w-[764px]">
<div className="flex shrink-0 flex-col gap-4 xl:sticky xl:top-0 xl:max-h-[100vh] xl:w-[734px]">
<TabButtons
tabs={[
{
@@ -283,11 +283,11 @@ function TabButtons(props: {
}) {
return (
<div>
<div className="flex justify-start gap-1 rounded-lg border bg-card p-2 shadow-md md:inline-flex">
<div className="flex justify-start gap-1 rounded-lg border bg-card p-1 md:inline-flex">
{props.tabs.map((tab) => (
<Button
className={cn(
"gap-2 px-4 text-base",
"gap-2 px-4",
tab.isActive
? "bg-accent text-foreground"
: "bg-transparent text-muted-foreground",
@@ -295,6 +295,7 @@ function TabButtons(props: {
key={tab.name}
onClick={tab.onClick}
variant="ghost"
size="sm"
>
{tab.name}
</Button>
@@ -0,0 +1,102 @@
"use client";
import { RectangleHorizontalIcon } from "lucide-react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import type { ConnectPlaygroundOptions } from "../components/types";
import { LeftSection } from "./LeftSection";
import { RightSection } from "./RightSection";
const defaultInAppLoginMethods: ConnectPlaygroundOptions["inAppWallet"]["methods"] =
[
"google",
"discord",
"telegram",
"farcaster",
"email",
"x",
"passkey",
"phone",
];
// NOTE: Only set the values that are actually the default values used by Connect component
const defaultConnectOptions: ConnectPlaygroundOptions = {
buttonLabel: undefined,
enableAccountAbstraction: false,
enableAuth: false,
inAppWallet: {
enabled: true,
methods: defaultInAppLoginMethods,
},
localeId: "en_US",
modalSize: "compact",
modalTitle: undefined,
modalTitleIcon: undefined,
privacyPolicyLink: undefined,
requireApproval: false,
ShowThirdwebBranding: true,
termsOfServiceLink: undefined,
theme: {
darkColorOverrides: {},
lightColorOverrides: {},
type: "dark",
},
walletIds: [
"io.metamask",
"com.coinbase.wallet",
"me.rainbow",
"io.rabby",
"io.zerion.wallet",
],
};
export function ConnectButtonPage(props: {
title: string;
description: string;
tab: string;
}) {
const { theme } = useTheme();
const [connectOptions, setConnectOptions] =
useState<ConnectPlaygroundOptions>(() => ({
...defaultConnectOptions,
theme: {
...defaultConnectOptions.theme,
type: theme === "dark" ? "dark" : "light",
},
}));
// change theme on global theme change
useEffect(() => {
setConnectOptions((prev) => ({
...prev,
theme: {
...prev.theme,
type: theme === "dark" ? "dark" : "light",
},
}));
}, [theme]);
return (
<ThirdwebProvider>
<PageLayout
icon={RectangleHorizontalIcon}
description={props.description}
docsLink="https://portal.thirdweb.com/wallets/auth?utm_source=playground"
title={props.title}
>
<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
<LeftSection
connectOptions={connectOptions}
setConnectOptions={setConnectOptions}
/>
</div>
<RightSection connectOptions={connectOptions} tab={props.tab} />
</div>
</PageLayout>
</ThirdwebProvider>
);
}
@@ -1,90 +1,31 @@
"use client";
import { createMetadata } from "@/lib/metadata";
import { ConnectButtonPage } from "./connect-button-page";
import { use, useState } from "react";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import type { ConnectPlaygroundOptions } from "../components/types";
import { LeftSection } from "./LeftSection";
import { RightSection } from "./RightSection";
const title = "Connect Button";
const description =
"Wallet connection component to enable sign-in to any 500+ EOA (external wallets) or in-app wallets via email, phone number, passkeys, or social logins";
const ogDescription =
"Plug-and-play wallet connect UI with support for 500+ wallets, passkey and social login, ERC-4337 upgrades, gasless flows, and Sign In with Ethereum.";
const defaultInAppLoginMethods: ConnectPlaygroundOptions["inAppWallet"]["methods"] =
[
"google",
"discord",
"telegram",
"farcaster",
"email",
"x",
"passkey",
"phone",
];
// NOTE: Only set the values that are actually the default values used by Connect component
const defaultConnectOptions: ConnectPlaygroundOptions = {
buttonLabel: undefined,
enableAccountAbstraction: false,
enableAuth: false,
inAppWallet: {
enabled: true,
methods: defaultInAppLoginMethods,
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "wallets",
title,
},
localeId: "en_US",
modalSize: "compact",
modalTitle: undefined,
modalTitleIcon: undefined,
privacyPolicyLink: undefined,
requireApproval: false,
ShowThirdwebBranding: true,
termsOfServiceLink: undefined,
theme: {
darkColorOverrides: {},
lightColorOverrides: {},
type: "dark",
},
walletIds: [
"io.metamask",
"com.coinbase.wallet",
"me.rainbow",
"io.rabby",
"io.zerion.wallet",
],
};
});
export default function Page(props: {
searchParams: Promise<{ tab: string }>;
export default async function Page(props: {
searchParams: Promise<{ tab: string | undefined | string[] }>;
}) {
const searchParams = use(props.searchParams);
const [connectOptions, setConnectOptions] =
useState<ConnectPlaygroundOptions>(defaultConnectOptions);
const searchParams = await props.searchParams;
return (
<ThirdwebProvider>
<PageLayout
description={
<>
A fully featured wallet connection component that allows to Connect
to 500+ external wallets, connect via email, phone number, passkey
or social logins, Convert any wallet to a ERC4337 smart wallet for
gasless transactions and provides SIWE (Sign In With Ethereum)
</>
}
docsLink="https://portal.thirdweb.com/wallets/auth?utm_source=playground"
title="ConnectButton"
>
<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
<LeftSection
connectOptions={connectOptions}
setConnectOptions={setConnectOptions}
/>
</div>
<RightSection
connectOptions={connectOptions}
tab={searchParams.tab}
/>
</div>
</PageLayout>
</ThirdwebProvider>
<ConnectButtonPage
title={title}
description={description}
tab={typeof searchParams.tab === "string" ? searchParams.tab : "modal"}
/>
);
}
@@ -1,5 +1,5 @@
import { CustomAccordion } from "@/components/ui/CustomAccordion";
import { cn } from "../../../../lib/utils";
import { cn } from "@/lib/utils";
export function CollapsibleSection(props: {
children: React.ReactNode;
@@ -10,13 +10,13 @@ export function CollapsibleSection(props: {
}) {
return (
<CustomAccordion
activeTriggerClassName="bg-card text-foreground "
activeTriggerClassName="bg-accent/50 text-foreground"
chevronClassName="size-5"
chevronPosition="right"
containerClassName="border-none"
defaultOpen={props.defaultOpen}
trigger={
<span className="flex items-center gap-3 font-semibold text-lg tracking-tight">
<span className="flex items-center gap-3 font-medium text-base tracking-tight">
<props.icon className="size-5" />
{props.title}
</span>
@@ -1,30 +1,33 @@
import type { Metadata } from "next";
import { PanelTopIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import { StyledConnectEmbed } from "@/components/styled-connect-embed";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { PageLayout } from "../../../../components/blocks/APIHeader";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"Let users sign up with their email, phone number, social media accounts or directly with a wallet. Seamlessly integrate account abstraction and SIWE auth.",
metadataBase,
title: "Sign In, Account Abstraction and SIWE Auth | thirdweb ConnectEmbed",
};
const title = "Connect Embed";
const description =
"Embeddable wallet component to manage logged in wallet states, view wallet balance, view assets, or buy and receive funds within an application.";
const ogDescription =
"Wallet component to manage logged in wallet, view wallet balance, view assets, or buy and receive funds within an application";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Create a login experience tailor-made for your app. Add your wallets
of choice, enable web2 sign-in options and create a modal that fits
your brand.
</>
}
icon={PanelTopIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets?utm_source=playground"
title="ConnectEmbed"
>
<EmbedComponent />
</PageLayout>
@@ -1,31 +1,34 @@
import type { Metadata } from "next";
import { SquircleDashedIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import { HooksPreview } from "@/components/sign-in/hooks";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { createMetadata } from "@/lib/metadata";
import { ModalPreview } from "../../../../components/sign-in/modal";
export const metadata: Metadata = {
description:
"Let users sign up with their email, phone number, social media accounts or directly with a wallet. Seamlessly integrate account abstraction and SIWE auth.",
metadataBase,
title: "Sign In, Account Abstraction and SIWE Auth | thirdweb Connect",
};
const title = "Headless Connect";
const description =
"Create sign-in flow with your choice of wallets and sign-in options built custom to your branding. Use React hooks for full UI control and built-in wallet state management.";
const ogDescription =
"Build a custom login flow with your choice of wallets and sign-in options. Use React hooks for full UI control and built-in wallet state management.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Create a login experience tailor-made for your app. Add your wallets
of choice, enable web2 sign-in options and create a modal that fits
your brand.
</>
}
icon={SquircleDashedIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets?utm_source=playground"
title="Headless"
>
<div className="flex flex-col gap-14">
<BuildCustomUISection />
@@ -1,29 +1,33 @@
import type { Metadata } from "next";
import { GlobeIcon } from "lucide-react";
import { PageLayout } from "@/components/blocks/APIHeader";
import { CodeExample } from "@/components/code/code-example";
import { SocialProfiles } from "@/components/social/social-profiles";
import ThirdwebProvider from "@/components/thirdweb-provider";
import { metadataBase } from "@/lib/constants";
import { PageLayout } from "../../../components/blocks/APIHeader";
import { createMetadata } from "@/lib/metadata";
export const metadata: Metadata = {
description:
"Retrieve any user's onchain identity from popular protocols like ENS, Lens, Farcaster, and more.",
metadataBase,
title: "Social APIs | thirdweb Connect",
};
const title = "Social Profiles";
const description =
"Enhance wallet authentication and context about user profiles with social identity data from ENS, Lens, and Farcaster";
const ogDescription =
"Enhance wallet authentication with social identity data from ENS, Lens, and Farcaster. Gain context-rich profiles on user login.";
export const metadata = createMetadata({
description: ogDescription,
title,
image: {
icon: "wallets",
title,
},
});
export default function Page() {
return (
<ThirdwebProvider>
<PageLayout
description={
<>
Gain context about your users and their profiles across other apps
as soon as they sign into your app.
</>
}
icon={GlobeIcon}
title={title}
description={description}
docsLink="https://portal.thirdweb.com/wallets?utm_source=playground"
title="Get any user's onchain identity" // TODO: update this once we have Social API docs
>
<UserProfiles />
</PageLayout>
@@ -0,0 +1,37 @@
"use client";
import { MoonIcon, SunIcon } from "lucide-react";
import { useTheme } from "next-themes";
import { ClientOnly } from "@/components/ClientOnly";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
export function ThemeToggle() {
const { setTheme, theme } = useTheme();
return (
<div className="border-t pt-2">
<ClientOnly
ssr={<Skeleton className="size-[34px] rounded-full border bg-accent" />}
>
<Button
aria-label="Toggle theme"
className="w-full text-muted-foreground hover:text-foreground px-2 py-1.5 text-left justify-start gap-2 capitalize h-auto font-normal"
onClick={() => {
setTheme(theme === "dark" ? "light" : "dark");
}}
size="sm"
variant="ghost"
>
{theme === "light" ? (
<SunIcon className="size-4" />
) : (
<MoonIcon className="size-4" />
)}
{theme}
</Button>
</ClientOnly>
</div>
);
}
@@ -11,26 +11,8 @@ import {
import { shortenAddress } from "thirdweb/utils";
import { inAppWallet, socialIcons } from "thirdweb/wallets/in-app";
import { THIRDWEB_CLIENT } from "../../lib/client";
import { StyledConnectButton } from "../styled-connect-button";
import { Button } from "../ui/button";
export function ConnectSmartAccountPreview() {
return (
<div className="flex flex-col">
<StyledConnectButton
accountAbstraction={{
chain: baseSepolia,
sponsorGas: true,
}}
detailsModal={{
// We hide the in-app wallet so they can't switch to it
hiddenWallets: ["inApp"],
}}
/>
</div>
);
}
export function ConnectSmartAccountCustomPreview() {
const account = useActiveAccount();
const wallet = useActiveWallet();
@@ -1,4 +1,4 @@
import { BookOpenIcon, PresentationIcon } from "lucide-react";
import { ArrowUpRightIcon } from "lucide-react";
import Link from "next/link";
import { cn } from "../../lib/utils";
import { Button } from "../ui/button";
@@ -7,38 +7,40 @@ export function PageHeader(props: {
title: string;
description: React.ReactNode;
docsLink: string;
icon: React.FC<{ className?: string }>;
containerClassName?: string;
}) {
return (
<div className="mb-8 border-b py-10">
<div className="container grid grid-cols-1 items-start justify-between gap-6 lg:grid-cols-[1fr_340px] lg:flex-row">
<div className="mb-8 border-b py-8 relative">
<div
className={cn(
"container max-w-7xl flex-col lg:flex-row flex items-start justify-between gap-4",
props.containerClassName,
)}
>
{/* Left */}
<div>
<h1 className="mb-1 font-bold text-3xl tracking-tight lg:text-4xl">
<div className="flex mb-4">
<div className="rounded-full border bg-card p-2.5">
<props.icon className="size-6 text-muted-foreground" />
</div>
</div>
<h1 className="mb-0.5 font-semibold text-3xl tracking-tight">
{props.title}
</h1>
<p className="text-balance text-base text-muted-foreground">
<p className="text-sm md:text-base text-muted-foreground max-w-4xl">
{props.description}
</p>
</div>
{/* right */}
<div className="flex flex-col gap-3 md:flex-row">
<Button asChild>
<Link
href={"https://thirdweb.com/dashboard?utm_source=playground"}
target="_blank"
>
<PresentationIcon className="mr-2 h-4 w-4" />
Add instantly
</Link>
</Button>
<Button asChild className="bg-card" variant="outline">
<Link href={props.docsLink} target="_blank">
<BookOpenIcon className="mr-2 h-4 w-4" />
Documentation
</Link>
</Button>
</div>
<Button asChild className="rounded-full gap-2 px-4" size="sm">
<Link href={props.docsLink} target="_blank">
Documentation
<ArrowUpRightIcon className="size-4" />
</Link>
</Button>
</div>
</div>
);
@@ -50,15 +52,17 @@ export function PageLayout(props: {
docsLink: string;
children: React.ReactNode;
containerClassName?: string;
icon: React.FC<{ className?: string }>;
}) {
return (
<main>
<PageHeader
description={props.description}
docsLink={props.docsLink}
icon={props.icon}
title={props.title}
/>
<div className={cn("container pb-8", props.containerClassName)}>
<div className={cn("container max-w-7xl pb-8", props.containerClassName)}>
{props.children}
</div>
</main>
@@ -1,11 +1,11 @@
"use client";
import { useCallback, useMemo } from "react";
import { ChainIcon } from "@/components/blocks/ChainIcon";
import { MultiSelect } from "@/components/blocks/multi-select";
import { Badge } from "@/components/ui/badge";
import { useAllChainsData } from "../../app/hooks/chains";
import { SelectWithSearch } from "../ui/select-with-search";
import { ChainIcon } from "./ChainIcon";
import { MultiSelect } from "./multi-select";
import { SelectWithSearch } from "@/components/ui/select-with-search";
import { useAllChainsData } from "@/hooks/chains";
function cleanChainName(chainName: string) {
return chainName.replace("Mainnet", "");
@@ -0,0 +1,434 @@
"use client";
import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";
import Link from "next/link";
import { Suspense, useMemo, useState } from "react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { DynamicHeight } from "@/components/ui/DynamicHeight";
import { NavLink } from "@/components/ui/NavLink";
import { Separator } from "@/components/ui/separator";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubItem,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { useFullPath } from "../../hooks/full-path";
import { ThirdwebIcon } from "../../icons/ThirdwebMiniLogo";
import { ThemeToggle } from "../ThemeToggle";
type ShadcnSidebarBaseLink = {
href: string;
label: React.ReactNode;
exactMatch?: boolean;
icon?: React.FC<{ className?: string }>;
};
export type ShadcnSidebarLink =
| ShadcnSidebarBaseLink
| {
group: string;
links: ShadcnSidebarLink[];
}
| {
separator: true;
className?: string;
}
| {
subMenu: {
icon?: React.FC<{ className?: string }>;
label: string;
};
links: ShadcnSidebarLink[];
};
export function FullWidthSidebarLayout(props: {
contentSidebarLinks: ShadcnSidebarLink[];
footerSidebarLinks?: ShadcnSidebarLink[];
children: React.ReactNode;
className?: string;
}) {
const { contentSidebarLinks, children, footerSidebarLinks } = props;
const sidebar = useSidebar();
return (
<div
className={cn(
"relative flex w-full flex-1 overflow-y-hidden",
props.className,
)}
>
{/* left - sidebar */}
<Sidebar collapsible="icon" side="left">
<div
className={cn("p-5", !sidebar.open && "p-0 py-5 flex justify-center")}
>
<Link href="/" className="flex items-center gap-2">
<ThirdwebIcon
className="size-5 text-muted-foreground"
isMonoChrome
/>
{sidebar.open && (
<span
className={cn(
"font-semibold text-lg tracking-tight leading-none",
)}
>
Playground
</span>
)}
</Link>
</div>
<SidebarSeparator />
<SidebarContent className="p-2 no-scrollbar">
<RenderSidebarMenu links={contentSidebarLinks} />
</SidebarContent>
{footerSidebarLinks && (
<SidebarFooter className="pb-3 pt-0">
<RenderSidebarMenu links={footerSidebarLinks} />
<ThemeToggle />
</SidebarFooter>
)}
<SidebarRail />
</Sidebar>
{/* right - content */}
<div className="flex h-full flex-grow flex-col overflow-y-auto">
<div className="border-b p-4 lg:hidden">
<div className="flex items-center gap-2">
<ThirdwebIcon
className="size-6 text-muted-foreground"
isMonoChrome
/>
<span className="font-semibold text-lg leading-none tracking-tight">
Playground
</span>
</div>
</div>
<MobileSidebarTrigger
links={[...contentSidebarLinks, ...(footerSidebarLinks || [])]}
/>
<main className="flex min-w-0 grow flex-col max-sm:w-full">
{children}
</main>
</div>
</div>
);
}
function MobileSidebarTrigger(props: { links: ShadcnSidebarLink[] }) {
return (
<Suspense fallback={null}>
<MobileSidebarTriggerInner links={props.links} />
</Suspense>
);
}
function MobileSidebarTriggerInner(props: { links: ShadcnSidebarLink[] }) {
const activeLink = useActiveShadcnSidebarLink(props.links);
return (
<div className="flex items-center gap-3 border-b px-4 py-4 lg:hidden">
<SidebarTrigger className="size-4" />
<Separator
className="h-4 bg-muted-foreground/50"
orientation="vertical"
/>
{activeLink && "subMenu" in activeLink && (
<>
<span className="text-sm">{activeLink.subMenu.label}</span>
<ChevronRightIcon className="size-4 text-muted-foreground/50 -mx-1.5" />
</>
)}
{activeLink && "label" in activeLink && (
<span className="text-sm">{activeLink.label}</span>
)}
</div>
);
}
function useActiveShadcnSidebarLink(links: ShadcnSidebarLink[]) {
const pathname = useFullPath();
const activeLink = useMemo(() => {
const isActive = (link: ShadcnSidebarLink): boolean => {
if ("href" in link) {
// Handle exact match
if (link.exactMatch) {
return link.href === pathname;
}
// Handle prefix match (ensure we don't match partial paths)
return pathname.startsWith(link.href);
}
if ("links" in link) {
return link.links.some(isActive);
}
return false;
};
return links.find(isActive);
}, [links, pathname]);
return activeLink;
}
function useIsSubnavActive(links: ShadcnSidebarLink[]) {
const pathname = useFullPath();
const isSubnavActive = useMemo(() => {
const isActive = (link: ShadcnSidebarLink): boolean => {
if ("href" in link) {
// Handle exact match
if (link.exactMatch) {
return link.href === pathname;
}
return pathname.startsWith(link.href);
}
if ("links" in link) {
return link.links.some(isActive);
}
return false;
};
return links.some(isActive);
}, [links, pathname]);
return isSubnavActive;
}
function RenderSidebarGroup(props: {
sidebarLinks: ShadcnSidebarLink[];
groupName: string;
}) {
return (
<Suspense fallback={null}>
<RenderSidebarGroupInner {...props} />
</Suspense>
);
}
function RenderSidebarGroupInner(props: {
sidebarLinks: ShadcnSidebarLink[];
groupName: string;
}) {
return (
<SidebarGroup className="p-0">
<SidebarMenuItem>
<SidebarGroupLabel> {props.groupName}</SidebarGroupLabel>
<SidebarGroupContent>
<RenderSidebarMenu links={props.sidebarLinks} />
</SidebarGroupContent>
</SidebarMenuItem>
</SidebarGroup>
);
}
function RenderSidebarSubmenu(props: {
links: ShadcnSidebarLink[];
subMenu: Omit<ShadcnSidebarBaseLink, "href" | "exactMatch">;
}) {
return (
<Suspense fallback={null}>
<RenderSidebarSubmenuInner {...props} />
</Suspense>
);
}
function RenderSidebarSubmenuInner(props: {
links: ShadcnSidebarLink[];
subMenu: Omit<ShadcnSidebarBaseLink, "href" | "exactMatch">;
}) {
const sidebar = useSidebar();
const isSubnavActive = useIsSubnavActive(props.links);
const [_open, setOpen] = useState<boolean | undefined>(undefined);
const open = _open === undefined ? isSubnavActive : _open;
// Add null check for links
if (!props.links || props.links.length === 0) {
return null;
}
return (
<SidebarMenu>
<DynamicHeight transition="height 200ms ease">
<SidebarMenuItem>
<Collapsible
className="group/collapsible [&[data-state=open]>button>svg[data-chevron]]:rotate-180"
defaultOpen={open}
open={open}
onOpenChange={setOpen}
>
<CollapsibleTrigger asChild>
<SidebarMenuButton
className={cn(
"text-sm text-muted-foreground whitespace-nowrap hover:bg-accent hover:text-foreground",
isSubnavActive && "text-foreground",
)}
>
{props.subMenu.icon && (
<props.subMenu.icon className="size-4" />
)}
<span>{props.subMenu.label}</span>
{sidebar.open && (
<ChevronDownIcon
data-chevron
className="transition-transform ml-auto"
/>
)}
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent className="mt-0.5 [&>ul]:mr-0">
<SidebarMenuSub className="pr-0 pl-2">
{props.links.map((link, index) => {
if ("href" in link) {
return (
<SidebarMenuSubItem key={link.href}>
<NavLink
key={link.href}
activeClassName="text-foreground bg-accent "
className="flex items-center gap-2 text-muted-foreground text-sm hover:bg-accent hover:text-foreground px-2 py-1.5 rounded-lg w-full"
exactMatch={link.exactMatch}
href={link.href}
onClick={() => {
sidebar.setOpenMobile(false);
}}
>
{link.icon && <link.icon className="size-4" />}
<span>{link.label}</span>
</NavLink>
</SidebarMenuSubItem>
);
}
if ("subMenu" in link) {
return (
<RenderSidebarSubmenu
key={`submenu_${link.subMenu.label}_${index}`}
links={link.links}
subMenu={link.subMenu}
/>
);
}
if ("group" in link) {
return (
<RenderSidebarGroup
key={`group_${link.group}_${index}`}
groupName={link.group}
sidebarLinks={link.links}
/>
);
}
return null;
})}
</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>
</SidebarMenuItem>
</DynamicHeight>
</SidebarMenu>
);
}
function RenderSidebarMenu(props: { links: ShadcnSidebarLink[] }) {
const sidebar = useSidebar();
// Add null check for links
if (!props.links || props.links.length === 0) {
return null;
}
return (
<SidebarMenu className="gap-1.5">
{props.links.map((link, idx) => {
// link
if ("href" in link) {
return (
<SidebarMenuSubItem key={link.href}>
<SidebarMenuButton asChild>
<NavLink
activeClassName="text-foreground bg-accent"
className="flex items-center gap-2 text-muted-foreground text-sm hover:bg-accent"
exactMatch={link.exactMatch}
href={link.href}
onClick={() => {
sidebar.setOpenMobile(false);
}}
>
{link.icon && <link.icon className="size-4" />}
<span>{link.label}</span>
</NavLink>
</SidebarMenuButton>
</SidebarMenuSubItem>
);
}
// separator
if ("separator" in link) {
return (
<SidebarSeparator
className={cn("my-1", link.className)}
// biome-ignore lint/suspicious/noArrayIndexKey:ok
key={idx}
/>
);
}
// subnav
if ("subMenu" in link) {
return (
<RenderSidebarSubmenu
key={`submenu_${link.subMenu.label}_${idx}`}
links={link.links}
subMenu={link.subMenu}
/>
);
}
// group
if ("group" in link) {
return (
<RenderSidebarGroup
groupName={link.group}
sidebarLinks={link.links}
key={`group_${link.group}_${idx}`}
/>
);
}
return null;
})}
</SidebarMenu>
);
}
@@ -145,7 +145,7 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
// scroll to top when options change
const popoverElRef = useRef<HTMLDivElement>(null);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
// biome-ignore lint/correctness/useExhaustiveDependencies: ok
useEffect(() => {
const scrollContainer =
popoverElRef.current?.querySelector("[data-scrollable]");
@@ -22,7 +22,7 @@ export function RenderCode(props: {
props.scrollableContainerClassName,
)}
scrollableClassName={cn("p-4", props.scrollableClassName)}
shadowColor="hsl(var(--muted))"
shadowColor="hsl(var(--card))"
>
<div
// biome-ignore lint/security/noDangerouslySetInnerHtml: we know what we're doing here
@@ -23,11 +23,11 @@ export const CodeExample: React.FC<CodeExampleProps> = ({
return (
<div className="relative z-0">
{header && (
<div className="mb-4 space-y-0.5">
<h2 className="font-semibold text-2xl tracking-tight">
<div className="mb-4">
<h2 className="font-semibold text-xl tracking-tight capitalize">
{header.title}
</h2>
<p className="max-w-3xl text-muted-foreground ">
<p className="max-w-4xl text-muted-foreground text-balance text-sm md:text-base">
{header.description}
</p>
</div>
@@ -44,7 +44,7 @@ export const CodeExample: React.FC<CodeExampleProps> = ({
<div className="flex grow flex-col">
<TabName icon={EyeIcon} name="Preview" />
<ClientOnly
className="relative grid h-full min-h-[300px] place-items-center bg-background py-20"
className="relative grid h-full min-h-[300px] place-items-center bg-card py-20"
ssr={null}
>
{preview}
@@ -4,7 +4,6 @@ import { WalletIcon, WalletName, WalletProvider } from "thirdweb/react";
export function WalletIconBasicPreview() {
return (
// biome-ignore lint/nursery/useUniqueElementIds:ID is not the html attribute in this case
<WalletProvider id="io.metamask">
<WalletIcon
className="h-20 w-20 rounded-full"
@@ -16,7 +15,6 @@ export function WalletIconBasicPreview() {
export function WalletNameBasicPreview() {
return (
// biome-ignore lint/nursery/useUniqueElementIds:ID is not the html attribute in this case
<WalletProvider id="io.metamask">
<WalletName loadingComponent={<span>Loading...</span>} />
</WalletProvider>
@@ -25,7 +23,6 @@ export function WalletNameBasicPreview() {
export function WalletNameFormatPreview() {
return (
// biome-ignore lint/nursery/useUniqueElementIds:ID is not the html attribute in this case
<WalletProvider id="io.metamask">
<WalletName
formatFn={(str: string) => `${str} Wallet`}
@@ -5,19 +5,17 @@ import { THIRDWEB_CLIENT } from "../../lib/client";
export function BuyMerchPreview() {
return (
<>
<CheckoutWidget
amount={"2"}
chain={base}
client={THIRDWEB_CLIENT}
description="Get your ticket for The Midnight Live"
feePayer="seller"
image="https://images.unsplash.com/photo-1501281668745-f7f57925c3b4?w=500&h=300&fit=crop"
name="Concert Ticket"
seller="0xEb0effdFB4dC5b3d5d3aC6ce29F3ED213E95d675"
theme="light"
tokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
/>
</>
<CheckoutWidget
amount={"2"}
chain={base}
client={THIRDWEB_CLIENT}
description="Get your ticket for The Midnight Live"
feePayer="seller"
image="https://images.unsplash.com/photo-1501281668745-f7f57925c3b4?w=500&h=300&fit=crop"
name="Concert Ticket"
seller="0xEb0effdFB4dC5b3d5d3aC6ce29F3ED213E95d675"
theme="light"
tokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
/>
);
}
@@ -0,0 +1,42 @@
"use client";
import Link from "next/link";
import { Suspense } from "react";
import { cn } from "@/lib/utils";
import { useFullPath } from "../../hooks/full-path";
export type NavButtonProps = {
className?: string;
activeClassName?: string;
href: string;
exactMatch?: boolean;
onClick?: () => void;
};
export function NavLink(props: React.PropsWithChildren<NavButtonProps>) {
return (
<Suspense>
<NavLinkInner {...props} />
</Suspense>
);
}
function NavLinkInner(props: React.PropsWithChildren<NavButtonProps>) {
const pathname = useFullPath();
const isActive = props.exactMatch
? pathname === props.href
: pathname.startsWith(props.href);
return (
<Link
className={cn(props.className, isActive && props.activeClassName)}
href={props.href}
onClick={props.onClick}
prefetch={false}
target={props.href.startsWith("http") ? "_blank" : undefined}
>
{props.children}
</Link>
);
}
@@ -17,10 +17,10 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useAllChainsData } from "@/hooks/chains";
import { useTokensData } from "@/hooks/useTokensData";
import type { TokenMetadata } from "@/lib/types";
import { cn, fallbackChainIcon, replaceIpfsUrl } from "@/lib/utils";
import { useAllChainsData } from "../../app/hooks/chains";
const checksummedNativeTokenAddress = getAddress(NATIVE_TOKEN_ADDRESS);
@@ -0,0 +1,11 @@
"use client";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
@@ -20,7 +20,7 @@ const SelectTrigger = React.forwardRef<
>(({ className, children, chevronClassName, ...props }, ref) => (
<SelectPrimitive.Trigger
className={cn(
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 [&>span]:w-full",
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
ref={ref}
@@ -119,7 +119,7 @@ const SelectItem = React.forwardRef<
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>span]:w-full",
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
ref={ref}
@@ -127,7 +127,7 @@ const SelectItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
@@ -0,0 +1,143 @@
"use client";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { XIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border",
{
defaultVariants: {
side: "right",
},
variants: {
side: {
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
},
},
},
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
className={cn(sheetVariants({ side }), className)}
ref={ref}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm border-border opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 border-border text-center sm:text-left",
className,
)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse border-border sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
className={cn(
"border-border font-semibold text-foreground text-lg",
className,
)}
ref={ref}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
className={cn("border-border text-muted-foreground text-sm", className)}
ref={ref}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};
+733 -188
View File
@@ -1,229 +1,774 @@
/** biome-ignore-all lint/suspicious/noDocumentCookie: FIXME */
"use client";
import clsx from "clsx";
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";
import { Suspense, useRef } from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { ClientOnly } from "../ClientOnly";
import { CustomAccordion } from "./CustomAccordion";
export type LinkMeta = {
name: string;
href: string;
crossedOut?: boolean;
const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "19rem";
const SIDEBAR_WIDTH_MOBILE = "19rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContextProps = {
state: "expanded" | "collapsed";
open: boolean;
setOpen: (open: boolean) => void;
openMobile: boolean;
setOpenMobile: (open: boolean) => void;
isMobile: boolean;
toggleSidebar: () => void;
};
export type LinkGroup = {
name: string;
href?: string;
links: SidebarLink[];
expanded?: boolean;
/**
* If set to false, the group will not be rendered as an accordion
* @defaultValue true
*/
isCollapsible?: boolean;
};
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
export type SidebarLink = LinkMeta | LinkGroup | { separator: true };
type ReferenceSideBarProps = {
links: SidebarLink[];
onLinkClick?: () => void;
};
export function Sidebar(props: ReferenceSideBarProps) {
return (
<ul className="transform-gpu">
{props.links.map((link, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
<li key={i}>
<Suspense fallback={null}>
<SidebarItem link={link} onLinkClick={props.onLinkClick} />
</Suspense>
</li>
))}
</ul>
);
}
function SidebarItem(props: { link: SidebarLink; onLinkClick?: () => void }) {
const href = useCurrentHref();
if ("separator" in props.link) {
return <hr className="my-2 border-t" />;
function useSidebar() {
const context = React.useContext(SidebarContext);
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.");
}
const isActive = props.link.href ? isSamePage(href, props.link.href) : false;
return context;
}
const { link } = props;
if ("links" in link) {
if (link.isCollapsible === false) {
const SidebarProvider = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
>(
(
{
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
},
ref,
) => {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen);
const open = openProp ?? _open;
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value;
if (setOpenProp) {
setOpenProp(openState);
} else {
_setOpen(openState);
}
// This sets the cookie to keep the sidebar state.
// eslint-disable-next-line react-compiler/react-compiler
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
[setOpenProp, open],
);
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile
? setOpenMobile((open) => !open)
: setOpen((open) => !open);
}, [isMobile, setOpen]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault();
toggleSidebar();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [toggleSidebar]);
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed";
const contextValue = React.useMemo<SidebarContextProps>(
() => ({
isMobile,
open,
openMobile,
setOpen,
setOpenMobile,
state,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
);
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
className={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
className,
)}
ref={ref}
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
{...props}
>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
);
},
);
SidebarProvider.displayName = "SidebarProvider";
const Sidebar = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none";
}
>(
(
{
side = "left",
variant = "sidebar",
collapsible = "offcanvas",
className,
children,
...props
},
ref,
) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === "none") {
return (
<DocSidebarNonCollapsible
key={link.name}
linkGroup={link}
onLinkClick={props.onLinkClick}
/>
<div
className={cn(
"flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
className,
)}
ref={ref}
{...props}
>
{children}
</div>
);
}
return (
<SidebarCategory
key={link.name}
linkGroup={link}
onLinkClick={props.onLinkClick}
/>
);
}
return (
<Link
className={clsx(
"block overflow-hidden text-ellipsis py-1 font-medium transition-colors duration-300 hover:text-foreground lg:text-base",
isActive ? "text-foreground" : "text-muted-foreground",
"crossedOut" in link && link.crossedOut && "line-through",
)}
href={link.href}
onClick={props.onLinkClick}
>
{link.name}
</Link>
);
}
function DocSidebarNonCollapsible(props: {
linkGroup: LinkGroup;
onLinkClick?: () => void;
}) {
const currentHref = useCurrentHref();
const { href, name, links } = props.linkGroup;
const isCategoryActive = href ? isSamePage(currentHref, href) : false;
return (
<div className="mb-4">
<div className="mb-1 flex items-center gap-2">
{href ? (
<Link
className={cn(
"block font-medium text-muted-foreground hover:text-foreground lg:text-base",
isCategoryActive && "!text-foreground",
)}
href={href}
if (isMobile) {
return (
<Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>
<SheetContent
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
data-mobile="true"
data-sidebar="sidebar"
side={side}
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
>
{name}
</Link>
) : (
<div className="font-medium text-lg">{name}</div>
)}
<SheetHeader className="sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
</SheetHeader>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
);
}
return (
<div
className="group peer relative hidden flex-shrink-0 text-sidebar-foreground md:block"
data-collapsible={state === "collapsed" ? collapsible : ""}
data-side={side}
data-state={state}
data-variant={variant}
ref={ref}
>
{/* This is what handles the sidebar gap on desktop */}
<div
className={cn(
"relative w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
)}
/>
<div
className={cn(
"absolute inset-y-0 z-10 hidden h-full w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
className,
)}
{...props}
>
<div
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
data-sidebar="sidebar"
>
{children}
</div>
</div>
</div>
<ul className="mb-8 flex flex-col">
{links.map((link, i) => {
return (
// biome-ignore lint/suspicious/noArrayIndexKey: order is static
<li key={i}>
<SidebarItem link={link} onLinkClick={props.onLinkClick} />
</li>
);
})}
</ul>
</div>
);
},
);
Sidebar.displayName = "Sidebar";
const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar();
return (
<Button
className={cn("h-7 w-7", className)}
data-sidebar="trigger"
onClick={(event) => {
onClick?.(event);
toggleSidebar();
}}
ref={ref}
size="icon"
variant="ghost"
{...props}
>
<PanelLeftIcon />
<span className="sr-only">Toggle Sidebar</span>
</Button>
);
}
});
SidebarTrigger.displayName = "SidebarTrigger";
function useCurrentHref() {
const pathname = usePathname();
const searchParams = useSearchParams();
return decodeURIComponent(
`${pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`,
const SidebarRail = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button">
>(({ className, ...props }, ref) => {
const { toggleSidebar } = useSidebar();
return (
<button
aria-label="Toggle Sidebar"
className={cn(
"-translate-x-1/2 group-data-[side=left]:-right-4 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex",
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className,
)}
data-sidebar="rail"
onClick={toggleSidebar}
ref={ref}
tabIndex={-1}
title="Toggle Sidebar"
{...props}
/>
);
}
});
SidebarRail.displayName = "SidebarRail";
function SidebarCategory(props: {
linkGroup: LinkGroup;
onLinkClick?: () => void;
}) {
const currentPageHref = useCurrentHref();
const { href, name, links, expanded } = props.linkGroup;
const isCategoryActive = href ? isSamePage(currentPageHref, href) : false;
const hasActiveHref = containsActiveHref(
{
href: href,
links: links,
name: name,
},
currentPageHref,
const SidebarInset = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"main">
>(({ className, ...props }, ref) => {
return (
<main
className={cn(
"relative flex w-full flex-1 flex-col bg-background",
"md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className,
)}
ref={ref}
{...props}
/>
);
});
SidebarInset.displayName = "SidebarInset";
const defaultOpen = isCategoryActive || !!(hasActiveHref || expanded);
const SidebarInput = React.forwardRef<
React.ElementRef<typeof Input>,
React.ComponentProps<typeof Input>
>(({ className, ...props }, ref) => {
return (
<Input
className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
className,
)}
data-sidebar="input"
ref={ref}
{...props}
/>
);
});
SidebarInput.displayName = "SidebarInput";
const triggerRef = useRef<HTMLDivElement>(null);
const SidebarHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
className={cn("flex flex-col gap-2 p-2", className)}
data-sidebar="header"
ref={ref}
{...props}
/>
);
});
SidebarHeader.displayName = "SidebarHeader";
const triggerElContent = (
const SidebarFooter = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
className={cn("flex flex-col gap-2 p-2", className)}
data-sidebar="footer"
ref={ref}
{...props}
/>
);
});
SidebarFooter.displayName = "SidebarFooter";
const SidebarSeparator = React.forwardRef<
React.ElementRef<typeof Separator>,
React.ComponentProps<typeof Separator>
>(({ className, ...props }, ref) => {
return (
<Separator
className={cn("mx-2 w-auto bg-sidebar-border", className)}
data-sidebar="separator"
ref={ref}
{...props}
/>
);
});
SidebarSeparator.displayName = "SidebarSeparator";
const SidebarContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
className={cn(
isCategoryActive && "!font-semibold !text-foreground",
"group-hover:!text-foreground text-muted-foreground",
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className,
)}
>
<div className="flex gap-2 py-1 font-medium" ref={triggerRef}>
{name}
</div>
</div>
data-sidebar="content"
ref={ref}
{...props}
/>
);
});
SidebarContent.displayName = "SidebarContent";
const triggerEl = href ? (
<Link className={cn("block w-full text-left font-medium")} href={href}>
{triggerElContent}
</Link>
) : (
triggerElContent
const SidebarGroup = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
data-sidebar="group"
ref={ref}
{...props}
/>
);
});
SidebarGroup.displayName = "SidebarGroup";
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div";
return (
<ClientOnly ssr={triggerEl}>
<CustomAccordion
chevronPosition="right"
containerClassName="border-none"
defaultOpen={defaultOpen}
trigger={triggerEl}
triggerContainerClassName="lg:text-base group"
>
<ul className="flex flex-col border-l pl-4">
{links.map((link, i) => {
return (
// biome-ignore lint/suspicious/noArrayIndexKey: order is static
<li key={i}>
<SidebarItem link={link} onLinkClick={props.onLinkClick} />
</li>
);
})}
</ul>
</CustomAccordion>
</ClientOnly>
<Comp
className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
data-sidebar="group-label"
ref={ref}
{...props}
/>
);
}
});
SidebarGroupLabel.displayName = "SidebarGroupLabel";
function containsActiveHref(
sidebarlink: SidebarLink,
pathname: string,
): boolean {
if ("links" in sidebarlink) {
return sidebarlink.links.some((link) => containsActiveHref(link, pathname));
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:-inset-2 after:absolute after:md:hidden",
"group-data-[collapsible=icon]:hidden",
className,
)}
data-sidebar="group-action"
ref={ref}
{...props}
/>
);
});
SidebarGroupAction.displayName = "SidebarGroupAction";
const SidebarGroupContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
className={cn("w-full text-sm", className)}
data-sidebar="group-content"
ref={ref}
{...props}
/>
));
SidebarGroupContent.displayName = "SidebarGroupContent";
const SidebarMenu = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
data-sidebar="menu"
ref={ref}
{...props}
/>
));
SidebarMenu.displayName = "SidebarMenu";
const SidebarMenuItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li
className={cn("group/menu-item relative", className)}
data-sidebar="menu-item"
ref={ref}
{...props}
/>
));
SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
defaultVariants: {
size: "default",
variant: "default",
},
variants: {
size: {
default: "h-8 text-sm",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
sm: "h-7 text-xs",
},
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
},
},
);
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>
>(
(
{
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
},
ref,
) => {
const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar();
const button = (
<Comp
className={cn(sidebarMenuButtonVariants({ size, variant }), className)}
data-active={isActive}
data-sidebar="menu-button"
data-size={size}
ref={ref}
{...props}
/>
);
if (!tooltip) {
return button;
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
};
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
align="center"
hidden={state !== "collapsed" || isMobile}
side="right"
{...tooltip}
/>
</Tooltip>
);
},
);
SidebarMenuButton.displayName = "SidebarMenuButton";
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
showOnHover?: boolean;
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
if ("separator" in sidebarlink) {
return false;
return (
<Comp
className={cn(
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:-inset-2 after:absolute after:md:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
className,
)}
data-sidebar="menu-action"
ref={ref}
{...props}
/>
);
});
SidebarMenuAction.displayName = "SidebarMenuAction";
const SidebarMenuBadge = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
className={cn(
"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
className,
)}
data-sidebar="menu-badge"
ref={ref}
{...props}
/>
));
SidebarMenuBadge.displayName = "SidebarMenuBadge";
const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
showIcon?: boolean;
}
>(({ className, showIcon = false, ...props }, ref) => {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
}, []);
if (isSamePage(pathname, sidebarlink.href)) {
return true;
return (
<div
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
data-sidebar="menu-skeleton"
ref={ref}
{...props}
>
{showIcon && (
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="h-4 max-w-[--skeleton-width] flex-1"
data-sidebar="menu-skeleton-text"
style={
{
"--skeleton-width": width,
} as React.CSSProperties
}
/>
</div>
);
});
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
const SidebarMenuSub = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
className={cn(
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden",
className,
)}
data-sidebar="menu-sub"
ref={ref}
{...props}
/>
));
SidebarMenuSub.displayName = "SidebarMenuSub";
const SidebarMenuSubItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ ...props }, ref) => <li ref={ref} {...props} />);
SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<"a"> & {
asChild?: boolean;
size?: "sm" | "md";
isActive?: boolean;
}
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return false;
}
return (
<Comp
className={cn(
"-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className,
)}
data-active={isActive}
data-sidebar="menu-sub-button"
data-size={size}
ref={ref}
{...props}
/>
);
});
SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
function isSamePage(currentPageHref: string, testHref: string): boolean {
return currentPageHref === testHref;
}
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
};
@@ -0,0 +1,50 @@
import { cn } from "@/lib/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-skeleton rounded-md bg-muted", className)}
{...props}
/>
);
}
function SkeletonContainer<T>(props: {
loadedData?: T;
skeletonData: T;
className?: string;
render: (data: T) => React.ReactNode;
style?: React.CSSProperties;
}) {
const isLoading = props.loadedData === undefined;
return (
<div
aria-hidden={isLoading ? "true" : "false"}
className={cn(
isLoading && "inline-block animate-pulse rounded-lg bg-muted",
props.className,
)}
style={props.style}
>
<div className={cn(isLoading && "invisible")}>
<div
className={cn(
"transition-opacity duration-300",
isLoading ? "opacity-0" : "opacity-100",
)}
>
{props.render(
props.loadedData === undefined
? props.skeletonData
: props.loadedData,
)}
</div>
</div>
</div>
);
}
export { Skeleton, SkeletonContainer };
@@ -0,0 +1,12 @@
"use client";
import { usePathname, useSearchParams } from "next/navigation";
export function useFullPath() {
const pathname = usePathname();
const searchParams = useSearchParams();
const searchParamsString = searchParams.toString();
return decodeURIComponent(
`${pathname}${searchParamsString ? `?${searchParamsString}` : ""}`,
);
}
@@ -0,0 +1,21 @@
import * as React from "react";
const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener("change", onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener("change", onChange);
}, []);
return !!isMobile;
}
@@ -0,0 +1,21 @@
export function ContractIcon(props: { className?: string }) {
return (
<svg
className={props.className}
fill="none"
height="24"
role="presentation"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 11H8M10 15H8M16 7H8M20 6.8V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V6.8C4 5.11984 4 4.27976 4.32698 3.63803C4.6146 3.07354 5.07354 2.6146 5.63803 2.32698C6.27976 2 7.11984 2 8.8 2H15.2C16.8802 2 17.7202 2 18.362 2.32698C18.9265 2.6146 19.3854 3.07354 19.673 3.63803C20 4.27976 20 5.11984 20 6.8Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
export function DashboardIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
role="presentation"
className={props.className}
>
<path
d="M3 9H21M9 9L9 21M7.8 3H16.2C17.8802 3 18.7202 3 19.362 3.32698C19.9265 3.6146 20.3854 4.07354 20.673 4.63803C21 5.27976 21 6.11984 21 7.8V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C18.7202 21 17.8802 21 16.2 21H7.8C6.11984 21 5.27976 21 4.63803 20.673C4.07354 20.3854 3.6146 19.9265 3.32698 19.362C3 18.7202 3 17.8802 3 16.2V7.8C3 6.11984 3 5.27976 3.32698 4.63803C3.6146 4.07354 4.07354 3.6146 4.63803 3.32698C5.27976 3 6.11984 3 7.8 3Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
export function InsightIcon(props: { className?: string }) {
return (
<svg
className={props.className}
fill="none"
height="24"
role="presentation"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20.5 7.27783L12 12.0001M12 12.0001L3.49997 7.27783M12 12.0001L12 21.5001M21 12.0001V7.94153C21 7.59889 21 7.42757 20.9495 7.27477C20.9049 7.13959 20.8318 7.01551 20.7354 6.91082C20.6263 6.79248 20.4766 6.70928 20.177 6.54288L12.777 2.43177C12.4934 2.27421 12.3516 2.19543 12.2015 2.16454C12.0685 2.13721 11.9315 2.13721 11.7986 2.16454C11.6484 2.19543 11.5066 2.27421 11.223 2.43177L3.82297 6.54288C3.52345 6.70928 3.37368 6.79248 3.26463 6.91082C3.16816 7.01551 3.09515 7.13959 3.05048 7.27477C3 7.42757 3 7.59889 3 7.94153V16.0586C3 16.4013 3 16.5726 3.05048 16.7254C3.09515 16.8606 3.16816 16.9847 3.26463 17.0893C3.37368 17.2077 3.52346 17.2909 3.82297 17.4573L11.223 21.5684C11.5066 21.726 11.6484 21.8047 11.7986 21.8356C11.9315 21.863 12.0685 21.863 12.2015 21.8356C12.3516 21.8047 12.4934 21.726 12.777 21.5684L13 21.4445M7.5 4.50008L16.5 9.50008M22 21.5001L21 20.5001M22 18.0001C22 19.6569 20.6569 21.0001 19 21.0001C17.3431 21.0001 16 19.6569 16 18.0001C16 16.3432 17.3431 15.0001 19 15.0001C20.6569 15.0001 22 16.3432 22 18.0001Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
+21
View File
@@ -0,0 +1,21 @@
export function PayIcon(props: { className?: string }) {
return (
<svg
className={props.className}
fill="none"
height="24"
role="presentation"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 10H2M2 8.2L2 15.8C2 16.9201 2 17.4802 2.21799 17.908C2.40973 18.2843 2.71569 18.5903 3.09202 18.782C3.51984 19 4.07989 19 5.2 19L18.8 19C19.9201 19 20.4802 19 20.908 18.782C21.2843 18.5903 21.5903 18.2843 21.782 17.908C22 17.4802 22 16.9201 22 15.8V8.2C22 7.0799 22 6.51984 21.782 6.09202C21.5903 5.7157 21.2843 5.40974 20.908 5.21799C20.4802 5 19.9201 5 18.8 5L5.2 5C4.0799 5 3.51984 5 3.09202 5.21799C2.7157 5.40973 2.40973 5.71569 2.21799 6.09202C2 6.51984 2 7.07989 2 8.2Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
export function SmartAccountIcon(props: { className?: string }) {
return (
<svg
className={props.className}
fill="none"
height="24"
role="presentation"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13 7L11.8845 4.76892C11.5634 4.1268 11.4029 3.80573 11.1634 3.57116C10.9516 3.36373 10.6963 3.20597 10.4161 3.10931C10.0992 3 9.74021 3 9.02229 3H5.2C4.0799 3 3.51984 3 3.09202 3.21799C2.71569 3.40973 2.40973 3.71569 2.21799 4.09202C2 4.51984 2 5.0799 2 6.2V7M2 7H17.2C18.8802 7 19.7202 7 20.362 7.32698C20.9265 7.6146 21.3854 8.07354 21.673 8.63803C22 9.27976 22 10.1198 22 11.8V16.2C22 17.8802 22 18.7202 21.673 19.362C21.3854 19.9265 20.9265 20.3854 20.362 20.673C19.7202 21 18.8802 21 17.2 21H6.8C5.11984 21 4.27976 21 3.63803 20.673C3.07354 20.3854 2.6146 19.9265 2.32698 19.362C2 18.7202 2 17.8802 2 16.2V7ZM12 17.5C12 17.5 15 16.0701 15 13.9252V11.4229L12.8124 10.6412C12.2868 10.4529 11.712 10.4529 11.1864 10.6412L9 11.4229V13.9252C9 16.0701 12 17.5 12 17.5Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
@@ -0,0 +1,90 @@
import { useId } from "react";
function prefixId(id: string, prefix?: string) {
return prefix ? `${prefix}_${id}` : id;
}
export function ThirdwebIcon(props: {
className?: string;
isMonoChrome?: boolean;
}) {
const id = useId();
return (
<svg className={props.className} fill="none" viewBox="0 0 516 321">
<title>thirdweb</title>
<g clipPath={`url(#${prefixId("clip0_3:35)", id)}`}>
<path
d="M1.40497 27.0011C-3.73633 14.022 5.84519 0 19.8669 0H106.919C115.098 0 122.342 4.86715 125.381 12.3996L194.671 185.299C196.541 189.935 196.541 195.149 194.671 199.901L151.087 308.484C144.427 325.056 120.823 325.056 114.163 308.484L1.40497 27.0011Z"
fill={
props.isMonoChrome
? "currentColor"
: `url(#${prefixId("paint0_linear_3:35)", id)}`
}
/>
<path
d="M169.547 26.4217C164.873 13.5585 174.454 0 188.242 0H264.077C272.49 0 279.968 5.2148 282.772 12.9791L345.753 185.879C347.272 190.166 347.272 194.918 345.753 199.321L307.894 303.27C301.585 320.652 276.813 320.652 270.503 303.27L169.547 26.4217Z"
fill={
props.isMonoChrome
? "currentColor"
: `url(#${prefixId("paint1_linear_3:35)", id)}`
}
/>
<path
d="M321.331 27.0011C316.19 14.022 325.771 0 339.793 0H426.845C435.024 0 442.269 4.86715 445.307 12.3996L514.597 185.299C516.467 189.935 516.467 195.149 514.597 199.901L471.013 308.484C464.353 325.056 440.75 325.056 434.089 308.484L321.331 27.0011Z"
fill={
props.isMonoChrome
? "currentColor"
: `url(#${prefixId("paint2_linear_3:35)", id)}`
}
/>
</g>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id={prefixId("paint0_linear_3:35", id)}
x1="7.40492"
x2="260.485"
y1="55.24"
y2="164.437"
>
<stop stopColor="#F213A4" />
<stop offset="0.1517" stopColor="#E011A7" />
<stop offset="0.4554" stopColor="#B20DAF" />
<stop offset="0.8789" stopColor="#6806BB" />
<stop offset="1" stopColor="#5204BF" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id={prefixId("paint1_linear_3:35", id)}
x1="175.093"
x2="410.968"
y1="54.447"
y2="148.471"
>
<stop stopColor="#F213A4" />
<stop offset="0.1517" stopColor="#E011A7" />
<stop offset="0.4554" stopColor="#B20DAF" />
<stop offset="0.8789" stopColor="#6806BB" />
<stop offset="1" stopColor="#5204BF" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id={prefixId("paint2_linear_3:35", id)}
x1="327.331"
x2="580.411"
y1="55.24"
y2="164.437"
>
<stop stopColor="#F213A4" />
<stop offset="0.1517" stopColor="#E011A7" />
<stop offset="0.4554" stopColor="#B20DAF" />
<stop offset="0.8789" stopColor="#6806BB" />
<stop offset="1" stopColor="#5204BF" />
</linearGradient>
<clipPath id={prefixId("clip0_3:35", id)}>
<rect fill="white" height="321" width="516" />
</clipPath>
</defs>
</svg>
);
}
@@ -0,0 +1,21 @@
export function TokenIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
role="presentation"
className={props.className}
>
<path
d="M15.9377 15.9377C19.3603 15.4795 22 12.548 22 9C22 5.13401 18.866 2 15 2C11.452 2 8.52049 4.63967 8.06227 8.06227M16 15C16 18.866 12.866 22 9 22C5.13401 22 2 18.866 2 15C2 11.134 5.13401 8 9 8C12.866 8 16 11.134 16 15Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
export function WalletProductIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
role="presentation"
className={props.className}
>
<path
d="M16 7.99995V4.5006C16 3.66887 16 3.253 15.8248 2.99742C15.6717 2.77413 15.4346 2.62245 15.1678 2.57703C14.8623 2.52505 14.4847 2.69932 13.7295 3.04787L4.85901 7.14194C4.18551 7.45279 3.84875 7.60822 3.60211 7.84927C3.38406 8.06237 3.21762 8.3225 3.1155 8.60978C3 8.93474 3 9.30563 3 10.0474V14.9999M16.5 14.4999H16.51M3 11.2L3 17.7999C3 18.9201 3 19.4801 3.21799 19.9079C3.40973 20.2843 3.71569 20.5902 4.09202 20.782C4.51984 20.9999 5.07989 20.9999 6.2 20.9999H17.8C18.9201 20.9999 19.4802 21 19.908 20.782C20.2843 20.5902 20.5903 20.2843 20.782 19.9079C21 19.4801 21 18.9201 21 17.7999V11.2C21 10.0798 21 9.51979 20.782 9.09197C20.5903 8.71565 20.2843 8.40969 19.908 8.21794C19.4802 7.99995 18.9201 7.99995 17.8 7.99995L6.2 7.99995C5.0799 7.99995 4.51984 7.99995 4.09202 8.21794C3.7157 8.40968 3.40973 8.71564 3.21799 9.09197C3 9.51979 3 10.0798 3 11.2ZM17 14.4999C17 14.7761 16.7761 14.9999 16.5 14.9999C16.2239 14.9999 16 14.7761 16 14.4999C16 14.2238 16.2239 13.9999 16.5 13.9999C16.7761 13.9999 17 14.2238 17 14.4999Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
+48
View File
@@ -1,3 +1,51 @@
export const isProd =
(process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_VERCEL_ENV) ===
"production";
function getVercelEnv(): "production" | "preview" | "development" {
const onVercel = process.env.VERCEL || process.env.NEXT_PUBLIC_VERCEL_ENV;
// localhost
if (!onVercel) {
return "development";
}
const vercelEnv =
process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_VERCEL_ENV;
if (
vercelEnv === "production" ||
vercelEnv === "preview" ||
vercelEnv === "development"
) {
return vercelEnv;
}
// if running on vercel but for some reason failed to vercel env is not set -> production
return "production";
}
export function getBaseUrl(): string {
const env = getVercelEnv();
// dev
if (env === "development") {
return "http://localhost:3000";
}
// prod
if (env === "production") {
return "https://playground.thirdweb.com";
}
// preview
const vercelUrl =
process.env.VERCEL_URL || process.env.NEXT_PUBLIC_VERCEL_URL;
if (vercelUrl) {
return `https://${vercelUrl}`;
}
// preview but missing the preview URL for some reason -> production
return "https://playground.thirdweb.com";
}
+46
View File
@@ -0,0 +1,46 @@
import type { Metadata } from "next";
import { getBaseUrl } from "./env";
const BASE_URL = getBaseUrl();
type DynamicImageOptions = {
title: string;
icon: "wallets" | "transactions" | "payments" | "insight" | "contract";
};
export function createMetadata(obj: {
title: string;
description: string;
image?: DynamicImageOptions;
}): Metadata {
const metaTitle = `${obj.title} | thirdweb Playground`;
return {
description: obj.description,
metadataBase: new URL(BASE_URL),
openGraph: {
description: obj.description,
images: obj.image
? [
{
height: 630,
url: `${BASE_URL}/api/og?icon=${obj.image.icon}&title=${obj.image.title}`,
width: 1200,
},
]
: undefined,
locale: "en_US",
siteName: "thirdweb Playground",
title: metaTitle,
type: "website",
},
title: metaTitle,
twitter: {
card: "summary_large_image",
creator: "@thirdweb",
description: obj.description,
site: "@thirdweb",
title: metaTitle,
},
};
}
+10
View File
@@ -75,6 +75,16 @@ module.exports = {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
sidebar: {
accent: "hsl(var(--sidebar-accent))",
"accent-foreground": "hsl(var(--sidebar-accent-foreground))",
border: "hsl(var(--sidebar-border))",
DEFAULT: "hsl(var(--sidebar-background))",
foreground: "hsl(var(--sidebar-foreground))",
primary: "hsl(var(--sidebar-primary))",
"primary-foreground": "hsl(var(--sidebar-primary-foreground))",
ring: "hsl(var(--sidebar-ring))",
},
success: {
text: "hsl(var(--success-text))",
},
+14 -3
View File
@@ -1,8 +1,9 @@
import { BotIcon, WebhookIcon, ZapIcon } from "lucide-react";
import { ArrowUpRightIcon, BotIcon, WebhookIcon, ZapIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { Heading } from "@/components/Document";
import { ChatButton } from "../components/AI/chat-button";
import { Button } from "../components/ui/button";
import {
DotNetIcon,
ExternalLinkIcon,
@@ -44,8 +45,18 @@ function Hero() {
<p className="mb-8 max-w-md text-lg text-muted-foreground leading-normal">
Development framework for building onchain apps, games, and agents.
</p>
<div className="flex">
<div className="flex gap-3">
<ChatButton />
<Button
asChild
className="flex items-center gap-2 rounded-full bg-card"
variant="outline"
>
<Link href="https://playground.thirdweb.com">
Playground
<ArrowUpRightIcon className="size-4" />
</Link>
</Button>
</div>
</div>
</div>
@@ -250,7 +261,7 @@ function SDKCard(props: {
<props.icon className="size-4 shrink-0" />
</div>
<div className="flex flex-col">
<h3 className="font-medium text-base text-foreground mb-0.5">
<h3 className="font-semibold text-base text-foreground mb-0.5">
<Link
href={props.href}
target={props.href.includes("http") ? "_blank" : undefined}
+1 -1
View File
@@ -1,5 +1,5 @@
function getVercelEnv(): "production" | "preview" | "development" {
const onVercel = process.env.vercel || process.env.NEXT_PUBLIC_VERCEL_ENV;
const onVercel = process.env.VERCEL || process.env.NEXT_PUBLIC_VERCEL_ENV;
// localhost
if (!onVercel) {
+21 -9
View File
@@ -186,7 +186,7 @@ importers:
version: 0.4.6([email protected]([email protected]))([email protected])
nextjs-toploader:
specifier: ^1.6.12
version: 1.6.12([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
version: 1.6.12([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
nuqs:
specifier: ^2.4.3
version: 2.4.3([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected])
@@ -370,7 +370,7 @@ importers:
version: 5.60.2(@types/[email protected])([email protected])
next-sitemap:
specifier: ^4.2.3
version: 4.2.3([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))
version: 4.2.3([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))
postcss:
specifier: 8.5.6
version: 8.5.6
@@ -384,6 +384,8 @@ importers:
specifier: 5.8.3
version: 5.8.3
apps/login: {}
apps/nebula:
dependencies:
'@hookform/resolvers':
@@ -454,7 +456,7 @@ importers:
version: 0.4.6([email protected]([email protected]))([email protected])
nextjs-toploader:
specifier: ^1.6.12
version: 1.6.12([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
version: 1.6.12([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
posthog-js:
specifier: 1.256.1
version: 1.256.1
@@ -557,7 +559,7 @@ importers:
version: 5.60.2(@types/[email protected])([email protected])
next-sitemap:
specifier: ^4.2.3
version: 4.2.3([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))
version: 4.2.3([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))
postcss:
specifier: 8.5.6
version: 8.5.6
@@ -582,6 +584,12 @@ importers:
'@radix-ui/react-checkbox':
specifier: ^1.3.2
version: 1.3.2(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-collapsible':
specifier: ^1.1.11
version: 1.1.11(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-dialog':
specifier: 1.1.14
version: 1.1.14(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-label':
specifier: ^2.1.7
version: 2.1.7(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
@@ -632,7 +640,7 @@ importers:
version: 0.4.6([email protected]([email protected]))([email protected])
nextjs-toploader:
specifier: ^1.6.12
version: 1.6.12([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
version: 1.6.12([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
openapi-types:
specifier: ^12.1.3
version: 12.1.3
@@ -783,7 +791,7 @@ importers:
version: 0.4.6([email protected]([email protected]))([email protected])
nextjs-toploader:
specifier: ^1.6.12
version: 1.6.12([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
version: 1.6.12([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
node-html-markdown:
specifier: ^1.3.0
version: 1.3.0
@@ -898,7 +906,7 @@ importers:
version: 5.60.2(@types/[email protected])([email protected])
next-sitemap:
specifier: ^4.2.3
version: 4.2.3([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))
version: 4.2.3([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))
postcss:
specifier: 8.5.6
version: 8.5.6
@@ -1454,6 +1462,10 @@ importers:
specifier: 3.2.4
version: 3.2.4(@types/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected]))([email protected])([email protected])([email protected])
packages/typedoc-gen: {}
packages/unity-js-bridge: {}
packages/vault-sdk:
dependencies:
'@noble/ciphers':
@@ -31539,7 +31551,7 @@ snapshots:
[email protected]: {}
[email protected]([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])):
[email protected]([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])):
dependencies:
'@corex/deepmerge': 4.0.43
'@next/env': 13.5.8
@@ -31579,7 +31591,7 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
[email protected]([email protected](@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected]):
[email protected]([email protected](@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected]):
dependencies:
next: 15.3.5(@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])
nprogress: 0.2.0