Skip to content

Commit f075fc2

Browse files
authored
feat: TAT-1697 update toast component to support animated loading spinner (#19715)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Changes: - Added optional `startAccessory` prop to Toast component - Added animated spinner for `inProgress` Perps toast variants <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: added optional startAccessory prop to Toast component CHANGELOG entry: added animated spinner for inProgress Perps toast variants ## **Related issues** Fixes: [TAT-1682: Reconsider icon for pending orders](https://consensyssoftware.atlassian.net/browse/TAT-1682) ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/948770df-f154-452d-ac03-b9acc8f1cdab ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent 7afb050 commit f075fc2

File tree

8 files changed

+97
-5
lines changed

8 files changed

+97
-5
lines changed

app/__mocks__/spinnerMock.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* eslint-disable import/no-commonjs */
2+
3+
module.exports = {
4+
Spinner: () => null,
5+
};

app/component-library/components/Toast/Toast.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,19 @@ const Toast = forwardRef((_, ref: React.ForwardedRef<ToastRef>) => {
202202
};
203203

204204
const renderToastContent = (options: ToastOptions) => {
205-
const { labelOptions, linkButtonOptions, closeButtonOptions } = options;
205+
const {
206+
labelOptions,
207+
linkButtonOptions,
208+
closeButtonOptions,
209+
startAccessory,
210+
} = options;
211+
212+
const isStartAccessoryValid =
213+
startAccessory != null && React.isValidElement(startAccessory);
206214

207215
return (
208216
<>
209-
{renderAvatar()}
217+
{isStartAccessoryValid ? startAccessory : renderAvatar()}
210218
<View
211219
style={styles.labelsContainer}
212220
testID={ToastSelectorsIDs.CONTAINER}

app/component-library/components/Toast/Toast.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ImageSourcePropType } from 'react-native';
55
import { AvatarAccountType } from '../Avatars/Avatar/variants/AvatarAccount';
66
import { ButtonProps } from '../Buttons/Button/Button.types';
77
import { IconName } from '../Icons/Icon';
8+
import { ReactElement } from 'react';
89

910
/**
1011
* Toast variants.
@@ -40,6 +41,7 @@ interface BaseToastVariants {
4041
labelOptions: ToastLabelOptions;
4142
linkButtonOptions?: ToastLinkButtonOptions;
4243
closeButtonOptions?: ButtonProps;
44+
startAccessory?: ReactElement;
4345
}
4446

4547
/**

app/components/UI/Perps/Views/PerpsWithdrawView/PerpsWithdrawView.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ jest.mock('@metamask/design-system-react-native', () => ({
133133
Between: 'Between',
134134
},
135135
ButtonBase: 'ButtonBase',
136+
IconSize: {
137+
Xl: 'Xl',
138+
},
139+
IconColor: {
140+
PrimaryDefault: 'PrimaryDefault',
141+
},
136142
}));
137143

138144
// Mock Text component

app/components/UI/Perps/components/PerpsPositionCard/PerpsPositionCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ const PerpsPositionCard: React.FC<PerpsPositionCardProps> = ({
325325
</TouchableOpacity>
326326
)}
327327
</View>
328-
<Text variant={TextVariant.BodySM} color={fundingColor}>
328+
<Text variant={TextVariant.BodyMD} color={fundingColor}>
329329
{fundingDisplay}
330330
</Text>
331331
</View>

app/components/UI/Perps/hooks/usePerpsToasts.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,22 @@ jest.mock('../../../../util/theme', () => ({
3434
primary: { default: '#0376C9' },
3535
background: { default: '#FFFFFF' },
3636
error: { default: '#D73A49' },
37+
accent03: { dark: '#000000', normal: '#FFFFFF' },
38+
accent04: { dark: '#000000', normal: '#FFFFFF' },
39+
accent01: { dark: '#000000', light: '#FFFFFF' },
3740
},
3841
}),
3942
}));
4043

44+
jest.mock('@metamask/design-system-react-native', () => ({
45+
IconSize: {
46+
Xl: 'xl',
47+
},
48+
IconColor: {
49+
PrimaryDefault: 'primary-default',
50+
},
51+
}));
52+
4153
jest.mock('../utils/perpsErrorHandler', () => ({
4254
handlePerpsError: ({
4355
error,
@@ -133,6 +145,9 @@ describe('usePerpsToasts', () => {
133145
iconName: IconName.Loading,
134146
hapticsType: NotificationFeedbackType.Warning,
135147
});
148+
expect(config.startAccessory).toBeDefined();
149+
expect(config.closeButtonOptions).toBeDefined();
150+
expect(config.closeButtonOptions?.label).toBe('Track');
136151
});
137152

138153
it('returns in progress configuration without processing time', () => {
@@ -173,6 +188,12 @@ describe('usePerpsToasts', () => {
173188
label: 'Withdrawal initiated',
174189
isBold: true,
175190
});
191+
expect(config.startAccessory).toBeDefined();
192+
expect(config).toMatchObject({
193+
variant: ToastVariants.Icon,
194+
iconName: IconName.Loading,
195+
hapticsType: NotificationFeedbackType.Warning,
196+
});
176197
});
177198

178199
it('returns withdrawal success configuration', () => {
@@ -232,6 +253,12 @@ describe('usePerpsToasts', () => {
232253
label: 'Long 0.5 ETH',
233254
isBold: false,
234255
});
256+
expect(config.startAccessory).toBeDefined();
257+
expect(config).toMatchObject({
258+
variant: ToastVariants.Icon,
259+
iconName: IconName.Loading,
260+
hapticsType: NotificationFeedbackType.Warning,
261+
});
235262
});
236263

237264
it('returns market order confirmed configuration', () => {
@@ -280,6 +307,12 @@ describe('usePerpsToasts', () => {
280307
label: 'Cancelling order',
281308
isBold: true,
282309
});
310+
expect(config.startAccessory).toBeDefined();
311+
expect(config).toMatchObject({
312+
variant: ToastVariants.Icon,
313+
iconName: IconName.Loading,
314+
hapticsType: NotificationFeedbackType.Warning,
315+
});
283316
});
284317

285318
it('returns limit order cancellation success configuration', () => {
@@ -332,6 +365,12 @@ describe('usePerpsToasts', () => {
332365
label: 'long 1.5 ETH',
333366
isBold: false,
334367
});
368+
expect(config.startAccessory).toBeDefined();
369+
expect(config).toMatchObject({
370+
variant: ToastVariants.Icon,
371+
iconName: IconName.Loading,
372+
hapticsType: NotificationFeedbackType.Warning,
373+
});
335374
});
336375

337376
it('returns close full position success configuration', () => {
@@ -365,6 +404,12 @@ describe('usePerpsToasts', () => {
365404
label: 'Partially closing position',
366405
isBold: true,
367406
});
407+
expect(marketConfig.startAccessory).toBeDefined();
408+
expect(marketConfig).toMatchObject({
409+
variant: ToastVariants.Icon,
410+
iconName: IconName.Loading,
411+
hapticsType: NotificationFeedbackType.Warning,
412+
});
368413
expect(limitConfig.labelOptions).toContainEqual({
369414
label: 'Partial close submitted',
370415
isBold: true,
@@ -459,6 +504,8 @@ describe('usePerpsToasts', () => {
459504
expect(inProgressToast.hapticsType).toBe(
460505
NotificationFeedbackType.Warning,
461506
);
507+
expect(inProgressToast.startAccessory).toBeDefined();
508+
expect(inProgressToast.closeButtonOptions).toBeDefined();
462509
expect(errorToast.hapticsType).toBe(NotificationFeedbackType.Error);
463510
});
464511
});

app/components/UI/Perps/hooks/usePerpsToasts.ts renamed to app/components/UI/Perps/hooks/usePerpsToasts.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useContext, useMemo } from 'react';
1+
import React, { useCallback, useContext, useMemo } from 'react';
22
import { ToastContext } from '../../../../component-library/components/Toast';
33
import {
44
ToastOptions,
@@ -16,6 +16,12 @@ import { handlePerpsError } from '../utils/perpsErrorHandler';
1616
import { OrderDirection } from '../types';
1717
import { formatDurationForDisplay } from '../utils/time';
1818
import { formatPerpsFiat } from '../utils/formatUtils';
19+
import { Spinner } from '@metamask/design-system-react-native/dist/components/temp-components/Spinner/index.cjs';
20+
import {
21+
IconSize as ReactNativeDsIconSize,
22+
IconColor as ReactNativeDsIconColor,
23+
} from '@metamask/design-system-react-native';
24+
import { View, StyleSheet } from 'react-native';
1925

2026
export type PerpsToastOptions = ToastOptions & {
2127
hapticsType: NotificationFeedbackType;
@@ -156,10 +162,18 @@ const getPerpsToastLabels = (primary: string, secondary?: string) => {
156162
};
157163

158164
const PERPS_TOASTS_DEFAULT_OPTIONS: Partial<PerpsToastOptions> = {
159-
// TODO: Determine if necessary or if it causes persistent toasts.
160165
hasNoTimeout: false,
161166
};
162167

168+
const toastStyles = StyleSheet.create({
169+
spinnerContainer: {
170+
paddingRight: 12,
171+
alignContent: 'center',
172+
alignItems: 'center',
173+
justifyContent: 'center',
174+
},
175+
});
176+
163177
const usePerpsToasts = (): {
164178
showToast: (config: PerpsToastOptions) => void;
165179
PerpsToastOptions: PerpsToastOptionsConfig;
@@ -186,6 +200,14 @@ const usePerpsToasts = (): {
186200
iconColor: theme.colors.accent04.dark,
187201
backgroundColor: theme.colors.accent04.normal,
188202
hapticsType: NotificationFeedbackType.Warning,
203+
startAccessory: (
204+
<View style={toastStyles.spinnerContainer}>
205+
<Spinner
206+
color={ReactNativeDsIconColor.PrimaryDefault}
207+
spinnerIconProps={{ size: ReactNativeDsIconSize.Xl }}
208+
/>
209+
</View>
210+
),
189211
},
190212
info: {
191213
...(PERPS_TOASTS_DEFAULT_OPTIONS as PerpsToastOptions),

jest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const config = {
6565
'<rootDir>/app/__mocks__/expo-apple-authentication.js',
6666
'^expo-haptics(/.*)?$': '<rootDir>/app/__mocks__/expo-haptics.js',
6767
'^expo-image$': '<rootDir>/app/__mocks__/expo-image.js',
68+
'^@metamask/design-system-react-native/dist/components/temp-components/Spinner/index.cjs$':
69+
'<rootDir>/app/__mocks__/spinnerMock.js',
6870
},
6971
// Disable jest cache
7072
cache: false,

0 commit comments

Comments
 (0)