Bluesky app fork with some witchin' additions 💫

Fix chat request buttons not moving with swipe gesture (#9155)

* portal in buttons so they move with swipe

* remove outline style buttons

authored by samuel.fm and committed by

GitHub 54b8eacb b7b47d30

+228 -214
+191 -173
src/screens/Messages/components/ChatListItem.tsx
··· 42 42 import {Link} from '#/components/Link' 43 43 import {useMenuControl} from '#/components/Menu' 44 44 import {PostAlerts} from '#/components/moderation/PostAlerts' 45 + import {createPortalGroup} from '#/components/Portal' 45 46 import {Text} from '#/components/Typography' 46 47 import {useSimpleVerificationState} from '#/components/verification' 47 48 import {VerificationCheck} from '#/components/verification/VerificationCheck' 48 49 import type * as bsky from '#/types/bsky' 50 + 51 + export const ChatListItemPortal = createPortalGroup() 49 52 50 53 export let ChatListItem = ({ 51 54 convo, ··· 331 334 const hasUnread = convo.unreadCount > 0 && !isDeletedAccount 332 335 333 336 return ( 334 - <GestureActionView actions={actions}> 335 - <View 336 - onMouseEnter={onMouseEnter} 337 - onMouseLeave={onMouseLeave} 338 - // @ts-expect-error web only 339 - onFocus={onFocus} 340 - onBlur={onMouseLeave} 341 - style={[a.relative, t.atoms.bg]}> 337 + <ChatListItemPortal.Provider> 338 + <GestureActionView actions={actions}> 342 339 <View 343 - style={[ 344 - a.z_10, 345 - a.absolute, 346 - {top: tokens.space.md, left: tokens.space.lg}, 347 - ]}> 348 - <PreviewableUserAvatar 349 - profile={profile} 350 - size={52} 351 - moderation={moderation.ui('avatar')} 352 - /> 353 - </View> 340 + onMouseEnter={onMouseEnter} 341 + onMouseLeave={onMouseLeave} 342 + // @ts-expect-error web only 343 + onFocus={onFocus} 344 + onBlur={onMouseLeave} 345 + style={[a.relative, t.atoms.bg]}> 346 + <View 347 + style={[ 348 + a.z_10, 349 + a.absolute, 350 + {top: tokens.space.md, left: tokens.space.lg}, 351 + ]}> 352 + <PreviewableUserAvatar 353 + profile={profile} 354 + size={52} 355 + moderation={moderation.ui('avatar')} 356 + /> 357 + </View> 358 + 359 + <Link 360 + to={`/messages/${convo.id}`} 361 + label={displayName} 362 + accessibilityHint={ 363 + !isDeletedAccount 364 + ? _(msg`Go to conversation with ${profile.handle}`) 365 + : _( 366 + msg`This conversation is with a deleted or a deactivated account. Press for options`, 367 + ) 368 + } 369 + accessibilityActions={ 370 + isNative 371 + ? [ 372 + { 373 + name: 'magicTap', 374 + label: _(msg`Open conversation options`), 375 + }, 376 + { 377 + name: 'longpress', 378 + label: _(msg`Open conversation options`), 379 + }, 380 + ] 381 + : undefined 382 + } 383 + onPress={onPress} 384 + onLongPress={isNative ? onLongPress : undefined} 385 + onAccessibilityAction={onLongPress}> 386 + {({hovered, pressed, focused}) => ( 387 + <View 388 + style={[ 389 + a.flex_row, 390 + isDeletedAccount ? a.align_center : a.align_start, 391 + a.flex_1, 392 + a.px_lg, 393 + a.py_md, 394 + a.gap_md, 395 + (hovered || pressed || focused) && t.atoms.bg_contrast_25, 396 + ]}> 397 + {/* Avatar goes here */} 398 + <View style={{width: 52, height: 52}} /> 354 399 355 - <Link 356 - to={`/messages/${convo.id}`} 357 - label={displayName} 358 - accessibilityHint={ 359 - !isDeletedAccount 360 - ? _(msg`Go to conversation with ${profile.handle}`) 361 - : _( 362 - msg`This conversation is with a deleted or a deactivated account. Press for options`, 363 - ) 364 - } 365 - accessibilityActions={ 366 - isNative 367 - ? [ 368 - {name: 'magicTap', label: _(msg`Open conversation options`)}, 369 - {name: 'longpress', label: _(msg`Open conversation options`)}, 370 - ] 371 - : undefined 372 - } 373 - onPress={onPress} 374 - onLongPress={isNative ? onLongPress : undefined} 375 - onAccessibilityAction={onLongPress}> 376 - {({hovered, pressed, focused}) => ( 377 - <View 378 - style={[ 379 - a.flex_row, 380 - isDeletedAccount ? a.align_center : a.align_start, 381 - a.flex_1, 382 - a.px_lg, 383 - a.py_md, 384 - a.gap_md, 385 - (hovered || pressed || focused) && t.atoms.bg_contrast_25, 386 - ]}> 387 - {/* Avatar goes here */} 388 - <View style={{width: 52, height: 52}} /> 400 + <View 401 + style={[a.flex_1, a.justify_center, web({paddingRight: 45})]}> 402 + <View style={[a.w_full, a.flex_row, a.align_end, a.pb_2xs]}> 403 + <View style={[a.flex_shrink]}> 404 + <Text 405 + emoji 406 + numberOfLines={1} 407 + style={[ 408 + a.text_md, 409 + t.atoms.text, 410 + a.font_semi_bold, 411 + {lineHeight: 21}, 412 + isDimStyle && t.atoms.text_contrast_medium, 413 + ]}> 414 + {displayName} 415 + </Text> 416 + </View> 417 + {verification.showBadge && ( 418 + <View style={[a.pl_xs, a.self_center]}> 419 + <VerificationCheck 420 + width={14} 421 + verifier={verification.role === 'verifier'} 422 + /> 423 + </View> 424 + )} 425 + {lastMessageSentAt && ( 426 + <View style={[a.pl_xs]}> 427 + <TimeElapsed timestamp={lastMessageSentAt}> 428 + {({timeElapsed}) => ( 429 + <Text 430 + style={[ 431 + a.text_sm, 432 + {lineHeight: 21}, 433 + t.atoms.text_contrast_medium, 434 + web({whiteSpace: 'preserve nowrap'}), 435 + ]}> 436 + &middot; {timeElapsed} 437 + </Text> 438 + )} 439 + </TimeElapsed> 440 + </View> 441 + )} 442 + {(convo.muted || moderation.blocked) && ( 443 + <Text 444 + style={[ 445 + a.text_sm, 446 + {lineHeight: 21}, 447 + t.atoms.text_contrast_medium, 448 + web({whiteSpace: 'preserve nowrap'}), 449 + ]}> 450 + {' '} 451 + &middot;{' '} 452 + <BellStroke 453 + size="xs" 454 + style={[t.atoms.text_contrast_medium]} 455 + /> 456 + </Text> 457 + )} 458 + </View> 389 459 390 - <View 391 - style={[a.flex_1, a.justify_center, web({paddingRight: 45})]}> 392 - <View style={[a.w_full, a.flex_row, a.align_end, a.pb_2xs]}> 393 - <View style={[a.flex_shrink]}> 460 + {!isDeletedAccount && ( 394 461 <Text 395 - emoji 396 462 numberOfLines={1} 397 463 style={[ 398 - a.text_md, 399 - t.atoms.text, 400 - a.font_semi_bold, 401 - {lineHeight: 21}, 402 - isDimStyle && t.atoms.text_contrast_medium, 403 - ]}> 404 - {displayName} 405 - </Text> 406 - </View> 407 - {verification.showBadge && ( 408 - <View style={[a.pl_xs, a.self_center]}> 409 - <VerificationCheck 410 - width={14} 411 - verifier={verification.role === 'verifier'} 412 - /> 413 - </View> 414 - )} 415 - {lastMessageSentAt && ( 416 - <View style={[a.pl_xs]}> 417 - <TimeElapsed timestamp={lastMessageSentAt}> 418 - {({timeElapsed}) => ( 419 - <Text 420 - style={[ 421 - a.text_sm, 422 - {lineHeight: 21}, 423 - t.atoms.text_contrast_medium, 424 - web({whiteSpace: 'preserve nowrap'}), 425 - ]}> 426 - &middot; {timeElapsed} 427 - </Text> 428 - )} 429 - </TimeElapsed> 430 - </View> 431 - )} 432 - {(convo.muted || moderation.blocked) && ( 433 - <Text 434 - style={[ 435 464 a.text_sm, 436 - {lineHeight: 21}, 437 465 t.atoms.text_contrast_medium, 438 - web({whiteSpace: 'preserve nowrap'}), 466 + a.pb_xs, 439 467 ]}> 440 - {' '} 441 - &middot;{' '} 442 - <BellStroke 443 - size="xs" 444 - style={[t.atoms.text_contrast_medium]} 445 - /> 468 + @{profile.handle} 446 469 </Text> 447 470 )} 448 - </View> 449 471 450 - {!isDeletedAccount && ( 451 472 <Text 452 - numberOfLines={1} 453 - style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}> 454 - @{profile.handle} 473 + emoji 474 + numberOfLines={2} 475 + style={[ 476 + a.text_sm, 477 + a.leading_snug, 478 + hasUnread ? a.font_semi_bold : t.atoms.text_contrast_high, 479 + isDimStyle && t.atoms.text_contrast_medium, 480 + ]}> 481 + {lastMessage} 455 482 </Text> 456 - )} 457 483 458 - <Text 459 - emoji 460 - numberOfLines={2} 461 - style={[ 462 - a.text_sm, 463 - a.leading_snug, 464 - hasUnread ? a.font_semi_bold : t.atoms.text_contrast_high, 465 - isDimStyle && t.atoms.text_contrast_medium, 466 - ]}> 467 - {lastMessage} 468 - </Text> 484 + <PostAlerts 485 + modui={moderation.ui('contentList')} 486 + size="lg" 487 + style={[a.pt_xs]} 488 + /> 469 489 470 - <PostAlerts 471 - modui={moderation.ui('contentList')} 472 - size="lg" 473 - style={[a.pt_xs]} 474 - /> 490 + {children} 491 + </View> 475 492 476 - {children} 493 + {hasUnread && ( 494 + <View 495 + style={[ 496 + a.absolute, 497 + a.rounded_full, 498 + { 499 + backgroundColor: isDimStyle 500 + ? t.palette.contrast_200 501 + : t.palette.primary_500, 502 + height: 7, 503 + width: 7, 504 + top: 15, 505 + right: 12, 506 + }, 507 + ]} 508 + /> 509 + )} 477 510 </View> 511 + )} 512 + </Link> 478 513 479 - {hasUnread && ( 480 - <View 481 - style={[ 482 - a.absolute, 483 - a.rounded_full, 484 - { 485 - backgroundColor: isDimStyle 486 - ? t.palette.contrast_200 487 - : t.palette.primary_500, 488 - height: 7, 489 - width: 7, 490 - top: 15, 491 - right: 12, 492 - }, 493 - ]} 494 - /> 495 - )} 496 - </View> 497 - )} 498 - </Link> 514 + <ChatListItemPortal.Outlet /> 499 515 500 - {showMenu && ( 501 - <ConvoMenu 502 - convo={convo} 503 - profile={profile} 504 - control={menuControl} 516 + {showMenu && ( 517 + <ConvoMenu 518 + convo={convo} 519 + profile={profile} 520 + control={menuControl} 521 + currentScreen="list" 522 + showMarkAsRead={convo.unreadCount > 0} 523 + hideTrigger={isNative} 524 + blockInfo={blockInfo} 525 + style={[ 526 + a.absolute, 527 + a.h_full, 528 + a.self_end, 529 + a.justify_center, 530 + { 531 + right: tokens.space.lg, 532 + opacity: 533 + !gtMobile || showActions || menuControl.isOpen ? 1 : 0, 534 + }, 535 + ]} 536 + latestReportableMessage={latestReportableMessage} 537 + /> 538 + )} 539 + <LeaveConvoPrompt 540 + control={leaveConvoControl} 541 + convoId={convo.id} 505 542 currentScreen="list" 506 - showMarkAsRead={convo.unreadCount > 0} 507 - hideTrigger={isNative} 508 - blockInfo={blockInfo} 509 - style={[ 510 - a.absolute, 511 - a.h_full, 512 - a.self_end, 513 - a.justify_center, 514 - { 515 - right: tokens.space.lg, 516 - opacity: !gtMobile || showActions || menuControl.isOpen ? 1 : 0, 517 - }, 518 - ]} 519 - latestReportableMessage={latestReportableMessage} 520 543 /> 521 - )} 522 - <LeaveConvoPrompt 523 - control={leaveConvoControl} 524 - convoId={convo.id} 525 - currentScreen="list" 526 - /> 527 - </View> 528 - </GestureActionView> 544 + </View> 545 + </GestureActionView> 546 + </ChatListItemPortal.Provider> 529 547 ) 530 548 }
+2 -3
src/screens/Messages/components/ChatStatusInfo.tsx
··· 46 46 label={_(msg`Block or report`)} 47 47 convo={convoState.convo} 48 48 profile={otherUser} 49 - color="negative" 49 + color="negative_subtle" 50 50 size="small" 51 51 currentScreen="conversation" 52 52 /> ··· 70 70 <AcceptChatButton 71 71 onAcceptConvo={onAcceptChat} 72 72 convo={convoState.convo} 73 - color="primary" 74 - variant="outline" 73 + color="primary_subtle" 75 74 size="small" 76 75 currentScreen="conversation" 77 76 />
+1 -7
src/screens/Messages/components/RequestButtons.tsx
··· 36 36 convo, 37 37 profile, 38 38 size = 'tiny', 39 - variant = 'outline', 40 39 color = 'secondary', 41 40 label, 42 41 showDeleteConvo, ··· 117 116 label={triggerProps.accessibilityLabel} 118 117 style={[a.flex_1]} 119 118 color={color} 120 - variant={variant} 121 119 size={size}> 122 120 <ButtonText> 123 121 {label || ( ··· 129 127 </Button> 130 128 )} 131 129 </Menu.Trigger> 132 - <Menu.Outer> 130 + <Menu.Outer showCancel> 133 131 <Menu.Group> 134 132 {showDeleteConvo && ( 135 133 <Menu.Item ··· 181 179 export function AcceptChatButton({ 182 180 convo, 183 181 size = 'tiny', 184 - variant = 'solid', 185 182 color = 'secondary_inverted', 186 183 label, 187 184 currentScreen, ··· 248 245 {...props} 249 246 label={label || _(msg`Accept chat request`)} 250 247 size={size} 251 - variant={variant} 252 248 color={color} 253 249 style={a.flex_1} 254 250 onPress={onPressAccept}> ··· 266 262 export function DeleteChatButton({ 267 263 convo, 268 264 size = 'tiny', 269 - variant = 'outline', 270 265 color = 'secondary', 271 266 label, 272 267 currentScreen, ··· 315 310 <Button 316 311 label={label || _(msg`Delete chat`)} 317 312 size={size} 318 - variant={variant} 319 313 color={color} 320 314 style={a.flex_1} 321 315 onPress={onPressDelete}
+34 -31
src/screens/Messages/components/RequestListItem.tsx
··· 7 7 import {atoms as a, tokens} from '#/alf' 8 8 import {KnownFollowers} from '#/components/KnownFollowers' 9 9 import {Text} from '#/components/Typography' 10 - import {ChatListItem} from './ChatListItem' 10 + import {ChatListItem, ChatListItemPortal} from './ChatListItem' 11 11 import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons' 12 12 13 13 export function RequestListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { ··· 42 42 <Trans comment="Accept a chat request">Accept Request</Trans> 43 43 </Text> 44 44 </View> 45 + {/* then, this gets absolutely positioned on top of the spacer */} 46 + <ChatListItemPortal.Portal> 47 + <View 48 + style={[ 49 + a.absolute, 50 + a.pr_md, 51 + a.w_full, 52 + a.flex_row, 53 + a.align_center, 54 + a.gap_sm, 55 + { 56 + bottom: tokens.space.md, 57 + paddingLeft: tokens.space.lg + 52 + tokens.space.md, 58 + }, 59 + ]}> 60 + {!isDeletedAccount ? ( 61 + <> 62 + <AcceptChatButton convo={convo} currentScreen="list" /> 63 + <RejectMenu 64 + convo={convo} 65 + profile={otherUser} 66 + showDeleteConvo 67 + currentScreen="list" 68 + /> 69 + </> 70 + ) : ( 71 + <> 72 + <DeleteChatButton convo={convo} currentScreen="list" /> 73 + <View style={a.flex_1} /> 74 + </> 75 + )} 76 + </View> 77 + </ChatListItemPortal.Portal> 45 78 </ChatListItem> 46 - <View 47 - style={[ 48 - a.absolute, 49 - a.pr_md, 50 - a.w_full, 51 - a.flex_row, 52 - a.align_center, 53 - a.gap_sm, 54 - { 55 - bottom: tokens.space.md, 56 - paddingLeft: tokens.space.lg + 52 + tokens.space.md, 57 - }, 58 - ]}> 59 - {!isDeletedAccount ? ( 60 - <> 61 - <AcceptChatButton convo={convo} currentScreen="list" /> 62 - <RejectMenu 63 - convo={convo} 64 - profile={otherUser} 65 - showDeleteConvo 66 - currentScreen="list" 67 - /> 68 - </> 69 - ) : ( 70 - <> 71 - <DeleteChatButton convo={convo} currentScreen="list" /> 72 - <View style={a.flex_1} /> 73 - </> 74 - )} 75 - </View> 76 79 </View> 77 80 ) 78 81 }