diff --git a/public/chainLogos/eth.svg b/public/chainLogos/eth.svg new file mode 100644 index 0000000000..1f9ad00f9f --- /dev/null +++ b/public/chainLogos/eth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx index 6722b57a3a..4e3f8a2e22 100644 --- a/src/components/Providers.tsx +++ b/src/components/Providers.tsx @@ -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(); @@ -21,12 +22,14 @@ export const Providers: FunctionComponent = ({ enableSystem disableTransitionOnChange > - - - {children} - - - + + + + {children} + + + + ); diff --git a/src/components/SignInDialog.tsx b/src/components/SignInDialog.tsx index db34c3a5f7..f3bfc40c55 100644 --- a/src/components/SignInDialog.tsx +++ b/src/components/SignInDialog.tsx @@ -96,7 +96,7 @@ const WalletList = () => { {"By continuing, you agree to our "} setOpen(false)} > Privacy Policy @@ -104,7 +104,7 @@ const WalletList = () => { {" and "} setOpen(false)} > Terms of use diff --git a/src/components/requirements/ChainIndicator.tsx b/src/components/requirements/ChainIndicator.tsx new file mode 100644 index 0000000000..72953f0a4e --- /dev/null +++ b/src/components/requirements/ChainIndicator.tsx @@ -0,0 +1,13 @@ +import { Badge } from "@/components/ui/Badge"; +import { CHAINS, type SupportedChainID } from "@/config/chains"; + +export const ChainIndicator = ({ chain }: { chain: SupportedChainID }) => ( + + {CHAINS[chain].name} + {CHAINS[chain].name} + +); diff --git a/src/components/requirements/DataBlock.tsx b/src/components/requirements/DataBlock.tsx new file mode 100644 index 0000000000..cb9f1ad7c0 --- /dev/null +++ b/src/components/requirements/DataBlock.tsx @@ -0,0 +1,9 @@ +import type { PropsWithChildren } from "react"; + +export const DataBlock = ({ children }: PropsWithChildren): JSX.Element => { + return ( + + {children} + + ); +}; diff --git a/src/components/requirements/DataBlockWithCopy.tsx b/src/components/requirements/DataBlockWithCopy.tsx new file mode 100644 index 0000000000..cee1806e0b --- /dev/null +++ b/src/components/requirements/DataBlockWithCopy.tsx @@ -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): 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 ( + + copy(text)} + className="inline-flex rounded-md" + > + + {children ?? text} + + + + + + {tooltipInCopiedState && } + {tooltipInCopiedState ? "Copied" : "Click to copy"} + + + + ); +}; diff --git a/src/components/requirements/Requirement.tsx b/src/components/requirements/Requirement.tsx new file mode 100644 index 0000000000..5066d2d651 --- /dev/null +++ b/src/components/requirements/Requirement.tsx @@ -0,0 +1,48 @@ +import { cn } from "@/lib/cssUtils"; +import type { PropsWithChildren } from "react"; + +export const Requirement = ({ + className, + children, +}: PropsWithChildren<{ className?: string }>) => ( +
+ {children} +
+); + +export const RequirementImage = ({ + className, + children, +}: PropsWithChildren<{ className?: string }>) => ( +
+ {children} +
+); + +export const RequirementContent = ({ + className, + children, +}: PropsWithChildren<{ className?: string }>) => ( +
+ {children} +
+); + +export const RequirementFooter = ({ + className, + children, +}: PropsWithChildren<{ className?: string }>) => ( +
*]:mt-1", + className, + )} + > + {children} +
+); diff --git a/src/components/requirements/RequirementLink.tsx b/src/components/requirements/RequirementLink.tsx new file mode 100644 index 0000000000..57d14f27d5 --- /dev/null +++ b/src/components/requirements/RequirementLink.tsx @@ -0,0 +1,18 @@ +import type { PropsWithChildren } from "react"; +import { Anchor } from "../ui/Anchor"; + +export const RequirementLink = ({ + href, + children, +}: PropsWithChildren<{ href: string }>) => ( + + {children} + +); diff --git a/src/components/ui/Anchor.tsx b/src/components/ui/Anchor.tsx index a712e42e15..11ead78fda 100644 --- a/src/components/ui/Anchor.tsx +++ b/src/components/ui/Anchor.tsx @@ -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: "", }, }, diff --git a/src/components/ui/Badge.tsx b/src/components/ui/Badge.tsx index 10a0e1b0fd..f81b6a5e8d 100644 --- a/src/components/ui/Badge.tsx +++ b/src/components/ui/Badge.tsx @@ -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", }, diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 85917978cf..b59343d5da 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -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", diff --git a/src/config/chains.ts b/src/config/chains.ts new file mode 100644 index 0000000000..dc56e4ac7e --- /dev/null +++ b/src/config/chains.ts @@ -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"]; diff --git a/src/stories/Anchor.stories.tsx b/src/stories/Anchor.stories.tsx index 0390308ceb..6032a0120f 100644 --- a/src/stories/Anchor.stories.tsx +++ b/src/stories/Anchor.stories.tsx @@ -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, diff --git a/src/stories/requirements/ChainIndicator.stories.tsx b/src/stories/requirements/ChainIndicator.stories.tsx new file mode 100644 index 0000000000..a032a29bdf --- /dev/null +++ b/src/stories/requirements/ChainIndicator.stories.tsx @@ -0,0 +1,17 @@ +import { ChainIndicator } from "@/components/requirements/ChainIndicator"; +import type { Meta, StoryObj } from "@storybook/react"; + +const meta: Meta = { + title: "Requirement building blocks/ChainIndicator", + component: ChainIndicator, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + chain: 1, + }, +}; diff --git a/src/stories/requirements/DataBlock.stories.tsx b/src/stories/requirements/DataBlock.stories.tsx new file mode 100644 index 0000000000..e277fe3657 --- /dev/null +++ b/src/stories/requirements/DataBlock.stories.tsx @@ -0,0 +1,17 @@ +import { DataBlock } from "@/components/requirements/DataBlock"; +import type { Meta, StoryObj } from "@storybook/react"; + +const meta: Meta = { + title: "Requirement building blocks/DataBlock", + component: DataBlock, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + children: "DataBlock", + }, +}; diff --git a/src/stories/requirements/DataBlockWithCopy.stories.tsx b/src/stories/requirements/DataBlockWithCopy.stories.tsx new file mode 100644 index 0000000000..672523e416 --- /dev/null +++ b/src/stories/requirements/DataBlockWithCopy.stories.tsx @@ -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 = { + title: "Requirement building blocks/DataBlockWithCopy", + component: DataBlockWithCopy, + decorators: (Story) => ( + + + + ), +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + children: "DataBlockWithCopy", + text: "DataBlockWithCopy", + }, +}; diff --git a/src/stories/requirements/Requirement.stories.tsx b/src/stories/requirements/Requirement.stories.tsx new file mode 100644 index 0000000000..ce064e1b7c --- /dev/null +++ b/src/stories/requirements/Requirement.stories.tsx @@ -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 = () => ( + + + + + + +

