diff --git a/.eslintrc.js b/.eslintrc.js index 04914bde64..fa2c74d9e9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -37,6 +37,7 @@ module.exports = { 'Toast.Action', 'AgeAssuranceAdmonition', 'Span', + 'StackedButton', ], impliedTextProps: [], suggestedTextWrappers: { diff --git a/.github/workflows/build-and-push-link-aws.yaml b/.github/workflows/build-and-push-link-aws.yaml index 1c17d6e190..7fbbc23c82 100644 --- a/.github/workflows/build-and-push-link-aws.yaml +++ b/.github/workflows/build-and-push-link-aws.yaml @@ -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 }} diff --git a/README.md b/README.md index eed6ac4fec..ae4511cc3b 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ Bluesky is an open social network built on the AT Protocol, a flexible technolog See [./LICENSE](./LICENSE) for the full license. +Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge). + ## P.S. We ❤️ you and all of the ways you support us. Thank you for making Bluesky a great place! diff --git a/bskylink/src/bin.ts b/bskylink/src/bin.ts index 3e0746a989..3dea1a7527 100644 --- a/bskylink/src/bin.ts +++ b/bskylink/src/bin.ts @@ -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) +}) diff --git a/bskylink/src/db/index.ts b/bskylink/src/db/index.ts index 7fe6aa536f..d335f80146 100644 --- a/bskylink/src/db/index.ts +++ b/bskylink/src/db/index.ts @@ -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({ diff --git a/bskyogcard/src/index.ts b/bskyogcard/src/index.ts index 110c3f50b7..53b5dbd6e1 100644 --- a/bskyogcard/src/index.ts +++ b/bskyogcard/src/index.ts @@ -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`) } } diff --git a/bskyweb/templates/post.html b/bskyweb/templates/post.html index 983f845355..ff21aaca14 100644 --- a/bskyweb/templates/post.html +++ b/bskyweb/templates/post.html @@ -56,6 +56,41 @@ + {%- elif requiresAuth and profileHandle -%} diff --git a/bskyweb/templates/profile.html b/bskyweb/templates/profile.html index cc33ed5318..bb552ef47d 100644 --- a/bskyweb/templates/profile.html +++ b/bskyweb/templates/profile.html @@ -51,6 +51,41 @@ {% endif %} + + {% endif -%} {%- endblock %} diff --git a/jest/test-utils.tsx b/jest/test-utils.tsx index 0a22d792bc..264b31fae5 100644 --- a/jest/test-utils.tsx +++ b/jest/test-utils.tsx @@ -1,22 +1,18 @@ -import React from 'react' -import {render} from '@testing-library/react-native' import {GestureHandlerRootView} from 'react-native-gesture-handler' -import {RootSiblingParent} from 'react-native-root-siblings' import {SafeAreaProvider} from 'react-native-safe-area-context' -import {RootStoreProvider, RootStoreModel} from '../src/state' +import {render} from '@testing-library/react-native' + import {ThemeProvider} from '../src/lib/ThemeContext' +import {type RootStoreModel, RootStoreProvider} from '../src/state' const customRender = (ui: any, rootStore: RootStoreModel) => render( - // eslint-disable-next-line react-native/no-inline-styles - - - - {ui} - - - + + + {ui} + + , ) diff --git a/package.json b/package.json index df8155d0cf..6ff8711102 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@atproto/api": "^0.16.7", "@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", @@ -196,7 +197,6 @@ "react-native-progress": "bluesky-social/react-native-progress", "react-native-qrcode-styled": "^0.3.3", "react-native-reanimated": "^3.19.1", - "react-native-root-siblings": "^5.0.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", "react-native-svg": "15.12.1", diff --git a/patches/react-native-screens+4.16.0.patch b/patches/react-native-screens+4.16.0.patch new file mode 100644 index 0000000000..8be75c770f --- /dev/null +++ b/patches/react-native-screens+4.16.0.patch @@ -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 *_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; diff --git a/src/App.native.tsx b/src/App.native.tsx index 036ecff60e..30a5e81296 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -4,7 +4,6 @@ import '#/view/icons' import React, {useEffect, useState} from 'react' import {GestureHandlerRootView} from 'react-native-gesture-handler' -import {RootSiblingParent} from 'react-native-root-siblings' import { initialWindowMetrics, SafeAreaProvider, @@ -38,7 +37,6 @@ import { } from '#/state/geolocation' import {GlobalGestureEventsProvider} from '#/state/global-gesture-events' import {Provider as HomeBadgeProvider} from '#/state/home-badge' -import {Provider as InvitesStateProvider} from '#/state/invites' import {Provider as LightboxStateProvider} from '#/state/lightbox' import {MessagesProvider} from '#/state/messages' import {Provider as ModalStateProvider} from '#/state/modals' @@ -85,7 +83,11 @@ if (isIOS) { } if (isAndroid) { // iOS is handled by the config plugin -sfn - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) + ScreenOrientation.lockAsync( + ScreenOrientation.OrientationLock.PORTRAIT_UP, + ).catch(error => + logger.debug('Could not lock orientation', {safeMessage: error}), + ) } /** @@ -134,64 +136,62 @@ function InnerApp() { - - - - - - - - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -225,24 +225,22 @@ function App() { - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index c86960172a..b7cba6122e 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -3,7 +3,6 @@ import '#/view/icons' import './style.css' import React, {useEffect, useState} from 'react' -import {RootSiblingParent} from 'react-native-root-siblings' import {SafeAreaProvider} from 'react-native-safe-area-context' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -26,7 +25,6 @@ import { Provider as GeolocationProvider, } from '#/state/geolocation' import {Provider as HomeBadgeProvider} from '#/state/home-badge' -import {Provider as InvitesStateProvider} from '#/state/invites' import {Provider as LightboxStateProvider} from '#/state/lightbox' import {MessagesProvider} from '#/state/messages' import {Provider as ModalStateProvider} from '#/state/modals' @@ -112,62 +110,60 @@ function InnerApp() { - - - - - - - - - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -199,19 +195,17 @@ function App() { - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index dc5d9f59c3..3168b1da18 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -1,115 +1,15 @@ -import { - Platform, - type StyleProp, - StyleSheet, - type ViewStyle, -} from 'react-native' +import {type StyleProp, type ViewStyle} from 'react-native' +import {atoms as baseAtoms} from '@bsky.app/alf' -import * as tokens from '#/alf/tokens' -import {ios, native, platform, web} from '#/alf/util/platform' +import {native, platform, web} from '#/alf/util/platform' import * as Layout from '#/components/Layout' export const atoms = { - debug: { - borderColor: 'red', - borderWidth: 1, - }, + ...baseAtoms, - /* - * Positioning - */ - fixed: { - position: Platform.select({web: 'fixed', native: 'absolute'}) as 'absolute', - }, - absolute: { - position: 'absolute', - }, - relative: { - position: 'relative', - }, - static: { - position: 'static', - }, - sticky: web({ - position: 'sticky', - }), - inset_0: { - top: 0, - left: 0, - right: 0, - bottom: 0, - }, - top_0: { - top: 0, - }, - right_0: { - right: 0, - }, - bottom_0: { - bottom: 0, - }, - left_0: { - left: 0, - }, - z_10: { - zIndex: 10, - }, - z_20: { - zIndex: 20, - }, - z_30: { - zIndex: 30, - }, - z_40: { - zIndex: 40, - }, - z_50: { - zIndex: 50, - }, - - overflow_visible: { - overflow: 'visible', - }, - overflow_x_visible: { - overflowX: 'visible', - }, - overflow_y_visible: { - overflowY: 'visible', - }, - overflow_hidden: { - overflow: 'hidden', - }, - overflow_x_hidden: { - overflowX: 'hidden', - }, - overflow_y_hidden: { - overflowY: 'hidden', - }, - /** - * @platform web - */ - overflow_auto: web({ - overflow: 'auto', - }), - - /* - * Width & Height - */ - w_full: { - width: '100%', - }, - h_full: { - height: '100%', - }, h_full_vh: web({ height: '100vh', }), - max_w_full: { - maxWidth: '100%', - }, - max_h_full: { - maxHeight: '100%', - }, /** * Used for the outermost components on screens, to ensure that they can fill @@ -131,895 +31,6 @@ export const atoms = { backgroundColor: 'transparent', }, - /* - * Border radius - */ - rounded_0: { - borderRadius: 0, - }, - rounded_2xs: { - borderRadius: tokens.borderRadius._2xs, - }, - rounded_xs: { - borderRadius: tokens.borderRadius.xs, - }, - rounded_sm: { - borderRadius: tokens.borderRadius.sm, - }, - rounded_md: { - borderRadius: tokens.borderRadius.md, - }, - rounded_lg: { - borderRadius: tokens.borderRadius.lg, - }, - rounded_full: { - borderRadius: tokens.borderRadius.full, - }, - - /* - * Flex - */ - gap_0: { - gap: 0, - }, - gap_2xs: { - gap: tokens.space._2xs, - }, - gap_xs: { - gap: tokens.space.xs, - }, - gap_sm: { - gap: tokens.space.sm, - }, - gap_md: { - gap: tokens.space.md, - }, - gap_lg: { - gap: tokens.space.lg, - }, - gap_xl: { - gap: tokens.space.xl, - }, - gap_2xl: { - gap: tokens.space._2xl, - }, - gap_3xl: { - gap: tokens.space._3xl, - }, - gap_4xl: { - gap: tokens.space._4xl, - }, - gap_5xl: { - gap: tokens.space._5xl, - }, - flex: { - display: 'flex', - }, - flex_col: { - flexDirection: 'column', - }, - flex_row: { - flexDirection: 'row', - }, - flex_col_reverse: { - flexDirection: 'column-reverse', - }, - flex_row_reverse: { - flexDirection: 'row-reverse', - }, - flex_wrap: { - flexWrap: 'wrap', - }, - flex_nowrap: { - flexWrap: 'nowrap', - }, - flex_0: { - flex: web('0 0 auto') || (native(0) as number), - }, - flex_1: { - flex: 1, - }, - flex_grow: { - flexGrow: 1, - }, - flex_grow_0: { - flexGrow: 0, - }, - flex_shrink: { - flexShrink: 1, - }, - flex_shrink_0: { - flexShrink: 0, - }, - justify_start: { - justifyContent: 'flex-start', - }, - justify_center: { - justifyContent: 'center', - }, - justify_between: { - justifyContent: 'space-between', - }, - justify_end: { - justifyContent: 'flex-end', - }, - align_center: { - alignItems: 'center', - }, - align_start: { - alignItems: 'flex-start', - }, - align_end: { - alignItems: 'flex-end', - }, - align_baseline: { - alignItems: 'baseline', - }, - align_stretch: { - alignItems: 'stretch', - }, - self_auto: { - alignSelf: 'auto', - }, - self_start: { - alignSelf: 'flex-start', - }, - self_end: { - alignSelf: 'flex-end', - }, - self_center: { - alignSelf: 'center', - }, - self_stretch: { - alignSelf: 'stretch', - }, - self_baseline: { - alignSelf: 'baseline', - }, - - /* - * Text - */ - text_left: { - textAlign: 'left', - }, - text_center: { - textAlign: 'center', - }, - text_right: { - textAlign: 'right', - }, - text_2xs: { - fontSize: tokens.fontSize._2xs, - letterSpacing: tokens.TRACKING, - }, - text_xs: { - fontSize: tokens.fontSize.xs, - letterSpacing: tokens.TRACKING, - }, - text_sm: { - fontSize: tokens.fontSize.sm, - letterSpacing: tokens.TRACKING, - }, - text_md: { - fontSize: tokens.fontSize.md, - letterSpacing: tokens.TRACKING, - }, - text_lg: { - fontSize: tokens.fontSize.lg, - letterSpacing: tokens.TRACKING, - }, - text_xl: { - fontSize: tokens.fontSize.xl, - letterSpacing: tokens.TRACKING, - }, - text_2xl: { - fontSize: tokens.fontSize._2xl, - letterSpacing: tokens.TRACKING, - }, - text_3xl: { - fontSize: tokens.fontSize._3xl, - letterSpacing: tokens.TRACKING, - }, - text_4xl: { - fontSize: tokens.fontSize._4xl, - letterSpacing: tokens.TRACKING, - }, - text_5xl: { - fontSize: tokens.fontSize._5xl, - letterSpacing: tokens.TRACKING, - }, - leading_tight: { - lineHeight: 1.15, - }, - leading_snug: { - lineHeight: 1.3, - }, - leading_normal: { - lineHeight: 1.5, - }, - tracking_normal: { - letterSpacing: tokens.TRACKING, - }, - font_normal: { - fontWeight: tokens.fontWeight.normal, - }, - font_medium: { - fontWeight: tokens.fontWeight.medium, - }, - font_bold: { - fontWeight: tokens.fontWeight.bold, - }, - font_heavy: { - fontWeight: tokens.fontWeight.heavy, - }, - italic: { - fontStyle: 'italic', - }, - - /* - * Border - */ - border_0: { - borderWidth: 0, - }, - border_t_0: { - borderTopWidth: 0, - }, - border_b_0: { - borderBottomWidth: 0, - }, - border_l_0: { - borderLeftWidth: 0, - }, - border_r_0: { - borderRightWidth: 0, - }, - border_x_0: { - borderLeftWidth: 0, - borderRightWidth: 0, - }, - border_y_0: { - borderTopWidth: 0, - borderBottomWidth: 0, - }, - border: { - borderWidth: StyleSheet.hairlineWidth, - }, - border_t: { - borderTopWidth: StyleSheet.hairlineWidth, - }, - border_b: { - borderBottomWidth: StyleSheet.hairlineWidth, - }, - border_l: { - borderLeftWidth: StyleSheet.hairlineWidth, - }, - border_r: { - borderRightWidth: StyleSheet.hairlineWidth, - }, - border_x: { - borderLeftWidth: StyleSheet.hairlineWidth, - borderRightWidth: StyleSheet.hairlineWidth, - }, - border_y: { - borderTopWidth: StyleSheet.hairlineWidth, - borderBottomWidth: StyleSheet.hairlineWidth, - }, - border_transparent: { - borderColor: 'transparent', - }, - curve_circular: ios({ - borderCurve: 'circular', - }), - curve_continuous: ios({ - borderCurve: 'continuous', - }), - - /* - * Shadow - */ - shadow_sm: { - shadowRadius: 8, - shadowOpacity: 0.1, - elevation: 8, - }, - shadow_md: { - shadowRadius: 16, - shadowOpacity: 0.1, - elevation: 16, - }, - shadow_lg: { - shadowRadius: 32, - shadowOpacity: 0.1, - elevation: 24, - }, - - /* - * Padding - */ - p_0: { - padding: 0, - }, - p_2xs: { - padding: tokens.space._2xs, - }, - p_xs: { - padding: tokens.space.xs, - }, - p_sm: { - padding: tokens.space.sm, - }, - p_md: { - padding: tokens.space.md, - }, - p_lg: { - padding: tokens.space.lg, - }, - p_xl: { - padding: tokens.space.xl, - }, - p_2xl: { - padding: tokens.space._2xl, - }, - p_3xl: { - padding: tokens.space._3xl, - }, - p_4xl: { - padding: tokens.space._4xl, - }, - p_5xl: { - padding: tokens.space._5xl, - }, - px_0: { - paddingLeft: 0, - paddingRight: 0, - }, - px_2xs: { - paddingLeft: tokens.space._2xs, - paddingRight: tokens.space._2xs, - }, - px_xs: { - paddingLeft: tokens.space.xs, - paddingRight: tokens.space.xs, - }, - px_sm: { - paddingLeft: tokens.space.sm, - paddingRight: tokens.space.sm, - }, - px_md: { - paddingLeft: tokens.space.md, - paddingRight: tokens.space.md, - }, - px_lg: { - paddingLeft: tokens.space.lg, - paddingRight: tokens.space.lg, - }, - px_xl: { - paddingLeft: tokens.space.xl, - paddingRight: tokens.space.xl, - }, - px_2xl: { - paddingLeft: tokens.space._2xl, - paddingRight: tokens.space._2xl, - }, - px_3xl: { - paddingLeft: tokens.space._3xl, - paddingRight: tokens.space._3xl, - }, - px_4xl: { - paddingLeft: tokens.space._4xl, - paddingRight: tokens.space._4xl, - }, - px_5xl: { - paddingLeft: tokens.space._5xl, - paddingRight: tokens.space._5xl, - }, - py_0: { - paddingTop: 0, - paddingBottom: 0, - }, - py_2xs: { - paddingTop: tokens.space._2xs, - paddingBottom: tokens.space._2xs, - }, - py_xs: { - paddingTop: tokens.space.xs, - paddingBottom: tokens.space.xs, - }, - py_sm: { - paddingTop: tokens.space.sm, - paddingBottom: tokens.space.sm, - }, - py_md: { - paddingTop: tokens.space.md, - paddingBottom: tokens.space.md, - }, - py_lg: { - paddingTop: tokens.space.lg, - paddingBottom: tokens.space.lg, - }, - py_xl: { - paddingTop: tokens.space.xl, - paddingBottom: tokens.space.xl, - }, - py_2xl: { - paddingTop: tokens.space._2xl, - paddingBottom: tokens.space._2xl, - }, - py_3xl: { - paddingTop: tokens.space._3xl, - paddingBottom: tokens.space._3xl, - }, - py_4xl: { - paddingTop: tokens.space._4xl, - paddingBottom: tokens.space._4xl, - }, - py_5xl: { - paddingTop: tokens.space._5xl, - paddingBottom: tokens.space._5xl, - }, - pt_0: { - paddingTop: 0, - }, - pt_2xs: { - paddingTop: tokens.space._2xs, - }, - pt_xs: { - paddingTop: tokens.space.xs, - }, - pt_sm: { - paddingTop: tokens.space.sm, - }, - pt_md: { - paddingTop: tokens.space.md, - }, - pt_lg: { - paddingTop: tokens.space.lg, - }, - pt_xl: { - paddingTop: tokens.space.xl, - }, - pt_2xl: { - paddingTop: tokens.space._2xl, - }, - pt_3xl: { - paddingTop: tokens.space._3xl, - }, - pt_4xl: { - paddingTop: tokens.space._4xl, - }, - pt_5xl: { - paddingTop: tokens.space._5xl, - }, - pb_0: { - paddingBottom: 0, - }, - pb_2xs: { - paddingBottom: tokens.space._2xs, - }, - pb_xs: { - paddingBottom: tokens.space.xs, - }, - pb_sm: { - paddingBottom: tokens.space.sm, - }, - pb_md: { - paddingBottom: tokens.space.md, - }, - pb_lg: { - paddingBottom: tokens.space.lg, - }, - pb_xl: { - paddingBottom: tokens.space.xl, - }, - pb_2xl: { - paddingBottom: tokens.space._2xl, - }, - pb_3xl: { - paddingBottom: tokens.space._3xl, - }, - pb_4xl: { - paddingBottom: tokens.space._4xl, - }, - pb_5xl: { - paddingBottom: tokens.space._5xl, - }, - pl_0: { - paddingLeft: 0, - }, - pl_2xs: { - paddingLeft: tokens.space._2xs, - }, - pl_xs: { - paddingLeft: tokens.space.xs, - }, - pl_sm: { - paddingLeft: tokens.space.sm, - }, - pl_md: { - paddingLeft: tokens.space.md, - }, - pl_lg: { - paddingLeft: tokens.space.lg, - }, - pl_xl: { - paddingLeft: tokens.space.xl, - }, - pl_2xl: { - paddingLeft: tokens.space._2xl, - }, - pl_3xl: { - paddingLeft: tokens.space._3xl, - }, - pl_4xl: { - paddingLeft: tokens.space._4xl, - }, - pl_5xl: { - paddingLeft: tokens.space._5xl, - }, - pr_0: { - paddingRight: 0, - }, - pr_2xs: { - paddingRight: tokens.space._2xs, - }, - pr_xs: { - paddingRight: tokens.space.xs, - }, - pr_sm: { - paddingRight: tokens.space.sm, - }, - pr_md: { - paddingRight: tokens.space.md, - }, - pr_lg: { - paddingRight: tokens.space.lg, - }, - pr_xl: { - paddingRight: tokens.space.xl, - }, - pr_2xl: { - paddingRight: tokens.space._2xl, - }, - pr_3xl: { - paddingRight: tokens.space._3xl, - }, - pr_4xl: { - paddingRight: tokens.space._4xl, - }, - pr_5xl: { - paddingRight: tokens.space._5xl, - }, - - /* - * Margin - */ - m_0: { - margin: 0, - }, - m_2xs: { - margin: tokens.space._2xs, - }, - m_xs: { - margin: tokens.space.xs, - }, - m_sm: { - margin: tokens.space.sm, - }, - m_md: { - margin: tokens.space.md, - }, - m_lg: { - margin: tokens.space.lg, - }, - m_xl: { - margin: tokens.space.xl, - }, - m_2xl: { - margin: tokens.space._2xl, - }, - m_3xl: { - margin: tokens.space._3xl, - }, - m_4xl: { - margin: tokens.space._4xl, - }, - m_5xl: { - margin: tokens.space._5xl, - }, - m_auto: { - margin: 'auto', - }, - mx_0: { - marginLeft: 0, - marginRight: 0, - }, - mx_2xs: { - marginLeft: tokens.space._2xs, - marginRight: tokens.space._2xs, - }, - mx_xs: { - marginLeft: tokens.space.xs, - marginRight: tokens.space.xs, - }, - mx_sm: { - marginLeft: tokens.space.sm, - marginRight: tokens.space.sm, - }, - mx_md: { - marginLeft: tokens.space.md, - marginRight: tokens.space.md, - }, - mx_lg: { - marginLeft: tokens.space.lg, - marginRight: tokens.space.lg, - }, - mx_xl: { - marginLeft: tokens.space.xl, - marginRight: tokens.space.xl, - }, - mx_2xl: { - marginLeft: tokens.space._2xl, - marginRight: tokens.space._2xl, - }, - mx_3xl: { - marginLeft: tokens.space._3xl, - marginRight: tokens.space._3xl, - }, - mx_4xl: { - marginLeft: tokens.space._4xl, - marginRight: tokens.space._4xl, - }, - mx_5xl: { - marginLeft: tokens.space._5xl, - marginRight: tokens.space._5xl, - }, - mx_auto: { - marginLeft: 'auto', - marginRight: 'auto', - }, - my_0: { - marginTop: 0, - marginBottom: 0, - }, - my_2xs: { - marginTop: tokens.space._2xs, - marginBottom: tokens.space._2xs, - }, - my_xs: { - marginTop: tokens.space.xs, - marginBottom: tokens.space.xs, - }, - my_sm: { - marginTop: tokens.space.sm, - marginBottom: tokens.space.sm, - }, - my_md: { - marginTop: tokens.space.md, - marginBottom: tokens.space.md, - }, - my_lg: { - marginTop: tokens.space.lg, - marginBottom: tokens.space.lg, - }, - my_xl: { - marginTop: tokens.space.xl, - marginBottom: tokens.space.xl, - }, - my_2xl: { - marginTop: tokens.space._2xl, - marginBottom: tokens.space._2xl, - }, - my_3xl: { - marginTop: tokens.space._3xl, - marginBottom: tokens.space._3xl, - }, - my_4xl: { - marginTop: tokens.space._4xl, - marginBottom: tokens.space._4xl, - }, - my_5xl: { - marginTop: tokens.space._5xl, - marginBottom: tokens.space._5xl, - }, - my_auto: { - marginTop: 'auto', - marginBottom: 'auto', - }, - mt_0: { - marginTop: 0, - }, - mt_2xs: { - marginTop: tokens.space._2xs, - }, - mt_xs: { - marginTop: tokens.space.xs, - }, - mt_sm: { - marginTop: tokens.space.sm, - }, - mt_md: { - marginTop: tokens.space.md, - }, - mt_lg: { - marginTop: tokens.space.lg, - }, - mt_xl: { - marginTop: tokens.space.xl, - }, - mt_2xl: { - marginTop: tokens.space._2xl, - }, - mt_3xl: { - marginTop: tokens.space._3xl, - }, - mt_4xl: { - marginTop: tokens.space._4xl, - }, - mt_5xl: { - marginTop: tokens.space._5xl, - }, - mt_auto: { - marginTop: 'auto', - }, - mb_0: { - marginBottom: 0, - }, - mb_2xs: { - marginBottom: tokens.space._2xs, - }, - mb_xs: { - marginBottom: tokens.space.xs, - }, - mb_sm: { - marginBottom: tokens.space.sm, - }, - mb_md: { - marginBottom: tokens.space.md, - }, - mb_lg: { - marginBottom: tokens.space.lg, - }, - mb_xl: { - marginBottom: tokens.space.xl, - }, - mb_2xl: { - marginBottom: tokens.space._2xl, - }, - mb_3xl: { - marginBottom: tokens.space._3xl, - }, - mb_4xl: { - marginBottom: tokens.space._4xl, - }, - mb_5xl: { - marginBottom: tokens.space._5xl, - }, - mb_auto: { - marginBottom: 'auto', - }, - ml_0: { - marginLeft: 0, - }, - ml_2xs: { - marginLeft: tokens.space._2xs, - }, - ml_xs: { - marginLeft: tokens.space.xs, - }, - ml_sm: { - marginLeft: tokens.space.sm, - }, - ml_md: { - marginLeft: tokens.space.md, - }, - ml_lg: { - marginLeft: tokens.space.lg, - }, - ml_xl: { - marginLeft: tokens.space.xl, - }, - ml_2xl: { - marginLeft: tokens.space._2xl, - }, - ml_3xl: { - marginLeft: tokens.space._3xl, - }, - ml_4xl: { - marginLeft: tokens.space._4xl, - }, - ml_5xl: { - marginLeft: tokens.space._5xl, - }, - ml_auto: { - marginLeft: 'auto', - }, - mr_0: { - marginRight: 0, - }, - mr_2xs: { - marginRight: tokens.space._2xs, - }, - mr_xs: { - marginRight: tokens.space.xs, - }, - mr_sm: { - marginRight: tokens.space.sm, - }, - mr_md: { - marginRight: tokens.space.md, - }, - mr_lg: { - marginRight: tokens.space.lg, - }, - mr_xl: { - marginRight: tokens.space.xl, - }, - mr_2xl: { - marginRight: tokens.space._2xl, - }, - mr_3xl: { - marginRight: tokens.space._3xl, - }, - mr_4xl: { - marginRight: tokens.space._4xl, - }, - mr_5xl: { - marginRight: tokens.space._5xl, - }, - mr_auto: { - marginRight: 'auto', - }, - - /* - * Pointer events & user select - */ - pointer_events_none: { - pointerEvents: 'none', - }, - pointer_events_auto: { - pointerEvents: 'auto', - }, - user_select_none: { - userSelect: 'none', - }, - user_select_text: { - userSelect: 'text', - }, - user_select_all: { - userSelect: 'all', - }, - outline_inset_1: { - outlineOffset: -1, - }, - - /* - * Text decoration - */ - underline: { - textDecorationLine: 'underline', - }, - strike_through: { - textDecorationLine: 'line-through', - }, - - /* - * Display - */ - hidden: { - display: 'none', - }, - inline: web({ - display: 'inline', - }), - block: web({ - display: 'block', - }), - contents: web({ - display: 'contents', - }), - /* * Transition */ @@ -1099,8 +110,4 @@ export const atoms = { transform: [], }, }) as {transform: Exclude}, - - pointer: web({ - cursor: 'pointer', - }), } as const diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts index 7366edef2a..ddf4c0b186 100644 --- a/src/alf/fonts.ts +++ b/src/alf/fonts.ts @@ -7,11 +7,11 @@ const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe const factor = 0.0625 // 1 - (15/16) const fontScaleMultipliers: Record = { - '-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']) { diff --git a/src/alf/index.tsx b/src/alf/index.tsx index bee8ed78cf..eed3fcbeb2 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -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 + themes: typeof themes fonts: { scale: Exclude scaleMultiplier: number @@ -44,14 +43,8 @@ export type Alf = { */ export const Context = React.createContext({ 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( () => ({ @@ -121,7 +105,6 @@ export function ThemeProvider({ }), [ themeName, - themes, fontScale, setFontScaleAndPersist, fontFamily, diff --git a/src/alf/themes.ts b/src/alf/themes.ts index bb3c62c490..708bc99705 100644 --- a/src/alf/themes.ts +++ b/src/alf/themes.ts @@ -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 diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts index 74cc160fc6..ac48bc7883 100644 --- a/src/alf/tokens.ts +++ b/src/alf/tokens.ts @@ -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 = { diff --git a/src/alf/types.ts b/src/alf/types.ts deleted file mode 100644 index d2ac5dbaa3..0000000000 --- a/src/alf/types.ts +++ /dev/null @@ -1,164 +0,0 @@ -import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native' - -export type TextStyleProp = { - style?: StyleProp -} - -export type ViewStyleProp = { - style?: StyleProp -} - -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 -} diff --git a/src/alf/typography.tsx b/src/alf/typography.tsx index c229ab4435..3c3bb95489 100644 --- a/src/alf/typography.tsx +++ b/src/alf/typography.tsx @@ -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. diff --git a/src/alf/util/__tests__/colors.test.ts b/src/alf/util/__tests__/colors.test.ts index 350b6ff4a4..1ecb7394bd 100644 --- a/src/alf/util/__tests__/colors.test.ts +++ b/src/alf/util/__tests__/colors.test.ts @@ -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`, - ) }) }) diff --git a/src/alf/util/colorGeneration.ts b/src/alf/util/colorGeneration.ts index 574ab0a496..467270676a 100644 --- a/src/alf/util/colorGeneration.ts +++ b/src/alf/util/colorGeneration.ts @@ -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 diff --git a/src/alf/util/platform.ts b/src/alf/util/platform.ts index 947f2bd163..2f66c76c1a 100644 --- a/src/alf/util/platform.ts +++ b/src/alf/util/platform.ts @@ -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' diff --git a/src/alf/util/systemUI.ts b/src/alf/util/systemUI.ts index 9e5769c4c6..d013f0c3f9 100644 --- a/src/alf/util/systemUI.ts +++ b/src/alf/util/systemUI.ts @@ -1,14 +1,20 @@ import * as SystemUI from 'expo-system-ui' +import {type Theme} from '@bsky.app/alf' +import {logger} from '#/logger' import {isAndroid} from '#/platform/detection' -import {type Theme} from '../types' export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { if (isAndroid) { - if (themeType === 'theme') { - SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) - } else { - SystemUI.setBackgroundColorAsync('black') + try { + if (themeType === 'theme') { + SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) + } else { + SystemUI.setBackgroundColorAsync('black') + } + } catch (error) { + // Can reject with 'The current activity is no longer available' - no big deal + logger.debug('Could not set system UI theme', {safeMessage: error}) } } } diff --git a/src/alf/util/themeSelector.ts b/src/alf/util/themeSelector.ts index c118b65069..b0827342a2 100644 --- a/src/alf/util/themeSelector.ts +++ b/src/alf/util/themeSelector.ts @@ -1,14 +1,3 @@ -import {type ThemeName} from '#/alf/types' +import {utils} from '@bsky.app/alf' -export function select(name: ThemeName, options: Record) { - 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 diff --git a/src/alf/util/useColorModeTheme.ts b/src/alf/util/useColorModeTheme.ts index b0b4f9e7c1..4ae94899cd 100644 --- a/src/alf/util/useColorModeTheme.ts +++ b/src/alf/util/useColorModeTheme.ts @@ -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() diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index eb770eeca7..e3b2b7d129 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -77,7 +77,7 @@ export function AccountList({ ]}> {sanitizeDisplayName( profile?.displayName || profile?.handle || account.handle, diff --git a/src/components/Admonition.tsx b/src/components/Admonition.tsx index ea6751955f..961d94b37e 100644 --- a/src/components/Admonition.tsx +++ b/src/components/Admonition.tsx @@ -3,17 +3,13 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button as BaseButton, type ButtonProps} from '#/components/Button' -import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' -import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye' -import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' +import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import {Text as BaseText, type TextProps} from '#/components/Typography' export const colors = { - warning: { - light: '#DFBC00', - dark: '#BFAF1F', - }, + warning: '#FFC404', } type Context = { @@ -29,29 +25,44 @@ export function Icon() { const t = useTheme() const {type} = useContext(Context) const Icon = { - info: InfoIcon, - tip: TipIcon, + info: CircleInfoIcon, + tip: CircleInfoIcon, warning: WarningIcon, - error: ErrorIcon, + error: CircleXIcon, }[type] const fill = { info: t.atoms.text_contrast_medium.color, tip: t.palette.primary_500, - warning: colors.warning.light, + warning: colors.warning, error: t.palette.negative_500, }[type] return } +export function Content({ + children, + style, + ...rest +}: { + children: React.ReactNode + style?: StyleProp +}) { + return ( + + {children} + + ) +} + export function Text({ children, style, ...rest }: Pick) { return ( - + {children} ) @@ -60,17 +71,23 @@ export function Text({ export function Button({ children, ...props -}: Omit) { +}: Omit) { return ( - + {children} ) } -export function Row({children}: {children: React.ReactNode}) { +export function Row({ + children, + style, +}: { + children: React.ReactNode + style?: StyleProp +}) { return ( - + {children} ) @@ -88,19 +105,20 @@ export function Outer({ const t = useTheme() const {gtMobile} = useBreakpoints() const borderColor = { - info: t.atoms.border_contrast_low.borderColor, - tip: t.atoms.border_contrast_low.borderColor, - warning: t.atoms.border_contrast_low.borderColor, - error: t.atoms.border_contrast_low.borderColor, + info: t.atoms.border_contrast_high.borderColor, + tip: t.palette.primary_500, + warning: colors.warning, + error: t.palette.negative_500, }[type] return ( @@ -123,7 +141,9 @@ export function Admonition({ - {children} + + {children} + ) diff --git a/src/components/BlockedGeoOverlay.tsx b/src/components/BlockedGeoOverlay.tsx index 8dd55c2bf5..d6e2626875 100644 --- a/src/components/BlockedGeoOverlay.tsx +++ b/src/components/BlockedGeoOverlay.tsx @@ -96,7 +96,7 @@ export function BlockedGeoOverlay() { - + Not in Mississippi? ( } } 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) @@ -274,51 +274,27 @@ export const Button = React.forwardRef( } else if (color === 'primary_subtle') { if (!disabled) { baseStyles.push({ - backgroundColor: select(t.name, { - light: t.palette.primary_50, - dim: t.palette.primary_100, - dark: t.palette.primary_100, - }), + backgroundColor: t.palette.primary_50, }) hoverStyles.push({ - backgroundColor: select(t.name, { - light: t.palette.primary_100, - dim: t.palette.primary_200, - dark: t.palette.primary_200, - }), + backgroundColor: t.palette.primary_100, }) } 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') { if (!disabled) { baseStyles.push({ - backgroundColor: select(t.name, { - light: t.palette.negative_50, - dim: t.palette.negative_100, - dark: t.palette.negative_100, - }), + backgroundColor: t.palette.negative_50, }) hoverStyles.push({ - backgroundColor: select(t.name, { - light: t.palette.negative_100, - dim: t.palette.negative_200, - dark: t.palette.negative_200, - }), + backgroundColor: t.palette.negative_100, }) } 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 +348,7 @@ export const Button = React.forwardRef( if (!disabled) { baseStyles.push(t.atoms.bg) hoverStyles.push({ - backgroundColor: t.palette.contrast_25, + backgroundColor: t.palette.contrast_50, }) } } @@ -396,7 +372,7 @@ export const Button = React.forwardRef( if (!disabled) { baseStyles.push(t.atoms.bg) hoverStyles.push({ - backgroundColor: t.palette.contrast_25, + backgroundColor: t.palette.contrast_50, }) } } @@ -626,37 +602,21 @@ export function useSharedButtonTextStyles() { } else if (color === 'primary_subtle') { if (!disabled) { baseStyles.push({ - color: select(t.name, { - light: t.palette.primary_600, - dim: t.palette.primary_800, - dark: t.palette.primary_800, - }), + color: t.palette.primary_600, }) } else { baseStyles.push({ - color: select(t.name, { - light: t.palette.primary_200, - dim: t.palette.primary_200, - dark: t.palette.primary_200, - }), + color: t.palette.primary_200, }) } } else if (color === 'negative_subtle') { if (!disabled) { baseStyles.push({ - color: select(t.name, { - light: t.palette.negative_600, - dim: t.palette.negative_800, - dark: t.palette.negative_800, - }), + color: t.palette.negative_600, }) } else { baseStyles.push({ - color: select(t.name, { - light: t.palette.negative_200, - dim: t.palette.negative_200, - dark: t.palette.negative_200, - }), + color: t.palette.negative_200, }) } } @@ -763,7 +723,7 @@ export function useSharedButtonTextStyles() { } else if (size === 'small') { baseStyles.push(a.text_sm, a.leading_snug, a.font_medium) } else if (size === 'tiny') { - baseStyles.push(a.text_xs, a.leading_snug, a.font_medium) + baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold) } return StyleSheet.flatten(baseStyles) @@ -877,3 +837,47 @@ export function ButtonIcon({ ) } + +export type StackedButtonProps = Omit< + ButtonProps, + keyof VariantProps | 'children' +> & + Pick & { + children: React.ReactNode + icon: React.ComponentType + } + +export function StackedButton({children, ...props}: StackedButtonProps) { + return ( + + ) +} + +function StackedButtonInnerText({ + children, + icon: Icon, +}: Pick) { + const textStyles = useSharedButtonTextStyles() + return ( + <> + + {children} + + ) +} diff --git a/src/components/ContextMenu/index.tsx b/src/components/ContextMenu/index.tsx index 9d7189473c..7d078bb2de 100644 --- a/src/components/ContextMenu/index.tsx +++ b/src/components/ContextMenu/index.tsx @@ -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 ( + style={[ + a.font_semi_bold, + t.atoms.text_contrast_medium, + {marginBottom: -8}, + ]}> {children} ) diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index de8287a53c..ee2c0f76c7 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -267,7 +267,10 @@ export const ScrollableInner = React.forwardRef( scrollEventThrottle={50} onScroll={isAndroid ? onScroll : undefined} keyboardShouldPersistTaps="handled" - stickyHeaderIndices={header ? [0] : undefined}> + // TODO: figure out why this positions the header absolutely (rather than stickily) + // on Android. fine to disable for now, because we don't have any + // dialogs that use this that actually scroll -sfn + stickyHeaderIndices={ios(header ? [0] : undefined)}> {header} {children} diff --git a/src/components/Dialog/shared.tsx b/src/components/Dialog/shared.tsx index b5513b19c4..e9fdf50fa7 100644 --- a/src/components/Dialog/shared.tsx +++ b/src/components/Dialog/shared.tsx @@ -62,7 +62,7 @@ export function HeaderText({ style?: StyleProp }) { return ( - + {children} ) diff --git a/src/components/Error.tsx b/src/components/Error.tsx index dc8e53b46e..92b8b2e7cd 100644 --- a/src/components/Error.tsx +++ b/src/components/Error.tsx @@ -40,7 +40,7 @@ export function Error({ ]} sideBorders={sideBorders}> - {title} + {title} {title} @@ -214,7 +214,7 @@ export function DescriptionPlaceholder() { export function Likes({count}: {count: number}) { const t = useTheme() return ( - + Liked by diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index 6278449a07..5a65215b12 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -380,7 +380,7 @@ export function ProfileGrid({ a.justify_between, ]} pointerEvents={isIOS ? 'auto' : 'box-none'}> - + {isFeedContext ? ( Suggested for you ) : ( @@ -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, ]}> Some other feeds you might like diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx index 990dbb1c69..ecb47a7588 100644 --- a/src/components/LabelingServiceCard/index.tsx +++ b/src/components/LabelingServiceCard/index.tsx @@ -45,7 +45,7 @@ export function Avatar({avatar}: {avatar?: string}) { export function Title({value}: {value: string}) { return ( - + {value} ) diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index 1a049a6966..596540a23e 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -181,7 +181,7 @@ export function TitleText({ Hidden list @@ -135,7 +135,7 @@ export function TitleAndByline({ {title} diff --git a/src/components/LoggedOutCTA.tsx b/src/components/LoggedOutCTA.tsx index 0bafbd45f1..de0e23dc96 100644 --- a/src/components/LoggedOutCTA.tsx +++ b/src/components/LoggedOutCTA.tsx @@ -50,7 +50,7 @@ export function LoggedOutCTA({style, gateName}: LoggedOutCTAProps) { - + Join Bluesky - + {isMe ? Welcome, friend! : Say hello!} diff --git a/src/components/Pills.tsx b/src/components/Pills.tsx index 550089a809..4469c83bd6 100644 --- a/src/components/Pills.tsx +++ b/src/components/Pills.tsx @@ -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}, diff --git a/src/components/PolicyUpdateOverlay/Badge.tsx b/src/components/PolicyUpdateOverlay/Badge.tsx index 3829f60a5a..b3bb8b77b2 100644 --- a/src/components/PolicyUpdateOverlay/Badge.tsx +++ b/src/components/PolicyUpdateOverlay/Badge.tsx @@ -25,7 +25,7 @@ export function Badge() { - + Hey there 👋 @@ -115,7 +115,7 @@ export function Content({state}: {state: PolicyUpdateState}) { ) : ( - + Hey there 👋 diff --git a/src/components/Post/Embed/ExternalEmbed/index.tsx b/src/components/Post/Embed/ExternalEmbed/index.tsx index 714eaecd63..7911df726b 100644 --- a/src/components/Post/Embed/ExternalEmbed/index.tsx +++ b/src/components/Post/Embed/ExternalEmbed/index.tsx @@ -126,7 +126,7 @@ export const ExternalEmbed = ({ + style={[a.text_md, a.font_semi_bold, a.leading_snug]}> {link.title || link.uri} )} diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx index 67af7618c3..01828011b2 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx @@ -54,7 +54,7 @@ export function TimeIndicator({ {`${minutes}:${seconds}`} diff --git a/src/components/PostControls/PostControlButton.tsx b/src/components/PostControls/PostControlButton.tsx index f7070c4c8c..9a24eb9173 100644 --- a/src/components/PostControls/PostControlButton.tsx +++ b/src/components/PostControls/PostControlButton.tsx @@ -128,7 +128,12 @@ export function PostControlButtonText({style, ...props}: TextProps) { return ( ) diff --git a/src/components/PostControls/RepostButton.tsx b/src/components/PostControls/RepostButton.tsx index d4a3960a7f..f34e37c29c 100644 --- a/src/components/PostControls/RepostButton.tsx +++ b/src/components/PostControls/RepostButton.tsx @@ -157,7 +157,7 @@ let RepostButtonDialogInner = ({ variant="ghost" color="primary"> - + {isReposted ? ( Remove repost ) : ( @@ -188,7 +188,7 @@ let RepostButtonDialogInner = ({ /> diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx index 4b94e0df98..28724061fd 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -203,7 +203,7 @@ function NoConvos() { a.text_sm, t.atoms.text_contrast_high, a.text_center, - a.font_bold, + a.font_semi_bold, ]}> Start a conversation, and it will appear here. diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 095b621679..70323f9fca 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -233,7 +233,7 @@ function InlineNameAndHandle({ + 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}> - {followers} + {followers} {pluralizedFollowers} @@ -572,7 +572,7 @@ function Inner({ label={_(msg`${following} following`)} style={[t.atoms.text]} onPress={hide}> - {following} + {following} {pluralizedFollowings} diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index c4a5f0fa02..3dd308e83e 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -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, ]}> diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index 52fc4c27a9..cae307a6dd 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -28,7 +28,7 @@ export function ProgressGuideList({style}: {style?: StyleProp}) { diff --git a/src/components/ProgressGuide/Task.tsx b/src/components/ProgressGuide/Task.tsx index b9ba3fd9ab..449a28fcd3 100644 --- a/src/components/ProgressGuide/Task.tsx +++ b/src/components/ProgressGuide/Task.tsx @@ -39,7 +39,7 @@ export function ProgressGuideTask({ diff --git a/src/components/ProgressGuide/Toast.tsx b/src/components/ProgressGuide/Toast.tsx index d4ac771b25..ad0feff2ec 100644 --- a/src/components/ProgressGuide/Toast.tsx +++ b/src/components/ProgressGuide/Toast.tsx @@ -160,7 +160,7 @@ export const ProgressGuideToast = React.forwardRef< ref={animatedCheckRef} /> - {title} + {title} {subtitle && ( {subtitle} diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index 626d8316d3..a7bd895219 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -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, diff --git a/src/components/ReportDialog/SelectLabelerView.tsx b/src/components/ReportDialog/SelectLabelerView.tsx index be666ced15..02b3244428 100644 --- a/src/components/ReportDialog/SelectLabelerView.tsx +++ b/src/components/ReportDialog/SelectLabelerView.tsx @@ -24,7 +24,7 @@ export function SelectLabelerView({ return ( - + Select moderator @@ -75,7 +75,8 @@ function LabelerButton({ handle: labeler.creator.handle, })} /> - + @{labeler.creator.handle} diff --git a/src/components/ReportDialog/SelectReportOptionView.tsx b/src/components/ReportDialog/SelectReportOptionView.tsx index f5165f8f74..0fd321cdf8 100644 --- a/src/components/ReportDialog/SelectReportOptionView.tsx +++ b/src/components/ReportDialog/SelectReportOptionView.tsx @@ -85,7 +85,7 @@ export function SelectReportOptionView(props: { ) : null} - {i18n.title} + {i18n.title} {i18n.description} @@ -173,7 +173,8 @@ function ReportOptionButton({ interacted && t.atoms.bg_contrast_50, ]}> - + {title} {description} diff --git a/src/components/ReportDialog/SubmitView.tsx b/src/components/ReportDialog/SubmitView.tsx index 418f364a7e..3dbb1389d2 100644 --- a/src/components/ReportDialog/SubmitView.tsx +++ b/src/components/ReportDialog/SubmitView.tsx @@ -125,7 +125,7 @@ export function SubmitView({ t.atoms.border_contrast_low, ]}> - + {selectedReportOption.title} diff --git a/src/components/StarterPack/Wizard/ScreenTransition.tsx b/src/components/ScreenTransition.tsx similarity index 51% rename from src/components/StarterPack/Wizard/ScreenTransition.tsx rename to src/components/ScreenTransition.tsx index c02888e1d1..8c4e7e01f5 100644 --- a/src/components/StarterPack/Wizard/ScreenTransition.tsx +++ b/src/components/ScreenTransition.tsx @@ -1,5 +1,6 @@ import {type StyleProp, type ViewStyle} from 'react-native' import Animated, { + Easing, FadeIn, FadeOut, SlideInLeft, @@ -13,17 +14,25 @@ export function ScreenTransition({ direction, style, children, + enabledWeb, }: { direction: 'Backward' | 'Forward' style?: StyleProp children: React.ReactNode + enabledWeb?: boolean }) { - const entering = direction === 'Forward' ? SlideInRight : SlideInLeft + const entering = + direction === 'Forward' + ? SlideInRight.easing(Easing.out(Easing.exp)) + : SlideInLeft.easing(Easing.out(Easing.exp)) + const webEntering = enabledWeb ? FadeIn.duration(90) : undefined + const exiting = FadeOut.duration(90) // Totally vibes based + const webExiting = enabledWeb ? FadeOut.duration(90) : undefined return ( {children} diff --git a/src/components/SearchError.tsx b/src/components/SearchError.tsx index 443bbab8f8..d478ac8850 100644 --- a/src/components/SearchError.tsx +++ b/src/components/SearchError.tsx @@ -34,7 +34,13 @@ export function SearchError({ {maxWidth: gtMobile ? 394 : 294}, gtMobile ? a.gap_md : a.gap_sm, ]}> - + {title} {children} diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index 82520f12a2..33a828911e 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -280,7 +280,7 @@ export function ItemText({children}: ItemTextProps) { // eslint-disable-next-line bsky-internal/avoid-unwrapped-text return ( - {children} + {children} ) } diff --git a/src/components/Select/index.web.tsx b/src/components/Select/index.web.tsx index 4e92d3c517..f53749ef0d 100644 --- a/src/components/Select/index.web.tsx +++ b/src/components/Select/index.web.tsx @@ -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, ])}> diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx index 7252a11627..ecb5357e0e 100644 --- a/src/components/StarterPack/ProfileStarterPacks.tsx +++ b/src/components/StarterPack/ProfileStarterPacks.tsx @@ -255,7 +255,7 @@ function Empty() { {marginTop: a.border.borderWidth}, ]}> - + You haven't created a starter pack yet! diff --git a/src/components/StarterPack/QrCode.tsx b/src/components/StarterPack/QrCode.tsx index 4c28a41c52..64c5cf50f7 100644 --- a/src/components/StarterPack/QrCode.tsx +++ b/src/components/StarterPack/QrCode.tsx @@ -54,14 +54,19 @@ export function QrCode({ ]}> + style={[ + a.font_semi_bold, + a.text_3xl, + a.text_center, + {color: 'white'}, + ]}> {record.name} @@ -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}, ]}> diff --git a/src/components/StarterPack/ShareDialog.tsx b/src/components/StarterPack/ShareDialog.tsx index 32932fe2de..6c282f117b 100644 --- a/src/components/StarterPack/ShareDialog.tsx +++ b/src/components/StarterPack/ShareDialog.tsx @@ -77,7 +77,7 @@ function ShareDialogInner({ ) : ( - + Invite people to this starter pack! diff --git a/src/components/StarterPack/StarterPackCard.tsx b/src/components/StarterPack/StarterPackCard.tsx index d889b2e725..e58c2ed271 100644 --- a/src/components/StarterPack/StarterPackCard.tsx +++ b/src/components/StarterPack/StarterPackCard.tsx @@ -77,7 +77,7 @@ export function Card({ {record.name} @@ -97,7 +97,7 @@ export function Card({ ) : null} {!!joinedAllTimeCount && joinedAllTimeCount >= 50 && ( - + joined! diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx index 7dfde900f8..b1103bb4c9 100644 --- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -105,7 +105,7 @@ export function WizardEditListDialog({ : [a.pb_sm, a.align_end], ]}> - + {state.currentStep === 'Profiles' ? ( Edit People ) : ( diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx index 09c265d780..4b74311577 100644 --- a/src/components/StarterPack/Wizard/WizardListCard.tsx +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -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, diff --git a/src/components/SubtleWebHover.web.tsx b/src/components/SubtleWebHover.web.tsx index af00cf43a0..8e7b48f93e 100644 --- a/src/components/SubtleWebHover.web.tsx +++ b/src/components/SubtleWebHover.web.tsx @@ -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 diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index 50e0fc5ed2..b2ee8140e9 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -90,7 +90,7 @@ export function TrendingTopic({ 0 && ( - + {formatCount(i18n, likeCount)} @@ -235,7 +236,8 @@ export function VideoPostCard({ {repostCount > 0 && ( - + {formatCount(i18n, repostCount)} @@ -520,7 +522,11 @@ export function CompactVideoPostCard({ + style={[ + a.text_sm, + a.font_semi_bold, + {color: 'white'}, + ]}> {formatCount(i18n, likeCount)} diff --git a/src/components/WelcomeModal.tsx b/src/components/WelcomeModal.tsx index 7c9ecd84de..1271a5f3ac 100644 --- a/src/components/WelcomeModal.tsx +++ b/src/components/WelcomeModal.tsx @@ -108,7 +108,7 @@ export function WelcomeModal({control}: WelcomeModalProps) { @@ -127,7 +127,7 @@ export function WelcomeModal({control}: WelcomeModalProps) { - + Who can interact with this post? - + Keep me posted diff --git a/src/components/ageAssurance/AgeAssuranceAdmonition.tsx b/src/components/ageAssurance/AgeAssuranceAdmonition.tsx index e53c37b345..028e1dad52 100644 --- a/src/components/ageAssurance/AgeAssuranceAdmonition.tsx +++ b/src/components/ageAssurance/AgeAssuranceAdmonition.tsx @@ -82,13 +82,13 @@ function Inner({ {children} - + Learn more in your{' '} { logger.metric('ageAssurance:navigateToSettings', {}) }}> diff --git a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx index b9dcff7062..a63c348fbd 100644 --- a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx @@ -89,7 +89,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) { - + Contact us diff --git a/src/components/ageAssurance/AgeAssuranceBadge.tsx b/src/components/ageAssurance/AgeAssuranceBadge.tsx index 030e30529d..f8b5ee9b29 100644 --- a/src/components/ageAssurance/AgeAssuranceBadge.tsx +++ b/src/components/ageAssurance/AgeAssuranceBadge.tsx @@ -29,7 +29,7 @@ export function AgeAssuranceBadge() { - + {success ? Success! : Verify your age} diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx index b1c287e1b3..c45bc3a2c8 100644 --- a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx @@ -160,7 +160,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { a.pb_md, ]}> - + Success @@ -209,7 +209,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { ]}> {error && } - + {error ? Connection issue : Verifying} diff --git a/src/components/ageAssurance/AgeRestrictedScreen.tsx b/src/components/ageAssurance/AgeRestrictedScreen.tsx index 1430aaaff9..b6a8c26a36 100644 --- a/src/components/ageAssurance/AgeRestrictedScreen.tsx +++ b/src/components/ageAssurance/AgeRestrictedScreen.tsx @@ -62,7 +62,7 @@ export function AgeRestrictedScreen({ - + You must complete age assurance in order to access this screen. diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx index 0b8dfb5405..e1c73b67cb 100644 --- a/src/components/dialogs/BirthDateSettings.tsx +++ b/src/components/dialogs/BirthDateSettings.tsx @@ -38,7 +38,7 @@ export function BirthDateSettingsDialog({ label={_(msg`My Birthday`)} style={web({maxWidth: 400})}> - + My Birthday diff --git a/src/components/dialogs/DeviceLocationRequestDialog.tsx b/src/components/dialogs/DeviceLocationRequestDialog.tsx index ac33624061..b6547d4f06 100644 --- a/src/components/dialogs/DeviceLocationRequestDialog.tsx +++ b/src/components/dialogs/DeviceLocationRequestDialog.tsx @@ -108,7 +108,7 @@ function DeviceLocationRequestDialogInner({onLocationAcquired}: Props) { return ( - + Confirm your location diff --git a/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx b/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx index 2b0e86c611..d10d5cab7a 100644 --- a/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx +++ b/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx @@ -153,7 +153,7 @@ export function Disable() { return ( - + Disable email 2FA @@ -163,7 +163,7 @@ export function Disable() { style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> To disable your email 2FA method, please verify your access to{' '} - {currentAccount?.email} + {currentAccount?.email} @@ -214,7 +214,7 @@ export function Disable() { style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> To disable your email 2FA method, please verify your access to{' '} - {currentAccount?.email} + {currentAccount?.email} diff --git a/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx b/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx index bf893701c6..4a3f438df4 100644 --- a/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx +++ b/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx @@ -88,7 +88,7 @@ export function Enable() { return ( - + Enable email 2FA diff --git a/src/components/dialogs/EmailDialog/screens/Update.tsx b/src/components/dialogs/EmailDialog/screens/Update.tsx index be0af88076..a0ec69c859 100644 --- a/src/components/dialogs/EmailDialog/screens/Update.tsx +++ b/src/components/dialogs/EmailDialog/screens/Update.tsx @@ -199,7 +199,7 @@ export function Update(_props: ScreenProps) { return ( - + Update your email @@ -239,7 +239,7 @@ export function Update(_props: ScreenProps) { <> - + Security step required ) { - + Success! diff --git a/src/components/dialogs/EmailDialog/screens/VerificationReminder.tsx b/src/components/dialogs/EmailDialog/screens/VerificationReminder.tsx index d6c946956f..77ee6ae094 100644 --- a/src/components/dialogs/EmailDialog/screens/VerificationReminder.tsx +++ b/src/components/dialogs/EmailDialog/screens/VerificationReminder.tsx @@ -58,7 +58,7 @@ export function VerificationReminder({ - + Please verify your email diff --git a/src/components/dialogs/EmailDialog/screens/Verify.tsx b/src/components/dialogs/EmailDialog/screens/Verify.tsx index 07aef6145f..74ff9c96be 100644 --- a/src/components/dialogs/EmailDialog/screens/Verify.tsx +++ b/src/components/dialogs/EmailDialog/screens/Verify.tsx @@ -174,7 +174,7 @@ export function Verify({config, showScreen}: ScreenProps) { return ( - + @@ -197,7 +197,7 @@ export function Verify({config, showScreen}: ScreenProps) { return ( - + {state.step === 'email' ? ( state.mutationStatus === 'success' ? ( <> @@ -239,7 +239,7 @@ export function Verify({config, showScreen}: ScreenProps) { state.mutationStatus === 'success' ? ( We sent an email to{' '} - + {currentAccount!.email} {' '} containing a link. Please click on it to complete the email @@ -248,7 +248,7 @@ export function Verify({config, showScreen}: ScreenProps) { ) : ( We'll send an email to{' '} - + {currentAccount!.email} {' '} containing a link. Please click on it to complete the email @@ -258,7 +258,7 @@ export function Verify({config, showScreen}: ScreenProps) { ) : ( Please enter the code we sent to{' '} - + {currentAccount!.email} {' '} below. diff --git a/src/components/dialogs/Embed.tsx b/src/components/dialogs/Embed.tsx index 594800c823..a61004fd2e 100644 --- a/src/components/dialogs/Embed.tsx +++ b/src/components/dialogs/Embed.tsx @@ -104,7 +104,7 @@ function EmbedDialogInner({ - + Embed post - + Color theme - + External Media diff --git a/src/components/dialogs/InAppBrowserConsent.tsx b/src/components/dialogs/InAppBrowserConsent.tsx index 4459c64db9..5ac3ad9165 100644 --- a/src/components/dialogs/InAppBrowserConsent.tsx +++ b/src/components/dialogs/InAppBrowserConsent.tsx @@ -62,7 +62,7 @@ function InAppBrowserConsentInner({href}: {href?: string}) { - + How should we open this link? diff --git a/src/components/dialogs/LinkWarning.tsx b/src/components/dialogs/LinkWarning.tsx index 9ae8718127..74be78f9d3 100644 --- a/src/components/dialogs/LinkWarning.tsx +++ b/src/components/dialogs/LinkWarning.tsx @@ -68,7 +68,7 @@ function InAppBrowserConsentInner({ }> - + {potentiallyMisleading ? ( Potentially misleading link ) : ( @@ -151,7 +151,8 @@ function LinkBox({href}: {href: string}) { ]}> {scheme} - + {hostname} {rest} diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index e289e1641b..dc9158edeb 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -108,7 +108,12 @@ function MutedWordsInner() { + style={[ + a.text_md, + a.font_semi_bold, + a.pb_sm, + t.atoms.text_contrast_high, + ]}> Add muted words and tags @@ -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, ]}> Duration: @@ -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, ]}> Mute in: @@ -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, ]}> Options: @@ -362,7 +367,7 @@ function MutedWordsInner() { @@ -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}{' '} in{' '} - + text & tags @@ -476,7 +482,8 @@ function MutedWordRow({ {word.value}{' '} in{' '} - + tags diff --git a/src/components/dialogs/PostInteractionSettingsDialog.tsx b/src/components/dialogs/PostInteractionSettingsDialog.tsx index dbb966fd3f..01194ef651 100644 --- a/src/components/dialogs/PostInteractionSettingsDialog.tsx +++ b/src/components/dialogs/PostInteractionSettingsDialog.tsx @@ -84,7 +84,7 @@ export function PostInteractionSettingsControlledDialog({ ]}> You can set default interaction settings in{' '} - + Settings → Moderation → Interaction settings . @@ -100,7 +100,7 @@ export function PostInteractionSettingsControlledDialog({ export function Header() { return ( - + Post interaction settings @@ -329,7 +329,7 @@ export function PostInteractionSettingsForm({ - + Quote settings @@ -385,7 +385,7 @@ export function PostInteractionSettingsForm({ opacity: replySettingsDisabled ? 0.3 : 1, }, ]}> - + Reply settings @@ -535,7 +535,9 @@ function Selectable({ }, style, ]}> - {label} + + {label} + {isSelected ? ( ) : ( diff --git a/src/components/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx index 4259f3760d..41b31b255c 100644 --- a/src/components/dialogs/SearchablePeopleList.tsx +++ b/src/components/dialogs/SearchablePeopleList.tsx @@ -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, ]}> diff --git a/src/components/dialogs/StarterPackDialog.tsx b/src/components/dialogs/StarterPackDialog.tsx index 6a502072ca..13b04e2017 100644 --- a/src/components/dialogs/StarterPackDialog.tsx +++ b/src/components/dialogs/StarterPackDialog.tsx @@ -172,7 +172,7 @@ function StarterPackList({ isWeb ? a.mb_2xl : a.my_lg, a.align_center, ]}> - + Add to starter packs + ), + [onPressCancel, _], + ) + + const saveButton = useCallback( + () => ( + + ), + [ + _, + t, + dirty, + onPressSave, + isCreatingList, + isUpdatingList, + displayNameTooLong, + descriptionTooLong, + ], + ) + + const onChangeDisplayName = useCallback( + (text: string) => { + setDisplayName(text) + if (text.length > 0 && displayNameTooShort) { + setDisplayNameTooShort(false) + } + }, + [displayNameTooShort], + ) + + const onChangeDescription = useCallback( + (newText: string) => { + const richText = new RichTextAPI({text: newText}) + richText.detectFacetsWithoutResolution() + + setDescriptionRt(richText) + }, + [setDescriptionRt], + ) + + const title = list + ? isCurateList + ? _(msg`Edit user list`) + : _(msg`Edit moderation list`) + : isCurateList + ? _(msg`Create user list`) + : _(msg`Create moderation list`) + + return ( + + {title} + + }> + {isUpdateListError && ( + + )} + {isCreateListError && ( + + )} + {imageError !== '' && } + + + + List avatar + + + + + + + + List name + + + + + {(displayNameTooLong || displayNameTooShort) && ( + + {displayNameTooLong ? ( + + List name is too long.{' '} + + + ) : displayNameTooShort ? ( + List must have a name. + ) : null} + + )} + + + + + List description + + + + + {descriptionTooLong && ( + + + List description is too long.{' '} + + + + )} + + + + ) +} diff --git a/src/components/dialogs/nuxs/ActivitySubscriptions.tsx b/src/components/dialogs/nuxs/ActivitySubscriptions.tsx index b9f3979ed4..5947b56015 100644 --- a/src/components/dialogs/nuxs/ActivitySubscriptions.tsx +++ b/src/components/dialogs/nuxs/ActivitySubscriptions.tsx @@ -66,7 +66,7 @@ export function ActivitySubscriptionsNUX() { - + A new form of verification @@ -123,7 +123,7 @@ export function InitialVerificationAnnouncement() { - + Who can verify? @@ -138,7 +138,7 @@ export function InitialVerificationAnnouncement() { Trust emerges from relationships, communities, and shared context, so we’re also enabling{' '} - trusted verifiers: + trusted verifiers: organizations that can directly issue verification. diff --git a/src/components/dms/ChatEmptyPill.tsx b/src/components/dms/ChatEmptyPill.tsx index 042c3ad76b..c11ac5ed9a 100644 --- a/src/components/dms/ChatEmptyPill.tsx +++ b/src/components/dms/ChatEmptyPill.tsx @@ -89,7 +89,9 @@ export function ChatEmptyPill() { onPress={onPress} onPressIn={onPressIn} onPressOut={onPressOut}> - + {prompts[promptIndex]} diff --git a/src/components/dms/DateDivider.tsx b/src/components/dms/DateDivider.tsx index a9c82e8ea2..ca70468340 100644 --- a/src/components/dms/DateDivider.tsx +++ b/src/components/dms/DateDivider.tsx @@ -67,7 +67,8 @@ let DateDivider = ({date: dateStr}: {date: string}): React.ReactNode => { a.px_md, ]}> - + {date} {' '} at {time} diff --git a/src/components/dms/EmojiPopup.android.tsx b/src/components/dms/EmojiPopup.android.tsx index 2205dcdea8..c2222e5bf4 100644 --- a/src/components/dms/EmojiPopup.android.tsx +++ b/src/components/dms/EmojiPopup.android.tsx @@ -51,7 +51,7 @@ export function EmojiPopup({ a.border_b, t.atoms.border_contrast_low, ]}> - + Add Reaction - {copy.title} + {copy.title} Your report will be sent to the Bluesky Moderation Service @@ -213,10 +213,11 @@ function SubmitStep({ )} - + Reason: {' '} - {reportOption.title} + {reportOption.title} @@ -346,7 +347,7 @@ function DoneStep({ return ( - + Report submitted diff --git a/src/components/forms/FormError.tsx b/src/components/forms/FormError.tsx index d51243d505..d28c89c8d4 100644 --- a/src/components/forms/FormError.tsx +++ b/src/components/forms/FormError.tsx @@ -20,7 +20,8 @@ export function FormError({error}: {error?: string}) { ]}> - + {error} diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index 85fb7c481a..48f71e73a0 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -15,7 +15,6 @@ import { android, applyFonts, atoms as a, - ios, platform, type TextStyleProp, tokens, @@ -202,17 +201,23 @@ export function createInput(Component: typeof TextInput) { a.px_xs, { // paddingVertical doesn't work w/multiline - esb - lineHeight: a.text_md.fontSize * 1.1875, + lineHeight: a.text_md.fontSize * 1.2, textAlignVertical: rest.multiline ? 'top' : undefined, minHeight: rest.multiline ? 80 : undefined, minWidth: 0, + paddingTop: 13, + paddingBottom: 13, }, - ios({paddingTop: 12, paddingBottom: 13}), - // Needs to be sm on Paper, md on Fabric for some godforsaken reason -sfn - android(a.py_sm), - // fix for autofill styles covering border + android({ + paddingTop: 8, + paddingBottom: 9, + }), + /* + * Margins are needed here to avoid autofill background overlapping the + * top and bottom borders - esb + */ web({ - paddingTop: 10, + paddingTop: 11, paddingBottom: 11, marginTop: 2, marginBottom: 2, @@ -262,7 +267,7 @@ export function createInput(Component: typeof TextInput) { a.absolute, a.inset_0, a.rounded_sm, - t.atoms.bg_contrast_25, + t.atoms.bg_contrast_50, {borderColor: 'transparent', borderWidth: 2}, ctx.hovered ? chromeHover : {}, ctx.focused ? chromeFocus : {}, @@ -287,7 +292,12 @@ export function LabelText({ return ( + style={[ + a.text_sm, + a.font_semi_bold, + t.atoms.text_contrast_medium, + a.mb_sm, + ]}> {children} ) diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx index bb9fde2e11..d6a968ecf7 100644 --- a/src/components/forms/Toggle.tsx +++ b/src/components/forms/Toggle.tsx @@ -249,7 +249,7 @@ export function LabelText({ return ( diff --git a/src/components/intents/VerifyEmailIntentDialog.tsx b/src/components/intents/VerifyEmailIntentDialog.tsx index ab628eeef9..3aca1b6d82 100644 --- a/src/components/intents/VerifyEmailIntentDialog.tsx +++ b/src/components/intents/VerifyEmailIntentDialog.tsx @@ -75,7 +75,7 @@ function Inner({}: {control: DialogControlProps}) { ) : status === 'success' ? ( - + Email Verified @@ -87,7 +87,7 @@ function Inner({}: {control: DialogControlProps}) { ) : status === 'failure' ? ( - + Invalid Verification Code @@ -100,13 +100,13 @@ function Inner({}: {control: DialogControlProps}) { ) : ( - + Email Resent We have sent another verification email to{' '} - + {currentAccount?.email} . diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index 5561be18e0..2580ef28f3 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -82,7 +82,7 @@ export function Inner() { style={[ t.atoms.text_contrast_medium, a.text_sm, - a.font_bold, + a.font_semi_bold, ]}> {' '} @@ -101,7 +101,7 @@ export function Inner() { style={[ t.atoms.text, a.text_sm, - a.font_bold, + a.font_semi_bold, {opacity: 0.7}, // NOTE: we use opacity 0.7 instead of a color to match the color of the home pager tab bar ]}> {topic.topic} diff --git a/src/components/interstitials/TrendingVideos.tsx b/src/components/interstitials/TrendingVideos.tsx index 6be64335a2..175f92fd57 100644 --- a/src/components/interstitials/TrendingVideos.tsx +++ b/src/components/interstitials/TrendingVideos.tsx @@ -82,7 +82,7 @@ export function TrendingVideos() { a.align_center, a.justify_between, ]}> - + Trending Videos - - - - - + + + - - - + + + + + + + + ) } diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index f3488e485d..22dd23d7f7 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -95,7 +95,11 @@ export const SplashScreen = ({ )} + style={[ + a.text_md, + a.font_semi_bold, + t.atoms.text_contrast_medium, + ]}> What's up? diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx index 9fd426a9b3..c79b8a5794 100644 --- a/src/view/com/auth/server-input/index.tsx +++ b/src/view/com/auth/server-input/index.tsx @@ -126,7 +126,7 @@ function DialogInner({ accessibilityDescribedBy="dialog-description" accessibilityLabelledBy="dialog-title"> - + Choose your account provider @@ -1913,7 +1913,7 @@ function VideoUploadToolbar({state}: {state: VideoState}) { progress={wheelProgress} /> - {text} + {text} ) } diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 5d95a9b61d..462779151f 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -101,7 +101,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { {sanitizeDisplayName( diff --git a/src/view/com/composer/GifAltText.tsx b/src/view/com/composer/GifAltText.tsx index ceee17eaa0..6401840b1f 100644 --- a/src/view/com/composer/GifAltText.tsx +++ b/src/view/com/composer/GifAltText.tsx @@ -95,7 +95,7 @@ export function GifAltTextDialogLoaded({ )} ALT @@ -206,7 +206,8 @@ function AltTextInner({ {/* below the text input to force tab order */} - + Add alt text diff --git a/src/view/com/composer/labels/LabelsBtn.tsx b/src/view/com/composer/labels/LabelsBtn.tsx index 902d89b7bb..592d954a44 100644 --- a/src/view/com/composer/labels/LabelsBtn.tsx +++ b/src/view/com/composer/labels/LabelsBtn.tsx @@ -108,7 +108,7 @@ function DialogInner({ style={[{maxWidth: 500}, a.w_full]}> - + Add a content warning @@ -123,7 +123,7 @@ function DialogInner({ - + Adult Content @@ -181,7 +181,7 @@ function DialogInner({ - + Other diff --git a/src/view/com/composer/photos/EditImageDialog.web.tsx b/src/view/com/composer/photos/EditImageDialog.web.tsx index cda4e9ecfa..b448fad3a7 100644 --- a/src/view/com/composer/photos/EditImageDialog.web.tsx +++ b/src/view/com/composer/photos/EditImageDialog.web.tsx @@ -19,7 +19,7 @@ import {type EditImageDialogProps} from './EditImageDialog' export function EditImageDialog(props: EditImageDialogProps) { return ( - + diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx index b356cde9b7..405b7a87ca 100644 --- a/src/view/com/composer/photos/ImageAltTextDialog.tsx +++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx @@ -89,7 +89,7 @@ const ImageAltTextInner = ({ - + Add alt text diff --git a/src/view/com/composer/select-language/PostLanguageSelect.tsx b/src/view/com/composer/select-language/PostLanguageSelect.tsx index 2bc425e840..7bfc9a4171 100644 --- a/src/view/com/composer/select-language/PostLanguageSelect.tsx +++ b/src/view/com/composer/select-language/PostLanguageSelect.tsx @@ -137,7 +137,7 @@ function LanguageBtn( @@ -262,7 +262,7 @@ export function DialogInner({ style={[ a.px_0, a.py_md, - a.font_bold, + a.font_semi_bold, a.text_xs, t.atoms.text_contrast_low, a.pt_3xl, diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 9f6cc6ae22..6a8595c128 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -353,7 +353,7 @@ export function TextInput({ {displayName} diff --git a/src/view/com/composer/videos/SubtitleDialog.tsx b/src/view/com/composer/videos/SubtitleDialog.tsx index 298e70896a..a3e8d62b5f 100644 --- a/src/view/com/composer/videos/SubtitleDialog.tsx +++ b/src/view/com/composer/videos/SubtitleDialog.tsx @@ -97,7 +97,7 @@ function SubtitleDialogInner({ return ( - + Alt text @@ -128,7 +128,7 @@ function SubtitleDialogInner({ a.my_md, ]} /> - + Captions (.vtt) )} {file.name} diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 18e2807a84..453ff6982c 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -139,7 +139,7 @@ export function FeedSourceCardLoaded({ {feed.displayName} @@ -165,7 +165,7 @@ export function FeedSourceCardLoaded({ diff --git a/src/view/com/feeds/MissingFeed.tsx b/src/view/com/feeds/MissingFeed.tsx index 3d281a7316..8017c094a4 100644 --- a/src/view/com/feeds/MissingFeed.tsx +++ b/src/view/com/feeds/MissingFeed.tsx @@ -67,7 +67,7 @@ export function MissingFeed({ {type === 'feed' ? ( Feed unavailable @@ -128,7 +128,7 @@ function DialogInner({ } style={web({maxWidth: 500})}> - + {type === 'feed' ? ( Could not connect to feed service ) : ( @@ -147,7 +147,7 @@ function DialogInner({ )} - + {type === 'feed' ? ( Feed creator ) : ( @@ -184,7 +184,8 @@ function DialogInner({ )} {type === 'feed' && ( <> - + Feed identifier @@ -194,7 +195,8 @@ function DialogInner({ )} {error instanceof Error && ( <> - + Error message diff --git a/src/view/com/modals/CreateOrEditList.tsx b/src/view/com/modals/CreateOrEditList.tsx deleted file mode 100644 index 3687dce901..0000000000 --- a/src/view/com/modals/CreateOrEditList.tsx +++ /dev/null @@ -1,403 +0,0 @@ -import {useCallback, useMemo, useState} from 'react' -import { - ActivityIndicator, - KeyboardAvoidingView, - ScrollView, - StyleSheet, - TextInput, - TouchableOpacity, - View, -} from 'react-native' -import {LinearGradient} from 'expo-linear-gradient' -import {type AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {cleanError, isNetworkError} from '#/lib/strings/errors' -import {enforceLen} from '#/lib/strings/helpers' -import {richTextToString} from '#/lib/strings/rich-text-helpers' -import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' -import {colors, gradients, s} from '#/lib/styles' -import {useTheme} from '#/lib/ThemeContext' -import {type ImageMeta} from '#/state/gallery' -import {useModalControls} from '#/state/modals' -import { - useListCreateMutation, - useListMetadataMutation, -} from '#/state/queries/list' -import {useAgent} from '#/state/session' -import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' -import {Text} from '#/view/com/util/text/Text' -import * as Toast from '#/view/com/util/Toast' -import {EditableUserAvatar} from '#/view/com/util/UserAvatar' - -const MAX_NAME = 64 // todo -const MAX_DESCRIPTION = 300 // todo - -export const snapPoints = ['fullscreen'] - -export function Component({ - purpose, - onSave, - list, -}: { - purpose?: string - onSave?: (uri: string) => void - list?: AppBskyGraphDefs.ListView -}) { - const {closeModal} = useModalControls() - const {isMobile} = useWebMediaQueries() - const [error, setError] = useState('') - const pal = usePalette('default') - const theme = useTheme() - const {_} = useLingui() - const listCreateMutation = useListCreateMutation() - const listMetadataMutation = useListMetadataMutation() - const agent = useAgent() - - const activePurpose = useMemo(() => { - if (list?.purpose) { - return list.purpose - } - if (purpose) { - return purpose - } - return 'app.bsky.graph.defs#curatelist' - }, [list, purpose]) - const isCurateList = activePurpose === 'app.bsky.graph.defs#curatelist' - - const [isProcessing, setProcessing] = useState(false) - const [name, setName] = useState(list?.name || '') - - const [descriptionRt, setDescriptionRt] = useState(() => { - const text = list?.description - const facets = list?.descriptionFacets - - if (!text || !facets) { - return new RichTextAPI({text: text || ''}) - } - - // We want to be working with a blank state here, so let's get the - // serialized version and turn it back into a RichText - const serialized = richTextToString(new RichTextAPI({text, facets}), false) - - const richText = new RichTextAPI({text: serialized}) - richText.detectFacetsWithoutResolution() - - return richText - }) - const graphemeLength = useMemo(() => { - return shortenLinks(descriptionRt).graphemeLength - }, [descriptionRt]) - const isDescriptionOver = graphemeLength > MAX_DESCRIPTION - - const [avatar, setAvatar] = useState(list?.avatar) - const [newAvatar, setNewAvatar] = useState() - - const onDescriptionChange = useCallback( - (newText: string) => { - const richText = new RichTextAPI({text: newText}) - richText.detectFacetsWithoutResolution() - - setDescriptionRt(richText) - }, - [setDescriptionRt], - ) - - const onPressCancel = useCallback(() => { - closeModal() - }, [closeModal]) - - const onSelectNewAvatar = useCallback( - (img: ImageMeta | null) => { - if (!img) { - setNewAvatar(null) - setAvatar(undefined) - return - } - try { - setNewAvatar(img) - setAvatar(img.path) - } catch (e: any) { - setError(cleanError(e)) - } - }, - [setNewAvatar, setAvatar, setError], - ) - - const onPressSave = useCallback(async () => { - const nameTrimmed = name.trim() - if (!nameTrimmed) { - setError(_(msg`Name is required`)) - return - } - setProcessing(true) - if (error) { - setError('') - } - try { - let richText = new RichTextAPI( - {text: descriptionRt.text.trimEnd()}, - {cleanNewlines: true}, - ) - - await richText.detectFacets(agent) - richText = shortenLinks(richText) - richText = stripInvalidMentions(richText) - - if (list) { - await listMetadataMutation.mutateAsync({ - uri: list.uri, - name: nameTrimmed, - description: richText.text, - descriptionFacets: richText.facets, - avatar: newAvatar, - }) - Toast.show( - isCurateList - ? _(msg({message: 'User list updated', context: 'toast'})) - : _(msg({message: 'Moderation list updated', context: 'toast'})), - ) - onSave?.(list.uri) - } else { - const res = await listCreateMutation.mutateAsync({ - purpose: activePurpose, - name, - description: richText.text, - descriptionFacets: richText.facets, - avatar: newAvatar, - }) - Toast.show( - isCurateList - ? _(msg({message: 'User list created', context: 'toast'})) - : _(msg({message: 'Moderation list created', context: 'toast'})), - ) - onSave?.(res.uri) - } - closeModal() - } catch (e: any) { - if (isNetworkError(e)) { - setError( - _( - msg`Failed to create the list. Check your internet connection and try again.`, - ), - ) - } else { - setError(cleanError(e)) - } - } - setProcessing(false) - }, [ - setProcessing, - setError, - error, - onSave, - closeModal, - activePurpose, - isCurateList, - name, - descriptionRt, - newAvatar, - list, - listMetadataMutation, - listCreateMutation, - _, - agent, - ]) - - return ( - - - - {isCurateList ? ( - list ? ( - Edit User List - ) : ( - New User List - ) - ) : list ? ( - Edit Moderation List - ) : ( - New Moderation List - )} - - {error !== '' && ( - - - - )} - - List Avatar - - - - - - - - - List Name - - - setName(enforceLen(v, MAX_NAME))} - accessible={true} - accessibilityLabel={_(msg`Name`)} - accessibilityHint="" - accessibilityLabelledBy="list-name" - /> - - - - - Description - - - {graphemeLength}/{MAX_DESCRIPTION} - - - - - {isProcessing ? ( - - - - ) : ( - - - - Save - - - - )} - - - - Cancel - - - - - - - ) -} - -const styles = StyleSheet.create({ - title: { - textAlign: 'center', - fontWeight: '600', - fontSize: 24, - marginBottom: 18, - }, - labelWrapper: { - flexDirection: 'row', - gap: 8, - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 4, - paddingBottom: 4, - marginTop: 20, - }, - label: { - fontWeight: '600', - }, - form: { - paddingHorizontal: 6, - }, - textInput: { - borderWidth: 1, - borderRadius: 6, - paddingHorizontal: 14, - paddingVertical: 10, - fontSize: 16, - }, - textArea: { - borderWidth: 1, - borderRadius: 6, - paddingHorizontal: 12, - paddingTop: 10, - fontSize: 16, - height: 100, - textAlignVertical: 'top', - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - borderRadius: 32, - padding: 10, - marginBottom: 10, - }, - avi: { - width: 84, - height: 84, - borderWidth: 2, - borderRadius: 42, - marginTop: 4, - }, - errorContainer: {marginTop: 20}, -}) diff --git a/src/view/com/modals/CropImage.web.tsx b/src/view/com/modals/CropImage.web.tsx deleted file mode 100644 index 78c0466f0b..0000000000 --- a/src/view/com/modals/CropImage.web.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {manipulateAsync, SaveFormat} from 'expo-image-manipulator' -import {LinearGradient} from 'expo-linear-gradient' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import ReactCrop, {type PercentCrop} from 'react-image-crop' - -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {type PickerImage} from '#/lib/media/picker.shared' -import {getDataUriSize} from '#/lib/media/util' -import {gradients, s} from '#/lib/styles' -import {useModalControls} from '#/state/modals' -import {Text} from '#/view/com/util/text/Text' - -export const snapPoints = ['0%'] - -export function Component({ - uri, - aspect, - circular, - onSelect, -}: { - uri: string - aspect?: number - circular?: boolean - onSelect: (img?: PickerImage) => void -}) { - const pal = usePalette('default') - const {_} = useLingui() - - const {closeModal} = useModalControls() - const {isMobile} = useWebMediaQueries() - - const imageRef = React.useRef(null) - const [crop, setCrop] = React.useState() - - const isEmpty = !crop || (crop.width || crop.height) === 0 - - const onPressCancel = () => { - onSelect(undefined) - closeModal() - } - const onPressDone = async () => { - const img = imageRef.current! - - const result = await manipulateAsync( - uri, - isEmpty - ? [] - : [ - { - crop: { - originX: (crop.x * img.naturalWidth) / 100, - originY: (crop.y * img.naturalHeight) / 100, - width: (crop.width * img.naturalWidth) / 100, - height: (crop.height * img.naturalHeight) / 100, - }, - }, - ], - { - base64: true, - format: SaveFormat.JPEG, - }, - ) - - onSelect({ - path: result.uri, - mime: 'image/jpeg', - size: result.base64 !== undefined ? getDataUriSize(result.base64) : 0, - width: result.width, - height: result.height, - }) - - closeModal() - } - - return ( - - - setCrop(percentCrop)} - circularCrop={circular}> - - - - - - - Cancel - - - - - - - Done - - - - - - ) -} - -const styles = StyleSheet.create({ - cropper: { - marginLeft: 'auto', - marginRight: 'auto', - borderWidth: 1, - borderRadius: 4, - overflow: 'hidden', - alignItems: 'center', - }, - ctrls: { - flexDirection: 'row', - alignItems: 'center', - marginTop: 10, - }, - btns: { - flexDirection: 'row', - alignItems: 'center', - marginTop: 10, - }, - btn: { - borderRadius: 4, - paddingVertical: 8, - paddingHorizontal: 24, - }, -}) diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx deleted file mode 100644 index 93f7490625..0000000000 --- a/src/view/com/modals/InviteCodes.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import React from 'react' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' -import {setStringAsync} from 'expo-clipboard' -import {type ComAtprotoServerDefs} from '@atproto/api' -import { - FontAwesomeIcon, - type FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {makeProfileLink} from '#/lib/routes/links' -import {cleanError} from '#/lib/strings/errors' -import {isWeb} from '#/platform/detection' -import {useInvitesAPI, useInvitesState} from '#/state/invites' -import {useModalControls} from '#/state/modals' -import { - type InviteCodesQueryResponse, - useInviteCodesQuery, -} from '#/state/queries/invites' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {Button} from '../util/forms/Button' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' -import * as Toast from '../util/Toast' -import {UserInfoText} from '../util/UserInfoText' -import {ScrollView} from './util' - -export const snapPoints = ['70%'] - -export function Component() { - const {isLoading, data: invites, error} = useInviteCodesQuery() - - return error ? ( - - ) : isLoading || !invites ? ( - - - - ) : ( - - ) -} - -export function Inner({invites}: {invites: InviteCodesQueryResponse}) { - const pal = usePalette('default') - const {_} = useLingui() - const {closeModal} = useModalControls() - const {isTabletOrDesktop} = useWebMediaQueries() - - const onClose = React.useCallback(() => { - closeModal() - }, [closeModal]) - - if (invites.all.length === 0) { - return ( - - - - - You don't have any invite codes yet! We'll send you some when - you've been on Bluesky for a little longer. - - - - - - + + + ) } diff --git a/src/view/screens/ModerationModlists.tsx b/src/view/screens/ModerationModlists.tsx index 23ed492f64..1f786d88bc 100644 --- a/src/view/screens/ModerationModlists.tsx +++ b/src/view/screens/ModerationModlists.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import {useCallback} from 'react' import {AtUri} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -10,11 +10,12 @@ import { type NativeStackScreenProps, } from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types' -import {useModalControls} from '#/state/modals' import {useSetMinimalShellMode} from '#/state/shell' import {MyLists} from '#/view/com/lists/MyLists' import {atoms as a} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {CreateOrEditListDialog} from '#/components/dialogs/lists/CreateOrEditListDialog' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import * as Layout from '#/components/Layout' @@ -23,30 +24,18 @@ export function ModerationModlistsScreen({}: Props) { const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() const navigation = useNavigation() - const {openModal} = useModalControls() const requireEmailVerification = useRequireEmailVerification() + const createListDialogControl = useDialogControl() useFocusEffect( - React.useCallback(() => { + useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]), ) - const onPressNewList = React.useCallback(() => { - openModal({ - name: 'create-or-edit-list', - purpose: 'app.bsky.graph.defs#modlist', - onSave: (uri: string) => { - try { - const urip = new AtUri(uri) - navigation.navigate('ProfileList', { - name: urip.hostname, - rkey: urip.rkey, - }) - } catch {} - }, - }) - }, [openModal, navigation]) + const onPressNewList = useCallback(() => { + createListDialogControl.open() + }, [createListDialogControl]) const wrappedOnPressNewList = requireEmailVerification(onPressNewList, { instructions: [ @@ -56,6 +45,19 @@ export function ModerationModlistsScreen({}: Props) { ], }) + const onCreateList = useCallback( + (uri: string) => { + try { + const urip = new AtUri(uri) + navigation.navigate('ProfileList', { + name: urip.hostname, + rkey: urip.rkey, + }) + } catch {} + }, + [navigation], + ) + return ( @@ -78,7 +80,14 @@ export function ModerationModlistsScreen({}: Props) { + + + ) } diff --git a/src/view/screens/ProfileFeedLikedBy.tsx b/src/view/screens/ProfileFeedLikedBy.tsx index 0a2ffc0976..d26ff61725 100644 --- a/src/view/screens/ProfileFeedLikedBy.tsx +++ b/src/view/screens/ProfileFeedLikedBy.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import {msg} from '@lingui/macro' +import {useCallback} from 'react' +import {Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' @@ -10,8 +10,6 @@ import { import {makeRecordUri} from '#/lib/strings/url-helpers' import {useSetMinimalShellMode} from '#/state/shell' import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {CenteredView} from '#/view/com/util/Views' import * as Layout from '#/components/Layout' type Props = NativeStackScreenProps @@ -22,17 +20,23 @@ export const ProfileFeedLikedByScreen = ({route}: Props) => { const {_} = useLingui() useFocusEffect( - React.useCallback(() => { + useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]), ) return ( - - - - + + + + + Liked By + + + + + ) } diff --git a/src/view/screens/Storybook/Admonitions.tsx b/src/view/screens/Storybook/Admonitions.tsx index 988342f171..6badb9ddfc 100644 --- a/src/view/screens/Storybook/Admonitions.tsx +++ b/src/view/screens/Storybook/Admonitions.tsx @@ -1,11 +1,27 @@ -import {View} from 'react-native' +import {Text as RNText, View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' -import {atoms as a} from '#/alf' -import {Admonition} from '#/components/Admonition' +import {atoms as a, useTheme} from '#/alf' +import { + Admonition, + Button as AdmonitionButton, + Content as AdmonitionContent, + Icon as AdmonitionIcon, + Outer as AdmonitionOuter, + Row as AdmonitionRow, + Text as AdmonitionText, +} from '#/components/Admonition' +import {ButtonIcon, ButtonText} from '#/components/Button' +import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise' +import {BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon} from '#/components/icons/BellRinging' import {InlineLinkText} from '#/components/Link' import {H1} from '#/components/Typography' export function Admonitions() { + const {_} = useLingui() + const t = useTheme() + return (

