Skip to content

Commit 1e0be21

Browse files
authored
feat: update prediction market cards to match latest design (#26795)
## **Description** Updates the prediction market cards on the homepage to match the latest Figma design specs ([TMCU-483](https://consensyssoftware.atlassian.net/browse/TMCU-483)): - **Outcome percentages as plain text**: Replaced `Button` components with `Text` to show outcome probabilities (e.g. "55%") as alternative-colored text without a background, matching the simplified card design. - **Removed date display**: Removed the `formatDate` helper and end-date rendering from cards. - **Reduced card width**: From 280px to 240px for a more compact carousel layout. - **Smaller outcome images**: From 32px to 24px thumbnails. - **Consistent card heights**: Added `flex-1` and `justifyContent: spaceBetween` so all cards in the carousel share the same height regardless of title length. - **ViewMoreCard flex alignment**: Changed from fixed `h-[180px]` to `flex-1` so it matches sibling card heights. - **Updated skeleton loader**: Aligned `PredictMarketCardSkeleton` dimensions (width, gaps, image/text placeholder sizes) with the new card layout. ## **Changelog** CHANGELOG entry: Updated prediction market card design with smaller cards, plain-text percentages, and consistent heights ## **Related issues** Refs: [TMCU-483](https://consensyssoftware.atlassian.net/browse/TMCU-483) ## **Manual testing steps** ```gherkin Feature: Prediction market cards on homepage Scenario: user views trending prediction markets carousel Given user is on the homepage with no prediction positions And the Predictions feature flag is enabled When user scrolls to the Predictions section Then prediction market cards display at 240px width And each card shows a title and up to 2 outcomes with percentage text (not buttons) And all cards in the carousel have the same height And a "More predictions" card appears at the end of the carousel Scenario: user sees skeleton loading state Given user is on the homepage And prediction markets are loading When user scrolls to the Predictions section Then skeleton placeholders match the new card dimensions ``` ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/6426ebbd-608e-4e23-ad1e-b905bf1a45c8 <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/8f66556d-bd91-48cc-b73f-70c1618eeb52 ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. [TMCU-483]: https://consensyssoftware.atlassian.net/browse/TMCU-483?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [TMCU-483]: https://consensyssoftware.atlassian.net/browse/TMCU-483?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Primarily UI/layout changes to the homepage predictions carousel (card sizing, alignment, and display), with no changes to data fetching or navigation logic. > > **Overview** > Updates the homepage Predictions carousel to match the latest card design by shrinking market cards (and snap offsets) from 280px to 240px and making the end `ViewMoreCard` use `flex-1` for consistent height. > > Simplifies `PredictMarketCard` UI by removing end-date rendering and replacing outcome price `Button`s with plain percentage `Text`, while adjusting spacing and thumbnail sizes; the skeleton loader is updated to mirror the new layout. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f65e8f7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent fc16f7a commit 1e0be21

3 files changed

Lines changed: 36 additions & 79 deletions

File tree

app/components/Views/Homepage/Sections/Predictions/PredictionsSection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { usePredictClaim } from '../../../../UI/Predict/hooks/usePredictClaim';
4545
const MAX_MARKETS_DISPLAYED = 5;
4646

4747
// Card dimensions for snap offsets
48-
const CARD_WIDTH = 280;
48+
const CARD_WIDTH = 240;
4949
const GAP = 12;
5050
const PADDING = 16; // px-4
5151

@@ -287,7 +287,7 @@ const PredictionsSection = forwardRef<SectionRefreshHandle>((_, ref) => {
287287
))}
288288
<ViewMoreCard
289289
onPress={handleViewAllPredictions}
290-
twClassName="w-[180px] h-[180px]"
290+
twClassName="w-[180px] flex-1"
291291
textVariant={TextVariant.BodyLg}
292292
/>
293293
</>

app/components/Views/Homepage/Sections/Predictions/components/PredictMarketCard.tsx

