Skip to content

Commit

Permalink
feat: requirement building blocks (#1571)
Browse files Browse the repository at this point in the history
* feat: DataBlock component

* feat: ChainIndicator component

* feat: simplify the DataBlock component

* feat: DataBlockWithCopy component

* feat: Requirement compound component

* feat: RequirementLink component

* cleanup(DataBlock): remove unused stuff

* cleanup: simplify DataBlock stories

* fix(DataBlockWithCopy): adjust height, add `"use client"`

* fix(Providers): add TooltipProvider

* fix(DataBlockWithCopy): tooltip trigger

* fix(DataBlockWithCopy): remove icon color

* refactor: make a `SupportedChainID` type

* fix(Button): adjust spacing for `sm` size
  • Loading branch information
BrickheadJohnny authored Nov 28, 2024
1 parent 7e25c56 commit 5494dee
Show file tree
Hide file tree
Showing 20 changed files with 344 additions and 20 deletions.
1 change: 1 addition & 0 deletions public/chainLogos/eth.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 9 additions & 6 deletions src/components/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Provider as JotaiProvider } from "jotai";
import { ThemeProvider } from "next-themes";
import type { FunctionComponent, PropsWithChildren } from "react";
import { WagmiProvider } from "wagmi";
import { TooltipProvider } from "./ui/Tooltip";

const queryClient = new QueryClient();

