Skip to content

Commit 9db0db8

Browse files
Merge pull request #2322 from Solid-Money/claude/desktop-mobile-first-ui-1xgcjp
fix(desktop): use ResponsiveModal for the sheets, not a restyled Dialog
2 parents b9c5ae7 + e442550 commit 9db0db8

6 files changed

Lines changed: 155 additions & 66 deletions

File tree

components/Rewards/NewRewards/CashbackDetailsContent.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ import Svg, { Defs, FeGaussianBlur, Filter, Path } from 'react-native-svg';
1313

1414
import { Button } from '@/components/ui/button';
1515
import { Text } from '@/components/ui/text';
16-
import { formatNumber } from '@/lib/utils';
16+
import { cn, formatNumber } from '@/lib/utils';
1717

1818
import type { CashbackDetailsData } from './CashbackDetailsSheet.types';
1919

2020
interface CashbackDetailsContentProps extends CashbackDetailsData {
2121
onGetMoreCashback: () => void;
2222
animationSession: number;
23+
/**
24+
* Bottom-sheet presentation: adds the top padding that clears the sheet's drag
25+
* handle. False inside a modal, which brings its own padding.
26+
*/
27+
isSheet?: boolean;
2328
}
2429

2530
const formatWholeDollars = (value: number) => `$${formatNumber(value || 0, 0, 0)}`;
@@ -136,8 +141,9 @@ const CashbackDetailsContent = ({
136141
allTimeCashback,
137142
onGetMoreCashback,
138143
animationSession,
144+
isSheet = true,
139145
}: CashbackDetailsContentProps) => (
140-
<View className="items-center px-[34px] pt-[46px]">
146+
<View className={cn('items-center px-[34px]', isSheet && 'pt-[46px]')}>
141147
<CashbackDiamondIcon key={animationSession} />
142148

143149
<Text

components/Rewards/NewRewards/CashbackDetailsSheet.tsx

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { useState } from 'react';
22
import { ScrollView, View } from 'react-native';
33

4+
import ResponsiveModal, { ModalState } from '@/components/ResponsiveModal';
45
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
56
import { useDimension } from '@/hooks/useDimension';
6-
import { cn } from '@/lib/utils';
77

88
import CashbackDetailsContent from './CashbackDetailsContent';
99

1010
import type { CashbackDetailsSheetProps } from './CashbackDetailsSheet.types';
1111

12-
// Bottom sheet on phones, the standard centred modal from `md` up — the sheet
13-
// styling used to apply at every width, so on desktop it stretched across the
14-
// whole viewport with square top corners.
12+
const MODAL_STATE: ModalState = { name: 'cashback-details', number: 1 };
13+
const CLOSE_STATE: ModalState = { name: 'close', number: 0 };
14+
15+
/**
16+
* Cashback details: a bottom sheet on phones, the standard `ResponsiveModal` from
17+
* `md` up — which brings the padding, close button and rounded corners a popup needs,
18+
* and leaves the drag handle to the sheet it belongs to.
19+
*/
1520
const CashbackDetailsSheet = ({
1621
trigger,
1722
onGetMoreCashback,
@@ -32,29 +37,45 @@ const CashbackDetailsSheet = ({
3237
setOpen(nextOpen);
3338
};
3439

40+
const content = (
41+
<CashbackDetailsContent
42+
{...cashbackData}
43+
animationSession={animationSession}
44+
isSheet={!isScreenMedium}
45+
onGetMoreCashback={handleGetMoreCashback}
46+
/>
47+
);
48+
49+
if (isScreenMedium) {
50+
return (
51+
<View className="flex-1">
52+
<ResponsiveModal
53+
currentModal={MODAL_STATE}
54+
previousModal={CLOSE_STATE}
55+
isOpen={open}
56+
onOpenChange={handleOpenChange}
57+
trigger={trigger}
58+
contentKey="cashback-details"
59+
shouldAnimate={false}
60+
hideHeader
61+
contentClassName="bg-[#1C1C1C]"
62+
>
63+
{content}
64+
</ResponsiveModal>
65+
</View>
66+
);
67+
}
68+
3569
return (
3670
<View className="flex-1">
3771
<Dialog open={open} onOpenChange={handleOpenChange}>
3872
<DialogTrigger asChild>{trigger}</DialogTrigger>
3973
<DialogContent
40-
showCloseButton={isScreenMedium}
41-
className={cn(
42-
'overflow-hidden bg-[#1C1C1C] p-0',
43-
isScreenMedium
44-
? 'max-h-[86vh] md:max-w-lg'
45-
: 'fixed bottom-0 left-0 right-0 h-[76vh] max-w-none rounded-b-none rounded-t-[40px]',
46-
)}
74+
showCloseButton={false}
75+
className="fixed bottom-0 left-0 right-0 h-[76vh] max-w-none overflow-hidden rounded-b-none rounded-t-[40px] bg-[#1C1C1C] p-0"
4776
>
48-
{!isScreenMedium && (
49-
<View className="absolute left-1/2 top-4 z-10 h-[5px] w-[73px] -translate-x-1/2 rounded-full bg-white/20" />
50-
)}
51-
<ScrollView showsVerticalScrollIndicator={false}>
52-
<CashbackDetailsContent
53-
{...cashbackData}
54-
animationSession={animationSession}
55-
onGetMoreCashback={handleGetMoreCashback}
56-
/>
57-
</ScrollView>
77+
<View className="absolute left-1/2 top-4 z-10 h-[5px] w-[73px] -translate-x-1/2 rounded-full bg-white/20" />
78+
<ScrollView showsVerticalScrollIndicator={false}>{content}</ScrollView>
5879
</DialogContent>
5980
</Dialog>
6081
</View>

components/Rewards/NewRewards/TierPointsSheet.tsx

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import { useEffect, useState } from 'react';
22
import { ScrollView, View } from 'react-native';
33

4+
import ResponsiveModal, { ModalState } from '@/components/ResponsiveModal';
45
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
56
import { useDimension } from '@/hooks/useDimension';
6-
import { cn } from '@/lib/utils';
77

88
import TierPointsSheetContent from './TierPointsSheetContent';
99

1010
import type { TierPointsSheetProps } from './TierPointsSheet.types';
1111

12-
// Bottom sheet on phones, the standard centred modal from `md` up.
12+
const MODAL_STATE: ModalState = { name: 'tier-points', number: 1 };
13+
const CLOSE_STATE: ModalState = { name: 'close', number: 0 };
14+
15+
/**
16+
* How points work: a bottom sheet on phones, the standard `ResponsiveModal` from
17+
* `md` up.
18+
*/
1319
const TierPointsSheet = ({ trigger, open: controlledOpen, onOpenChange }: TierPointsSheetProps) => {
1420
const { isScreenMedium } = useDimension();
1521
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
@@ -33,28 +39,44 @@ const TierPointsSheet = ({ trigger, open: controlledOpen, onOpenChange }: TierPo
3339
onOpenChange?.(nextOpen);
3440
};
3541

42+
const content = (
43+
<TierPointsSheetContent
44+
animationSession={animationSession}
45+
isSheet={!isScreenMedium}
46+
onClose={() => handleOpenChange(false)}
47+
/>
48+
);
49+
50+
if (isScreenMedium) {
51+
return (
52+
<View>
53+
<ResponsiveModal
54+
currentModal={MODAL_STATE}
55+
previousModal={CLOSE_STATE}
56+
isOpen={open}
57+
onOpenChange={handleOpenChange}
58+
trigger={trigger ?? null}
59+
contentKey="tier-points"
60+
shouldAnimate={false}
61+
hideHeader
62+
contentClassName="bg-[#1C1C1C]"
63+
>
64+
{content}
65+
</ResponsiveModal>
66+
</View>
67+
);
68+
}
69+
3670
return (
3771
<View>
3872
<Dialog open={open} onOpenChange={handleOpenChange}>
3973
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
4074
<DialogContent
41-
showCloseButton={isScreenMedium}
42-
className={cn(
43-
'overflow-hidden bg-[#1C1C1C] p-0',
44-
isScreenMedium
45-
? 'max-h-[86vh] md:max-w-lg'
46-
: 'fixed bottom-0 left-1/2 h-[min(792px,calc(100vh-16px))] w-full max-w-[419px] -translate-x-1/2 rounded-b-none rounded-t-[40px]',
47-
)}
75+
showCloseButton={false}
76+
className="fixed bottom-0 left-1/2 h-[min(792px,calc(100vh-16px))] w-full max-w-[419px] -translate-x-1/2 overflow-hidden rounded-b-none rounded-t-[40px] bg-[#1C1C1C] p-0"
4877
>
49-
{!isScreenMedium && (
50-
<View className="absolute left-1/2 top-4 z-10 h-[5px] w-[73px] -translate-x-1/2 rounded-full bg-white/20" />
51-
)}
52-
<ScrollView showsVerticalScrollIndicator={false}>
53-
<TierPointsSheetContent
54-
animationSession={animationSession}
55-
onClose={() => handleOpenChange(false)}
56-
/>
57-
</ScrollView>
78+
<View className="absolute left-1/2 top-4 z-10 h-[5px] w-[73px] -translate-x-1/2 rounded-full bg-white/20" />
79+
<ScrollView showsVerticalScrollIndicator={false}>{content}</ScrollView>
5880
</DialogContent>
5981
</Dialog>
6082
</View>

components/Rewards/NewRewards/TierPointsSheetContent.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Image } from 'expo-image';
1212

1313
import { Button } from '@/components/ui/button';
1414
import { Text } from '@/components/ui/text';
15+
import { cn } from '@/lib/utils';
1516

1617
const POINTS_STAR = require('@/assets/images/rewards-tiers/points-drawer-star.png');
1718
const POINTS_SAVE = require('@/assets/images/rewards-tiers/points-save.png');
@@ -22,6 +23,11 @@ const POINTS_SWAP = require('@/assets/images/rewards-tiers/points-swap.png');
2223
interface TierPointsSheetContentProps {
2324
animationSession: number;
2425
onClose: () => void;
26+
/**
27+
* Bottom-sheet presentation: adds the top padding that clears the sheet's drag
28+
* handle. False inside a modal, which brings its own padding.
29+
*/
30+
isSheet?: boolean;
2531
}
2632

2733
interface PointsMethod {
@@ -147,8 +153,12 @@ const PointsCell = ({ method, bottom }: { method: PointsMethod; bottom?: boolean
147153
</View>
148154
);
149155

150-
const TierPointsSheetContent = ({ animationSession, onClose }: TierPointsSheetContentProps) => (
151-
<View className="items-center px-[34px] pt-[46px]">
156+
const TierPointsSheetContent = ({
157+
animationSession,
158+
onClose,
159+
isSheet = true,
160+
}: TierPointsSheetContentProps) => (
161+
<View className={cn('items-center px-[34px]', isSheet && 'pt-[46px]')}>
152162
<AnimatedTierStar key={animationSession} />
153163

154164
<Text

components/SupportDrawer/SupportDrawerContent.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,15 @@ const SupportRow = ({ icon, label, onPress }: SupportRowProps) => (
107107

108108
const Divider = () => <View className="h-px bg-white/10" />;
109109

110-
const SupportDrawerContent = () => {
110+
interface SupportDrawerContentProps {
111+
/**
112+
* Bottom-sheet presentation: draws the drag handle and the top padding that
113+
* clears it. False inside a modal, which supplies its own chrome and padding.
114+
*/
115+
isSheet?: boolean;
116+
}
117+
118+
const SupportDrawerContent = ({ isSheet = true }: SupportDrawerContentProps) => {
111119
const intercom = useIntercom();
112120
const chatMessage = useSupportDrawerStore(state => state.chatMessage);
113121

@@ -136,12 +144,12 @@ const SupportDrawerContent = () => {
136144
);
137145

138146
return (
139-
<View style={styles.container}>
140-
<View style={styles.handle} />
147+
<View style={isSheet ? styles.container : undefined}>
148+
{isSheet && <View style={styles.handle} />}
141149
<Text className="text-center text-[30px] font-semibold leading-[36px] text-white">
142150
Help & Support
143151
</Text>
144-
<View style={styles.card}>
152+
<View style={[styles.card, isSheet ? undefined : styles.cardInModal]}>
145153
<SupportRow icon={<ChatIcon />} label="Chat with us" onPress={handleChatPress} />
146154
<Divider />
147155
<SupportRow icon={<FaqIcon />} label="FAQ" onPress={() => openLink(FAQ_URL)} />
@@ -185,6 +193,8 @@ const styles = StyleSheet.create({
185193
borderRadius: 20,
186194
backgroundColor: '#2b2b2b',
187195
},
196+
// The modal already supplies its own horizontal padding.
197+
cardInModal: { marginHorizontal: 0, marginTop: 24 },
188198
});
189199

190200
export default SupportDrawerContent;

components/SupportDrawer/SupportDrawerProvider.tsx

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,62 @@
11
import { useWindowDimensions, View } from 'react-native';
22

3+
import ResponsiveModal, { ModalState } from '@/components/ResponsiveModal';
34
import { Dialog, DialogContent } from '@/components/ui/dialog';
45
import { useDimension } from '@/hooks/useDimension';
5-
import { cn } from '@/lib/utils';
66
import { closeSupportDrawer, useSupportDrawerStore } from '@/store/useSupportDrawerStore';
77

88
import SupportDrawerContent from './SupportDrawerContent';
99

10+
const MODAL_STATE: ModalState = { name: 'support', number: 1 };
11+
const CLOSE_STATE: ModalState = { name: 'close', number: 0 };
12+
1013
/**
11-
* Bottom sheet on phones, the standard centred modal from `md` up. The sheet
12-
* styling — and the translate that pinned it to the bottom of the viewport —
13-
* used to apply at every width.
14+
* Help & Support: a bottom sheet on phones, the standard `ResponsiveModal` from `md`
15+
* up. The sheet styling — the drag handle, and the translate that pinned the card to
16+
* the bottom of the viewport — used to apply at every width.
1417
*/
1518
const SupportDrawerProvider = () => {
1619
const isOpen = useSupportDrawerStore(state => state.isOpen);
1720
const { isScreenMedium } = useDimension();
1821
const { height, width } = useWindowDimensions();
1922
const sheetHeight = Math.min(522, height - 8);
2023

24+
const handleOpenChange = (open: boolean) => {
25+
if (!open) closeSupportDrawer();
26+
};
27+
28+
if (isScreenMedium) {
29+
return (
30+
<View>
31+
<ResponsiveModal
32+
currentModal={MODAL_STATE}
33+
previousModal={CLOSE_STATE}
34+
isOpen={isOpen}
35+
onOpenChange={handleOpenChange}
36+
trigger={null}
37+
contentKey="support"
38+
shouldAnimate={false}
39+
hideHeader
40+
contentClassName="bg-[#1c1c1c]"
41+
>
42+
<SupportDrawerContent isSheet={false} />
43+
</ResponsiveModal>
44+
</View>
45+
);
46+
}
47+
2148
return (
2249
<View>
23-
<Dialog open={isOpen} onOpenChange={open => !open && closeSupportDrawer()}>
50+
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
2451
<DialogContent
25-
showCloseButton={isScreenMedium}
52+
showCloseButton={false}
2653
overlayClassName="web:backdrop-blur-none"
27-
className={cn(
28-
'gap-0 overflow-hidden bg-[#1c1c1c] p-0',
29-
isScreenMedium ? 'md:max-w-lg' : 'max-w-none rounded-b-none rounded-t-[40px]',
30-
)}
31-
style={
32-
isScreenMedium
33-
? undefined
34-
: {
35-
width: Math.min(419, width),
36-
height: sheetHeight,
37-
transform: [{ translateY: (height - sheetHeight) / 2 }],
38-
}
39-
}
54+
className="max-w-none gap-0 overflow-hidden rounded-b-none rounded-t-[40px] bg-[#1c1c1c] p-0"
55+
style={{
56+
width: Math.min(419, width),
57+
height: sheetHeight,
58+
transform: [{ translateY: (height - sheetHeight) / 2 }],
59+
}}
4060
>
4161
<SupportDrawerContent />
4262
</DialogContent>

0 commit comments

Comments
 (0)