Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions apps/storybook.namekit.io/stories/Namekit/Identity.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import React, { useRef } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
Identity,
NameKitProvider,
ProfileLinkGenerator,
} from "@namehash/namekit-react/client";

const meta: Meta<typeof Identity.Root> = {
title: "Namekit/Identity",
component: Identity.Root,
argTypes: {
address: { control: "text" },
network: {
control: {
type: "select",
options: ["mainnet", "sepolia"],
},
},
className: { control: "text" },
},
};

export default meta;

type Story = StoryObj<typeof Identity.Root>;

const TwitterProfileLink = new ProfileLinkGenerator(
"Twitter",
"https://twitter.com/",
);
const GitHubProfileLink = new ProfileLinkGenerator(
"GitHub",
"https://github.com/",
);

const DefaultIdentityCard: React.FC<{
address: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check all places in the code where you have an address param. These should all use the Address type defined in viem.

network?: "mainnet" | "sepolia";
returnNameGuardReport?: boolean;
}> = ({ address, network, returnNameGuardReport }) => (
<Identity.Root
address={address}
network={network}
returnNameGuardReport={returnNameGuardReport}
>
<Identity.Avatar />
<Identity.Name />
<Identity.Address />
<Identity.NameGuardShield />
<Identity.ProfileLink>
<div>
<ENSLogo />
<span>View on ENS App</span>
</div>
</Identity.ProfileLink>
<Identity.Followers />
</Identity.Root>
);

const CustomAppIdentityCard: React.FC<{ address: string }> = ({ address }) => (
<NameKitProvider
config={{ profileLinks: [TwitterProfileLink, GitHubProfileLink] }}
>
<Identity.Root address={address}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we make a few updates here to put our "Identity" components on a stronger foundation. Specifically: an "identity reference" is more than just an address. It is a combination of a chain and an address.

  1. Suggest defining an interface for ImplicitIdentityReference that combines both an address and a chain, but where the chain param is optional. The purpose of this interface is to optimize DX for the average case where all addresses within an app are on the same chain. It should be noted how this isn't always the case though, and increasingly won't be in the future the way things are headed.
  2. Suggest defining an interface for IdentityReference that extends ImplicitIdentityReference but makes the chain param required.
  3. Suggest defining a little utility function that can be used within the context of a NameKitProvider that automatically converts any ImplicitIdentityReference to an explicit IdentityReference using the configured chain as the default.
  4. There's a bunch of functions here that currently only take an "address" param. These should change so that they instead take a param of type IdentityReference or ImplicitIdentityReference. Inside the implementation of these functions, we can call the utility function described above that takes either an IdentityReference or ImplicitIdentityReference as a param and always returns an explicit IdentityReference for use in all our internal logic.
  5. Functions such as getProfileURL and getProfileLink should take an IdentityReference as a param. The default implementation of these functions should then consider the chain parameter and not only the address. For example. If the chain param is mainnet, then our existing strategy is good. If the chain param is anything else (such as sepolia, etc..), then there isn't an existing profile service we can easily link to for this. So instead suggest making our default implementation of all the profile link generator functions trigger some alert box or something like that for the case that the chain is anything but mainnet. Once we build the backend APIs into NameKit for generating profile data, we can come back to this and implement some improved default behaviour for showing profiles on chains other than mainnet.

Here's an acceptance criteria for everything described above: If I use our Identity components to show the identity for a chain other than mainnet (such as sepolia) I shouldn't get a profile link to view the profile of that address on mainnet as that wouldn't actually be the correct profile / identity.

Please ask me if any questions 👍

<Identity.Avatar />
<Identity.Name />
<Identity.ProfileLink>
<button>View Profile</button>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason not to use our Button?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned before, I'm avoiding the UI side of things until we nail the DX. Swapping out components is easy, but if we need to make changes to the UI components, it could cause conflicts so I've opted not to focus on that yet.

</Identity.ProfileLink>
</Identity.Root>
</NameKitProvider>
);

const ModalIdentityCard: React.FC<{ address: string }> = ({ address }) => {
const dialogRef = useRef<HTMLDialogElement>(null);

const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
dialogRef.current?.showModal();
};

return (
<NameKitProvider
config={{ profileLinks: [new ProfileLinkGenerator("Modal", "#")] }}
>
<Identity.Root address={address}>
<Identity.Avatar />
<Identity.Name />
<Identity.ProfileLink onClick={handleClick}>
<>
<span>Open Profile Modal</span>
<span>→</span>
</>
</Identity.ProfileLink>
</Identity.Root>
<dialog ref={dialogRef}>
Hello {address}
<button onClick={() => dialogRef.current?.close()}>Close</button>
</dialog>
</NameKitProvider>
);
};

