From fb233659444bf995f0f77b772e5e7f11b6d8185d Mon Sep 17 00:00:00 2001 From: Johnero542 Date: Mon, 28 Sep 2026 13:42:00 +0100 Subject: [PATCH] Add Storybook stories for sonner toast wrapper Created sonner.stories.tsx with interactive toast triggers for success, error, info, and warning toast types. Includes stories for default state, individual toast types with descriptions, and long-message scenarios. All toasts render with proper icon and theme support through Sonner's theming system. Add Storybook stories for tabs component Created tabs.stories.tsx with comprehensive tab interaction patterns: default 3-tab layout (All/Receiving/Sending dashboard tabs), 4-tab extended layout, line variant styling, disabled tab state, and interactive component tracking active tab. Demonstrates keyboard navigation support and tab content switching. Add Storybook stories for token-amount formatter Created token-amount.stories.tsx covering amount formatting edge cases: small amounts (0.001 USDC), standard amounts (1 USDC), very large amounts (1,234,567.89 USDC), compact notation for large values (1.2K, 5.4T), different tokens (EUR, WBTC), and customizable fraction digits. Includes side-by-side comparison of normal vs compact formatting. Add Storybook stories for tx-preview-dialog Created tx-preview-dialog.stories.tsx with all three simulation states: simulating (spinner, no preview), successful simulation (fee/resource display, confirm enabled), and failed simulation (error message, confirm disabled). Includes pending sign state and interactive dialog with form handling. Closes #727 Closes #728 Closes #729 Closes #730 --- components/ui/sonner.stories.tsx | 135 ++++++++++++++ components/ui/tabs.stories.tsx | 168 +++++++++++++++++ components/ui/token-amount.stories.tsx | 148 +++++++++++++++ components/ui/tx-preview-dialog.stories.tsx | 192 ++++++++++++++++++++ 4 files changed, 643 insertions(+) create mode 100644 components/ui/sonner.stories.tsx create mode 100644 components/ui/tabs.stories.tsx create mode 100644 components/ui/token-amount.stories.tsx create mode 100644 components/ui/tx-preview-dialog.stories.tsx diff --git a/components/ui/sonner.stories.tsx b/components/ui/sonner.stories.tsx new file mode 100644 index 0000000..79e79af --- /dev/null +++ b/components/ui/sonner.stories.tsx @@ -0,0 +1,135 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { Toaster } from '@/components/ui/sonner' +import { Button } from '@/components/ui/button' +import { toast } from 'sonner' + +const meta: Meta = { + title: 'UI/Sonner', + component: Toaster, + tags: ['autodocs'], + parameters: { + layout: 'centered', + }, +} + +export default meta +type Story = StoryObj + +function ToastTriggers() { + return ( +
+ + + + + + +
+ ) +} + +export const Default: Story = { + render: () => ( + <> + + + + ), +} + +export const SuccessToast: Story = { + render: () => ( + <> + + + + ), +} + +export const ErrorToast: Story = { + render: () => ( + <> + + + + ), +} + +export const InfoToast: Story = { + render: () => ( + <> + + + + ), +} + +export const WithLongMessage: Story = { + render: () => ( + <> + + + + ), +} diff --git a/components/ui/tabs.stories.tsx b/components/ui/tabs.stories.tsx new file mode 100644 index 0000000..ed13d98 --- /dev/null +++ b/components/ui/tabs.stories.tsx @@ -0,0 +1,168 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { useState } from 'react' +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' +import { Card } from '@/components/ui/card' + +const meta: Meta = { + title: 'UI/Tabs', + component: Tabs, + tags: ['autodocs'], + parameters: { + layout: 'centered', + }, +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => ( + + + All + Receiving + Sending + + + +

All transactions and activity

+
+
+ + +

Incoming streams and transfers

+
+
+ + +

Outgoing streams and transfers

