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', + }, + }, + }, +}