From 368e7e64828662cd7957feff9752e1dc532ecb8b Mon Sep 17 00:00:00 2001 From: katefominmoore23 Date: Mon, 28 Sep 2026 12:31:14 +0100 Subject: [PATCH] Add Storybook stories for countdown-timer component Created comprehensive countdown-timer.stories.tsx with stories for far-future targets (30 days), imminent targets (5 minutes), and expired states (1 hour ago). Includes stories for custom ended labels, very short time remaining (30 seconds), multiple states in one story, custom className variants, and intermediate durations (1 day, 1 hour). Stories demonstrate live countdown updates and state transitions using React's Date.now() for realistic time calculations. Add Storybook stories for card component layout primitive Created card.stories.tsx showcasing all Card component compositions: basic card with content only, cards with headers and titles, cards with header descriptions, complete cards with header/content/footer, cards with action slots, default and small size variants, multiple card grids, complex content layouts, interactive hover states, and size comparison. Stories cover all subcomponents (Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction) with realistic examples of common UI patterns. Add Storybook stories for dialog component Created dialog.stories.tsx with stories for basic dialog with trigger, dialogs with footer actions, dialogs without close button, long-content scrolling, stream operation (withdrawal) dialog, alert-style dialog with destructive actions, form-based dialog, and complex header structures with multi-section layouts. Stories demonstrate Dialog/DialogTrigger/DialogContent/DialogHeader/DialogTitle/DialogDescription/DialogFooter composition with varied button configurations and content patterns. Add Storybook stories for dropdown-menu component Created dropdown-menu.stories.tsx with stories for basic menus, grouped menu items with labels, checkbox-style radio selection (network/theme switching), theme selector with checked indicators, navbar-style dropdowns with icon triggers, large menus with multiple groups, nested menu simulation, destructive action groups, and alignment variations (start/center/end). Stories showcase DropdownMenu/DropdownMenuTrigger/DropdownMenuContent/DropdownMenuGroup/DropdownMenuLabel composition with state management and visual indicators for selected items. Closes #720 Closes #719 Closes #721 Closes #722 --- components/ui/card.stories.tsx | 224 ++++++++++++++++++ components/ui/countdown-timer.stories.tsx | 156 +++++++++++++ components/ui/dialog.stories.tsx | 261 +++++++++++++++++++++ components/ui/dropdown-menu.stories.tsx | 269 ++++++++++++++++++++++ 4 files changed, 910 insertions(+) create mode 100644 components/ui/card.stories.tsx create mode 100644 components/ui/countdown-timer.stories.tsx create mode 100644 components/ui/dialog.stories.tsx create mode 100644 components/ui/dropdown-menu.stories.tsx diff --git a/components/ui/card.stories.tsx b/components/ui/card.stories.tsx new file mode 100644 index 0000000..480e082 --- /dev/null +++ b/components/ui/card.stories.tsx @@ -0,0 +1,224 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction } from "./card" +import { Button } from "./button" + +const meta = { + title: "UI/Card", + component: Card, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta + +export default meta +type Story = StoryObj + +// Basic card with content only +export const BasicCard: Story = { + render: () => ( + + +

This is a simple card with just content.

+
+
+ ), +} + +// Card with header and content +export const WithHeader: Story = { + render: () => ( + + + Card Title + + +

This card has a header with a title.

+
+
+ ), +} + +// Card with header, title, and description +export const WithHeaderAndDescription: Story = { + render: () => ( + + + Stream Overview + View details about your active streams + + +

Your stream details would go here.

+
+
+ ), +} + +// Complete card with header, content, and footer +export const CompleteCard: Story = { + render: () => ( + + + Confirm Action + Are you sure you want to proceed? + + +

This action cannot be undone.

+
+ + + + +
+ ), +} + +// Card with header and action +export const WithHeaderAction: Story = { + render: () => ( + + + Stream Details + + + + + +

Stream information displayed here.

+
+
+ ), +} + +// Small size card +export const SmallCard: Story = { + render: () => ( + + + Small Card + + +

This is a compact card with reduced spacing.

+
+
+ ), +} + +// Multiple cards layout +export const MultipleCards: Story = { + render: () => ( +
+ + + Card One + First card + + +

Content for the first card.

+
+
+ + + Card Two + Second card + + +

Content for the second card.

+
+
+ + + Card Three + Third card + + +

Content for the third card.

+
+
+
+ ), +} + +// Card with complex content +export const ComplexContent: Story = { + render: () => ( + + + Transaction Details + Complete information about your transaction + + + + + +
+
+

From

+

GXXX...XXX

