Compare commits

..

16 Commits

Author SHA1 Message Date
Eric Bailey 9abf028c85 Revert "Debug code"
This reverts commit 38445f31f4.
2025-10-02 11:32:35 -05:00
Eric Bailey 38445f31f4 Debug code 2025-09-30 13:02:54 -05:00
Eric Bailey 4e418c0d05 Bump api pkg 2025-09-30 11:18:42 -05:00
Dan Abramov 3fe5cc1d5e Rip out the network hack in favor of bluesky-social/atproto#4238 2025-09-30 00:11:19 +01:00
Samuel Newman 7f4e3091f7 patch react native screens (#9087) 2025-09-26 10:41:17 -07:00
jim 6114b33ce2 Merge pull request #8924 from internet-development/elijaharita/google-structured-data
feat: google structured data for profiles and posts
2025-09-26 09:27:39 -07:00
Elijah Seed-Arita 305ea8bdfb fix: remove escapejs and fix profile issues 2025-09-25 20:14:46 -07:00
Elijah Seed-Arita a322ae412a feat: profile.html escapejs 2025-09-25 20:14:46 -07:00
Elijah Seed-Arita caa7736035 feat: post.html structured content, wip 2025-09-25 20:14:46 -07:00
Elijah Seed-Arita 655964ec0b feat: profile.html structured content 2025-09-25 20:14:46 -07:00
Samuel Newman 66526a8e10 Fix extToMime function (#9084) 2025-09-25 10:26:25 -05:00
pfrazee 8cded435d1 Nightly source-language update 2025-09-25 02:36:36 +00:00
Eric Bailey bd17f04810 Migrate to @bsky.app/alf package (#9030)
* Add storybook shortcut in dev

* Migrate to alg pkg

* Migrate font_bold to new font_semi_bold

* Migrate font_heavy to font_bold

* Fix old theme refs

* Remove leading util

* Bump

* Revert "Add storybook shortcut in dev"

This reverts commit 7a86195c77fb5d449c7782e64ebabb6addfab919.

* Remove alf script

* Adjust font scale

* Bump

* Bump pkg

* Migrate values from conflicts

* Create default themes outside react

* Use built-in alf utils

* Migrate old font styles

* Remove unused tokens

* Move theme creation to more appropriate file

* Update button colors

* Adjust TextField colors, line heights
2025-09-24 11:47:08 -05:00
pfrazee bdc58045cf Nightly source-language update 2025-09-24 02:35:40 +00:00
Austin McKinley 0482e917b6 Merge pull request #9069 from bluesky-social/migrate-blink
Start migrating blink. Also add better shutdown handling for ogcard
2025-09-23 16:36:22 -07:00
Austin McKinley 0bc2355226 Start migrating blink. Also add better shutdown handling for ogcard 2025-09-23 15:17:49 -07:00
240 changed files with 1631 additions and 3420 deletions
@@ -1,6 +1,11 @@
name: build-and-push-link-aws
on:
workflow_dispatch:
pull_request:
paths:
- 'bskylink/**'
- 'Dockerfile.bskylink'
- '.github/workflows/build-and-push-link-aws.yaml'
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
@@ -45,7 +50,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: true
file: ./Dockerfile.bskylink
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+56 -23
View File
@@ -1,29 +1,62 @@
import {Database, envToCfg, httpLogger, LinkService, readEnv} from './index.js'
async function main() {
const env = readEnv()
const cfg = envToCfg(env)
if (cfg.db.migrationUrl) {
const migrateDb = Database.postgres({
url: cfg.db.migrationUrl,
schema: cfg.db.schema,
try {
httpLogger.info('Starting blink service')
const env = readEnv()
const cfg = envToCfg(env)
httpLogger.info(
{
port: cfg.service.port,
safelinkEnabled: cfg.service.safelinkEnabled,
hasDbUrl: !!cfg.db.url,
hasDbMigrationUrl: !!cfg.db.migrationUrl,
},
'Configuration loaded',
)
if (cfg.db.migrationUrl) {
httpLogger.info('Running database migrations')
const migrateDb = Database.postgres({
url: cfg.db.migrationUrl,
schema: cfg.db.schema,
})
await migrateDb.migrateToLatestOrThrow()
await migrateDb.close()
httpLogger.info('Database migrations completed')
}
httpLogger.info('Creating LinkService')
const link = await LinkService.create(cfg)
if (link.ctx.cfg.service.safelinkEnabled) {
httpLogger.info('Starting Safelink client')
link.ctx.safelinkClient.runFetchEvents()
}
await link.start()
httpLogger.info('Link service is running')
process.on('SIGTERM', async () => {
httpLogger.info('Link service is stopping')
await link.destroy()
httpLogger.info('Link service is stopped')
})
await migrateDb.migrateToLatestOrThrow()
await migrateDb.close()
} catch (error) {
httpLogger.error(
{
error: String(error),
stack: error instanceof Error ? error.stack : undefined,
},
'Failed to start blink service',
)
process.exit(1)
}
const link = await LinkService.create(cfg)
if (link.ctx.cfg.service.safelinkEnabled) {
link.ctx.safelinkClient.runFetchEvents()
}
await link.start()
httpLogger.info('link service is running')
process.on('SIGTERM', async () => {
httpLogger.info('link service is stopping')
await link.destroy()
httpLogger.info('link service is stopped')
})
}
main()
main().catch(error => {
console.error('Unhandled startup error:', error)
process.exit(1)
})
+10
View File
@@ -34,6 +34,16 @@ export class Database {
static postgres(opts: PgOptions): Database {
const {schema, url, txLockNonce} = opts
log.info(
{
schema,
poolSize: opts.poolSize,
poolMaxUses: opts.poolMaxUses,
poolIdleTimeoutMs: opts.poolIdleTimeoutMs,
},
'Creating database connection',
)
const pool =
opts.pool ??
new Pg.Pool({
+26 -4
View File
@@ -62,7 +62,10 @@ export class CardService {
// Start main application server
this.server = this.app.listen(this.ctx.cfg.service.port)
this.server.keepAliveTimeout = 90000
this.terminator = createHttpTerminator({server: this.server})
this.terminator = createHttpTerminator({
server: this.server,
gracefulTerminationTimeout: 15000, // 15s timeout for in-flight requests
})
await events.once(this.server, 'listening')
// Start separate metrics server
@@ -73,13 +76,32 @@ export class CardService {
})
this.metricsServer = metricsApp.listen(this.ctx.cfg.service.metricsPort)
this.metricsTerminator = createHttpTerminator({server: this.metricsServer})
this.metricsTerminator = createHttpTerminator({
server: this.metricsServer,
gracefulTerminationTimeout: 2000, // 2s timeout for metrics server
})
await events.once(this.metricsServer, 'listening')
}
async destroy() {
const startTime = Date.now()
this.ctx.abortController.abort()
await this.terminator?.terminate()
await this.metricsTerminator?.terminate()
const shutdownPromises = []
if (this.terminator) {
shutdownPromises.push(this.terminator.terminate())
}
if (this.metricsTerminator) {
shutdownPromises.push(this.metricsTerminator.terminate())
}
await Promise.all(shutdownPromises)
const elapsed = Date.now() - startTime
const {httpLogger} = await import('./logger.js')
httpLogger.info(`Graceful shutdown completed in ${elapsed}ms`)
}
}
+35
View File
@@ -56,6 +56,41 @@
<meta property="article:published_time" content="{{ postView.IndexedAt }}">
<link rel="alternate" type="application/json+oembed" href="https://embed.bsky.app/oembed?format=json&url={{ postView.Uri | urlencode }}" />
<link rel="alternate" href="{{ postView.Uri }}" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "DiscussionForumPosting",
"author": {
"@type": "Person",
"name": "{{ postView.Author.DisplayName }}",
"url": "https://bsky.app/profile/{{ postView.Author.Handle }}"
},
{%- if postText %}
"text": "{{ postText }}",
{% endif %}
{%- if imageThumbUrls %}
"image": "{{ imageThumbUrls[0] }}",
{% endif %}
"datePublished": "{{ postView.IndexedAt }}",
"interactionStatistic": [
{
"@type": "InteractionCounter",
"interactionType": "https://schema.org/LikeAction",
"userInteractionCount": {{ postView.LikeCount }}
},
{
"@type": "InteractionCounter",
"interactionType": "https://schema.org/CommentAction",
"userInteractionCount": {{ postView.ReplyCount }}
},
{
"@type": "InteractionCounter",
"interactionType": "https://schema.org/ShareAction",
"userInteractionCount": {{ postView.RepostCount + postView.QuoteCount }}
}
]
}
</script>
{%- elif requiresAuth and profileHandle -%}
<meta property="og:type" content="article">
<meta property="profile:username" content="{{ profileHandle }}">
+35
View File
@@ -51,6 +51,41 @@
<meta property="og:description" content="This profile requires authentication to view.">
<meta property="twitter:description" content="This profile requires authentication to view.">
{% endif %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ProfilePage",
"dateCreated": "{{ profileView.CreatedAt }}",
"mainEntity": {
"@type": "Person",
"name": "{{ profileView.DisplayName }}",
"alternateName": "@{{ profileView.Handle }}",
"identifier": "{{ profileView.Did }}",
"description": "{{ profileView.Description }}",
"image": "{{ profileView.Avatar }}",
"interactionStatistic": [
{
"@type": "InteractionCounter",
"interactionType": "https://schema.org/FollowAction",
"userInteractionCount": {{ profileView.FollowersCount }}
}
],
"agentInteractionStatistic": [
{
"@type": "InteractionCounter",
"interactionType": "https://schema.org/FollowAction",
"userInteractionCount": {{ profileView.FollowsCount }}
},
{
"@type": "InteractionCounter",
"interactionType": "https://schema.org/WriteAction",
"userInteractionCount": {{ profileView.PostsCount }}
}
]
}
}
</script>
{% endif -%}
{%- endblock %}
+2 -1
View File
@@ -71,9 +71,10 @@
"icons:optimize": "svgo -f ./assets/icons"
},
"dependencies": {
"@atproto/api": "^0.16.7",
"@atproto/api": "^0.17.0",
"@bitdrift/react-native": "^0.6.8",
"@braintree/sanitize-url": "^6.0.2",
"@bsky.app/alf": "^0.1.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
"@emoji-mart/react": "^1.1.1",
"@expo/html-elements": "^0.12.5",
+326
View File
@@ -0,0 +1,326 @@
diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm
index b62a2e2..cb469db 100644
--- a/node_modules/react-native-screens/ios/RNSScreen.mm
+++ b/node_modules/react-native-screens/ios/RNSScreen.mm
@@ -729,9 +729,26 @@ - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingFor
#endif
}
-#if !RCT_NEW_ARCH_ENABLED
+- (void)willMoveToWindow:(UIWindow *)newWindow
+{
+ if (@available(iOS 26, *)) {
+ // In iOS 26, as soon as another screen appears in transition, it is interactable
+ // To avoid glitches resulting from clicking buttons mid transition, we temporarily disable all interactions
+ // Disabling interactions for parent navigation controller won't be enough in case of nested stack
+ // Furthermore, a stack put inside a modal will exist in an entirely different hierarchy
+ // To be sure, we block interactions on the whole window.
+ // Note that newWindows is nil when moving from instead of moving to, and Obj-C handles nil correctly
+ newWindow.userInteractionEnabled = false;
+ }
+}
+
- (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
{
+ if (@available(iOS 26, *)) {
+ // Disable interactions to disallow multiple modals dismissed at once; see willMoveToWindow
+ presentationController.containerView.window.userInteractionEnabled = false;
+ }
+#if !RCT_NEW_ARCH_ENABLED
// On Paper, we need to call both "cancel" and "reset" here because RN's gesture
// recognizer does not handle the scenario when it gets cancelled by other top
// level gesture recognizer. In this case by the modal dismiss gesture.
@@ -744,8 +761,8 @@ - (void)presentationControllerWillDismiss:(UIPresentationController *)presentati
// down.
[_touchHandler cancel];
[_touchHandler reset];
-}
#endif // !RCT_NEW_ARCH_ENABLED
+}
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
{
@@ -757,6 +774,10 @@ - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presenta
- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController
{
+ if (@available(iOS 26, *)) {
+ // Reenable interactions; see presentationControllerWillDismiss
+ presentationController.containerView.window.userInteractionEnabled = true;
+ }
// NOTE(kkafar): We should consider depracating the use of gesture cancel here & align
// with usePreventRemove API of react-navigation v7.
[self notifyGestureCancel];
@@ -767,6 +788,11 @@ - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)pr
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
+ if (@available(iOS 26, *)) {
+ // Reenable interactions; see presentationControllerWillDismiss
+ // Dismissed screen doesn't hold a reference to window, but presentingViewController.view does
+ presentationController.presentingViewController.view.window.userInteractionEnabled = true;
+ }
if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
[_reactSuperview performSelector:@selector(presentationControllerDidDismiss:) withObject:presentationController];
}
@@ -1518,6 +1544,10 @@ - (void)viewWillDisappear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
{
+ if (@available(iOS 26, *)) {
+ // Reenable interactions, see willMoveToWindow
+ self.view.window.userInteractionEnabled = true;
+ }
[super viewDidAppear:animated];
if (!_isSwiping || _shouldNotify) {
// we are going forward or dismissing without swipe
diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm
index 229dc58..10b365b 100644
--- a/node_modules/react-native-screens/ios/RNSScreenStack.mm
+++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm
@@ -62,26 +62,6 @@ @interface RNSScreenStackView () <
@implementation RNSNavigationController
-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
-- (void)viewDidLoad
-{
- // iOS 26 introduces new gesture recognizer which replaces our RNSPanGestureRecognizer.
- // The problem is that we are not able to handle it here for various reasons:
- // - the new recognizer comes with its own delegate and our current approach is to wire
- // all recognizers to RNSScreenStackView; to be 100% sure we don't break the logic,
- // we would have to decorate its delegate and call it after our code, which would
- // break other recognizers that the stack view is the delegate for
- // - when RNSScreenStackView.setupGestureHandler method is called, the recognizer hasn't been
- // loaded yet and there is no other place to configure in a not "hacky" way
- // - the official docs warn us to not use it for anything other than "setting up failure requirements with it"
- // - we expose fullScreenGestureEnabled prop to enable/disable the feature,
- // so we need control over the delegate
- if (@available(iOS 26.0, *)) {
- self.interactiveContentPopGestureRecognizer.enabled = NO;
- }
-}
-#endif // iOS 26
-
#if !TARGET_OS_TV
- (UIViewController *)childViewControllerForStatusBarStyle
{
@@ -219,50 +199,6 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController
return false;
}
-#pragma mark - UINavigationBarDelegate
-
-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
-- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
-{
- if (@available(iOS 26, *)) {
- // To prevent popping multiple screens when back button is pressed repeatedly,
- // We allow for pop operation to proceed only if no transition is in progress,
- // which we check indirectly by checking if transitionCoordinator is set.
- // If it's not, we are safe to proceed.
- if (self.transitionCoordinator == nil) {
- // We still need to disable interactions for back button so click effects are not applied,
- // and there is unfortunately no better place for it currently
- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
- if (button != nil) {
- button.userInteractionEnabled = false;
- }
-
- return true;
- }
-
- return false;
- }
-
- return true;
-}
-
-- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
-{
- if (@available(iOS 26, *)) {
- // Reset interactions on back button -> see navigationBar:shouldPopItem
- // IMPORTANT: This reset won't execute when preventNativeDismiss is on.
- // However, on iOS 26, unlike in previous versions, the back button instance changes
- // when handling preventNativeDismiss and userIteractionEnabled is reset.
- // The instance also changes when regular screen pop happens, but in that case
- // the value of userInteractionEnabled is carried on, and we reset it here.
- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
- if (button != nil) {
- button.userInteractionEnabled = true;
- }
- }
-}
-#endif // Check for iOS >= 26
-
#pragma mark - RNSFrameCorrectionProvider
#ifdef RNS_GAMMA_ENABLED
@@ -327,7 +263,7 @@ @implementation RNSScreenStackView {
UINavigationController *_controller;
NSMutableArray<RNSScreenView *> *_reactSubviews;
BOOL _invalidated;
- BOOL _isFullWidthSwiping;
+ BOOL _isFullWidthSwipingWithPanGesture; // used only for content swipe with RNSPanGestureRecognizer
RNSPercentDrivenInteractiveTransition *_interactionController;
__weak RNSScreenStackManager *_manager;
BOOL _updateScheduled;
@@ -522,6 +458,11 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller
[self addSubview:controller.view];
#if !TARGET_OS_TV
_controller.interactivePopGestureRecognizer.delegate = self;
+ #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+ if (@available(iOS 26, *)) {
+ _controller.interactiveContentPopGestureRecognizer.delegate = self;
+ }
+#endif // Check for iOS >= 26.0
#endif
[controller didMoveToParentViewController:parentView.reactViewController];
// On iOS pre 12 we observed that `willShowViewController` delegate method does not always
@@ -943,7 +884,7 @@ - (void)dismissOnReload
// when preventing the native dismiss with back button, we have to return the animator.
// Also, we need to return the animator when full width swiping even if the animation is not custom,
// otherwise the screen will be just popped immediately due to no animation
- ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwiping ||
+ ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwipingWithPanGesture ||
[RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation] || _customAnimation)) {
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
}
@@ -967,23 +908,39 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
}
RNSScreenView *topScreen = _reactSubviews.lastObject;
+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
+
#if TARGET_OS_TV || TARGET_OS_VISION
[self cancelTouchesInParent];
return YES;
#else
- // RNSPanGestureRecognizer will receive events iff topScreen.fullScreenSwipeEnabled == YES;
- // Events are filtered in gestureRecognizer:shouldReceivePressOrTouchEvent: method
if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
- if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
- _isFullWidthSwiping = YES;
- [self cancelTouchesInParent];
- return YES;
+ // On iOS < 26, we have a custom full screen swipe recognizer that functions similarily
+ // to interactiveContentPopGestureRecognizer introduced in iOS 26.
+ // On iOS >= 26, we want to use the native one, but we are unable to handle custom animations
+ // with native interactiveContentPopGestureRecognizer, so we have to fallback to the old implementation.
+ // In this case, the old one should behave as close as the new native one, having only the difference
+ // in animation, and without any customization that is exclusive for it (e.g. gestureResponseDistance).
+ if (@available(iOS 26, *)) {
+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
+ _isFullWidthSwipingWithPanGesture = YES;
+ [self cancelTouchesInParent];
+ return YES;
+ }
+ return NO;
+ } else {
+ if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
+ _isFullWidthSwipingWithPanGesture = YES;
+ [self cancelTouchesInParent];
+ return YES;
+ }
+ return NO;
}
- return NO;
}
// Now we're dealing with RNSScreenEdgeGestureRecognizer (or _UIParallaxTransitionPanGestureRecognizer)
- if (topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]) {
+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) {
UIRectEdge edges = ((RNSScreenEdgeGestureRecognizer *)gestureRecognizer).edges;
BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft;
@@ -1028,7 +985,9 @@ - (void)setupGestureHandlers
rightEdgeSwipeGestureRecognizer.delegate = self;
[self addGestureRecognizer:rightEdgeSwipeGestureRecognizer];
- // gesture recognizer for full width swipe gesture
+ // Starting from iOS 26, RNSPanGestureRecognizer has been mostly replaced by native
+ // interactiveContentPopGestureRecognizer. It still needs to handle custom dismiss animations,
+ // which we are not able to handle with the latter.
RNSPanGestureRecognizer *panRecognizer = [[RNSPanGestureRecognizer alloc] initWithTarget:self
action:@selector(handleSwipe:)];
panRecognizer.delegate = self;
@@ -1091,7 +1050,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
[_interactionController cancelInteractiveTransition];
}
_interactionController = nil;
- _isFullWidthSwiping = NO;
+ _isFullWidthSwipingWithPanGesture = NO;
}
default: {
break;
@@ -1225,14 +1184,6 @@ - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognize
// Be careful when adding another type of gesture recognizer.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePressOrTouchEvent:(NSObject *)event
{
- if (@available(iOS 26, *)) {
- // in iOS 26, you can swipe to pop screen before the previous one finished transitioning;
- // this prevents from registering the second gesture
- if ([self isTransitionInProgress]) {
- return NO;
- }
- }
-
RNSScreenView *topScreen = _reactSubviews.lastObject;
for (RNSScreenView *s in _reactSubviews.reverseObjectEnumerator) {
@@ -1249,10 +1200,30 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
return NO;
}
+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
+ if (@available(iOS 26, *)) {
+ // On iOS 26, fullScreenSwipeEnabled takes no effect, and depending on whether custom animations are on,
+ // we select either interactiveContentPopGestureRecognizer or RNSPanGestureRecognizer
+ if (([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] &&
+ !customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) ||
+ (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
+ customAnimationOnSwipePropSetAndSelectedAnimationIsCustom)) {
+ return NO;
+ }
+ } else {
+ // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
+ return topScreen.fullScreenSwipeEnabled;
+ }
+ }
+#else // check for iOS >= 26
// We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
return topScreen.fullScreenSwipeEnabled;
}
+#endif // check for iOS >= 26
// RNSScreenEdgeGestureRecognizer || _UIParallaxTransitionPanGestureRecognizer
return YES;
@@ -1268,15 +1239,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:touch];
}
-- (BOOL)isTransitionInProgress
-{
- if (_controller.transitionCoordinator != nil) {
- return YES;
- }
-
- return NO;
-}
-
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
@@ -1289,7 +1251,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) {
return NO;
}
-
return YES;
}
return NO;
-6
View File
@@ -65,7 +65,6 @@ import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy'
import {ProfileScreen} from '#/view/screens/Profile'
import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy'
import {Storybook} from '#/view/screens/Storybook'
import {StorybookLists} from '#/view/screens/StorybookLists'
import {SupportScreen} from '#/view/screens/Support'
import {TermsOfServiceScreen} from '#/view/screens/TermsOfService'
import {BottomBar} from '#/view/shell/bottom-bar/BottomBar'
@@ -306,11 +305,6 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
getComponent={() => Storybook}
options={{title: title(msg`Storybook`), requireAuth: true}}
/>
<Stack.Screen
name="StorybookLists"
getComponent={() => StorybookLists}
options={{title: title(msg`Storybook Lists`), requireAuth: true}}
/>
<Stack.Screen
name="DebugMod"
getComponent={() => DebugModScreen}
+4 -997
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -7,11 +7,11 @@ const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe
const factor = 0.0625 // 1 - (15/16)
const fontScaleMultipliers: Record<Device['fontScale'], number> = {
'-2': 1 - factor * 3,
'-1': 1 - factor * 2,
'0': 1 - factor * 1, // default
'1': 1,
'2': 1 + factor * 1,
'-2': 1 - factor * 1, // unused
'-1': 1 - factor * 1,
'0': 1, // default
'1': 1 + factor * 1,
'2': 1 + factor * 1, // unused
}
export function computeFontScaleMultiplier(scale: Device['fontScale']) {
+13 -30
View File
@@ -1,4 +1,5 @@
import React from 'react'
import {type Theme, type ThemeName} from '@bsky.app/alf'
import {
computeFontScaleMultiplier,
@@ -7,16 +8,14 @@ import {
setFontFamily as persistFontFamily,
setFontScale as persistFontScale,
} from '#/alf/fonts'
import {createThemes, defaultTheme} from '#/alf/themes'
import {type Theme, type ThemeName} from '#/alf/types'
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
import {themes} from '#/alf/themes'
import {type Device} from '#/storage'
export {type TextStyleProp, type Theme, type ViewStyleProp} from '@bsky.app/alf'
export {atoms} from '#/alf/atoms'
export * from '#/alf/breakpoints'
export * from '#/alf/fonts'
export * as tokens from '#/alf/tokens'
export * from '#/alf/types'
export * from '#/alf/util/flatten'
export * from '#/alf/util/platform'
export * from '#/alf/util/themeSelector'
@@ -25,7 +24,7 @@ export * from '#/alf/util/useGutters'
export type Alf = {
themeName: ThemeName
theme: Theme
themes: ReturnType<typeof createThemes>
themes: typeof themes
fonts: {
scale: Exclude<Device['fontScale'], undefined>
scaleMultiplier: number
@@ -44,14 +43,8 @@ export type Alf = {
*/
export const Context = React.createContext<Alf>({
themeName: 'light',
theme: defaultTheme,
themes: createThemes({
hues: {
primary: BLUE_HUE,
negative: RED_HUE,
positive: GREEN_HUE,
},
}),
theme: themes.light,
themes,
fonts: {
scale: getFontScale(),
scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
@@ -76,10 +69,10 @@ export function ThemeProvider({
const setFontScaleAndPersist = React.useCallback<
Alf['fonts']['setFontScale']
>(
fontScale => {
setFontScale(fontScale)
persistFontScale(fontScale)
setFontScaleMultiplier(computeFontScaleMultiplier(fontScale))
fs => {
setFontScale(fs)
persistFontScale(fs)
setFontScaleMultiplier(computeFontScaleMultiplier(fs))
},
[setFontScale],
)
@@ -89,21 +82,12 @@ export function ThemeProvider({
const setFontFamilyAndPersist = React.useCallback<
Alf['fonts']['setFontFamily']
>(
fontFamily => {
setFontFamily(fontFamily)
persistFontFamily(fontFamily)
ff => {
setFontFamily(ff)
persistFontFamily(ff)
},
[setFontFamily],
)
const themes = React.useMemo(() => {
return createThemes({
hues: {
primary: BLUE_HUE,
negative: RED_HUE,
positive: GREEN_HUE,
},
})
}, [])
const value = React.useMemo<Alf>(
() => ({
@@ -121,7 +105,6 @@ export function ThemeProvider({
}),
[
themeName,
themes,
fontScale,
setFontScaleAndPersist,
fontFamily,
+39 -580
View File
@@ -1,585 +1,44 @@
import {atoms} from '#/alf/atoms'
import {type Palette, type Theme} from '#/alf/types'
import {
BLUE_HUE,
defaultScale,
dimScale,
GREEN_HUE,
RED_HUE,
} from '#/alf/util/colorGeneration'
createThemes,
DEFAULT_PALETTE,
DEFAULT_SUBDUED_PALETTE,
} from '@bsky.app/alf'
const themes = createThemes({
hues: {
primary: BLUE_HUE,
negative: RED_HUE,
positive: GREEN_HUE,
},
const DEFAULT_THEMES = createThemes({
defaultPalette: DEFAULT_PALETTE,
subduedPalette: DEFAULT_SUBDUED_PALETTE,
})
/**
* @deprecated use ALF and access palette from `useTheme()`
*/
export const lightPalette = themes.lightPalette
/**
* @deprecated use ALF and access palette from `useTheme()`
*/
export const darkPalette = themes.darkPalette
/**
* @deprecated use ALF and access palette from `useTheme()`
*/
export const dimPalette = themes.dimPalette
/**
* @deprecated use ALF and access theme from `useTheme()`
*/
export const light = themes.light
/**
* @deprecated use ALF and access theme from `useTheme()`
*/
export const dark = themes.dark
/**
* @deprecated use ALF and access theme from `useTheme()`
*/
export const dim = themes.dim
export const defaultTheme = themes.light
export function createThemes({
hues,
}: {
hues: {
primary: number
negative: number
positive: number
}
}): {
lightPalette: Palette
darkPalette: Palette
dimPalette: Palette
light: Theme
dark: Theme
dim: Theme
} {
const color = {
like: '#ec4899',
trueBlack: '#000000',
gray_0: `hsl(${hues.primary}, 20%, ${defaultScale[14]}%)`,
gray_25: `hsl(${hues.primary}, 20%, ${defaultScale[13]}%)`,
gray_50: `hsl(${hues.primary}, 20%, ${defaultScale[12]}%)`,
gray_100: `hsl(${hues.primary}, 20%, ${defaultScale[11]}%)`,
gray_200: `hsl(${hues.primary}, 20%, ${defaultScale[10]}%)`,
gray_300: `hsl(${hues.primary}, 20%, ${defaultScale[9]}%)`,
gray_400: `hsl(${hues.primary}, 20%, ${defaultScale[8]}%)`,
gray_500: `hsl(${hues.primary}, 20%, ${defaultScale[7]}%)`,
gray_600: `hsl(${hues.primary}, 24%, ${defaultScale[6]}%)`,
gray_700: `hsl(${hues.primary}, 24%, ${defaultScale[5]}%)`,
gray_800: `hsl(${hues.primary}, 28%, ${defaultScale[4]}%)`,
gray_900: `hsl(${hues.primary}, 28%, ${defaultScale[3]}%)`,
gray_950: `hsl(${hues.primary}, 28%, ${defaultScale[2]}%)`,
gray_975: `hsl(${hues.primary}, 28%, ${defaultScale[1]}%)`,
gray_1000: `hsl(${hues.primary}, 28%, ${defaultScale[0]}%)`,
primary_25: `hsl(${hues.primary}, 99%, 97%)`,
primary_50: `hsl(${hues.primary}, 99%, 95%)`,
primary_100: `hsl(${hues.primary}, 99%, 90%)`,
primary_200: `hsl(${hues.primary}, 99%, 80%)`,
primary_300: `hsl(${hues.primary}, 99%, 70%)`,
primary_400: `hsl(${hues.primary}, 99%, 60%)`,
primary_500: `hsl(${hues.primary}, 99%, 53%)`,
primary_600: `hsl(${hues.primary}, 99%, 42%)`,
primary_700: `hsl(${hues.primary}, 99%, 34%)`,
primary_800: `hsl(${hues.primary}, 99%, 26%)`,
primary_900: `hsl(${hues.primary}, 99%, 18%)`,
primary_950: `hsl(${hues.primary}, 99%, 10%)`,
primary_975: `hsl(${hues.primary}, 99%, 7%)`,
green_25: `hsl(${hues.positive}, 82%, 97%)`,
green_50: `hsl(${hues.positive}, 82%, 95%)`,
green_100: `hsl(${hues.positive}, 82%, 90%)`,
green_200: `hsl(${hues.positive}, 82%, 80%)`,
green_300: `hsl(${hues.positive}, 82%, 70%)`,
green_400: `hsl(${hues.positive}, 82%, 60%)`,
green_500: `hsl(${hues.positive}, 82%, 50%)`,
green_600: `hsl(${hues.positive}, 82%, 42%)`,
green_700: `hsl(${hues.positive}, 82%, 34%)`,
green_800: `hsl(${hues.positive}, 82%, 26%)`,
green_900: `hsl(${hues.positive}, 82%, 18%)`,
green_950: `hsl(${hues.positive}, 82%, 10%)`,
green_975: `hsl(${hues.positive}, 82%, 7%)`,
red_25: `hsl(${hues.negative}, 91%, 97%)`,
red_50: `hsl(${hues.negative}, 91%, 95%)`,
red_100: `hsl(${hues.negative}, 91%, 90%)`,
red_200: `hsl(${hues.negative}, 91%, 80%)`,
red_300: `hsl(${hues.negative}, 91%, 70%)`,
red_400: `hsl(${hues.negative}, 91%, 60%)`,
red_500: `hsl(${hues.negative}, 91%, 50%)`,
red_600: `hsl(${hues.negative}, 91%, 42%)`,
red_700: `hsl(${hues.negative}, 91%, 34%)`,
red_800: `hsl(${hues.negative}, 91%, 26%)`,
red_900: `hsl(${hues.negative}, 91%, 18%)`,
red_950: `hsl(${hues.negative}, 91%, 10%)`,
red_975: `hsl(${hues.negative}, 91%, 7%)`,
} as const
const lightPalette = {
white: color.gray_0,
black: color.gray_1000,
like: color.like,
contrast_25: color.gray_25,
contrast_50: color.gray_50,
contrast_100: color.gray_100,
contrast_200: color.gray_200,
contrast_300: color.gray_300,
contrast_400: color.gray_400,
contrast_500: color.gray_500,
contrast_600: color.gray_600,
contrast_700: color.gray_700,
contrast_800: color.gray_800,
contrast_900: color.gray_900,
contrast_950: color.gray_950,
contrast_975: color.gray_975,
primary_25: color.primary_25,
primary_50: color.primary_50,
primary_100: color.primary_100,
primary_200: color.primary_200,
primary_300: color.primary_300,
primary_400: color.primary_400,
primary_500: color.primary_500,
primary_600: color.primary_600,
primary_700: color.primary_700,
primary_800: color.primary_800,
primary_900: color.primary_900,
primary_950: color.primary_950,
primary_975: color.primary_975,
positive_25: color.green_25,
positive_50: color.green_50,
positive_100: color.green_100,
positive_200: color.green_200,
positive_300: color.green_300,
positive_400: color.green_400,
positive_500: color.green_500,
positive_600: color.green_600,
positive_700: color.green_700,
positive_800: color.green_800,
positive_900: color.green_900,
positive_950: color.green_950,
positive_975: color.green_975,
negative_25: color.red_25,
negative_50: color.red_50,
negative_100: color.red_100,
negative_200: color.red_200,
negative_300: color.red_300,
negative_400: color.red_400,
negative_500: color.red_500,
negative_600: color.red_600,
negative_700: color.red_700,
negative_800: color.red_800,
negative_900: color.red_900,
negative_950: color.red_950,
negative_975: color.red_975,
} as const
const darkPalette: Palette = {
white: color.gray_25,
black: color.trueBlack,
like: color.like,
contrast_25: color.gray_975,
contrast_50: color.gray_950,
contrast_100: color.gray_900,
contrast_200: color.gray_800,
contrast_300: color.gray_700,
contrast_400: color.gray_600,
contrast_500: color.gray_500,
contrast_600: color.gray_400,
contrast_700: color.gray_300,
contrast_800: color.gray_200,
contrast_900: color.gray_100,
contrast_950: color.gray_50,
contrast_975: color.gray_25,
primary_25: color.primary_975,
primary_50: color.primary_950,
primary_100: color.primary_900,
primary_200: color.primary_800,
primary_300: color.primary_700,
primary_400: color.primary_600,
primary_500: color.primary_500,
primary_600: color.primary_400,
primary_700: color.primary_300,
primary_800: color.primary_200,
primary_900: color.primary_100,
primary_950: color.primary_50,
primary_975: color.primary_25,
positive_25: color.green_975,
positive_50: color.green_950,
positive_100: color.green_900,
positive_200: color.green_800,
positive_300: color.green_700,
positive_400: color.green_600,
positive_500: color.green_500,
positive_600: color.green_400,
positive_700: color.green_300,
positive_800: color.green_200,
positive_900: color.green_100,
positive_950: color.green_50,
positive_975: color.green_25,
negative_25: color.red_975,
negative_50: color.red_950,
negative_100: color.red_900,
negative_200: color.red_800,
negative_300: color.red_700,
negative_400: color.red_600,
negative_500: color.red_500,
negative_600: color.red_400,
negative_700: color.red_300,
negative_800: color.red_200,
negative_900: color.red_100,
negative_950: color.red_50,
negative_975: color.red_25,
} as const
const dimPalette: Palette = {
...darkPalette,
black: `hsl(${hues.primary}, 28%, ${dimScale[0]}%)`,
like: color.like,
contrast_25: `hsl(${hues.primary}, 28%, ${dimScale[1]}%)`,
contrast_50: `hsl(${hues.primary}, 28%, ${dimScale[2]}%)`,
contrast_100: `hsl(${hues.primary}, 28%, ${dimScale[3]}%)`,
contrast_200: `hsl(${hues.primary}, 28%, ${dimScale[4]}%)`,
contrast_300: `hsl(${hues.primary}, 24%, ${dimScale[5]}%)`,
contrast_400: `hsl(${hues.primary}, 24%, ${dimScale[6]}%)`,
contrast_500: `hsl(${hues.primary}, 20%, ${dimScale[7]}%)`,
contrast_600: `hsl(${hues.primary}, 20%, ${dimScale[8]}%)`,
contrast_700: `hsl(${hues.primary}, 20%, ${dimScale[9]}%)`,
contrast_800: `hsl(${hues.primary}, 20%, ${dimScale[10]}%)`,
contrast_900: `hsl(${hues.primary}, 20%, ${dimScale[11]}%)`,
contrast_950: `hsl(${hues.primary}, 20%, ${dimScale[12]}%)`,
contrast_975: `hsl(${hues.primary}, 20%, ${dimScale[13]}%)`,
primary_25: `hsl(${hues.primary}, 50%, ${dimScale[1]}%)`,
primary_50: `hsl(${hues.primary}, 60%, ${dimScale[2]}%)`,
primary_100: `hsl(${hues.primary}, 70%, ${dimScale[3]}%)`,
primary_200: `hsl(${hues.primary}, 82%, ${dimScale[4]}%)`,
primary_300: `hsl(${hues.primary}, 90%, ${dimScale[5]}%)`,
primary_400: `hsl(${hues.primary}, 95%, ${dimScale[6]}%)`,
primary_500: `hsl(${hues.primary}, 99%, ${dimScale[7]}%)`,
primary_600: `hsl(${hues.primary}, 99%, ${dimScale[8]}%)`,
primary_700: `hsl(${hues.primary}, 99%, ${dimScale[9]}%)`,
primary_800: `hsl(${hues.primary}, 99%, ${dimScale[10]}%)`,
primary_900: `hsl(${hues.primary}, 99%, ${dimScale[11]}%)`,
primary_950: `hsl(${hues.primary}, 99%, ${dimScale[12]}%)`,
primary_975: `hsl(${hues.primary}, 99%, ${dimScale[13]}%)`,
positive_25: `hsl(${hues.positive}, 50%, ${dimScale[1]}%)`,
positive_50: `hsl(${hues.positive}, 60%, ${dimScale[2]}%)`,
positive_100: `hsl(${hues.positive}, 70%, ${dimScale[3]}%)`,
positive_200: `hsl(${hues.positive}, 82%, ${dimScale[4]}%)`,
positive_300: `hsl(${hues.positive}, 82%, ${dimScale[5]}%)`,
positive_400: `hsl(${hues.positive}, 82%, ${dimScale[6]}%)`,
positive_500: `hsl(${hues.positive}, 82%, ${dimScale[7]}%)`,
positive_600: `hsl(${hues.positive}, 82%, ${dimScale[8]}%)`,
positive_700: `hsl(${hues.positive}, 82%, ${dimScale[9]}%)`,
positive_800: `hsl(${hues.positive}, 82%, ${dimScale[10]}%)`,
positive_900: `hsl(${hues.positive}, 82%, ${dimScale[11]}%)`,
positive_950: `hsl(${hues.positive}, 82%, ${dimScale[12]}%)`,
positive_975: `hsl(${hues.positive}, 82%, ${dimScale[13]}%)`,
negative_25: `hsl(${hues.negative}, 70%, ${dimScale[1]}%)`,
negative_50: `hsl(${hues.negative}, 80%, ${dimScale[2]}%)`,
negative_100: `hsl(${hues.negative}, 84%, ${dimScale[3]}%)`,
negative_200: `hsl(${hues.negative}, 88%, ${dimScale[4]}%)`,
negative_300: `hsl(${hues.negative}, 91%, ${dimScale[5]}%)`,
negative_400: `hsl(${hues.negative}, 91%, ${dimScale[6]}%)`,
negative_500: `hsl(${hues.negative}, 91%, ${dimScale[7]}%)`,
negative_600: `hsl(${hues.negative}, 91%, ${dimScale[8]}%)`,
negative_700: `hsl(${hues.negative}, 91%, ${dimScale[9]}%)`,
negative_800: `hsl(${hues.negative}, 91%, ${dimScale[10]}%)`,
negative_900: `hsl(${hues.negative}, 91%, ${dimScale[11]}%)`,
negative_950: `hsl(${hues.negative}, 91%, ${dimScale[12]}%)`,
negative_975: `hsl(${hues.negative}, 91%, ${dimScale[13]}%)`,
} as const
const light: Theme = {
scheme: 'light',
name: 'light',
palette: lightPalette,
atoms: {
text: {
color: lightPalette.black,
},
text_contrast_low: {
color: lightPalette.contrast_400,
},
text_contrast_medium: {
color: lightPalette.contrast_700,
},
text_contrast_high: {
color: lightPalette.contrast_900,
},
text_inverted: {
color: lightPalette.white,
},
bg: {
backgroundColor: lightPalette.white,
},
bg_contrast_25: {
backgroundColor: lightPalette.contrast_25,
},
bg_contrast_50: {
backgroundColor: lightPalette.contrast_50,
},
bg_contrast_100: {
backgroundColor: lightPalette.contrast_100,
},
bg_contrast_200: {
backgroundColor: lightPalette.contrast_200,
},
bg_contrast_300: {
backgroundColor: lightPalette.contrast_300,
},
bg_contrast_400: {
backgroundColor: lightPalette.contrast_400,
},
bg_contrast_500: {
backgroundColor: lightPalette.contrast_500,
},
bg_contrast_600: {
backgroundColor: lightPalette.contrast_600,
},
bg_contrast_700: {
backgroundColor: lightPalette.contrast_700,
},
bg_contrast_800: {
backgroundColor: lightPalette.contrast_800,
},
bg_contrast_900: {
backgroundColor: lightPalette.contrast_900,
},
bg_contrast_950: {
backgroundColor: lightPalette.contrast_950,
},
bg_contrast_975: {
backgroundColor: lightPalette.contrast_975,
},
border_contrast_low: {
borderColor: lightPalette.contrast_100,
},
border_contrast_medium: {
borderColor: lightPalette.contrast_200,
},
border_contrast_high: {
borderColor: lightPalette.contrast_300,
},
shadow_sm: {
...atoms.shadow_sm,
shadowColor: lightPalette.black,
},
shadow_md: {
...atoms.shadow_md,
shadowColor: lightPalette.black,
},
shadow_lg: {
...atoms.shadow_lg,
shadowColor: lightPalette.black,
},
},
}
const dark: Theme = {
scheme: 'dark',
name: 'dark',
palette: darkPalette,
atoms: {
text: {
color: darkPalette.white,
},
text_contrast_low: {
color: darkPalette.contrast_400,
},
text_contrast_medium: {
color: darkPalette.contrast_600,
},
text_contrast_high: {
color: darkPalette.contrast_900,
},
text_inverted: {
color: darkPalette.black,
},
bg: {
backgroundColor: darkPalette.black,
},
bg_contrast_25: {
backgroundColor: darkPalette.contrast_25,
},
bg_contrast_50: {
backgroundColor: darkPalette.contrast_50,
},
bg_contrast_100: {
backgroundColor: darkPalette.contrast_100,
},
bg_contrast_200: {
backgroundColor: darkPalette.contrast_200,
},
bg_contrast_300: {
backgroundColor: darkPalette.contrast_300,
},
bg_contrast_400: {
backgroundColor: darkPalette.contrast_400,
},
bg_contrast_500: {
backgroundColor: darkPalette.contrast_500,
},
bg_contrast_600: {
backgroundColor: darkPalette.contrast_600,
},
bg_contrast_700: {
backgroundColor: darkPalette.contrast_700,
},
bg_contrast_800: {
backgroundColor: darkPalette.contrast_800,
},
bg_contrast_900: {
backgroundColor: darkPalette.contrast_900,
},
bg_contrast_950: {
backgroundColor: darkPalette.contrast_950,
},
bg_contrast_975: {
backgroundColor: darkPalette.contrast_975,
},
border_contrast_low: {
borderColor: darkPalette.contrast_100,
},
border_contrast_medium: {
borderColor: darkPalette.contrast_200,
},
border_contrast_high: {
borderColor: darkPalette.contrast_300,
},
shadow_sm: {
...atoms.shadow_sm,
shadowOpacity: 0.7,
shadowColor: color.trueBlack,
},
shadow_md: {
...atoms.shadow_md,
shadowOpacity: 0.7,
shadowColor: color.trueBlack,
},
shadow_lg: {
...atoms.shadow_lg,
shadowOpacity: 0.7,
shadowColor: color.trueBlack,
},
},
}
const dim: Theme = {
...dark,
scheme: 'dark',
name: 'dim',
palette: dimPalette,
atoms: {
...dark.atoms,
text: {
color: dimPalette.white,
},
text_contrast_low: {
color: dimPalette.contrast_400,
},
text_contrast_medium: {
color: dimPalette.contrast_600,
},
text_contrast_high: {
color: dimPalette.contrast_900,
},
text_inverted: {
color: dimPalette.black,
},
bg: {
backgroundColor: dimPalette.black,
},
bg_contrast_25: {
backgroundColor: dimPalette.contrast_25,
},
bg_contrast_50: {
backgroundColor: dimPalette.contrast_50,
},
bg_contrast_100: {
backgroundColor: dimPalette.contrast_100,
},
bg_contrast_200: {
backgroundColor: dimPalette.contrast_200,
},
bg_contrast_300: {
backgroundColor: dimPalette.contrast_300,
},
bg_contrast_400: {
backgroundColor: dimPalette.contrast_400,
},
bg_contrast_500: {
backgroundColor: dimPalette.contrast_500,
},
bg_contrast_600: {
backgroundColor: dimPalette.contrast_600,
},
bg_contrast_700: {
backgroundColor: dimPalette.contrast_700,
},
bg_contrast_800: {
backgroundColor: dimPalette.contrast_800,
},
bg_contrast_900: {
backgroundColor: dimPalette.contrast_900,
},
bg_contrast_950: {
backgroundColor: dimPalette.contrast_950,
},
bg_contrast_975: {
backgroundColor: dimPalette.contrast_975,
},
border_contrast_low: {
borderColor: dimPalette.contrast_100,
},
border_contrast_medium: {
borderColor: dimPalette.contrast_200,
},
border_contrast_high: {
borderColor: dimPalette.contrast_300,
},
shadow_sm: {
...atoms.shadow_sm,
shadowOpacity: 0.7,
shadowColor: `hsl(${hues.primary}, 28%, 6%)`,
},
shadow_md: {
...atoms.shadow_md,
shadowOpacity: 0.7,
shadowColor: `hsl(${hues.primary}, 28%, 6%)`,
},
shadow_lg: {
...atoms.shadow_lg,
shadowOpacity: 0.7,
shadowColor: `hsl(${hues.primary}, 28%, 6%)`,
},
},
}
return {
lightPalette,
darkPalette,
dimPalette,
light,
dark,
dim,
}
export const themes = {
lightPalette: DEFAULT_THEMES.light.palette,
darkPalette: DEFAULT_THEMES.dark.palette,
dimPalette: DEFAULT_THEMES.dim.palette,
light: DEFAULT_THEMES.light,
dark: DEFAULT_THEMES.dark,
dim: DEFAULT_THEMES.dim,
}
/**
* @deprecated use ALF and access palette from `useTheme()`
*/
export const lightPalette = DEFAULT_THEMES.light.palette
/**
* @deprecated use ALF and access palette from `useTheme()`
*/
export const darkPalette = DEFAULT_THEMES.dark.palette
/**
* @deprecated use ALF and access palette from `useTheme()`
*/
export const dimPalette = DEFAULT_THEMES.dim.palette
/**
* @deprecated use ALF and access theme from `useTheme()`
*/
export const light = DEFAULT_THEMES.light
/**
* @deprecated use ALF and access theme from `useTheme()`
*/
export const dark = DEFAULT_THEMES.dark
/**
* @deprecated use ALF and access theme from `useTheme()`
*/
export const dim = DEFAULT_THEMES.dim
+4 -55
View File
@@ -1,61 +1,10 @@
import {isAndroid} from '#/platform/detection'
import {tokens} from '@bsky.app/alf'
export const TRACKING = isAndroid ? 0.1 : 0
export * from '@bsky.app/alf/dist/tokens'
export const color = {
temp_purple: 'rgb(105 0 255)',
temp_purple_dark: 'rgb(83 0 202)',
} as const
export const space = {
_2xs: 2,
xs: 4,
sm: 8,
md: 12,
lg: 16,
xl: 20,
_2xl: 24,
_3xl: 28,
_4xl: 32,
_5xl: 40,
} as const
export const fontSize = {
_2xs: 10,
xs: 12,
sm: 14,
md: 16,
lg: 18,
xl: 20,
_2xl: 22,
_3xl: 26,
_4xl: 32,
_5xl: 40,
} as const
export const lineHeight = {
none: 1,
normal: 1.5,
relaxed: 1.625,
} as const
export const borderRadius = {
_2xs: 2,
xs: 4,
sm: 8,
md: 12,
lg: 16,
full: 999,
} as const
/**
* These correspond to Inter font files we actually load.
*/
export const fontWeight = {
normal: '400',
medium: '500',
bold: '600',
heavy: '800',
temp_purple: tokens.labelerColor.purple,
temp_purple_dark: tokens.labelerColor.purple_dark,
} as const
export const gradients = {
-164
View File
@@ -1,164 +0,0 @@
import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native'
export type TextStyleProp = {
style?: StyleProp<TextStyle>
}
export type ViewStyleProp = {
style?: StyleProp<ViewStyle>
}
export type ThemeName = 'light' | 'dim' | 'dark'
export type Palette = {
white: string
black: string
like: string
contrast_25: string
contrast_50: string
contrast_100: string
contrast_200: string
contrast_300: string
contrast_400: string
contrast_500: string
contrast_600: string
contrast_700: string
contrast_800: string
contrast_900: string
contrast_950: string
contrast_975: string
primary_25: string
primary_50: string
primary_100: string
primary_200: string
primary_300: string
primary_400: string
primary_500: string
primary_600: string
primary_700: string
primary_800: string
primary_900: string
primary_950: string
primary_975: string
positive_25: string
positive_50: string
positive_100: string
positive_200: string
positive_300: string
positive_400: string
positive_500: string
positive_600: string
positive_700: string
positive_800: string
positive_900: string
positive_950: string
positive_975: string
negative_25: string
negative_50: string
negative_100: string
negative_200: string
negative_300: string
negative_400: string
negative_500: string
negative_600: string
negative_700: string
negative_800: string
negative_900: string
negative_950: string
negative_975: string
}
export type ThemedAtoms = {
text: {
color: string
}
text_contrast_low: {
color: string
}
text_contrast_medium: {
color: string
}
text_contrast_high: {
color: string
}
text_inverted: {
color: string
}
bg: {
backgroundColor: string
}
bg_contrast_25: {
backgroundColor: string
}
bg_contrast_50: {
backgroundColor: string
}
bg_contrast_100: {
backgroundColor: string
}
bg_contrast_200: {
backgroundColor: string
}
bg_contrast_300: {
backgroundColor: string
}
bg_contrast_400: {
backgroundColor: string
}
bg_contrast_500: {
backgroundColor: string
}
bg_contrast_600: {
backgroundColor: string
}
bg_contrast_700: {
backgroundColor: string
}
bg_contrast_800: {
backgroundColor: string
}
bg_contrast_900: {
backgroundColor: string
}
bg_contrast_950: {
backgroundColor: string
}
bg_contrast_975: {
backgroundColor: string
}
border_contrast_low: {
borderColor: string
}
border_contrast_medium: {
borderColor: string
}
border_contrast_high: {
borderColor: string
}
shadow_sm: {
shadowRadius: number
shadowOpacity: number
elevation: number
shadowColor: string
}
shadow_md: {
shadowRadius: number
shadowOpacity: number
elevation: number
shadowColor: string
}
shadow_lg: {
shadowRadius: number
shadowOpacity: number
elevation: number
shadowColor: string
}
}
export type Theme = {
scheme: 'light' | 'dark' // for library support
name: ThemeName
palette: Palette
atoms: ThemedAtoms
}
-15
View File
@@ -8,21 +8,6 @@ import {isNative} from '#/platform/detection'
import {isIOS} from '#/platform/detection'
import {type Alf, applyFonts, atoms, flatten} from '#/alf'
/**
* Util to calculate lineHeight from a text size atom and a leading atom
*
* Example:
* `leading(atoms.text_md, atoms.leading_normal)` // => 24
*/
export function leading<
Size extends {fontSize?: number},
Leading extends {lineHeight?: number},
>(textSize: Size, leading: Leading) {
const size = textSize?.fontSize || atoms.text_md.fontSize
const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
return Math.round(size * lineHeight)
}
/**
* Ensures that `lineHeight` defaults to a relative value of `1`, or applies
* other relative leading atoms.
-8
View File
@@ -1,12 +1,7 @@
import {jest} from '@jest/globals'
import {logger} from '#/logger'
import {transparentifyColor} from '../colorGeneration'
jest.mock('#/logger', () => ({
logger: {warn: jest.fn()},
}))
describe('transparentifyColor', () => {
beforeEach(() => {
jest.clearAllMocks()
@@ -41,8 +36,5 @@ describe('transparentifyColor', () => {
const unsupported = 'blue'
const result = transparentifyColor(unsupported, 0.5)
expect(result).toBe(unsupported)
expect(logger.warn).toHaveBeenCalledWith(
`Could not make '${unsupported}' transparent`,
)
})
})
+3 -44
View File
@@ -1,49 +1,8 @@
import {logger} from '#/logger'
import {utils} from '@bsky.app/alf'
export const BLUE_HUE = 211
export const RED_HUE = 346
export const GREEN_HUE = 152
/**
* Smooth progression of lightness "stops" for generating HSL colors.
* @deprecated use `utils.alpha` from `@bsky.app/alf` instead
*/
export const COLOR_STOPS = [
0, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.85, 0.9, 0.95, 1,
]
export function generateScale(start: number, end: number) {
const range = end - start
return COLOR_STOPS.map(stop => {
return start + range * stop
})
}
export const defaultScale = generateScale(6, 100)
// dim shifted 6% lighter
export const dimScale = generateScale(12, 100)
export function transparentifyColor(color: string, alpha: number) {
if (color.startsWith('hsl(')) {
return 'hsla(' + color.slice('hsl('.length, -1) + `, ${alpha})`
} else if (color.startsWith('rgb(')) {
return 'rgba(' + color.slice('rgb('.length, -1) + `, ${alpha})`
} else if (color.startsWith('#')) {
if (color.length === 7) {
const alphaHex = Math.round(alpha * 255).toString(16)
// Per MDN: If there is only one number, it is duplicated: e means ee
// https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
return color.slice(0, 7) + alphaHex.padStart(2, alphaHex)
} else if (color.length === 4) {
// convert to 6-digit hex before adding alpha
const [r, g, b] = color.slice(1).split('')
const alphaHex = Math.round(alpha * 255).toString(16)
return `#${r.repeat(2)}${g.repeat(2)}${b.repeat(2)}${alphaHex.padStart(
2,
alphaHex,
)}`
}
} else {
logger.warn(`Could not make '${color}' transparent`)
}
return color
}
export const transparentifyColor = utils.alpha
+1 -62
View File
@@ -1,62 +1 @@
import {Platform} from 'react-native'
import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
/**
* Identity function on web. Returns nothing on other platforms.
*
* Note: Platform splitting does not tree-shake away the other platforms,
* so don't do stuff like e.g. rely on platform-specific imports. Use
* platform-split files instead.
*/
export function web(value: any) {
if (isWeb) {
return value
}
}
/**
* Identity function on iOS. Returns nothing on other platforms.
*
* Note: Platform splitting does not tree-shake away the other platforms,
* so don't do stuff like e.g. rely on platform-specific imports. Use
* platform-split files instead.
*/
export function ios(value: any) {
if (isIOS) {
return value
}
}
/**
* Identity function on Android. Returns nothing on other platforms..
*
* Note: Platform splitting does not tree-shake away the other platforms,
* so don't do stuff like e.g. rely on platform-specific imports. Use
* platform-split files instead.
*/
export function android(value: any) {
if (isAndroid) {
return value
}
}
/**
* Identity function on iOS and Android. Returns nothing on web.
*
* Note: Platform splitting does not tree-shake away the other platforms,
* so don't do stuff like e.g. rely on platform-specific imports. Use
* platform-split files instead.
*/
export function native(value: any) {
if (isNative) {
return value
}
}
/**
* Note: Platform splitting does not tree-shake away the other platforms,
* so don't do stuff like e.g. rely on platform-specific imports. Use
* platform-split files instead.
*/
export const platform = Platform.select
export {android, ios, native, platform, web} from '@bsky.app/alf'
+1 -1
View File
@@ -1,7 +1,7 @@
import * as SystemUI from 'expo-system-ui'
import {type Theme} from '@bsky.app/alf'
import {isAndroid} from '#/platform/detection'
import {type Theme} from '../types'
export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
if (isAndroid) {
+2 -13
View File
@@ -1,14 +1,3 @@
import {type ThemeName} from '#/alf/types'
import {utils} from '@bsky.app/alf'
export function select<T>(name: ThemeName, options: Record<ThemeName, T>) {
switch (name) {
case 'light':
return options.light
case 'dark':
return options.dark || options.dim
case 'dim':
return options.dim || options.dark
default:
throw new Error(`select(theme, options) received unknown theme ${name}`)
}
}
export const select = utils.select
+1 -1
View File
@@ -1,10 +1,10 @@
import React from 'react'
import {type ColorSchemeName, useColorScheme} from 'react-native'
import {type ThemeName} from '@bsky.app/alf'
import {isWeb} from '#/platform/detection'
import {useThemePrefs} from '#/state/shell'
import {dark, dim, light} from '#/alf/themes'
import {type ThemeName} from '#/alf/types'
export function useColorModeTheme(): ThemeName {
const theme = useThemeName()
+2 -2
View File
@@ -77,7 +77,7 @@ export function AccountList({
]}>
<Text
style={[
a.font_bold,
a.font_semi_bold,
a.flex_1,
a.flex_row,
a.py_sm,
@@ -151,7 +151,7 @@ function AccountItem({
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Text
emoji
style={[a.font_bold, a.leading_tight]}
style={[a.font_semi_bold, a.leading_tight]}
numberOfLines={1}>
{sanitizeDisplayName(
profile?.displayName || profile?.handle || account.handle,
+2 -3
View File
@@ -96,7 +96,7 @@ export function BlockedGeoOverlay() {
<Mark fill={t.palette.primary_600} width={14} />
<Text
style={[
a.font_bold,
a.font_semi_bold,
{
color: t.palette.primary_600,
},
@@ -121,8 +121,7 @@ export function BlockedGeoOverlay() {
</View>
<View style={[a.mt_xl, a.align_start]}>
<Text
style={[a.text_lg, a.font_heavy, a.leading_snug, a.pb_xs]}>
<Text style={[a.text_lg, a.font_bold, a.leading_snug, a.pb_xs]}>
<Trans>Not in Mississippi?</Trans>
</Text>
<Text
+5 -13
View File
@@ -240,7 +240,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
}
} else if (color === 'secondary') {
if (!disabled) {
baseStyles.push(t.atoms.bg_contrast_25)
baseStyles.push(t.atoms.bg_contrast_50)
hoverStyles.push(t.atoms.bg_contrast_100)
} else {
baseStyles.push(t.atoms.bg_contrast_50)
@@ -289,11 +289,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
})
} else {
baseStyles.push({
backgroundColor: select(t.name, {
light: t.palette.primary_25,
dim: t.palette.primary_50,
dark: t.palette.primary_50,
}),
backgroundColor: t.palette.primary_50,
})
}
} else if (color === 'negative_subtle') {
@@ -314,11 +310,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
})
} else {
baseStyles.push({
backgroundColor: select(t.name, {
light: t.palette.negative_25,
dim: t.palette.negative_50,
dark: t.palette.negative_50,
}),
backgroundColor: t.palette.negative_50,
})
}
}
@@ -372,7 +364,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: t.palette.contrast_25,
backgroundColor: t.palette.contrast_50,
})
}
}
@@ -396,7 +388,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: t.palette.contrast_25,
backgroundColor: t.palette.contrast_50,
})
}
}
+6 -2
View File
@@ -796,7 +796,7 @@ export function ItemText({children, style}: ItemTextProps) {
style={[
a.flex_1,
a.text_md,
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_high,
{paddingTop: 3},
style,
@@ -855,7 +855,11 @@ export function LabelText({children}: {children: React.ReactNode}) {
const t = useTheme()
return (
<Text
style={[a.font_bold, t.atoms.text_contrast_medium, {marginBottom: -8}]}>
style={[
a.font_semi_bold,
t.atoms.text_contrast_medium,
{marginBottom: -8},
]}>
{children}
</Text>
)
+1 -1
View File
@@ -62,7 +62,7 @@ export function HeaderText({
style?: StyleProp<TextStyle>
}) {
return (
<Text style={[a.text_lg, a.text_center, a.font_heavy, style]}>
<Text style={[a.text_lg, a.text_center, a.font_bold, style]}>
{children}
</Text>
)
+1 -1
View File
@@ -40,7 +40,7 @@ export function Error({
]}
sideBorders={sideBorders}>
<View style={[a.w_full, a.align_center, a.gap_lg]}>
<Text style={[a.font_bold, a.text_3xl]}>{title}</Text>
<Text style={[a.font_semi_bold, a.text_3xl]}>{title}</Text>
<Text
style={[
a.text_md,
+2 -2
View File
@@ -128,7 +128,7 @@ export function TitleAndByline({
<View style={[a.flex_1]}>
<Text
emoji
style={[a.text_md, a.font_bold, a.leading_snug]}
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
numberOfLines={1}>
{title}
</Text>
@@ -214,7 +214,7 @@ export function DescriptionPlaceholder() {
export function Likes({count}: {count: number}) {
const t = useTheme()
return (
<Text style={[a.text_sm, t.atoms.text_contrast_medium, a.font_bold]}>
<Text style={[a.text_sm, t.atoms.text_contrast_medium, a.font_semi_bold]}>
<Trans>
Liked by <Plural value={count || 0} one="# user" other="# users" />
</Trans>
+2 -2
View File
@@ -380,7 +380,7 @@ export function ProfileGrid({
a.justify_between,
]}
pointerEvents={isIOS ? 'auto' : 'box-none'}>
<Text style={[a.text_sm, a.font_bold, t.atoms.text]}>
<Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}>
{isFeedContext ? (
<Trans>Suggested for you</Trans>
) : (
@@ -516,7 +516,7 @@ export function SuggestedFeeds() {
style={[
a.flex_1,
a.text_lg,
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_medium,
]}>
<Trans>Some other feeds you might like</Trans>
+1 -1
View File
@@ -45,7 +45,7 @@ export function Avatar({avatar}: {avatar?: string}) {
export function Title({value}: {value: string}) {
return (
<Text emoji style={[a.text_md, a.font_bold, a.leading_tight]}>
<Text emoji style={[a.text_md, a.font_semi_bold, a.leading_tight]}>
{value}
</Text>
)
+1 -1
View File
@@ -181,7 +181,7 @@ export function TitleText({
<Text
style={[
a.text_lg,
a.font_heavy,
a.font_bold,
a.leading_tight,
isIOS && align === 'platform' && a.text_center,
gtMobile && a.text_xl,
-17
View File
@@ -23,11 +23,6 @@ import {
import {useDialogContext} from '#/components/Dialog'
import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const'
import {ScrollbarOffsetContext} from '#/components/Layout/context'
import {
List as BaseList,
type ListItem,
type ListProps,
} from '#/components/List'
export * from '#/components/Layout/const'
export * as Header from '#/components/Layout/Header'
@@ -228,15 +223,3 @@ const WebCenterBorders = memo(function LayoutWebCenterBorders() {
/>
) : null
})
export function List<Item extends ListItem>(props: ListProps<Item>) {
return (
<BaseList<Item>
{...props}
renderItem={item => {
return <Center>{props.renderItem!(item)}</Center>
}}
style={[a.h_full_vh, props.style]}
/>
)
}
@@ -1,72 +0,0 @@
import {createContext, useCallback, useContext, useMemo} from 'react'
import {type NativeScrollEvent} from 'react-native'
import {type ScrollHandlers} from 'react-native-reanimated'
export type NormalizedScrollHandlers = {
/**
* Web + Native — needs to be a `worklet` on native, but can be either on web
*/
onScroll?: ScrollHandlers<any>['onScroll']
/**
* Native only
*/
onScrollBeginDrag?: ScrollHandlers<any>['onBeginDrag']
/**
* Native only
*/
onScrollEndDrag?: ScrollHandlers<any>['onEndDrag']
/**
* Native only
*/
onMomentumScrollBegin?: ScrollHandlers<any>['onMomentumBegin']
/**
* Native only
*/
onMomentumScrollEnd?: ScrollHandlers<any>['onMomentumEnd']
}
const ListScrollContext = createContext<NormalizedScrollHandlers>({
onScroll: undefined,
onScrollBeginDrag: undefined,
onScrollEndDrag: undefined,
onMomentumScrollBegin: undefined,
onMomentumScrollEnd: undefined,
})
ListScrollContext.displayName = 'ListScrollContext'
export function ListScrollProvider({
children,
onScrollBeginDrag,
onScrollEndDrag,
onScroll,
onMomentumScrollBegin,
onMomentumScrollEnd,
}: {children: React.ReactNode} & NormalizedScrollHandlers) {
const handlers = useMemo(
() => ({
onScroll,
onScrollBeginDrag: onScrollBeginDrag,
onScrollEndDrag: onScrollEndDrag,
onMomentumScrollBegin: onMomentumScrollBegin,
onMomentumScrollEnd: onMomentumScrollEnd,
}),
[
onScrollBeginDrag,
onScrollEndDrag,
onScroll,
onMomentumScrollBegin,
onMomentumScrollEnd,
],
)
return (
<ListScrollContext.Provider value={handlers}>
{children}
</ListScrollContext.Provider>
)
}
export function useListScrollContext(): NormalizedScrollHandlers {
return useContext(ListScrollContext)
}
export const useListScrollHandler = useCallback<(e: NativeScrollEvent) => void>
-235
View File
@@ -1,235 +0,0 @@
import {forwardRef, useMemo} from 'react'
import {
type FlatList,
type FlatListProps,
RefreshControl,
type ViewToken,
} from 'react-native'
import Animated, {
type FlatListPropsWithLayout,
runOnJS,
useAnimatedScrollHandler,
useSharedValue,
} from 'react-native-reanimated'
import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video'
import {useDedupe} from '#/lib/hooks/useDedupe'
import {isIOS, isNative} from '#/platform/detection'
import {useLightbox} from '#/state/lightbox'
import {atoms as a, useTheme} from '#/alf'
import {useListScrollContext} from '#/components/List/ListScrollProvider'
export {
ListScrollProvider,
useListScrollHandler,
} from '#/components/List/ListScrollProvider'
export type ListRef<Item extends {key: string}> =
React.MutableRefObject<FlatList<Item> | null>
export type ListItem = {key: string}
export type ListProps<Item extends ListItem> = Omit<
FlatListProps<Item>,
| 'onScroll'
| 'onScrollBeginDrag'
| 'onScrollEndDrag'
| 'onMomentumScrollBegin'
| 'onMomentumScrollEnd'
| 'refreshControl'
| 'contentOffset'
> & {
/**
* @deprecated use `ListScrollProvider` handler instead
*/
onScroll?: FlatListProps<Item>['onScroll']
/**
* @deprecated use `ListScrollProvider` handler instead
*/
onScrollBeginDrag?: FlatListProps<Item>['onScrollBeginDrag']
/**
* @deprecated use `ListScrollProvider` handler instead
*/
onScrollEndDrag?: FlatListProps<Item>['onScrollEndDrag']
/**
* @deprecated use `ListScrollProvider` handler instead
*/
onMomentumScrollBegin?: FlatListProps<Item>['onMomentumScrollBegin']
/**
* @deprecated use `ListScrollProvider` handler instead
*/
onMomentumScrollEnd?: FlatListProps<Item>['onMomentumScrollEnd']
/**
* @deprecated pass `refreshing` and `onRefresh` instead to enable
*/
refreshControl?: FlatListProps<Item>['refreshControl']
/**
* @deprecated use `headerOffset` instead
*/
contentOffset?: FlatListProps<Item>['contentOffset']
/**
* Wrapper around `onViewableItemsChanged` that calls back with individual
* items IF they `item.isViewable` is true.
*/
onItemSeen?: (item: Item) => void
/**
* Sugar for adding padding to the top of the list to accommodate fixed
* headers. Also applies insets to the scroll indicators.
*/
headerOffset?: number
/**
* Sugar for adding padding to the bottom of the list to accommodate fixed
* footers. Also applies insets to the scroll indicators.
*/
footerOffset?: number
/**
* Configures the point at which `onScrolledDownChange` is called.
*/
didScrollDownThreshold?: number
onScrolledDownChange?: (isScrolledDown: boolean) => void
}
export const List = forwardRef(function List<Item extends ListItem>(
{...props}: ListProps<Item>,
ref: React.Ref<FlatList<Item>>,
) {
const t = useTheme()
const {activeLightbox} = useLightbox()
const debounce400 = useDedupe(400)
const isScrolledDown = useSharedValue(false)
const scrollHandlers = useListScrollContext()
const onScroll = useAnimatedScrollHandler({
onScroll(e, ctx) {
scrollHandlers.onScroll?.(e, ctx)
const didScrollDown =
e.contentOffset.y > (props.didScrollDownThreshold ?? 200)
if (isScrolledDown.get() !== didScrollDown) {
isScrolledDown.set(didScrollDown)
if (props.onScrolledDownChange) {
runOnJS(props.onScrolledDownChange)(didScrollDown)
}
}
if (isIOS) runOnJS(debounce400)(updateActiveVideoViewAsync)
},
onBeginDrag(e, ctx) {
scrollHandlers.onScrollBeginDrag?.(e, ctx)
},
onEndDrag(e, ctx) {
scrollHandlers.onScrollEndDrag?.(e, ctx)
if (isNative) runOnJS(updateActiveVideoViewAsync)()
},
/*
* Note: adding onMomentumBegin here makes simulator scroll lag on Android.
* So either don't add it, or figure out why. - sfn
* TODO
*/
onMomentumBegin(e, ctx) {
scrollHandlers.onMomentumScrollBegin?.(e, ctx)
},
onMomentumEnd(e, ctx) {
scrollHandlers.onMomentumScrollEnd?.(e, ctx)
if (isNative) runOnJS(updateActiveVideoViewAsync)()
},
})
const [onViewableItemsChanged, viewabilityConfig] = useMemo(() => {
const onItemSeen = props.onItemSeen
if (!onItemSeen) return [undefined, undefined]
return [
(info: {
viewableItems: Array<ViewToken<Item>>
changed: Array<ViewToken<Item>>
}) => {
for (const item of info.changed) {
if (item.isViewable) {
onItemSeen(item.item)
}
}
},
{
itemVisiblePercentThreshold: 40,
minimumViewTime: 0.5e3,
},
]
}, [props.onItemSeen])
let refreshControl
if (props.refreshing !== undefined || props.onRefresh !== undefined) {
refreshControl = (
<RefreshControl
key={t.atoms.text.color}
refreshing={props.refreshing ?? false}
onRefresh={props.onRefresh ?? undefined}
tintColor={t.atoms.text.color}
titleColor={t.atoms.text.color}
progressViewOffset={props.progressViewOffset ?? props.headerOffset}
/>
)
}
/**
* Web only, provides a handle on the resulting `List` DOM element, which we
* then use to apply sticky styles to individual list item wrappers.
*/
const dataSet = props.stickyHeaderIndices?.reduce((ds, i) => {
return {...ds, [`sticky-header-index-${i}`]: 1}
}, {})
return (
<>
<Animated.FlatList
// @ts-ignore
dataSet={dataSet}
ref={ref}
keyExtractor={props.keyExtractor || (i => i.key)}
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={onViewableItemsChanged}
/**
* iOS automatically adds in the safe area to the scroll indicator
* insets, even though the overwhelming majority of our ScrollViews do
* not stretch from edge to edge.
* @see https://github.com/bluesky-social/social-app/pull/7131
*/
automaticallyAdjustsScrollIndicatorInsets={false}
/**
* For better UX, we default to true, but it can be disabled if needed.
* @see https://github.com/bluesky-social/social-app/pull/8529
*/
showsVerticalScrollIndicator
indicatorStyle={t.name === 'light' ? 'black' : 'white'}
scrollIndicatorInsets={{
top: props.headerOffset ?? 0,
bottom: props.footerOffset ?? 0,
/**
* May fix a bug where the scroll indicator is in the middle of the screen
* @see https://github.com/facebook/react-native/issues/26610
*/
right: 1,
}}
/**
* Native only. On web, we use padding on `style` instead.
*/
contentOffset={
props.headerOffset ? {x: 0, y: props.headerOffset * -1} : undefined
}
scrollsToTop={!activeLightbox}
refreshControl={refreshControl}
{...(props as FlatListPropsWithLayout<Item>)}
style={[
a.h_full,
{
paddingTop: props.headerOffset,
paddingBottom: props.footerOffset,
transform: 'unset',
},
props.style,
]}
onScroll={onScroll}
/>
</>
)
}) as <Item extends {key: string}>(
props: ListProps<Item> & {ref?: React.Ref<FlatList<Item>>},
) => React.ReactElement
+2 -2
View File
@@ -127,7 +127,7 @@ export function TitleAndByline({
allowOverride={creator && currentAccount?.did === creator.did}>
<Hider.Mask>
<Text
style={[a.text_md, a.font_bold, a.leading_snug, a.italic]}
style={[a.text_md, a.font_semi_bold, a.leading_snug, a.italic]}
numberOfLines={1}>
<Trans>Hidden list</Trans>
</Text>
@@ -135,7 +135,7 @@ export function TitleAndByline({
<Hider.Content>
<Text
emoji
style={[a.text_md, a.font_bold, a.leading_snug]}
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
numberOfLines={1}>
{title}
</Text>
+1 -1
View File
@@ -50,7 +50,7 @@ export function LoggedOutCTA({style, gateName}: LoggedOutCTAProps) {
<View style={[a.flex_row, a.align_center, a.flex_1, a.pr_md]}>
<Logo width={30} style={[a.mr_md]} />
<View style={[a.flex_1]}>
<Text style={[a.text_lg, a.font_bold, a.leading_snug]}>
<Text style={[a.text_lg, a.font_semi_bold, a.leading_snug]}>
<Trans>Join Bluesky</Trans>
</Text>
<Text
+2 -2
View File
@@ -191,7 +191,7 @@ export function ItemText({children, style}: ItemTextProps) {
style={[
a.flex_1,
a.text_md,
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_high,
{paddingTop: 3},
style,
@@ -293,7 +293,7 @@ export function LabelText({
return (
<Text
style={[
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_medium,
{marginBottom: -8},
style,
+2 -2
View File
@@ -292,7 +292,7 @@ export function ItemText({children, style}: ItemTextProps) {
<Text
style={[
a.flex_1,
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_high,
style,
disabled && t.atoms.text_contrast_low,
@@ -372,7 +372,7 @@ export function LabelText({
return (
<Text
style={[
a.font_bold,
a.font_semi_bold,
a.p_sm,
t.atoms.text_contrast_low,
a.leading_snug,
+1 -1
View File
@@ -135,7 +135,7 @@ function DialogInner({
style={[a.absolute, a.inset_0]}
/>
</View>
<Text style={[a.font_bold, a.text_xl]}>
<Text style={[a.font_semi_bold, a.text_xl]}>
{isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>}
</Text>
</View>
+1 -1
View File
@@ -133,7 +133,7 @@ export function Label({
emoji
style={[
text,
a.font_bold,
a.font_semi_bold,
a.leading_tight,
t.atoms.text_contrast_medium,
{paddingRight: 3},
+1 -1
View File
@@ -25,7 +25,7 @@ export function Badge() {
<Logo fill={t.palette.primary_600} width={14} />
<Text
style={[
a.font_bold,
a.font_semi_bold,
{
color: t.palette.primary_600,
},
@@ -69,7 +69,7 @@ export function Content({state}: {state: PolicyUpdateState}) {
{screenReaderEnabled ? (
<View style={[a.gap_sm]}>
<Text emoji style={[a.text_2xl, a.font_bold, a.leading_snug]}>
<Text emoji style={[a.text_2xl, a.font_semi_bold, a.leading_snug]}>
<Trans>Hey there 👋</Trans>
</Text>
<Text style={[a.leading_snug, a.text_md]}>
@@ -115,7 +115,7 @@ export function Content({state}: {state: PolicyUpdateState}) {
</View>
) : (
<View style={[a.gap_sm]}>
<Text emoji style={[a.text_2xl, a.font_bold, a.leading_snug]}>
<Text emoji style={[a.text_2xl, a.font_semi_bold, a.leading_snug]}>
<Trans>Hey there 👋</Trans>
</Text>
<Text style={[a.leading_snug, a.text_md]}>
@@ -126,7 +126,7 @@ export const ExternalEmbed = ({
<Text
emoji
numberOfLines={3}
style={[a.text_md, a.font_bold, a.leading_snug]}>
style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
{link.title || link.uri}
</Text>
)}
@@ -54,7 +54,7 @@ export function TimeIndicator({
<Text
style={[
{color: t.palette.white, fontSize: 12, fontVariant: ['tabular-nums']},
a.font_bold,
a.font_semi_bold,
{lineHeight: 1.25},
]}>
{`${minutes}:${seconds}`}
@@ -128,7 +128,12 @@ export function PostControlButtonText({style, ...props}: TextProps) {
return (
<Text
style={[color, big ? a.text_md : a.text_sm, active && a.font_bold, style]}
style={[
color,
big ? a.text_md : a.text_sm,
active && a.font_semi_bold,
style,
]}
{...props}
/>
)
+2 -2
View File
@@ -157,7 +157,7 @@ let RepostButtonDialogInner = ({
variant="ghost"
color="primary">
<Repost size="lg" fill={t.palette.primary_500} />
<Text style={[a.font_bold, a.text_xl]}>
<Text style={[a.font_semi_bold, a.text_xl]}>
{isReposted ? (
<Trans>Remove repost</Trans>
) : (
@@ -188,7 +188,7 @@ let RepostButtonDialogInner = ({
/>
<Text
style={[
a.font_bold,
a.font_semi_bold,
a.text_xl,
embeddingDisabled && t.atoms.text_contrast_low,
]}>
@@ -203,7 +203,7 @@ function NoConvos() {
a.text_sm,
t.atoms.text_contrast_high,
a.text_center,
a.font_bold,
a.font_semi_bold,
]}>
<Trans>Start a conversation, and it will appear here.</Trans>
</Text>
+2 -2
View File
@@ -233,7 +233,7 @@ function InlineNameAndHandle({
<Text
emoji
style={[
a.font_bold,
a.font_semi_bold,
a.leading_tight,
a.flex_shrink_0,
{maxWidth: '70%'},
@@ -287,7 +287,7 @@ export function Name({
emoji
style={[
a.text_md,
a.font_bold,
a.font_semi_bold,
a.leading_snug,
a.self_start,
a.flex_shrink,
@@ -515,7 +515,7 @@ function Inner({
<View style={[a.flex_row, a.align_center, a.pt_md, a.pb_xs]}>
<Text
numberOfLines={1}
style={[a.text_lg, a.font_bold, a.self_start]}>
style={[a.text_lg, a.font_semi_bold, a.self_start]}>
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -562,7 +562,7 @@ function Inner({
label={`${followers} ${pluralizedFollowers}`}
style={[t.atoms.text]}
onPress={hide}>
<Text style={[a.text_md, a.font_bold]}>{followers} </Text>
<Text style={[a.text_md, a.font_semi_bold]}>{followers} </Text>
<Text style={[t.atoms.text_contrast_medium]}>
{pluralizedFollowers}
</Text>
@@ -572,7 +572,7 @@ function Inner({
label={_(msg`${following} following`)}
style={[t.atoms.text]}
onPress={hide}>
<Text style={[a.text_md, a.font_bold]}>{following} </Text>
<Text style={[a.text_md, a.font_semi_bold]}>{following} </Text>
<Text style={[t.atoms.text_contrast_medium]}>
{pluralizedFollowings}
</Text>
@@ -360,7 +360,7 @@ function HeaderTop({guide}: {guide: Follow10ProgressGuide}) {
style={[
a.z_10,
a.text_lg,
a.font_heavy,
a.font_bold,
a.leading_tight,
t.atoms.text_contrast_high,
]}>
+1 -1
View File
@@ -28,7 +28,7 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
<Text
style={[
t.atoms.text_contrast_medium,
a.font_bold,
a.font_semi_bold,
a.text_sm,
{textTransform: 'uppercase'},
]}>
+1 -1
View File
@@ -39,7 +39,7 @@ export function ProgressGuideTask({
<Text
style={[
a.text_sm,
a.font_bold,
a.font_semi_bold,
a.leading_tight,
tabularNumsTitle && {fontVariant: ['tabular-nums']},
]}>
+1 -1
View File
@@ -160,7 +160,7 @@ export const ProgressGuideToast = React.forwardRef<
ref={animatedCheckRef}
/>
<View>
<Text style={[a.text_md, a.font_bold]}>{title}</Text>
<Text style={[a.text_md, a.font_semi_bold]}>{title}</Text>
{subtitle && (
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
{subtitle}
+1 -1
View File
@@ -77,7 +77,7 @@ export function TitleText({
style={[
a.flex_1,
a.text_2xl,
a.font_bold,
a.font_semi_bold,
a.pb_sm,
a.leading_snug,
style,
@@ -24,7 +24,7 @@ export function SelectLabelerView({
return (
<View style={[a.gap_lg]}>
<View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Text style={[a.text_2xl, a.font_semi_bold]}>
<Trans>Select moderator</Trans>
</Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
@@ -75,7 +75,8 @@ function LabelerButton({
handle: labeler.creator.handle,
})}
/>
<Text style={[t.atoms.text_contrast_medium, a.text_sm, a.font_bold]}>
<Text
style={[t.atoms.text_contrast_medium, a.text_sm, a.font_semi_bold]}>
@{labeler.creator.handle}
</Text>
</LabelingServiceCard.Content>
@@ -85,7 +85,7 @@ export function SelectReportOptionView(props: {
) : null}
<View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
<Text style={[a.text_2xl, a.font_semi_bold]}>{i18n.title}</Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
{i18n.description}
</Text>
@@ -173,7 +173,8 @@ function ReportOptionButton({
interacted && t.atoms.bg_contrast_50,
]}>
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
<Text
style={[a.text_md, a.font_semi_bold, t.atoms.text_contrast_medium]}>
{title}
</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text>
+1 -1
View File
@@ -125,7 +125,7 @@ export function SubmitView({
t.atoms.border_contrast_low,
]}>
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.text_md, a.font_bold]}>
<Text style={[a.text_md, a.font_semi_bold]}>
{selectedReportOption.title}
</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>
+7 -1
View File
@@ -34,7 +34,13 @@ export function SearchError({
{maxWidth: gtMobile ? 394 : 294},
gtMobile ? a.gap_md : a.gap_sm,
]}>
<Text style={[a.font_bold, a.text_lg, a.text_center, a.leading_snug]}>
<Text
style={[
a.font_semi_bold,
a.text_lg,
a.text_center,
a.leading_snug,
]}>
{title}
</Text>
{children}
+1 -1
View File
@@ -280,7 +280,7 @@ export function ItemText({children}: ItemTextProps) {
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text
return (
<View style={[a.flex_1, a.py_md, a.border_b, t.atoms.border_contrast_low]}>
<Text style={[a.text_md, selected && a.font_bold]}>{children}</Text>
<Text style={[a.text_md, selected && a.font_semi_bold]}>{children}</Text>
</View>
)
}
+1 -1
View File
@@ -259,7 +259,7 @@ export function Item({ref, value, style, children}: ItemProps) {
a.text_sm,
{outline: 0},
(hovered || focused) && {backgroundColor: t.palette.primary_50},
selected && [a.font_bold],
selected && [a.font_semi_bold],
a.transition_color,
style,
])}>
@@ -255,7 +255,7 @@ function Empty() {
{marginTop: a.border.borderWidth},
]}>
<View style={[a.gap_xs]}>
<Text style={[a.font_bold, a.text_lg, {color: 'white'}]}>
<Text style={[a.font_semi_bold, a.text_lg, {color: 'white'}]}>
<Trans>You haven't created a starter pack yet!</Trans>
</Text>
<Text style={[a.text_md, {color: 'white'}]}>
+8 -3
View File
@@ -54,14 +54,19 @@ export function QrCode({
]}>
<View style={[a.gap_sm]}>
<Text
style={[a.font_bold, a.text_3xl, a.text_center, {color: 'white'}]}>
style={[
a.font_semi_bold,
a.text_3xl,
a.text_center,
{color: 'white'},
]}>
{record.name}
</Text>
</View>
<View style={[a.gap_xl, a.align_center]}>
<Text
style={[
a.font_bold,
a.font_semi_bold,
a.text_center,
{color: 'white', fontSize: 18},
]}>
@@ -76,7 +81,7 @@ export function QrCode({
a.flex,
a.flex_row,
a.align_center,
a.font_bold,
a.font_semi_bold,
{color: 'white', fontSize: 18, gap: 6},
]}>
<Trans>
+1 -1
View File
@@ -77,7 +77,7 @@ function ShareDialogInner({
) : (
<View style={[!gtMobile && a.gap_lg]}>
<View style={[a.gap_sm, gtMobile && a.pb_lg]}>
<Text style={[a.font_bold, a.text_2xl]}>
<Text style={[a.font_semi_bold, a.text_2xl]}>
<Trans>Invite people to this starter pack!</Trans>
</Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
@@ -77,7 +77,7 @@ export function Card({
<View style={[a.flex_1]}>
<Text
emoji
style={[a.text_md, a.font_bold, a.leading_snug]}
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
numberOfLines={2}>
{record.name}
</Text>
@@ -97,7 +97,7 @@ export function Card({
</Text>
) : null}
{!!joinedAllTimeCount && joinedAllTimeCount >= 50 && (
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
<Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
<Trans comment="Number of users (always at least 50) who have joined Bluesky using a specific starter pack">
<Plural value={joinedAllTimeCount} other="# users have" /> joined!
</Trans>
@@ -105,7 +105,7 @@ export function WizardEditListDialog({
: [a.pb_sm, a.align_end],
]}>
<View style={{width: 60}} />
<Text style={[a.font_bold, a.text_xl]}>
<Text style={[a.font_semi_bold, a.text_xl]}>
{state.currentStep === 'Profiles' ? (
<Trans>Edit People</Trans>
) : (
@@ -83,7 +83,7 @@ function WizardListCard({
emoji
style={[
a.flex_1,
a.font_bold,
a.font_semi_bold,
a.text_md,
a.leading_tight,
a.self_start,
+1 -1
View File
@@ -11,7 +11,7 @@ export function SubtleWebHover({
if (isTouchDevice) {
return null
}
let opacity: number
let opacity = 0.5
switch (t.name) {
case 'dark':
opacity = 0.4
+1 -1
View File
@@ -90,7 +90,7 @@ export function TrendingTopic({
<Text
style={[
a.font_bold,
a.font_semi_bold,
a.leading_tight,
isSmall ? [a.text_sm] : [a.text_md, {paddingBottom: 1}],
]}
+9 -3
View File
@@ -227,7 +227,8 @@ export function VideoPostCard({
{likeCount > 0 && (
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Heart size="sm" fill="white" />
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
<Text
style={[a.text_sm, a.font_semi_bold, {color: 'white'}]}>
{formatCount(i18n, likeCount)}
</Text>
</View>
@@ -235,7 +236,8 @@ export function VideoPostCard({
{repostCount > 0 && (
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Repost size="sm" fill="white" />
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
<Text
style={[a.text_sm, a.font_semi_bold, {color: 'white'}]}>
{formatCount(i18n, repostCount)}
</Text>
</View>
@@ -520,7 +522,11 @@ export function CompactVideoPostCard({
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Heart size="sm" fill="white" />
<Text
style={[a.text_sm, a.font_bold, {color: 'white'}]}>
style={[
a.text_sm,
a.font_semi_bold,
{color: 'white'},
]}>
{formatCount(i18n, likeCount)}
</Text>
</View>
+2 -2
View File
@@ -108,7 +108,7 @@ export function WelcomeModal({control}: WelcomeModalProps) {
<Text
style={[
a.text_2xl,
a.font_bold,
a.font_semi_bold,
a.user_select_none,
{color: '#354358', letterSpacing: -0.5},
]}>
@@ -127,7 +127,7 @@ export function WelcomeModal({control}: WelcomeModalProps) {
<Text
style={[
gtMobile ? a.text_4xl : a.text_3xl,
a.font_bold,
a.font_semi_bold,
a.text_center,
{color: '#354358'},
web({
+1 -1
View File
@@ -188,7 +188,7 @@ function WhoCanReplyDialog({
label={_(msg`Dialog: adjust who can interact with this post`)}
style={web({maxWidth: 400})}>
<View style={[a.gap_sm]}>
<Text style={[a.font_bold, a.text_xl, a.pb_sm]}>
<Text style={[a.font_semi_bold, a.text_xl, a.pb_sm]}>
<Trans>Who can interact with this post?</Trans>
</Text>
<Rules
@@ -219,7 +219,7 @@ function DialogInner({
label={_(msg`Get notified of new posts from ${name}`)}>
<View style={[a.gap_lg]}>
<View style={[a.gap_xs]}>
<Text style={[a.font_heavy, a.text_2xl]}>
<Text style={[a.font_bold, a.text_2xl]}>
<Trans>Keep me posted</Trans>
</Text>
<Text style={[t.atoms.text_contrast_medium, a.text_md]}>
@@ -82,13 +82,13 @@ function Inner({
</View>
<View style={[a.flex_1, a.gap_xs, a.pr_4xl]}>
<Text style={[a.text_sm, a.leading_snug]}>{children}</Text>
<Text style={[a.text_sm, a.leading_snug, a.font_bold]}>
<Text style={[a.text_sm, a.leading_snug, a.font_semi_bold]}>
<Trans>
Learn more in your{' '}
<InlineLinkText
label={_(msg`Go to account settings`)}
to={'/settings/account'}
style={[a.text_sm, a.leading_snug, a.font_bold]}
style={[a.text_sm, a.leading_snug, a.font_semi_bold]}
onPress={() => {
logger.metric('ageAssurance:navigateToSettings', {})
}}>
@@ -89,7 +89,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) {
<AgeAssuranceBadge />
</View>
<Text style={[a.text_2xl, a.font_heavy, a.pt_md, a.leading_tight]}>
<Text style={[a.text_2xl, a.font_bold, a.pt_md, a.leading_tight]}>
<Trans>Contact us</Trans>
</Text>
@@ -29,7 +29,7 @@ export function AgeAssuranceBadge() {
<Shield size="sm" />
<Text
style={[
a.font_bold,
a.font_semi_bold,
a.leading_snug,
{
color: select(t.name, {
@@ -178,7 +178,7 @@ function Inner() {
<View style={[a.align_start]}>
<AgeAssuranceBadge />
<Text style={[a.text_xl, a.font_heavy, a.pt_xl, a.pb_md]}>
<Text style={[a.text_xl, a.font_bold, a.pt_xl, a.pb_md]}>
{success ? <Trans>Success!</Trans> : <Trans>Verify your age</Trans>}
</Text>
@@ -160,7 +160,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
a.pb_md,
]}>
<SuccessIcon size="sm" fill={t.palette.positive_600} />
<Text style={[a.text_xl, a.font_heavy]}>
<Text style={[a.text_xl, a.font_bold]}>
<Trans>Success</Trans>
</Text>
</View>
@@ -209,7 +209,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
]}>
{error && <ErrorIcon size="md" fill={t.palette.negative_500} />}
<Text style={[a.text_xl, a.font_heavy]}>
<Text style={[a.text_xl, a.font_bold]}>
{error ? <Trans>Connection issue</Trans> : <Trans>Verifying</Trans>}
</Text>
@@ -62,7 +62,7 @@ export function AgeRestrictedScreen({
</View>
<View style={[a.gap_sm, a.pb_lg]}>
<Text style={[a.text_xl, a.leading_snug, a.font_heavy]}>
<Text style={[a.text_xl, a.leading_snug, a.font_bold]}>
<Trans>
You must complete age assurance in order to access this screen.
</Trans>
+1 -1
View File
@@ -38,7 +38,7 @@ export function BirthDateSettingsDialog({
label={_(msg`My Birthday`)}
style={web({maxWidth: 400})}>
<View style={[a.gap_sm]}>
<Text style={[a.text_xl, a.font_bold]}>
<Text style={[a.text_xl, a.font_semi_bold]}>
<Trans>My Birthday</Trans>
</Text>
<Text style={[a.leading_snug, t.atoms.text_contrast_medium]}>
@@ -108,7 +108,7 @@ function DeviceLocationRequestDialogInner({onLocationAcquired}: Props) {
return (
<View style={[a.gap_md]}>
<Text style={[a.text_xl, a.font_heavy]}>
<Text style={[a.text_xl, a.font_bold]}>
<Trans>Confirm your location</Trans>
</Text>
<View style={[a.gap_sm, a.pb_xs]}>
@@ -153,7 +153,7 @@ export function Disable() {
return (
<View style={[a.gap_sm]}>
<Text style={[a.text_xl, a.font_heavy, a.leading_snug]}>
<Text style={[a.text_xl, a.font_bold, a.leading_snug]}>
<Trans>Disable email 2FA</Trans>
</Text>
@@ -163,7 +163,7 @@ export function Disable() {
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
<Trans>
To disable your email 2FA method, please verify your access to{' '}
<Span style={[a.font_bold]}>{currentAccount?.email}</Span>
<Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
</Trans>
</Text>
@@ -214,7 +214,7 @@ export function Disable() {
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
<Trans>
To disable your email 2FA method, please verify your access to{' '}
<Span style={[a.font_bold]}>{currentAccount?.email}</Span>
<Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
</Trans>
</Text>
@@ -88,7 +88,7 @@ export function Enable() {
return (
<View style={[a.gap_lg]}>
<View style={[a.gap_sm]}>
<Text style={[a.text_xl, a.font_heavy, a.leading_snug]}>
<Text style={[a.text_xl, a.font_bold, a.leading_snug]}>
<Trans>Enable email 2FA</Trans>
</Text>
@@ -199,7 +199,7 @@ export function Update(_props: ScreenProps<ScreenID.Update>) {
return (
<View style={[a.gap_lg]}>
<Text style={[a.text_xl, a.font_heavy]}>
<Text style={[a.text_xl, a.font_bold]}>
<Trans>Update your email</Trans>
</Text>
@@ -239,7 +239,7 @@ export function Update(_props: ScreenProps<ScreenID.Update>) {
<>
<Divider />
<View>
<Text style={[a.text_md, a.pb_sm, a.font_bold]}>
<Text style={[a.text_md, a.pb_sm, a.font_semi_bold]}>
<Trans>Security step required</Trans>
</Text>
<Text
@@ -282,7 +282,7 @@ export function Update(_props: ScreenProps<ScreenID.Update>) {
<View style={[a.gap_sm]}>
<View style={[a.flex_row, a.gap_sm, a.align_center]}>
<Check fill={t.palette.positive_600} size="xs" />
<Text style={[a.text_md, a.font_heavy]}>
<Text style={[a.text_md, a.font_bold]}>
<Trans>Success!</Trans>
</Text>
</View>
@@ -58,7 +58,7 @@ export function VerificationReminder({
<View style={[a.mb_xs, {height: 150 - dialogPadding}]} />
<View style={[a.gap_sm]}>
<Text style={[a.text_xl, a.font_heavy]}>
<Text style={[a.text_xl, a.font_bold]}>
<Trans>Please verify your email</Trans>
</Text>
<Text style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
@@ -174,7 +174,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
return (
<View style={[a.gap_lg]}>
<View style={[a.gap_sm]}>
<Text style={[a.text_xl, a.font_heavy]}>
<Text style={[a.text_xl, a.font_bold]}>
<Span style={{top: 1}}>
<Check size="sm" fill={t.palette.positive_600} />
</Span>
@@ -197,7 +197,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
return (
<View style={[a.gap_lg]}>
<View style={[a.gap_sm]}>
<Text style={[a.text_xl, a.font_heavy]}>
<Text style={[a.text_xl, a.font_bold]}>
{state.step === 'email' ? (
state.mutationStatus === 'success' ? (
<>
@@ -239,7 +239,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
state.mutationStatus === 'success' ? (
<Trans>
We sent an email to{' '}
<Span style={[a.font_bold, t.atoms.text]}>
<Span style={[a.font_semi_bold, t.atoms.text]}>
{currentAccount!.email}
</Span>{' '}
containing a link. Please click on it to complete the email
@@ -248,7 +248,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
) : (
<Trans>
We'll send an email to{' '}
<Span style={[a.font_bold, t.atoms.text]}>
<Span style={[a.font_semi_bold, t.atoms.text]}>
{currentAccount!.email}
</Span>{' '}
containing a link. Please click on it to complete the email
@@ -258,7 +258,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
) : (
<Trans>
Please enter the code we sent to{' '}
<Span style={[a.font_bold, t.atoms.text]}>
<Span style={[a.font_semi_bold, t.atoms.text]}>
{currentAccount!.email}
</Span>{' '}
below.
+2 -2
View File
@@ -104,7 +104,7 @@ function EmbedDialogInner({
<Dialog.Inner label={_(msg`Embed post`)} style={[{maxWidth: 500}]}>
<View style={[a.gap_lg]}>
<View style={[a.gap_sm]}>
<Text style={[a.text_2xl, a.font_heavy]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Trans>Embed post</Trans>
</Text>
<Text
@@ -147,7 +147,7 @@ function EmbedDialogInner({
{showCustomisation && (
<View style={[a.gap_sm, a.p_md]}>
<Text style={[t.atoms.text_contrast_medium, a.font_bold]}>
<Text style={[t.atoms.text_contrast_medium, a.font_semi_bold]}>
<Trans>Color theme</Trans>
</Text>
<ToggleButton.Group
+1 -1
View File
@@ -54,7 +54,7 @@ export function EmbedConsentDialog({
label={_(msg`External Media`)}
style={[gtMobile ? {width: 'auto', maxWidth: 400} : a.w_full]}>
<View style={a.gap_sm}>
<Text style={[a.text_2xl, a.font_bold]}>
<Text style={[a.text_2xl, a.font_semi_bold]}>
<Trans>External Media</Trans>
</Text>
@@ -62,7 +62,7 @@ function InAppBrowserConsentInner({href}: {href?: string}) {
<Dialog.ScrollableInner label={_(msg`How should we open this link?`)}>
<View style={[a.gap_2xl]}>
<View style={[a.gap_sm]}>
<Text style={[a.font_heavy, a.text_2xl]}>
<Text style={[a.font_bold, a.text_2xl]}>
<Trans>How should we open this link?</Trans>
</Text>
<Text style={[t.atoms.text_contrast_high, a.leading_snug, a.text_md]}>
+3 -2
View File
@@ -68,7 +68,7 @@ function InAppBrowserConsentInner({
}>
<View style={[a.gap_2xl]}>
<View style={[a.gap_sm]}>
<Text style={[a.font_heavy, a.text_2xl]}>
<Text style={[a.font_bold, a.text_2xl]}>
{potentiallyMisleading ? (
<Trans>Potentially misleading link</Trans>
) : (
@@ -151,7 +151,8 @@ function LinkBox({href}: {href: string}) {
]}>
<Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
{scheme}
<Text style={[a.text_md, a.leading_snug, t.atoms.text, a.font_bold]}>
<Text
style={[a.text_md, a.leading_snug, t.atoms.text, a.font_semi_bold]}>
{hostname}
</Text>
{rest}
+15 -8
View File
@@ -108,7 +108,12 @@ function MutedWordsInner() {
<Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}>
<View>
<Text
style={[a.text_md, a.font_bold, a.pb_sm, t.atoms.text_contrast_high]}>
style={[
a.text_md,
a.font_semi_bold,
a.pb_sm,
t.atoms.text_contrast_high,
]}>
<Trans>Add muted words and tags</Trans>
</Text>
<Text style={[a.pb_lg, a.leading_snug, t.atoms.text_contrast_medium]}>
@@ -147,7 +152,7 @@ function MutedWordsInner() {
style={[
a.pb_xs,
a.text_sm,
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_medium,
]}>
<Trans>Duration:</Trans>
@@ -247,7 +252,7 @@ function MutedWordsInner() {
style={[
a.pb_xs,
a.text_sm,
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_medium,
]}>
<Trans>Mute in:</Trans>
@@ -293,7 +298,7 @@ function MutedWordsInner() {
style={[
a.pb_xs,
a.text_sm,
a.font_bold,
a.font_semi_bold,
t.atoms.text_contrast_medium,
]}>
<Trans>Options:</Trans>
@@ -362,7 +367,7 @@ function MutedWordsInner() {
<Text
style={[
a.text_md,
a.font_bold,
a.font_semi_bold,
a.pb_md,
t.atoms.text_contrast_high,
]}>
@@ -455,7 +460,7 @@ function MutedWordRow({
style={[
a.flex_1,
a.leading_snug,
a.font_bold,
a.font_semi_bold,
web({
overflowWrap: 'break-word',
wordBreak: 'break-word',
@@ -466,7 +471,8 @@ function MutedWordRow({
{word.value}{' '}
<Text style={[a.font_normal, t.atoms.text_contrast_medium]}>
in{' '}
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
<Text
style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
text & tags
</Text>
</Text>
@@ -476,7 +482,8 @@ function MutedWordRow({
{word.value}{' '}
<Text style={[a.font_normal, t.atoms.text_contrast_medium]}>
in{' '}
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
<Text
style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
tags
</Text>
</Text>
@@ -84,7 +84,7 @@ export function PostInteractionSettingsControlledDialog({
]}>
<Trans>
You can set default interaction settings in{' '}
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
<Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
Settings &rarr; Moderation &rarr; Interaction settings
</Text>
.
@@ -100,7 +100,7 @@ export function PostInteractionSettingsControlledDialog({
export function Header() {
return (
<View style={[a.gap_md, a.pb_sm]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Text style={[a.text_2xl, a.font_semi_bold]}>
<Trans>Post interaction settings</Trans>
</Text>
<Text style={[a.text_md, a.pb_xs]}>
@@ -329,7 +329,7 @@ export function PostInteractionSettingsForm({
<View style={[a.flex_1, a.gap_md]}>
<View style={[a.gap_lg]}>
<View style={[a.gap_sm]}>
<Text style={[a.font_bold, a.text_lg]}>
<Text style={[a.font_semi_bold, a.text_lg]}>
<Trans>Quote settings</Trans>
</Text>
@@ -385,7 +385,7 @@ export function PostInteractionSettingsForm({
opacity: replySettingsDisabled ? 0.3 : 1,
},
]}>
<Text style={[a.font_bold, a.text_lg]}>
<Text style={[a.font_semi_bold, a.text_lg]}>
<Trans>Reply settings</Trans>
</Text>
@@ -535,7 +535,9 @@ function Selectable({
},
style,
]}>
<Text style={[a.text_sm, isSelected && a.font_bold]}>{label}</Text>
<Text style={[a.text_sm, isSelected && a.font_semi_bold]}>
{label}
</Text>
{isSelected ? (
<Check size="sm" fill={t.palette.primary_500} />
) : (
@@ -284,7 +284,7 @@ export function SearchablePeopleList({
style={[
a.z_10,
a.text_lg,
a.font_heavy,
a.font_bold,
a.leading_tight,
t.atoms.text_contrast_high,
]}>
+3 -3
View File
@@ -172,7 +172,7 @@ function StarterPackList({
isWeb ? a.mb_2xl : a.my_lg,
a.align_center,
]}>
<Text style={[a.text_lg, a.font_bold]}>
<Text style={[a.text_lg, a.font_semi_bold]}>
<Trans>Add to starter packs</Trans>
</Text>
<Button
@@ -189,7 +189,7 @@ function StarterPackList({
<>
<View
style={[a.flex_row, a.justify_between, a.align_center, a.py_md]}>
<Text style={[a.text_md, a.font_bold]}>
<Text style={[a.text_md, a.font_semi_bold]}>
<Trans>New starter pack</Trans>
</Text>
<Button
@@ -331,7 +331,7 @@ function StarterPackItem({
return (
<View style={[a.flex_row, a.justify_between, a.align_center, a.py_md]}>
<View>
<Text emoji style={[a.text_md, a.font_bold]} numberOfLines={1}>
<Text emoji style={[a.text_md, a.font_semi_bold]} numberOfLines={1}>
{record.name}
</Text>
+1 -1
View File
@@ -45,7 +45,7 @@ export function SwitchAccountDialog({
<Dialog.Handle />
<Dialog.ScrollableInner label={_(msg`Switch Account`)}>
<View style={[a.gap_lg]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Text style={[a.text_2xl, a.font_semi_bold]}>
<Trans>Switch Account</Trans>
</Text>
@@ -66,7 +66,7 @@ export function ActivitySubscriptionsNUX() {
<SparkleIcon fill={t.palette.primary_800} size="sm" />
<Text
style={[
a.font_bold,
a.font_semi_bold,
{
color: t.palette.primary_800,
},
@@ -127,7 +127,7 @@ export function ActivitySubscriptionsNUX() {
style={[
a.text_3xl,
a.leading_tight,
a.font_heavy,
a.font_bold,
a.text_center,
{
fontSize: isWeb ? 28 : 32,
@@ -65,7 +65,7 @@ export function BookmarksAnnouncement() {
<SparkleIcon fill={t.palette.primary_800} size="sm" />
<Text
style={[
a.font_bold,
a.font_semi_bold,
{
color: t.palette.primary_800,
},
@@ -133,7 +133,7 @@ export function BookmarksAnnouncement() {
style={[
a.text_3xl,
a.leading_tight,
a.font_heavy,
a.font_bold,
a.text_center,
{
fontSize: isWeb ? 28 : 32,
@@ -55,7 +55,7 @@ export function InitialVerificationAnnouncement() {
<SparkleIcon fill={t.palette.primary_700} size="sm" />
<Text
style={[
a.font_bold,
a.font_semi_bold,
{
color: t.palette.primary_700,
},
@@ -87,7 +87,7 @@ export function InitialVerificationAnnouncement() {
</View>
<View style={[a.gap_xs]}>
<Text style={[a.text_2xl, a.font_bold, a.leading_snug]}>
<Text style={[a.text_2xl, a.font_semi_bold, a.leading_snug]}>
<Trans>A new form of verification</Trans>
</Text>
<Text style={[a.leading_snug, a.text_md]}>
@@ -123,7 +123,7 @@ export function InitialVerificationAnnouncement() {
<View style={[a.gap_sm]}>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<VerifierCheck width={14} />
<Text style={[a.text_lg, a.font_bold, a.leading_snug]}>
<Text style={[a.text_lg, a.font_semi_bold, a.leading_snug]}>
<Trans>Who can verify?</Trans>
</Text>
</View>
@@ -138,7 +138,7 @@ export function InitialVerificationAnnouncement() {
<Trans>
Trust emerges from relationships, communities, and shared
context, so were also enabling{' '}
<Span style={[a.font_bold]}>trusted verifiers</Span>:
<Span style={[a.font_semi_bold]}>trusted verifiers</Span>:
organizations that can directly issue verification.
</Trans>
</Text>
+3 -1
View File
@@ -89,7 +89,9 @@ export function ChatEmptyPill() {
onPress={onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}>
<Text style={[a.font_bold, a.pointer_events_none]} selectable={false}>
<Text
style={[a.font_semi_bold, a.pointer_events_none]}
selectable={false}>
{prompts[promptIndex]}
</Text>
</AnimatedPressable>
+2 -1
View File
@@ -67,7 +67,8 @@ let DateDivider = ({date: dateStr}: {date: string}): React.ReactNode => {
a.px_md,
]}>
<Trans>
<Text style={[a.text_xs, t.atoms.text_contrast_medium, a.font_bold]}>
<Text
style={[a.text_xs, t.atoms.text_contrast_medium, a.font_semi_bold]}>
{date}
</Text>{' '}
at {time}

Some files were not shown because too many files have changed in this diff Show More