Expand All @@ -21,12 +22,14 @@ export const Providers: FunctionComponent<PropsWithChildren> = ({
enableSystem
disableTransitionOnChange
>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</WagmiProvider>
<TooltipProvider>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</WagmiProvider>
</TooltipProvider>
</ThemeProvider>
</JotaiProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/SignInDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ const WalletList = () => {
<span>{"By continuing, you agree to our "}</span>
<Anchor
href="/privacy-policy"
variant="muted"
variant="secondary"
onClick={() => setOpen(false)}
>
Privacy Policy
</Anchor>
<span>{" and "}</span>
<Anchor
href="/terms-of-use"
variant="muted"
variant="secondary"
onClick={() => setOpen(false)}
>
Terms of use
Expand Down
13 changes: 13 additions & 0 deletions src/components/requirements/ChainIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Badge } from "@/components/ui/Badge";
import { CHAINS, type SupportedChainID } from "@/config/chains";

export const ChainIndicator = ({ chain }: { chain: SupportedChainID }) => (
<Badge size="sm">
<img
src={CHAINS[chain].icon}
alt={CHAINS[chain].name}
className="size-3.5"
/>
<span>{CHAINS[chain].name}</span>
</Badge>
);
9 changes: 9 additions & 0 deletions src/components/requirements/DataBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { PropsWithChildren } from "react";

export const DataBlock = ({ children }: PropsWithChildren): JSX.Element => {
return (
<span className="h-6 break-words rounded-md bg-blackAlpha px-1.5 py-0.5 font-mono text-sm dark:bg-blackAlpha-hard">
{children}
</span>
);
};
58 changes: 58 additions & 0 deletions src/components/requirements/DataBlockWithCopy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client";

import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "@/components/ui/Tooltip";
import { Check } from "@phosphor-icons/react/dist/ssr";
import { useClipboard } from "foxact/use-clipboard";
import { useDebouncedState } from "foxact/use-debounced-state";
import { type PropsWithChildren, useEffect } from "react";
import { DataBlock } from "./DataBlock";

type Props = {
text: string;
};

export const DataBlockWithCopy = ({
text,
children,
}: PropsWithChildren<Props>): JSX.Element => {
const { copied, copy } = useClipboard({
timeout: 1500,
});

/**
* Maintaining a debounced copied state too, so we can change the tooltip content only after the tooltip is actually invisible
*/

const [debouncedCopied, debouncilySetState] = useDebouncedState(copied, 200);

useEffect(() => {
debouncilySetState(copied);
}, [debouncilySetState, copied]);

const tooltipInCopiedState = copied || debouncedCopied;

return (
<Tooltip open={copied || undefined}>
<TooltipTrigger
onClick={() => copy(text)}
className="inline-flex rounded-md"
>
<DataBlock>
<span>{children ?? text}</span>
</DataBlock>
</TooltipTrigger>

<TooltipPortal>
<TooltipContent side="top" className="flex items-center gap-1.5">
{tooltipInCopiedState && <Check weight="bold" />}
<span>{tooltipInCopiedState ? "Copied" : "Click to copy"}</span>
</TooltipContent>
</TooltipPortal>
</Tooltip>
);
};
48 changes: 48 additions & 0 deletions src/components/requirements/Requirement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { cn } from "@/lib/cssUtils";
import type { PropsWithChildren } from "react";

export const Requirement = ({
className,
children,
}: PropsWithChildren<{ className?: string }>) => (
<div className={cn("flex w-full items-center gap-4 py-2", className)}>
{children}
</div>
);

export const RequirementImage = ({
className,
children,
}: PropsWithChildren<{ className?: string }>) => (
<div
className={cn(
"flex size-11 shrink-0 items-center justify-center overflow-hidden rounded-full bg-blackAlpha dark:bg-blackAlpha-hard",
className,
)}
>
{children}
</div>
);

export const RequirementContent = ({
className,
children,
}: PropsWithChildren<{ className?: string }>) => (
<div className={cn("flex flex-grow flex-col items-start", className)}>
{children}
</div>
);

export const RequirementFooter = ({
className,
children,
}: PropsWithChildren<{ className?: string }>) => (
<div
className={cn(
"flex flex-wrap items-center gap-1.5 has-[>*]:mt-1",
className,
)}
>
{children}
</div>
);
18 changes: 18 additions & 0 deletions src/components/requirements/RequirementLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { PropsWithChildren } from "react";
import { Anchor } from "../ui/Anchor";

export const RequirementLink = ({
href,
children,
}: PropsWithChildren<{ href: string }>) => (
<Anchor
href={href}
target="_blank"
rel="noopener"
variant="secondary"
showExternal
className="font-medium text-xs"
>
{children}
</Anchor>
);
2 changes: 1 addition & 1 deletion src/components/ui/Anchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const anchorVariants = cva(
variant: {
default: "text-foreground hover:underline",
highlighted: "text-blue-500 dark:text-blue-400 hover:underline",
muted: "text-muted-foreground hover:underline",
secondary: "text-foreground-secondary hover:underline",
unstyled: "",
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const badgeVariants = cva(
{
variants: {
size: {
sm: "text-xs h-5",
sm: "text-xs h-5 gap-1",
md: "text-sm h-6",
lg: "text-base h-8",
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const buttonVariants = cva(
},
size: {
xs: "h-6 px-2 text-xs gap-1",
sm: "h-8 px-3 text-sm",
sm: "h-8 px-3 text-sm gap-1",
md: "h-11 px-4 py-2",
lg: "h-12 px-6 py-4 text-lg",
xl: "h-14 px-6 py-4 text-lg gap-2",
Expand Down
21 changes: 21 additions & 0 deletions src/config/chains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { mainnet, sepolia } from "viem/chains";
import type { Register } from "wagmi";

export const CHAINS = {
[mainnet.id]: {
name: mainnet.name,
icon: "/chainLogos/eth.svg",
},
[sepolia.id]: {
name: sepolia.name,
icon: "/chainLogos/eth.svg",
},
} satisfies Record<
SupportedChainID,
{
name: string;
icon: string;
}
>;

export type SupportedChainID = Register["config"]["chains"][number]["id"];
6 changes: 3 additions & 3 deletions src/stories/Anchor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ export const Default: Story = {
},
};

export const Muted: Story = {
export const Secondary: Story = {
args: {
...Default.args,
variant: "muted",
children: "Muted",
variant: "secondary",
children: "Secondary",
},
argTypes: {
...Default.argTypes,
Expand Down
17 changes: 17 additions & 0 deletions src/stories/requirements/ChainIndicator.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ChainIndicator } from "@/components/requirements/ChainIndicator";
import type { Meta, StoryObj } from "@storybook/react";

const meta: Meta<typeof ChainIndicator> = {
title: "Requirement building blocks/ChainIndicator",
component: ChainIndicator,
};

export default meta;

type Story = StoryObj<typeof ChainIndicator>;

export const Default: Story = {
args: {
chain: 1,
},
};
17 changes: 17 additions & 0 deletions src/stories/requirements/DataBlock.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DataBlock } from "@/components/requirements/DataBlock";
import type { Meta, StoryObj } from "@storybook/react";

const meta: Meta<typeof DataBlock> = {
title: "Requirement building blocks/DataBlock",
component: DataBlock,
};

export default meta;

type Story = StoryObj<typeof DataBlock>;

export const Default: Story = {
args: {
children: "DataBlock",
},
};
24 changes: 24 additions & 0 deletions src/stories/requirements/DataBlockWithCopy.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DataBlockWithCopy } from "@/components/requirements/DataBlockWithCopy";
import { TooltipProvider } from "@/components/ui/Tooltip";
import type { Meta, StoryObj } from "@storybook/react";

const meta: Meta<typeof DataBlockWithCopy> = {
title: "Requirement building blocks/DataBlockWithCopy",
component: DataBlockWithCopy,
decorators: (Story) => (
<TooltipProvider>
<Story />
</TooltipProvider>
),
};

export default meta;

type Story = StoryObj<typeof DataBlockWithCopy>;

export const Default: Story = {
args: {
children: "DataBlockWithCopy",
text: "DataBlockWithCopy",
},
};
53 changes: 53 additions & 0 deletions src/stories/requirements/Requirement.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ChainIndicator } from "@/components/requirements/ChainIndicator";
import { DataBlockWithCopy } from "@/components/requirements/DataBlockWithCopy";
import {
Requirement,
RequirementContent,
RequirementFooter,
RequirementImage,
} from "@/components/requirements/Requirement";
import { RequirementLink } from "@/components/requirements/RequirementLink";
import { Card } from "@/components/ui/Card";
import { TooltipProvider } from "@/components/ui/Tooltip";
import { QuestionMark } from "@phosphor-icons/react/dist/ssr";
import type { Meta, StoryObj } from "@storybook/react";

const RequirementExample = () => (
<Card className="max-w-sm px-6 py-4">
<Requirement>
<RequirementImage>
<QuestionMark weight="bold" className="size-6" />
</RequirementImage>
<RequirementContent>
<p>
<span>{"Copy "}</span>
<DataBlockWithCopy text="this text" />
<span>{" to your clipboard"}</span>
</p>

<RequirementFooter className="text-secondary text-sm">
<ChainIndicator chain={1} />
<RequirementLink href="https://polygonscan.com/token/0xff04820c36759c9f5203021fe051239ad2dcca8a">
View on explorer
</RequirementLink>
</RequirementFooter>
</RequirementContent>
</Requirement>
</Card>
);

const meta: Meta<typeof RequirementExample> = {
title: "Requirement building blocks/Requirement",
component: RequirementExample,
decorators: (Story) => (
<TooltipProvider>
<Story />
</TooltipProvider>
),
};

export default meta;

type Story = StoryObj<typeof RequirementExample>;

export const Default: Story = {};
18 changes: 18 additions & 0 deletions src/stories/requirements/RequirementLink.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { RequirementLink } from "@/components/requirements/RequirementLink";
import type { Meta, StoryObj } from "@storybook/react";

const meta: Meta<typeof RequirementLink> = {
title: "Requirement building blocks/RequirementLink",
component: RequirementLink,
};

export default meta;

type Story = StoryObj<typeof RequirementLink>;

export const Default: Story = {
args: {
href: "https://polygonscan.com/token/0xff04820c36759c9f5203021fe051239ad2dcca8a",
children: "Guild Pin contract",
},
};
Loading

0 comments on commit 5494dee

Please sign in to comment.