From 0cdf29d06168030abbc95679dee6356e07b7d560 Mon Sep 17 00:00:00 2001 From: myelinated-wackerow <263208946+myelinated-wackerow@users.noreply.github.com> Date: Fri, 15 May 2026 05:40:14 -0700 Subject: [PATCH] chore: stories for widget-primitive components PR 3 of #18191. Adds Storybook stories for six small widget components used across two or more areas (plus ListingMethodology, single-consumer today but explicitly designed for reuse). Components covered: ChainImages, ListingMethodology (async server), MobileButtonDropdown (thin positioning wrapper over ButtonDropdown), Morpher (client text-morph animation), SocialListItem, and SupportedLanguagesTooltip. MobileButtonDropdown and SocialListItem migrated from loose .tsx into Foo/index.tsx so the story files can live alongside. Each story uses the Components / Widgets / title pattern and opts out of Chromatic snapshots at the meta level per the #18121 convention. Co-Authored-By: Claude Opus 4.7 Co-Authored-By: wackerow <54227730+wackerow@users.noreply.github.com> --- .../ChainImages/ChainImages.stories.tsx | 65 +++++++++++++++ .../ListingMethodology.stories.tsx | 72 +++++++++++++++++ .../MobileButtonDropdown.stories.tsx | 49 ++++++++++++ .../index.tsx} | 4 +- src/components/Morpher/Morpher.stories.tsx | 47 +++++++++++ .../SocialListItem/SocialListItem.stories.tsx | 63 +++++++++++++++ .../index.tsx} | 0 .../SupportedLanguagesTooltip.stories.tsx | 80 +++++++++++++++++++ 8 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 src/components/ChainImages/ChainImages.stories.tsx create mode 100644 src/components/ListingMethodology/ListingMethodology.stories.tsx create mode 100644 src/components/MobileButtonDropdown/MobileButtonDropdown.stories.tsx rename src/components/{MobileButtonDropdown.tsx => MobileButtonDropdown/index.tsx} (76%) create mode 100644 src/components/Morpher/Morpher.stories.tsx create mode 100644 src/components/SocialListItem/SocialListItem.stories.tsx rename src/components/{SocialListItem.tsx => SocialListItem/index.tsx} (100%) create mode 100644 src/components/SupportedLanguagesTooltip/SupportedLanguagesTooltip.stories.tsx diff --git a/src/components/ChainImages/ChainImages.stories.tsx b/src/components/ChainImages/ChainImages.stories.tsx new file mode 100644 index 00000000000..1254856575a --- /dev/null +++ b/src/components/ChainImages/ChainImages.stories.tsx @@ -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 + +export default meta + +type Story = StoryObj + +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: () => ( + + {[16, 24, 32, 48].map((size) => ( +
+ {size}px + +
+ ))} +
+ ), +} diff --git a/src/components/ListingMethodology/ListingMethodology.stories.tsx b/src/components/ListingMethodology/ListingMethodology.stories.tsx new file mode 100644 index 00000000000..189a581b1db --- /dev/null +++ b/src/components/ListingMethodology/ListingMethodology.stories.tsx @@ -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 + +export default meta + +type Story = StoryObj + +const sampleCriteria = ( +
+

+ Wallets are listed against an objective rubric covering security posture, + open-source status, supported networks, and self-custody guarantees. +

+
    +
  • Independently audited security model
  • +
  • Source code available under an OSI-approved licence
  • +
  • Supports at least one Ethereum mainnet client
  • +
  • Self-custodial -- keys held by the user, not the issuer
  • +
+
+) + +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.", + ], + }, +} diff --git a/src/components/MobileButtonDropdown/MobileButtonDropdown.stories.tsx b/src/components/MobileButtonDropdown/MobileButtonDropdown.stories.tsx new file mode 100644 index 00000000000..d95300e7572 --- /dev/null +++ b/src/components/MobileButtonDropdown/MobileButtonDropdown.stories.tsx @@ -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 ` + +export default meta + +type Story = StoryObj + +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" }, + }, +} diff --git a/src/components/MobileButtonDropdown.tsx b/src/components/MobileButtonDropdown/index.tsx similarity index 76% rename from src/components/MobileButtonDropdown.tsx rename to src/components/MobileButtonDropdown/index.tsx index bc53f5ebdf7..97783811568 100644 --- a/src/components/MobileButtonDropdown.tsx +++ b/src/components/MobileButtonDropdown/index.tsx @@ -1,4 +1,6 @@ -import ButtonDropdown, { type ButtonDropdownProps } from "./ButtonDropdown" +import ButtonDropdown, { + type ButtonDropdownProps, +} from "@/components/ButtonDropdown" const MobileButtonDropdown = (props: ButtonDropdownProps) => { return ( diff --git a/src/components/Morpher/Morpher.stories.tsx b/src/components/Morpher/Morpher.stories.tsx new file mode 100644 index 00000000000..c4717527f78 --- /dev/null +++ b/src/components/Morpher/Morpher.stories.tsx @@ -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) => ( +
+ +
+ ), + ], +} satisfies Meta + +export default meta + +type Story = StoryObj + +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"], + }, +} diff --git a/src/components/SocialListItem/SocialListItem.stories.tsx b/src/components/SocialListItem/SocialListItem.stories.tsx new file mode 100644 index 00000000000..6dba556e3b5 --- /dev/null +++ b/src/components/SocialListItem/SocialListItem.stories.tsx @@ -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) => ( +
+ +
+ ), + ], +} satisfies Meta + +export default meta + +type Story = StoryObj + +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: () => ( + + ethereum.org + /r/ethereum on Reddit + + @ethereum on Twitter / X + + + Ethereum Foundation YouTube + + Ethereum on Discord + + Ethereum Stack Exchange + + + ), +} diff --git a/src/components/SocialListItem.tsx b/src/components/SocialListItem/index.tsx similarity index 100% rename from src/components/SocialListItem.tsx rename to src/components/SocialListItem/index.tsx diff --git a/src/components/SupportedLanguagesTooltip/SupportedLanguagesTooltip.stories.tsx b/src/components/SupportedLanguagesTooltip/SupportedLanguagesTooltip.stories.tsx new file mode 100644 index 00000000000..69fb820d734 --- /dev/null +++ b/src/components/SupportedLanguagesTooltip/SupportedLanguagesTooltip.stories.tsx @@ -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) => ( +
+ First 5 languages, then + +
+ ), + ], +} satisfies Meta + +export default meta + +type Story = StoryObj + +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.", + }, + }, + }, +}