Merge remote-tracking branch 'origin/main' into eric/temporary-language-selection
* origin/main: (29 commits) add patent pledge link to readme (#9118) Fix feedfeedback metrics not distinguishing which feed it's from (#9099) Nightly source-language update Add StackedButton component (#9086) [Web] Fix thread jumps (#9111) Button tweaks (#9106) Update admonition component (#9068) fix gap on profile (#9081) Nightly source-language update Catch errors on geolocation request, reduce Sentry logs (#9098) remove root sibling library (#9097) don't send some "activity no longer available" errors (#9100) Log OTA errors properly (#9101) Fix link crash (#9102) Send inferrable interactions to third-party feeds (#9094) patch react native screens (#9087) fix: remove escapejs and fix profile issues feat: profile.html escapejs feat: post.html structured content, wip feat: profile.html structured content ...
This commit is contained in:
@@ -37,6 +37,7 @@ module.exports = {
|
||||
'Toast.Action',
|
||||
'AgeAssuranceAdmonition',
|
||||
'Span',
|
||||
'StackedButton',
|
||||
],
|
||||
impliedTextProps: [],
|
||||
suggestedTextWrappers: {
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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!
|
||||
|
||||
+56
-23
@@ -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)
|
||||
})
|
||||
|
||||
@@ -34,6 +34,16 @@ export class Database {
|
||||
|
||||
static postgres(opts: PgOptions): Database {
|
||||
const {schema, url, txLockNonce} = opts
|
||||
log.info(
|
||||
{
|
||||
schema,
|
||||
poolSize: opts.poolSize,
|
||||
poolMaxUses: opts.poolMaxUses,
|
||||
poolIdleTimeoutMs: opts.poolIdleTimeoutMs,
|
||||
},
|
||||
'Creating database connection',
|
||||
)
|
||||
|
||||
const pool =
|
||||
opts.pool ??
|
||||
new Pg.Pool({
|
||||
|
||||
+26
-4
@@ -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`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,41 @@
|
||||
<meta property="article:published_time" content="{{ postView.IndexedAt }}">
|
||||
<link rel="alternate" type="application/json+oembed" href="https://embed.bsky.app/oembed?format=json&url={{ postView.Uri | urlencode }}" />
|
||||
<link rel="alternate" href="{{ postView.Uri }}" />
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "DiscussionForumPosting",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "{{ postView.Author.DisplayName }}",
|
||||
"url": "https://bsky.app/profile/{{ postView.Author.Handle }}"
|
||||
},
|
||||
{%- if postText %}
|
||||
"text": "{{ postText }}",
|
||||
{% endif %}
|
||||
{%- if imageThumbUrls %}
|
||||
"image": "{{ imageThumbUrls[0] }}",
|
||||
{% endif %}
|
||||
"datePublished": "{{ postView.IndexedAt }}",
|
||||
"interactionStatistic": [
|
||||
{
|
||||
"@type": "InteractionCounter",
|
||||
"interactionType": "https://schema.org/LikeAction",
|
||||
"userInteractionCount": {{ postView.LikeCount }}
|
||||
},
|
||||
{
|
||||
"@type": "InteractionCounter",
|
||||
"interactionType": "https://schema.org/CommentAction",
|
||||
"userInteractionCount": {{ postView.ReplyCount }}
|
||||
},
|
||||
{
|
||||
"@type": "InteractionCounter",
|
||||
"interactionType": "https://schema.org/ShareAction",
|
||||
"userInteractionCount": {{ postView.RepostCount + postView.QuoteCount }}
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{%- elif requiresAuth and profileHandle -%}
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="profile:username" content="{{ profileHandle }}">
|
||||
|
||||
@@ -51,6 +51,41 @@
|
||||
<meta property="og:description" content="This profile requires authentication to view.">
|
||||
<meta property="twitter:description" content="This profile requires authentication to view.">
|
||||
{% endif %}
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ProfilePage",
|
||||
"dateCreated": "{{ profileView.CreatedAt }}",
|
||||
"mainEntity": {
|
||||
"@type": "Person",
|
||||
"name": "{{ profileView.DisplayName }}",
|
||||
"alternateName": "@{{ profileView.Handle }}",
|
||||
"identifier": "{{ profileView.Did }}",
|
||||
"description": "{{ profileView.Description }}",
|
||||
"image": "{{ profileView.Avatar }}",
|
||||
"interactionStatistic": [
|
||||
{
|
||||
"@type": "InteractionCounter",
|
||||
"interactionType": "https://schema.org/FollowAction",
|
||||
"userInteractionCount": {{ profileView.FollowersCount }}
|
||||
}
|
||||
],
|
||||
"agentInteractionStatistic": [
|
||||
{
|
||||
"@type": "InteractionCounter",
|
||||
"interactionType": "https://schema.org/FollowAction",
|
||||
"userInteractionCount": {{ profileView.FollowsCount }}
|
||||
},
|
||||
{
|
||||
"@type": "InteractionCounter",
|
||||
"interactionType": "https://schema.org/WriteAction",
|
||||
"userInteractionCount": {{ profileView.PostsCount }}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endif -%}
|
||||
{%- endblock %}
|
||||
|
||||
|
||||
+8
-12
@@ -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
|
||||
<GestureHandlerRootView style={{flex: 1}}>
|
||||
<RootSiblingParent>
|
||||
<RootStoreProvider value={rootStore}>
|
||||
<ThemeProvider theme="light">
|
||||
<SafeAreaProvider>{ui}</SafeAreaProvider>
|
||||
</ThemeProvider>
|
||||
</RootStoreProvider>
|
||||
</RootSiblingParent>
|
||||
<RootStoreProvider value={rootStore}>
|
||||
<ThemeProvider theme="light">
|
||||
<SafeAreaProvider>{ui}</SafeAreaProvider>
|
||||
</ThemeProvider>
|
||||
</RootStoreProvider>
|
||||
</GestureHandlerRootView>,
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm
|
||||
index b62a2e2..cb469db 100644
|
||||
--- a/node_modules/react-native-screens/ios/RNSScreen.mm
|
||||
+++ b/node_modules/react-native-screens/ios/RNSScreen.mm
|
||||
@@ -729,9 +729,26 @@ - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingFor
|
||||
#endif
|
||||
}
|
||||
|
||||
-#if !RCT_NEW_ARCH_ENABLED
|
||||
+- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
+{
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ // In iOS 26, as soon as another screen appears in transition, it is interactable
|
||||
+ // To avoid glitches resulting from clicking buttons mid transition, we temporarily disable all interactions
|
||||
+ // Disabling interactions for parent navigation controller won't be enough in case of nested stack
|
||||
+ // Furthermore, a stack put inside a modal will exist in an entirely different hierarchy
|
||||
+ // To be sure, we block interactions on the whole window.
|
||||
+ // Note that newWindows is nil when moving from instead of moving to, and Obj-C handles nil correctly
|
||||
+ newWindow.userInteractionEnabled = false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
- (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ // Disable interactions to disallow multiple modals dismissed at once; see willMoveToWindow
|
||||
+ presentationController.containerView.window.userInteractionEnabled = false;
|
||||
+ }
|
||||
+#if !RCT_NEW_ARCH_ENABLED
|
||||
// On Paper, we need to call both "cancel" and "reset" here because RN's gesture
|
||||
// recognizer does not handle the scenario when it gets cancelled by other top
|
||||
// level gesture recognizer. In this case by the modal dismiss gesture.
|
||||
@@ -744,8 +761,8 @@ - (void)presentationControllerWillDismiss:(UIPresentationController *)presentati
|
||||
// down.
|
||||
[_touchHandler cancel];
|
||||
[_touchHandler reset];
|
||||
-}
|
||||
#endif // !RCT_NEW_ARCH_ENABLED
|
||||
+}
|
||||
|
||||
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
@@ -757,6 +774,10 @@ - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presenta
|
||||
|
||||
- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ // Reenable interactions; see presentationControllerWillDismiss
|
||||
+ presentationController.containerView.window.userInteractionEnabled = true;
|
||||
+ }
|
||||
// NOTE(kkafar): We should consider depracating the use of gesture cancel here & align
|
||||
// with usePreventRemove API of react-navigation v7.
|
||||
[self notifyGestureCancel];
|
||||
@@ -767,6 +788,11 @@ - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)pr
|
||||
|
||||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ // Reenable interactions; see presentationControllerWillDismiss
|
||||
+ // Dismissed screen doesn't hold a reference to window, but presentingViewController.view does
|
||||
+ presentationController.presentingViewController.view.window.userInteractionEnabled = true;
|
||||
+ }
|
||||
if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
|
||||
[_reactSuperview performSelector:@selector(presentationControllerDidDismiss:) withObject:presentationController];
|
||||
}
|
||||
@@ -1518,6 +1544,10 @@ - (void)viewWillDisappear:(BOOL)animated
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ // Reenable interactions, see willMoveToWindow
|
||||
+ self.view.window.userInteractionEnabled = true;
|
||||
+ }
|
||||
[super viewDidAppear:animated];
|
||||
if (!_isSwiping || _shouldNotify) {
|
||||
// we are going forward or dismissing without swipe
|
||||
diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm
|
||||
index 229dc58..10b365b 100644
|
||||
--- a/node_modules/react-native-screens/ios/RNSScreenStack.mm
|
||||
+++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm
|
||||
@@ -62,26 +62,6 @@ @interface RNSScreenStackView () <
|
||||
|
||||
@implementation RNSNavigationController
|
||||
|
||||
-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
||||
-- (void)viewDidLoad
|
||||
-{
|
||||
- // iOS 26 introduces new gesture recognizer which replaces our RNSPanGestureRecognizer.
|
||||
- // The problem is that we are not able to handle it here for various reasons:
|
||||
- // - the new recognizer comes with its own delegate and our current approach is to wire
|
||||
- // all recognizers to RNSScreenStackView; to be 100% sure we don't break the logic,
|
||||
- // we would have to decorate its delegate and call it after our code, which would
|
||||
- // break other recognizers that the stack view is the delegate for
|
||||
- // - when RNSScreenStackView.setupGestureHandler method is called, the recognizer hasn't been
|
||||
- // loaded yet and there is no other place to configure in a not "hacky" way
|
||||
- // - the official docs warn us to not use it for anything other than "setting up failure requirements with it"
|
||||
- // - we expose fullScreenGestureEnabled prop to enable/disable the feature,
|
||||
- // so we need control over the delegate
|
||||
- if (@available(iOS 26.0, *)) {
|
||||
- self.interactiveContentPopGestureRecognizer.enabled = NO;
|
||||
- }
|
||||
-}
|
||||
-#endif // iOS 26
|
||||
-
|
||||
#if !TARGET_OS_TV
|
||||
- (UIViewController *)childViewControllerForStatusBarStyle
|
||||
{
|
||||
@@ -219,50 +199,6 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController
|
||||
return false;
|
||||
}
|
||||
|
||||
-#pragma mark - UINavigationBarDelegate
|
||||
-
|
||||
-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
||||
-- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
|
||||
-{
|
||||
- if (@available(iOS 26, *)) {
|
||||
- // To prevent popping multiple screens when back button is pressed repeatedly,
|
||||
- // We allow for pop operation to proceed only if no transition is in progress,
|
||||
- // which we check indirectly by checking if transitionCoordinator is set.
|
||||
- // If it's not, we are safe to proceed.
|
||||
- if (self.transitionCoordinator == nil) {
|
||||
- // We still need to disable interactions for back button so click effects are not applied,
|
||||
- // and there is unfortunately no better place for it currently
|
||||
- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
|
||||
- if (button != nil) {
|
||||
- button.userInteractionEnabled = false;
|
||||
- }
|
||||
-
|
||||
- return true;
|
||||
- }
|
||||
-
|
||||
- return false;
|
||||
- }
|
||||
-
|
||||
- return true;
|
||||
-}
|
||||
-
|
||||
-- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
|
||||
-{
|
||||
- if (@available(iOS 26, *)) {
|
||||
- // Reset interactions on back button -> see navigationBar:shouldPopItem
|
||||
- // IMPORTANT: This reset won't execute when preventNativeDismiss is on.
|
||||
- // However, on iOS 26, unlike in previous versions, the back button instance changes
|
||||
- // when handling preventNativeDismiss and userIteractionEnabled is reset.
|
||||
- // The instance also changes when regular screen pop happens, but in that case
|
||||
- // the value of userInteractionEnabled is carried on, and we reset it here.
|
||||
- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
|
||||
- if (button != nil) {
|
||||
- button.userInteractionEnabled = true;
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
-#endif // Check for iOS >= 26
|
||||
-
|
||||
#pragma mark - RNSFrameCorrectionProvider
|
||||
|
||||
#ifdef RNS_GAMMA_ENABLED
|
||||
@@ -327,7 +263,7 @@ @implementation RNSScreenStackView {
|
||||
UINavigationController *_controller;
|
||||
NSMutableArray<RNSScreenView *> *_reactSubviews;
|
||||
BOOL _invalidated;
|
||||
- BOOL _isFullWidthSwiping;
|
||||
+ BOOL _isFullWidthSwipingWithPanGesture; // used only for content swipe with RNSPanGestureRecognizer
|
||||
RNSPercentDrivenInteractiveTransition *_interactionController;
|
||||
__weak RNSScreenStackManager *_manager;
|
||||
BOOL _updateScheduled;
|
||||
@@ -522,6 +458,11 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller
|
||||
[self addSubview:controller.view];
|
||||
#if !TARGET_OS_TV
|
||||
_controller.interactivePopGestureRecognizer.delegate = self;
|
||||
+ #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ _controller.interactiveContentPopGestureRecognizer.delegate = self;
|
||||
+ }
|
||||
+#endif // Check for iOS >= 26.0
|
||||
#endif
|
||||
[controller didMoveToParentViewController:parentView.reactViewController];
|
||||
// On iOS pre 12 we observed that `willShowViewController` delegate method does not always
|
||||
@@ -943,7 +884,7 @@ - (void)dismissOnReload
|
||||
// when preventing the native dismiss with back button, we have to return the animator.
|
||||
// Also, we need to return the animator when full width swiping even if the animation is not custom,
|
||||
// otherwise the screen will be just popped immediately due to no animation
|
||||
- ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwiping ||
|
||||
+ ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwipingWithPanGesture ||
|
||||
[RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation] || _customAnimation)) {
|
||||
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
|
||||
}
|
||||
@@ -967,23 +908,39 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||
}
|
||||
RNSScreenView *topScreen = _reactSubviews.lastObject;
|
||||
|
||||
+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
|
||||
+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
|
||||
+
|
||||
#if TARGET_OS_TV || TARGET_OS_VISION
|
||||
[self cancelTouchesInParent];
|
||||
return YES;
|
||||
#else
|
||||
- // RNSPanGestureRecognizer will receive events iff topScreen.fullScreenSwipeEnabled == YES;
|
||||
- // Events are filtered in gestureRecognizer:shouldReceivePressOrTouchEvent: method
|
||||
if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
|
||||
- if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
|
||||
- _isFullWidthSwiping = YES;
|
||||
- [self cancelTouchesInParent];
|
||||
- return YES;
|
||||
+ // On iOS < 26, we have a custom full screen swipe recognizer that functions similarily
|
||||
+ // to interactiveContentPopGestureRecognizer introduced in iOS 26.
|
||||
+ // On iOS >= 26, we want to use the native one, but we are unable to handle custom animations
|
||||
+ // with native interactiveContentPopGestureRecognizer, so we have to fallback to the old implementation.
|
||||
+ // In this case, the old one should behave as close as the new native one, having only the difference
|
||||
+ // in animation, and without any customization that is exclusive for it (e.g. gestureResponseDistance).
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
|
||||
+ _isFullWidthSwipingWithPanGesture = YES;
|
||||
+ [self cancelTouchesInParent];
|
||||
+ return YES;
|
||||
+ }
|
||||
+ return NO;
|
||||
+ } else {
|
||||
+ if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
|
||||
+ _isFullWidthSwipingWithPanGesture = YES;
|
||||
+ [self cancelTouchesInParent];
|
||||
+ return YES;
|
||||
+ }
|
||||
+ return NO;
|
||||
}
|
||||
- return NO;
|
||||
}
|
||||
|
||||
// Now we're dealing with RNSScreenEdgeGestureRecognizer (or _UIParallaxTransitionPanGestureRecognizer)
|
||||
- if (topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]) {
|
||||
+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
|
||||
if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) {
|
||||
UIRectEdge edges = ((RNSScreenEdgeGestureRecognizer *)gestureRecognizer).edges;
|
||||
BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft;
|
||||
@@ -1028,7 +985,9 @@ - (void)setupGestureHandlers
|
||||
rightEdgeSwipeGestureRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:rightEdgeSwipeGestureRecognizer];
|
||||
|
||||
- // gesture recognizer for full width swipe gesture
|
||||
+ // Starting from iOS 26, RNSPanGestureRecognizer has been mostly replaced by native
|
||||
+ // interactiveContentPopGestureRecognizer. It still needs to handle custom dismiss animations,
|
||||
+ // which we are not able to handle with the latter.
|
||||
RNSPanGestureRecognizer *panRecognizer = [[RNSPanGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleSwipe:)];
|
||||
panRecognizer.delegate = self;
|
||||
@@ -1091,7 +1050,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
|
||||
[_interactionController cancelInteractiveTransition];
|
||||
}
|
||||
_interactionController = nil;
|
||||
- _isFullWidthSwiping = NO;
|
||||
+ _isFullWidthSwipingWithPanGesture = NO;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
@@ -1225,14 +1184,6 @@ - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognize
|
||||
// Be careful when adding another type of gesture recognizer.
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePressOrTouchEvent:(NSObject *)event
|
||||
{
|
||||
- if (@available(iOS 26, *)) {
|
||||
- // in iOS 26, you can swipe to pop screen before the previous one finished transitioning;
|
||||
- // this prevents from registering the second gesture
|
||||
- if ([self isTransitionInProgress]) {
|
||||
- return NO;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
RNSScreenView *topScreen = _reactSubviews.lastObject;
|
||||
|
||||
for (RNSScreenView *s in _reactSubviews.reverseObjectEnumerator) {
|
||||
@@ -1249,10 +1200,30 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
|
||||
+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
|
||||
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
||||
+ if (@available(iOS 26, *)) {
|
||||
+ // On iOS 26, fullScreenSwipeEnabled takes no effect, and depending on whether custom animations are on,
|
||||
+ // we select either interactiveContentPopGestureRecognizer or RNSPanGestureRecognizer
|
||||
+ if (([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] &&
|
||||
+ !customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) ||
|
||||
+ (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
|
||||
+ customAnimationOnSwipePropSetAndSelectedAnimationIsCustom)) {
|
||||
+ return NO;
|
||||
+ }
|
||||
+ } else {
|
||||
+ // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
|
||||
+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
|
||||
+ return topScreen.fullScreenSwipeEnabled;
|
||||
+ }
|
||||
+ }
|
||||
+#else // check for iOS >= 26
|
||||
// We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
|
||||
if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
|
||||
return topScreen.fullScreenSwipeEnabled;
|
||||
}
|
||||
+#endif // check for iOS >= 26
|
||||
|
||||
// RNSScreenEdgeGestureRecognizer || _UIParallaxTransitionPanGestureRecognizer
|
||||
return YES;
|
||||
@@ -1268,15 +1239,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
|
||||
return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:touch];
|
||||
}
|
||||
|
||||
-- (BOOL)isTransitionInProgress
|
||||
-{
|
||||
- if (_controller.transitionCoordinator != nil) {
|
||||
- return YES;
|
||||
- }
|
||||
-
|
||||
- return NO;
|
||||
-}
|
||||
-
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
||||
{
|
||||
@@ -1289,7 +1251,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
||||
if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) {
|
||||
return NO;
|
||||
}
|
||||
-
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
+77
-79
@@ -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() {
|
||||
<ThemeProvider theme={theme}>
|
||||
<ContextMenuProvider>
|
||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||
<RootSiblingParent>
|
||||
<VideoVolumeProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceAccountManager>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<GlobalGestureEventsProvider>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</GlobalGestureEventsProvider>
|
||||
</GestureHandlerRootView>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceAccountManager>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</VideoVolumeProvider>
|
||||
</RootSiblingParent>
|
||||
<VideoVolumeProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceAccountManager>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<GlobalGestureEventsProvider>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</GlobalGestureEventsProvider>
|
||||
</GestureHandlerRootView>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceAccountManager>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</VideoVolumeProvider>
|
||||
</Splash>
|
||||
</ContextMenuProvider>
|
||||
</ThemeProvider>
|
||||
@@ -225,24 +225,22 @@ function App() {
|
||||
<PrefsStateProvider>
|
||||
<I18nProvider>
|
||||
<ShellStateProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<BottomSheetProvider>
|
||||
<StarterPackProvider>
|
||||
<SafeAreaProvider
|
||||
initialMetrics={initialWindowMetrics}>
|
||||
<InnerApp />
|
||||
</SafeAreaProvider>
|
||||
</StarterPackProvider>
|
||||
</BottomSheetProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<BottomSheetProvider>
|
||||
<StarterPackProvider>
|
||||
<SafeAreaProvider
|
||||
initialMetrics={initialWindowMetrics}>
|
||||
<InnerApp />
|
||||
</SafeAreaProvider>
|
||||
</StarterPackProvider>
|
||||
</BottomSheetProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</ShellStateProvider>
|
||||
</I18nProvider>
|
||||
</PrefsStateProvider>
|
||||
|
||||
+65
-71
@@ -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() {
|
||||
<Alf theme={theme}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ContextMenuProvider>
|
||||
<RootSiblingParent>
|
||||
<VideoVolumeProvider>
|
||||
<ActiveVideoProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<SafeAreaProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceConfigProvider>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceConfigProvider>
|
||||
</ProgressGuideProvider>
|
||||
</SafeAreaProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</ActiveVideoProvider>
|
||||
</VideoVolumeProvider>
|
||||
</RootSiblingParent>
|
||||
<VideoVolumeProvider>
|
||||
<ActiveVideoProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<SafeAreaProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceConfigProvider>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceConfigProvider>
|
||||
</ProgressGuideProvider>
|
||||
</SafeAreaProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</ActiveVideoProvider>
|
||||
</VideoVolumeProvider>
|
||||
</ContextMenuProvider>
|
||||
</ThemeProvider>
|
||||
</Alf>
|
||||
@@ -199,19 +195,17 @@ function App() {
|
||||
<PrefsStateProvider>
|
||||
<I18nProvider>
|
||||
<ShellStateProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<StarterPackProvider>
|
||||
<InnerApp />
|
||||
</StarterPackProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<StarterPackProvider>
|
||||
<InnerApp />
|
||||
</StarterPackProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</ShellStateProvider>
|
||||
</I18nProvider>
|
||||
</PrefsStateProvider>
|
||||
|
||||
+4
-997
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -7,11 +7,11 @@ const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe
|
||||
|
||||
const factor = 0.0625 // 1 - (15/16)
|
||||
const fontScaleMultipliers: Record<Device['fontScale'], number> = {
|
||||
'-2': 1 - factor * 3,
|
||||
'-1': 1 - factor * 2,
|
||||
'0': 1 - factor * 1, // default
|
||||
'1': 1,
|
||||
'2': 1 + factor * 1,
|
||||
'-2': 1 - factor * 1, // unused
|
||||
'-1': 1 - factor * 1,
|
||||
'0': 1, // default
|
||||
'1': 1 + factor * 1,
|
||||
'2': 1 + factor * 1, // unused
|
||||
}
|
||||
|
||||
export function computeFontScaleMultiplier(scale: Device['fontScale']) {
|
||||
|
||||
+13
-30
@@ -1,4 +1,5 @@
|
||||
import React from 'react'
|
||||
import {type Theme, type ThemeName} from '@bsky.app/alf'
|
||||
|
||||
import {
|
||||
computeFontScaleMultiplier,
|
||||
@@ -7,16 +8,14 @@ import {
|
||||
setFontFamily as persistFontFamily,
|
||||
setFontScale as persistFontScale,
|
||||
} from '#/alf/fonts'
|
||||
import {createThemes, defaultTheme} from '#/alf/themes'
|
||||
import {type Theme, type ThemeName} from '#/alf/types'
|
||||
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
|
||||
import {themes} from '#/alf/themes'
|
||||
import {type Device} from '#/storage'
|
||||
|
||||
export {type TextStyleProp, type Theme, type ViewStyleProp} from '@bsky.app/alf'
|
||||
export {atoms} from '#/alf/atoms'
|
||||
export * from '#/alf/breakpoints'
|
||||
export * from '#/alf/fonts'
|
||||
export * as tokens from '#/alf/tokens'
|
||||
export * from '#/alf/types'
|
||||
export * from '#/alf/util/flatten'
|
||||
export * from '#/alf/util/platform'
|
||||
export * from '#/alf/util/themeSelector'
|
||||
@@ -25,7 +24,7 @@ export * from '#/alf/util/useGutters'
|
||||
export type Alf = {
|
||||
themeName: ThemeName
|
||||
theme: Theme
|
||||
themes: ReturnType<typeof createThemes>
|
||||
themes: typeof themes
|
||||
fonts: {
|
||||
scale: Exclude<Device['fontScale'], undefined>
|
||||
scaleMultiplier: number
|
||||
@@ -44,14 +43,8 @@ export type Alf = {
|
||||
*/
|
||||
export const Context = React.createContext<Alf>({
|
||||
themeName: 'light',
|
||||
theme: defaultTheme,
|
||||
themes: createThemes({
|
||||
hues: {
|
||||
primary: BLUE_HUE,
|
||||
negative: RED_HUE,
|
||||
positive: GREEN_HUE,
|
||||
},
|
||||
}),
|
||||
theme: themes.light,
|
||||
themes,
|
||||
fonts: {
|
||||
scale: getFontScale(),
|
||||
scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
|
||||
@@ -76,10 +69,10 @@ export function ThemeProvider({
|
||||
const setFontScaleAndPersist = React.useCallback<
|
||||
Alf['fonts']['setFontScale']
|
||||
>(
|
||||
fontScale => {
|
||||
setFontScale(fontScale)
|
||||
persistFontScale(fontScale)
|
||||
setFontScaleMultiplier(computeFontScaleMultiplier(fontScale))
|
||||
fs => {
|
||||
setFontScale(fs)
|
||||
persistFontScale(fs)
|
||||
setFontScaleMultiplier(computeFontScaleMultiplier(fs))
|
||||
},
|
||||
[setFontScale],
|
||||
)
|
||||
@@ -89,21 +82,12 @@ export function ThemeProvider({
|
||||
const setFontFamilyAndPersist = React.useCallback<
|
||||
Alf['fonts']['setFontFamily']
|
||||
>(
|
||||
fontFamily => {
|
||||
setFontFamily(fontFamily)
|
||||
persistFontFamily(fontFamily)
|
||||
ff => {
|
||||
setFontFamily(ff)
|
||||
persistFontFamily(ff)
|
||||
},
|
||||
[setFontFamily],
|
||||
)
|
||||
const themes = React.useMemo(() => {
|
||||
return createThemes({
|
||||
hues: {
|
||||
primary: BLUE_HUE,
|
||||
negative: RED_HUE,
|
||||
positive: GREEN_HUE,
|
||||
},
|
||||
})
|
||||
}, [])
|
||||
|
||||
const value = React.useMemo<Alf>(
|
||||
() => ({
|
||||
@@ -121,7 +105,6 @@ export function ThemeProvider({
|
||||
}),
|
||||
[
|
||||
themeName,
|
||||
themes,
|
||||
fontScale,
|
||||
setFontScaleAndPersist,
|
||||
fontFamily,
|
||||
|
||||
+39
-580
@@ -1,585 +1,44 @@
|
||||
import {atoms} from '#/alf/atoms'
|
||||
import {type Palette, type Theme} from '#/alf/types'
|
||||
import {
|
||||
BLUE_HUE,
|
||||
defaultScale,
|
||||
dimScale,
|
||||
GREEN_HUE,
|
||||
RED_HUE,
|
||||
} from '#/alf/util/colorGeneration'
|
||||
createThemes,
|
||||
DEFAULT_PALETTE,
|
||||
DEFAULT_SUBDUED_PALETTE,
|
||||
} from '@bsky.app/alf'
|
||||
|
||||
const themes = createThemes({
|
||||
hues: {
|
||||
primary: BLUE_HUE,
|
||||
negative: RED_HUE,
|
||||
positive: GREEN_HUE,
|
||||
},
|
||||
const DEFAULT_THEMES = createThemes({
|
||||
defaultPalette: DEFAULT_PALETTE,
|
||||
subduedPalette: DEFAULT_SUBDUED_PALETTE,
|
||||
})
|
||||
|
||||
/**
|
||||
* @deprecated use ALF and access palette from `useTheme()`
|
||||
*/
|
||||
export const lightPalette = themes.lightPalette
|
||||
/**
|
||||
* @deprecated use ALF and access palette from `useTheme()`
|
||||
*/
|
||||
export const darkPalette = themes.darkPalette
|
||||
/**
|
||||
* @deprecated use ALF and access palette from `useTheme()`
|
||||
*/
|
||||
export const dimPalette = themes.dimPalette
|
||||
/**
|
||||
* @deprecated use ALF and access theme from `useTheme()`
|
||||
*/
|
||||
export const light = themes.light
|
||||
/**
|
||||
* @deprecated use ALF and access theme from `useTheme()`
|
||||
*/
|
||||
export const dark = themes.dark
|
||||
/**
|
||||
* @deprecated use ALF and access theme from `useTheme()`
|
||||
*/
|
||||
export const dim = themes.dim
|
||||
|
||||
export const defaultTheme = themes.light
|
||||
|
||||
export function createThemes({
|
||||
hues,
|
||||
}: {
|
||||
hues: {
|
||||
primary: number
|
||||
negative: number
|
||||
positive: number
|
||||
}
|
||||
}): {
|
||||
lightPalette: Palette
|
||||
darkPalette: Palette
|
||||
dimPalette: Palette
|
||||
light: Theme
|
||||
dark: Theme
|
||||
dim: Theme
|
||||
} {
|
||||
const color = {
|
||||
like: '#ec4899',
|
||||
trueBlack: '#000000',
|
||||
|
||||
gray_0: `hsl(${hues.primary}, 20%, ${defaultScale[14]}%)`,
|
||||
gray_25: `hsl(${hues.primary}, 20%, ${defaultScale[13]}%)`,
|
||||
gray_50: `hsl(${hues.primary}, 20%, ${defaultScale[12]}%)`,
|
||||
gray_100: `hsl(${hues.primary}, 20%, ${defaultScale[11]}%)`,
|
||||
gray_200: `hsl(${hues.primary}, 20%, ${defaultScale[10]}%)`,
|
||||
gray_300: `hsl(${hues.primary}, 20%, ${defaultScale[9]}%)`,
|
||||
gray_400: `hsl(${hues.primary}, 20%, ${defaultScale[8]}%)`,
|
||||
gray_500: `hsl(${hues.primary}, 20%, ${defaultScale[7]}%)`,
|
||||
gray_600: `hsl(${hues.primary}, 24%, ${defaultScale[6]}%)`,
|
||||
gray_700: `hsl(${hues.primary}, 24%, ${defaultScale[5]}%)`,
|
||||
gray_800: `hsl(${hues.primary}, 28%, ${defaultScale[4]}%)`,
|
||||
gray_900: `hsl(${hues.primary}, 28%, ${defaultScale[3]}%)`,
|
||||
gray_950: `hsl(${hues.primary}, 28%, ${defaultScale[2]}%)`,
|
||||
gray_975: `hsl(${hues.primary}, 28%, ${defaultScale[1]}%)`,
|
||||
gray_1000: `hsl(${hues.primary}, 28%, ${defaultScale[0]}%)`,
|
||||
|
||||
primary_25: `hsl(${hues.primary}, 99%, 97%)`,
|
||||
primary_50: `hsl(${hues.primary}, 99%, 95%)`,
|
||||
primary_100: `hsl(${hues.primary}, 99%, 90%)`,
|
||||
primary_200: `hsl(${hues.primary}, 99%, 80%)`,
|
||||
primary_300: `hsl(${hues.primary}, 99%, 70%)`,
|
||||
primary_400: `hsl(${hues.primary}, 99%, 60%)`,
|
||||
primary_500: `hsl(${hues.primary}, 99%, 53%)`,
|
||||
primary_600: `hsl(${hues.primary}, 99%, 42%)`,
|
||||
primary_700: `hsl(${hues.primary}, 99%, 34%)`,
|
||||
primary_800: `hsl(${hues.primary}, 99%, 26%)`,
|
||||
primary_900: `hsl(${hues.primary}, 99%, 18%)`,
|
||||
primary_950: `hsl(${hues.primary}, 99%, 10%)`,
|
||||
primary_975: `hsl(${hues.primary}, 99%, 7%)`,
|
||||
|
||||
green_25: `hsl(${hues.positive}, 82%, 97%)`,
|
||||
green_50: `hsl(${hues.positive}, 82%, 95%)`,
|
||||
green_100: `hsl(${hues.positive}, 82%, 90%)`,
|
||||
green_200: `hsl(${hues.positive}, 82%, 80%)`,
|
||||
green_300: `hsl(${hues.positive}, 82%, 70%)`,
|
||||
green_400: `hsl(${hues.positive}, 82%, 60%)`,
|
||||
green_500: `hsl(${hues.positive}, 82%, 50%)`,
|
||||
green_600: `hsl(${hues.positive}, 82%, 42%)`,
|
||||
green_700: `hsl(${hues.positive}, 82%, 34%)`,
|
||||
green_800: `hsl(${hues.positive}, 82%, 26%)`,
|
||||
green_900: `hsl(${hues.positive}, 82%, 18%)`,
|
||||
green_950: `hsl(${hues.positive}, 82%, 10%)`,
|
||||
green_975: `hsl(${hues.positive}, 82%, 7%)`,
|
||||
|
||||
red_25: `hsl(${hues.negative}, 91%, 97%)`,
|
||||
red_50: `hsl(${hues.negative}, 91%, 95%)`,
|
||||
red_100: `hsl(${hues.negative}, 91%, 90%)`,
|
||||
red_200: `hsl(${hues.negative}, 91%, 80%)`,
|
||||
red_300: `hsl(${hues.negative}, 91%, 70%)`,
|
||||
red_400: `hsl(${hues.negative}, 91%, 60%)`,
|
||||
red_500: `hsl(${hues.negative}, 91%, 50%)`,
|
||||
red_600: `hsl(${hues.negative}, 91%, 42%)`,
|
||||
red_700: `hsl(${hues.negative}, 91%, 34%)`,
|
||||
red_800: `hsl(${hues.negative}, 91%, 26%)`,
|
||||
red_900: `hsl(${hues.negative}, 91%, 18%)`,
|
||||
red_950: `hsl(${hues.negative}, 91%, 10%)`,
|
||||
red_975: `hsl(${hues.negative}, 91%, 7%)`,
|
||||
} as const
|
||||
|
||||
const lightPalette = {
|
||||
white: color.gray_0,
|
||||
black: color.gray_1000,
|
||||
like: color.like,
|
||||
|
||||
contrast_25: color.gray_25,
|
||||
contrast_50: color.gray_50,
|
||||
contrast_100: color.gray_100,
|
||||
contrast_200: color.gray_200,
|
||||
contrast_300: color.gray_300,
|
||||
contrast_400: color.gray_400,
|
||||
contrast_500: color.gray_500,
|
||||
contrast_600: color.gray_600,
|
||||
contrast_700: color.gray_700,
|
||||
contrast_800: color.gray_800,
|
||||
contrast_900: color.gray_900,
|
||||
contrast_950: color.gray_950,
|
||||
contrast_975: color.gray_975,
|
||||
|
||||
primary_25: color.primary_25,
|
||||
primary_50: color.primary_50,
|
||||
primary_100: color.primary_100,
|
||||
primary_200: color.primary_200,
|
||||
primary_300: color.primary_300,
|
||||
primary_400: color.primary_400,
|
||||
primary_500: color.primary_500,
|
||||
primary_600: color.primary_600,
|
||||
primary_700: color.primary_700,
|
||||
primary_800: color.primary_800,
|
||||
primary_900: color.primary_900,
|
||||
primary_950: color.primary_950,
|
||||
primary_975: color.primary_975,
|
||||
|
||||
positive_25: color.green_25,
|
||||
positive_50: color.green_50,
|
||||
positive_100: color.green_100,
|
||||
positive_200: color.green_200,
|
||||
positive_300: color.green_300,
|
||||
positive_400: color.green_400,
|
||||
positive_500: color.green_500,
|
||||
positive_600: color.green_600,
|
||||
positive_700: color.green_700,
|
||||
positive_800: color.green_800,
|
||||
positive_900: color.green_900,
|
||||
positive_950: color.green_950,
|
||||
positive_975: color.green_975,
|
||||
|
||||
negative_25: color.red_25,
|
||||
negative_50: color.red_50,
|
||||
negative_100: color.red_100,
|
||||
negative_200: color.red_200,
|
||||
negative_300: color.red_300,
|
||||
negative_400: color.red_400,
|
||||
negative_500: color.red_500,
|
||||
negative_600: color.red_600,
|
||||
negative_700: color.red_700,
|
||||
negative_800: color.red_800,
|
||||
negative_900: color.red_900,
|
||||
negative_950: color.red_950,
|
||||
negative_975: color.red_975,
|
||||
} as const
|
||||
|
||||
const darkPalette: Palette = {
|
||||
white: color.gray_25,
|
||||
black: color.trueBlack,
|
||||
like: color.like,
|
||||
|
||||
contrast_25: color.gray_975,
|
||||
contrast_50: color.gray_950,
|
||||
contrast_100: color.gray_900,
|
||||
contrast_200: color.gray_800,
|
||||
contrast_300: color.gray_700,
|
||||
contrast_400: color.gray_600,
|
||||
contrast_500: color.gray_500,
|
||||
contrast_600: color.gray_400,
|
||||
contrast_700: color.gray_300,
|
||||
contrast_800: color.gray_200,
|
||||
contrast_900: color.gray_100,
|
||||
contrast_950: color.gray_50,
|
||||
contrast_975: color.gray_25,
|
||||
|
||||
primary_25: color.primary_975,
|
||||
primary_50: color.primary_950,
|
||||
primary_100: color.primary_900,
|
||||
primary_200: color.primary_800,
|
||||
primary_300: color.primary_700,
|
||||
primary_400: color.primary_600,
|
||||
primary_500: color.primary_500,
|
||||
primary_600: color.primary_400,
|
||||
primary_700: color.primary_300,
|
||||
primary_800: color.primary_200,
|
||||
primary_900: color.primary_100,
|
||||
primary_950: color.primary_50,
|
||||
primary_975: color.primary_25,
|
||||
|
||||
positive_25: color.green_975,
|
||||
positive_50: color.green_950,
|
||||
positive_100: color.green_900,
|
||||
positive_200: color.green_800,
|
||||
positive_300: color.green_700,
|
||||
positive_400: color.green_600,
|
||||
positive_500: color.green_500,
|
||||
positive_600: color.green_400,
|
||||
positive_700: color.green_300,
|
||||
positive_800: color.green_200,
|
||||
positive_900: color.green_100,
|
||||
positive_950: color.green_50,
|
||||
positive_975: color.green_25,
|
||||
|
||||
negative_25: color.red_975,
|
||||
negative_50: color.red_950,
|
||||
negative_100: color.red_900,
|
||||
negative_200: color.red_800,
|
||||
negative_300: color.red_700,
|
||||
negative_400: color.red_600,
|
||||
negative_500: color.red_500,
|
||||
negative_600: color.red_400,
|
||||
negative_700: color.red_300,
|
||||
negative_800: color.red_200,
|
||||
negative_900: color.red_100,
|
||||
negative_950: color.red_50,
|
||||
negative_975: color.red_25,
|
||||
} as const
|
||||
|
||||
const dimPalette: Palette = {
|
||||
...darkPalette,
|
||||
black: `hsl(${hues.primary}, 28%, ${dimScale[0]}%)`,
|
||||
like: color.like,
|
||||
|
||||
contrast_25: `hsl(${hues.primary}, 28%, ${dimScale[1]}%)`,
|
||||
contrast_50: `hsl(${hues.primary}, 28%, ${dimScale[2]}%)`,
|
||||
contrast_100: `hsl(${hues.primary}, 28%, ${dimScale[3]}%)`,
|
||||
contrast_200: `hsl(${hues.primary}, 28%, ${dimScale[4]}%)`,
|
||||
contrast_300: `hsl(${hues.primary}, 24%, ${dimScale[5]}%)`,
|
||||
contrast_400: `hsl(${hues.primary}, 24%, ${dimScale[6]}%)`,
|
||||
contrast_500: `hsl(${hues.primary}, 20%, ${dimScale[7]}%)`,
|
||||
contrast_600: `hsl(${hues.primary}, 20%, ${dimScale[8]}%)`,
|
||||
contrast_700: `hsl(${hues.primary}, 20%, ${dimScale[9]}%)`,
|
||||
contrast_800: `hsl(${hues.primary}, 20%, ${dimScale[10]}%)`,
|
||||
contrast_900: `hsl(${hues.primary}, 20%, ${dimScale[11]}%)`,
|
||||
contrast_950: `hsl(${hues.primary}, 20%, ${dimScale[12]}%)`,
|
||||
contrast_975: `hsl(${hues.primary}, 20%, ${dimScale[13]}%)`,
|
||||
|
||||
primary_25: `hsl(${hues.primary}, 50%, ${dimScale[1]}%)`,
|
||||
primary_50: `hsl(${hues.primary}, 60%, ${dimScale[2]}%)`,
|
||||
primary_100: `hsl(${hues.primary}, 70%, ${dimScale[3]}%)`,
|
||||
primary_200: `hsl(${hues.primary}, 82%, ${dimScale[4]}%)`,
|
||||
primary_300: `hsl(${hues.primary}, 90%, ${dimScale[5]}%)`,
|
||||
primary_400: `hsl(${hues.primary}, 95%, ${dimScale[6]}%)`,
|
||||
primary_500: `hsl(${hues.primary}, 99%, ${dimScale[7]}%)`,
|
||||
primary_600: `hsl(${hues.primary}, 99%, ${dimScale[8]}%)`,
|
||||
primary_700: `hsl(${hues.primary}, 99%, ${dimScale[9]}%)`,
|
||||
primary_800: `hsl(${hues.primary}, 99%, ${dimScale[10]}%)`,
|
||||
primary_900: `hsl(${hues.primary}, 99%, ${dimScale[11]}%)`,
|
||||
primary_950: `hsl(${hues.primary}, 99%, ${dimScale[12]}%)`,
|
||||
primary_975: `hsl(${hues.primary}, 99%, ${dimScale[13]}%)`,
|
||||
|
||||
positive_25: `hsl(${hues.positive}, 50%, ${dimScale[1]}%)`,
|
||||
positive_50: `hsl(${hues.positive}, 60%, ${dimScale[2]}%)`,
|
||||
positive_100: `hsl(${hues.positive}, 70%, ${dimScale[3]}%)`,
|
||||
positive_200: `hsl(${hues.positive}, 82%, ${dimScale[4]}%)`,
|
||||
positive_300: `hsl(${hues.positive}, 82%, ${dimScale[5]}%)`,
|
||||
positive_400: `hsl(${hues.positive}, 82%, ${dimScale[6]}%)`,
|
||||
positive_500: `hsl(${hues.positive}, 82%, ${dimScale[7]}%)`,
|
||||
positive_600: `hsl(${hues.positive}, 82%, ${dimScale[8]}%)`,
|
||||
positive_700: `hsl(${hues.positive}, 82%, ${dimScale[9]}%)`,
|
||||
positive_800: `hsl(${hues.positive}, 82%, ${dimScale[10]}%)`,
|
||||
positive_900: `hsl(${hues.positive}, 82%, ${dimScale[11]}%)`,
|
||||
positive_950: `hsl(${hues.positive}, 82%, ${dimScale[12]}%)`,
|
||||
positive_975: `hsl(${hues.positive}, 82%, ${dimScale[13]}%)`,
|
||||
|
||||
negative_25: `hsl(${hues.negative}, 70%, ${dimScale[1]}%)`,
|
||||
negative_50: `hsl(${hues.negative}, 80%, ${dimScale[2]}%)`,
|
||||
negative_100: `hsl(${hues.negative}, 84%, ${dimScale[3]}%)`,
|
||||
negative_200: `hsl(${hues.negative}, 88%, ${dimScale[4]}%)`,
|
||||
negative_300: `hsl(${hues.negative}, 91%, ${dimScale[5]}%)`,
|
||||
negative_400: `hsl(${hues.negative}, 91%, ${dimScale[6]}%)`,
|
||||
negative_500: `hsl(${hues.negative}, 91%, ${dimScale[7]}%)`,
|
||||
negative_600: `hsl(${hues.negative}, 91%, ${dimScale[8]}%)`,
|
||||
negative_700: `hsl(${hues.negative}, 91%, ${dimScale[9]}%)`,
|
||||
negative_800: `hsl(${hues.negative}, 91%, ${dimScale[10]}%)`,
|
||||
negative_900: `hsl(${hues.negative}, 91%, ${dimScale[11]}%)`,
|
||||
negative_950: `hsl(${hues.negative}, 91%, ${dimScale[12]}%)`,
|
||||
negative_975: `hsl(${hues.negative}, 91%, ${dimScale[13]}%)`,
|
||||
} as const
|
||||
|
||||
const light: Theme = {
|
||||
scheme: 'light',
|
||||
name: 'light',
|
||||
palette: lightPalette,
|
||||
atoms: {
|
||||
text: {
|
||||
color: lightPalette.black,
|
||||
},
|
||||
text_contrast_low: {
|
||||
color: lightPalette.contrast_400,
|
||||
},
|
||||
text_contrast_medium: {
|
||||
color: lightPalette.contrast_700,
|
||||
},
|
||||
text_contrast_high: {
|
||||
color: lightPalette.contrast_900,
|
||||
},
|
||||
text_inverted: {
|
||||
color: lightPalette.white,
|
||||
},
|
||||
bg: {
|
||||
backgroundColor: lightPalette.white,
|
||||
},
|
||||
bg_contrast_25: {
|
||||
backgroundColor: lightPalette.contrast_25,
|
||||
},
|
||||
bg_contrast_50: {
|
||||
backgroundColor: lightPalette.contrast_50,
|
||||
},
|
||||
bg_contrast_100: {
|
||||
backgroundColor: lightPalette.contrast_100,
|
||||
},
|
||||
bg_contrast_200: {
|
||||
backgroundColor: lightPalette.contrast_200,
|
||||
},
|
||||
bg_contrast_300: {
|
||||
backgroundColor: lightPalette.contrast_300,
|
||||
},
|
||||
bg_contrast_400: {
|
||||
backgroundColor: lightPalette.contrast_400,
|
||||
},
|
||||
bg_contrast_500: {
|
||||
backgroundColor: lightPalette.contrast_500,
|
||||
},
|
||||
bg_contrast_600: {
|
||||
backgroundColor: lightPalette.contrast_600,
|
||||
},
|
||||
bg_contrast_700: {
|
||||
backgroundColor: lightPalette.contrast_700,
|
||||
},
|
||||
bg_contrast_800: {
|
||||
backgroundColor: lightPalette.contrast_800,
|
||||
},
|
||||
bg_contrast_900: {
|
||||
backgroundColor: lightPalette.contrast_900,
|
||||
},
|
||||
bg_contrast_950: {
|
||||
backgroundColor: lightPalette.contrast_950,
|
||||
},
|
||||
bg_contrast_975: {
|
||||
backgroundColor: lightPalette.contrast_975,
|
||||
},
|
||||
border_contrast_low: {
|
||||
borderColor: lightPalette.contrast_100,
|
||||
},
|
||||
border_contrast_medium: {
|
||||
borderColor: lightPalette.contrast_200,
|
||||
},
|
||||
border_contrast_high: {
|
||||
borderColor: lightPalette.contrast_300,
|
||||
},
|
||||
shadow_sm: {
|
||||
...atoms.shadow_sm,
|
||||
shadowColor: lightPalette.black,
|
||||
},
|
||||
shadow_md: {
|
||||
...atoms.shadow_md,
|
||||
shadowColor: lightPalette.black,
|
||||
},
|
||||
shadow_lg: {
|
||||
...atoms.shadow_lg,
|
||||
shadowColor: lightPalette.black,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const dark: Theme = {
|
||||
scheme: 'dark',
|
||||
name: 'dark',
|
||||
palette: darkPalette,
|
||||
atoms: {
|
||||
text: {
|
||||
color: darkPalette.white,
|
||||
},
|
||||
text_contrast_low: {
|
||||
color: darkPalette.contrast_400,
|
||||
},
|
||||
text_contrast_medium: {
|
||||
color: darkPalette.contrast_600,
|
||||
},
|
||||
text_contrast_high: {
|
||||
color: darkPalette.contrast_900,
|
||||
},
|
||||
text_inverted: {
|
||||
color: darkPalette.black,
|
||||
},
|
||||
bg: {
|
||||
backgroundColor: darkPalette.black,
|
||||
},
|
||||
bg_contrast_25: {
|
||||
backgroundColor: darkPalette.contrast_25,
|
||||
},
|
||||
bg_contrast_50: {
|
||||
backgroundColor: darkPalette.contrast_50,
|
||||
},
|
||||
bg_contrast_100: {
|
||||
backgroundColor: darkPalette.contrast_100,
|
||||
},
|
||||
bg_contrast_200: {
|
||||
backgroundColor: darkPalette.contrast_200,
|
||||
},
|
||||
bg_contrast_300: {
|
||||
backgroundColor: darkPalette.contrast_300,
|
||||
},
|
||||
bg_contrast_400: {
|
||||
backgroundColor: darkPalette.contrast_400,
|
||||
},
|
||||
bg_contrast_500: {
|
||||
backgroundColor: darkPalette.contrast_500,
|
||||
},
|
||||
bg_contrast_600: {
|
||||
backgroundColor: darkPalette.contrast_600,
|
||||
},
|
||||
bg_contrast_700: {
|
||||
backgroundColor: darkPalette.contrast_700,
|
||||
},
|
||||
bg_contrast_800: {
|
||||
backgroundColor: darkPalette.contrast_800,
|
||||
},
|
||||
bg_contrast_900: {
|
||||
backgroundColor: darkPalette.contrast_900,
|
||||
},
|
||||
bg_contrast_950: {
|
||||
backgroundColor: darkPalette.contrast_950,
|
||||
},
|
||||
bg_contrast_975: {
|
||||
backgroundColor: darkPalette.contrast_975,
|
||||
},
|
||||
border_contrast_low: {
|
||||
borderColor: darkPalette.contrast_100,
|
||||
},
|
||||
border_contrast_medium: {
|
||||
borderColor: darkPalette.contrast_200,
|
||||
},
|
||||
border_contrast_high: {
|
||||
borderColor: darkPalette.contrast_300,
|
||||
},
|
||||
shadow_sm: {
|
||||
...atoms.shadow_sm,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: color.trueBlack,
|
||||
},
|
||||
shadow_md: {
|
||||
...atoms.shadow_md,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: color.trueBlack,
|
||||
},
|
||||
shadow_lg: {
|
||||
...atoms.shadow_lg,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: color.trueBlack,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const dim: Theme = {
|
||||
...dark,
|
||||
scheme: 'dark',
|
||||
name: 'dim',
|
||||
palette: dimPalette,
|
||||
atoms: {
|
||||
...dark.atoms,
|
||||
text: {
|
||||
color: dimPalette.white,
|
||||
},
|
||||
text_contrast_low: {
|
||||
color: dimPalette.contrast_400,
|
||||
},
|
||||
text_contrast_medium: {
|
||||
color: dimPalette.contrast_600,
|
||||
},
|
||||
text_contrast_high: {
|
||||
color: dimPalette.contrast_900,
|
||||
},
|
||||
text_inverted: {
|
||||
color: dimPalette.black,
|
||||
},
|
||||
bg: {
|
||||
backgroundColor: dimPalette.black,
|
||||
},
|
||||
bg_contrast_25: {
|
||||
backgroundColor: dimPalette.contrast_25,
|
||||
},
|
||||
bg_contrast_50: {
|
||||
backgroundColor: dimPalette.contrast_50,
|
||||
},
|
||||
bg_contrast_100: {
|
||||
backgroundColor: dimPalette.contrast_100,
|
||||
},
|
||||
bg_contrast_200: {
|
||||
backgroundColor: dimPalette.contrast_200,
|
||||
},
|
||||
bg_contrast_300: {
|
||||
backgroundColor: dimPalette.contrast_300,
|
||||
},
|
||||
bg_contrast_400: {
|
||||
backgroundColor: dimPalette.contrast_400,
|
||||
},
|
||||
bg_contrast_500: {
|
||||
backgroundColor: dimPalette.contrast_500,
|
||||
},
|
||||
bg_contrast_600: {
|
||||
backgroundColor: dimPalette.contrast_600,
|
||||
},
|
||||
bg_contrast_700: {
|
||||
backgroundColor: dimPalette.contrast_700,
|
||||
},
|
||||
bg_contrast_800: {
|
||||
backgroundColor: dimPalette.contrast_800,
|
||||
},
|
||||
bg_contrast_900: {
|
||||
backgroundColor: dimPalette.contrast_900,
|
||||
},
|
||||
bg_contrast_950: {
|
||||
backgroundColor: dimPalette.contrast_950,
|
||||
},
|
||||
bg_contrast_975: {
|
||||
backgroundColor: dimPalette.contrast_975,
|
||||
},
|
||||
border_contrast_low: {
|
||||
borderColor: dimPalette.contrast_100,
|
||||
},
|
||||
border_contrast_medium: {
|
||||
borderColor: dimPalette.contrast_200,
|
||||
},
|
||||
border_contrast_high: {
|
||||
borderColor: dimPalette.contrast_300,
|
||||
},
|
||||
shadow_sm: {
|
||||
...atoms.shadow_sm,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: `hsl(${hues.primary}, 28%, 6%)`,
|
||||
},
|
||||
shadow_md: {
|
||||
...atoms.shadow_md,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: `hsl(${hues.primary}, 28%, 6%)`,
|
||||
},
|
||||
shadow_lg: {
|
||||
...atoms.shadow_lg,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: `hsl(${hues.primary}, 28%, 6%)`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
lightPalette,
|
||||
darkPalette,
|
||||
dimPalette,
|
||||
light,
|
||||
dark,
|
||||
dim,
|
||||
}
|
||||
export const themes = {
|
||||
lightPalette: DEFAULT_THEMES.light.palette,
|
||||
darkPalette: DEFAULT_THEMES.dark.palette,
|
||||
dimPalette: DEFAULT_THEMES.dim.palette,
|
||||
light: DEFAULT_THEMES.light,
|
||||
dark: DEFAULT_THEMES.dark,
|
||||
dim: DEFAULT_THEMES.dim,
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use ALF and access palette from `useTheme()`
|
||||
*/
|
||||
export const lightPalette = DEFAULT_THEMES.light.palette
|
||||
/**
|
||||
* @deprecated use ALF and access palette from `useTheme()`
|
||||
*/
|
||||
export const darkPalette = DEFAULT_THEMES.dark.palette
|
||||
/**
|
||||
* @deprecated use ALF and access palette from `useTheme()`
|
||||
*/
|
||||
export const dimPalette = DEFAULT_THEMES.dim.palette
|
||||
/**
|
||||
* @deprecated use ALF and access theme from `useTheme()`
|
||||
*/
|
||||
export const light = DEFAULT_THEMES.light
|
||||
/**
|
||||
* @deprecated use ALF and access theme from `useTheme()`
|
||||
*/
|
||||
export const dark = DEFAULT_THEMES.dark
|
||||
/**
|
||||
* @deprecated use ALF and access theme from `useTheme()`
|
||||
*/
|
||||
export const dim = DEFAULT_THEMES.dim
|
||||
|
||||
+4
-55
@@ -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 = {
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native'
|
||||
|
||||
export type TextStyleProp = {
|
||||
style?: StyleProp<TextStyle>
|
||||
}
|
||||
|
||||
export type ViewStyleProp = {
|
||||
style?: StyleProp<ViewStyle>
|
||||
}
|
||||
|
||||
export type ThemeName = 'light' | 'dim' | 'dark'
|
||||
export type Palette = {
|
||||
white: string
|
||||
black: string
|
||||
like: string
|
||||
|
||||
contrast_25: string
|
||||
contrast_50: string
|
||||
contrast_100: string
|
||||
contrast_200: string
|
||||
contrast_300: string
|
||||
contrast_400: string
|
||||
contrast_500: string
|
||||
contrast_600: string
|
||||
contrast_700: string
|
||||
contrast_800: string
|
||||
contrast_900: string
|
||||
contrast_950: string
|
||||
contrast_975: string
|
||||
|
||||
primary_25: string
|
||||
primary_50: string
|
||||
primary_100: string
|
||||
primary_200: string
|
||||
primary_300: string
|
||||
primary_400: string
|
||||
primary_500: string
|
||||
primary_600: string
|
||||
primary_700: string
|
||||
primary_800: string
|
||||
primary_900: string
|
||||
primary_950: string
|
||||
primary_975: string
|
||||
|
||||
positive_25: string
|
||||
positive_50: string
|
||||
positive_100: string
|
||||
positive_200: string
|
||||
positive_300: string
|
||||
positive_400: string
|
||||
positive_500: string
|
||||
positive_600: string
|
||||
positive_700: string
|
||||
positive_800: string
|
||||
positive_900: string
|
||||
positive_950: string
|
||||
positive_975: string
|
||||
|
||||
negative_25: string
|
||||
negative_50: string
|
||||
negative_100: string
|
||||
negative_200: string
|
||||
negative_300: string
|
||||
negative_400: string
|
||||
negative_500: string
|
||||
negative_600: string
|
||||
negative_700: string
|
||||
negative_800: string
|
||||
negative_900: string
|
||||
negative_950: string
|
||||
negative_975: string
|
||||
}
|
||||
export type ThemedAtoms = {
|
||||
text: {
|
||||
color: string
|
||||
}
|
||||
text_contrast_low: {
|
||||
color: string
|
||||
}
|
||||
text_contrast_medium: {
|
||||
color: string
|
||||
}
|
||||
text_contrast_high: {
|
||||
color: string
|
||||
}
|
||||
text_inverted: {
|
||||
color: string
|
||||
}
|
||||
bg: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_25: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_50: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_100: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_200: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_300: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_400: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_500: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_600: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_700: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_800: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_900: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_950: {
|
||||
backgroundColor: string
|
||||
}
|
||||
bg_contrast_975: {
|
||||
backgroundColor: string
|
||||
}
|
||||
border_contrast_low: {
|
||||
borderColor: string
|
||||
}
|
||||
border_contrast_medium: {
|
||||
borderColor: string
|
||||
}
|
||||
border_contrast_high: {
|
||||
borderColor: string
|
||||
}
|
||||
shadow_sm: {
|
||||
shadowRadius: number
|
||||
shadowOpacity: number
|
||||
elevation: number
|
||||
shadowColor: string
|
||||
}
|
||||
shadow_md: {
|
||||
shadowRadius: number
|
||||
shadowOpacity: number
|
||||
elevation: number
|
||||
shadowColor: string
|
||||
}
|
||||
shadow_lg: {
|
||||
shadowRadius: number
|
||||
shadowOpacity: number
|
||||
elevation: number
|
||||
shadowColor: string
|
||||
}
|
||||
}
|
||||
export type Theme = {
|
||||
scheme: 'light' | 'dark' // for library support
|
||||
name: ThemeName
|
||||
palette: Palette
|
||||
atoms: ThemedAtoms
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,49 +1,8 @@
|
||||
import {logger} from '#/logger'
|
||||
import {utils} from '@bsky.app/alf'
|
||||
|
||||
export const BLUE_HUE = 211
|
||||
export const RED_HUE = 346
|
||||
export const GREEN_HUE = 152
|
||||
|
||||
/**
|
||||
* Smooth progression of lightness "stops" for generating HSL colors.
|
||||
* @deprecated use `utils.alpha` from `@bsky.app/alf` instead
|
||||
*/
|
||||
export const COLOR_STOPS = [
|
||||
0, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.85, 0.9, 0.95, 1,
|
||||
]
|
||||
|
||||
export function generateScale(start: number, end: number) {
|
||||
const range = end - start
|
||||
return COLOR_STOPS.map(stop => {
|
||||
return start + range * stop
|
||||
})
|
||||
}
|
||||
|
||||
export const defaultScale = generateScale(6, 100)
|
||||
// dim shifted 6% lighter
|
||||
export const dimScale = generateScale(12, 100)
|
||||
|
||||
export function transparentifyColor(color: string, alpha: number) {
|
||||
if (color.startsWith('hsl(')) {
|
||||
return 'hsla(' + color.slice('hsl('.length, -1) + `, ${alpha})`
|
||||
} else if (color.startsWith('rgb(')) {
|
||||
return 'rgba(' + color.slice('rgb('.length, -1) + `, ${alpha})`
|
||||
} else if (color.startsWith('#')) {
|
||||
if (color.length === 7) {
|
||||
const alphaHex = Math.round(alpha * 255).toString(16)
|
||||
// Per MDN: If there is only one number, it is duplicated: e means ee
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
|
||||
return color.slice(0, 7) + alphaHex.padStart(2, alphaHex)
|
||||
} else if (color.length === 4) {
|
||||
// convert to 6-digit hex before adding alpha
|
||||
const [r, g, b] = color.slice(1).split('')
|
||||
const alphaHex = Math.round(alpha * 255).toString(16)
|
||||
return `#${r.repeat(2)}${g.repeat(2)}${b.repeat(2)}${alphaHex.padStart(
|
||||
2,
|
||||
alphaHex,
|
||||
)}`
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Could not make '${color}' transparent`)
|
||||
}
|
||||
return color
|
||||
}
|
||||
export const transparentifyColor = utils.alpha
|
||||
|
||||
@@ -1,62 +1 @@
|
||||
import {Platform} from 'react-native'
|
||||
|
||||
import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
|
||||
|
||||
/**
|
||||
* Identity function on web. Returns nothing on other platforms.
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function web(value: any) {
|
||||
if (isWeb) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity function on iOS. Returns nothing on other platforms.
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function ios(value: any) {
|
||||
if (isIOS) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity function on Android. Returns nothing on other platforms..
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function android(value: any) {
|
||||
if (isAndroid) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity function on iOS and Android. Returns nothing on web.
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function native(value: any) {
|
||||
if (isNative) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export const platform = Platform.select
|
||||
export {android, ios, native, platform, web} from '@bsky.app/alf'
|
||||
|
||||
@@ -1,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})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import {type ThemeName} from '#/alf/types'
|
||||
import {utils} from '@bsky.app/alf'
|
||||
|
||||
export function select<T>(name: ThemeName, options: Record<ThemeName, T>) {
|
||||
switch (name) {
|
||||
case 'light':
|
||||
return options.light
|
||||
case 'dark':
|
||||
return options.dark || options.dim
|
||||
case 'dim':
|
||||
return options.dim || options.dark
|
||||
default:
|
||||
throw new Error(`select(theme, options) received unknown theme ${name}`)
|
||||
}
|
||||
}
|
||||
export const select = utils.select
|
||||
|
||||
@@ -1,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()
|
||||
|
||||
@@ -77,7 +77,7 @@ export function AccountList({
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.flex_1,
|
||||
a.flex_row,
|
||||
a.py_sm,
|
||||
@@ -151,7 +151,7 @@ function AccountItem({
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.font_bold, a.leading_tight]}
|
||||
style={[a.font_semi_bold, a.leading_tight]}
|
||||
numberOfLines={1}>
|
||||
{sanitizeDisplayName(
|
||||
profile?.displayName || profile?.handle || account.handle,
|
||||
|
||||
@@ -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 <Icon fill={fill} size="md" />
|
||||
}
|
||||
|
||||
export function Content({
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={[a.gap_sm, a.flex_1, {minHeight: 20}, a.justify_center, style]}
|
||||
{...rest}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function Text({
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: Pick<TextProps, 'children' | 'style'>) {
|
||||
return (
|
||||
<BaseText
|
||||
{...rest}
|
||||
style={[a.flex_1, a.text_sm, a.leading_snug, a.pr_md, style]}>
|
||||
<BaseText {...rest} style={[a.text_sm, a.leading_snug, a.pr_md, style]}>
|
||||
{children}
|
||||
</BaseText>
|
||||
)
|
||||
@@ -60,17 +71,23 @@ export function Text({
|
||||
export function Button({
|
||||
children,
|
||||
...props
|
||||
}: Omit<ButtonProps, 'size' | 'variant' | 'color'>) {
|
||||
}: Omit<ButtonProps, 'size' | 'variant'>) {
|
||||
return (
|
||||
<BaseButton size="tiny" variant="outline" color="secondary" {...props}>
|
||||
<BaseButton size="tiny" {...props}>
|
||||
{children}
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
|
||||
export function Row({children}: {children: React.ReactNode}) {
|
||||
export function Row({
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
return (
|
||||
<View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<View style={[a.flex_1, a.flex_row, a.align_start, a.gap_sm, style]}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
@@ -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 (
|
||||
<Context.Provider value={{type}}>
|
||||
<View
|
||||
style={[
|
||||
gtMobile ? a.p_md : a.p_sm,
|
||||
a.p_md,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.bg_contrast_25,
|
||||
t.atoms.bg,
|
||||
{borderColor},
|
||||
style,
|
||||
]}>
|
||||
@@ -123,7 +141,9 @@ export function Admonition({
|
||||
<Outer type={type} style={style}>
|
||||
<Row>
|
||||
<Icon />
|
||||
<Text>{children}</Text>
|
||||
<Content>
|
||||
<Text>{children}</Text>
|
||||
</Content>
|
||||
</Row>
|
||||
</Outer>
|
||||
)
|
||||
|
||||
@@ -96,7 +96,7 @@ export function BlockedGeoOverlay() {
|
||||
<Mark fill={t.palette.primary_600} width={14} />
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
{
|
||||
color: t.palette.primary_600,
|
||||
},
|
||||
@@ -121,8 +121,7 @@ export function BlockedGeoOverlay() {
|
||||
</View>
|
||||
|
||||
<View style={[a.mt_xl, a.align_start]}>
|
||||
<Text
|
||||
style={[a.text_lg, a.font_heavy, a.leading_snug, a.pb_xs]}>
|
||||
<Text style={[a.text_lg, a.font_bold, a.leading_snug, a.pb_xs]}>
|
||||
<Trans>Not in Mississippi?</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
|
||||
+58
-54
@@ -240,7 +240,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
}
|
||||
} else if (color === 'secondary') {
|
||||
if (!disabled) {
|
||||
baseStyles.push(t.atoms.bg_contrast_25)
|
||||
baseStyles.push(t.atoms.bg_contrast_50)
|
||||
hoverStyles.push(t.atoms.bg_contrast_100)
|
||||
} else {
|
||||
baseStyles.push(t.atoms.bg_contrast_50)
|
||||
@@ -274,51 +274,27 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
} 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<View, ButtonProps>(
|
||||
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<View, ButtonProps>(
|
||||
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({
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export type StackedButtonProps = Omit<
|
||||
ButtonProps,
|
||||
keyof VariantProps | 'children'
|
||||
> &
|
||||
Pick<VariantProps, 'color'> & {
|
||||
children: React.ReactNode
|
||||
icon: React.ComponentType<SVGIconProps>
|
||||
}
|
||||
|
||||
export function StackedButton({children, ...props}: StackedButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
size="tiny"
|
||||
style={[
|
||||
a.flex_col,
|
||||
{
|
||||
height: 72,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 20,
|
||||
gap: 4,
|
||||
},
|
||||
props.style,
|
||||
]}>
|
||||
<StackedButtonInnerText icon={props.icon}>
|
||||
{children}
|
||||
</StackedButtonInnerText>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function StackedButtonInnerText({
|
||||
children,
|
||||
icon: Icon,
|
||||
}: Pick<StackedButtonProps, 'icon' | 'children'>) {
|
||||
const textStyles = useSharedButtonTextStyles()
|
||||
return (
|
||||
<>
|
||||
<Icon width={24} fill={textStyles.color} />
|
||||
<ButtonText>{children}</ButtonText>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -796,7 +796,7 @@ export function ItemText({children, style}: ItemTextProps) {
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_high,
|
||||
{paddingTop: 3},
|
||||
style,
|
||||
@@ -855,7 +855,11 @@ export function LabelText({children}: {children: React.ReactNode}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<Text
|
||||
style={[a.font_bold, t.atoms.text_contrast_medium, {marginBottom: -8}]}>
|
||||
style={[
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_medium,
|
||||
{marginBottom: -8},
|
||||
]}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
|
||||
@@ -267,7 +267,10 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
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}
|
||||
</KeyboardAwareScrollView>
|
||||
|
||||
@@ -62,7 +62,7 @@ export function HeaderText({
|
||||
style?: StyleProp<TextStyle>
|
||||
}) {
|
||||
return (
|
||||
<Text style={[a.text_lg, a.text_center, a.font_heavy, style]}>
|
||||
<Text style={[a.text_lg, a.text_center, a.font_bold, style]}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
|
||||
@@ -40,7 +40,7 @@ export function Error({
|
||||
]}
|
||||
sideBorders={sideBorders}>
|
||||
<View style={[a.w_full, a.align_center, a.gap_lg]}>
|
||||
<Text style={[a.font_bold, a.text_3xl]}>{title}</Text>
|
||||
<Text style={[a.font_semi_bold, a.text_3xl]}>{title}</Text>
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
|
||||
@@ -128,7 +128,7 @@ export function TitleAndByline({
|
||||
<View style={[a.flex_1]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}
|
||||
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
|
||||
numberOfLines={1}>
|
||||
{title}
|
||||
</Text>
|
||||
@@ -214,7 +214,7 @@ export function DescriptionPlaceholder() {
|
||||
export function Likes({count}: {count: number}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium, a.font_bold]}>
|
||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium, a.font_semi_bold]}>
|
||||
<Trans>
|
||||
Liked by <Plural value={count || 0} one="# user" other="# users" />
|
||||
</Trans>
|
||||
|
||||
@@ -380,7 +380,7 @@ export function ProfileGrid({
|
||||
a.justify_between,
|
||||
]}
|
||||
pointerEvents={isIOS ? 'auto' : 'box-none'}>
|
||||
<Text style={[a.text_sm, a.font_bold, t.atoms.text]}>
|
||||
<Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}>
|
||||
{isFeedContext ? (
|
||||
<Trans>Suggested for you</Trans>
|
||||
) : (
|
||||
@@ -516,7 +516,7 @@ export function SuggestedFeeds() {
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_lg,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>Some other feeds you might like</Trans>
|
||||
|
||||
@@ -45,7 +45,7 @@ export function Avatar({avatar}: {avatar?: string}) {
|
||||
|
||||
export function Title({value}: {value: string}) {
|
||||
return (
|
||||
<Text emoji style={[a.text_md, a.font_bold, a.leading_tight]}>
|
||||
<Text emoji style={[a.text_md, a.font_semi_bold, a.leading_tight]}>
|
||||
{value}
|
||||
</Text>
|
||||
)
|
||||
|
||||
@@ -181,7 +181,7 @@ export function TitleText({
|
||||
<Text
|
||||
style={[
|
||||
a.text_lg,
|
||||
a.font_heavy,
|
||||
a.font_bold,
|
||||
a.leading_tight,
|
||||
isIOS && align === 'platform' && a.text_center,
|
||||
gtMobile && a.text_xl,
|
||||
|
||||
@@ -165,8 +165,8 @@ export function useLink({
|
||||
if (isNative && screen !== 'NotFound') {
|
||||
const state = navigation.getState()
|
||||
// if screen is not in the current navigator, it means it's
|
||||
// most likely a tab screen
|
||||
if (!state.routeNames.includes(screen)) {
|
||||
// most likely a tab screen. note: state can be undefined
|
||||
if (!state?.routeNames.includes(screen)) {
|
||||
const parent = navigation.getParent()
|
||||
if (
|
||||
parent &&
|
||||
|
||||
@@ -127,7 +127,7 @@ export function TitleAndByline({
|
||||
allowOverride={creator && currentAccount?.did === creator.did}>
|
||||
<Hider.Mask>
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, a.leading_snug, a.italic]}
|
||||
style={[a.text_md, a.font_semi_bold, a.leading_snug, a.italic]}
|
||||
numberOfLines={1}>
|
||||
<Trans>Hidden list</Trans>
|
||||
</Text>
|
||||
@@ -135,7 +135,7 @@ export function TitleAndByline({
|
||||
<Hider.Content>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}
|
||||
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
|
||||
numberOfLines={1}>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
@@ -50,7 +50,7 @@ export function LoggedOutCTA({style, gateName}: LoggedOutCTAProps) {
|
||||
<View style={[a.flex_row, a.align_center, a.flex_1, a.pr_md]}>
|
||||
<Logo width={30} style={[a.mr_md]} />
|
||||
<View style={[a.flex_1]}>
|
||||
<Text style={[a.text_lg, a.font_bold, a.leading_snug]}>
|
||||
<Text style={[a.text_lg, a.font_semi_bold, a.leading_snug]}>
|
||||
<Trans>Join Bluesky</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
|
||||
@@ -191,7 +191,7 @@ export function ItemText({children, style}: ItemTextProps) {
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_high,
|
||||
{paddingTop: 3},
|
||||
style,
|
||||
@@ -293,7 +293,7 @@ export function LabelText({
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_medium,
|
||||
{marginBottom: -8},
|
||||
style,
|
||||
|
||||
@@ -292,7 +292,7 @@ export function ItemText({children, style}: ItemTextProps) {
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_high,
|
||||
style,
|
||||
disabled && t.atoms.text_contrast_low,
|
||||
@@ -372,7 +372,7 @@ export function LabelText({
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.p_sm,
|
||||
t.atoms.text_contrast_low,
|
||||
a.leading_snug,
|
||||
|
||||
@@ -135,7 +135,7 @@ function DialogInner({
|
||||
style={[a.absolute, a.inset_0]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[a.font_bold, a.text_xl]}>
|
||||
<Text style={[a.font_semi_bold, a.text_xl]}>
|
||||
{isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -25,7 +25,7 @@ export function Badge() {
|
||||
<Logo fill={t.palette.primary_600} width={14} />
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
{
|
||||
color: t.palette.primary_600,
|
||||
},
|
||||
|
||||
@@ -69,7 +69,7 @@ export function Content({state}: {state: PolicyUpdateState}) {
|
||||
|
||||
{screenReaderEnabled ? (
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text emoji style={[a.text_2xl, a.font_bold, a.leading_snug]}>
|
||||
<Text emoji style={[a.text_2xl, a.font_semi_bold, a.leading_snug]}>
|
||||
<Trans>Hey there 👋</Trans>
|
||||
</Text>
|
||||
<Text style={[a.leading_snug, a.text_md]}>
|
||||
@@ -115,7 +115,7 @@ export function Content({state}: {state: PolicyUpdateState}) {
|
||||
</View>
|
||||
) : (
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text emoji style={[a.text_2xl, a.font_bold, a.leading_snug]}>
|
||||
<Text emoji style={[a.text_2xl, a.font_semi_bold, a.leading_snug]}>
|
||||
<Trans>Hey there 👋</Trans>
|
||||
</Text>
|
||||
<Text style={[a.leading_snug, a.text_md]}>
|
||||
|
||||
@@ -126,7 +126,7 @@ export const ExternalEmbed = ({
|
||||
<Text
|
||||
emoji
|
||||
numberOfLines={3}
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}>
|
||||
style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
|
||||
{link.title || link.uri}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -54,7 +54,7 @@ export function TimeIndicator({
|
||||
<Text
|
||||
style={[
|
||||
{color: t.palette.white, fontSize: 12, fontVariant: ['tabular-nums']},
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
{lineHeight: 1.25},
|
||||
]}>
|
||||
{`${minutes}:${seconds}`}
|
||||
|
||||
@@ -128,7 +128,12 @@ export function PostControlButtonText({style, ...props}: TextProps) {
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[color, big ? a.text_md : a.text_sm, active && a.font_bold, style]}
|
||||
style={[
|
||||
color,
|
||||
big ? a.text_md : a.text_sm,
|
||||
active && a.font_semi_bold,
|
||||
style,
|
||||
]}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -157,7 +157,7 @@ let RepostButtonDialogInner = ({
|
||||
variant="ghost"
|
||||
color="primary">
|
||||
<Repost size="lg" fill={t.palette.primary_500} />
|
||||
<Text style={[a.font_bold, a.text_xl]}>
|
||||
<Text style={[a.font_semi_bold, a.text_xl]}>
|
||||
{isReposted ? (
|
||||
<Trans>Remove repost</Trans>
|
||||
) : (
|
||||
@@ -188,7 +188,7 @@ let RepostButtonDialogInner = ({
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.text_xl,
|
||||
embeddingDisabled && t.atoms.text_contrast_low,
|
||||
]}>
|
||||
|
||||
@@ -203,7 +203,7 @@ function NoConvos() {
|
||||
a.text_sm,
|
||||
t.atoms.text_contrast_high,
|
||||
a.text_center,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
]}>
|
||||
<Trans>Start a conversation, and it will appear here.</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -233,7 +233,7 @@ function InlineNameAndHandle({
|
||||
<Text
|
||||
emoji
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.leading_tight,
|
||||
a.flex_shrink_0,
|
||||
{maxWidth: '70%'},
|
||||
@@ -287,7 +287,7 @@ export function Name({
|
||||
emoji
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.leading_snug,
|
||||
a.self_start,
|
||||
a.flex_shrink,
|
||||
|
||||
@@ -515,7 +515,7 @@ function Inner({
|
||||
<View style={[a.flex_row, a.align_center, a.pt_md, a.pb_xs]}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[a.text_lg, a.font_bold, a.self_start]}>
|
||||
style={[a.text_lg, a.font_semi_bold, a.self_start]}>
|
||||
{sanitizeDisplayName(
|
||||
profile.displayName || sanitizeHandle(profile.handle),
|
||||
moderation.ui('displayName'),
|
||||
@@ -562,7 +562,7 @@ function Inner({
|
||||
label={`${followers} ${pluralizedFollowers}`}
|
||||
style={[t.atoms.text]}
|
||||
onPress={hide}>
|
||||
<Text style={[a.text_md, a.font_bold]}>{followers} </Text>
|
||||
<Text style={[a.text_md, a.font_semi_bold]}>{followers} </Text>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{pluralizedFollowers}
|
||||
</Text>
|
||||
@@ -572,7 +572,7 @@ function Inner({
|
||||
label={_(msg`${following} following`)}
|
||||
style={[t.atoms.text]}
|
||||
onPress={hide}>
|
||||
<Text style={[a.text_md, a.font_bold]}>{following} </Text>
|
||||
<Text style={[a.text_md, a.font_semi_bold]}>{following} </Text>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{pluralizedFollowings}
|
||||
</Text>
|
||||
|
||||
@@ -360,7 +360,7 @@ function HeaderTop({guide}: {guide: Follow10ProgressGuide}) {
|
||||
style={[
|
||||
a.z_10,
|
||||
a.text_lg,
|
||||
a.font_heavy,
|
||||
a.font_bold,
|
||||
a.leading_tight,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
|
||||
<Text
|
||||
style={[
|
||||
t.atoms.text_contrast_medium,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.text_sm,
|
||||
{textTransform: 'uppercase'},
|
||||
]}>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function ProgressGuideTask({
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.leading_tight,
|
||||
tabularNumsTitle && {fontVariant: ['tabular-nums']},
|
||||
]}>
|
||||
|
||||
@@ -160,7 +160,7 @@ export const ProgressGuideToast = React.forwardRef<
|
||||
ref={animatedCheckRef}
|
||||
/>
|
||||
<View>
|
||||
<Text style={[a.text_md, a.font_bold]}>{title}</Text>
|
||||
<Text style={[a.text_md, a.font_semi_bold]}>{title}</Text>
|
||||
{subtitle && (
|
||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
|
||||
{subtitle}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function TitleText({
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_2xl,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.pb_sm,
|
||||
a.leading_snug,
|
||||
style,
|
||||
|
||||
@@ -24,7 +24,7 @@ export function SelectLabelerView({
|
||||
return (
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||
<Trans>Select moderator</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
@@ -75,7 +75,8 @@ function LabelerButton({
|
||||
handle: labeler.creator.handle,
|
||||
})}
|
||||
/>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.text_sm, a.font_bold]}>
|
||||
<Text
|
||||
style={[t.atoms.text_contrast_medium, a.text_sm, a.font_semi_bold]}>
|
||||
@{labeler.creator.handle}
|
||||
</Text>
|
||||
</LabelingServiceCard.Content>
|
||||
|
||||
@@ -85,7 +85,7 @@ export function SelectReportOptionView(props: {
|
||||
) : null}
|
||||
|
||||
<View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
||||
<Text style={[a.text_2xl, a.font_semi_bold]}>{i18n.title}</Text>
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
{i18n.description}
|
||||
</Text>
|
||||
@@ -173,7 +173,8 @@ function ReportOptionButton({
|
||||
interacted && t.atoms.bg_contrast_50,
|
||||
]}>
|
||||
<View style={[a.flex_1, a.gap_xs]}>
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
<Text
|
||||
style={[a.text_md, a.font_semi_bold, t.atoms.text_contrast_medium]}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text>
|
||||
|
||||
@@ -125,7 +125,7 @@ export function SubmitView({
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<View style={[a.flex_1, a.gap_xs]}>
|
||||
<Text style={[a.text_md, a.font_bold]}>
|
||||
<Text style={[a.text_md, a.font_semi_bold]}>
|
||||
{selectedReportOption.title}
|
||||
</Text>
|
||||
<Text style={[a.leading_tight, {maxWidth: 400}]}>
|
||||
|
||||
+12
-3
@@ -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<ViewStyle>
|
||||
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 (
|
||||
<Animated.View
|
||||
entering={isWeb ? FadeIn.duration(90) : entering}
|
||||
exiting={FadeOut.duration(90)} // Totally vibes based
|
||||
entering={isWeb ? webEntering : entering}
|
||||
exiting={isWeb ? webExiting : exiting}
|
||||
style={style}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
@@ -34,7 +34,13 @@ export function SearchError({
|
||||
{maxWidth: gtMobile ? 394 : 294},
|
||||
gtMobile ? a.gap_md : a.gap_sm,
|
||||
]}>
|
||||
<Text style={[a.font_bold, a.text_lg, a.text_center, a.leading_snug]}>
|
||||
<Text
|
||||
style={[
|
||||
a.font_semi_bold,
|
||||
a.text_lg,
|
||||
a.text_center,
|
||||
a.leading_snug,
|
||||
]}>
|
||||
{title}
|
||||
</Text>
|
||||
{children}
|
||||
|
||||
@@ -280,7 +280,7 @@ export function ItemText({children}: ItemTextProps) {
|
||||
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text
|
||||
return (
|
||||
<View style={[a.flex_1, a.py_md, a.border_b, t.atoms.border_contrast_low]}>
|
||||
<Text style={[a.text_md, selected && a.font_bold]}>{children}</Text>
|
||||
<Text style={[a.text_md, selected && a.font_semi_bold]}>{children}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ export function Item({ref, value, style, children}: ItemProps) {
|
||||
a.text_sm,
|
||||
{outline: 0},
|
||||
(hovered || focused) && {backgroundColor: t.palette.primary_50},
|
||||
selected && [a.font_bold],
|
||||
selected && [a.font_semi_bold],
|
||||
a.transition_color,
|
||||
style,
|
||||
])}>
|
||||
|
||||
@@ -255,7 +255,7 @@ function Empty() {
|
||||
{marginTop: a.border.borderWidth},
|
||||
]}>
|
||||
<View style={[a.gap_xs]}>
|
||||
<Text style={[a.font_bold, a.text_lg, {color: 'white'}]}>
|
||||
<Text style={[a.font_semi_bold, a.text_lg, {color: 'white'}]}>
|
||||
<Trans>You haven't created a starter pack yet!</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, {color: 'white'}]}>
|
||||
|
||||
@@ -54,14 +54,19 @@ export function QrCode({
|
||||
]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text
|
||||
style={[a.font_bold, a.text_3xl, a.text_center, {color: 'white'}]}>
|
||||
style={[
|
||||
a.font_semi_bold,
|
||||
a.text_3xl,
|
||||
a.text_center,
|
||||
{color: 'white'},
|
||||
]}>
|
||||
{record.name}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[a.gap_xl, a.align_center]}>
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.text_center,
|
||||
{color: 'white', fontSize: 18},
|
||||
]}>
|
||||
@@ -76,7 +81,7 @@ export function QrCode({
|
||||
a.flex,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
{color: 'white', fontSize: 18, gap: 6},
|
||||
]}>
|
||||
<Trans>
|
||||
|
||||
@@ -77,7 +77,7 @@ function ShareDialogInner({
|
||||
) : (
|
||||
<View style={[!gtMobile && a.gap_lg]}>
|
||||
<View style={[a.gap_sm, gtMobile && a.pb_lg]}>
|
||||
<Text style={[a.font_bold, a.text_2xl]}>
|
||||
<Text style={[a.font_semi_bold, a.text_2xl]}>
|
||||
<Trans>Invite people to this starter pack!</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
|
||||
@@ -77,7 +77,7 @@ export function Card({
|
||||
<View style={[a.flex_1]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}
|
||||
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
|
||||
numberOfLines={2}>
|
||||
{record.name}
|
||||
</Text>
|
||||
@@ -97,7 +97,7 @@ export function Card({
|
||||
</Text>
|
||||
) : null}
|
||||
{!!joinedAllTimeCount && joinedAllTimeCount >= 50 && (
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
<Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
|
||||
<Trans comment="Number of users (always at least 50) who have joined Bluesky using a specific starter pack">
|
||||
<Plural value={joinedAllTimeCount} other="# users have" /> joined!
|
||||
</Trans>
|
||||
|
||||
@@ -105,7 +105,7 @@ export function WizardEditListDialog({
|
||||
: [a.pb_sm, a.align_end],
|
||||
]}>
|
||||
<View style={{width: 60}} />
|
||||
<Text style={[a.font_bold, a.text_xl]}>
|
||||
<Text style={[a.font_semi_bold, a.text_xl]}>
|
||||
{state.currentStep === 'Profiles' ? (
|
||||
<Trans>Edit People</Trans>
|
||||
) : (
|
||||
|
||||
@@ -83,7 +83,7 @@ function WizardListCard({
|
||||
emoji
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.text_md,
|
||||
a.leading_tight,
|
||||
a.self_start,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,7 +90,7 @@ export function TrendingTopic({
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.leading_tight,
|
||||
isSmall ? [a.text_sm] : [a.text_md, {paddingBottom: 1}],
|
||||
]}
|
||||
|
||||
@@ -227,7 +227,8 @@ export function VideoPostCard({
|
||||
{likeCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Heart size="sm" fill="white" />
|
||||
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
<Text
|
||||
style={[a.text_sm, a.font_semi_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, likeCount)}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -235,7 +236,8 @@ export function VideoPostCard({
|
||||
{repostCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Repost size="sm" fill="white" />
|
||||
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
<Text
|
||||
style={[a.text_sm, a.font_semi_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, repostCount)}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -520,7 +522,11 @@ export function CompactVideoPostCard({
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Heart size="sm" fill="white" />
|
||||
<Text
|
||||
style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_semi_bold,
|
||||
{color: 'white'},
|
||||
]}>
|
||||
{formatCount(i18n, likeCount)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -108,7 +108,7 @@ export function WelcomeModal({control}: WelcomeModalProps) {
|
||||
<Text
|
||||
style={[
|
||||
a.text_2xl,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.user_select_none,
|
||||
{color: '#354358', letterSpacing: -0.5},
|
||||
]}>
|
||||
@@ -127,7 +127,7 @@ export function WelcomeModal({control}: WelcomeModalProps) {
|
||||
<Text
|
||||
style={[
|
||||
gtMobile ? a.text_4xl : a.text_3xl,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.text_center,
|
||||
{color: '#354358'},
|
||||
web({
|
||||
|
||||
@@ -188,7 +188,7 @@ function WhoCanReplyDialog({
|
||||
label={_(msg`Dialog: adjust who can interact with this post`)}
|
||||
style={web({maxWidth: 400})}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.font_bold, a.text_xl, a.pb_sm]}>
|
||||
<Text style={[a.font_semi_bold, a.text_xl, a.pb_sm]}>
|
||||
<Trans>Who can interact with this post?</Trans>
|
||||
</Text>
|
||||
<Rules
|
||||
|
||||
@@ -219,7 +219,7 @@ function DialogInner({
|
||||
label={_(msg`Get notified of new posts from ${name}`)}>
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.gap_xs]}>
|
||||
<Text style={[a.font_heavy, a.text_2xl]}>
|
||||
<Text style={[a.font_bold, a.text_2xl]}>
|
||||
<Trans>Keep me posted</Trans>
|
||||
</Text>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.text_md]}>
|
||||
|
||||
@@ -82,13 +82,13 @@ function Inner({
|
||||
</View>
|
||||
<View style={[a.flex_1, a.gap_xs, a.pr_4xl]}>
|
||||
<Text style={[a.text_sm, a.leading_snug]}>{children}</Text>
|
||||
<Text style={[a.text_sm, a.leading_snug, a.font_bold]}>
|
||||
<Text style={[a.text_sm, a.leading_snug, a.font_semi_bold]}>
|
||||
<Trans>
|
||||
Learn more in your{' '}
|
||||
<InlineLinkText
|
||||
label={_(msg`Go to account settings`)}
|
||||
to={'/settings/account'}
|
||||
style={[a.text_sm, a.leading_snug, a.font_bold]}
|
||||
style={[a.text_sm, a.leading_snug, a.font_semi_bold]}
|
||||
onPress={() => {
|
||||
logger.metric('ageAssurance:navigateToSettings', {})
|
||||
}}>
|
||||
|
||||
@@ -89,7 +89,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) {
|
||||
<AgeAssuranceBadge />
|
||||
</View>
|
||||
|
||||
<Text style={[a.text_2xl, a.font_heavy, a.pt_md, a.leading_tight]}>
|
||||
<Text style={[a.text_2xl, a.font_bold, a.pt_md, a.leading_tight]}>
|
||||
<Trans>Contact us</Trans>
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export function AgeAssuranceBadge() {
|
||||
<Shield size="sm" />
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.leading_snug,
|
||||
{
|
||||
color: select(t.name, {
|
||||
|
||||
@@ -178,7 +178,7 @@ function Inner() {
|
||||
<View style={[a.align_start]}>
|
||||
<AgeAssuranceBadge />
|
||||
|
||||
<Text style={[a.text_xl, a.font_heavy, a.pt_xl, a.pb_md]}>
|
||||
<Text style={[a.text_xl, a.font_bold, a.pt_xl, a.pb_md]}>
|
||||
{success ? <Trans>Success!</Trans> : <Trans>Verify your age</Trans>}
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
|
||||
a.pb_md,
|
||||
]}>
|
||||
<SuccessIcon size="sm" fill={t.palette.positive_600} />
|
||||
<Text style={[a.text_xl, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
<Trans>Success</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
@@ -209,7 +209,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
|
||||
]}>
|
||||
{error && <ErrorIcon size="md" fill={t.palette.negative_500} />}
|
||||
|
||||
<Text style={[a.text_xl, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
{error ? <Trans>Connection issue</Trans> : <Trans>Verifying</Trans>}
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export function AgeRestrictedScreen({
|
||||
</View>
|
||||
|
||||
<View style={[a.gap_sm, a.pb_lg]}>
|
||||
<Text style={[a.text_xl, a.leading_snug, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.leading_snug, a.font_bold]}>
|
||||
<Trans>
|
||||
You must complete age assurance in order to access this screen.
|
||||
</Trans>
|
||||
|
||||
@@ -38,7 +38,7 @@ export function BirthDateSettingsDialog({
|
||||
label={_(msg`My Birthday`)}
|
||||
style={web({maxWidth: 400})}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
<Text style={[a.text_xl, a.font_semi_bold]}>
|
||||
<Trans>My Birthday</Trans>
|
||||
</Text>
|
||||
<Text style={[a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
|
||||
@@ -108,7 +108,7 @@ function DeviceLocationRequestDialogInner({onLocationAcquired}: Props) {
|
||||
|
||||
return (
|
||||
<View style={[a.gap_md]}>
|
||||
<Text style={[a.text_xl, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
<Trans>Confirm your location</Trans>
|
||||
</Text>
|
||||
<View style={[a.gap_sm, a.pb_xs]}>
|
||||
|
||||
@@ -153,7 +153,7 @@ export function Disable() {
|
||||
|
||||
return (
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_xl, a.font_heavy, a.leading_snug]}>
|
||||
<Text style={[a.text_xl, a.font_bold, a.leading_snug]}>
|
||||
<Trans>Disable email 2FA</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -163,7 +163,7 @@ export function Disable() {
|
||||
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
<Trans>
|
||||
To disable your email 2FA method, please verify your access to{' '}
|
||||
<Span style={[a.font_bold]}>{currentAccount?.email}</Span>
|
||||
<Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -214,7 +214,7 @@ export function Disable() {
|
||||
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
<Trans>
|
||||
To disable your email 2FA method, please verify your access to{' '}
|
||||
<Span style={[a.font_bold]}>{currentAccount?.email}</Span>
|
||||
<Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ export function Enable() {
|
||||
return (
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_xl, a.font_heavy, a.leading_snug]}>
|
||||
<Text style={[a.text_xl, a.font_bold, a.leading_snug]}>
|
||||
<Trans>Enable email 2FA</Trans>
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ export function Update(_props: ScreenProps<ScreenID.Update>) {
|
||||
|
||||
return (
|
||||
<View style={[a.gap_lg]}>
|
||||
<Text style={[a.text_xl, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
<Trans>Update your email</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -239,7 +239,7 @@ export function Update(_props: ScreenProps<ScreenID.Update>) {
|
||||
<>
|
||||
<Divider />
|
||||
<View>
|
||||
<Text style={[a.text_md, a.pb_sm, a.font_bold]}>
|
||||
<Text style={[a.text_md, a.pb_sm, a.font_semi_bold]}>
|
||||
<Trans>Security step required</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
@@ -282,7 +282,7 @@ export function Update(_props: ScreenProps<ScreenID.Update>) {
|
||||
<View style={[a.gap_sm]}>
|
||||
<View style={[a.flex_row, a.gap_sm, a.align_center]}>
|
||||
<Check fill={t.palette.positive_600} size="xs" />
|
||||
<Text style={[a.text_md, a.font_heavy]}>
|
||||
<Text style={[a.text_md, a.font_bold]}>
|
||||
<Trans>Success!</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -58,7 +58,7 @@ export function VerificationReminder({
|
||||
<View style={[a.mb_xs, {height: 150 - dialogPadding}]} />
|
||||
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_xl, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
<Trans>Please verify your email</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
|
||||
@@ -174,7 +174,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
|
||||
return (
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_xl, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
<Span style={{top: 1}}>
|
||||
<Check size="sm" fill={t.palette.positive_600} />
|
||||
</Span>
|
||||
@@ -197,7 +197,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
|
||||
return (
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_xl, a.font_heavy]}>
|
||||
<Text style={[a.text_xl, a.font_bold]}>
|
||||
{state.step === 'email' ? (
|
||||
state.mutationStatus === 'success' ? (
|
||||
<>
|
||||
@@ -239,7 +239,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
|
||||
state.mutationStatus === 'success' ? (
|
||||
<Trans>
|
||||
We sent an email to{' '}
|
||||
<Span style={[a.font_bold, t.atoms.text]}>
|
||||
<Span style={[a.font_semi_bold, t.atoms.text]}>
|
||||
{currentAccount!.email}
|
||||
</Span>{' '}
|
||||
containing a link. Please click on it to complete the email
|
||||
@@ -248,7 +248,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
|
||||
) : (
|
||||
<Trans>
|
||||
We'll send an email to{' '}
|
||||
<Span style={[a.font_bold, t.atoms.text]}>
|
||||
<Span style={[a.font_semi_bold, t.atoms.text]}>
|
||||
{currentAccount!.email}
|
||||
</Span>{' '}
|
||||
containing a link. Please click on it to complete the email
|
||||
@@ -258,7 +258,7 @@ export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) {
|
||||
) : (
|
||||
<Trans>
|
||||
Please enter the code we sent to{' '}
|
||||
<Span style={[a.font_bold, t.atoms.text]}>
|
||||
<Span style={[a.font_semi_bold, t.atoms.text]}>
|
||||
{currentAccount!.email}
|
||||
</Span>{' '}
|
||||
below.
|
||||
|
||||
@@ -104,7 +104,7 @@ function EmbedDialogInner({
|
||||
<Dialog.Inner label={_(msg`Embed post`)} style={[{maxWidth: 500}]}>
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_2xl, a.font_heavy]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>Embed post</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
@@ -147,7 +147,7 @@ function EmbedDialogInner({
|
||||
|
||||
{showCustomisation && (
|
||||
<View style={[a.gap_sm, a.p_md]}>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.font_bold]}>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.font_semi_bold]}>
|
||||
<Trans>Color theme</Trans>
|
||||
</Text>
|
||||
<ToggleButton.Group
|
||||
|
||||
@@ -54,7 +54,7 @@ export function EmbedConsentDialog({
|
||||
label={_(msg`External Media`)}
|
||||
style={[gtMobile ? {width: 'auto', maxWidth: 400} : a.w_full]}>
|
||||
<View style={a.gap_sm}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||
<Trans>External Media</Trans>
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ function InAppBrowserConsentInner({href}: {href?: string}) {
|
||||
<Dialog.ScrollableInner label={_(msg`How should we open this link?`)}>
|
||||
<View style={[a.gap_2xl]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.font_heavy, a.text_2xl]}>
|
||||
<Text style={[a.font_bold, a.text_2xl]}>
|
||||
<Trans>How should we open this link?</Trans>
|
||||
</Text>
|
||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug, a.text_md]}>
|
||||
|
||||
@@ -68,7 +68,7 @@ function InAppBrowserConsentInner({
|
||||
}>
|
||||
<View style={[a.gap_2xl]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.font_heavy, a.text_2xl]}>
|
||||
<Text style={[a.font_bold, a.text_2xl]}>
|
||||
{potentiallyMisleading ? (
|
||||
<Trans>Potentially misleading link</Trans>
|
||||
) : (
|
||||
@@ -151,7 +151,8 @@ function LinkBox({href}: {href: string}) {
|
||||
]}>
|
||||
<Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
{scheme}
|
||||
<Text style={[a.text_md, a.leading_snug, t.atoms.text, a.font_bold]}>
|
||||
<Text
|
||||
style={[a.text_md, a.leading_snug, t.atoms.text, a.font_semi_bold]}>
|
||||
{hostname}
|
||||
</Text>
|
||||
{rest}
|
||||
|
||||
@@ -108,7 +108,12 @@ function MutedWordsInner() {
|
||||
<Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}>
|
||||
<View>
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, a.pb_sm, t.atoms.text_contrast_high]}>
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_semi_bold,
|
||||
a.pb_sm,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Add muted words and tags</Trans>
|
||||
</Text>
|
||||
<Text style={[a.pb_lg, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
@@ -147,7 +152,7 @@ function MutedWordsInner() {
|
||||
style={[
|
||||
a.pb_xs,
|
||||
a.text_sm,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>Duration:</Trans>
|
||||
@@ -247,7 +252,7 @@ function MutedWordsInner() {
|
||||
style={[
|
||||
a.pb_xs,
|
||||
a.text_sm,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>Mute in:</Trans>
|
||||
@@ -293,7 +298,7 @@ function MutedWordsInner() {
|
||||
style={[
|
||||
a.pb_xs,
|
||||
a.text_sm,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>Options:</Trans>
|
||||
@@ -362,7 +367,7 @@ function MutedWordsInner() {
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
@@ -455,7 +460,7 @@ function MutedWordRow({
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.leading_snug,
|
||||
a.font_bold,
|
||||
a.font_semi_bold,
|
||||
web({
|
||||
overflowWrap: 'break-word',
|
||||
wordBreak: 'break-word',
|
||||
@@ -466,7 +471,8 @@ function MutedWordRow({
|
||||
{word.value}{' '}
|
||||
<Text style={[a.font_normal, t.atoms.text_contrast_medium]}>
|
||||
in{' '}
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
<Text
|
||||
style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
|
||||
text & tags
|
||||
</Text>
|
||||
</Text>
|
||||
@@ -476,7 +482,8 @@ function MutedWordRow({
|
||||
{word.value}{' '}
|
||||
<Text style={[a.font_normal, t.atoms.text_contrast_medium]}>
|
||||
in{' '}
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
<Text
|
||||
style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
|
||||
tags
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
@@ -84,7 +84,7 @@ export function PostInteractionSettingsControlledDialog({
|
||||
]}>
|
||||
<Trans>
|
||||
You can set default interaction settings in{' '}
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
<Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
|
||||
Settings → Moderation → Interaction settings
|
||||
</Text>
|
||||
.
|
||||
@@ -100,7 +100,7 @@ export function PostInteractionSettingsControlledDialog({
|
||||
export function Header() {
|
||||
return (
|
||||
<View style={[a.gap_md, a.pb_sm]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||
<Trans>Post interaction settings</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, a.pb_xs]}>
|
||||
@@ -329,7 +329,7 @@ export function PostInteractionSettingsForm({
|
||||
<View style={[a.flex_1, a.gap_md]}>
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.font_bold, a.text_lg]}>
|
||||
<Text style={[a.font_semi_bold, a.text_lg]}>
|
||||
<Trans>Quote settings</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -385,7 +385,7 @@ export function PostInteractionSettingsForm({
|
||||
opacity: replySettingsDisabled ? 0.3 : 1,
|
||||
},
|
||||
]}>
|
||||
<Text style={[a.font_bold, a.text_lg]}>
|
||||
<Text style={[a.font_semi_bold, a.text_lg]}>
|
||||
<Trans>Reply settings</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -535,7 +535,9 @@ function Selectable({
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
<Text style={[a.text_sm, isSelected && a.font_bold]}>{label}</Text>
|
||||
<Text style={[a.text_sm, isSelected && a.font_semi_bold]}>
|
||||
{label}
|
||||
</Text>
|
||||
{isSelected ? (
|
||||
<Check size="sm" fill={t.palette.primary_500} />
|
||||
) : (
|
||||
|
||||
@@ -284,7 +284,7 @@ export function SearchablePeopleList({
|
||||
style={[
|
||||
a.z_10,
|
||||
a.text_lg,
|
||||
a.font_heavy,
|
||||
a.font_bold,
|
||||
a.leading_tight,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
|
||||
@@ -172,7 +172,7 @@ function StarterPackList({
|
||||
isWeb ? a.mb_2xl : a.my_lg,
|
||||
a.align_center,
|
||||
]}>
|
||||
<Text style={[a.text_lg, a.font_bold]}>
|
||||
<Text style={[a.text_lg, a.font_semi_bold]}>
|
||||
<Trans>Add to starter packs</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
@@ -189,7 +189,7 @@ function StarterPackList({
|
||||
<>
|
||||
<View
|
||||
style={[a.flex_row, a.justify_between, a.align_center, a.py_md]}>
|
||||
<Text style={[a.text_md, a.font_bold]}>
|
||||
<Text style={[a.text_md, a.font_semi_bold]}>
|
||||
<Trans>New starter pack</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
@@ -331,7 +331,7 @@ function StarterPackItem({
|
||||
return (
|
||||
<View style={[a.flex_row, a.justify_between, a.align_center, a.py_md]}>
|
||||
<View>
|
||||
<Text emoji style={[a.text_md, a.font_bold]} numberOfLines={1}>
|
||||
<Text emoji style={[a.text_md, a.font_semi_bold]} numberOfLines={1}>
|
||||
{record.name}
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export function SwitchAccountDialog({
|
||||
<Dialog.Handle />
|
||||
<Dialog.ScrollableInner label={_(msg`Switch Account`)}>
|
||||
<View style={[a.gap_lg]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||
<Trans>Switch Account</Trans>
|
||||
</Text>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user