From b1df928bf317d317c8516410a371f7a5a0d3f832 Mon Sep 17 00:00:00 2001 From: Hayden Briese Date: Tue, 30 Jan 2024 21:12:00 +1100 Subject: [PATCH] feat(app): use Link for hrefs where possible This allows greater accessibility e.g. middle clicking links --- app/src/app/(drawer)/[account]/settings.tsx | 20 +- .../(drawer)/approvers/[address]/index.tsx | 24 +- app/src/app/onboard/auth.tsx | 14 +- app/src/components/Button.tsx | 8 +- .../useCreatePolicySuggestion.tsx | 23 +- .../useLinkDeviceSuggestion.tsx | 12 +- app/src/components/item/UserApproverItem.tsx | 25 +- app/src/components/list/ListItem.tsx | 224 +++++++++--------- app/src/components/message/MessageItem.tsx | 18 +- app/src/components/policy/TokenLimitItem.tsx | 55 +++-- app/src/components/shared/AuthSettings.tsx | 9 +- .../transaction/TransactionItem.tsx | 58 +++-- .../components/walletconnect/PairingItem.tsx | 33 +-- 13 files changed, 260 insertions(+), 263 deletions(-) diff --git a/app/src/app/(drawer)/[account]/settings.tsx b/app/src/app/(drawer)/[account]/settings.tsx index 936df58cd..b7ba15cb5 100644 --- a/app/src/app/(drawer)/[account]/settings.tsx +++ b/app/src/app/(drawer)/[account]/settings.tsx @@ -1,4 +1,4 @@ -import { useRouter } from 'expo-router'; +import { Link, useRouter } from 'expo-router'; import { gql } from '@api/generated'; import { FlashList } from '@shopify/flash-list'; import { EditIcon, NavigateNextIcon } from '@theme/icons'; @@ -117,17 +117,15 @@ function AccountSettingsScreen() { )} - + + diff --git a/app/src/app/(drawer)/approvers/[address]/index.tsx b/app/src/app/(drawer)/approvers/[address]/index.tsx index 87854e456..adc9987fe 100644 --- a/app/src/app/(drawer)/approvers/[address]/index.tsx +++ b/app/src/app/(drawer)/approvers/[address]/index.tsx @@ -1,4 +1,4 @@ -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; import { View } from 'react-native'; import { Actions } from '~/components/layout/Actions'; import { StyleSheet } from 'react-native'; @@ -55,7 +55,6 @@ const ApproverScreenParams = z.object({ address: zAddress() }); function ApproverScreen() { const params = useLocalParams(ApproverScreenParams); - const router = useRouter(); const update = useMutation(Update)[1]; const query = useQuery(Query, { approver: params.address }); @@ -98,18 +97,17 @@ function ApproverScreen() { - + + diff --git a/app/src/app/onboard/auth.tsx b/app/src/app/onboard/auth.tsx index 176765626..54fd26d63 100644 --- a/app/src/app/onboard/auth.tsx +++ b/app/src/app/onboard/auth.tsx @@ -1,17 +1,15 @@ -import { useRouter } from 'expo-router'; -import { Button } from 'react-native-paper'; +import { Link } from 'expo-router'; +import { Button } from '~/components/Button'; import { AuthSettings } from '~/components/shared/AuthSettings'; export default function NotificationsOnboardingScreen() { - const router = useRouter(); - return ( router.push(`/onboard/notifications`)}> - Continue - + + + } /> ); diff --git a/app/src/components/Button.tsx b/app/src/components/Button.tsx index c0f0f6ca8..19214596b 100644 --- a/app/src/components/Button.tsx +++ b/app/src/components/Button.tsx @@ -1,14 +1,16 @@ import { useWithLoading } from '~/hooks/useWithLoading'; -import { Keyboard } from 'react-native'; +import { Keyboard, View } from 'react-native'; import { Button as PaperButton, ButtonProps as PaperButtonProps } from 'react-native-paper'; +import { forwardRef } from 'react'; export interface ButtonProps extends PaperButtonProps {} -export const Button = (props: PaperButtonProps) => { +export const Button = forwardRef((props, ref) => { const [loading, onPress] = useWithLoading(props.onPress); return ( { })} /> ); -}; +}); diff --git a/app/src/components/home/GettingStarted/useCreatePolicySuggestion.tsx b/app/src/components/home/GettingStarted/useCreatePolicySuggestion.tsx index 67b2e3373..09829e95e 100644 --- a/app/src/components/home/GettingStarted/useCreatePolicySuggestion.tsx +++ b/app/src/components/home/GettingStarted/useCreatePolicySuggestion.tsx @@ -1,6 +1,6 @@ import { FragmentType, gql, useFragment } from '@api'; import { PolicyIcon } from '@theme/icons'; -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; import { Suggestion } from '~/components/home/GettingStarted/suggestions'; import { ListItem } from '~/components/list/ListItem'; @@ -23,21 +23,18 @@ export interface UseCreatePolicySuggestionParams { export function useCreatePolicySuggestion(props: UseCreatePolicySuggestionParams): Suggestion { const account = useFragment(Account, props.account); - const router = useRouter(); return { Item: (props) => ( - - router.push({ - pathname: `/(drawer)/[account]/policies/[key]/`, - params: { account: account.address, key: 'add' }, - }) - } - {...props} - /> + + + ), complete: account.policies.filter((p) => p.state).length > 1, }; diff --git a/app/src/components/home/GettingStarted/useLinkDeviceSuggestion.tsx b/app/src/components/home/GettingStarted/useLinkDeviceSuggestion.tsx index ad8111a71..5871436bd 100644 --- a/app/src/components/home/GettingStarted/useLinkDeviceSuggestion.tsx +++ b/app/src/components/home/GettingStarted/useLinkDeviceSuggestion.tsx @@ -1,7 +1,7 @@ import { FragmentType, gql, useFragment } from '@api'; import { useApproverAddress } from '~/lib/network/useApprover'; import { DevicesIcon } from '@theme/icons'; -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; import { Suggestion } from '~/components/home/GettingStarted/suggestions'; import { ListItem } from '~/components/list/ListItem'; @@ -27,16 +27,12 @@ export interface UseLinkDeviceSuggestionProps { export function useLinkDeviceSuggestion(props: UseLinkDeviceSuggestionProps): Suggestion { const user = useFragment(User, props.user); const approver = useApproverAddress(); - const router = useRouter(); return { Item: (props) => ( - router.push(`/link/`)} - {...props} - /> + + + ), complete: !!user.approvers.find( (a) => a.address !== approver && !a.bluetoothDevices?.length && !a.cloud, diff --git a/app/src/components/item/UserApproverItem.tsx b/app/src/components/item/UserApproverItem.tsx index 3c89f6e88..da82169e6 100644 --- a/app/src/components/item/UserApproverItem.tsx +++ b/app/src/components/item/UserApproverItem.tsx @@ -1,6 +1,6 @@ import { FragmentType, gql, useFragment } from '@api/generated'; import { useApproverAddress } from '~/lib/network/useApprover'; -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; import { ListItem, ListItemProps } from '~/components/list/ListItem'; import { truncateAddr } from '~/util/format'; @@ -23,19 +23,20 @@ export interface UserApproverItemProps extends Partial { export function UserApproverItem(props: UserApproverItemProps) { const a = useFragment(UserApprover, props.approver); - const router = useRouter(); const selected = useApproverAddress() === a.address; return ( - - router.push({ pathname: `/(drawer)/approvers/[address]/`, params: { address: a.address } }) - } - {...props} - /> + + + ); } diff --git a/app/src/components/list/ListItem.tsx b/app/src/components/list/ListItem.tsx index 95f6a0038..fafe30a64 100644 --- a/app/src/components/list/ListItem.tsx +++ b/app/src/components/list/ListItem.tsx @@ -1,4 +1,4 @@ -import { FC, ReactNode } from 'react'; +import { FC, ReactNode, forwardRef } from 'react'; import { IconProps } from '@theme/icons'; import { Text, TouchableRipple, TouchableRippleProps } from 'react-native-paper'; import { AddressOrLabelIcon } from '../Identicon/AddressOrLabelIcon'; @@ -34,119 +34,125 @@ export type ListItemProps = Pick & textStyle?: StyleProp; }; -export function ListItem({ - leading: Leading, - leadingSize = typeof Leading === 'string' ? 'medium' : 'small', - overline: Overline, - headline: Headline, - supporting: Supporting, - trailing: Trailing, - lines = (1 + Number(!!Overline) + Number(!!Supporting)) as Lines, - selected, - disabled, - avatarLeadingSize, - containerStyle, - textStyle, - ...touchableProps -}: ListItemProps) { - const { styles } = useStyles( - useMemoApply(getStylesheet, { lines, leadingSize, selected, disabled, avatarLeadingSize }), - ); - - const OverlineText = ({ style, ...props }: TextProps) => ( - - ); - const HeadlineText = ({ style, ...props }: TextProps) => ( - - ); - const SupportingText = ({ style, ...props }: TextProps) => ( - - ); - const TrailingText = ({ style, ...props }: TextProps) => ( - - ); - - return ( - - <> - {Leading && ( - - {typeof Leading === 'string' ? ( - - ) : ( - - )} - - )} - - - {Overline && - (typeof Overline === 'function' ? ( - - ) : ( - {Overline} - ))} - - {typeof Headline === 'function' ? ( - - ) : ( - {Headline} +export const ListItem = forwardRef( + ( + { + leading: Leading, + leadingSize = typeof Leading === 'string' ? 'medium' : 'small', + overline: Overline, + headline: Headline, + supporting: Supporting, + trailing: Trailing, + lines = (1 + Number(!!Overline) + Number(!!Supporting)) as Lines, + selected, + disabled, + avatarLeadingSize, + containerStyle, + textStyle, + ...touchableProps + }, + ref, + ) => { + const { styles } = useStyles( + useMemoApply(getStylesheet, { lines, leadingSize, selected, disabled, avatarLeadingSize }), + ); + + const OverlineText = ({ style, ...props }: TextProps) => ( + + ); + const HeadlineText = ({ style, ...props }: TextProps) => ( + + ); + const SupportingText = ({ style, ...props }: TextProps) => ( + + ); + const TrailingText = ({ style, ...props }: TextProps) => ( + + ); + + return ( + + <> + {Leading && ( + + {typeof Leading === 'string' ? ( + + ) : ( + + )} + )} - {Supporting && - (typeof Supporting === 'function' ? ( - - ) : ( - {Supporting} - ))} - - - {Trailing && ( - - {typeof Trailing === 'function' ? ( - + + {Overline && + (typeof Overline === 'function' ? ( + + ) : ( + {Overline} + ))} + + {typeof Headline === 'function' ? ( + ) : ( - {Trailing} + {Headline} )} + + {Supporting && + (typeof Supporting === 'function' ? ( + + ) : ( + {Supporting} + ))} - )} - - - ); -} + + {Trailing && ( + + {typeof Trailing === 'function' ? ( + + ) : ( + {Trailing} + )} + + )} + + + ); + }, +); interface StyleProps { lines: Lines; diff --git a/app/src/components/message/MessageItem.tsx b/app/src/components/message/MessageItem.tsx index 45eed9cf3..828ab1d13 100644 --- a/app/src/components/message/MessageItem.tsx +++ b/app/src/components/message/MessageItem.tsx @@ -2,7 +2,7 @@ import { FragmentType, gql, useFragment } from '@api/generated'; import { ListItem, ListItemProps } from '../list/ListItem'; import { MessageIcon } from './MessageIcon'; import { P, match } from 'ts-pattern'; -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; import { createStyles, useStyles } from '@theme/styles'; const MessageProposal = gql(/* GraphQL */ ` @@ -34,7 +34,6 @@ export interface MessageItemProps { export function MessageItem(props: MessageItemProps) { const { styles } = useStyles(stylesheet); - const router = useRouter(); const p = useFragment(MessageProposal, props.proposal); const user = useFragment(User, props.user); @@ -52,13 +51,14 @@ export function MessageItem(props: MessageItemProps) { .exhaustive(); return ( - } - leadingSize="medium" - headline={p.label || 'Message'} - supporting={supporting} - onPress={() => router.push({ pathname: `/(drawer)/message/[id]`, params: { id: p.id } })} - /> + + } + leadingSize="medium" + headline={p.label || 'Message'} + supporting={supporting} + /> + ); } diff --git a/app/src/components/policy/TokenLimitItem.tsx b/app/src/components/policy/TokenLimitItem.tsx index 34bf07ec0..1fe41f396 100644 --- a/app/src/components/policy/TokenLimitItem.tsx +++ b/app/src/components/policy/TokenLimitItem.tsx @@ -1,6 +1,6 @@ import { FragmentType, gql, useFragment as getFragment } from '@api'; import { NavigateNextIcon } from '@theme/icons'; -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; import { Address, TransferLimit, asDecimal } from 'lib'; import _ from 'lodash'; import { Duration } from 'luxon'; @@ -29,7 +29,6 @@ export interface TokenSpendingProps { export function TokenLimitItem({ address, ...props }: TokenSpendingProps) { const token = getFragment(Token, props.token); - const router = useRouter(); const [policy] = usePolicyDraftState(); const limit: TransferLimit | undefined = policy.transfers.limits[address]; @@ -41,30 +40,34 @@ export function TokenLimitItem({ address, ...props }: TokenSpendingProps) { const duration = Duration.fromObject({ seconds: limit?.duration ?? 0 }); return ( - - token ? ( - - ) : ( - - ) - } - headline={token?.name ?? truncateAddr(address)} - trailing={(props) => ( - - - {!limit?.amount ? 'Not allowed' : `${formattedAmount} per ${prettyDuration(duration)}`} - - - - )} - onPress={() => - router.push({ - pathname: `/(drawer)/[account]/policies/[key]/spending/[token]`, - params: { account: policy.account, key: policy.key ?? 'add', token: address }, - }) - } - /> + + + token ? ( + + ) : ( + + ) + } + headline={token?.name ?? truncateAddr(address)} + trailing={(props) => ( + + + {!limit?.amount + ? 'Not allowed' + : `${formattedAmount} per ${prettyDuration(duration)}`} + + + + )} + /> + ); } diff --git a/app/src/components/shared/AuthSettings.tsx b/app/src/components/shared/AuthSettings.tsx index 96333fdcd..a5cdc3b6b 100644 --- a/app/src/components/shared/AuthSettings.tsx +++ b/app/src/components/shared/AuthSettings.tsx @@ -12,7 +12,7 @@ import { ScrollableScreenSurface } from '~/components/layout/ScrollableScreenSur import { AppbarOptions } from '~/components/Appbar/AppbarOptions'; import { AppbarMenu } from '~/components/Appbar/AppbarMenu'; import { Button } from '~/components/Button'; -import { Href, useRouter } from 'expo-router'; +import { Href, Link } from 'expo-router'; import { useBiometrics } from '~/hooks/useBiometrics'; import { usePasswordHash } from '~/app/(drawer)/settings/password'; import { persistedAtom } from '~/lib/persistedAtom'; @@ -35,7 +35,6 @@ export interface AuthSettingsProps { } function AuthSettings_({ actions, appbarMenu, passwordHref }: AuthSettingsProps) { - const router = useRouter(); const biometrics = useBiometrics(); const passwordConfigured = !!usePasswordHash(); @@ -62,9 +61,9 @@ function AuthSettings_({ actions, appbarMenu, passwordHref }: AuthSettingsProps) leading={PasswordIcon} headline="Password" trailing={() => ( - + + + )} /> diff --git a/app/src/components/transaction/TransactionItem.tsx b/app/src/components/transaction/TransactionItem.tsx index fe73599ef..3b6a5559f 100644 --- a/app/src/components/transaction/TransactionItem.tsx +++ b/app/src/components/transaction/TransactionItem.tsx @@ -7,11 +7,9 @@ import { materialCommunityIcon } from '@theme/icons'; import { ICON_SIZE } from '@theme/paper'; import { FragmentType, gql, useFragment } from '@api/generated'; import { OperationLabel } from './OperationLabel'; -import { ETH_ICON_URI, TokenIcon } from '../token/TokenIcon'; import { ProposalValue } from './ProposalValue'; -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; import { createStyles, useStyles } from '@theme/styles'; -import { asUAddress } from 'lib'; import { OperationIcon } from '~/components/transaction/OperationIcon'; const Proposal = gql(/* GraphQL */ ` @@ -66,7 +64,6 @@ function TransactionItem_({ ...itemProps }: TransactionItemProps) { const { styles } = useStyles(stylesheet); - const router = useRouter(); const p = useFragment(Proposal, proposalFragment); const user = useFragment(User, userFragment); @@ -99,32 +96,33 @@ function TransactionItem_({ .exhaustive(); return ( - - isMulti ? ( - - ) : ( - - ) - } - leadingSize="medium" - headline={ - p.label ?? - (isMulti ? ( - `${p.operations.length} operations` - ) : ( - - )) - } - supporting={supporting} - trailing={({ Text }) => ( - - - - )} - onPress={() => router.push({ pathname: `/(drawer)/transaction/[id]`, params: { id: p.id } })} - {...itemProps} - /> + + + isMulti ? ( + + ) : ( + + ) + } + leadingSize="medium" + headline={ + p.label ?? + (isMulti ? ( + `${p.operations.length} operations` + ) : ( + + )) + } + supporting={supporting} + trailing={({ Text }) => ( + + + + )} + {...itemProps} + /> + ); } diff --git a/app/src/components/walletconnect/PairingItem.tsx b/app/src/components/walletconnect/PairingItem.tsx index 423b0c98b..4a9312008 100644 --- a/app/src/components/walletconnect/PairingItem.tsx +++ b/app/src/components/walletconnect/PairingItem.tsx @@ -7,7 +7,7 @@ import { useWalletConnect } from '~/util/walletconnect'; import { useTimestamp } from '~/components/format/Timestamp'; import { DateTime } from 'luxon'; import { MoreVerticalIcon } from '@theme/icons'; -import { useRouter } from 'expo-router'; +import { Link } from 'expo-router'; export interface PairingItemProps { pairing: PairingTypes.Struct; @@ -15,7 +15,6 @@ export interface PairingItemProps { export const PairingItem = ({ pairing }: PairingItemProps) => { const client = useWalletConnect(); - const router = useRouter(); const session: SessionTypes.Struct | undefined = client.session.getAll({ pairingTopic: pairing.topic, })?.[0]; @@ -26,20 +25,22 @@ export const PairingItem = ({ pairing }: PairingItemProps) => { const expires = status !== 'Expired' ? `Expires ${expiry}` : ''; return ( - 0 - ? (props) => - : peer.name || '?' - } - headline={peer.name || 'Unnamed DApp'} - supporting={`${status}\n${expires}`} - lines={3} - trailing={MoreVerticalIcon} - onPress={() => - router.push({ pathname: `/(sheet)/sessions/[topic]`, params: { topic: pairing.topic } }) - } - /> + + 0 + ? (props) => + : peer.name || '?' + } + headline={peer.name || 'Unnamed DApp'} + supporting={`${status}\n${expires}`} + lines={3} + trailing={MoreVerticalIcon} + /> + ); };