Skip to content

Commit fbdaef2

Browse files
authored
Merge pull request #1004 from Mystery-CLI/storybook-stream-components
test: add stream component Storybook stories
2 parents 9b9fac6 + da6de24 commit fbdaef2

4 files changed

Lines changed: 271 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import { DashboardStats, DashboardStatsSkeleton } from './dashboard-stats'
3+
import type { StreamData } from '@/types/stream'
4+
5+
const NOW = 1_767_225_600 // 2026-01-01T00:00:00Z
6+
const DAY = 86_400n
7+
const USDC = {
8+
address: 'CBBT7UGKPGP7EOUDJYVWGXQKD4CWPVD54PH43D5F7WAJT3XJVJ2XPMA',
9+
symbol: 'USDC',
10+
decimals: 6,
11+
}
12+
13+
function makeStream(id: string, overrides: Partial<StreamData> = {}): StreamData {
14+
const startTime = NOW - Number(DAY * 10n)
15+
const endTime = NOW + Number(DAY * 20n)
16+
const depositedAmount = 3_000_000_000n
17+
return {
18+
id,
19+
sender: 'GBQ2X7KFY3R4VZ6N5LJ7WQH3M2PD8C9SAUTV4EXAMPLE0WALLET00ADDR',
20+
recipient: 'GDZSTFXVCDTUJ76ZAV2HA72KYRMF5QJMJBFPMJDHKHXU4LBKCZW75J2Z',
21+
token: USDC,
22+
depositedAmount,
23+
withdrawnAmount: 500_000_000n,
24+
startTime: BigInt(startTime),
25+
endTime: BigInt(endTime),
26+
cliffTime: BigInt(startTime),
27+
cliffAmount: 0n,
28+
amountPerSecond: depositedAmount / (DAY * 30n),
29+
linearAmount: depositedAmount,
30+
duration: DAY * 30n,
31+
cancelled: false,
32+
metadata: { name: 'Monthly payroll', category: 'payroll', memo: '' },
33+
...overrides,
34+
}
35+
}
36+
37+
const meta = {
38+
title: 'Streams/DashboardStats',
39+
component: DashboardStats,
40+
parameters: {
41+
layout: 'padded',
42+
},
43+
tags: ['autodocs'],
44+
decorators: [
45+
(Story) => (
46+
<div className="w-full max-w-5xl">
47+
<Story />
48+
</div>
49+
),
50+
],
51+
} satisfies Meta<typeof DashboardStats>
52+
53+
export default meta
54+
type Story = StoryObj<typeof meta>
55+
56+
/** Populated dashboard with incoming and outgoing streams. */
57+
export const WithData: Story = {
58+
args: {
59+
sent: [makeStream('sent-1')],
60+
received: [
61+
makeStream('received-1', { withdrawnAmount: 750_000_000n }),
62+
makeStream('received-2', { depositedAmount: 1_500_000_000n, withdrawnAmount: 0n }),
63+
],
64+
},
65+
}
66+
67+
/** Loading placeholders shown while stream and price data are being fetched. */
68+
export const Skeleton: Story = {
69+
render: () => <DashboardStatsSkeleton />,
70+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import { EmptyStreams } from './empty-state'
3+
4+
const meta = {
5+
title: 'Streams/EmptyStreams',
6+
component: EmptyStreams,
7+
parameters: {
8+
layout: 'padded',
9+
},
10+
tags: ['autodocs'],
11+
argTypes: {
12+
title: { control: 'text' },
13+
description: { control: 'text' },
14+
showCreate: { control: 'boolean' },
15+
},
16+
} satisfies Meta<typeof EmptyStreams>
17+
18+
export default meta
19+
type Story = StoryObj<typeof meta>
20+
21+
/** The default empty state shown when a user has no streams. */
22+
export const Default: Story = {}
23+
24+
/** Empty state with copy tailored to a filtered or specialized stream list. */
25+
export const CustomTitleAndDescription: Story = {
26+
args: {
27+
title: 'No payroll streams',
28+
description: 'Create a payroll stream to pay contributors automatically over time.',
29+
},
30+
}
31+
32+
/** Read-only empty state when stream creation is not available in the current view. */
33+
export const WithoutCreateButton: Story = {
34+
args: {
35+
showCreate: false,
36+
},
37+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import { fn, mocked } from 'storybook/test'
3+
import { useWallet } from '@/hooks/use-wallet'
4+
import type { StreamData } from '@/types/stream'
5+
import { StreamCard } from './stream-card'
6+
7+
const NOW = 1_767_225_600 // 2026-01-01T00:00:00Z
8+
const DAY = 86_400n
9+
const ME = 'GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI'
10+
const SENDER = 'GCKFBEIYV2U22IO2BJ4KVJOIP7XPWQGQFKKWXR6DOSJBV7STMAQSMTGG'
11+
const RECIPIENT = ME
12+
const USDC = {
13+
address: 'CBBT7UGKPGP7EOUDJYVWGXQKD4CWPVD54PH43D5F7WAJT3XJVJ2XPMA',
14+
symbol: 'USDC',
15+
decimals: 6,
16+
}
17+
18+
type WalletState = ReturnType<typeof useWallet>
19+
20+
function mockWallet(address: string): WalletState {
21+
return {
22+
address,
23+
walletId: 'freighter',
24+
connecting: false,
25+
reconnecting: false,
26+
isConnected: true,
27+
networkMismatch: false,
28+
walletNetwork: 'TESTNET',
29+
connect: fn(async () => {}),
30+
disconnect: fn(),
31+
signTransaction: fn(async (xdr: string) => xdr),
32+
}
33+
}
34+
35+
function makeStream(id = 'stream-42', overrides: Partial<StreamData> = {}): StreamData {
36+
const startTime = BigInt(NOW - Number(DAY * 10n))
37+
const endTime = BigInt(NOW + Number(DAY * 20n))
38+
const depositedAmount = 3_000_000_000n
39+
return {
40+
id,
41+
sender: SENDER,
42+
recipient: RECIPIENT,
43+
token: USDC,
44+
depositedAmount,
45+
withdrawnAmount: 500_000_000n,
46+
startTime,
47+
endTime,
48+
cliffTime: startTime,
49+
cliffAmount: 0n,
50+
amountPerSecond: depositedAmount / (DAY * 30n),
51+
linearAmount: depositedAmount,
52+
duration: DAY * 30n,
53+
cancelled: false,
54+
metadata: { name: 'Monthly payroll', category: 'payroll', memo: 'January payment' },
55+
...overrides,
56+
}
57+
}
58+
59+
const meta = {
60+
title: 'Streams/StreamCard',
61+
component: StreamCard,
62+
parameters: {
63+
layout: 'padded',
64+
},
65+
tags: ['autodocs'],
66+
decorators: [
67+
(Story) => (
68+
<div className="w-full max-w-2xl">
69+
<Story />
70+
</div>
71+
),
72+
],
73+
beforeEach: () => {
74+
mocked(useWallet).mockReturnValue(mockWallet(ME))
75+
localStorage.removeItem('flowstar:pinned-streams')
76+
},
77+
} satisfies Meta<typeof StreamCard>
78+
79+
export default meta
80+
type Story = StoryObj<typeof meta>
81+
82+
/** Incoming stream — the current wallet is the recipient. */
83+
export const Incoming: Story = {
84+
args: {
85+
stream: makeStream(),
86+
},
87+
}
88+
89+
/** Outgoing stream — the current wallet is the sender. */
90+
export const Outgoing: Story = {
91+
beforeEach: () => {
92+
mocked(useWallet).mockReturnValue(mockWallet(SENDER))
93+
},
94+
args: {
95+
stream: makeStream(),
96+
},
97+
}
98+
99+
/** Dense single-row card for high-volume stream lists. */
100+
export const Compact: Story = {
101+
args: {
102+
stream: makeStream(),
103+
compact: true,
104+
},
105+
}
106+
107+
/** Incoming stream with the pinned indicator and highlighted border. */
108+
export const Pinned: Story = {
109+
beforeEach: () => {
110+
localStorage.setItem('flowstar:pinned-streams', JSON.stringify(['stream-42']))
111+
},
112+
args: {
113+
stream: makeStream(),
114+
},
115+
}
116+
117+
/** Selection mode with a checked stream selector. */
118+
export const Selectable: Story = {
119+
args: {
120+
stream: makeStream(),
121+
selectable: true,
122+
selected: true,
123+
onToggleSelect: fn(),
124+
},
125+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import type { StreamStatus } from '@/types/stream'
3+
import { StreamStatusBadge } from './stream-status-badge'
4+
5+
const statuses: StreamStatus[] = ['streaming', 'scheduled', 'completed', 'cancelled']
6+
7+
const meta = {
8+
title: 'Streams/StreamStatusBadge',
9+
component: StreamStatusBadge,
10+
parameters: {
11+
layout: 'centered',
12+
},
13+
tags: ['autodocs'],
14+
argTypes: {
15+
status: {
16+
control: 'select',
17+
options: statuses,
18+
},
19+
},
20+
} satisfies Meta<typeof StreamStatusBadge>
21+
22+
export default meta
23+
type Story = StoryObj<typeof meta>
24+
25+
export const Streaming: Story = {
26+
args: { status: 'streaming' },
27+
}
28+
29+
export const Scheduled: Story = {
30+
args: { status: 'scheduled' },
31+
}
32+
33+
export const Completed: Story = {
34+
args: { status: 'completed' },
35+
}
36+
37+
export const Cancelled: Story = {
38+
args: { status: 'cancelled' },
39+
}

0 commit comments

Comments
 (0)