+ ),
+ ],
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ component:
+ "Vertically-stacked disclosure built on Radix Accordion. `type='single'` allows only one open at a time (with optional `collapsible`); `type='multiple'` allows any combination. The chevron icon flips for RTL via `:dir(rtl)` and rotates open via `data-state=open`. Pass `hideIcon` to suppress the chevron and provide your own visual cue.",
+ },
+ },
+ },
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+const SAMPLE = [
+ {
+ id: "item-1",
+ title: "What is Ethereum?",
+ body: "Ethereum is open access to digital money and data-friendly services for everyone -- no matter your background or location.",
+ },
+ {
+ id: "item-2",
+ title: "What is a layer 2?",
+ body: "Layer 2 networks scale Ethereum by handling transactions off the main chain while inheriting its security guarantees.",
+ },
+ {
+ id: "item-3",
+ title: "What is a validator?",
+ body: "Validators secure the network by proposing and attesting to blocks.",
+ },
+]
+
+export const SingleCollapsible: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "`type='single' collapsible` allows only one item open at a time, with the option to close the active item.",
+ },
+ },
+ },
+ render: () => (
+
+ {SAMPLE.map((item) => (
+
+ {item.title}
+ {item.body}
+
+ ))}
+
+ ),
+}
+
+export const Multiple: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "`type='multiple'` allows any combination of items to be open simultaneously.",
+ },
+ },
+ },
+ render: () => (
+
+ {SAMPLE.map((item) => (
+
+ {item.title}
+ {item.body}
+
+ ))}
+
+ ),
+}
+
+export const HideIcon: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "`hideIcon` on `AccordionTrigger` removes the default chevron. Useful when supplying a custom visual cue.",
+ },
+ },
+ },
+ render: () => (
+
+ {SAMPLE.map((item) => (
+
+ {item.title}
+ {item.body}
+
+ ))}
+
+ ),
+}
+
+export const CustomTrigger: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "Build a custom trigger by passing structured children to `AccordionTrigger`. The default chevron stays at the end.",
+ },
+ },
+ },
+ render: () => (
+
+ {SAMPLE.map((item, idx) => (
+
+
+
+
+ {idx + 1}
+
+ {item.title}
+
+
+ {item.body}
+
+ ))}
+
+ ),
+}
+
+export const RtlChevron: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "The chevron uses `ChevronNext`, which rotates for RTL locales via `:dir(rtl)`. Toggle the locale in the Storybook toolbar to see the flip.",
+ },
+ },
+ },
+ render: () => (
+
+
+ Toggle locale to test RTL flip
+
+ When the locale is RTL (Arabic, Urdu), the chevron points the other
+ way to match reading direction.
+
+
+
+ ),
+}
diff --git a/src/components/ui/__stories__/Alert.stories.tsx b/src/components/ui/__stories__/Alert.stories.tsx
index 1004907a00a..197b9e72b8f 100644
--- a/src/components/ui/__stories__/Alert.stories.tsx
+++ b/src/components/ui/__stories__/Alert.stories.tsx
@@ -1,11 +1,12 @@
import { Info } from "lucide-react"
-import { Meta, StoryObj } from "@storybook/nextjs"
+import type { Meta, StoryObj } from "@storybook/nextjs"
import {
Alert,
AlertCloseButton,
AlertContent,
AlertDescription,
+ AlertEmoji,
AlertIcon,
AlertTitle,
} from "../alert"
@@ -15,6 +16,12 @@ const meta = {
title: "Molecules / Action Feedback / Alerts",
component: Alert,
parameters: {
+ docs: {
+ description: {
+ component:
+ "Inline alert/callout. Six `variant` colors (`info | error | success | warning | update | banner`); `banner` renders edge-to-edge with no border-radius for top-of-page use. Sub-components: `AlertContent`, `AlertTitle`, `AlertDescription`, `AlertIcon` (lucide or other SVG), `AlertEmoji`, `AlertCloseButton`.",
+ },
+ },
layout: "none",
},
decorators: [
@@ -35,7 +42,26 @@ const DEMO_DESC = "This is an alert to be used for important information."
const VARIANTS = ["info", "error", "success", "warning", "update"] as const
+export const Default: Story = {
+ parameters: { chromatic: { disableSnapshot: true } },
+ render: (args) => (
+
+
+ {DEMO_TITLE}
+ {DEMO_DESC}
+
+
+ ),
+}
+
export const Variants: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story: "All five `variant` options stacked for visual comparison.",
+ },
+ },
+ },
render: (args) => (
{VARIANTS.map((variant) => (
@@ -67,6 +93,14 @@ export const WithCloseButton: Story = {
}
export const WithIcon: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "`AlertIcon` wraps an SVG icon with `[&>svg]:size-6` to constrain size. Icon color inherits from the variant via the alert's `**:[svg]:text-*` class.",
+ },
+ },
+ },
render: (args) => (
{VARIANTS.map((variant) => (
@@ -84,6 +118,41 @@ export const WithIcon: Story = {
),
}
+export const WithEmoji: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`AlertEmoji` renders the project's `Emoji` component at `text-4xl` aligned to the start of the alert.",
+ },
+ },
+ },
+ render: (args) => (
+
+
+
+
+ New feature
+
+ Layer 2 network filtering is now live across the dapps directory.
+
+
+
+
+
+
+ Did you know?
+
+ Validators secure the Ethereum network by proposing and attesting to
+ blocks.
+
+
+
+
diff --git a/src/components/ui/__stories__/Avatar.stories.tsx b/src/components/ui/__stories__/Avatar.stories.tsx
index 45a701ce12f..d5c6a66fb77 100644
--- a/src/components/ui/__stories__/Avatar.stories.tsx
+++ b/src/components/ui/__stories__/Avatar.stories.tsx
@@ -1,41 +1,94 @@
import type { Meta, StoryObj } from "@storybook/nextjs"
-import { Avatar, AvatarGroup } from "../avatar"
+import {
+ Avatar,
+ AvatarBase,
+ AvatarFallback,
+ AvatarGroup,
+ AvatarImage,
+} from "../avatar"
import { HStack, VStack } from "../flex"
+const SAMPLE = {
+ name: "Sam Richards",
+ src: "https://avatars.githubusercontent.com/u/8097623?v=4",
+ href: "#",
+}
+
const meta = {
- title: "Atoms / Media & Icons / Avatars",
+ title: "UI / Avatar",
component: Avatar,
+ args: SAMPLE,
+ parameters: {
+ docs: {
+ description: {
+ component:
+ "User avatar built on Radix Avatar. `Avatar` is the high-level component (link + image + fallback + optional label). The lower-level `AvatarBase` + `AvatarImage` + `AvatarFallback` primitives are exposed for custom compositions. `AvatarGroup` stacks avatars with overlap and an optional `max` cap.",
+ },
+ },
+ },
} satisfies Meta
export default meta
type Story = StoryObj
-export const Single: Story = {
- args: {
- name: "Sam Richards",
- src: "https://avatars.githubusercontent.com/u/8097623?v=4",
- href: "#",
- },
+export const Sizes: Story = {
+ parameters: { chromatic: { disableSnapshot: true } },
+ args: SAMPLE,
render: (args) => (
-
- {(["lg", "md", "sm", "xs"] as const).map((size) => (
+
+ {(["xs", "sm", "md", "lg"] as const).map((size) => (
))}
-
+
),
}
-export const Group: Story = {
+export const WithLabel: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "When `label` is provided, the avatar renders alongside the label inside a `LinkBox`. `direction: 'row'` (default) is horizontal; `'column'` stacks vertically.",
+ },
+ },
+ },
args: {
- name: "Sam Richards",
- src: "https://avatars.githubusercontent.com/u/8097623?v=4",
- href: "#",
+ ...SAMPLE,
+ href: "https://github.com/samajammin",
+ label: "samajammin",
},
render: (args) => (
-
- {(["sm", "xs"] as const).map((size) => (
+
+
+ {(["md", "sm", "xs"] as const).map((size, idx) => (
+
+ ))}
+
+
+ {(["md", "sm", "xs"] as const).map((size, idx) => (
+
+ ))}
+
+
+ ),
+}
+
+export const Group: Story = {
+ args: SAMPLE,
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "`AvatarGroup` overlaps avatars and renders a `+N` overflow fallback when `max` is exceeded.",
+ },
+ },
+ },
+ render: (args) => (
+
+ {(["sm", "md"] as const).map((size) => (
@@ -49,38 +102,66 @@ export const Group: Story = {
export const BrokenImageFallback: Story = {
args: {
- name: "Sam Richards",
+ ...SAMPLE,
src: "https://placehold.co/404error",
- href: "#",
+ },
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "When the image fails to load, `Avatar` swaps to `AvatarImage` (which then falls back to `AvatarFallback` rendering the user's initials).",
+ },
+ },
},
render: (args) => (
-
- {(["lg", "md", "sm", "xs"] as const).map((size) => (
+
+ {(["xs", "sm", "md", "lg"] as const).map((size) => (
))}
-
+
),
}
-export const WithUsername: Story = {
- args: {
- name: "Sam Richards",
- src: "https://avatars.githubusercontent.com/u/8097623?v=4",
- href: "https://github.com/samajammin",
- label: "samajammin",
+export const BasePrimitives: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`AvatarBase`, `AvatarImage`, and `AvatarFallback` exposed for custom compositions. Combine them when the high-level `Avatar` does not fit the use case.",
+ },
+ },
},
- render: (args) => (
-
-
- {(["md", "sm", "xs"] as const).map((size, idx) => (
-
- ))}
-
-
- {(["md", "sm", "xs"] as const).map((size, idx) => (
-
- ))}
-
+ render: () => (
+
+
+
+ SR
+
+
+
+ SR
+
+
+ ),
+}
+
+export const FallbackOnly: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story: "`AvatarFallback` rendered without an image source.",
+ },
+ },
+ },
+ render: () => (
+
+ {(["xs", "sm", "md", "lg"] as const).map((size) => (
+
+ SR
+
+ ))}
),
}
diff --git a/src/components/ui/__stories__/Button.stories.tsx b/src/components/ui/__stories__/Button.stories.tsx
index 1e8db8be06d..57c8b2c3eb0 100644
--- a/src/components/ui/__stories__/Button.stories.tsx
+++ b/src/components/ui/__stories__/Button.stories.tsx
@@ -5,37 +5,149 @@ import { Button, type ButtonVariantProps } from "../buttons/Button"
import { HStack, VStack } from "../flex"
const meta = {
- title: "Atoms / Form / Buttons",
+ title: "UI / Button",
component: Button,
args: {
children: "What is Ethereum?",
},
+ parameters: {
+ docs: {
+ description: {
+ component:
+ "Action button. `variant`: `solid | outline | ghost | link`. `size`: `lg | md | sm`. `isSecondary` switches the text/border tone from primary to body color, but is a no-op on `solid` and `link`. Pass `asChild` to render as another element (e.g. anchor) while keeping the button styling.",
+ },
+ },
+ },
} satisfies Meta
export default meta
type Story = StoryObj
-const variants: ButtonVariantProps["variant"][] = [
+const VARIANTS: ButtonVariantProps["variant"][] = [
"solid",
"outline",
"ghost",
"link",
]
-export const StyleVariants: Story = {
+const SIZES: ButtonVariantProps["size"][] = ["lg", "md", "sm"]
+
+export const Default: Story = {
+ parameters: { chromatic: { disableSnapshot: true } },
+}
+
+export const Variants: Story = {
+ parameters: { chromatic: { disableSnapshot: true } },
render: (args) => (
-
- {variants.map((variant) => (
+
+ {VARIANTS.map((variant) => (
-
+
))}
),
}
+export const Sizes: Story = {
+ parameters: { chromatic: { disableSnapshot: true } },
+ render: (args) => (
+
+ {SIZES.map((size) => (
+
+ ))}
+
+ ),
+}
+
+export const SizeVariantMatrix: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "Each variant rendered at each size. Use this to spot regressions across the matrix.",
+ },
+ },
+ },
+ render: (args) => (
+
+
+
+
+ {SIZES.map((size) => (
+
+ {size}
+
+ ))}
+
+
+
+ {VARIANTS.map((variant) => (
+
+
{variant}
+ {SIZES.map((size) => (
+
+
+
+ ))}
+
+ ))}
+
+
+ ),
+}
+
+export const IsSecondary: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`isSecondary` switches the primary tone to the body tone. No-op on `solid` and `link` variants -- they keep their canonical styling.",
+ },
+ },
+ },
+ render: (args) => (
+
+ {VARIANTS.map((variant) => (
+
+
+
+
+ ))}
+
+ ),
+}
+
+export const AsChild: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`asChild` renders the button styling on the child element. Useful for wrapping non-button elements (anchors, custom components) without losing button styles.",
+ },
+ },
+ },
+ render: () => (
+
+
+
+
+ ),
+}
+
export const IconVariants: Story = {
render: (args) => (
diff --git a/src/components/ui/__stories__/ButtonLink.stories.tsx b/src/components/ui/__stories__/ButtonLink.stories.tsx
index 3fa557a06ee..44f5bd91c9c 100644
--- a/src/components/ui/__stories__/ButtonLink.stories.tsx
+++ b/src/components/ui/__stories__/ButtonLink.stories.tsx
@@ -1,17 +1,128 @@
import type { Meta, StoryObj } from "@storybook/nextjs"
-import { ButtonLink as ButtonLinkComponent } from "../buttons/Button"
+import { ButtonLink, type ButtonVariantProps } from "../buttons/Button"
+import { HStack, VStack } from "../flex"
const meta = {
- title: "Atoms / Form / Buttons",
- component: ButtonLinkComponent,
-} satisfies Meta
+ title: "UI / ButtonLink",
+ component: ButtonLink,
+ args: {
+ href: "#",
+ children: "What is Ethereum?",
+ },
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ component:
+ "Anchor styled as a `Button`. Same `variant`, `size`, and `isSecondary` props as `Button`. Inherits auto-detected behaviors from `BaseLink`: external links open in a new tab, file links get a download icon, hash links smooth-scroll within the page.",
+ },
+ },
+ },
+} satisfies Meta
export default meta
-export const ButtonLink: StoryObj = {
+type Story = StoryObj
+
+const VARIANTS: ButtonVariantProps["variant"][] = [
+ "solid",
+ "outline",
+ "ghost",
+ "link",
+]
+
+const SIZES: ButtonVariantProps["size"][] = ["lg", "md", "sm"]
+
+export const Default: Story = {}
+
+export const Variants: Story = {
+ render: (args) => (
+
+ {VARIANTS.map((variant) => (
+
+ {variant}
+
+ ))}
+
+ ),
+}
+
+export const Sizes: Story = {
+ render: (args) => (
+
+ {SIZES.map((size) => (
+
+ ))}
+
+ ),
+}
+
+export const IsSecondary: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "Same `isSecondary` semantics as `Button`: no-op on `solid` and `link`.",
+ },
+ },
+ },
+ render: (args) => (
+
+ {VARIANTS.map((variant) => (
+
+
+ {variant}
+
+
+ {variant} + isSecondary
+
+
+ ))}
+
+ ),
+}
+
+export const ExternalLink: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "External URLs open in a new tab and get a small external-link arrow appended via `BaseLink`'s auto-detection.",
+ },
+ },
+ },
args: {
- href: "#",
- children: "What is Ethereum?",
+ href: "https://ethresear.ch",
+ children: "ethresear.ch",
+ },
+}
+
+export const FileDownload: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "URLs ending in a recognized file extension are detected by `BaseLink` and rendered with a download icon.",
+ },
+ },
+ },
+ args: {
+ href: "/ethereum-whitepaper.pdf",
+ children: "Download whitepaper",
+ },
+}
+
+export const HashLink: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story: "Hash-only hrefs scroll within the page rather than navigating.",
+ },
+ },
+ },
+ args: {
+ href: "#section",
+ children: "Jump to section",
},
}
diff --git a/src/components/ui/__stories__/EdgeScrollContainer.stories.tsx b/src/components/ui/__stories__/EdgeScrollContainer.stories.tsx
index ae71bdaf8a6..efe4cb50459 100644
--- a/src/components/ui/__stories__/EdgeScrollContainer.stories.tsx
+++ b/src/components/ui/__stories__/EdgeScrollContainer.stories.tsx
@@ -1,12 +1,11 @@
-import { Meta, StoryObj } from "@storybook/nextjs"
+import type { Meta, StoryObj } from "@storybook/nextjs"
import { EdgeScrollContainer, EdgeScrollItem } from "../edge-scroll-container"
const meta = {
- title: "Molecules / Navigation / EdgeScrollContainer",
+ title: "UI / EdgeScrollContainer",
component: EdgeScrollContainer,
decorators: [
- // Simulate page container to demonstrate edge-to-edge effect
(Story) => (
@@ -15,6 +14,12 @@ const meta = {
],
parameters: {
layout: "fullscreen",
+ docs: {
+ description: {
+ component:
+ "Horizontal scroll container that bleeds to the screen edges while keeping content aligned with the page gutter. Items snap by default; pass `snap={false}` to disable. CSS variables (`--edge-spacing`, `--edge-mask-size`, `--edge-overflow-y-pad`) are configurable via props or `className` for responsive overrides.",
+ },
+ },
},
} satisfies Meta
@@ -30,7 +35,8 @@ const SampleCard = ({ children }: { children: React.ReactNode }) => (
const cardsArray = Array.from({ length: 10 }).map((_, idx) => idx + 1)
-export const Basic: Story = {
+export const Default: Story = {
+ parameters: { chromatic: { disableSnapshot: true } },
render: () => (
{cardsArray.map((i) => (
@@ -43,6 +49,14 @@ export const Basic: Story = {
}
export const WithoutSnap: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "`snap={false}` disables snap-mandatory scrolling. Use for free-scroll content where snap points would feel constrained.",
+ },
+ },
+ },
render: () => (
{cardsArray.map((i) => (
@@ -54,17 +68,68 @@ export const WithoutSnap: Story = {
),
}
-export const AsChildExample: Story = {
+export const AsChild: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`EdgeScrollItem asChild` renders the wrapper as the child element so each card can be a single `` with the same scroll-snap behavior.",
+ },
+ },
+ },
render: () => (
{cardsArray.map((i) => (
-
- Clickable Card {i}
-
+ Clickable card {i}
+
+
+ ))}
+
+ ),
+}
+
+export const CustomSpacing: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "Override edge spacing via the `spacing` prop. For responsive overrides, pass via `className` (e.g. `[--edge-spacing:0.5rem] md:[--edge-spacing:1rem]`).",
+ },
+ },
+ },
+ render: () => (
+
+ {cardsArray.map((i) => (
+
+ Card {i}
+
+ ))}
+
+ ),
+}
+
+export const FewItems: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "When content does not overflow, the container behaves like a regular flex row. Useful for confirming the no-scroll state.",
+ },
+ },
+ },
+ render: () => (
+
+ {[1, 2].map((i) => (
+
+ Card {i}
))}
diff --git a/src/components/ui/__stories__/Link.stories.tsx b/src/components/ui/__stories__/Link.stories.tsx
index d99ac27fc4d..726413b9adb 100644
--- a/src/components/ui/__stories__/Link.stories.tsx
+++ b/src/components/ui/__stories__/Link.stories.tsx
@@ -1,12 +1,12 @@
-import { Meta, StoryObj } from "@storybook/nextjs"
+import type { Meta, StoryObj } from "@storybook/nextjs"
-import { Center, Stack } from "../flex"
-import Link from "../Link"
+import { Center, Stack, VStack } from "../flex"
+import InlineLink, { BaseLink, LinkWithArrow } from "../Link"
import { ListItem, UnorderedList } from "../list"
const meta = {
- title: "Molecules / Navigation / Links",
- component: Link,
+ title: "UI / Link",
+ component: InlineLink,
decorators: [
(Story) => (
@@ -14,7 +14,15 @@ const meta = {
),
],
-} satisfies Meta
+ parameters: {
+ docs: {
+ description: {
+ component:
+ "Link variants. `InlineLink` (default export) is the in-prose link with visited styling. `BaseLink` is the underlying primitive without visited styling -- use for nav/CTA contexts. `LinkWithArrow` appends a chevron that flips for RTL locales. All three auto-detect external URLs (open in new tab + external icon), `mailto:` (mail icon), and recognized file extensions (download icon).",
+ },
+ },
+ },
+} satisfies Meta
export default meta
@@ -22,45 +30,157 @@ type Story = StoryObj
const MockParagraph = ({ href }: { href: string }) => (
- Text body normal. Ethereum is open access to digital money and data-friendly
- services for everyone – no matter your background or location.
- It's a community-built technology behind the
+ Ethereum is open access to digital money and data-friendly services for
+ everyone -- no matter your background or location. It is a{" "}
+ community-built technology behind the
cryptocurrency ether (ETH) and thousands of applications you can use today.
)
-export const InternalLink: Story = {
- args: {
- href: "#",
+export const InlineLinkStory: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`InlineLink` (default export of `Link.tsx`). Use in prose; the visited state is preserved.",
+ },
+ },
},
+ name: "InlineLink",
+ args: { href: "#" },
render: (args) => ,
}
-export const ExternalLink: Story = {
- args: {
- href: "https://example.com",
+export const BaseLinkStory: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`BaseLink` is the underlying anchor without visited styling. Use in nav, CTA, or non-prose contexts.",
+ },
+ },
+ },
+ name: "BaseLink",
+ render: () => (
+
+ Internal nav link
+ External nav link
+ mailto: link
+
+ ),
+}
+
+export const LinkWithArrowStory: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`LinkWithArrow` appends a chevron that flips for RTL locales (Arabic, Urdu). Toggle the locale in the Storybook toolbar to see the flip.",
+ },
+ },
+ },
+ name: "LinkWithArrow",
+ render: () => (
+
+ Read more
+ Explore dapps
+
+ ),
+}
+
+export const ExternalDetection: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "External URLs (anything outside the site origin) open in a new tab and get an external-link icon.",
+ },
+ },
},
+ args: { href: "https://example.com" },
render: (args) => ,
}
+export const MailtoDetection: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story:
+ "`mailto:` URLs are detected and rendered with a mail icon by `BaseLink`.",
+ },
+ },
+ },
+ render: () => (
+
+ Read the{" "}
+
+ Ethereum whitepaper
+ {" "}
+ for the original design rationale.
+
+ ),
+}
+
+export const HashLink: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ docs: {
+ description: {
+ story: "Hash-only hrefs scroll within the page rather than navigating.",
+ },
+ },
+ },
+ render: () => (
+
+ Skip to the summary for the
+ bottom-line answer.
+
+ ),
+}
+
export const LinkList: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "Realistic list of mixed internal and external links to confirm visited styling and external-icon placement.",
+ },
+ },
+ },
render: () => (
-
- Text body normal. Ethereum is open access to digital money and
- data-friendly services for everyone – no matter your background or
- location. It's a community-built technology behind the
- cryptocurrency ether (ETH) and thousands of applications you can use
- today.
-