diff --git a/packages/smart-routing-address-react-ui/src/components/AddressDisplay/AddressDisplay.stories.tsx b/packages/smart-routing-address-react-ui/src/components/AddressDisplay/AddressDisplay.stories.tsx new file mode 100644 index 00000000..428989f8 --- /dev/null +++ b/packages/smart-routing-address-react-ui/src/components/AddressDisplay/AddressDisplay.stories.tsx @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { AddressDisplay } from './index' + +const meta: Meta = { + title: 'SmartRoutingAddress/AddressDisplay', + component: AddressDisplay, + parameters: { layout: 'centered' }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + onQrClick: { action: 'qr-clicked' }, + }, +} + +export default meta +type Story = StoryObj + +/** + * Loading variant — matches Figma 17634:104343 ("Smart Funding" card). + * Rendered while the parent is watching for a deposit at the generated + * address. + */ +export const LoadingWatching: Story = { + args: { + loadingText: 'Watching for your deposit on Base…', + }, +} + +/** + * Same loading variant, used earlier in the flow while the SRA server is + * still generating the deposit address itself. + */ +export const LoadingGenerating: Story = { + args: { + loadingText: 'Generating deposit address…', + }, +} + +/** + * Ready variant — matches Figma 17762:78875. Shows the full deposit address + * on the left and a 52×52 white QR button on the right. + */ +export const Ready: Story = { + args: { + address: '0x8f527b33CD7c791aEDe7EbA077140D81A0000001', + onQrClick: () => {}, + }, +} diff --git a/packages/smart-routing-address-react-ui/src/components/AddressDisplay/AddressDisplay.test.tsx b/packages/smart-routing-address-react-ui/src/components/AddressDisplay/AddressDisplay.test.tsx new file mode 100644 index 00000000..343b9076 --- /dev/null +++ b/packages/smart-routing-address-react-ui/src/components/AddressDisplay/AddressDisplay.test.tsx @@ -0,0 +1,70 @@ +/** + * @vitest-environment happy-dom + */ +import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { AddressDisplay } from './index' + +afterEach(cleanup) + +const ADDRESS = '0x8f527b33CD7c791aEDe7EbA077140D81A0000001' + +describe('AddressDisplay', () => { + describe('loading variant', () => { + it('renders the loading variant when address is omitted', () => { + render() + expect(screen.getByTestId('address-display-loading')).toBeDefined() + expect(screen.queryByTestId('address-display-ready')).toBeNull() + }) + + it('renders the default loading text', () => { + render() + expect(screen.getByText('Watching for your deposit…')).toBeDefined() + }) + + it('respects the loadingText prop', () => { + render() + expect(screen.getByText('Generating deposit address…')).toBeDefined() + }) + + it('does not render the QR button', () => { + render() + expect(screen.queryByTestId('address-display-qr-button')).toBeNull() + }) + }) + + describe('ready variant', () => { + it('renders the ready variant when address is supplied', () => { + render() + expect(screen.getByTestId('address-display-ready')).toBeDefined() + expect(screen.queryByTestId('address-display-loading')).toBeNull() + }) + + it('renders the full address text', () => { + render() + expect(screen.getByTestId('address-display-address').textContent).toBe( + ADDRESS, + ) + }) + + it('renders the QR button', () => { + render() + expect(screen.getByTestId('address-display-qr-button')).toBeDefined() + expect(screen.getByRole('button', { name: 'Show QR code' })).toBeDefined() + }) + + it('fires onQrClick when the QR button is clicked', () => { + const onQrClick = vi.fn() + render() + fireEvent.click(screen.getByTestId('address-display-qr-button')) + expect(onQrClick).toHaveBeenCalledTimes(1) + }) + + it('does not throw when onQrClick is omitted and the button is clicked', () => { + render() + expect(() => + fireEvent.click(screen.getByTestId('address-display-qr-button')), + ).not.toThrow() + }) + }) +}) diff --git a/packages/smart-routing-address-react-ui/src/components/AddressDisplay/index.tsx b/packages/smart-routing-address-react-ui/src/components/AddressDisplay/index.tsx new file mode 100644 index 00000000..d434ed2d --- /dev/null +++ b/packages/smart-routing-address-react-ui/src/components/AddressDisplay/index.tsx @@ -0,0 +1,92 @@ +import { cn, Icon, Text, Wrapper } from '@zerodev/react-ui' + +/** + * The card that renders the deposit address in the SRA "Deposit funds" screen. + * + * Two visual states, each mapped 1:1 to a Figma node: + * - Loading (Figma 17634:104343, "Smart Funding" card): 16px radius, 20% + * white surface, centered loading icon + `loadingText`. Rendered when + * `address` is omitted. + * - Ready (Figma 17762:78875, address+QR row): 14px radius, 40% offWhite + * surface, left-aligned address text + a 52×52 white QR button on the + * right. Rendered when `address` is supplied. + * + * The two states have deliberately different visuals; they share this + * component because the parent renders the same slot in both cases. + */ +export interface AddressDisplayProps { + /** Deposit address to render; when omitted, the loading variant is shown. */ + address?: string + /** + * Message rendered in the loading variant. Include chain-specific context + * from the caller, e.g., `Watching for your deposit on Base…`. + */ + loadingText?: string + /** Handler for the QR icon button (ready variant). */ + onQrClick?: () => void + className?: string +} + +export function AddressDisplay({ + address, + loadingText = 'Watching for your deposit…', + onQrClick, + className, +}: AddressDisplayProps) { + if (address === undefined) { + return ( + + + + {loadingText} + + + ) + } + + return ( + + + {address} + + + + ) +}