export const Default: Story = {
args: {
address: "0x838aD0EAE54F99F1926dA7C3b6bFbF617389B4D9",
network: "mainnet",
className: "rounded-xl",
},
render: (args) => <DefaultIdentityCard {...args} />,
};

export const MultipleCards: Story = {
render: () => (
<>
<DefaultIdentityCard address="0x838aD0EAE54F99F1926dA7C3b6bFbF617389B4D9" />
<DefaultIdentityCard address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" />
<DefaultIdentityCard address="0xf81bc66316a3f2a60adc258f97f61dfcbdd23bb1" />
</>
),
};

export const ProfileLinkVariants: Story = {
render: () => {
const address = "0x838aD0EAE54F99F1926dA7C3b6bFbF617389B4D9";

return (
<div className="nk-space-y-8">
<div>
<h3>Default ENS App Link</h3>
<Identity.Root address={address}>
<Identity.Avatar />
<Identity.Name />
<Identity.ProfileLink>
<div className="nk-flex nk-items-center nk-gap-2">
<ENSLogo />
<span>View on ENS App</span>
</div>
</Identity.ProfileLink>
</Identity.Root>
</div>

<div>
<h3>Custom App Link</h3>
<NameKitProvider
config={{ profileLinks: [TwitterProfileLink, GitHubProfileLink] }}
>
<Identity.Root address={address}>
<Identity.Avatar />
<Identity.Name />
<Identity.ProfileLinks />
</Identity.Root>
</NameKitProvider>
</div>

<div>
<h3>Modal Link</h3>
<ModalIdentityCard address={address} />
</div>
</div>
);
},
};

const ENSLogo = () => (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we move this into a dedicated UI component inside NameKit React.

<svg
fill="none"
height="16"
viewBox="0 0 202 231"
width="14"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="#0080bc">
<path d="m98.3592 2.80337-63.5239 104.52363c-.4982.82-1.6556.911-2.2736.178-5.5924-6.641-26.42692-34.89-.6463-60.6377 23.5249-23.4947 53.4891-40.24601 64.5942-46.035595 1.2599-.656858 2.587.758365 1.8496 1.971665z" />
<path d="m94.8459 230.385c1.2678.888 2.8299-.626 1.9802-1.918-14.1887-21.581-61.3548-93.386-67.8702-104.165-6.4264-10.632-19.06614-28.301-20.12056-43.4178-.10524-1.5091-2.19202-1.8155-2.71696-.3963-.8466 2.2888-1.74793 5.0206-2.58796 8.1413-10.60469 39.3938 4.79656 81.1968 38.24488 104.6088l53.0706 37.148z" />
<path d="m103.571 228.526 63.524-104.523c.498-.82 1.656-.911 2.274-.178 5.592 6.64 26.427 34.89.646 60.638-23.525 23.494-53.489 40.246-64.594 46.035-1.26.657-2.587-.758-1.85-1.972z" />
<path d="m107.154.930762c-1.268-.8873666-2.83.625938-1.98 1.918258 14.189 21.58108 61.355 93.38638 67.87 104.16498 6.427 10.632 19.066 28.301 20.121 43.418.105 1.509 2.192 1.815 2.717.396.846-2.289 1.748-5.02 2.588-8.141 10.604-39.394-4.797-81.1965-38.245-104.609z" />
</g>
</svg>
);
1 change: 1 addition & 0 deletions packages/namekit-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@headlessui/react": "1.7.17",
"@namehash/ens-utils": "workspace:*",
"@namehash/ens-webfont": "workspace:*",
"@namehash/nameguard": "workspace:*",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion to make this a peer dependency sounds good 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: This hasn't been changed yet.

"classcat": "5.0.5"
},
"devDependencies": {
Expand Down
8 changes: 8 additions & 0 deletions packages/namekit-react/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import "@namehash/ens-webfont";
import "./styles.css";

Expand All @@ -12,3 +14,9 @@ export {
CurrencySymbolSize,
} from "./components/CurrencySymbol/CurrencySymbol";
export { TruncatedText } from "./components/TruncatedText";

export {
Identity,
NameKitProvider,
ProfileLinkGenerator,
} from "./components/Identity";
Loading