Skip to content
Merged
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
65 changes: 65 additions & 0 deletions src/components/ChainImages/ChainImages.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Meta, StoryObj } from "@storybook/nextjs"

import { VStack } from "@/components/ui/flex"

import ChainImages from "."

const meta = {
title: "Components / Widgets / ChainImages",
component: ChainImages,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Horizontal row of circular chain logos with a name tooltip on each. Filters the supplied `chains` array to those known in `data/networks/networks` -- unrecognised names are silently dropped. Default size is 24px.",
},
},
},
} satisfies Meta<typeof ChainImages>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
chains: ["Ethereum Mainnet", "Arbitrum One", "Base", "OP Mainnet"],
},
}

export const SingleChain: Story = {
args: {
chains: ["Ethereum Mainnet"],
},
}

export const ManyChains: Story = {
args: {
chains: [
"Ethereum Mainnet",
"Arbitrum One",
"Base",
"OP Mainnet",
"Scroll",
"Linea",
"zkSync Mainnet",
],
},
}

export const SizeComparison = {
render: () => (
<VStack className="items-start gap-4">
{[16, 24, 32, 48].map((size) => (
<div key={size} className="flex items-center gap-3">
<span className="w-12 text-sm text-body-medium">{size}px</span>
<ChainImages
chains={["Ethereum Mainnet", "Arbitrum One", "Base"]}
size={size}
/>
</div>
))}
</VStack>
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Meta, StoryObj } from "@storybook/nextjs"

import ListingMethodology from "."

const meta = {
title: "Components / Widgets / ListingMethodology",
component: ListingMethodology,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Section that documents the listing criteria for a directory page (e.g. find-wallet). Renders a heading + description, an optional link to the full criteria, an attribution + last-updated line, and the body criteria collapsed inside an `ExpandableCard`. Server-rendered and async (uses `getTranslations`). Designed for reuse across other directory pages even though only one consumer exists today.",
},
},
},
} satisfies Meta<typeof ListingMethodology>

export default meta

type Story = StoryObj<typeof meta>

const sampleCriteria = (
<div className="space-y-4">
<p>
Wallets are listed against an objective rubric covering security posture,
open-source status, supported networks, and self-custody guarantees.
</p>
<ul className="ml-6 list-disc space-y-1">
<li>Independently audited security model</li>
<li>Source code available under an OSI-approved licence</li>
<li>Supports at least one Ethereum mainnet client</li>
<li>Self-custodial -- keys held by the user, not the issuer</li>
</ul>
</div>
)

export const Default: Story = {
args: {
heading: "Listing methodology",
description:
"Criteria used to evaluate wallets considered for inclusion in the find-a-wallet directory.",
href: "/wallets/criteria/",
lastUpdated: "May 2026",
children: sampleCriteria,
},
}

export const WithoutLink: Story = {
args: {
heading: "Listing methodology",
description:
"Criteria used to evaluate wallets considered for inclusion in the find-a-wallet directory.",
lastUpdated: "May 2026",
children: sampleCriteria,
},
}

export const WithFooters: Story = {
args: {
heading: "Listing methodology",
description:
"Criteria used to evaluate dapps considered for inclusion in the dapps directory.",
href: "/dapps/criteria/",
lastUpdated: "May 2026",
children: sampleCriteria,
footers: [
"Source code review handled by the ethereum.org team.",
"Listings refreshed quarterly. Submit a correction via GitHub if a project's status has changed.",
],
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Meta, StoryObj } from "@storybook/nextjs"

import type { List } from "@/components/ButtonDropdown"

import MobileButtonDropdown from "."

const meta = {
title: "Components / Widgets / MobileButtonDropdown",
component: MobileButtonDropdown,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Thin positioning wrapper around `ButtonDropdown`: sticks to the bottom of the viewport on `<lg` breakpoints with full-width sizing, and hides itself on `lg` and up. Visual surface is essentially the underlying dropdown plus its mobile placement -- documented here so future pages picking it up know to expect both behaviors.",
},
},
},
} satisfies Meta<typeof MobileButtonDropdown>

export default meta

type Story = StoryObj<typeof meta>

const sampleList: List = {
text: "Choose section",
ariaLabel: "Page sections",
items: [
{ text: "Overview", href: "#overview" },
{ text: "Security", href: "#security" },
{ text: "Languages", href: "#languages" },
{ text: "Networks", href: "#networks" },
],
}

export const Default: Story = {
args: {
list: sampleList,
},
}

