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"