+
+
+

To

+

GYYY...YYY

+
+
+

Amount

+

1,000 USDC

+
+
+
+ + + + +
+ ), +} + +// Interactive card with hover effect +export const InteractiveCard: Story = { + render: () => ( + + + Clickable Stream + Click to view details + + +

USDC 1000 → John Doe

+
+
+ ), +} + +// Default vs Small size comparison +export const SizeComparison: Story = { + render: () => ( +
+
+

Default size

+ + + Default Card + + +

Standard spacing

+
+
+
+
+

Small size

+ + + Small Card + + +

Compact spacing

+
+
+
+
+ ), +} diff --git a/components/ui/countdown-timer.stories.tsx b/components/ui/countdown-timer.stories.tsx new file mode 100644 index 0000000..b09ea21 --- /dev/null +++ b/components/ui/countdown-timer.stories.tsx @@ -0,0 +1,156 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { CountdownTimer } from "./countdown-timer" + +const meta = { + title: "UI/CountdownTimer", + component: CountdownTimer, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta + +export default meta +type Story = StoryObj + +// Far future target (30 days from now) +export const FarFuture: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const thirtyDaysFromNow = BigInt(now + 30 * 24 * 60 * 60) + return ( +
+

Target: 30 days from now

+ +
+ ) + }, +} + +// Imminent (5 minutes from now) +export const Imminent: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const fiveMinutesFromNow = BigInt(now + 5 * 60) + return ( +
+

Target: 5 minutes from now

+ +
+ ) + }, +} + +// Expired (1 hour ago) +export const Expired: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const oneHourAgo = BigInt(now - 60 * 60) + return ( +
+

Target: 1 hour ago

+ +
+ ) + }, +} + +// Custom ended label +export const CustomEndedLabel: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const pastTime = BigInt(now - 60 * 60) + return ( +
+

Custom ended label: "Completed"

+ +
+ ) + }, +} + +// Very short time remaining (30 seconds) +export const VeryShortTime: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const thirtySecondsFromNow = BigInt(now + 30) + return ( +
+

Target: 30 seconds from now

+ +
+ ) + }, +} + +// Multiple timers with different states +export const MultipleStates: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const farFuture = BigInt(now + 30 * 24 * 60 * 60) + const imminent = BigInt(now + 5 * 60) + const expired = BigInt(now - 60 * 60) + + return ( +
+
+

Far future:

+ +
+
+

Imminent:

+ +
+
+

Expired:

+ +
+
+ ) + }, +} + +// With custom className +export const WithCustomClassName: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const futureTime = BigInt(now + 7 * 24 * 60 * 60) + return ( +
+

With custom styling

+ +
+ ) + }, +} + +// One day remaining +export const OneDayRemaining: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const oneDayFromNow = BigInt(now + 24 * 60 * 60) + return ( +
+

Target: 1 day from now

+ +
+ ) + }, +} + +// One hour remaining +export const OneHourRemaining: Story = { + render: () => { + const now = Math.floor(Date.now() / 1000) + const oneHourFromNow = BigInt(now + 60 * 60) + return ( +
+

Target: 1 hour from now

+ +
+ ) + }, +} diff --git a/components/ui/dialog.stories.tsx b/components/ui/dialog.stories.tsx new file mode 100644 index 0000000..465a2b0 --- /dev/null +++ b/components/ui/dialog.stories.tsx @@ -0,0 +1,261 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "./dialog" +import { Button } from "./button" + +const meta = { + title: "UI/Dialog", + component: Dialog, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta + +export default meta +type Story = StoryObj + +// Basic dialog with trigger +export const BasicDialog: Story = { + render: () => ( + + + + + + + Dialog Title + This is a basic dialog component. + +
+

Dialog content goes here.

+
+
+
+ ), +} + +// Dialog with footer actions +export const WithFooter: Story = { + render: () => ( + + + + + + + Confirm Action + Are you sure you want to continue? + +
+

This action cannot be undone.

+
+ + + + +
+
+ ), +} + +// Dialog without close button +export const WithoutCloseButton: Story = { + render: () => ( + + + + + + + Dialog Without Close Button + Close via the buttons below + +
+

You must use the action buttons to close this dialog.

+
+ + + + +
+
+ ), +} + +// Long content dialog +export const LongContent: Story = { + render: () => ( + + + + + + + Terms of Service + Please read our terms carefully + +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

+

Nisi ut aliquip ex ea commodo consequat.

+

Duis aute irure dolor in reprehenderit in voluptate velit esse.

+

Cillum dolore eu fugiat nulla pariatur.

+