Admonitions

@@ -30,6 +46,61 @@ export function Admonitions() { The quick brown fox jumps over the lazy dog. + + + + + + + Something went wrong, please try again + + + {}}> + + Retry + + + + + + + + + + + + + Enable notifications for an account by visiting their profile + and pressing the{' '} + + bell icon + {' '} + + . + + + + + If you want to restrict who can receive notifications for your + account's activity, you can change this in{' '} + + Settings → Privacy and Security + + . + + + + +
) } diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx index 0db0629135..f6a2c36a0b 100644 --- a/src/view/screens/Storybook/Buttons.tsx +++ b/src/view/screens/Storybook/Buttons.tsx @@ -8,6 +8,7 @@ import { ButtonIcon, type ButtonSize, ButtonText, + StackedButton, } from '#/components/Button' import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' @@ -16,7 +17,31 @@ import {Text} from '#/components/Typography' export function Buttons() { return ( - Buttons + Buttons + + + + Bop it + + + Twist it + + + Pull it + + {[ 'primary', @@ -29,7 +54,7 @@ export function Buttons() { {['tiny', 'small', 'large'].map(size => ( - + color={color} size={size} diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx index dddd47a5fd..196dfac060 100644 --- a/src/view/screens/Storybook/Settings.tsx +++ b/src/view/screens/Storybook/Settings.tsx @@ -106,7 +106,7 @@ export function Settings() { color={t.palette.primary_500} /> + style={[{color: t.palette.primary_500}, a.font_semi_bold]}> Protect your account diff --git a/src/view/screens/Storybook/Theming.tsx b/src/view/screens/Storybook/Theming.tsx index 673425b477..8f153f4790 100644 --- a/src/view/screens/Storybook/Theming.tsx +++ b/src/view/screens/Storybook/Theming.tsx @@ -11,20 +11,20 @@ export function Theming() { - theme.atoms.text + theme.atoms.text - + theme.atoms.text_contrast_high - + theme.atoms.text_contrast_medium - + theme.atoms.text_contrast_low diff --git a/src/view/screens/Storybook/Typography.tsx b/src/view/screens/Storybook/Typography.tsx index 3f22091e14..711c8e02c7 100644 --- a/src/view/screens/Storybook/Typography.tsx +++ b/src/view/screens/Storybook/Typography.tsx @@ -26,12 +26,12 @@ export function Typography() { This is medium italic text - This is bold text - + This is bold text + This is bold italic text - This is heavy text - + This is heavy text + This is heavy italic text diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 832e4fc3a4..ed2a6cfb7e 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -90,7 +90,7 @@ let DrawerProfileCard = ({ {profile?.displayName || account.handle} @@ -115,7 +115,7 @@ let DrawerProfileCard = ({ - + {formatCount(i18n, profile?.followersCount ?? 0)} {' '} {' '} ·{' '} - + {formatCount(i18n, profile?.followsCount ?? 0)} {' '} diff --git a/src/view/shell/NavSignupCard.tsx b/src/view/shell/NavSignupCard.tsx index 000f5824c0..062e4f880d 100644 --- a/src/view/shell/NavSignupCard.tsx +++ b/src/view/shell/NavSignupCard.tsx @@ -36,7 +36,7 @@ let NavSignupCard = ({}: {}): React.ReactNode => { + style={[a.text_3xl, a.font_bold, {lineHeight: a.text_3xl.fontSize}]}> Join the conversation diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 441b35e3b5..4cbf04cda4 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -90,7 +90,7 @@ export function DesktopFeeds() { a.text_md, a.leading_snug, current - ? [a.font_bold, t.atoms.text] + ? [a.font_semi_bold, t.atoms.text] : [t.atoms.text_contrast_medium], web({ marginHorizontal: 2, diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index a51e98f31d..2c44a5b00f 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -161,7 +161,7 @@ function ProfileCard() { }, ]}> {sanitizeDisplayName( profile.displayName || profile.handle, @@ -462,7 +462,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { style={[ a.absolute, a.text_xs, - a.font_bold, + a.font_semi_bold, a.rounded_full, a.text_center, a.leading_tight, @@ -509,7 +509,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { ) : null} {!leftNavMinimal && ( - + {label} )} diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index c8ef49ee7d..11dcff3a4d 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -53,7 +53,7 @@ function Inner() { style={[ a.flex_1, a.text_sm, - a.font_bold, + a.font_semi_bold, t.atoms.text_contrast_medium, ]}> Trending diff --git a/yarn.lock b/yarn.lock index 8c09828e7c..dfab6f354a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3635,6 +3635,13 @@ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== +"@bsky.app/alf@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@bsky.app/alf/-/alf-0.1.2.tgz#8e3b3cd0b27f1dfe359d99edd2afd7d123b7ab69" + integrity sha512-lIidmkoHsqXwj07BY4fYIu/JyWGXRAGLfh65qhGzWpXLTK1DGyoQUjz5CwsqTDSQWpCfBC7eEaOtYjjv9cAjSQ== + dependencies: + react-responsive "^10.0.1" + "@bufbuild/protobuf@^1.5.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.7.0.tgz#cecddc8162a231642b410bc7b99309cd5969733c" @@ -17064,11 +17071,6 @@ react-native-reanimated@^3.19.1: invariant "^2.2.4" react-native-is-edge-to-edge "1.1.7" -react-native-root-siblings@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c" - integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g== - react-native-safe-area-context@~5.6.0: version "5.6.1" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6"