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
152 changes: 152 additions & 0 deletions src/components/ui/__stories__/Flex.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type { Meta, StoryObj } from "@storybook/nextjs"

import { Center, Flex, HStack, Stack, VStack } from "../flex"

const meta = {
title: "UI / Flex",
component: Flex,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Flex layout primitives. `Flex` is the base (no axis or alignment defaults beyond `display: flex`). `Center` aligns and centers with `gap-2`. `Stack` is `flex-col gap-2` and accepts a `separator` element rendered between children. `HStack` and `VStack` extend `Stack` with horizontal/vertical orientation and centered cross-axis alignment.",
},
},
},
} satisfies Meta<typeof Flex>

export default meta

type Story = StoryObj<typeof meta>

const Box = ({ children }: { children: React.ReactNode }) => (
<div className="rounded bg-primary-low-contrast px-3 py-2 text-sm text-primary">
{children}
</div>
)

export const FlexBase: Story = {
parameters: {
docs: {
description: {
story:
"`Flex` only applies `display: flex`. Add direction, alignment, and gap with Tailwind utilities.",
},
},
},
render: () => (
<Flex className="gap-2">
<Box>One</Box>
<Box>Two</Box>
<Box>Three</Box>
</Flex>
),
}

export const CenterBase: Story = {
parameters: {
docs: {
description: {
story:
"`Center` is `flex items-center justify-center gap-2`. Use for centered icon + text pairs or single-element centering.",
},
},
},
render: () => (
<Center className="h-32 rounded-md border">
<Box>Centered</Box>
</Center>
),
}

export const StackBase: Story = {
parameters: {
docs: {
description: {
story: "`Stack` is `flex flex-col gap-2` (vertical, no separator).",
},
},
},
render: () => (
<Stack>
<Box>One</Box>
<Box>Two</Box>
<Box>Three</Box>
</Stack>
),
}

export const StackWithSeparator: Story = {
parameters: {
docs: {
description: {
story:
"Pass a `separator` element to render it between children. The separator is cloned with `border self-stretch`.",
},
},
},
render: () => (
<Stack separator={<hr />}>
<Box>One</Box>
<Box>Two</Box>
<Box>Three</Box>
</Stack>
),
}

export const HStackBase: Story = {
parameters: {
docs: {
description: {
story:
"`HStack` lays out children horizontally with centered cross-axis alignment.",
},
},
},
render: () => (
<HStack>
<Box>One</Box>
<Box>Two</Box>
<Box>Three</Box>
</HStack>
),
}

export const VStackBase: Story = {
parameters: {
docs: {
description: {
story:
"`VStack` lays out children vertically with centered cross-axis alignment (each item is centered horizontally).",
},
},
},
render: () => (
<VStack>
<Box>One</Box>
<Box>Two</Box>
<Box>Three</Box>
</VStack>
),
}

export const HStackWithSeparator: Story = {
render: () => (
<HStack separator={<hr />}>
<Box>One</Box>
<Box>Two</Box>
<Box>Three</Box>
</HStack>
),
}

export const VStackWithSeparator: Story = {
render: () => (
<VStack separator={<hr />}>
<Box>One</Box>
<Box>Two</Box>
<Box>Three</Box>
</VStack>
),
}
90 changes: 90 additions & 0 deletions src/components/ui/__stories__/LinkBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Meta, StoryObj } from "@storybook/nextjs"

import { BaseLink } from "../Link"
import { LinkBox, LinkOverlay } from "../link-box"

const meta = {
title: "UI / LinkBox",
component: LinkBox,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Whole-card-clickable pattern. `LinkBox` is the positioned wrapper (`relative z-10`); `LinkOverlay` (built on `BaseLink`) places a `::before` pseudo-element across the entire box so any click within the card navigates. Other interactive children stay accessible because they have higher z-index.",
},
},
},
} satisfies Meta<typeof LinkBox>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {
render: () => (
<LinkBox className="block w-[320px] rounded-md border p-4 hover:bg-background-highlight">
<h3 className="font-semibold">
<LinkOverlay href="/layer-2">Layer 2 networks</LinkOverlay>
</h3>
<p className="mt-1 text-sm text-body-medium">
Anywhere on this card is a click target. The overlay covers the whole
box via the `before:absolute` pseudo-element.
</p>
</LinkBox>
),
}