+ {"Copy "} + + {" to your clipboard"} +

+ + + + + View on explorer + + +
+
+
+); + +const meta: Meta = { + title: "Requirement building blocks/Requirement", + component: RequirementExample, + decorators: (Story) => ( + + + + ), +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/requirements/RequirementLink.stories.tsx b/src/stories/requirements/RequirementLink.stories.tsx new file mode 100644 index 0000000000..e701e8a6df --- /dev/null +++ b/src/stories/requirements/RequirementLink.stories.tsx @@ -0,0 +1,18 @@ +import { RequirementLink } from "@/components/requirements/RequirementLink"; +import type { Meta, StoryObj } from "@storybook/react"; + +const meta: Meta = { + title: "Requirement building blocks/RequirementLink", + component: RequirementLink, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + href: "https://polygonscan.com/token/0xff04820c36759c9f5203021fe051239ad2dcca8a", + children: "Guild Pin contract", + }, +}; diff --git a/src/styles/globals.css b/src/styles/globals.css index a618b6a950..e74452b9e7 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -63,6 +63,17 @@ html { --blue-800: #1e40af; --blue-900: #1e3a8a; + --orange-50: #fff7ed; + --orange-100: #ffedd5; + --orange-200: #fed7aa; + --orange-300: #fdba74; + --orange-400: #fb923c; + --orange-500: #f97316; + --orange-600: #ea580c; + --orange-700: #c2410c; + --orange-800: #9a3412; + --orange-900: #7c2d12; + --red-50: #fef2f2; --red-100: #fee2e2; --red-200: #fecaca; @@ -156,6 +167,7 @@ html { --icon-success: var(--green-500); --icon-error: var(--red-500); + --icon-warning: var(--orange-500); } .dark { @@ -213,6 +225,7 @@ html { --icon-success: var(--green-400); --icon-error: var(--red-400); + --icon-warning: var(--orange-300); } body { diff --git a/tailwind.config.ts b/tailwind.config.ts index d35465e192..85b633f1da 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -12,11 +12,11 @@ const config = { ], prefix: "", theme: { - fontFamily: { - sans: ["var(--font-inter,sans-serif)"], - display: ["var(--font-dystopian,sans-serif)"], - }, extend: { + fontFamily: { + sans: ["var(--font-inter,sans-serif)"], + display: ["var(--font-dystopian,sans-serif)"], + }, colors: { background: "var(--background)", foreground: { @@ -71,8 +71,19 @@ const config = { "scroll-thumb": "var(--scroll-thumb)", icon: { success: "var(--icon-success)", - error: "var(--icon-error)" - } + error: "var(--icon-error)", + warning: "var(--icon-warning)", + }, + blackAlpha: { + DEFAULT: "var(--blackAlpha)", + medium: "var(--blackAlpha-medium)", + hard: "var(--blackAlpha-hard)", + }, + whiteAlpha: { + DEFAULT: "var(--whiteAlpha)", + medium: "var(--whiteAlpha-medium)", + hard: "var(--whiteAlpha-hard)", + }, }, keyframes: { "collapse-open": {