Skip to content

Commit

Permalink
Build out the transactions screen (#14)
Browse files Browse the repository at this point in the history
* Create <List /> component

* Extract PraxDripsyProvider

* Set up Storybook to work with i18n; Install testing libs

* Add XS icon size

* Truncate text in ListItem

* Create factories

* Create TransactionList

* Fix text overflow

* Add screenHorizontalMargin

* Create transactions state

* Add transactions list to home screen

* Add a primaryAction prop to List

* Remove unused props

* Remove unused var

* Rework header components

* Build out TransactionsScreen

* Tweak comments

* Add comments

* Fix suffix

* Add comment

* Add RN Jest matchers

* Update tests

* Update args

* Add comment

* Reorganize and add comments

* Early return

* Create initial portfolio screen (#15)

* Rename header

* Extract BalanceAndActions component; use it in PortfolioScreen

* Create store/type/factory for balances

* Create balance UI in portfolio screen

* Use more precise naming

* Tweak factories

* Wrap Storybook stories in <ReduxProvider />

* Fix ATOM mock data

* Delete BalanceList stories for now

* Build a simple AssetActionSheet

* Add comment
  • Loading branch information
jessepinho authored Jan 6, 2025
1 parent cedf501 commit 39cbe81
Show file tree
Hide file tree
Showing 52 changed files with 1,284 additions and 113 deletions.
16 changes: 14 additions & 2 deletions react-native-expo/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { lingui } from '@lingui/vite-plugin';
import path from 'path';
import react from '@vitejs/plugin-react';
import type { StorybookConfig } from '@storybook/react-vite';
Expand All @@ -19,8 +20,19 @@ const config: StorybookConfig = {
},
},
plugins: [
// Allows us to use JSX without `import React from 'react'`
react({ jsxRuntime: 'automatic' }),
// Required so that i18n works in Storybook
lingui(),
react({
// Allows us to use JSX without `import React from 'react'`
jsxRuntime: 'automatic',
babel: {
plugins: [
// Required so that Lingui `t()` calls and `<Trans />` components
// will work.
'@lingui/babel-plugin-lingui-macro',
],
},
}),
// Allows us to `import foo from './foo.svg'` in Storybook Web just like
// in React Native.
svgr({ svgrOptions: { exportType: 'default' } }),
Expand Down
12 changes: 9 additions & 3 deletions react-native-expo/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { DripsyProvider } from 'dripsy';
import dripsyTheme from '../utils/dripsyTheme';
import PraxI18nProvider from '../components/PraxI18nProvider';
import type { Preview } from '@storybook/react';
import React from 'react';
import ReduxProvider from '../components/ReduxProvider';
/**
* Ideally, we'd use `<FontProvider />` in the root decorator to provide fonts
* to Storybook. But that caused weird import issues. So for now, we'll just add
Expand All @@ -12,9 +14,13 @@ import './fonts.css';
const preview: Preview = {
decorators: [
Story => (
<DripsyProvider theme={dripsyTheme}>
<Story />
</DripsyProvider>
<ReduxProvider>
<PraxI18nProvider>
<DripsyProvider theme={dripsyTheme}>
<Story />
</DripsyProvider>
</PraxI18nProvider>
</ReduxProvider>
),
],
parameters: {
Expand Down
5 changes: 5 additions & 0 deletions react-native-expo/app/transactions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import TransactionsScreen from '@/components/TransactionsScreen';

export default function TransactionsRoute() {
return <TransactionsScreen />;
}
19 changes: 19 additions & 0 deletions react-native-expo/components/BackButtonHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Pressable } from 'dripsy';
import Header from '../Header';
import Icon from '../Icon';
import { ArrowLeftCircle } from 'lucide-react-native';
import { useRouter } from 'expo-router';

export default function BackButtonHeader() {
const router = useRouter();

return (
<Header
left={
<Pressable onPress={() => router.back()}>
<Icon IconComponent={ArrowLeftCircle} size='md' />
</Pressable>
}
/>
);
}
71 changes: 71 additions & 0 deletions react-native-expo/components/BalanceAndActions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Trans } from '@lingui/react/macro';
import { Sx, Text, View } from 'dripsy';
import Button from '../Button';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { close, open } from '@/store/depositFlow';
import DepositFlow from '../DepositFlow';

/**
* Renders the user's current balance, as well as buttons for actions related to
* their balance (such as Send/Deposit/Request).
*/
export default function BalanceAndActions() {
const dispatch = useAppDispatch();
const isOpen = useAppSelector(state => state.depositFlow.isOpen);

return (
<>
<View sx={sx.root}>
<View sx={sx.balanceWrapper}>
<Text sx={sx.balanceLabel}>
<Trans>Balance</Trans>
</Text>
<Text sx={sx.balance}>0.00 USDC</Text>
</View>

<View sx={sx.buttons}>
<Button actionType='accent' onPress={() => dispatch(open())}>
<Trans>Deposit</Trans>
</Button>
<Button>
<Trans>Request</Trans>
</Button>
</View>
</View>

<DepositFlow isOpen={isOpen} onClose={() => dispatch(close())} />
</>
);
}

const sx = {
balance: {
variant: 'text.h4',
},

balanceLabel: {
variant: 'text.small',

color: 'neutralLight',
},

balanceWrapper: {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
flexGrow: 1,
},

buttons: {
flexDirection: 'row',
flexGrow: 0,
gap: '$2',
px: '$4',
pb: '$4',
},

root: {
flexGrow: 1,
flexDirection: 'column',
},
} satisfies Record<string, Sx>;
44 changes: 27 additions & 17 deletions react-native-expo/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { Image, Sx, View, SafeAreaView } from 'dripsy';
import logo from './logo.png';
import Avatar from '../Avatar';
import { SafeAreaView, Sx, View } from 'dripsy';
import { ReactNode } from 'react';

export default function Header() {
export interface HeaderProps {
/**
* Content to render on the left side of the header, like a back button or
* avatar.
*/
left?: ReactNode;
/**
* Content to render in the left side of the header, like a title or logo.
*/
center?: ReactNode;
/** Content to render on the right side of the header. */
right?: ReactNode;
}

/**
* Render via a screen's `options.header` to show a header at the top of the
* screen.
*
* If you're looking for a header with a back button, use
* `<BackButtonHeader />`.
*/
export default function Header({ left, center, right }: HeaderProps) {
return (
<SafeAreaView>
<View sx={sx.root}>
<View sx={sx.left}>
<Avatar />
</View>
<View sx={sx.center}>
<Image sx={sx.logo} source={logo} />
</View>
<View sx={sx.right} />
<View sx={sx.left}>{left}</View>
<View sx={sx.center}>{center}</View>
<View sx={sx.right}>{right}</View>
</View>
</SafeAreaView>
);
Expand All @@ -31,12 +47,6 @@ const sx = {
height: '$8',
},

logo: {
height: 16,
width: 76,
resizeMode: 'contain',
},

right: {
width: '$8',
height: '$8',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useAppSelector } from '@/store/hooks';
import TransactionList from '../TransactionList';
import { shallowEqual } from 'react-redux';
import { Sx, Text } from 'dripsy';
import { Trans } from '@lingui/react/macro';
import { Link } from 'expo-router';

function SeeAllButton() {
return (
<Link href='/transactions'>
<Text sx={sx.seeAllButtonLabel}>
<Trans>See all</Trans>
</Text>
</Link>
);
}

/**
* A preview of the latest few transactions a user has, with a button to view
* them all.
*/
export default function HomeScreenTransactionsList() {
const first5Transactions = useAppSelector(
state => state.transactions.transactions.slice(0, 5),
shallowEqual,
);

return (
<TransactionList transactions={first5Transactions} primaryAction={<SeeAllButton />} showTitle />
);
}

const sx = {
seeAllButtonLabel: {
textDecorationLine: 'underline',
},
} satisfies Record<string, Sx>;
66 changes: 9 additions & 57 deletions react-native-expo/components/HomeScreen/index.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,21 @@
import Button from '../Button';
import { Sx, Text, View } from 'dripsy';
import DepositFlow from '../DepositFlow';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { close, open } from '@/store/depositFlow';
import { Trans } from '@lingui/react/macro';

export interface HomeScreenProps {}
import { Sx, View } from 'dripsy';
import HomeScreenTransactionsList from './HomeScreenTransactionsList';
import BalanceAndActions from '../BalanceAndActions';

export default function HomeScreen() {
const dispatch = useAppDispatch();
const isOpen = useAppSelector(state => state.depositFlow.isOpen);

return (
<>
<View sx={sx.root}>
<View sx={sx.balanceWrapper}>
<Text sx={sx.balanceLabel}>
<Trans>Balance</Trans>
</Text>
<Text sx={sx.balance}>0.00 USDC</Text>
</View>
<View sx={sx.root}>
{/** @todo: Make this a `ScrollView`. */}
<BalanceAndActions />

<View sx={sx.buttons}>
<Button actionType='accent' onPress={() => dispatch(open())}>
<Trans>Deposit</Trans>
</Button>
<Button>
<Trans>Request</Trans>
</Button>
</View>
</View>

<DepositFlow isOpen={isOpen} onClose={() => dispatch(close())} />
</>
<HomeScreenTransactionsList />
</View>
);
}

const sx = {
balance: {
variant: 'text.h4',
},

balanceLabel: {
variant: 'text.small',

color: 'neutralLight',
},

balanceWrapper: {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
flexGrow: 1,
},

buttons: {
flexDirection: 'row',
flexGrow: 0,
gap: '$2',
px: '$4',
pb: '$4',
},

root: {
flexGrow: 1,
flexDirection: 'column',
px: 'screenHorizontalMargin',
},
} satisfies Record<string, Sx>;
7 changes: 6 additions & 1 deletion react-native-expo/components/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useSx } from 'dripsy';
import { LucideIcon } from 'lucide-react-native';
import { ComponentProps, FC } from 'react';

export type IconSize = 'sm' | 'md' | 'lg';
export type IconSize = 'xs' | 'sm' | 'md' | 'lg';

export interface IconProps {
/**
Expand All @@ -16,6 +16,7 @@ export interface IconProps {
*/
IconComponent: LucideIcon | FC;
/**
* - `xs`: 10px square
* - `sm`: 16px square
* - `md`: 24px square
* - `lg`: 32px square
Expand All @@ -28,6 +29,10 @@ export interface IconProps {
}

const PROPS_BY_SIZE: Record<IconSize, ComponentProps<LucideIcon>> = {
xs: {
size: 10,
strokeWidth: 1,
},
sm: {
size: 16,
strokeWidth: 1,
Expand Down
32 changes: 32 additions & 0 deletions react-native-expo/components/List/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Meta, StoryObj } from '@storybook/react';

import List from '.';
import ListItem from '../ListItem';
import AssetIcon from '../AssetIcon';
import { Text } from 'dripsy';

const meta: Meta<typeof List> = {
component: List,
tags: ['autodocs'],
argTypes: {
children: { control: false },
primaryAction: { control: false },
},
};

export default meta;

export const Basic: StoryObj<typeof List> = {
args: {
title: 'Assets',
children: (
<>
<ListItem primaryText='ETH' secondaryText='Ethereum' avatar={<AssetIcon />} />
<ListItem primaryText='ATOM' secondaryText='Cosmos' avatar={<AssetIcon />} />
<ListItem primaryText='UM' secondaryText='Penumbra' avatar={<AssetIcon />} />
<ListItem primaryText='USDC' secondaryText='USD Coin' avatar={<AssetIcon />} />
</>
),
primaryAction: <Text sx={{ textDecorationLine: 'underline' }}>See all</Text>,
},
};
Loading

0 comments on commit 39cbe81

Please sign in to comment.