export const WithNestedInteractive: Story = {
parameters: {
docs: {
description: {
story:
"Nested interactive elements (like a secondary link or button) need a higher stacking context to remain clickable. Use `relative z-10` on them.",
},
},
},
render: () => (
<LinkBox className="flex w-[320px] items-start gap-3 rounded-md border p-4 hover:bg-background-highlight">
<div className="flex-1">
<h3 className="font-semibold">
<LinkOverlay href="/dapps">Decentralized applications</LinkOverlay>
</h3>
<p className="mt-1 text-sm text-body-medium">
Clicking the card navigates to dapps. The badge below stays
independently clickable because it sits above the overlay.
</p>
</div>
<BaseLink
href="/dapps?filter=defi"
className="relative z-10 rounded-full border bg-background px-2 py-1 text-xs no-underline hover:bg-background-highlight"
hideArrow
>
DeFi
</BaseLink>
</LinkBox>
),
}

export const ExternalLink: Story = {
parameters: {
docs: {
description: {
story:
"When `LinkOverlay`'s href is external, `BaseLink` would normally apply `relative` positioning; `LinkOverlay` overrides this with `!static` so the `::before` overlay still anchors to the parent `LinkBox`.",
},
},
},
render: () => (
<LinkBox className="block w-[320px] rounded-md border p-4 hover:bg-background-highlight">
<h3 className="font-semibold">
<LinkOverlay href="https://ethresear.ch">
ethresear.ch (external)
</LinkOverlay>
</h3>
<p className="mt-1 text-sm text-body-medium">
Even with an external destination, the entire card is the click target.
</p>
</LinkBox>
),
}
114 changes: 114 additions & 0 deletions src/components/ui/__stories__/Section.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { Meta, StoryObj } from "@storybook/nextjs"

import {
Section,
SectionBanner,
SectionContent,
SectionHeader,
SectionTag,
} from "../section"

const meta = {
title: "UI / Section",
component: Section,
parameters: {
chromatic: { disableSnapshot: true },
docs: {
description: {
component:
"Top-level page section. `variant: responsiveFlex` enables a column-on-mobile / row-on-desktop layout. `scrollMargin: tabNav` adds extra scroll-margin so sticky-nav layouts land below the nav. Sub-components: `SectionBanner`, `SectionTag`, `SectionHeader`, `SectionContent`.",
},
},
},
} satisfies Meta<typeof Section>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {
render: () => (
<Section id="overview" className="space-y-4">
<SectionTag>Overview</SectionTag>
<SectionHeader>What is Ethereum?</SectionHeader>
<SectionContent>
<p>
Ethereum is a decentralized, open-source blockchain featuring smart
contract functionality.
</p>
</SectionContent>
</Section>
),
}

export const ResponsiveFlex: Story = {
parameters: {
docs: {
description: {
story:
"`variant: responsiveFlex` stacks on mobile and switches to a row at `md`.",
},
},
},
render: () => (
<Section id="responsive" variant="responsiveFlex">
<SectionBanner>
<div className="grid h-48 place-items-center text-sm text-body-medium">
Banner area
</div>
</SectionBanner>
<SectionContent>
<SectionTag>Layer 2</SectionTag>
<SectionHeader>Scaling Ethereum</SectionHeader>
<p>
Layer 2 networks bundle transactions off-chain and post proofs to
mainnet, reducing fees and increasing throughput.
</p>
</SectionContent>
</Section>
),
}

export const TabNavScrollMargin: Story = {
parameters: {
docs: {
description: {
story:
"`scrollMargin: tabNav` adds the extra scroll-margin needed when the page has a sticky tab nav above the fold. The visual effect is invisible at rest; observe by hash-navigating to `#tab-nav-section`.",
},
},
},
render: () => (
<Section
id="tab-nav-section"
scrollMargin="tabNav"
className="space-y-4 rounded-md border bg-background-highlight p-6"
>
<SectionTag>Sticky-nav layout</SectionTag>
<SectionHeader>Lands below the tab nav</SectionHeader>
<SectionContent>
<p>
Linking to `#tab-nav-section` will scroll so the section starts below
the sticky nav, not under it.
</p>
</SectionContent>
</Section>
),
}

export const TagVariants: Story = {
parameters: {
docs: {
description: {
story:
"`SectionTag` has two variants: `pill` (default, low-contrast bg) and `plain` (semibold uppercase, no background).",
},
},
},
render: () => (
<div className="space-y-4">
<SectionTag variant="pill">Pill (default)</SectionTag>
<SectionTag variant="plain">Plain</SectionTag>
</div>
),
}
Loading
Loading