Lines changed: 26 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@ import {
66
Text,
77
BoxFlexDirection,
88
BoxAlignItems,
9+
BoxJustifyContent,
910
TextVariant,
1011
TextColor,
1112
FontWeight,
1213
} from '@metamask/design-system-react-native';
1314
import { useNavigation, NavigationProp } from '@react-navigation/native';
1415
import Routes from '../../../../../../constants/navigation/Routes';
15-
import Button, {
16-
ButtonVariants,
17-
ButtonSize,
18-
} from '../../../../../../component-library/components/Buttons/Button';
1916
import type {
2017
PredictMarket,
2118
PredictOutcome,
@@ -29,34 +26,19 @@ interface PredictMarketCardProps {
2926
const MAX_OUTCOMES_DISPLAYED = 2;
3027

3128
/**
32-
* Format price as cents (e.g., 0.55 -> "55¢")
29+
* Format price as percentage (e.g., 0.55 -> "55%")
3330
*/
3431
const formatPrice = (price: number): string => {
35-
const cents = Math.round(price * 100);
36-
return `${cents}¢`;
32+
const pct = Math.round(price * 100);
33+
return `${pct}%`;
3734
};
3835

3936
/**
40-
* Format end date to short format (e.g., "Jan 8")
41-
*/
42-
const formatDate = (dateString?: string): string | null => {
43-
if (!dateString) return null;
44-
try {
45-
const date = new Date(dateString);
46-
if (isNaN(date.getTime())) return null;
47-
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
48-
} catch {
49-
return null;
50-
}
51-
};
52-
53-
/**
54-
* OutcomeRow - Single outcome row with image, name, and price button
37+
* OutcomeRow - Single outcome row with image, name, and percentage
5538
*/
5639
const OutcomeRow: React.FC<{
5740
outcome: PredictOutcome;
58-
onPress: () => void;
59-
}> = ({ outcome, onPress }) => {
41+
}> = ({ outcome }) => {
6042
const tw = useTailwind();
6143
const yesToken = outcome.tokens?.[0];
6244
const price = yesToken?.price ?? 0;
@@ -68,11 +50,11 @@ const OutcomeRow: React.FC<{
6850
gap={3}
6951
>
7052
{/* Outcome image */}
71-
<Box twClassName="w-8 h-8 rounded-lg overflow-hidden bg-background-alternative">
53+
<Box twClassName="w-6 h-6 rounded-lg overflow-hidden bg-background-alternative">
7254
{outcome.image ? (
7355
<Image
7456
source={{ uri: outcome.image }}
75-
style={tw.style('w-8 h-8')}
57+
style={tw.style('w-6 h-6')}
7658
resizeMode="cover"
7759
/>
7860
) : null}
@@ -82,28 +64,24 @@ const OutcomeRow: React.FC<{
8264
<Box twClassName="flex-1">
8365
<Text
8466
variant={TextVariant.BodyMd}
85-
fontWeight={FontWeight.Medium}
8667
color={TextColor.TextDefault}
8768
numberOfLines={1}
8869
>
8970
{outcome.groupItemTitle || outcome.title}
9071
</Text>
9172
</Box>
9273

93-
{/* Price button */}
94-
<Button
95-
variant={ButtonVariants.Secondary}
96-
size={ButtonSize.Sm}
97-
label={formatPrice(price)}
98-
onPress={onPress}
99-
/>
74+
{/* Percentage */}
75+
<Text variant={TextVariant.BodyMd} color={TextColor.TextAlternative}>
76+
{formatPrice(price)}
77+
</Text>
10078
</Box>
10179
);
10280
};
10381

10482
/**
10583
* Compact prediction market card for homepage carousel.
106-
* Shows title, date, and top 2 outcomes with prices.
84+
* Shows title and top 2 outcomes with prices.
10785
*/
10886
const PredictMarketCard: React.FC<PredictMarketCardProps> = ({ market }) => {
10987
const navigation =
@@ -116,11 +94,6 @@ const PredictMarketCard: React.FC<PredictMarketCardProps> = ({ market }) => {
11694
});
11795
}, [navigation, market.id]);
11896

119-
const formattedDate = useMemo(
120-
() => formatDate(market.endDate),
121-
[market.endDate],
122-
);
123-
12497
// Get top outcomes to display
12598
const displayOutcomes = useMemo(() => {
12699
if (!market.outcomes?.length) {
@@ -150,38 +123,25 @@ const PredictMarketCard: React.FC<PredictMarketCardProps> = ({ market }) => {
150123
return (
151124
<TouchableOpacity onPress={handlePress}>
152125
<Box
153-
twClassName="w-[280px] rounded-2xl bg-background-muted"
126+
twClassName="w-[240px] rounded-2xl bg-background-muted flex-1"
154127
padding={4}
155-
gap={3}
128+
gap={6}
129+
justifyContent={BoxJustifyContent.Between}
156130
>
157-
{/* Header: Title + Date */}
158-
<Box gap={1}>
159-
<Text
160-
variant={TextVariant.BodyMd}
161-
fontWeight={FontWeight.Medium}
162-
color={TextColor.TextDefault}
163-
numberOfLines={2}
164-
>
165-
{market.title}
166-
</Text>
167-
{formattedDate && (
168-
<Text
169-
variant={TextVariant.BodyMd}
170-
color={TextColor.TextAlternative}
171-
>
172-
{formattedDate}
173-
</Text>
174-
)}
175-
</Box>
131+
{/* Header: Title */}
132+
<Text
133+
variant={TextVariant.BodyMd}
134+
fontWeight={FontWeight.Medium}
135+
color={TextColor.TextDefault}
136+
numberOfLines={2}
137+
>
138+
{market.title}
139+
</Text>
176140

177141
{/* Outcomes */}
178142
<Box gap={2}>
179143
{displayOutcomes.map((outcome) => (
180-
<OutcomeRow
181-
key={outcome.id}
182-
outcome={outcome}
183-
onPress={handlePress}
184-
/>
144+
<OutcomeRow key={outcome.id} outcome={outcome} />
185145
))}
186146
</Box>
187147
</Box>

app/components/Views/Homepage/Sections/Predictions/components/PredictMarketCardSkeleton.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,33 @@ const PredictMarketCardSkeleton: React.FC = () => {
1313
const { colors } = useTheme();
1414

1515
return (
16-
<View style={tw.style('w-[280px] rounded-2xl bg-background-muted p-4')}>
16+
<View style={tw.style('w-[240px] rounded-2xl bg-background-muted p-4')}>
1717
<SkeletonPlaceholder
1818
backgroundColor={colors.background.section}
1919
highlightColor={colors.background.subsection}
2020
>
21-
<View style={tw.style('gap-3')}>
22-
{/* Header skeleton */}
23-
<View style={tw.style('gap-1')}>
24-
<View style={tw.style('w-[80%] h-5 rounded')} />
25-
<View style={tw.style('w-[40%] h-4 rounded')} />
26-
</View>
21+
<View style={tw.style('gap-6')}>
22+
{/* Title skeleton */}
23+
<View style={tw.style('w-[80%] h-5 rounded')} />
2724

2825
{/* Outcome rows skeleton */}
2926
<View style={tw.style('gap-2')}>
3027
{/* Outcome 1 */}
3128
<View style={tw.style('flex-row items-center gap-3')}>
32-
<View style={tw.style('w-8 h-8 rounded-lg')} />
29+
<View style={tw.style('w-6 h-6 rounded-lg')} />
3330
<View style={tw.style('flex-1')}>
3431
<View style={tw.style('w-[60%] h-4 rounded')} />
3532
</View>
36-
<View style={tw.style('w-12 h-8 rounded-lg')} />
33+
<View style={tw.style('w-8 h-4 rounded')} />
3734
</View>
3835

3936
{/* Outcome 2 */}
4037
<View style={tw.style('flex-row items-center gap-3')}>
41-
<View style={tw.style('w-8 h-8 rounded-lg')} />
38+
<View style={tw.style('w-6 h-6 rounded-lg')} />
4239
<View style={tw.style('flex-1')}>
4340
<View style={tw.style('w-[50%] h-4 rounded')} />
4441
</View>
45-
<View style={tw.style('w-12 h-8 rounded-lg')} />
42+
<View style={tw.style('w-8 h-4 rounded')} />
4643
</View>
4744
</View>
4845
</View>

0 commit comments

Comments
 (0)