Excepteur sint occaecat cupidatat non proident, sunt in culpa qui.

+

Officia deserunt mollit anim id est laborum sed ut perspiciatis.

+
+ + + + +
+
+ ), +} + +// Stream operation dialog example +export const StreamOperationDialog: Story = { + render: () => ( + + + + + + + Withdraw Funds + Withdraw your available balance from this stream + +
+
+ + +
+
+ +

1,250.50 USDC

+
+
+ + + + +
+
+ ), +} + +// Simple alert-style dialog +export const AlertDialog: Story = { + render: () => ( + + + + + + + Delete Stream? + This action cannot be undone + +
+

Are you sure you want to permanently delete this stream?

+
+ + + + +
+
+ ), +} + +// Dialog with form content +export const FormDialog: Story = { + render: () => ( + + + + + + + Create New Stream + Set up a new payment stream + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ ), +} + +// Dialog with complex header structure +export const ComplexHeader: Story = { + render: () => ( + + + + + + + Transaction Preview + Review the details of your transaction before confirming + +
+
+
+ From + GXXX...XXX +
+
+ To + GYYY...YYY +
+
+ Amount + 1,000.00 USDC +
+
+ Gas Fee + 0.01 USDC +
+
+ Total + 1,000.01 USDC +
+
+
+ + + + +
+
+ ), +} diff --git a/components/ui/dropdown-menu.stories.tsx b/components/ui/dropdown-menu.stories.tsx new file mode 100644 index 0000000..26becdf --- /dev/null +++ b/components/ui/dropdown-menu.stories.tsx @@ -0,0 +1,269 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuGroup, + DropdownMenuLabel, +} from "./dropdown-menu" +import { Button } from "./button" +import { Check, ChevronRight } from "lucide-react" + +const meta = { + title: "UI/DropdownMenu", + component: DropdownMenu, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta + +export default meta +type Story = StoryObj + +// Basic dropdown menu +export const BasicMenu: Story = { + render: () => ( + + + + + + Option 1 + Option 2 + Option 3 + + + ), +} + +// Menu with grouped items +export const GroupedMenu: Story = { + render: () => ( + + + + + + + Stream Actions + View Details + Edit + Pause + + + Admin + Settings + Delete Stream + + + + ), +} + +// Menu with checkbox items (radio-style selection) +export const WithCheckboxSelection: Story = { + render: () => { + const [selectedNetwork, setSelectedNetwork] = React.useState<"mainnet" | "testnet">("mainnet") + + return ( + + + + + + Select Network + setSelectedNetwork("mainnet")} + className="flex items-center justify-between" + > + Mainnet + {selectedNetwork === "mainnet" && } + + setSelectedNetwork("testnet")} + className="flex items-center justify-between" + > + Testnet + {selectedNetwork === "testnet" && } + + + + ) + }, +} + +// Menu with theme selection +export const ThemeMenu: Story = { + render: () => { + const [theme, setTheme] = React.useState<"light" | "dark" | "system">("light") + + return ( + + + + + + Choose Theme + setTheme("light")} className="flex items-center justify-between"> + Light + {theme === "light" && } + + setTheme("dark")} className="flex items-center justify-between"> + Dark + {theme === "dark" && } + + setTheme("system")} className="flex items-center justify-between"> + System + {theme === "system" && } + + + + ) + }, +} + +// Navbar-style dropdown with icons +export const NavbarDropdown: Story = { + render: () => ( + + + + + + Profile + Settings + Help + Logout + + + ), +} + +// Large menu with many options +export const LargeMenu: Story = { + render: () => ( + + + + + + + Streams + View All + Active Streams + Completed Streams + Cancelled Streams + + + Analytics + Summary + Volume Report + Recipient List + + + Utility + Export Data + Settings + Logout + + + + ), +} + +// Nested menu simulation (for context) +export const NestedMenuExample: Story = { + render: () => ( + + + + + + Share with + Email + Copy Link + QR Code + + Social + Twitter + Discord + + + + ), +} + +// Menu with destructive actions +export const DestructiveMenu: Story = { + render: () => ( + + + + + + + Safe Actions + View + Pause + Duplicate + + + Danger Zone + Cancel Stream + Delete Stream + + + + ), +} + +// Alignment variations +export const AlignmentVariations: Story = { + render: () => ( +
+
+

Start (left)

+ + + + + + Option 1 + Option 2 + + +
+
+

Center

+ + + + + + Option 1 + Option 2 + + +
+
+

End (right)

+ + + + + + Option 1 + Option 2 + + +
+
+ ), +} + +// Import React for the useState hook +import React from "react"