+
+
+
+ ), +} + +export const FourTabs: Story = { + render: () => ( + + + Overview + History + Settings + Advanced + + + +

Overview tab content

+
+
+ + +

History tab content with transaction list

+
+
+ + +

Settings tab content with preferences

+
+
+ + +

Advanced tab content with developer options

+
+
+
+ ), +} + +export const LineVariant: Story = { + render: () => ( + + + Tab 1 + Tab 2 + Tab 3 + + + +

Line variant tab 1

+
+
+ + +

Line variant tab 2

+
+
+ + +

Line variant tab 3

+
+
+
+ ), +} + +export const DisabledTab: Story = { + render: () => ( + + + Enabled + + Disabled + + Enabled + + + +

This tab is enabled

+
+
+ + +

This tab is also enabled

+
+
+
+ ), +} + +function InteractiveTabsExample() { + const [activeTab, setActiveTab] = useState('tab1') + + return ( +
+ + + Stream + Transfer + Claim + + + +

Recurring Stream

+

Set up a continuous payment stream over time

+
+
+ + +

One-time Transfer

+

Send tokens immediately to an address

+
+
+ + +

Claim Tokens

+

Claim available tokens from a stream

+
+
+
+

+ Use arrow keys to navigate between tabs (or try Alt+Arrow on some browsers) +

+
+ ) +} + +export const Interactive: Story = { + render: () => , +} diff --git a/components/ui/token-amount.stories.tsx b/components/ui/token-amount.stories.tsx new file mode 100644 index 0000000..2219de0 --- /dev/null +++ b/components/ui/token-amount.stories.tsx @@ -0,0 +1,148 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { TokenAmount } from '@/components/ui/token-amount' +import type { TokenInfo } from '@/types/stream' + +const meta: Meta = { + title: 'UI/TokenAmount', + component: TokenAmount, + tags: ['autodocs'], + parameters: { + layout: 'centered', + }, +} + +export default meta +type Story = StoryObj + +const mockUSDC: TokenInfo = { + symbol: 'USDC', + decimals: 6, + address: 'GBBD47UZQ', // truncated for example + issuer: 'GBBD47UZQD', +} + +const mockEUR: TokenInfo = { + symbol: 'EUR', + decimals: 2, + address: 'CEUR5CRBJZ', + issuer: 'CEUR5CRBJZTRWZXWV', +} + +const mockWBTC: TokenInfo = { + symbol: 'WBTC', + decimals: 8, + address: 'CWBTC52345', + issuer: 'CWBTC52345678901', +} + +export const SmallAmount: Story = { + args: { + amount: BigInt(100000), // 0.1 USDC + token: mockUSDC, + }, +} + +export const StandardAmount: Story = { + args: { + amount: BigInt(1000000), // 1 USDC + token: mockUSDC, + }, +} + +export const LargeAmount: Story = { + args: { + amount: BigInt(1234567890000), // 1,234,567.89 USDC + token: mockUSDC, + }, +} + +export const CompactSmall: Story = { + args: { + amount: BigInt(1200000), // 1.2 USDC + token: mockUSDC, + compact: true, + }, +} + +export const CompactLarge: Story = { + args: { + amount: BigInt(1200000000), // 1.2B USDC in compact + token: mockUSDC, + compact: true, + }, +} + +export const VeryLargeWithCompact: Story = { + args: { + amount: BigInt(5400000000000), // 5.4T in compact notation + token: mockUSDC, + compact: true, + }, +} + +export const DifferentToken: Story = { + args: { + amount: BigInt(12345), // 123.45 EUR + token: mockEUR, + }, +} + +export const Bitcoin: Story = { + args: { + amount: BigInt(50000000), // 0.5 WBTC + token: mockWBTC, + }, +} + +export const WithoutSymbol: Story = { + args: { + amount: BigInt(1000000), + token: mockUSDC, + showSymbol: false, + }, +} + +export const CustomMaxFractionDigits: Story = { + args: { + amount: BigInt(1234567), + token: mockUSDC, + maxFractionDigits: 2, + }, +} + +function TokenAmountComparison() { + const amounts = [ + { label: '0.001 USDC', amount: BigInt(1000) }, + { label: '0.1 USDC', amount: BigInt(100000) }, + { label: '1 USDC', amount: BigInt(1000000) }, + { label: '100 USDC', amount: BigInt(100000000) }, + { label: '1000 USDC', amount: BigInt(1000000000) }, + { label: '1,000,000 USDC', amount: BigInt(1000000000000) }, + ] + + return ( +
+

+ Amount Formatting Comparison +

+ {amounts.map((item) => ( +
+ {item.label} +
+ + + + + + {' USDC'} + +
+
+ ))} +
+ ) +} + +export const Comparison: Story = { + render: () => , +} diff --git a/components/ui/tx-preview-dialog.stories.tsx b/components/ui/tx-preview-dialog.stories.tsx new file mode 100644 index 0000000..ae41a5f --- /dev/null +++ b/components/ui/tx-preview-dialog.stories.tsx @@ -0,0 +1,192 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { useState } from 'react' +import { TxPreviewDialog } from '@/components/ui/tx-preview-dialog' +import { Button } from '@/components/ui/button' +import type { CreateStreamInput } from '@/types/stream' + +const meta: Meta = { + title: 'UI/TxPreviewDialog', + component: TxPreviewDialog, + tags: ['autodocs'], + parameters: { + layout: 'centered', + }, +} + +export default meta +type Story = StoryObj + +const mockInput: CreateStreamInput = { + recipientAddress: 'GDZSTFXVCDTUJ76ZAV2HA72KYRMF5QJMJBFPMJDHKHXU4LBKCZW75J2Z', + tokenAddress: 'CBBT7UGKPGP7EOUDJYVWGXQKD4CWPVD54PH43D5F7WAJT3XJVJ2XPMA', + startTime: Math.floor(Date.now() / 1000), + endTime: Math.floor(Date.now() / 1000) + 86400 * 30, // 30 days + totalAmount: '1000000000', // 1000 USDC + cancelable: true, + transferable: true, +} + +function DialogTrigger() { + const [open, setOpen] = useState(false) + const [pending, setPending] = useState(false) + + const handleConfirm = () => { + setPending(true) + setTimeout(() => { + setPending(false) + setOpen(false) + }, 2000) + } + + return ( + <> + + setOpen(false)} + pending={pending} + /> + + ) +} + +export const Default: Story = { + render: () => , +} + +function SimulatingState() { + const [open, setOpen] = useState(false) + + return ( + <> + + {}} + onCancel={() => setOpen(false)} + pending={false} + /> + + ) +} + +export const Simulating: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Shows the simulating state with spinner while transaction is being simulated', + }, + }, + }, +} + +function SuccessState() { + const [open, setOpen] = useState(false) + const [pending, setPending] = useState(false) + + const handleConfirm = () => { + setPending(true) + setTimeout(() => { + setPending(false) + setOpen(false) + }, 2000) + } + + return ( + <> + + setOpen(false)} + pending={pending} + /> + + ) +} + +export const SuccessSimulation: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Shows successful simulation with estimated fees and resource usage', + }, + }, + }, +} + +function FailedState() { + const [open, setOpen] = useState(false) + + return ( + <> + + {}} + onCancel={() => setOpen(false)} + pending={false} + /> + + ) +} + +export const FailedSimulation: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Shows failed simulation with error message, confirm button is disabled', + }, + }, + }, +} + +function PendingState() { + const [open, setOpen] = useState(true) + + return ( + {}} + onCancel={() => setOpen(false)} + pending={true} + /> + ) +} + +export const PendingSign: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Shows pending state when user is signing the transaction in their wallet', + }, + }, + }, +}