export const MobileViewport: Story = {
args: {
list: sampleList,
},
parameters: {
viewport: { defaultViewport: "mobile1" },
},
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import ButtonDropdown, { type ButtonDropdownProps } from "./ButtonDropdown"
import ButtonDropdown, {
type ButtonDropdownProps,
} from "@/components/ButtonDropdown"

const MobileButtonDropdown = (props: ButtonDropdownProps) => {
return (
Expand Down
47 changes: 47 additions & 0 deletions src/components/Morpher/Morpher.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Meta, StoryObj } from "@storybook/nextjs"

import Morpher from "."

const meta = {
title: "Components / Widgets / Morpher",
component: Morpher,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Animated text effect that morphs between a list of `words`, cycling roughly every 3 seconds. Respects `prefers-reduced-motion`: when reduced motion is requested, the component cross-fades between words instead of doing the per-character scramble. Pass `charSet` to control the alphabet used during the morph.",
},
},
},
decorators: [
(Story) => (
<div className="text-4xl font-bold">
<Story />
</div>
),
],
} satisfies Meta<typeof Morpher>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
words: ["finance", "art", "gaming", "governance"],
},
}

export const CustomCharSet: Story = {
args: {
words: ["alpha", "beta", "gamma", "delta"],
charSet: "abcdefghijklmnopqrstuvwxyz0123456789",
},
}

export const SingleWord: Story = {
args: {
words: ["ethereum"],
},
}
63 changes: 63 additions & 0 deletions src/components/SocialListItem/SocialListItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Meta, StoryObj } from "@storybook/nextjs"

import { VStack } from "@/components/ui/flex"

import SocialListItem from "."

const meta = {
title: "Components / Widgets / SocialListItem",
component: SocialListItem,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Row item used in the community page social directory. Each row pairs a brand-coloured icon (`reddit`, `twitter`, `youtube`, `discord`, `stackExchange`, or the generic `webpage` globe) with italic content describing the channel. Layout-only -- the parent controls click/link behavior.",
},
},
},
decorators: [
(Story) => (
<div className="max-w-md">
<Story />
</div>
),
],
} satisfies Meta<typeof SocialListItem>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
socialIcon: "webpage",
children: "ethereum.org official community page",
},
}

export const Twitter: Story = {
args: {
socialIcon: "twitter",
children: "@ethereum on Twitter / X",
},
}

export const AllPlatforms = {
render: () => (
<VStack className="items-stretch gap-1">
<SocialListItem socialIcon="webpage">ethereum.org</SocialListItem>
<SocialListItem socialIcon="reddit">/r/ethereum on Reddit</SocialListItem>
<SocialListItem socialIcon="twitter">
@ethereum on Twitter / X
</SocialListItem>
<SocialListItem socialIcon="youtube">
Ethereum Foundation YouTube
</SocialListItem>
<SocialListItem socialIcon="discord">Ethereum on Discord</SocialListItem>
<SocialListItem socialIcon="stackExchange">
Ethereum Stack Exchange
</SocialListItem>
</VStack>
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Meta, StoryObj } from "@storybook/nextjs"

import { SupportedLanguagesTooltip } from "."

const meta = {
title: "Components / Widgets / SupportedLanguagesTooltip",
component: SupportedLanguagesTooltip,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Renders a `+ N` chip with a tooltip listing the languages beyond the first `NUMBER_OF_SUPPORTED_LANGUAGES_SHOWN` (5 by default). Returns `null` if the supplied array fits within that threshold -- so the component is invisible until the overflow exists.",
},
},
},
decorators: [
(Story) => (
<div className="flex items-center gap-2">
<span>First 5 languages, then</span>
<Story />
</div>
),
],
} satisfies Meta<typeof SupportedLanguagesTooltip>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
supportedLanguages: [
"English",
"Spanish",
"French",
"German",
"Japanese",
"Korean",
"Portuguese",
"Russian",
],
},
}

export const ManyExtraLanguages: Story = {
args: {
supportedLanguages: [
"English",
"Spanish",
"French",
"German",
"Japanese",
"Korean",
"Portuguese",
"Russian",
"Italian",
"Dutch",
"Polish",
"Turkish",
"Arabic",
"Hindi",
"Vietnamese",
],
},
}

export const BelowThreshold: Story = {
args: {
supportedLanguages: ["English", "Spanish", "French"],
},
parameters: {
docs: {
description: {
story:
"Renders `null` -- the chip is suppressed because the supported list is at or below the threshold.",
},
},
},
}
Loading