diff --git a/apps/playground-web/public/headless-ui-header.png b/apps/playground-web/public/headless-ui-header.png new file mode 100644 index 00000000000..cf87071feea Binary files /dev/null and b/apps/playground-web/public/headless-ui-header.png differ diff --git a/apps/playground-web/src/app/connect/ui/nft/page.tsx b/apps/playground-web/src/app/connect/ui/nft/page.tsx new file mode 100644 index 00000000000..44d016b8ed5 --- /dev/null +++ b/apps/playground-web/src/app/connect/ui/nft/page.tsx @@ -0,0 +1,56 @@ +import { APIHeader } from "@/components/blocks/APIHeader"; +import { + NftCardDemo, + NftDescriptionBasic, + NftMediaBasic, + NftMediaOverride, + NftNameBasic, +} from "@/components/headless-ui/nft-examples"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { metadataBase } from "@/lib/constants"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + metadataBase, + title: "NFT Components", + 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.", +}; + +export default function Page() { + return ( + +
+ + Elevate your NFT applications 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. + + } + docsLink="https://portal.thirdweb.com/react/v5/connecting-wallets/ui-components" + heroLink="/headless-ui-header.png" + /> +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} diff --git a/apps/playground-web/src/app/connect/ui/page.tsx b/apps/playground-web/src/app/connect/ui/page.tsx new file mode 100644 index 00000000000..48aaccb8075 --- /dev/null +++ b/apps/playground-web/src/app/connect/ui/page.tsx @@ -0,0 +1,80 @@ +import { APIHeader } from "@/components/blocks/APIHeader"; +import { + AccountAddressBasic, + AccountAddressFormat, + AccountAvatarBasic, + AccountBalanceBasic, + AccountBalanceCustomToken, + AccountBalanceFormat, + AccountBalanceUSD, + AccountBlobbieBasic, + AccountNameBasic, + AccountNameCustom, + ConnectDetailsButtonClone, +} from "@/components/headless-ui/account-examples"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { metadataBase } from "@/lib/constants"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + metadataBase, + title: "Account Components", + description: + "Streamline your Web3 development with our React headless UI components for wallet integration. These unstyled, customizable components handle complex wallet operations while giving you complete control over your dApp's design.", +}; + +export default function Page() { + return ( + +
+ + Streamline your Web3 development with our React headless UI + components for wallet integration. These unstyled, customizable + components handle complex wallet operations while giving you + complete control over your dApp's design. + + } + docsLink="https://portal.thirdweb.com/react/v5/connecting-wallets/ui-components" + heroLink="/headless-ui-header.png" + /> + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} diff --git a/apps/playground-web/src/app/navLinks.ts b/apps/playground-web/src/app/navLinks.ts index 8b42463b616..d4169e1c54f 100644 --- a/apps/playground-web/src/app/navLinks.ts +++ b/apps/playground-web/src/app/navLinks.ts @@ -111,4 +111,18 @@ export const navLinks: SidebarLink[] = [ name: "Blockchain API", href: "/connect/blockchain-api", }, + { + name: "Headless UI", + expanded: true, + links: [ + { + name: "Account", + href: "/connect/ui", + }, + { + name: "NFT", + href: "/connect/ui/nft", + }, + ], + }, ]; diff --git a/apps/playground-web/src/app/page.tsx b/apps/playground-web/src/app/page.tsx index 9c9651a6230..1a84adf3d7a 100644 --- a/apps/playground-web/src/app/page.tsx +++ b/apps/playground-web/src/app/page.tsx @@ -60,6 +60,12 @@ function WalletsSection() { description="Performant, and reliable blockchain API" icon={CodeIcon} /> + ); diff --git a/apps/playground-web/src/components/headless-ui/account-examples.tsx b/apps/playground-web/src/components/headless-ui/account-examples.tsx new file mode 100644 index 00000000000..f19bf9b9345 --- /dev/null +++ b/apps/playground-web/src/components/headless-ui/account-examples.tsx @@ -0,0 +1,509 @@ +"use client"; + +import { THIRDWEB_CLIENT } from "@/lib/client"; +import { ethereum } from "thirdweb/chains"; +import { + AccountAddress, + AccountAvatar, + AccountBalance, + type AccountBalanceInfo, + AccountBlobbie, + AccountName, + AccountProvider, +} from "thirdweb/react"; +import { shortenAddress } from "thirdweb/utils"; +import { CodeExample } from "../code/code-example"; + +const vitalikAddress = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"; + +/** + * Show full wallet address with AccountAddress + */ +export function AccountAddressBasic() { + return ( + <> +
+

+ AccountAddress +

+

+ Show the wallet address of the account. +

+
+ + + + + } + code={`import { AccountProvider, AccountAddress } from "thirdweb/react"; + +function App() { + return + + +}`} + lang="tsx" + /> + + ); +} + +/** + * Shorten wallet address with AccountAddress + */ +export function AccountAddressFormat() { + return ( + <> +
+

+ Shorten the wallet address using the formatFn prop. +

+
+ + + + + } + code={`import { AccountProvider, AccountAddress } from "thirdweb/react"; +import { shortenAddress } from "thirdweb/utils"; + +function App() { + return + + +}`} + lang="tsx" + /> + + ); +} + +/** + * Show the social name of an account + */ +export function AccountNameBasic() { + return ( + <> +
+

+ AccountName +

+

+ Show the social alias associated with the account +

+
+ + + Loading...} /> + + } + code={`import { AccountProvider, AccountName } from "thirdweb/react"; + +function App() { + return + Loading...} /> + +}`} + lang="tsx" + /> + + ); +} + +export function AccountNameCustom() { + return ( + <> +
+

+ Show account name for a specific social network +

+
+ + + Loading...} + /> + + } + code={`import { AccountProvider, AccountName } from "thirdweb/react"; + +function App() { + return + {/* You can choose between "ens", "lens" and "farcaster" */} + Loading...} /> + +}`} + lang="tsx" + /> + + ); +} + +/** + * AccountBalance: Fetch native balance on Ethereum mainnet + */ +export function AccountBalanceBasic() { + return ( + <> +
+

+ AccountBalance +

+

+ Display the current native balance of the wallet. +

+
+ + + Loading...} + /> + + } + code={`import { AccountProvider, AccountAddress } from "thirdweb/react"; +import { shortenAddress } from "thirdweb/utils"; + +function App() { + return + Loading...} + /> + +}`} + lang="tsx" + /> + + ); +} + +/** + * AccountBalance: Fetch USDC balance on mainnet + */ +export function AccountBalanceCustomToken() { + return ( + <> +
+

+ Display the current balance of a custom token +

+
+ + + Loading...} + /> + + } + code={`import { AccountProvider, AccountAddress } from "thirdweb/react"; +import { shortenAddress } from "thirdweb/utils"; + +const USDC_ETHEREUM = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + +function App() { + return + Loading...} + /> + +}`} + lang="tsx" + /> + + ); +} + +/** + * AccountBalance: Round up the token balance + */ +export function AccountBalanceFormat() { + return ( + <> +
+

+ Round up the wallet balance using the formatFn prop. +

+
+ + + + `${Math.ceil(props.balance * 1000) / 1000} ${props.symbol}` + } + loadingComponent={Loading...} + /> + + } + code={`import { AccountProvider, AccountAddress, type AccountBalanceFormatParams } from "thirdweb/react"; + +function App() { + return + Loading...} + formatFn={(props: AccountBalanceInfo) => \`\${Math.ceil(props.balance * 1000) / 1000} \${props.symbol}\`} /> + +}`} + lang="tsx" + /> + + ); +} + +/** + * AccountBalance: Show USD balance + */ +export function AccountBalanceUSD() { + return ( + <> +
+

+ Display the USD value of the token balance. +

+
+ + + Loading...} + /> + + } + code={`import { AccountProvider, AccountAddress } from "thirdweb/react"; + +function App() { + return ( + + Loading...} + /> + + ) +}`} + lang="tsx" + /> + + ); +} + +/** + * Show the social name of an account + */ +export function AccountAvatarBasic() { + return ( + <> +
+

+ AccountAvatar +

+

+ Show the social avatar associated with the account +

+
+ + + Loading...} + /> + + } + code={`import { AccountProvider, AccountAvatar } from "thirdweb/react"; + +function App() { + return + Loading...} /> + +}`} + lang="tsx" + /> + + ); +} + +export function AccountBlobbieBasic() { + return ( + <> +
+

+ AccountBlobbie +

+

+ Show the unique blobbie generated from the wallet address +

+
+ + + + + } + code={`import { AccountProvider, AccountBlobbie } from "thirdweb/react"; + +function App() { + return + + +}`} + lang="tsx" + /> + + ); +} + +export function ConnectDetailsButtonClone() { + return ( + <> +
+

+ Rebuild the Connected-Button +

+

+ Using these headless components, you can easily build your own complex + UI, such as our Connected-button. +

+
+ + + + + } + code={`import { AccountProvider, AccountAvatar, AccountName, AccountBalance, AccountAddress, AccountBlobbie } from "thirdweb/react"; + +function App() { + return ( + + + + ) +}`} + lang="tsx" + /> + + ); +} diff --git a/apps/playground-web/src/components/headless-ui/nft-examples.tsx b/apps/playground-web/src/components/headless-ui/nft-examples.tsx new file mode 100644 index 00000000000..dcb549d2297 --- /dev/null +++ b/apps/playground-web/src/components/headless-ui/nft-examples.tsx @@ -0,0 +1,205 @@ +"use client"; + +import { THIRDWEB_CLIENT } from "@/lib/client"; +import { getContract } from "thirdweb"; +import { ethereum } from "thirdweb/chains"; +import { NFTDescription, NFTMedia, NFTName, NFTProvider } from "thirdweb/react"; +import { CodeExample } from "../code/code-example"; + +const nftContract = getContract({ + address: "0xbd3531da5cf5857e7cfaa92426877b022e612cf8", + chain: ethereum, + client: THIRDWEB_CLIENT, +}); + +export function NftMediaBasic() { + return ( + <> +
+

+ NFTMedia +

+

+ Show the media of an NFT in a collection. +

+
+ + + Loading...} + /> + + } + code={`import { NFTProvider, NFTMedia } from "thirdweb/react"; + +function App() { + return + Loading...} /> + +}`} + lang="tsx" + /> + + ); +} + +export function NftMediaOverride() { + return ( + <> +
+

+ Override the NFT media using the mediaResolver prop. +

+

+ This prop is very useful when you already have the media src and want + to skip the network requests on the client. +

+
+ + + + + } + code={`import { NFTProvider, NFTMedia } from "thirdweb/react"; + +function App() { + return + + +}`} + lang="tsx" + /> + + ); +} + +export function NftNameBasic() { + return ( + <> +
+

+ NFTName +

+

+ Show the name of an NFT in a collection. +

+
+ + + Loading...} /> + + } + code={`import { NFTProvider, NFTName } from "thirdweb/react"; + +function App() { + return + Loading...} /> + +}`} + lang="tsx" + /> + + ); +} + +export function NftDescriptionBasic() { + return ( + <> +
+

+ NFTDescription +

+

+ Show the description of an NFT in a collection. +

+
+ + + Loading...} + /> + + } + code={`import { NFTProvider, NFTDescription } from "thirdweb/react"; + +function App() { + return + Loading...} /> + +}`} + lang="tsx" + /> + + ); +} + +export function NftCardDemo() { + return ( + <> +
+

+ Build an NFT Card +

+

+ Using these headless components, you can easily build your own NFT + Card +

+
+ + +
+ + + Loading...} + /> +
+ + } + code={`import { NFTProvider, NFTDescription, NFTName, NFTMedia } from "thirdweb/react"; + +function App() { + return ( + +
+ + + Loading...} + /> +
+
+ ) +}`} + lang="tsx" + /> + + ); +} diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx index ad6444a07df..7aa319cfb12 100644 --- a/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx +++ b/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx @@ -164,7 +164,12 @@ export function AccountAvatar({ }: AccountAvatarProps) { const { address, client } = useAccountContext(); const avatarQuery = useQuery({ - queryKey: ["account-avatar", address], + queryKey: [ + "account-avatar", + address, + { socialType }, + { resolverAddress, resolverChain }, + ], queryFn: async (): Promise => { const [socialData, ensName] = await Promise.all([ getSocialProfiles({ address, client }), diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx index 75b634f4861..ff7abd1217c 100644 --- a/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx +++ b/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx @@ -57,7 +57,7 @@ export interface AccountNameProps /** * Optional `useQuery` params */ - queryOptions?: Omit, "queryKey" | "queryFn">; + queryOptions?: Omit, "queryFn" | "queryKey">; } /** @@ -138,7 +138,12 @@ export function AccountName({ }: AccountNameProps) { const { address, client } = useAccountContext(); const nameQuery = useQuery({ - queryKey: ["account-name", address], + queryKey: [ + "account-name", + address, + { socialType }, + { resolverAddress, resolverChain }, + ], queryFn: async () => { const [socialData, ensName] = await Promise.all([ getSocialProfiles({ address, client }),